天天看點

JuiceFS架構介紹和讀寫流程解析

1.基本元件介紹

JuiceFS架構介紹和讀寫流程解析

JuiceFS Client:支援多種Client端的接口,比如相容POSIX檔案系統的接口,以此你可以将它挂載到系統上當檔案系統使用,且可以為k8s提供存儲使用,用ks8s的csi driver進行接入。同時也支援S3協定,開發了對應的S3網關進行支援;

Data Storage:對象存儲服務,用以存儲具體資料的,可以類比檔案系統裡的block資料儲存,支援多種後端存儲;

Metadata Engine:中繼資料服務,用以存儲檔案中繼資料資訊的,比如檔案名、目錄資訊、檔案inode等資訊,可以類比檔案系統裡的inode資料管理,支援多種中繼資料存儲;

2.快速部署

我們使用docker部署一個minio作為對象存儲服務,用docker部署一個redis作為中繼資料服務。

git下載下傳juicefs代碼并進行編譯生成juicefs二進制可執行檔案:

git clone https://github.com/juicedata/juicefs.git

cd juicefs && make

安裝docker和下載下傳redis和minio/minio鏡像。

部署中繼資料服務和對象存儲服務:

sudo docker run -d --name redis -v /data/redis-data:/data -p 6379:6379 --restart unless-stopped redis redis-server --appendonly yes

sudo docker run -d --name minio -v /data/minio-data:/data -p 9000:9000 --restart unless-stopped minio/minio server /data

進行format和挂載:

mkdir /data/ussfs

./juicefs format --storage minio --bucket http://127.0.0.1:9000/test123 --access-key minioadmin --secret-key minioadmin redis://127.0.0.1:6379/10 test123

./juicefs mount -d redis://127.0.0.1:6379/10 /data/ussfs

3.挂載過程分析

format過程:

format的作用是将一些中繼資料資訊先注冊好,在mount時進行擷取作為配置參數,比如對象存儲的相關資訊,bucket的相關資訊等。

JuiceFS架構介紹和讀寫流程解析

流程說明:

(1)接收用戶端執行的指令,如果是mount的指令,則進入mount邏輯;

(2)通過format給定的url來判斷是哪種中繼資料服務,并初始化中繼資料服務對象;

(3)建構format結構體對象,結構體裡包含對象存儲服務資訊,block size等資訊;

(4)根據format的資訊初始化對象存儲服務并測試對象存儲服務是否可增删改操作,確定對象存儲服務可用;

(5)持久化format資訊,在redis為中繼資料服務時,即是将格式化後的format資料儲存到redis的setting這個key中;

(6)建立inode号為1的第一個檔案,檔案類型為目錄,作為後續建立檔案的父目錄,并持久化到中繼資料中。

mount過程:

mount就是将自定義的檔案系統挂載到指定目錄下,可供符合POSIX的接口進行調用,由mount中開啟的server服務進行檔案操作請求接收并處理。

JuiceFS架構介紹和讀寫流程解析

(1)擷取mount中的指令行參數,擷取到中繼資料資訊url;

(2)建立中繼資料服務連接配接執行個體,從中繼資料服務中擷取之前儲存的format資訊;

(3)根據format資訊建立對象存儲服務連接配接執行個體storage,建立store對象,store對象是對對象存儲資料進行管理,store對象屬性裡包含了storage對象和對cache的管理;

(4)初始化vfs對象,vfs是一層虛拟檔案系統對象,它包含了對meta和storage的管理,建立了讀寫對象和檔案句柄管理;

(5)如果指令行參數中是有用-d指定了mount程序背景運作,則調用makeDaemon函數将fork出一個程序作為daemon程序背景運作;

(6)通過讀取挂載目錄檔案屬性來檢查挂載目錄是否可進行挂載;

(7)建立本次挂載session,生成session資訊儲存到中繼資料服務中;

(8)建立自定義的檔案系統類型,通過使用者态fuse(使用者空間實作檔案系統)庫來進行實作,啟動服務來接收fuse的請求資訊并封裝成request,解析request找到對應的處理函數進行處理并傳回。

啟動的server接收fuse請求大緻流程圖:

JuiceFS架構介紹和讀寫流程解析

4.中繼資料儲存key含義解析

主要介紹在redis為中繼資料服務時,儲存的各個key的含義,友善下面講解讀寫流程。

這裡我們先列出redis中儲存的所有key:

JuiceFS架構介紹和讀寫流程解析

可以看到,大概分成好幾種key,有i開頭的,有d開頭的,有c開頭的,還有其它的一些固定字元串的key。

setting:儲存的format資訊的key,對應的結構體:

type Format struct {
    Name        string
    UUID        string
    Storage     string
    Bucket      string
    AccessKey   string
    SecretKey   string `json:",omitempty"`
    BlockSize   int
    Compression string
    Shards      int
    Partitions  int
    Capacity    uint64
    Inodes      uint64
    EncryptKey  string `json:",omitempty"`
}
      

i1:表示記錄的inode号為1的檔案的屬性資訊,同理i2為inode号為2的檔案屬性key,儲存的值對應的結構體為:

// Attr represents attributes of a node.
type Attr struct {
    Flags     uint8  // reserved flags
    Typ       uint8  // type of a node
    Mode      uint16 // permission mode
    Uid       uint32 // owner id
    Gid       uint32 // group id of owner
    Atime     int64  // last access time
    Mtime     int64  // last modified time
    Ctime     int64  // last change time for meta
    Atimensec uint32 // nanosecond part of atime
    Mtimensec uint32 // nanosecond part of mtime
    Ctimensec uint32 // nanosecond part of ctime
    Nlink     uint32 // number of links (sub-directories or hardlinks)
    Length    uint64 // length of regular file
    Rdev      uint32 // device number
 
    Parent    Ino  // inode of parent, only for Directory
    Full      bool // the attributes are completed or not
    KeepCache bool // whether to keep the cached page or not
}
      

d1:當檔案為目錄類型時,就會對應一個d開頭的key,1表示該檔案夾是inode号為1的,它是一個hash類型的鍵,它的每個field key是檔案名字,field key對應的值是檔案類型和對應的inode号,field key的值儲存着檔案類型和檔案inode号。這樣的話就可以友善的找到某個目錄下的某個檔案名的inode号及該檔案的類型,同理d3就是inode号為3的檔案夾記錄着它檔案夾下的檔案元資訊。

c14_0:c表示chunk的意思,我們先來看下這個資料結構圖

JuiceFS架構介紹和讀寫流程解析

可以看到一個檔案資料會被拆分成chunk來進行儲存的,如果是大檔案,那麼會被按指定大小拆分成多個chunk,這個c14的14就表示這個chunk是歸屬于inode14這個檔案的,c14_0的0則表示是第一個chunk,該key是清單類型的鍵,它的每個元素是一個slice,slice不是固定大小的,slice會根據block size大小拆分成多個block進行存儲,比如block size是4M,如果slice是6M,則拆分成2個block來進行存儲,計算出對應的兩個key把鍵值往對象存儲服務中存儲。比如生成35_0_16384和35_1_8192,35是表示這個slice的id(Slice結構體中的Chunkid字段),0和1表示兩個block,16384和8192分别表示block的大小,分别為4M和2M。

chunk的結構體:是slices清單的資料結構,[]meta.Slice

Slice結構體:

// Slice is a slice of a chunk.
// Multiple slices could be combined together as a chunk.
type Slice struct {
    Chunkid uint64
    Size    uint32
    Off     uint32
    Len     uint32
}
      

nextinode:它是一個自增的key,作用是配置設定新的inode;

nextchunk:它也是自增的,用來配置設定給slice的chunkid的;

nextsession:它也是自增的,用來給session結構體配置設定session id的。

totalInodes和usedSpace是記錄目前一共有多少個inode和目前空間已使用量的。

5.讀寫檔案過程分析

5.1.建立檔案夾

建立檔案夾指令示例:mkdir /data/ussfs/testdir

流程圖:

JuiceFS架構介紹和讀寫流程解析

(1)先擷取根檔案屬性資訊,然後通過檔案路徑和檔案名擷取指定檔案屬性,擷取的方式就是一層層擷取,比如/a/b/c,先是在a目錄下找b,然後在b目錄下找c,這是通過調用doLookup函數來查詢擷取;

(2)如果要建立的檔案夾不存在則進行建立,建立調用doMkdir函數進行建立,該函數又調用mknode函數來建立檔案資訊(檔案夾也是一個檔案);

(3)從中繼資料伺服器中擷取一個新的inode号,如果是redis中繼資料服務,則是由一個自增的nextinode來儲存目前配置設定的inode号;

(4)設定新檔案的檔案屬性資訊,比如權限、建立時間等資訊;

(5)向父目錄中添加該新檔案,當redis為中繼資料服務時,則是向d開頭的key裡比如d1裡添加一條該新檔案資訊;

(6)更新父目錄檔案屬性資訊、新檔案屬性資訊和檔案系統的一些總體使用資訊。

建立檔案跟建立檔案夾類似,先調用的doCreate,然後也會調用mknode來生成inode和儲存檔案屬性資訊。

5.2.寫入資料到檔案

寫入檔案指令示例:echo "123456789" > /data/ussfs/testdir/testfile

JuiceFS架構介紹和讀寫流程解析

(1)跟先前類似,先通過檔案路徑找到該檔案,擷取該檔案屬性資訊,如果檔案不存在先建立檔案;

(2)調用doopen函數打開檔案,主要是初始化檔案handle,建立檔案讀寫對象,傳回檔案描述符;

(3)調用dowrite,傳入偏移位置和寫入資料,通過偏移位置計算出要寫入到第幾個chunk中去,如果是跨chunk(一個chunk預設64M),則先寫入一部分資料到一個chunk,然後再寫剩下的資料到下一個chunk;

(4)在一個chunk中查找合适的slice進行寫入,比如改變的資料是在中間部分的,那其實隻要更新那一個slice資料即可,其它slice可以不變更,我們目前這場景是找不到一個合适的slice,它會建立一個slice,然後通過該slice進行資料上傳;

(5)通過偏移量進行block的計算,每個block會生成對應的key,然後調用對象存儲的put方法進行key value的上傳來存儲資料;

(6)儲存slice元資訊到中繼資料服務中。

5.3.讀取檔案資料

讀取檔案指令示例:cat /data/ussfs/testdir/testfile

JuiceFS架構介紹和讀寫流程解析

(3)配置設定存儲資料的page資料結構,從中繼資料服務擷取所有slice清單;

(4)周遊每個slice,取出slice對應的所有block資訊儲存到page對象中;

(5)如果block有緩存則直接從緩沖中擷取,否則從對象存儲中重新擷取,并進行緩存。

繼續閱讀