Seaweedfs Erasure-coding
介绍
https://github.com/seaweedfs/seaweedfs/wiki/Erasure-coding-for-warm-storage
SeaweedFS 实现了RS(10,4),每 10 个硬盘中允许丢失 4 块仍可正常访问
复制数据5次以实现相同的稳健性相比,它节省了3.6倍的磁盘空间。
服务启动
启动 master 服务器,4个卷服务器,一个 filer 文件管理服务器
mkdir disk1 disk2 disk3 disk4
./weed master -mdir=$PWD/master -volumeSizeLimitMB=64
./weed volume -port=8081 -dir=$PWD/disk1 -max=100 -mserver="localhost:9333"
./weed volume -port=8082 -dir=$PWD/disk2 -max=100 -mserver="localhost:9333"
./weed volume -port=8083 -dir=$PWD/disk3 -max=100 -mserver="localhost:9333"
./weed volume -port=8084 -dir=$PWD/disk4 -max=100 -mserver="localhost:9333"
./weed filer -master=localhost:9333
浏览器访问 master 服务器: http://192.168.1.24:9333
可以看到一些基本信息:
- Volume Size Limit 每个卷大小 64MB
- free 剩余卷数量 400
- Max 最大卷数量 400
- 也就是目前有 64*400 = 25G 对象存储大小
浏览器访问filer服务器 http://192.168.1.24:8888
拖动文件上传一个大小为 395M 的视频(2023-09-07-20-32-34.mp4)
回到 master 网页,看到 volume 有如下变化:
- Free 剩余 393 个卷,也就是 master 为 filer 文件服务器一次性分配了 7个卷,总计 7*64 = 448M 大小
- master 每次默认分配 7 个卷,如果用完会再次分配 7个
- 7 个卷被分摊到端口号为 8081、8082、8084 的卷服务器中,如何分配是由master自主管理的
- 8081 有3个卷,卷id为 2 4 7
- 8082 有3个卷,对应卷id为 1 3 5
- 8083 没有被分配卷
- 8084 有1个卷,卷id为 6
- 每个卷大小 64M
- 如果此时这3台卷服务器任意一台掉线,或者 disk1、disk2、disk4 任意一个硬盘故障,数据将无法访问
因此需要在 Seaweedfs 中配置纠删码,提高可用性。
在配置纠删码之前,我们需要了解什么是数据块,因为纠删码是根据数据块进行编码解码的。
数据块(chunks)
master 分配 7 个卷的卷 id 为 1~7,每个卷 64M 大小,总计 448M。
刚刚上传的文件大小有 395M,可以推断它已经把 6 个卷都占满了,只剩最后一个卷没占满,我们可以用指令查看 filer 文件服务器对于该文件的分配情况:
# 这个指令会导出 filer 目录下所有文件信息,把结果下载到当前目录 fs.json 文件中
curl -H "Accept: application/json" http://192.168.1.24:8888/?pretty=y -o fs.json
这是 fs.json 文件的内容:
"Path": "",
"Entries": [
{
"FullPath": "/2023-09-07-20-32-34.mp4",
"Mtime": "2023-10-26T00:26:30+08:00",
...
"chunks": [
{
"file_id": "5,06ed294c0f",
"size": 4194304,
"modified_ts_ns": 1698251187647289116,
"e_tag": "VVvWUbQ+4E6gSL6yWq/Jjw==",
"fid": {
"volume_id": 5,
"file_key": 6,
"cookie": 3978906639
}
},
...
{
"file_id": "4,5eac18c4e1",
"offset": 394264576,
"size": 729504,
"modified_ts_ns": 1698251190710407585,
"e_tag": "uVx8HBfAD4i/FwZUvQnzOw==",
"fid": {
"volume_id": 4,
"file_key": 94,
"cookie": 2887304417
}
}
...
其中, chunks 代表的就是一个数据块,每个数据块大小为4M "size": 4194304
,数据块由 filer 自动拆分,可以用指令 ./weed filer -h
查看 filer 服务默认拆分的块大小:
-maxMB int
split files larger than the limit (default 4)
由于 fs.json
内容太长,我们可以用一个 python 脚本统计用了多少个数据块。
import json
f = open('./fs.json')
fs = json.load(f)
chunks = fs['Entries'][0]['chunks']
print("chkuns num:", len(chunks))
total_size = 0
for chkun in chunks:
total_size += chkun['size']
print("total size:", total_size)
# output:
chkuns num: 95
total size: 394994080
可见它使用了 95 个数据块,每个 4M ,总计约等于(最后一个块没写满) 95*4*1024*1024 = 394994080
(395M)
将大文件拆分成数据块有许多好处:
- 便于纠删码编码和解码,校验数据
- 减少计算机一次性加载的数据量,降低内存和计算需求
- 分组有利于检索,提高效率
- 提高并发性,意味着可以同时处理大量数据
纠删码(Erasure-coding)
纠删码(erasure coding,EC)是一种数据保护方法,它将数据分割成片段,把冗余数据块扩展、编码,并将其存储在不同的位置,比如磁盘、存储节点或者其它地理位置。
Seaweedfs纠删码架构
官方介绍:
SeaweedFS implemented 10.4 Reed-Solomon Erasure Coding (EC). The large volumes are split into chunks of 1GB, and every 10 data chunks are also encoded into 4 parity chunks. So a 30 GB data volume will be encoded into 14 EC shards, each shard is of size 3 GB and has 3 EC blocks.
For smaller volumes less than 10GB, and for edge cases, the volume is split into smaller 1MB chunks.
EC 分块策略
seaweedfs 除了 filer 会把文件分块,EC 模块也会把卷分割成许多不同的数据块(chunks):
- 如果卷超过 10G,EC 会把卷分割成一个个 1GB 的数据块
- 如果卷小于 10G ,则会分割成 1M 的数据块。
- 与 filer 不同的是, filer 是基于单个文件分割数据块,而 EC 是基于卷分割数据块
RS(10,4) 纠删码策略
- 假设一个卷大小是30G, EC 会把卷拆分30个成 1G 的数据块
- 每 10 个1G数据块为一组,做一次 Erasure Coding ,生成 4个校验块,得到 14个块为一组 RS(10,4)纠删码数据
- 在这一组 RS(10,4) 数据中,丢失任何4个数据块,都能完整恢复数据
- 无论丢失的是数据块还是校验块
- 30G 数据块就被编码成3组 RS(10,4) ,总计 42 个数据块
- 所以一个卷 30G 会被编码成 14 个EC分片(EC shards),每个 EC 分片含有 3G 数据块,或者含有3个EC校验块,总计 42G
以上作者的假设是每个卷大小为 30G,而我为了方便测试,卷大小设置为 64M,下面将启用 EC 看看数据会发生什么变化
启用 Erasure-coding
启用 EC 的方式是运行 ./weed scaffold -config=master >> master.toml
,在当前路径生成一个 master.toml
文件,在文件中:
[master.maintenance]
# periodically run these scripts are the same as running them from 'weed shell'
scripts = """
lock
ec.encode -fullPercent=50 -quietFor=1h
ec.rebuild -force
ec.balance -force
volume.deleteEmpty -quietFor=24h -force
volume.balance -force
volume.fix.replication
s3.clean.uploads -timeAgo=24h
unlock
"""
sleep_minutes = 17 # sleep minutes between each script execution
scripts 就是它的启动脚本,由于它是按时间间隔定时执行的,为了方便触发,我们进入 shell 后台,手动触发:
# 进入管理后台
./weed shell
# 查看帮助
help
lock
# 启用 EC ,将使用量大于 50% 的 volume ,最近2s内没有更新的冷数据使能 EC, -quietFor 可选择 1h0m0s
ec.encode -fullPercent=50 -quietFor=2s
ec.rebuild -force
# 均匀分配 EC 碎片
ec.balance -force
volume.deleteEmpty -quietFor=24h -force
volume.balance -force
volume.fix.replication
s3.clean.uploads -timeAgo=24h
unlock
- Free 剩余数据卷由原来的 393 变成了 390,多了2个卷
- volume id 原来有 1~7个卷,现在全部没有了
- EC纠删码分片(ErasureCodingShards) 由原来的 0 变成了总计 98 个:
- 8081 分配了 23 个 EC 纠删码分片
- 8082 分配了 26 个EC 纠删码分片
- 8083 分配了 26 个EC 纠删码分片
- 8084 分配了 23 个EC 纠删码分片
- RS(10,4) 表明我们丢失任意 4/10 个EC 纠删码分片,数据仍然可以正常访问。
- 从目前的服务中,允许 4/10 * 98 = 32 个 EC 纠删码分片丢失,也就是可以容忍一个 disk 损坏,或者说容忍一个 volume server 下线。
随意下线一个 volume 测试:
数据依然可以正常访问
但是下线两个 volume 就无法访问了。
边缘情况
从上面的例子中,我们可以发现 fullPercent
是定义容量大于 50% 的卷才会被编码。
ec.encode -fullPercent=50 -quietFor=2m
假设我的卷大小是 64M ,文件大小是 395M,filer 分配的 7 个卷中,虽然是平均分的,但必然有些卷分配多一点,有些卷分配少一点
在 EC 编码之前,我每一个卷的分布情况如下:
- 8081:
- volume id 2 68M
- volume id 4 56M
- volume id 7 44M
- 8082:
- volume id 1 48M
- volume id 3 40M
- volume id 5 72M
- 8083
- null
- 8084
- volume id 6 48M
可以看到,文件被拆分到每一个卷的占比不同,假如 ec.encode -fullPercent=50
设定的百分比不是50,而是 95 或 80 ,那 EC encode 并非能对所有卷进行编码。
万一卷该卷恰好掉线,数据就无法访问。
如果想让所有的文件实现容错恢复,可使用 ec.encode -collection t -quietFor 1s -fullPercent=0.001
触发,对所有 1s 内无更新的卷进行编码。
不过,这不是最优策略:
- 每次编码解码会带来占用CPU计算
- 占用磁盘 IO 操作过多
- 用户访问大量文件会很慢
冷热分离
为了避免对所有数据都进行编码校验,作者存储策略参考了 《Facebook ’s Warm BLOB Storage System》 的一些想法:
热数据是指访问频率较高、经常被更新的数据,所以使用备份可以提高数据的访问速度。
而冷数据的访问频率较低,使用 erasure coding 可以节省存储空间,同时仍然保证数据的可靠性。
对于冷数据使用 ec.encode
,默认 fullPercent
是 95,-quietFor
是 1h0m0s ,也就是卷容量超过 95% 且 1小时内没有更新的卷,进行 EC 编码。
而对于其他的卷,使用数据备份。
同时启用编码和数据备份后,如果该卷数据“变冷”,编码操作仅会对源副本生效,编码成功后,其他副本会自动删除。
If the volume is replicated, only one copy will be erasure encoded. All the original copies will be purged after a successful erasure encoding.
热数据备份
关闭所有服务器,并删除测试数据,启用备份策略
rm -rf disk1/* disk2/* disk3/* disk4/* master filerldb2
# 开启 master 服务器,-defaultReplication 启用备份
./weed master -mdir=$PWD/master -volumeSizeLimitMB=64 -defaultReplication=001
# 设置 volume 服务器数据中心为 dc1 ,8081、8082机架为 rack1,8083、8084 为 rack2
./weed volume -port=8081 -dir=$PWD/disk1 -max=100 -mserver="localhost:9333" -dataCenter=dc1 -rack=rack1
./weed volume -port=8082 -dir=$PWD/disk2 -max=100 -mserver="localhost:9333" -dataCenter=dc1 -rack=rack1
./weed volume -port=8083 -dir=$PWD/disk3 -max=100 -mserver="localhost:9333" -dataCenter=dc1 -rack=rack2
./weed volume -port=8084 -dir=$PWD/disk4 -max=100 -mserver="localhost:9333" -dataCenter=dc1 -rack=rack2
./weed filer -master=localhost:9333
defaultReplication
的配置说明参考作者文档Replication
我使用的是 001 备份策略:备份一次在同一机架上。
在实际使用过程中,一个机架通常意味着很多台实体服务器,每台服务器都可以配置多块硬盘。由于我只在一台机器上测试,所以用不同端口区分。
- 这张图代表我有一个 dc1 数据中心,有两个机架(rack1和rack2),每个机架上有两台 volume 服务器
# 查看每个磁盘的初始大小
du -b disk*
4132 disk1
4132 disk2
4132 disk3
4132 disk4
随意上传一个 395M 的视频
再次查看磁盘变化,可以看到存储容量是大约是 790M,刚好是文件的两倍
du -b disk*
130759792 disk1 # 8081 rack1
130759792 disk2 # 8082 rack1
264052148 disk3 # 8083 rack2
264250668 disk4 # 8084 rack2
390M 文件被 master 安排在 rack1 上存储 130M ,在 rack2 上存储 130M;同时备份策略要求“在同一机架上复制一次”,所以 disk2 和 disk4 获得了相同的副本。
这种情况下,任意掉线一个节点,由于副本的存在,依然可以正常读写。
冷数据封存
我们假定刚刚的数据为冷数据,将它进行 EC 编码
./weed shell
lock
# 启用 EC ,将使用量大于 95% 的 volume ,最近2分钟内没有更新的冷数据编码
ec.encode -fullPercent=95 -quietFor=2s
ec.rebuild -force
ec.balance -force
volume.deleteEmpty -quietFor=2s -force
volume.balance -force
volume.fix.replication
s3.clean.uploads -timeAgo=24h
unlock
编码后架构解析
💡 编码前:
虽然在 master 中设置了卷大小为 64M,但是每个存满的卷,并非严格按照 64M 保存,可能会向上浮动 20M
💡 编码后:
disk1 EC 分片
磁盘数据总共为 641M 相对于 编码前 790M 少了 150M
du -b disk*
160444532 disk1
160444532 disk2
144709880 disk3
176167160 disk4
💡 解析
架构原理参考前面章节:Seaweedfs纠删码架构
- 卷 id 1、2、4、6 因为存储空间大于 64M * 95 %,所以每个卷被拆分成 10 个1M的原始数据块为一组
- 对这 10 个数据块EC编码成 4 个校验块,每个卷共计 14个块,4个卷共计56个块(也叫做 56个纠删码分片 ErasureCodingShards)
- 将 56 个纠删码分片均匀地分布式存储在卷服务器中,同时清除原始卷及备份副本。
- 举例:
- 编码前卷1 是76M,向上取整 80M
- 拆分成 10 个1M的原始数据块
- 对 10 个数据块进行 RS(10,4) 编码,得到 4 个校验块,总计 14 个块,统称为纠删码分片(ErasureCodingShards),每个分片 8M
- 卷6编码前64M,向上取整 70M,进行 RS(10,4) 编码得到 14 个纠删码分片(Shard Size) 每个分片 7M
✨注意,虽然我在 master 中将卷大小设置成了 64M ,但 volume server 并非严格按照 64M 来划分,卷大小会在 64M 上浮动 20M 左右
以卷服务器8081 disk1 为例,它存储了原始卷id为 1、2、4、6的纠删码分片(Shards):
同理,disk2、disk3、disk4中,也存储了不同卷中各自剩余的分片。
硬盘/卷id | disk1 | disk2 | disk3 | disk4 | 分片大小 |
---|---|---|---|---|---|
voulme 1 | [1 5 9 13] | [2 6 10] | [3 7 11] | [0 4 8 12] | 8M |
voulme 2 | [3 7 11] | [0 2 6 10] | [4 8 12] | [1 5 9 13] | 8M |
voulme 3 |
|
| 没有被编码 | ||
voulme 4 | [1 3 7 11] | [2 6 10] | [5 9 13] | [0 4 8 12] | 7M |
voulme 5 |
|
| 没有被编码 | ||
voulme 6 | [2 6 10] | [1 5 9 13] | [3 7 11] | [0 4 8 12] | 7M |
存储合计 | (7*8+7*7) =105+48.1 =154M | (7*8+7*7) =105+48.1 =154M | (6*8+6*7) =105+48 =138M | (6*8+6*7) =105+48 =138M |
在shell中查看存储分布,与计算一致:
du -h disk*
154M disk1
154M disk2
139M disk3
169M disk4
下线硬盘测试
根据里德所罗门纠删码 RS(10,4): N = K + M
- N 是 EC 纠删码分片 14,K 是原始数据块 10 个,M 是容忍丢失数量 4
- 即存在纠删码分片大于等于 10,则数据可以恢复
- 对于我这个 395M 的视频来说,总共用到了 7 个卷,4个卷被 EC 编码,剩余两个卷没有被编码,但各自有2份副本。
- 总共 56个 EC 纠删码分片,4:14 = x:56,可容忍16个分片丢失
- 但是备份策略在同一机架只有2个副本,因此同一个机架不允许同时两台卷服务器
单独下线任意一个硬盘
单独下线 8081:
访问正常
单独下线8082、8083、8084 访问正常
下线两个硬盘
下线 rack1 和一个 rack2
无法访问
下线两个 rack1,
无法访问
下线两个 rack2