原文:http://www.linuxidc.com/Linux/2013-08/89105.htm

1. GlusterFS概述

GlusterFS是一个开源的分布式文件系统,具有强大的Scale-Out横向扩展能力,通过扩展能够支持数PB存储容量和处理数千客户端。GlusterFS借助TCP/IP或InfiniBand RDMA网络将物理分布的存储资源聚集在一起,使用单一全局命名空间来管理数据。GlusterFS基于可堆叠的用户空间设计,可为各种不同的数据负载提供优异的性能。

GlusterFS支持运行在任何标准IP网络上标准应用程序的标准客户端,用户可以在全局统一的命名空间中使用Glusterfs/NFS/CIFS等标准协议来访问应用数据。GlusterFS使得用户可摆脱原有的独立、高成本的封闭存储系统,能够利用普通廉价的存储设备来部署可集中管理、横向扩展、虚拟化的存储池,存储容量可扩展至TB/PB级。Glusterfs的深入剖析请参考”GlusterFS集群文件系统研究”一文 http://www.linuxidc.com/Linux/2011-12/50579.htm 。GlusterFS主要特征如下:

1) 扩展性和高性能
2) 高可用性
3) 全局统一命名空间
4) 弹性哈希算法
5) 弹性卷管理
6) 基于标准协议

2. Xlator工作原理

GlusterFS采用模块化、堆栈式的架构,可通过灵活的配置支持高度定制化的应用环境,比如大文件存储、海量小文件存储、云存储、多传输协议应用等。每个功能以模块形式实现,然后以积木方式进行简单的组合,即可实现复杂的功能。比如,Replicate模块可实现RAID1,Stripe模块可实现RAID0,通过两者的组合可实现RAID10和RAID01,同时获得高性能和高可靠性。

GlusterFS堆栈式设计思想源自GNU/Hurd微内核操作系统,具有很强的系统扩展能力,系统设计实现复杂性降低很多,基本功能模块的堆栈式组合就可以实现强大的功能。基本模块称为Translator,它是GlusterFS提供的一种强大文件系统功能扩展机制,借助这种良好定义的接口可以高效简便地扩展文件系统的功能。

GlusterFS中所有的功能都通过Translator机制实现,服务端与客户端模块接口是兼容的,同一个translator可同时在两边加载。每个translator都是SO动态库,运行时根据配置动态加载。每个模块实现特定基本功能,比如Cluster, Storage, Performance, Protocol,Features等,基本简单的模块可以通过堆栈式的组合来实现复杂的功能,Translator可以把对外部系统的访问转换成目标系统的适当调用。大部分模块都运行在客户端,比如合成器、I/O调度器和性能优化等,服务端相对简单许多。客户端和存储服务器均有自己的存储栈,构成了一棵Translator功能树,应用了若干模块。模块化和堆栈式的架构设计,极大降低了系统设计复杂性,简化了系统的实现、升级以及系统维护。


Gluster卷Translator栈图

GlusterFS概念中,由一系列translator构成的完整功能栈称之为Volume(如上图所示),分配给一个volume的本地文件系统称为brick,被至少一个translator处理过的brick称为subvolume。FUSE模块位于客户端,POSIX模块位于服务器端,它们通常是volume中首个或最后一个模块,依赖于访问数据流的方向。中间部分会再加入其他功能的模块,构成一个完整的volume,这些模块通过一张图(graph)有机结合在一起。这是一种多层设计,运行时通过有序地向上或向下调用相邻模块接口来传递消息,调用关系由每个模块根据自身功能和translator图来决定。由translator实现的卷的完整数据流,如下图所示。


GlusterFS数据流

3. Xlator结构和相关API

Xlator是高度模块化的组件,具有良好定义的内部结构,包括结构体和接口函数原型定义。因此,要实现一个xlator,必须严格按照定义来实现,具体讲就是要实现xlator.h中定义的xlator_fops、xlator_cbks、init、fini、volume_options等结构体中的参数和函数指针,描述如下:

