diff --git a/README.md b/README.md index 162fc8441c..1d4b14a734 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,8 @@ CURVE是网易自主设计研发的高性能、高可用、高可靠分布式存 - [Snapshotcloneserver](docs/cn/snapshotcloneserver.md) - [CURVE质量体系介绍](docs/cn/quality.md) - [CURVE监控体系介绍](docs/cn/monitor.md) + - [Client](docs/cn/curve-client.md) + - [Client Python API](docs/cn/curve-client-python-api.md) - CURVE上层应用 - [对接k8s文档](docs/cn/k8s_csi_interface.md) diff --git a/docs/cn/curve-client-python-api.md b/docs/cn/curve-client-python-api.md new file mode 100644 index 0000000000..b730ad24a2 --- /dev/null +++ b/docs/cn/curve-client-python-api.md @@ -0,0 +1,304 @@ +#### 获取一个与curve集群交互的CBDClient对象 + +```python +import curvefs + +# 获取一个与后端集群交互的CBDClient对象,不同的CBDClient对象可以与不同的集群建立连接 +cbd1 = curvefs.CBDClient() +cbd2 = curvefs.CBDClient() +``` + +#### 初始化CBDClient + +```python +import curvefs +cbd = curvefs.CBDClient() + +# 参数:curve-client配置文件的绝对路径 +# 返回值:返回0表示初始化成功,-1表示初始化失败 +cbd.Init("/etc/curve/client.conf") +``` + +#### 创建文件 + +```python +import curvefs +cbd = curvefs.CBDClient() +cbd.Init("/etc/curve/client.conf") # 后续示例省略初始化过程 + +# 参数:三个参数分别为 +# 文件全路径 +# 文件所属用户信息 +# 文件大小 +# 返回值:返回0表示创建成功,否则返回错误码 + +# 首先初始化user信息(curvefs的控制面接口都有用户信息验证,都需要传入用户信息) +user = curvefs.UserInfo_t() +user.owner = "curve" +user.password = "" # 密码为空时,可以省略 + +# 调用Create接口创建文件 +cbd.Create("/curve", user, 10*1024*1024*1024) + +# UserInfo定义如下 +typedef struct UserInfo { + char owner[256]; # 用户名 + char password[256]; # 用户密码 +} UserInfo_t; +``` + +#### 查看文件信息 + +```python +# 参数:三个参数分别为 +# 文件名 +# 用户信息 +# 文件信息[出参] +# 返回值:返回0表示获取成功,否则返回错误码 + +# 构造user信息 +user = curvefs.UserInfo_t() +user.owner = "curve" + +# 构造file信息 +finfo = curvefs.FileInfo_t() + +# 查看文件信息 +cbd.StatFile("/curve", user, finfo) +print finfo.filetype +print finfo.length +print finfo.ctime + +# FileInfo定义如下 +typedef struct FileInfo { + uint64_t id; + uint64_t parentid; + int filetype; # 卷类型 + uint64_t length; # 卷大小 + uint64_t ctime; # 卷创建时间 + char filename[256]; # 卷名 + char owner[256]; # 卷所属用户 + int fileStatus; # 卷状态 +} FileInfo_t; + +# 文件状态 +#define CURVE_FILE_CREATED 0 +#define CURVE_FILE_DELETING 1 +#define CURVE_FILE_CLONING 2 +#define CURVE_FILE_CLONEMETAINSTALLED 3 +#define CURVE_FILE_CLONED 4 +#define CURVE_FILE_BEINGCLONED 5 +``` + +#### 扩容文件 + +```python +# 参数:三个参数分别为 +# 文件名 +# 用户信息 +# 扩容后文件大小 +# 返回值:返回0表示扩容成功,否则返回错误码 + +# 构造user信息 +user = curvefs.UserInfo_t() +user.owner = "curve" + +# 扩容 +cbd.Extend("/curve", user, 20*1024*1024*1024) + +# 查看扩容后的文件信息 +finfo = curvefs.FileInfo_t() +cbd.StatFile("/curve", user, finfo) +print finfo.length +``` + +#### 打开/关闭文件 + +```python +# 打开文件 +# 参数:两个参数分别为 +# 文件名 +# 用户信息 +# 返回值:打开成功返回文件fd,否则返回错误码 + +# 构造user信息 +user = curvefs.UserInfo_t() +user.owner = "user1" + +# 打开文件,返回fd +fd = cbd.Open("/tmp1", user) + +# 关闭文件 +# 参数:打开文件时返回的fd +# 返回值:关闭成功返回0,否则返回错误码 +cbd.Close(fd) +``` + +#### 读写文件 + +```python +# 写文件 +# 参数:四个参数分别为 +# 文件fd +# 待写入数据 +# 偏移量 +# 写入数据长度 +# 返回值:写入成功返回写入字节数,否则返回错误码 + +# 构造user信息 +user = curvefs.UserInfo_t() +user.owner = "user1" + +# 打开文件,返回fd +fd = cbd.Open("/tmp1", user) + +# 写文件(目前读写都需要4k对齐) +cbd.Write(fd, "aaaaaaaa"*512, 0, 4096) +cbd.Write(fd, "bbbbbbbb"*512, 4096, 4096) + +# 读文件 +# 参数:四个参数分别为 +# 文件fd +# 空字符串 +# 偏移量 +# 读取数据长度 +# 返回值:读取成功返回读取数据,否则返回错误码 + +# 读取的内容通过返回值返回,buf在此没有意义,可以传入一个空串 +cbd.Read(fd,"", 0, 4096) + +# 关闭文件 +cbd.Close(fd) +``` + +备注:当前python api接口,不支持异步读写 + +#### 删除文件 + +```python +# 参数:两个参数分别为 +# 文件名 +# 用户信息 +# 返回值:删除成功返回0,否则返回错误码 + +# 构造user信息 +user = curvefs.UserInfo_t() +user.owner = "curve" + +# 删除文件 +cbd.Unlink("/curve", user) +``` + +#### 重命名文件 + +```python +# 参数:三个参数分别为 +# 用户信息 +# 旧文件名 +# 新文件名 +# 返回值:成功返回0,否则返回错误码 + +# 构造user信息 +user = curvefs.UserInfo_t() +user.owner = "curve" + +# 重命名 +cbd.Rename(user, "/curve", "/curve-new") +``` + +#### 创建目录 + +```python +# 参数:两个参数分别为 +# 目录路径 +# 用户信息 +# 返回值:成功返回0,否则返回错误码 + +# 构造user信息 +user = curvefs.UserInfo_t() +user.owner = "curve" +# 创建目录 +cbd.Mkdir("/curvedir", user) +``` + +#### 删除目录 + +```python +# 参数:两个参数分别为 +# 目录路径 +# 用户信息 +# 返回值:成功返回0,否则返回错误码 + +# 构造user信息 +user = curvefs.UserInfo_t() +user.owner = "curve" + +# 删除目录 +cbd.Rmdir("/curvedir", user) +``` + +#### 获取目录下的文件 + +```python +# 参数:两个参数分别为 +# 目录路径 +# 用户信息 +# 返回值:当前目录下的文件列表(只包括文件名) +files = cbd.Listdir("/test", user) +for f in files: + print f +``` + +#### 获取集群ID + +```python +# 通过返回值判断是否获取成功 +# 成功返回集群id字符串 +# 失败返回空字符串 +clusterId = cbd.GetClusterId() +print clusterId +# c355675a-f4d2-4729-b80a-5a7bcc749d1c +``` + +#### 清理CBDClient对象 + +```python +cbd.UnInit() +``` + +### 错误码 + +| Code | Message | 描述 | +| :--: | :-------------------------- | ------------------------ | +| 0 | OK | 操作成功 | +| -1 | EXISTS | 文件或目录已存存在 | +| -2 | FAILED | 操作失败 | +| -3 | DISABLEDIO | 禁止IO | +| -4 | AUTHFAIL | 认证失败 | +| -5 | DELETING | 正在删除 | +| -6 | NOTEXIST | 文件不存在 | +| -7 | UNDER_SNAPSHOT | 快照中 | +| -8 | NOT_UNDERSNAPSHOT | 非快照状态 | +| -9 | DELETE_ERROR | 删除错误 | +| -10 | NOT_ALLOCATE | Segment未分配 | +| -11 | NOT_SUPPORT | 操作不支持 | +| -12 | NOT_EMPTY | 目录非空 | +| -13 | NO_SHRINK_BIGGER_FILE | 禁止缩容 | +| -14 | SESSION_NOTEXISTS | Session不存在 | +| -15 | FILE_OCCUPIED | 文件被占用 | +| -16 | PARAM_ERROR | 参数错误 | +| -17 | INTERNAL_ERROR | 内部错误 | +| -18 | CRC_ERROR | CRC检查错误 | +| -19 | INVALID_REQUEST | 请求参数存在异常 | +| -20 | DISK_FAIL | 磁盘异常 | +| -21 | NO_SPACE | 空间不足 | +| -22 | NOT_ALIGNED | IO未对齐 | +| -23 | BAD_FD | 文件正在被关闭,fd不可用 | +| -24 | LENGTH_NOT_SUPPORT | 文件长度不满足要求 | +| -25 | SESSION_NOT_EXIST | Session不存在(与-14重复) | +| -26 | STATUS_NOT_MATCH | 状态异常 | +| -27 | DELETE_BEING_CLONED | 删除文件正在被克隆 | +| -28 | CLIENT_NOT_SUPPORT_SNAPSHOT | Client版本不支持快照 | +| -29 | SNAPSHOT_FROZEN | Snapshot功能禁用中 | +| -100 | UNKNOWN | 未知错误 | + diff --git a/docs/cn/curve-client.md b/docs/cn/curve-client.md new file mode 100644 index 0000000000..476a293286 --- /dev/null +++ b/docs/cn/curve-client.md @@ -0,0 +1,139 @@ +## 1 概要 + +curve作为中心化的分布式存储系统主要组件分为前端驱动Client模块、底层存储Chunkserver模块、元数据服务器MDS模块,以及快照系统。 + +所谓前端驱动client模块即为curve client,它以动态库libcurve的方式向上提供服务,外部服务进程如Qemu、Curve-Nbd通过链接该库来使用curve提供的存储服务。(热升级后,curve client不再直接对接Qemu和Curve-Nbd等上层服务。) + +所以,curve client可以理解为用户行为入口。 + +## 2 curve client功能点 + +Curve Client存在的目的就是通过提供接口的方式向上提供用户数据存储服务,curve系统以分布式文件系统作为底层存储向上提供块存储服务。从用户角度来看,curve为其提供的就是一个可以进行随机读写的块设备,这个块设备对应的就是底层curvefs的一个文件。 + +这个curvefs文件的底层存储方式是分布在多个底层集群节点上的,举个例子: + +用户创建一个1T的块设备,这个块设备在底层curvefs上就是一个1T大小的文件。如下图: + +> 注意:这个所谓的1T大小的文件只是一个逻辑概念,这个文件只有curve client和mds两个模块感知,底层存储集群chunksevrer节点不感知这个文件。 + +curve-file + +用户一侧创建一个1T的块设备,对其而言是一个连续的0-1T的连续地址空间,其可以在这个空间内进行随机读写。但是从curve角度来看,这个1T大小的块设备就是一个由一批chunk组成的地址空间,每个chunk有唯一的ID编号,这些chunk会对应到底层chunkserver上一段真实的物理存储空间。 + +curve client与底层存储集群以rpc方式通信,因此curve client的功能点也就呼之欲出。 + +### 2.1 提供接口 + +用户创建块设备及读写块设备都是通过curve client提供的接口,这些接口包括创建块设备、打开块设备等等,以及读写该块设备,为了追求极致的性能,curve向上提供异步读写接口。 + +### 2.2 IO拆分 + +从上图可以看到,用户的块设备其实在底层物理存储中是以chunk为单位打散在底层多个存储节点上的,这一方面可以提供处理的并发度,同时也能够提高数据的可靠性。 + +用户写数据到curve client,client能够拿到用户IO的offset、length、data,这个offset + length在curve的文件上有可能会覆盖多个chunk,因为底层chunk是分布在不同的chunkserver上的,所以curve client需要把用户对块设备的的IO请求,转换为对底层不同chunkserver的chunk请求,并分别下发。 + +### 2.3 IO跟踪 + +IO拆分会将用户的IO根据offset和length拆分,比如一个用户的IO `offset = 0, length = 2*chunksize`,那么这个请求到client后,会被至少拆分成两个请求,因为这个IO正好横跨两个chunk,这两个chunk分布在不同的chunkserver上,因此一个大IO被拆分成了两个小IO,由于client的IO接口是异步的,而且client写chunk也是异步的,所以需要跟踪一个大IO的每个子IO,记录其返回状态,只有所有的子IO都返回这个IO才能算完成,然后才能向上返回给用户。 + +### 2.4 元数据获取及缓存 + +curve client本身是一个无状态的组件,不会保存文件的任何元数据信息,上面提到的IO拆分,是需要依据的,client需要知道chunk对应到具体chunkserver上的某一个物理chunk。这些信息都是通过mds获取的,mds会持久化文件的路由信息,当用户写某个块设备时,client需要从mds获取这个文件的元数据信息,然后保存在内存,供后续使用。 + +一个用户IO下发需要的元信息: + +1. 上层文件与底层chunk的对应关系,即上层文件的逻辑chunk,与底层物理chunk的对应关系 +2. 底层物理chunk所属的raft group +3. raft group所在的chunkserver列表 + + +### 2.5 mds failover 支持 + +mds提供元数据存储服务,为了提高可用性,mds以集群方式服务,同一时刻只有一个mds提供服务,其他mds节点通过etcd进行监听,随时准备替代leader,成为新leader提供服务。 + +curve client在IO下发及控制面调用时会与mds进行通信,通信以RPC方式进行,通信时需要指定对应的mds的地址,所以如果mds集群中服务节点切换时还需要client进行地址切换。 + +### 2.6 Chunkserver failover 支持 + +与mds高可用设计类似,chunkserver存储节点通过使用raft算法,实现多节点数据复制及高可用保证,在curve client一侧下发请求时需要先获取当前raft group的leader,然后将请求发往leader所在的节点。 + +### 2.7 IO流控 + +所谓IO流控就是curve client在感知底层集群负载过高时进行一定的流量控制,防止底层负载恶性循环。从本质上讲,client一侧的IO流控只是一种缓解方式,并非一种根本解决方案。 + +## 3 curve client架构 + +上节大致描述了client的功能点,本节分析client的架构,主要从模块架构和线程模型两方面。 + +### 3.1 模块架构 + +curve client的基本模块架构如下,其中client qos目前暂未实现 + +curve-client-arch.png + +### 3.2 线程模型 + +curve-client-thread-model.png + +这里以异步IO请求为例,client一侧共涉及到4类线程: + +1. 用户线程:用户发起异步IO请求,会调用client的AioWrite接口,AioWrite会将用户请求封装成task放入任务队列中,此时用户调用就返回了。 +2. 任务队列线程:任务队列线程会监听任务队列,只要有任务进来,就从队列中取出,进行IO拆分,拆分成的Sub-IO请求会放入IO分发队列中。 +3. IO分发线程:根据Sub-IO的信息,调用RPC发送异步请求到对应的chunkserver节点。 +4. BRPC线程:RPC的发送以及请求的返回都是在BRPC线程中处理的,rpc返回后还会调用异步请求的Closure回调。如果所有的Sub-IO都返回成功,则会找到对应的上层IO请求,并调用异步IO的回调(回调的操作也是在BRPC线程中执行)。 + +## 4 关键点 + +### 4.1 MetaCache + +IO拆分过程中,需要依赖文件的元数据信息,这些元数据信息在client一侧并不持久化,只是在需要的时候从mds获取。 + +为了避免频繁与mds通信,client会对获取到的元数据进行缓存。 + +缓存的元数据在2.4节已经提到,主要是以下几点: + +1. file chunk -> chunk id:上层文件与底层chunk的对应关系,即上层文件的逻辑chunk,与底层物理chunk的对应关系 +2. chunk id -> copyset id(raft group):底层物理chunk所属的raft group +3. copyset id -> serverlist:raft group所在的chunkserver列表 + +#### 4.1.1 元数据缓存更新 + +通常文件一旦分配了对应的chunkid和copysetid,这些信息就不会变化。 + +上述三种元数据会出现更新的就是copysetid到serverlist的映射关系。因为底层如果出现节点宕机、负载不均衡等情况会触发配置变更,这种情况下会导致copyset的chunkserver列表发生变更。 + +client触发元数据更新都是通过RPC失败。 + +更新的元数据信息包括 + +1. raft group的leader信息 + + client发送请求是发送到raft group leader节点,在每次发送请求之前都会获取当前raft group的leader信息(metacache进行缓存)。 + + 请求发送到chunkserver之后,如果这个chunkserver已经不是leader了,那么**client会发起GetLeader请求到当前raft group里的其他chunkserver节点**。因为如果底层重新选举出了leader,节点肯定知道当前leader是谁,然后会把这个信息返回给client,client更新完metacache中的leader信息之后将请求转发到新leader。 + +2. raft group的peer信息 + + 极端状况下,某个raft group因为底层节点宕机等原因,其group peer全部变更了。这种情况下,通过GetLeader是无法获取真正的leader信息,所以在重试多次之后client会向mds拉取当前raft group的最新信息。 + +### 4.2 IO请求重试 + +IO分发线程获取IO任务后会发送异步RPC请求,RPC请求返回后,会调用异步请求的回调。在回调中,会根据RPC请求的返回值,判断请求成功与否。如果请求失败,而且需要进行重试,那么会在回调中再次发起rpc请求。 + +> 对于读写请求,重试次数设置很大,目的在于让IO尽可能成功返回。因为对于块设备而言,如果用户写入返回错误会造成上层认为块设备错误,硬盘损坏。 + +RPC请求重试时,会进行预处理,分为两种情况: + +1. chunkserver overload + + 这种情况下,对应的RPC Response中返回的错误码是OVERLOAD,说明底层chunkserver目前负载较高。 + + 此时,如果直接发送重试请求,大概率的情况下还是会返回OVERLOAD。所以这种情况下,应该让client在下发重试请求前,一段时间的sleep。 + + 在client一侧,针对这种情况,加入了睡眠重试时间指数退避,并加入了睡眠时间随机抖动,避免大量请求同时被返回OVERLOAD后,又同时重试。 + +2. RPC Timeout + + 出现这种情况的原因比较多,但是常见的原因是,底层chunkserver处理请求较多,压力较大,导致请求的返回时间超过了预期的RPC超时时间。 + + 这种情况下,如果重试请求的RPC超时时间不发生变化,那么很可能会重复上述流程,导致用户IO请求迟迟未能返回。所以,在这种情况下,重试请求会将RPC超时时间进行增加。 \ No newline at end of file diff --git a/docs/images/curve-client-arch.png b/docs/images/curve-client-arch.png new file mode 100644 index 0000000000..9ce137922b Binary files /dev/null and b/docs/images/curve-client-arch.png differ diff --git a/docs/images/curve-client-thread-model.png b/docs/images/curve-client-thread-model.png new file mode 100644 index 0000000000..8e5b6277a5 Binary files /dev/null and b/docs/images/curve-client-thread-model.png differ diff --git a/docs/images/curve-file.png b/docs/images/curve-file.png new file mode 100644 index 0000000000..99dcda4132 Binary files /dev/null and b/docs/images/curve-file.png differ