|
| 1 | +--- |
| 2 | +layout : post |
| 3 | +title : "JuiceFS 元数据引擎初探:高层架构、引擎选型、读写工作流(2024)" |
| 4 | +date : 2024-09-12 |
| 5 | +lastupdate: 2024-09-12 |
| 6 | +categories: storage juicefs |
| 7 | +--- |
| 8 | + |
| 9 | +<p align="center"><img src="/assets/img/juicefs-metadata-deep-dive/juicefs-tikv-cluster.png" width="90%"/></p> |
| 10 | +<p align="center">Fig. JuiceFS cluster initialization, and how POSIX file operations are handled by JuiceFS.</p> |
| 11 | + |
| 12 | +* [JuiceFS 元数据引擎初探:高层架构、引擎选型、读写工作流(2024)]({% link _posts/2024-09-12-juicefs-metadata-deep-dive-1-zh.md %}) |
| 13 | +* [JuiceFS 元数据引擎再探:开箱解读 TiKV 中的 JuiceFS 元数据(2024)]({% link _posts/2024-09-12-juicefs-metadata-deep-dive-2-zh.md %}) |
| 14 | +* [JuiceFS 元数据引擎三探:从实践中学习 TiKV 的 MVCC 和 GC(2024)]({% link _posts/2024-09-12-juicefs-metadata-deep-dive-3-zh.md %}) |
| 15 | +* [JuiceFS 元数据引擎四探:元数据大小评估、限流与限速的设计思考(2024)]({% link _posts/2024-09-12-juicefs-metadata-deep-dive-4-zh.md %}) |
| 16 | + |
| 17 | +水平及维护精力所限,文中不免存在错误或过时之处,请酌情参考。 |
| 18 | +**<mark>传播知识,尊重劳动,年满十八周岁,转载请注明<a href="https://arthurchiao.art">出处</a></mark>**。 |
| 19 | + |
| 20 | +---- |
| 21 | + |
| 22 | +* TOC |
| 23 | +{:toc} |
| 24 | + |
| 25 | +---- |
| 26 | + |
| 27 | + |
| 28 | +# 1 JuiceFS 高层架构与组件 |
| 29 | + |
| 30 | +<p align="center"><img src="/assets/img/juicefs-metadata-deep-dive/juicefs-components-1.png" width="70%"/></p> |
| 31 | +<p align="center">Fig. JuiceFS components and architecutre.</p> |
| 32 | + |
| 33 | +如图,最粗的粒度上可以分为三个组件。 |
| 34 | + |
| 35 | +## 1.1 JuiceFS client |
| 36 | + |
| 37 | +* `juicefs format ...` 可以创建一个 volume; |
| 38 | +* `juicefs config ...` 可以修改一个 volume 的配置; |
| 39 | +* `juicefs mount ...` 可以把一个 volume 挂载到机器上,然后用户就可以在里面读写文件了; |
| 40 | + |
| 41 | +## 1.2 Metatdata engine(元数据引擎) |
| 42 | + |
| 43 | +* 用于**<mark>存储 JuiceFS 的元数据</mark>**,例如每个文件的文件名、最后修改时间等等; |
| 44 | +* 可选择 etcd、TiKV 等等; |
| 45 | + |
| 46 | +## 1.3. Object store |
| 47 | + |
| 48 | +实际的对象存储,例如 S3、Ceph、阿里云 OSS 等等,存放 JuiceFS volume 内的数据。 |
| 49 | + |
| 50 | +# 2 JuiceFS 元数据存储引擎对比:`tikv vs. etcd` |
| 51 | + |
| 52 | +## 2.1 设计与优缺点对比 |
| 53 | + |
| 54 | +| | TiKV as metadata engine | etcd as metadata engine | |
| 55 | +|:-----|:-----|:-----| |
| 56 | +| **<mark>管理节点</mark>**(e.g. leader election)| PD (TiKV cluster manager) | etcd server | |
| 57 | +| **<mark>数据节点</mark>**(存储 juicefs metadata) | TiKV server | etcd server | |
| 58 | +| **<mark>数据节点对等</mark>** | 无要求 | **<mark>完全对等</mark>** | |
| 59 | +| **<mark>数据一致性粒度</mark>** | region-level (TiKV 的概念,`region < node`) | node-level | |
| 60 | +| **<mark>Raft 粒度</mark>** | region-level (multi-raft,TiKV 的概念) | node-level | |
| 61 | +| **<mark>缓存多少磁盘数据在内存中</mark>** | 一部分 | **<mark>所有</mark>** | |
| 62 | +| **<mark>集群支持的最大数据量</mark>** | **<mark><code>PB</code></mark>** 级别 | 几十 GB 级别 | |
| 63 | +| **<mark>性能</mark>**(JuiceFS 场景) | 高(猜测是因为 raft 粒度更细,并发读写高) | **<mark>低</mark>** | |
| 64 | +| 维护和二次开发门槛 | 高(相比 etcd) | 低 | |
| 65 | +| 流行度 & 社区活跃度 | 低(相比 etcd) | 高 | |
| 66 | +| 适用场景 | **<mark>大和超大 JuiceFS 集群</mark>** | 中小 JuiceFS 集群 | |
| 67 | + |
| 68 | +## 2.2 几点解释 |
| 69 | + |
| 70 | +etcd 集群, |
| 71 | + |
| 72 | +* 每个节点完全对等,既负责管理又负责存储数据; |
| 73 | +* 所有数据**<mark>全部缓存在内存中</mark>**,每个节点的数据完全一致。 |
| 74 | + 这一点限制了 etcd 集群支持的最大数据量和扩展性, |
| 75 | + 例如现在官网还是建议不要超过 8GB(实际上较新的版本在技术上已经没有这个限制了, |
| 76 | + 但仍受限于机器的内存)。 |
| 77 | + |
| 78 | +TiKV 方案可以可以理解成把管理和数据存储分开了, |
| 79 | + |
| 80 | +* PD 可以理解为 **<mark><code>TiKV cluster manager</code></mark>**,负责 leader 选举、multi-raft、元数据到 region 的映射等等; |
| 81 | +* 节点之间也**<mark>不要求对等</mark>**,PD 按照 region(比如 96MB)为单位,将 N(默认 3)个副本放到 N 个 TiKV node 上,而实际上 TiKV 的 node 数量是 M,`M >= N`; |
| 82 | +* 数据放在 TiKV 节点的磁盘,内存中**<mark>只缓存一部分</mark>**(默认是用机器 45% 的内存,可控制)。 |
| 83 | + |
| 84 | +## 2.3 例子:TiKV 集群 engine size 和内存使用监控 |
| 85 | + |
| 86 | +TiKV 作为存储引擎,总结成一句话就是:**<mark>根据硬件配置干活,能者多劳</mark>** —— |
| 87 | +内存大、磁盘大就多干活,反之就少干活。 |
| 88 | + |
| 89 | +下面的监控展示是 7 台 TiKV node 组成的一个集群,各 **<mark>node 内存不完全一致</mark>**: |
| 90 | +3 台 256GB 的,2 台 128GB 的,2 台 64GB 的, |
| 91 | +可以看到每个 TiKV server 确实只用了各自所在 node 一半左右的内存: |
| 92 | + |
| 93 | +<p align="center"><img src="/assets/img/juicefs-metadata-deep-dive/tikv-engine-size.png" width="100%"/></p> |
| 94 | +<p align="center">Fig. TiKV engine size and memory usage of a 7-node (with various RAMs) cluster.</p> |
| 95 | + |
| 96 | +# 3 JuiceFS + TiKV:集群启动和宏观读写流程 |
| 97 | + |
| 98 | +## 3.1 架构 |
| 99 | + |
| 100 | +用 TiKV 作为元数据引擎,架构如下(先忽略其中的细节信息,稍后会介绍): |
| 101 | + |
| 102 | +<p align="center"><img src="/assets/img/juicefs-metadata-deep-dive/juicefs-tikv-cluster.png" width="90%"/></p> |
| 103 | +<p align="center">Fig. JuiceFS cluster initialization, and how POSIX file operations are handled by JuiceFS.</p> |
| 104 | + |
| 105 | +## 3.2 TiKV 集群启动 |
| 106 | + |
| 107 | +### 3.2.1 TiKV & PD 配置差异 |
| 108 | + |
| 109 | +两个组件的几个核心配置项, |
| 110 | + |
| 111 | +```shell |
| 112 | +$ cat /etc/tikv/pd-config.toml |
| 113 | +name = "pd-node1" |
| 114 | +data-dir = "/var/data/pd" |
| 115 | + |
| 116 | +client-urls = "https://192.168.1.1:2379" # 客户端(例如 JuiceFS)访问 PD 时,连接这个地址 |
| 117 | +peer-urls = "https://192.168.1.1:2380" # 其他 PD 节点访问这个 PD 时,连接这个地址,也就是集群内互相通信的地址 |
| 118 | + |
| 119 | +# 创建集群时的首批 PD |
| 120 | +initial-cluster-token = "<anything you like>" |
| 121 | +initial-cluster = "pd-node1=https://192.168.1.3:2380,pd-node2=https://192.168.1.2:2380,pd-node3=https://192.168.1.1:2380" |
| 122 | +``` |
| 123 | + |
| 124 | +可以看到,**<mark>PD 的配置和 etcd 就比较类似,需要指定其他 PD 节点地址</mark>**,它们之间互相通信。 |
| 125 | + |
| 126 | +TiKV 节点(tikv-server)的配置就不一样了, |
| 127 | + |
| 128 | +```shell |
| 129 | +$ cat /etc/tikv/tikv-config.toml |
| 130 | +[pd] |
| 131 | +endpoints = ["https://192.168.1.1:2379", "https://192.168.1.2:2379", "https://192.168.1.3:2379"] |
| 132 | + |
| 133 | +[server] |
| 134 | +addr = "192.168.1.1:20160" # 服务地址,JuiceFS client 会直接访问这个地址读写数据 |
| 135 | +status-addr = "192.168.1.1:20180" # prometheus |
| 136 | +``` |
| 137 | + |
| 138 | +可以看到, |
| 139 | + |
| 140 | +1. TiKV 会配置所有 PD 节点的地址,以便自己注册到 PD 作为一个数据节点(存储JuiceFS 元数据); |
| 141 | +2. TiKV 还会配置一个地址的 server 地址,这个读写本节点所管理的 region 内的数据用的; |
| 142 | + 正常流程是 JuiceFS client 先访问 PD,拿到 region 和 tikv-server 信息, |
| 143 | + 然后再到 tikv-server 来读写数据(对应 JuiceFS 的元数据); |
| 144 | +3. TiKV **<mark>不会配置其他 TiKV 节点的地址</mark>**,也就是说 TiKV 节点之间不会 peer-to-peer 互连。 |
| 145 | + |
| 146 | +### 3.2.2 服务启动 |
| 147 | + |
| 148 | +<p align="center"><img src="/assets/img/juicefs-metadata-deep-dive/juicefs-tikv-cluster.png" width="90%"/></p> |
| 149 | +<p align="center">Fig. JuiceFS cluster initialization, and how POSIX file operations are handled by JuiceFS.</p> |
| 150 | + |
| 151 | +对应图中 step 1 & 2: |
| 152 | + |
| 153 | +* step 1. PD 集群启动,选主; |
| 154 | +* step 2. TiKV 节点启动,向 PD 注册;每个 TiKV 节点称为一个 store,也就是元数据仓库。 |
| 155 | + |
| 156 | +## 3.3 宏观读写流程 |
| 157 | + |
| 158 | +对应图中 step 3~5: |
| 159 | + |
| 160 | +* step 3. JuiceFS 客户端连接到 PD;发出读写文件请求; |
| 161 | + |
| 162 | + * JuiceFS 客户端中会初始化一个 TiKV 的 transaction kv client,这里面又会初始化一个 PD client, |
| 163 | + * 简单来说,此时 JuiceFS 客户端就有了 PD 集群的信息,例如哪个文件对应到哪个 region,这个 region 分布在哪个 TiKV 节点上,TiKV 服务端连接地址是多少等等; |
| 164 | + |
| 165 | +* step 4. JuiceFS (内部的 TiKV 客户端)直接向 TiKV 节点(准确说是 region leader)发起读写请求; |
| 166 | +* step 5. 元数据处理完成,JuiceFS 客户端开始往对象存储里读写文件。 |
| 167 | + |
| 168 | +# 4 TiKV 内部数据初探 |
| 169 | + |
| 170 | +TiKV 内部存储的都是 JuiceFS 的元数据。具体来说又分为两种: |
| 171 | + |
| 172 | +1. 用户文件的元数据:例如用户创建了一个 `foo.txt`,在 TiKV 里面就会对应一条或多条元数据来描述这个文件的信息; |
| 173 | +2. JuiceFS 系统元数据:例如每个 volume 的配置信息,这些对用户是不可见的。 |
| 174 | + |
| 175 | +TiKV 是扁平的 KV 存储,所以以上两类文件都放在同一个扁平空间,通过 key 访问。 |
| 176 | +本文先简单通过命令看看里面的元数据长什么样,下一篇再结合具体 JuiceFS 操作来深入解读这些元数据。 |
| 177 | + |
| 178 | +## 4.1 简单脚本 `tikv-ctl.sh/pd-ctl.sh` |
| 179 | + |
| 180 | +简单封装一下对应的命令行工具,使用更方便, |
| 181 | + |
| 182 | +```shell |
| 183 | +$ cat pd-ctl.sh |
| 184 | +tikv-ctl \ |
| 185 | + --ca-path /etc/tikv/pki/root.crt --cert-path /etc/tikv/pki/tikv.crt --key-path /etc/tikv/pki/tikv.key \ |
| 186 | + --host 192.168.1.1:20160 \ |
| 187 | + "$@" |
| 188 | + |
| 189 | +$ cat pd-ctl.sh |
| 190 | +pd-ctl \ |
| 191 | + --cacert /etc/tikv/pki/root.crt --cert /etc/tikv/pki/pd.crt --key /etc/tikv/pki/pd.key \ |
| 192 | + --pd https://192.168.1.1:2379 \ |
| 193 | + "$@" |
| 194 | +``` |
| 195 | + |
| 196 | +## 4.2 `tikv-ctl scan` 扫描 key/value |
| 197 | + |
| 198 | +tikv-ctl **<mark>不支持只列出所有 keys</mark>**,所以只能 key 和 value 一起打印(扫描)。 |
| 199 | + |
| 200 | +扫描前缀是 `foo` 开头的所有 key: |
| 201 | + |
| 202 | +```shell |
| 203 | +$ ./tikv-ctl.sh scan --from 'zfoo' --to 'zfop' --limit 100 |
| 204 | +... |
| 205 | +key: zfoo-dev\375\377A\001\000\000\000\000\000\000\377\000Dfile3.\377txt\000\000\000\000\000\372 |
| 206 | +key: zfoo-dev\375\377A\001\000\000\000\000\000\000\377\000Dfile4.\377txt\000\000\000\000\000\372 |
| 207 | +... |
| 208 | +key: zfoo-dev\375\377setting\000\376 |
| 209 | + default cf value: start_ts: 452330324173520898 value: 7B0A22... |
| 210 | +``` |
| 211 | + |
| 212 | +扫描的时候一定要在 key 前面加一个 `z` 前缀,这是 TiKV 的一个[设计](https://tikv.org/docs/3.0/reference/tools/tikv-ctl/), |
| 213 | + |
| 214 | +> The raw-scan command scans directly from the RocksDB. Note that to scan data keys you need to add a 'z' prefix to keys. |
| 215 | +
|
| 216 | +代码出处 [components/keys/src/lib.rs](https://github.com/tikv/tikv/blob/v5.0.0/components/keys/src/lib.rs#L29)。 |
| 217 | +但对用户来说不是太友好,暴露了太多内部细节,没有 `etcdctl` 方便直接。 |
| 218 | + |
| 219 | +## 4.3 `tikv-ctl mvcc` 查看给定 key 对应的 value |
| 220 | + |
| 221 | +```shell |
| 222 | +$ ./tikv-ctl.sh mvcc -k 'zfoo-dev\375\377A\001\000\000\000\000\000\000\377\000Dfile1.\377txt\000\000\000\000\000\372' --show-cf default,lock,write |
| 223 | +key: zfoo-dev\375\377A\001\000\000\000\000\000\000\377\000Dfile1.\377txt\000\000\000\000\000\372 |
| 224 | + write cf value: start_ts: 452330816414416901 commit_ts: 452330816414416903 short_value: 010000000000000002 |
| 225 | +``` |
| 226 | + |
| 227 | +## 4.4 `tikv-ctl --decode <key>` 解除字符转义 |
| 228 | + |
| 229 | +```shell |
| 230 | +# tikv escaped format -> raw format |
| 231 | +./tikv-ctl.sh --decode 'foo-dev\375\377A\001\000\000\000\000\000\000\377\000Dfile4.\377txt\000\000\000\000\000\372' |
| 232 | +foo-dev\375A\001\000\000\000\000\000\000\000Dfile4.txt |
| 233 | +``` |
| 234 | + |
| 235 | +## 4.5 `tikv-ctl --to-hex`:转义表示 -> 十六进制表示 |
| 236 | + |
| 237 | +```shell |
| 238 | +$ ./tikv-ctl.sh --to-hex '\375' |
| 239 | +FD |
| 240 | +``` |
| 241 | + |
| 242 | +## 4.6 `tikv-ctl --to-escaped <value>`:十六进制 value -> 带转义的字符串 |
| 243 | + |
| 244 | +```shell |
| 245 | +./tikv-ctl.sh scan --from 'zfoo' --to 'zfop' --limit 100 |
| 246 | +key: zfoo-dev\375\377setting\000\376 |
| 247 | + default cf value: start_ts: 452330324173520898 value: 7B0A22... |
| 248 | +``` |
| 249 | + |
| 250 | +其中的 value 是可以解码出来的, |
| 251 | + |
| 252 | +```shell |
| 253 | +# hex -> escaped string |
| 254 | +$ ./tikv-ctl.sh --to-escaped '7B0A22...' |
| 255 | +{\n\"Name\": \"...\",\n\"UUID\": \"8cd1ac73\",\n\"Storage\": \"S3\",\n\"Bucket\": \"http://xxx\",\n\"AccessKey\": \"...\",\n\"BlockSize\": 4096,\n\"Compression\": \"none\",\n\"KeyEncrypted\": true,\n\"MetaVersion\": 1,\n\"UploadLimit\": 0,\n\"DownloadLimit\": 0,\n\"\": \"\"\n} |
| 256 | +``` |
| 257 | + |
| 258 | +# 5 总结 |
| 259 | + |
| 260 | +本文介绍了一些 JuiceFS 元数据引擎相关的内容。 |
| 261 | + |
| 262 | +---- |
| 263 | + |
| 264 | +<a href="https://notbyai.fyi"><img src="/assets/img/Written-By-Human-Not-By-AI-Badge-white.svg" alt="Written by Human, Not by AI"></a> |
| 265 | +<a href="https://notbyai.fyi"><img src="/assets/img/Written-By-Human-Not-By-AI-Badge-black.svg" alt="Written by Human, Not by AI"></a> |
0 commit comments