struct xlator_fops {
        fop_lookup_t        lookup;
        fop_stat_t          stat;
        fop_fstat_t          fstat;
        fop_truncate_t      truncate;
        fop_ftruncate_t      ftruncate;
        fop_access_t        access;
        fop_readlink_t      readlink;
        fop_mknod_t          mknod;
        fop_mkdir_t          mkdir;
        fop_unlink_t        unlink;
        fop_rmdir_t          rmdir;
        fop_symlink_t        symlink;
        fop_rename_t        rename;
        fop_link_t          link;
        fop_create_t        create;
        fop_open_t          open;
        fop_readv_t          readv;
        fop_writev_t        writev;
        fop_flush_t          flush;
        fop_fsync_t          fsync;
        fop_opendir_t        opendir;
        fop_readdir_t        readdir;
        fop_readdirp_t      readdirp;
        fop_fsyncdir_t      fsyncdir;
        fop_statfs_t        statfs;
        fop_setxattr_t      setxattr;
        fop_getxattr_t      getxattr;
        fop_fsetxattr_t      fsetxattr;
        fop_fgetxattr_t      fgetxattr;
        fop_removexattr_t    removexattr;
        fop_lk_t            lk;
        fop_inodelk_t        inodelk;
        fop_finodelk_t      finodelk;
        fop_entrylk_t        entrylk;
        fop_fentrylk_t      fentrylk;
        fop_rchecksum_t      rchecksum;
        fop_xattrop_t        xattrop;
        fop_fxattrop_t      fxattrop;
        fop_setattr_t        setattr;
        fop_fsetattr_t      fsetattr;
        fop_getspec_t        getspec;

/* these entries are used for a typechecking hack in STACK_WIND _only_ */
        fop_lookup_cbk_t        lookup_cbk;
        fop_stat_cbk_t          stat_cbk;
        fop_fstat_cbk_t          fstat_cbk;
        fop_truncate_cbk_t      truncate_cbk;
        fop_ftruncate_cbk_t      ftruncate_cbk;
        fop_access_cbk_t        access_cbk;
        fop_readlink_cbk_t      readlink_cbk;
        fop_mknod_cbk_t          mknod_cbk;
        fop_mkdir_cbk_t          mkdir_cbk;
        fop_unlink_cbk_t        unlink_cbk;
        fop_rmdir_cbk_t          rmdir_cbk;
        fop_symlink_cbk_t        symlink_cbk;
        fop_rename_cbk_t        rename_cbk;
        fop_link_cbk_t          link_cbk;
        fop_create_cbk_t        create_cbk;
        fop_open_cbk_t          open_cbk;
        fop_readv_cbk_t          readv_cbk;
        fop_writev_cbk_t        writev_cbk;
        fop_flush_cbk_t          flush_cbk;
        fop_fsync_cbk_t          fsync_cbk;
        fop_opendir_cbk_t        opendir_cbk;
        fop_readdir_cbk_t        readdir_cbk;
        fop_readdirp_cbk_t      readdirp_cbk;
        fop_fsyncdir_cbk_t      fsyncdir_cbk;
        fop_statfs_cbk_t        statfs_cbk;
        fop_setxattr_cbk_t      setxattr_cbk;
        fop_getxattr_cbk_t      getxattr_cbk;
        fop_fsetxattr_cbk_t      fsetxattr_cbk;
        fop_fgetxattr_cbk_t      fgetxattr_cbk;
        fop_removexattr_cbk_t    removexattr_cbk;
        fop_lk_cbk_t            lk_cbk;
        fop_inodelk_cbk_t        inodelk_cbk;
        fop_finodelk_cbk_t      finodelk_cbk;
        fop_entrylk_cbk_t        entrylk_cbk;
        fop_fentrylk_cbk_t      fentrylk_cbk;
        fop_rchecksum_cbk_t      rchecksum_cbk;
        fop_xattrop_cbk_t        xattrop_cbk;
        fop_fxattrop_cbk_t      fxattrop_cbk;
        fop_setattr_cbk_t        setattr_cbk;
        fop_fsetattr_cbk_t      fsetattr_cbk;
        fop_getspec_cbk_t        getspec_cbk;
};

struct xlator_cbks {
        cbk_forget_t    forget;
        cbk_release_t  release;
        cbk_release_t  releasedir;
};

void            (*fini) (xlator_t *this);
int32_t          (*init) (xlator_t *this);

typedef struct volume_options {
        char                *key[ZR_VOLUME_MAX_NUM_KEY];
        /* different key, same meaning */
        volume_option_type_t type;
        int64_t              min;  /* 0 means no range */
        int64_t              max;  /* 0 means no range */
        char                *value[ZR_OPTION_MAX_ARRAY_SIZE];
        /* If specified, will check for one of
          the value from this array */
        char                *default_value;
        char                *description; /* about the key */
} volume_option_t;

xlator_fops和xlator_cbks结构体中的函数指针在xlator.h中都有严格的明确定义,实现时必须完全遵从。其中,xlator_fops是Linux中file_operations, inode_operations和super_operatioins的组合。另外,以上结构体和函数指针名分别确定为fops, cbks, init, fini, options,不可更改。因为xlator最终以SO动态库形式提供给glusterfs主体程序使用,需要使用统一确定的名称来加载和定位xlator中函数指针和变量。Init, fini分别用于xlator加载和卸载时的处理工作,这个对于每个xlator的个性化私有数据处理非常有用。如果xlator模板提供的接口和参数无法满足需求,可以有效利用这两个接口进行处理。值得一提的是,xlator并不一定要实现以上全部的函数指针和变量,可以仅实现特定相关的部分,其它的部分会在运行时自动填入默认的值,并直接传递给下一个translator, 同时指定回调函数,回调函数传回之前translator的结果。

Translator采用异步和回调函数的实现机制,这意味着处理特定请求的代码必须被分为两个部分:调用函数和回调函数。一个xlator的函数调用下一个translator的函数,然后无阻塞的返回。当调用下一个translator的函数时,回调函数可能会立即被调用,也可能稍后在一个不同的线程上被调用。在两种情况下,回调函数都不会像同步函数那样获取其上下文。GlusterFS提供了几种方式用于在调用函数及其回调函数间保存和传递上下文,但是必须xlator自行处理而不能完全依赖协议栈。

Translator的回调机制主要采用了STACK_WIND和STACK_UNWIND。当xlator fops某个函数被调用,表示接受到一个请求,使用frame stack来表示。Fops函数中执行相应操作,然后可把该请求使用STACK_WIND传递给下一个或多个translator。当完成一个请求而不再需要调用下一个translator,或者当任务完成从回调函数中回到上一个translator时需要调用STACK_UNWIND。实际上,最好使用STACK_UNWIND_STRICT,它可以用来指定那类请求你已经完成了。相关宏在stack.h中定义,原型如下:

#define STACK_WIND(frame, rfn, obj, fn, params ...)
#define STACK_WIND_COOKIE(frame, rfn, cky, obj, fn,params ...)
#define STACK_UNWIND(frame, params ...)
#define STACK_UNWIND_STRICT(op, frame, params ...)

其中用到的参数如下: 
Frame:stack frame表示请求
Rfn::回调函数,当下一个translator完成时会调用该函数
Obj::正在控制的translator对象
Fn:从下一个translator的fops table中指定要调用的translator函数
Params:任何其他被调用函数的参数(比如,inodes, fd, offset, data buffer)
Cky:cookie,这是一个opaque指针
Op:操作类型,用来检查附加的参数符合函数的期望

每个translator-stack frame都有一个local指针,用来储存该translator特定的上下文,这是在调用和回调函数间存储上下文的主要机制。当stack销毁时,每个frame的local如果不为NULL都会被传递给GF_FREE,但是不会执行其他的清理工作。如果local结构体包含指针或引用其他对象,就需要仔细处理这些。因此比较理想的情况是,内存和其它资源能在stack被销毁前被释放,不要完全依赖自动的GFS_GFREE。最为妥当的做法是定义特定translator的销毁函数,并在STACK_UNWIND返回前手工调用。

Xlator大部分的调用函数和回调函数以文件描述符(fd_t)或inode(inode_t)作为参数。通常translator需要存储一些自有的上下文,这些上下文独立于单个请求的生命周期。比如,DHT存储目录对应的布局映射(layout map)和某inode最后可知的位置。Glusterfs提供了一系列的函数用于存储此类上下文。在每种情况下,第二个参数是一个指向translator对象的指针,需要存储的数据与其相关,存储的值是一个unsigned的64位整数。这些函数返回0代表成功,在_get和_del函数中使用引用参数而不是返回值。

inode_ctx_put (inode, xlator, value)
inode_ctx_get (inode, xlator, &value)
inode_ctx_del (inode, xlator, &value)
fd_ctx_set (fd, xlator, value)
fd_ctx_get (fd, xlator, &value)
fd_ctx_del (fd, xlator, &value)

传递给调用函数和回调函数的inode_t或fd_t指针只是借来(borrowed)引用。如果希望该对象稍后还存在,最好调用inode_ref或fd_ref增加一个持久引用,并且当引用不再需要时调用inode_unref或fd_unref。 
另外一个常用的类型是dict_t,它是一个通用的排序字典或hash-map的数据结构,可用来存放任意类型值并以字符串为键值。例如,存储的值可以是任意大小的有符号或无符号整数,字符串,或二进制。字符串和二进制需要被标记在不需要时由glusterfs函数释放,或由glibc释放或根本不释放。Dict_t*和*data_t对象都是引用计数的,只有当引用数为0时被释放。同inodes和文件描述符一样,如果希望通过参数接受的dict_t持久存在,必须调用_ref和_unref处理器生命周期。字典并不是仅用于调用和回调函数,也可用于传递不同的模块选项,包括translator初始化的选项。事实上,目前translator的init函数主要用于解析字典中的选项。向translatro中添加一个选项,需要在translator的options数组中添加一个实体。每一个选项可以是boolean,整数,字符串,路径,translator名称,和其它一些自定义类型。如果是字符串,可以指定有效值。解析后的选项和其它的信息可以存放在xlator_t结构体中的private内。

Translators中大部分的logging是通过gf_log函数实现,其参数包括字符串(通常是this->name)、log等级、格式化串以及格式化的其他参数。日志等级分为GF_LOG_ERROR, GF_LOG_WARNING , GF_LOG_LOG和GF_LOG_DEBUG。Xlator可以封装gfs_log自定义相关宏,或采用现有等级,这样translator的日志在运行时就能被输出。设计xlator时,可以添加一个translator 日志等级选项,也可以实现一个特定的xattr调用用于传递新值。

4. 构造新Xlator

这里我们通过构造一个称为NULL的Xlator来梳理构造新xlator的基本方法。NULL Xlator本身不实现具体的功能,仅作为类似Proxy的中转,用于演示构造xlator的结构和方法。NULL Xlator实现包括四个文件,null.h, null.c, null_fops.h, null_fops.c,其中null_fops.h, null_fops.c与defaults.h, defaults.c完全相同。Null.h内容如下:

#ifndef __NULL_H__
#define __NULL_H__

#ifndef _CONFIG_H
#define _CONFIG_H
#include "config.h"
#endif
#include "mem-types.h"

typedef struct {
        xlator_t *target;
} null_private_t;

enum gf_null_mem_types_ {
        gf_null_mt_priv_t = gf_common_mt_end + 1,
        gf_null_mt_end
};

#endif /* __NULL_H__ */

其中,自定义了私有数据结构体null_private_t和内部数据类型。Null.c内容如下:

#include <ctype.h>
#include <sys/uio.h>

#ifndef _CONFIG_H
#define _CONFIG_H
#include "config.h"
#endif

#include "glusterfs.h"
#include "call-stub.h"
#include "defaults.h"
#include "logging.h"
#include "xlator.h"

#include "null.h"
#include "null_fops.h"
int32_t
init (xlator_t *this)
{
        xlator_t *tgt_xl = NULL;
        null_private_t *priv = NULL;

if (!this->children || this->children->next) {
                gf_log (this->name, GF_LOG_ERROR,
                        "FATAL: null should have exactly one child");
                return -1;
        }

priv = GF_CALLOC (1, sizeof (null_private_t), gf_null_mt_priv_t);
        if (!priv)
                return -1;

/* Init priv here */
        priv->target = tgt_xl;

gf_log (this->name, GF_LOG_DEBUG, "null xlator loaded");
        return 0;
}
void
fini (xlator_t *this)
{
        null_private_t *priv = this->private;

if (!priv)
                return;
        this->private = NULL;
        GF_FREE (priv);

return;
}
struct xlator_fops fops = {
        .lookup        = null_lookup,
        .stat          = null_stat,
        .fstat          = null_fstat,
        .truncate      = null_truncate,
        .ftruncate      = null_ftruncate,
        .access        = null_access,
        .readlink      = null_readlink,
        .mknod          = null_mknod,
        .mkdir          = null_mkdir,
        .unlink        = null_unlink,
        .rmdir          = null_rmdir,
        .symlink        = null_symlink,
        .rename        = null_rename,
        .link          = null_link,
        .create        = null_create,
        .open          = null_open,
        .readv          = null_readv,
        .writev        = null_writev,
        .flush          = null_flush,
        .fsync          = null_fsync,
        .opendir        = null_opendir,
        .readdir        = null_readdir,
        .readdirp      = null_readdirp,
        .fsyncdir      = null_fsyncdir,
        .statfs        = null_statfs,
        .setxattr      = null_setxattr,
        .getxattr      = null_getxattr,
        .fsetxattr      = null_fsetxattr,
        .fgetxattr      = null_fgetxattr,
        .removexattr    = null_removexattr,
        .lk            = null_lk,
        .inodelk        = null_inodelk,
        .finodelk      = null_finodelk,
        .entrylk        = null_entrylk,
        .fentrylk      = null_fentrylk,
        .rchecksum      = null_rchecksum,
        .xattrop        = null_xattrop,
        .fxattrop      = null_fxattrop,
        .setattr        = null_setattr,
        .fsetattr      = null_fsetattr,
        .getspec        = null_getspec,
};

struct xlator_cbks cbks = {
        .forget = null_forget,
        .release = null_release,
        .releasedir = null_releasedir,
};

struct volume_options options[] = {
        { .key  = {NULL} },
};

这其中主要实现了上面提到的init和fini函数,fops调用函数指针和cbks回调函数指针,以及卷参数选项options。一个新xlator的代码基本框架就是如此,这里因为没有实现具体功能,各种结构体、变量和函数实现都相对非常简单。如果要实现特定功能的xlator,可以以此为模块进行扩展。Glusterfs源码中xlator都是很好的例子,但有些很复杂,不适合初学者,可以先从简单的rot-13, read-only, bypass, negative-lookup等xlator开始研究,然后构造出自己所需功能的xlator。

5. 编译新Xlator

设计并编码实现新Xlator后,我们需要将其编译成SO形式的动态库,提供给Glusterfs使用。编译过程中,需要使用到glusterfs其他部分的相关代码,要求设置较为复杂的编译环境。这里我们编写了Makefile文件设置环境并进行编译,内容如下:

# Change these to match your source code.
TARGET  = null.so
OBJECTS = null.o null_fops.o

# Change these to match your environment.
GLFS_SRC  = /home/liuag/glusterfs-3.2.5
GLFS_VERS = 3.2.5
GLFS_LIB  = /opt/glusterfs/3.2.5/lib64/
HOST_OS  = GF_LINUX_HOST_OS

# You shouldn't need to change anything below here.

CFLAGS  = -fPIC -Wall -O2 \
          -DHAVE_CONFIG_H -D_FILE_OFFSET_BITS=64 -D_GNU_SOURCE -D$(HOST_OS) \
          -I$(GLFS_SRC) -I$(GLFS_SRC)/libglusterfs/src \
          -I$(GLFS_SRC)/contrib/uuid -I.
LDFLAGS = -shared -nostartfiles -L$(GLFS_LIB) -lglusterfs -lpthread

$(TARGET): $(OBJECTS)
        $(CC) $(CFLAGS) $(OBJECTS) $(LDFLAGS) -o $(TARGET)

install: $(TARGET)
        cp $(TARGET) $(GLFS_LIB)/glusterfs/$(GLFS_VERS)/xlator/null

clean:
        rm -f $(TARGET) $(OBJECTS)

将Makefile设置成与自己相匹配的编译环境,然后直接make即可生成null.so动态库,make install安装null新xlator。至此,一个新xlator就构造成功了,之后我们就可以使用它了。

6. 测试新Xlator

最激动人心的时刻终于到来了,现在我们可以通过修改volume配置文件来添加并测试新构造的null xlator。这个xlator可以工作在客户端或服务器端,可以修改相应卷配置文件或fuse卷配置文件来实现。下面以服务器端加载为例,局部卷配置修改如下:

volume test-posix
    type storage/posix
    option directory /data/test-1
end-volume

volume test-null
    type null/null
    subvolumes test-posix
end-volume

volume test-access-control
    type features/access-control
    subvolumes test-null
end-volume
… …

OK,现在重启glusterd服务,然后mount这个卷,就可以测试null xlator功能了。当然,你可能什么功能都测试不出来,因为我们什么功能都没实现。

转载于:https://www.cnblogs.com/langren1992/p/5316309.html

设计新Xlator扩展GlusterFS[转]相关推荐

  1. 设计一个可扩展的用户登录系统

    在Web系统中,用户登录是最基本的功能.如何设计一个可扩展的用户登录系统呢?本文结合实际案例对用户登录系统设计进行多维度的讲解,帮助各设计者在应用中将复杂变得简单. 来源:廖雪峰的官方网站,作者:廖雪 ...

  2. python 类的各类设计、子类扩展、变化及继承学习笔记

    python 类的各类设计.子类扩展.变化及继承,边学边练,边练边记录. print("=============类的设计=======================") cla ...

  3. 《.Net 软件设计新思维:像搭积木一样搭建软件》成书背后的故事

    本文节选自<.Net 软件设计新思维:像搭积木一样搭建软件 >一书 前   言 一个真实的故事 1996 年 10 月,一套经历了 2 年多时间开发出来的商务管理软件终于问世了,它带给我们 ...

  4. ML之FE:基于自定义数据集(银行客户信息贷款和赔偿)对比实现特征衍生(手动设计新特征、利用featuretools工具实现自动特征生成)

    ML之FE:基于自定义数据集(银行客户信息贷款和赔偿)对比实现特征衍生(手动设计新特征.利用featuretools工具实现自动特征生成) 目录 基于自定义数据集(银行客户信息贷款和赔偿)对比实现特征 ...

  5. C语言设计新思维分享

    没有任何套路,直接获取资源 C语言已经有几十年的历史了,经过长时间的发展和普及,C语言的应用场景也有了很大的变化,一些的老的观念已经不在适用,在这里给大家推荐一本讲C语言特别好的书,<C语言设计 ...

  6. 90后CV男神Workshop | 祥雨带你畅聊Model设计新视角

    2021年已经过半 各位CVer顶会论文准备的如何啦? 你的CV能力变强了么? 本次,我们重磅邀请"90后CV男神" 资深CVer-张祥雨博士 与各位同学一起畅聊 模型设计的一些新 ...

  7. atitit.jQuery Validate验证框架详解与ati Validate 设计新特性

    atitit.jQuery Validate验证框架详解与ati Validate 设计新特性 1. AtiValidate的目标1 2. 默的认校验规则1 2.1. 使用方式 1.metadata用 ...

  8. JDK8之新特性扩展篇

    转载自 JDK8之新特性扩展篇 BASE64 base64编码解码已经被加入到了jdk8中了. import java.nio.charset.StandardCharsets; import jav ...

  9. 地税局工资管理系统(论文+设计)新

    地税局工资管理系统(论文+设计)新强烈推荐!目录第一章系统需求分析1.1需求分析41.2需求识别41.3功能模块的划分51.4需求规定7第二章模块化的开发环境的简介2.1事件驱动的应用程序112.2V ...

最新文章

  1. php约瑟夫问题,php 解决约瑟夫问题实现方法
  2. JAVA 中BIO,NIO,AIO的理解
  3. matlab的PDE工具箱的简单使用
  4. 20200314 SQ Intel Realsense D435 USB 线长、转接线个数测试数据表
  5. P2082 区间覆盖(加强版)
  6. django分页的东西, 不详细, 但是也足够了。
  7. 互联网公司是如何腐败的?
  8. 181006扇贝有道每日一句
  9. 接口测试用例设计(详细干货)
  10. 详解如何用爬虫工具批量采集阿里巴巴批发网商品数据
  11. java获取大写字母_获取中文大写首字母java实现
  12. 关于sip软电话嵌入到网页web端的学习----第一天(1)(高手指点)
  13. uniapp 添加table不显示
  14. 1055 集体照(JAVA)
  15. 离散数学 --- 命题逻辑 --- 基本推理形式和自然演绎法推理
  16. Android的屏幕多样性支持
  17. 网站标题设计与搜索引擎
  18. 编程思想 之「泛型」
  19. [mysql安装教程]解决ERROR 2003 (HY000): Can‘t connect to MySQL server on ‘localhost:3306‘ (10061)问题
  20. 软件设计师-系统开发与软件工程

热门文章

  1. 配置apache支持cgi
  2. 打破你的认知,数字除以 0 一定会崩溃吗?
  3. JVM CPU Profiler技术原理及源码深度解析
  4. 一篇文章带你详解 HTTP 协议之报文首部及字段详解(中)
  5. Spring Cloud构建微服务架构(五)服务网关
  6. JVM:堆中对象的创建?定位?可达性?
  7. springboot java.util.NoSuchElementException: No value present 异常处理
  8. 4.MYSQL 三大范式+BC范式
  9. 建议收藏:服务器和存储技术知识
  10. 2020年全球十大数据中心趋势