客户端
客户端以用户态可执行程序的形式可以运行在容器中,并且通过 FUSE 将挂载的卷及文件系统接口提供给其它用户态使用。
客户端缓存
客户端进程在以下几种情况下会使用客户端缓存。
客户端为了减少与资源管理节点的通信负担,会在挂载启动时获取该挂载卷中所有元数据和数据节点的地址,并且进行缓存,后续会定期从资源管理节点进行更新。
客户端为了减少与元数据节点的通信,会缓存
inode,dentry 以及 extent
元数据信息。通常意义上,读请求应该能够读到之前所有的写入,但是客户端元数据缓存可能会导致多客户端写同一个文件时的一致性问题。所以,CubeFS 的设计中,不同客户端,或者说挂载点,可以同时读一个文件,但是不能够同时写一个文件。打开文件时,客户端会强制从元数据节点更新文件元数据信息。
注意
不同进程可以从同一个挂载点并发写一个文件,但是需要解决数据并发写导致的错乱问题
由于故障恢复时,raft
复制组的主节点有可能发生变化,导致客户端缓存的主节点地址无效。因此,客户端在发送请求收到 not leader
回复时,会轮询重试该复制组的所有节点。重试成功后识别出新的主节点,客户端会缓存新的主节点地址。
对接 FUSE 接口
CubeFS 客户端通过对接 FUSE 为提供用户态文件系统接口。
提示
之前,性能较低被认为是用户态文件系统最大的缺点。但是经过多年的发展,FUSE 已经在性能上有了很大提高。后续,CubeFS 会着手开发内核态文件系统客户端。
目前来看,FUSE 的 writeback cache 特性并未达到预期的性能提升。
FUSE 默认的写流程走的是 directIO 接口,使得每次写入长度较小时会有性能问题,因为每次的写请求都会被推送至后端存储。
FUSE 的解决方案是 writeback cache,即小写入写到缓存页即返回,由内核根据回刷策略推送至后端存储。这样,顺序的小请求会被聚合。
但是,在实际生产中,我们发现 writeback cache 特性作用非常有限,原因是走 writecache 的写操作触发了内核 balance dirty page 的流程,使得本应该是响应时间非常短的写操作仍然会等较长时间才返回,这个问题在小的写入时尤其明显。
在线升级或热重启
支持不停止旧客户端进程进行在线升级。新客户端会先恢复旧客户端 FUSE 读写请求,接替并继续服务旧客户端的数据读写请求。
程序执行如下操作:
旧客户端停止从
/dev/fuse
读取请求旧客户端保存上下文信息到本地
上下文信息包括还在 fuse 客户端和内核之间的、使用中(已经打开 open 或者没有被驱逐evicted)的
inodes/files
旧客户端传输
/dev/fuse
的文件描述符使用 Unix 域套接字(Unix Domain Socket)向新客户端传输文件描述符
旧客户端退出,新客户端启动
新客户端恢复上下文
新客户端尝试恢复旧客户端的上下文而不是真实挂载 fuse。
恢复
/dev/fuse
文件描述符新客户端读取的旧客户端未处理的请求,继续服务。
客户端预热
客户端为了提高纠删卷的读取效率,可以通过预热功能将纠删码子系统的数据缓存到副本子系统中。副本子系统中的缓存内容会在预热 TTL 过期后,自动删除。
一级缓存(数据缓存)
L1Cache 是独立于客户端的本地数据缓存服务,对外提供 Put/Get/Delete
接口,基于数据块(Block)进行缓存读写、淘汰操作,整体结构如下图所示。
L1Cache 缓存服务为本机所有打开一级缓存配置的客户端提供缓存服务。
本地缓存数据块与远端存储数据块一一对应,并按块进行索引(BlockKey)访问,数据块索引BlockKey 生成方式为:VolumeName_Inode_hex(FileOffset)
。
提示
数据块索引经过两次取模计算将内存数据块结构映射到本地缓存文件,LocalPath / hash(BlockKey)%512 / hash(BlockKey)%256 / BlockKey
。
L1CacheStoreService 统一维护全局的 BlockKeys,定期按照 LRU 进行淘汰。
L1Cache 服务重启时自动扫描磁盘上的缓存数据,并重建缓存索引信息。缓存目录的增加和退出不涉及数据迁移,丢失的缓存数据重新缓存,残留的缓存数据最终会被 LRU 淘汰。