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


1.基本組件介紹

  

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的相關信息等。

 

流程說明:

(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服務進行文件操作請求接收並處理。

  

流程說明:

(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請求大致流程圖:

   

4.元數據保存key含義解析

主要介紹在redis為元數據服務時,保存的各個key的含義,方便下面講解讀寫流程。

這里我們先列出redis中保存的所有key:

 

可以看到,大概分成好幾種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的意思,我們先來看下這個數據結構圖

 

可以看到一個文件數據會被拆分成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

流程圖:

 

流程說明:

(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

流程圖:

 

流程說明:

(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

流程圖:

 

流程說明:

(1)跟先前類似,先通過文件路徑找到該文件,獲取該文件屬性信息,如果文件不存在先創建文件;

(2)調用doopen函數打開文件,主要是初始化文件handle,創建文件讀寫對象,返回文件描述符;

(3)分配存儲數據的page數據結構,從元數據服務獲取所有slice列表;

(4)遍歷每個slice,取出slice對應的所有block信息保存到page對象中;

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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM