138

RAID 6 应用于消息队列

 6 years ago
source link: https://zhuanlan.zhihu.com/p/32141275?
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

RAID 6 应用于消息队列

problem solver

The Problem

Kafka 是一个很流行的消息队列。但是在使用中,我们发现目前的消息队列设计仍然有改进的空间

v2-231edd17a36b4e4ff7d847fe6cf5cb34_720w.webp

问题出在从在线业务系统往kafka master写入的过程中。kafka给业务系统提出了两难的选择

  • 当往kafka master的网络出问题了。或者kafka master自身在选主的时候。要么业务系统选择放弃latency,去等待故障修复
  • 要么业务系统选择丢数据。在kafka master不可用的时候把消息给扔掉

这两个选择都很艰难。kafka虽然号称是一个分布式的系统,但是对于单partition的写入仍然是单点的。

解决办法也很简单。就是如果partition 1写入失败,就去写partition 2。不同partition的master可以是不同的节点。或者可以同时写多个partition,一个成功就算成功。代价是放弃了单partition的消息有序性,以及更多的消息冗余而且消费方要做更多的努力去保持幂等。

v2-991cc3c812d8d395bd6f6049b0c1d5db_720w.webp

第二个问题在master和replica之间的复制。如果不等任何一个replica复制成功就返回,master挂了就会丢数据。如果需要replica,则显著增加写入的延迟,并且占用更多的资源。

我们来看看是否可以应用 RAID6 的磁盘存储技术到消息队列上。

冲突是可以避免的

我们先来看是否可以把中心化的master节点给干掉?形成这样的部署结构呢?

其中写入代理和在线业务系统部署在同一台机器上。当然写入代理也可以是写入的sdk,做为lib嵌入到在线业务系统里。读取代理和离线系统部署在同一台机器上,也可以是读取的sdk,做为lib嵌入到离线系统里。

但是这样会引起一个很严重的问题,offset冲突。kafka通过把给定partition的数据交给唯一的一个master机器来写入来分配这个offset。没有了这个中心化的master,怎么保证多个在线业务系统的机器在写入的时候不会产生offset冲突呢?

这个offset可以使用机器的timestamp的nanosecond。这个选择听起来很糟糕对不对?

  • 不要求多个写入机器之间的绝对有序。本来前序的处理就是乱序的负载均衡的。多个机器生产的数据严格排序大部分场景上下没有收益。使用timestamp可以使得总体上来说基本有序。
  • nanosecond timestamp分配的offset是为了尽量避免多个producer写入到同一个offset,产生写入冲突。
  • nanosecond是十亿分之一秒。也就是说按照大部分业务生产的速度来说,冲突的概率是很小的。
  • 如果写入发生了冲突,则offset+一个random的整数,然后重试。

然后问题就是怎样知道写入冲突了

每个replica对应到一个offset就是一个洞。offset与offset之间不需要连续。如果是6个replica,那么每个offset就有6个洞要填。每个repica保证每个洞只能被写入一次。那么多个写入方对于同一个offset就是争抢这6个洞。比如6个写入成功了4份就可以认为写入成功了。因为其他的producer只剩2个洞可以用,它们在这个offset肯定写不成功的。

但是如果一个producer写成功了3个洞,另外一个producer写成功了3个洞,这样怎么办?这种情况就是两个producer都写入失败了。这个offset就算作废了。读取的时候不能只读一个洞就认为读到了值。读取也要读到了4份完全一致的数据才算读取成功了。

这样的好处就是如果是要写6份,写成功了4份就算成功。可以并行地启动6份写入,然后只要等到4份ACK了就可以认为写入成功了,剩下的2份异步处理就可以了。一方面容忍了三分之一机器的故障。同时也容忍了三分之一机器出现写入慢的长尾问题。最大化的利用了不把鸡蛋放到一个篮子里的好处。

但是消费方怎么办?它如何能读取到所有producer写入的数据呢?毕竟producer之间的时钟不是完全同步的。有可能读到了offset10024之后,之前的offset10018又被写入了,这条消息不就被漏掉了吗?解决办法就是延迟读取。只要滞后500ms就可以cover大部分NTP不同步引起的问题了。对于大部分准实时和离线业务来说,500ms的延迟读取不算什么大的问题。另外一个办法就是让读取的窗口重叠。比如之前是[0, 500ms], [500ms, 1000ms],现在就改成[0, 400ms], [200ms, 600ms], [400ms, 800ms],以读取放大的代价来避免漏掉消息。

完全逐条的硬实时大部分情况下就是个伪需求。micro batch is good enough。micro batch比逐条的模型,减少了单个offset产生的contention。

复制是可以避免的

基于多副本复制的另外一个问题是因为复制引起的磁盘,网络和cpu的消耗。如果复制6份写入,4份成功就算成功的话,1份数据就要冗余出5份来做写入。虽然通过并发写入降低了延迟,但是总的写入放大仍然是非常可观的。对于一些价值可能并不高的数据,也许不希望有这么多副本。这个也是kafka设计ISR的模式的出发点,希望尽可能少的减少副本数量,降低成本。

如何既保持6副本,但是减少总的写入放大呢?也许我们可以尝试RAID 6,通过reed solomon编码,把一份消息切成6份,存入6个节点里。只要4份写入成功了,就算写入完了。这样写入放大的成本仅仅是reed solomon编码带来的冗余。而且实际需要同步等待写入的只是6份中的4份而已。

reed solomon编码的问题是编码慢,解码慢。编码可以用SSE进行优化,比如klauspost/reedsolomon

解码慢可以用多个消费方共享同一份拷贝来解决。本质上是一份读缓存。这份缓存不用考虑可用性,随时可以用原始的拷贝重建。

本文构想了一种降低消息队列写入延迟和提高可用性的方法。把kafka的单partition从CP系统变成了AP系统。同时减少了总体的持久存储成本。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK