juicefs是一款面向云原生设计的高性能分布式文件系统,其有如下特点:
数据存储和元数据存储分离,可以适配多种数据和元数据存储引擎。
后端存储可以直接对接各种对象存储,使用起来更方便,更加适配云服务趋势。
相关技术架构可直接参考:https://juicefs.com/docs/zh/community/architecture
cache分为两层,磁盘cache和内存cache
磁盘cache:数据刷盘时先写入磁盘cache,即本地文件系统,然后将文件上传到对象存储,然后再将本地文件系统文件删除掉
内存cache:内存cache只记录key和datasize
func (fs *fileSystem) Write(cancel <-chan struct{}, in *fuse.WriteIn, data []byte) (written uint32, code fuse.Status)
func (v *VFS) Write(ctx Context, ino Ino, buf []byte, off, fh uint64) (err syscall.Errno)
// 获取filehandle
func (v *VFS) findHandle(inode Ino, fh uint64) *handle
// 加文件写锁,若有其他client进行并发写,等待
func (h *handle) Wlock(ctx Context) bool
// filewriter写数据
func (f *fileWriter) Write(ctx meta.Context, off uint64, data []byte) syscall.Errno
// 当前file使用超过1000个slice,或使用的buffersize大于预留,等待
// 加filewriter mutex锁,使用cas操作
f.Lock()
// 等待正在进行的flush完成
// 根据offset和length,拆分chunk,每个chunk单独写
func (f *fileWriter) writeChunk(ctx meta.Context, indx uint32, off uint32, data []byte) syscall.Errno
// 根据文件的chunk idx找到对应的chunkwriter
func (f *fileWriter) findChunk(i uint32) *chunkWriter
// 在chunkwriter中找到slicewriter,如果没有合适的slice,申请一个新的写
func (c *chunkWriter) findWritableSlice(pos uint32, size uint32) *sliceWriter
// 遍历每个slice,发现未覆盖的slice直接用,如果没有可用的slice,再外面申请一个
// 对于每个chunk,第一个slice生成的时候启动一个异步线程,等待数据写盘后刷slice到对象存储的映射
func (c *chunkWriter) commitThread()
// slicewriter写数据
func (s *sliceWriter) write(ctx meta.Context, off uint32, data []uint8) syscall.Errno
// 将数据写入buffer
func (s *wSlice) WriteAt(p []byte, off int64) (n int, err error)
// 如果刚好写满一个chunk,异步刷盘(异步)
func (s *sliceWriter) flushData()
func (s *sliceWriter) flushData(
// 生成sliceid
func (s *sliceWriter) prepareID(
// 刷盘
func (s *wSlice) Finish(
func (s *wSlice) FlushTo(
// 遍历每一个block刷盘
func (s *wSlice) upload(
// 根据sliceid,chunkidx,blocksize生成key,用于对象存储文件名
func (s *rSlice) key(
// 进行slice数据写盘(异步)
go func() {
// 标记slice刷盘完成
func (s *sliceWriter) markDone(
func (s *wSlice) upload(
go func()
// 写磁盘cache,用加速盘进行存储
func (cache *cacheStore) stage
// 生成路径,与对象存储相关路径一致,写数据到本地文件系统目录
func (cache *cacheStore) flushPage(
// 将数据写入磁盘cache,即使用os本地文件系统写文件
func (f *File) Write(
// 插入keymap,将key、datasize、atime,插入内存cache
func (cache *cacheStore) add(
// 若内存cache空间不足,触发淘汰
// 数据上传到对象存储
func (store *cachedStore) upload(
// 上传对象存储
func (store *cachedStore) put(
// 更新内存cache中的key,和对应的data
func (cache *cacheStore) uploaded(
// 删除本地文件系统cache的文件
os.Remove(stagingPath)
多客户端如何保证数据一致性:不同客户端在读写时需要先调用SetLk、SetLkw、Flock接口,保证客户端之间的互斥。
并发读写如何保证数据一致性:使用filehandle上的读写锁保证数据一致性。
并发写如何保证内存原子性:使用filewriter上面的mutex锁保证原子性。
并发写如何保证元数据和数据的一致性:写数据到盘的过程中使用的是追加写,元数据没有修改时不会更新数据,保证原子性。写流程不会更新inode信息(如size等),只会更新block到存储对象的映射,因此不会出现更新inode信息并发的情况。
写文件如何保证inode数据和文件的一致性:不能保证,只有close文件后,才会更新inode信息,因此juicefs只能保证文件最终inode和文件信息的一致性。
slice中连续小io会在slice里面进行聚合,等待触发刷slice