存储设备都有吞吐量限制,会影响集群性能和可扩展性,所以存储系统一般都支持条带化(把连续的信息分片存储于多个设备)以增加吞吐量和性能。数据条带化最常见于 RAID 中, RAID 中最接近 Ceph 条带化方式的是 RAID 0 、或者条带卷, Ceph 的条带化提供了像 RAID 0 一样的吞吐量、像 N 路 RAID 镜像一样的可靠性、和更快的恢复。
当遇到大文件处理时,条带化到一个对象集中的多个对象能带来显著的读/写性能提升。当客户端把条带单元并行地写入相应对象时,带宽明显提升,因为对象映射到了不同的 pg、并进一步映射到不同 OSD ,可以并行地以最大速度写入。到单个磁盘的写入受限于磁头移动(如:6ms 寻道时间)和存储设备带宽(如:100MB/s), Ceph把写入分布到多个对象(它们映射到了不同 pg 和 OSD ),这样可减少每设备寻道次数、联合多个驱动器的吞吐量,以达到更高的写(或读)速度。
在下图中,客户端数据条带化到一个对象集(下图中的 object set 1 ),它包含 4 个对象,其中,第一个条带单元是 object 0 的 stripe unit 0 、第四个条带是 object 3 的 stripe unit 3 ,写完第四个条带,客户端要确认对象集是否满了。如果对象集没满,客户端再从第一个对象起写入条带(下图中的 object 0 );如果对象集满了,客户端就得创建新对象集(下图的 object set 2 ),然后从新对象集中的第一个对象(下图中的 object 4 )起开始写入第一个条带( stripe unit 16 )。
三个重要变量决定着 Ceph 如何条带化数据:
• 对象尺寸(object-size): Ceph 存储集群里的对象有最大可配置尺寸(如 2MB 、 4MB 等等),对象尺寸必须足够大以便容纳很多条带单元、而且应该是条带单元的整数倍。
• 条带宽度(stripe-unit): 条带都有可配置的单元尺寸(如 64KB )。 Ceph 客户端把数据等分成适合写入对象的条带单元,除了最后一个。条带宽度应该是对象尺寸的分片,这样对象才能 包含很多条带单元。
• 条带数量(stripe-count): Ceph 客户端把一系列条带单元写入由条带数量所确定的一系列对象,这一系列的对象称为一个对象集。客户端写到对象集内的最后一个对象时,再返回到第一个。
ref: http://docs.ceph.org.cn/architecture/#index-15
评分模型
本文的目标为,通过设计评分模型对不同硬件环境的 ceph 集群进行测试,最终通过脚本得到最高分时对应的 stripe_unit & stripe_count 的值。
采用 fio 对 sata、ssd-journal 、ssd 三种环境进行压力测试。测试块大小(bs)分别为 4k、8k、16k…512k,分别测试这些 bs 的顺序读/写、随机读/写。unit 分别取 4k、8k、16k…2048k,count 分别取 16、32、64、128。
1. 对于不同 bs 的顺序读,取其中 iops 的最大值作为基准线(1.0),所有块的权值为当前 iops 除以最大值(<=1.0)。顺序写、随机读/写以此类推。这样我们就得到了某一种读写方式时,不同 bs 的权值。
2. 定义「普通场景」(bs = 4k,与 bs = 8k…. 权重都相同,为了均衡评分设为 (1+0.3)/2=65%),「小 IO 场景」(4k 权重 100%,8k 权重 90%,16k 权重 80%,类推),「大 IO 场景」(512k 权重 100%, 256k 权重 90% …. 类推)。
3. 定义「普通读写场景」(读写测试权重相同,为了均衡评分设为 50%), 「多读少写场景」(读权重 70%, 写权重30%),「多写少读场景」(写权重70% , 读权重 30%)。
4. 根据上述定义可以计算出,不同硬件环境在不同场景下,某两个 unit、count 值对应的评分。进而得出不同场景下,评分最高时,这两个参数对应的值分别是多少。
决策:
1. 通常情况下,采用「普通 IO 场景」+「普通读写场景」获得的最后评分,决定 stripe_unit & stripe_count 的值。
2. 现在大部分情况为虚机使用,通常使用「小 IO 场景」+「多写少读场景」评分,决定 stripe_unit & stripe_count 值。
3. 其余结果在特定客户使用环境中,由解决方案架构师根据客户的具体应用场景,根据本次测试评分结果进行决策,决定 stripe_unit & stripe_count 值。
编写 python 脚本按上述模型跑分,得到结果如下表所示:
下表为,当取到最高得分时,count,unit 与 最高得分的值。
结果分析
性能影响指标:
1.当条带过小后,原先在连续的 LBA 地址写入的大块数据,现在需要拆分成小块数据后,在发送至 OSD 进行写入,该工作在 librbd 库中完成。例如:将条带单元设置为 4k,则一次连续的 64k 写入(在条带单元 >64k 时,只需要向 OSD 发送一个 64k 大小的写入请求即可)需要向多个 OSD 发送 16 个 4k 大小的写入请求,这将会严重影响写入性能(当前 OSD 处理每次请求都将消耗一定 CPU 的计算能力,并且请求越多,越容易造成各水线的请求队列积压,从而影响性能)。
2.当条带过大后,原先在临近范围内写入的小块数据,将有可能都是向同一个对象进行写入,而由于现在 librbd 层 readv, writev 接口还在实现中,所以这些不连续,但写入相同对象的请求将会发送至同一个 object 进行处理。
此时会出现明显的数据写入热点。例如:一个 4M 的 object,有可能短时间内出现 300 次连续的 4k 更新(例如:虚拟机 page cache flush 操作),这 300 次操作将落入同一个 OSD,同一个 PG,同个 Object 进行处理,而现在 OSD 水线分片是已 PG 为粒度进行的,这将会短时间内严重阻塞该 PG 的请求处理质量,假设每个请求的处理为 2ms,那么 300 次请求在最后一个请求被开始处理时,至少已经等待了 600ms,从而同时影响该 PG 小的其它对象请求处理速度,而在数据重平衡,以及数据 scrubing 的时候,情况将会更糟糕。
根据原理介绍,与上述分析,选取合理的条带宽度,条带数量是重要的。合理的配置将有效的增加集群的处理带宽,以及请求的处理质量,提升性能。所以我们通过 fio 测试,设计不同场景进而对各个硬件环境的各个场景进行评分,选出最高分时 stripe_unit & stripe_count 的值。
无论对于普通环境还是虚机环境,建议设置如下:
(作者:闫创,UnitedStack)