150倍加速机械盘,UCloud云主机IO加速技术揭秘
现如今CPU的计算能力和磁盘的访问延迟之间的差距逐渐扩大,使得用户云主机的磁盘IO经常成为严重的性能瓶颈,云计算环境下更加明显。针对机械盘IO性能低下的问题,我们通过自研的云主机IO加速方案,使4K随机写的最高性能由原来的300 IOPS提升至4.5W IOPS,提高了150倍,即用机械盘的成本获得了SSD的性能。13年上线至今,该方案已历经五年的运营实践,并成功应用于全网93%的标准型云主机,覆盖12.7万台实例,总容量达26PB。
为什么需要IO加速
传统的机械磁盘在寻址时需要移动磁头到目标位置,移动磁头的操作是机械磁盘性能低下的主要原因,虽然各种系统软件或者IO调度器都致力于减少磁头的移动来提高性能,但大部分场景下只是改善效果。一般,一块SATA机械磁盘只有300左右的4K随机IOPS,对于大多数云主机来说,300的随机IOPS哪怕独享也是不够的,更何况在云计算的场景中,一台物理宿主机上会有多台云主机。因此,必须要有其他的方法来大幅提升IO性能。
早期SSD价格昂贵,采用SSD必然会带来用户使用成本的提升。于是,我们开始思考能否从技术角度来解决这个问题,通过对磁盘性能特性的分析,我们开始研发第一代IO加速方案。即使今天SSD越来越普及,机械盘凭借成本低廉以及存储稳定的特点,仍然广泛应用,而IO加速技术能让机械盘满足绝大多数应用场景的高IO性能需求。
IO加速原理及第一代IO加速
我们知道机械磁盘的特性是随机IO性能较差,但顺序IO性能较好,如前文中提到的4K随机IO只能有300 IOPS的性能,但其顺序IO性能可以达到45000 IOPS。
IO加速的基本原理就是利用了机械磁盘的这种性能特性,首先系统有两块盘:一块是cache盘,它是容量稍小的机械盘,用来暂时保存写入的数据;另一块是目标盘,它是容量较大的机械盘,存放最终的数据。
IO的读写
写入时将上层的IO顺序的写入到cache盘上,因为是顺序的方式写入所以性能非常好,然后在cache盘空闲时由专门的线程将该盘的数据按照写入的先后顺序回刷到目标盘,使得cache盘保持有一定的空闲空间来存储新的写入数据。
为了做到上层业务无感知,我们选择在宿主机内核态的device mapper层(简称dm层)来实现该功能,dm层结构清晰,模块化较为方便,实现后对上层体现为一个dm块设备,上层不用关心这个块设备是如何实现的,只需要知道这是一个块设备可以直接做文件系统使用。
按照上述方式,当新的IO写入时,dm层模块会将该IO先写入cache盘,然后再回刷到目标盘,这里需要有索引来记录写入的IO在cache盘上的位置和在目标盘上的位置信息,后续的回刷线程就可以利用该索引来确定IO数据源位置和目标位置。我们将索引的大小设计为512字节,因为磁盘的扇区是512字节,所以每次的写入信息变成了4K数据+512字节索引的模式,为了性能考虑,索引的信息也会在内存中保留一份。
读取过程比较简单,通过内存中的索引判断需要读取位置的数据是在cache盘中还是在目标盘中,然后到对应的位置读取即可。
写入的数据一般为4K大小,这是由内核dm层的特性决定的,当写入IO大于4K时,dm层默认会将数据切分,比如写入IO是16K,那么就会切分成4个4K的IO;如果写入数据不是按照4K对齐的,比如只有1024字节,那么就会先进行特殊处理,首先检查该IO所覆盖的数据区,如果所覆盖的内存区在cache盘中有数据,那么需要将该数据先写入目标盘,再将该IO写入目标盘,这个处理过程相对比较复杂,但在文件系统场景中大部分IO都是4K对齐的,只有极少数IO是非对齐的,所以并不会对业务的性能造成太大影响。
索引的快速恢复与备份
系统在运行过程中无法避免意外掉电或者系统关闭等情况发生,一个健壮的系统必须能够在遇到这些情况时依然能保证数据的可靠性。当系统启动恢复时,需要重建内存中的索引数据,这个数据在cache盘中已经和IO数据一起写入了,但因为索引是间隔存放的,如果每次都从cache盘中读取索引,那么,数据的恢复速度会非常慢。
为此,我们设计了内存索引的定期dump机制,每隔大约1小时就将内存中的索引数据dump到系统盘上,启动时首先读取该dump索引,然后再从cache盘中读取dump索引之后的最新1小时内的索引,这样,就大大提升了系统恢复的启动时间。
依据上述原理,UCloud自研了第一代IO加速方案。采用该方案后,系统在加速随机写入方面取得了显著效果,且已在线上稳定运行。
第一代IO加速方案存在的问题
但随着系统的运行,我们也发现了一些问题。
1.索引内存占用较大
磁盘索引因为扇区的原因最小为512字节,但内存中的索引其实没有必要使用这么多,过大的索引会过度消耗内存。
2.负载高时cache盘中堆积的IO数据较多
IO加速的原理主要是加速随机IO,对顺序IO因为机械盘本身性能较好不需要加速,但该版本中没有区分顺序IO和随机IO,所有IO都会统一写入cache盘中,使得cache堆积IO过多。
3.热升级不友好
初始设计时对在线升级的场景考虑不足,所以第一代IO加速方案的热升级并不友好。
4.无法兼容新的512e机械磁盘
传统机械磁盘物理扇区和逻辑扇区都是512字节,而新的512e磁盘物理扇区是4K了,虽然逻辑扇区还可以使用512字节,但性能下降严重,所以第一代IO加速方案的4K数据+512字节索引的写入方式需要进行调整。
5.性能无法扩展
系统性能取决于cache盘的负载,无法进行扩展。
第二代IO加速技术
上述问题都是在第一代IO加速技术线上运营的过程中发现的。并且在对新的机械磁盘的兼容性方面,因为传统的512字节物理扇区和逻辑扇区的512n类型磁盘已经逐渐不再生产,如果不对系统做改进,系统可能会无法适应未来需求。因此,我们在第一代方案的基础上,着手进行了第二代IO加速技术的研发和优化迭代。
1.新的索引和索引备份机制
第一代的IO加速技术因为盘的原因无法沿用4K+512Byte的格式,为了解决这个问题,我们把数据和索引分开,在系统盘上专门创建了一个索引文件,因为系统盘基本处于空闲状态,所以不用担心系统盘负载高影响索引写入,同时我们还优化了索引的大小由512B减少到了64B,而数据部分还是写入cache盘,如下图所示:
其中索引文件头部和数据盘头部保留两个4K用于存放头数据,头数据中包含了当前cache盘数据的开始和结束的偏移,其后是具体的索引数据,索引与cache盘中的4K数据是一一对应的关系,也就是每个4K的数据就会有一个索引。
因为索引放在了系统盘,所以也要考虑,如果系统盘发生了不可恢复的损坏时,如何恢复索引的问题。虽然系统盘发生损坏是非常小概率的事件,但一旦发生,索引文件将会完全丢失,这显然是无法接受的。因此,我们设计了索引的备份机制,每当写入8个索引,系统就会将这些索引合并成一个4K的索引备份块写入cache盘中(具体见上图中的紫色块),不满4K的部分就用0来填充,这样当系统盘真的发生意外也可以利用备份索引来恢复数据。
在写入时会同时写入索引和数据,为了提高写入效率,我们优化了索引的写入机制,引入了合并写入的方式。
当写入时可以将需要写入的多个索引合并到一个4K的write buffer中,一次性写入该write buffer,这样避免了每个索引都会产生一个写入的低效率行为,同时也保证了每次的写入是4K对齐的。
2.顺序IO识别能力
在运营第一代IO加速技术的过程中,我们发现用户在做数据备份、导入等操作时,会产生大量的写入,这些写入基本都是顺序的,其实不需要加速,但第一代的IO加速技术并没有做区分,所以这些IO都会被写入在cache盘中,导致cache盘堆积的IO过多。为此,我们添加了顺序IO识别算法,通过算法识别出顺序的IO,这些IO不需要通过加速器,会直接写入目标盘。
一个IO刚开始写入时是无法预测此IO是顺序的还是随机的,一般的处理方式是当一个IO流写入的位置是连续的,并且持续到了一定的数量时,才能认为这个IO流是顺序的,所以算法的关键在于如何判断一个IO流是连续的,这里我们使用了触发器的方式。
当一个IO流开始写入时,我们会在这个IO流写入位置的下一个block设置一个触发器,触发器被触发就意味着该block被写入了,那么就将触发器往后移动到再下一个block,当触发器被触发了一定的次数,我们就可以认为这个IO流是顺序的,当然如果触发器被触发后一定时间没有继续被触发,那么我们就可以回收该触发器。
3.无感知热升级
第一代的IO加速技术设计上对热升级支持并不友好,更新存量版本时只能通过热迁移然后重启的方式,整个流程较为繁琐,所以在开发第二代IO加速技术时,我们设计了无感知热升级的方案。
由于我们的模块是位于内核态的dm层,一旦初始化后就会生成一个虚拟的dm块设备,该块设备又被上层文件系统引用,所以这个模块一旦初始化后就不能卸载了。为了解决这个问题,我们设计了父子模块的方式,父模块在子模块和dm层之间起到一个桥梁的作用,该父模块只有非常简单的IO转发功能,并不包含复杂的逻辑,因此可以确保父模块不需要进行升级,而子模块包含了复杂的业务逻辑,子模块可以从父模块中卸载来实现无感知热升级:
上图中的binlogdev.ko就是父模块,cachedev.ko为子模块,当需要热升级时可以将cache盘设置为只读模式,这样cache盘只回刷数据不再写入,等cache盘回刷完成后,可以认为后续的写入IO可直接写入目标盘而不用担心覆盖cache盘中的数据,这样子模块就可以顺利拔出替换了。
这样的热升级机制不仅实现了热升级的功能,还提供了故障时的规避机制,在IO加速技术的灰度过程中,我们就发现有个偶现的bug会导致宿主机重启,我们第一时间将所有cache盘设置为只读,以避免故障的再次发生,并争取到了debug的时间。
4.兼容512e机械磁盘
新一代的机械磁盘以512e为主,该类型的磁盘需要写入IO按照4K对齐的方式才能发挥最大性能,所以原先的4K+512B的索引格式已经无法使用,我们也考虑过把512B的索引扩大到4K,但这样会导致索引占用空间过多,且写入时也会额外占用磁盘的带宽效率太低,所以最终通过将索引放到系统盘并结合上文提到的合并写入技术来解决该问题。
5.性能扩展和提升
在第一代的IO加速技术中只能使用1块cache盘,当这块cache盘负载较高时就会影响系统的性能,在第二代IO加速中,我们设计了支持多块cache盘,并按照系统负载按需插入的方式,使加速随机IO的能力随着盘数量的提升而提升。在本地cache盘以及网络cache盘都采用SATA机械磁盘的条件下,测试发现,随着使用的cache盘数量的增多,随机写的性能也得到了大幅度的提升。
在只使用一块本地cache盘时,随机写性能可达4.7W IOPS:
在使用一块本地cache盘加一块网络cache盘时,随机写性能可达9W IOPS:
在使用一块本地cache盘加两块网络cache盘时,随机写性能可达13.6W IOPS:
目前,我们已经大规模部署应用了第二代IO加速技术的云主机。得益于上述设计,之前备受困扰的cache盘IO堆积过多、性能瓶颈等问题得到了极大的缓解,特别是IO堆积的问题,在第一代方案下,负载较高时经常触发并导致性能损失和运维开销,采用第二代IO加速技术后,该监控告警只有偶现的几例触发。
写在最后
云主机IO加速技术极大提升了机械盘随机写的处理能力,使得用户可以利用较低的价格满足业务需求。且该技术的本质并不单单在于对机械盘加速,更是使系统层面具备了一种可以把性能和所处的存储介质进行分离的能力,使得IO的性能并不受限于其所存储的介质。此外,一项底层技术在实际生产环境中的大规模应用,其设计非常关键,特别是版本热升级、容错以及性能考虑等都需要仔细斟酌。希望本文可以帮助大家更好的理解底层技术的特点及应用,并在以后的设计中做出更好的改进。