- 存储
- 分布式文件系统
- 对象存储
- 云原生
【免费下载链接】cubefs
cloud-native distributed storage
本指南基于 CubeFS 官方开发文档,系统讲解 libsdk(用户态客户端库)的定位、构建方式、生命周期管理以及全部 C 语言接口的用法,并结合仓库源码(client/libsdk)说明其底层实现原理。读完本文,你将能够自行编译 libcfs.so,并在自己的 C/C++ 或支持 C ABI 的应用程序中完成客户端创建、配置、启动以及文件/目录的增删改查、读写与加锁等全套操作。
libsdk 是什么
libsdk 是 CubeFS 文件存储的用户态客户端库,用于应用程序与 CubeFS 分布式文件系统进行交互。它对外提供了一整套文件系统操作接口,包括文件和目录的创建、读取、写入和删除等。与传统的 FUSE 内核态挂载方式不同,应用程序通过 libsdk 可以直接连接到 CubeFS 存储集群,不经过内核态挂载与转发。
从源码结构看,libsdk 的 Go 实现位于 client/libsdk/libsdk.go,通过 cgo 的//export指令把 Go 函数导出为 C 可调用的符号;C 头文件为 client/libsdk/libcfs.h(由 cgo 生成的导出声明,被 cgo 注释中的结构体定义所支撑)。应用程序使用 libsdk 时,需要引用两个产物:
libcfs.h:C 接口声明头文件,位于 client/libsdk/libcfs.h;libcfs.so:动态链接库,通过执行make libsdk编译得到。
在 Makefile 中可以看到libsdk目标依赖libsdkpre,二者均通过 build/build.sh 执行构建:build_libsdkpre与build_libsdk实际执行CGO_ENABLED=1 go build -buildmode c-shared命令,将 client/libsdk/libsdk.go 编译为共享库,输出路径默认为${BuildBinPath}/libcfs.so。
libsdk 访问流程
libsdk 的使用遵循一个固定而简单的生命周期,共五步:
- 应用程序创建 client,对应
cfs_new_client; - 应用程序设置 client 配置参数,对应
cfs_set_client; - 应用程序启动 client,对应
cfs_start_client; - 应用程序调用对应的文件处理接口,对文件执行 open、read、write、close 等操作;
- 应用程序关闭 client,对应
cfs_close_client。
该流程与 client/libsdk/libsdk.go 中的客户端管理逻辑一一对应:cfs_new_client内部通过newClient()创建client结构并注册到全局clientManager的clientsmap 中;cfs_start_client调用client.start(),完成元数据封装(meta.MetaWrapper)与数据流客户端(stream.ExtentClient)的初始化;cfs_close_client依次关闭数据流客户端、元数据封装并移除客户端。
libsdk 的优点
libsdk 相比内核态挂载方式具有以下特点:
- 灵活性:可以使用各种编程语言和框架来开发,从而提供更大的灵活性和自由度;
- 独立性:在用户空间中运行,相对独立于操作系统内核,可以更容易地升级和调试客户端程序,而无需关注操作系统的限制和依赖;
- 易扩展:用户态客户端可以实现更高级的功能和协议,以满足特定的应用需求;
- 高性能:相比 FUSE 挂载,libsdk 减少了内核态的转发,有助于性能提高。
从源码看,这种“用户态直连”体现在 client/libsdk/libsdk.go 的start()方法中:libsdk 直接通过 Master SDK 拉取卷信息(loadConfFromMaster)、构造元数据与数据流客户端,所有 I/O 均在用户态完成,不经过 VFS/FUSE 内核路径。
libsdk 接口使用说明
以下接口按功能分组逐一说明。所有函数签名均可在 client/libsdk/libcfs.h 中查到;返回值中的statusOK为 0,其余错误状态均为负数(对应各 errno 取反,见 client/libsdk/libsdk.go)。
客户端生命周期接口
cfs_new_client
extern int64_t cfs_new_client();创建一个新的客户端。返回值是一个int64_t类型的值,即新创建客户端的 ID。
从实现看,cfs_new_client内部会预先把 fd 0、1、2 置位,避免与标准输入/输出/错误混淆;新客户端的 ID 由atomic.AddInt64自增分配(见 client/libsdk/libsdk.go)。
cfs_set_client
extern int cfs_set_client(int64_t id, char* key, char* val);设置客户端的配置项,参数说明如下:
id:客户端的 ID;key:配置项的键;val:配置项的值。
返回statusOK(成功)或statusEINVAL(非法参数)。配置示例:
cfs_set_client(client_id, "volName", "test"); cfs_set_client(client_id, "accessKey", "WgkHUb03XViuRkHX"); cfs_set_client(client_id, "secretKey", "oMQsYfGNcEdaUbbrw5D2GEp092RgNvQb"); cfs_set_client(client_id, "masterAddr", "172.16.1.101:17010"); cfs_set_client(client_id, "logDir", "/home/test_sdk/log"); cfs_set_client(client_id, "logLevel", "debug"); cfs_set_client(client_id, "enableSummary", "true");通过 client/libsdk/libsdk.go 的cfs_set_client实现,可以确认当前支持的配置键及其行为:
| 配置键 | 取值示例 | 说明 |
|---|---|---|
volName | test | 卷(Volume)名称,必填 |
masterAddr | 172.16.1.101:17010 | Master 节点地址,多个地址用逗号分隔 |
followerRead | true/false | 是否允许从 follower 副本读 |
logDir | /home/test_sdk/log | 日志输出目录,空则不初始化日志 |
logLevel | debug/info/warn/error | 日志级别,未设置时默认WARN |
enableBcache | true/false | 是否启用本地块缓存(BlockCache) |
readBlockThread | 整数 | 读并发线程数,为 0 时默认 10 |
writeBlockThread | 整数 | 写并发线程数,为 0 时默认 10 |
accessKey | 字符串 | 访问密钥,用于权限校验 |
secretKey | 字符串 | 秘密密钥,用于权限校验 |
pushAddr | 字符串 | 监控指标上报地址 |
enableAudit | true/false | 是否开启客户端审计日志 |
enableInnerReq | true/false | 是否使用内部请求通道 |
说明:文档中出现的enableSummary对应 proto/mount_options.go 中的EnableSummary字段,其配置入口属于挂载级选项;libsdk 的cfs_set_client仅识别上表中的键,传入未知键将返回statusEINVAL。accessKey与secretKey在cfs_start_client阶段会通过checkPermission()与 Master 返回的用户信息做一致性校验(见 client/libsdk/libsdk.go),两者缺失或错误会导致启动失败。
cfs_start_client
extern int cfs_start_client(int64_t id);启动客户端。参数id为客户端的 ID。返回statusOK(成功)、statusEINVAL(非法参数)或statusEIO(I/O 错误)。
启动过程(client/libsdk/libsdk.go)主要完成:初始化日志与统计模块、初始化 buffer 池、从 Master 拉取卷与集群配置、校验权限、按需初始化审计与 BlockCache,并构造MetaWrapper与ExtentClient。
cfs_close_client
extern void cfs_close_client(int64_t id);关闭客户端。参数id为客户端的 ID,无返回值。实现会关闭数据流客户端与元数据封装、移除客户端记录并刷新日志(见 client/libsdk/libsdk.go)。
文件打开与关闭接口
cfs_open
extern int cfs_open(int64_t id, char* path, int flags, mode_t mode);打开/创建文件。参数说明:
id:client 的 ID;path:文件的路径;flags:打开文件的标志(如O_RDONLY、O_WRONLY、O_RDWR、O_CREAT、O_TRUNC、O_APPEND、O_DIRECT、O_SYNC等);mode:文件的权限模式。
返回值大于 0 时表示成功,返回文件描述符;失败返回小于 0 的值,可能的错误包括statusEINVAL(非法参数)、statusEACCES(权限错误)、statusEMFILE(文件描述符已达上限)、statusEIO(I/O 错误)。
需要注意两点实现细节:其一,libsdk 打开文件时忽略 rwx 模式位(源码注释明确说明rwx mode is ignored);其二,以O_CREAT打开时必须同时指定写权限(O_WRONLY或O_RDWR),否则返回statusEACCES(见 client/libsdk/libsdk.go)。
cfs_close
extern void cfs_close(int64_t id, int fd);关闭文件。参数id为 client 的 ID,fd为文件描述符,无返回值。对于普通文件,cfs_close在释放 fd 的同时会执行 flush 并关闭数据流(见 client/libsdk/libsdk.go)。
读写接口
cfs_read
extern ssize_t cfs_read(int64_t id, int fd, void* buf, size_t size, off_t off);从指定的文件描述符中读取数据。参数说明:
id:client 的 ID;fd:文件描述符;buf:指向内存区域的指针,用于存储读取的数据;size:要读取的数据大小;off:文件偏移量。
返回值大于 0 表示实际读取的数据大小;失败返回小于 0 的值,可能的错误包括statusEINVAL、statusEACCES(以只写方式打开的文件不可读)、statusEBADFD(错误文件描述符)、statusEIO。
从实现看,cfs_read通过反射构造 Go slice 直接引用调用方传入的缓冲区,零拷贝传递给内部读取逻辑;对于热卷走ExtentClient.Read,对于冷卷/BlobStore 卷走blobstore.Reader.Read(见 client/libsdk/libsdk.go 与 client/libsdk/libsdk.go)。
cfs_write
extern ssize_t cfs_write(int64_t id, int fd, void* buf, size_t size, off_t off);向指定的文件描述符中写数据。参数说明:
id:client 的 ID;fd:文件描述符;buf:指向要写的数据缓冲区;size:缓冲区大小;off:偏移量。
返回值大于 0 表示写入的字节数;失败返回小于 0 的值,可能的错误包括statusEINVAL、statusEACCES、statusEBADFD、statusENOSPC(空间不足)、statusEIO。
实现要点(client/libsdk/libsdk.go):
- 以
O_DIRECT、O_SYNC、O_DSYNC打开的热卷文件,写入后会自动 flush 以保证落盘; - 以
O_APPEND打开的文件以及冷卷文件,会自动追加FlagsAppend | FlagsSyncWrite标志; - 热卷写入会经过配额检查(
UidIsLimited/IsQuotaLimitedById),配额受限时返回ENOSPC。
cfs_flush
extern int cfs_flush(int64_t id, int fd);flush 文件,将缓冲数据刷盘。参数id为 client 的 ID,fd为文件描述符。成功返回 0,失败返回小于 0 的值:statusOK、statusEINVAL、statusEBADFD、statusEIO。热卷通过ExtentClient.Flush完成刷盘(见 client/libsdk/libsdk.go)。
cfs_truncate
extern int cfs_truncate(int64_t id, int fd, size_t size);truncate 文件。参数id为 client 的 ID,fd为文件描述符,size为 truncate 后文件的大小。成功返回 0,失败返回小于 0 的值:statusOK、statusEINVAL、statusEBADFD、statusEIO。
文件与目录操作接口
cfs_mkdirs
extern int cfs_mkdirs(int64_t id, char* path, mode_t mode);创建多级目录。参数id为 client 的 ID,path为要创建的目录路径,mode为权限模式。成功返回 0,失败返回小于 0 的值:statusOK、statusEINVAL、statusEEXIST(目录已存在)。实现会按/逐级拆分路径,逐级Lookup/Create,路径为根目录/时直接返回statusEEXIST(见 client/libsdk/libsdk.go)。
cfs_rmdir
extern int cfs_rmdir(int64_t id, char* path);删除指定路径的目录。参数id为 client 的 ID,path为目录路径。成功返回 0,失败返回小于 0 的值。底层调用MetaWrapper.Delete_ll并清除 inode/dentry 缓存(见 client/libsdk/libsdk.go)。
cfs_unlink
extern int cfs_unlink(int64_t id, char* path);删除文件。参数id为 client 的 ID,path为文件路径。成功返回 0,失败返回小于 0 的值:statusOK、statusEINVAL、statusEISDIR(目标为目录)。实现中会先校验目标不是目录,删除后还会调用Evict驱逐对应 inode(见 client/libsdk/libsdk.go)。
cfs_rename
extern int cfs_rename(int64_t id, char* from, char* to, GoUint8 overwritten);重命名一个文件或目录。参数说明:
id:client 的 ID;from:原始文件或目录的路径;to:新的文件或目录的路径;overwritten:是否允许覆盖已存在的目标。
成功返回 0,失败返回小于 0 的值。该操作在客户端内部加锁串行执行,并同步清理源/目标两侧的 dentry 缓存(见 client/libsdk/libsdk.go)。
cfs_readdir
extern int cfs_readdir(int64_t id, int fd, GoSlice dirents, int count);读取目录的文件和子目录列表。参数说明:
id:client 的 ID;fd:文件描述符;dirents:存储文件和子目录信息的数组(元素为cfs_dirent结构,包含ino、name[256]、d_type、nameLen);count:要读取的目录项数量。
返回值大于 0 表示读取的目录项数,失败返回小于 0 的值:statusEINVAL、statusEBADFD。实现使用dirStream维护游标,支持分多次读取(见 client/libsdk/libsdk.go)。
cfs_lsdir
extern int cfs_lsdir(int64_t id, int fd, GoSlice direntsInfo, int count);列出指定目录下的文件和子目录信息,包括元数据信息。参数说明:
id:client 的 ID;fd:文件描述符;direntsInfo:存储文件和子目录信息的数组(元素为cfs_dirent_info,内含cfs_hdfs_stat_info元数据);count:要读取的目录项数量。
返回值大于 0 表示读取的目录项数,失败返回小于 0 的值:statusEINVAL、statusEBADFD。与cfs_readdir不同,cfs_lsdir会对每个目录项批量获取 inode 元数据(size、mode、atime、mtime 等)一并返回(见 client/libsdk/libsdk.go)。
属性接口
cfs_getattr
extern int cfs_getattr(int64_t id, char* path, struct cfs_stat_info* stat);获取文件的属性。参数说明:
id:client 的 ID;path:文件的路径;stat:cfs_stat_info结构体指针,用于存储文件属性,定义在 client/libsdk/libcfs.h 中。
成功返回 0,失败返回小于 0 的值。cfs_stat_info包含ino、size、blocks、atime/mtime/ctime(及纳秒部分)、mode、nlink、blk_size、uid、gid等字段。实现中mode会根据文件类型(普通文件/目录/软链/其他)自动补上S_IFREG、S_IFDIR、S_IFLNK等类型位(见 client/libsdk/libsdk.go)。
cfs_setattr
extern int cfs_setattr(int64_t id, char* path, struct cfs_stat_info* stat, int valid);设置文件的属性。参数说明:
id:client 的 ID;path:文件的路径;stat:cfs_stat_info结构体指针,包含待设置的属性;valid:位掩码,标识哪些属性是有效的(如模式、uid、gid、atime、mtime)。
成功返回 0,失败返回小于 0 的值。实现中仅允许设置 rwx 权限位(mode & 0o777),且会保留原有的文件类型位(见 client/libsdk/libsdk.go 与 client/libsdk/libsdk.go)。
cfs_IsDir 与 cfs_IsRegular
extern int cfs_IsDir(mode_t mode); extern int cfs_IsRegular(mode_t mode);根据mode判断类型:cfs_IsDir返回 1 表示是目录、0 表示不是;cfs_IsRegular返回 1 表示是普通文件、0 表示不是。实现分别对mode & S_IFMT与S_IFDIR/S_IFREG做比较(见 client/libsdk/libsdk.go)。
链接接口
cfs_symlink
extern int cfs_symlink(int64_t id, char *src_path, char *dst_path);创建软链接。参数id为 client 的 ID,src_path为源路径,dst_path为目的路径。成功返回 0,失败返回小于 0 的值。实现通过MetaWrapper.Create_ll以ModeSymlink | ModePerm模式创建(见 client/libsdk/libsdk.go)。
cfs_link
extern int cfs_link(int64_t id, char *src_path, char *dst_path);创建硬链接。参数id为 client 的 ID,src_path为源路径,dst_path为目的路径。成功返回 0,失败返回小于 0 的值。注意源文件必须是普通文件,否则返回statusEPERM(见 client/libsdk/libsdk.go)。
目录锁接口
目录锁适用于对目录有互斥需求的应用,基于元数据 XAttr(key 为dir_lock)实现。目录加锁后,如果在有效期内再次加锁会返回失败;加锁成功会返回唯一的lockId。
cfs_lock_dir
extern int64_t cfs_lock_dir(int64_t id, char *path, int64_t lease, int64_t lock_id);对目录加锁。参数说明:
id:client 的 ID;path:目录路径;lease:加锁期限;lock_id:若指定了lock_id,则表示对该lock_id的加锁期限lease进行续期修改。
成功时返回唯一的lock_id,失败时返回小于 0 的值(见 client/libsdk/libsdk.go)。
cfs_unlock_dir
extern int cfs_unlock_dir(int64_t id, char *path);对目录解锁。参数id为 client 的 ID,path为目录路径。成功返回 0,失败返回小于 0 的值(见 client/libsdk/libsdk.go)。
cfs_get_dir_lock
extern int cfs_get_dir_lock(int64_t id, char *path, int64_t *lock_id, char **valid_time);获取目录锁的有效时间。参数说明:
id:client 的 ID;path:目录路径;lock_id:输出参数,返回对应的锁 ID;valid_time:输出参数,返回对应lock_id的有效期。
成功返回 0,失败返回小于 0 的值(目录未加锁时返回ENOENT)。实现从 XAttr 中解析出lockId|lease两部分(见 client/libsdk/libsdk.go)。
其他扩展接口
client/libsdk/libcfs.h 中还导出了文档未逐条展开但同样可用的接口,包括:
cfs_chdir/cfs_getcwd:修改/获取客户端当前工作目录(相对路径基于该目录解析);cfs_fchmod:按 fd 修改文件权限;cfs_batch_get_inodes:批量获取 inode 属性;cfs_getsummary/cfs_refreshsummary:获取/刷新目录摘要(文件数与字节数,按 HDD/SSD/BlobStore 分类,支持缓存与并发数控制);cfs_get_xattr/cfs_list_vols/cfs_get_accessFiles:扩展属性、卷列表与访问文件清单查询。
错误码速查
所有接口的返回约定统一为:0 表示成功,负数表示错误。常见错误码及含义汇总如下(对应 client/libsdk/libsdk.go 中定义的 status 常量):
| 返回值 | 名称 | 含义 |
|---|---|---|
| 0 | statusOK | 成功 |
| -EINVAL | statusEINVAL | 非法参数(如 client 不存在) |
| -EIO | statusEIO | I/O 错误 |
| -EACCES | statusEACCES | 权限错误(如只写 fd 上执行读) |
| -EBADFD | statusEBADFD | 错误文件描述符 |
| -EMFILE | statusEMFILE | 文件描述符已达上限 |
| -ENOSPC | statusENOSPC | 空间不足 |
| -EEXIST | statusEEXIST | 目标已存在 |
| -EISDIR | statusEISDIR | 目标是目录(如对目录执行 unlink) |
| -ENOTDIR | statusENOTDIR | 目标不是目录 |
| -EPERM | statusEPERM | 操作不允许(如对非普通文件建立硬链接) |
结语:快速上手指南
- 构建:在仓库根目录执行
make libsdk(依赖libsdkpre),产物为libcfs.so与libcfs.h(见 Makefile 与 build/build.sh); - 接入:将 client/libsdk/libcfs.h 加入头文件路径,链接
libcfs.so; - 编码:按“new → set → start → 文件操作 → close”五步生命周期编写业务逻辑;
- 部署:确保应用可访问 Master 地址与目标卷,并配置正确的
accessKey/secretKey,否则cfs_start_client会因权限校验失败而无法启动。
libsdk 让开发者在不引入内核模块与 FUSE 的前提下,以纯用户态方式获得 CubeFS 文件系统的全部能力,适合对灵活性、可移植性与低延迟有较高要求的应用场景。
- 存储
- 分布式文件系统
- 对象存储
- 云原生
【免费下载链接】cubefs
cloud-native distributed storage
相关推荐
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考