最近一直在看 fabric, fabric 目前使用的共识算法是 kafka + zookeeper,最近查了些资料,先大概了解下这个算法。这是第一篇,主要介绍 kafka。
来源及设计目标
Kafka 是一种分布式的,基于发布/订阅的消息系统,原本开发自 LinkedIn,它以可水平扩展和高吞吐率等特点被广泛使用。
Kafka 主要设计目标如下:
- 以时间负责度为 O(1) 的方式提供消息持久化能力,即使对 TB 级以上数据也能保证常数时间负责度的访问性能。
- 高吞吐率。即使在非常廉价的商用机器上也能做到单机支持每秒 100K 条以上消息的传输。
- 支持 Kafka Server 间的消息分区,及分布式消费,同时保证每个 Partition 内的消息顺序传输。
- 同时支持离线数据处理和实时数据处理。
- 支持在线水平扩展,broker 数量越多,集群吞吐率越高
架构
- Broker Kafka 集群包含一个或多个服务器,这种服务器被称为 broker
- Topic 每条发布到 Kafka 集群的消息都有一个类别,这个类别被称为 Topic。(物理上不同 Topic 的消息分开存储,逻辑上一个 Topic 的消息虽然保存于一个或多个 broker 上但用户只需指定消息的 Topic 即可生产或消费数据而不必关心数据存于何处)
- Partition Parition 是物理上的概念,每个 Topic 包含一个或多个 Partition。producer 可以决定将消息发送到哪个 partition(依靠指定 partition 或者 key),这种设计提高了 kafka 的吞吐率。
- Producer 负责发布消息到 Kafka broker
- Consumer 消息消费者,向 Kafka broker 读取消息的客户端。
- Consumer Group 每个 Consumer 属于一个特定的 Consumer Group(可为每个 Consumer 指定 group name,若不指定 group name 则属于默认的 group)
Topic 和 Partition
每个 topic 类似一个 queue,不过 topic 可以分多个 partition,每个 partition 对应一个目录,目录下是消息文件。partition 的设计,使消息被分开存储,在每个 partition 内属于顺序写磁盘,因此效率很高,提高了吞吐率。
另外 kafka 集群会保留所有的消息,无论是否被消费,但是因为磁盘有限制,kafka 提供了两种策略删除旧数据,一种是基于时间,一种是基于 partition 文件大小。
Consumer Group
kafka 的消费组是一个很有意思的概念,可以看下面这个图:
同一个 Topic 的一条消息只能被同一个 group 内的一个 consumer 消费,但是多个 consumer group 可同时消费这条消息。
这样的设计可以实现下面这些功能:
- 消息的广播(发送给所有 consumer)。只要每个 consumer 有一个独立的 group 就可以。
- 消息的单播(只有一个 consumer 会收到消息)。只要将所有 consumer 放到一个 group 内即可。
- 同时提供离线和实时处理。比如用 Storm 对消息进行实时在线处理,用 Hadoop 对消息离线处理,同时将消息备份到另一个数据中心,这种情况只需要将三种不同的 consumer 放到不同的 group 内即可。
消息投递语义
- At most once 消息可能会丢,但绝不会重复传输
- At least one 消息绝不会丢,但可能会重复传输
- Exactly once 每条消息肯定会被传输一次且仅传输一次
分区选择策略
Producer 可以通过分区选择策略来选择将消息提交到 topic 的哪个 partition 内,通过指定 partiton 参数或者 key。
分区选择策略分为两种:
- 消息的key为 null
如果 key 为 null,则先根据 topic 名获取上次计算分区时使用的一个整数并加一。然后判断 topic 的可用分区数是否大于 0,如果大于 0 则使用获取的nextValue
的值和可用分区数进行取模操作。 如果 topic 的可用分区数小于等于 0,则用获取的nextValue
的值和总分区数进行取模操作(其实就是随机选择了一个不可用分区)。
- 消息的key不为 null
不为 null 的选择策略很简单,就是根据 hash 算法murmur2
就算出 key 的 hash 值,然后和分区数进行取模运算。
所以:
- 如果不手动指定分区选择策略类,则会使用默认的分区策略类。
- 如果不指定消息的 key,则消息发送到的分区是随着时间不停变换的。
- 如果指定了消息的 key,则会根据消息的 hash 值和 topic 的分区数取模来获取分区的。
- 如果应用有消息顺序性的需要,则可以通过指定消息的 key 和自定义分区类来将符合某种规则的消息发送到同一个分区。同一个分区消息是有序的,同一个分区只有一个消费者就可以保证消息的顺序性消费。
kafka 与 zookeeper
zookeeper 管理 kafka 集群配置,选举 kafka leader,以及 rebalance 等操作。
在旧版里,zookeekper 还负责消费的 offset 的管理,不过这增加了 zookeeper 的负担,所以在新版里,offset 的管理被移动到了 kafka 集群的一个叫 __consumer_offsets 的 topic 内。
这里单独说下,leader。每个 topic 的每个 partition 都有一个 leader,producer 发布的消息最开始只存到 leader 内,leader 再跟其他 follower 同步消息,所以这简化了 producer 的操作。而如果 leader 出了问题,zookeeper 会根据维护的 ISR (in-sync replica) 来选择一个合适的副本作为新的 leader,注意哦,这里不是传统的少数服从多数的算法,是 zookeeper leader 直接选取的(其实 zookeeper 不是单独一个节点,也是一个集群,他们之间的同步和选举是使用的 ZAB (Zookeeper Atomic Broadcast) 算法,这个以后的 zookeeper 文章中再说)。
扩展 - 为何使用消息系统
解耦
在项目启动之初来预测将来项目会碰到什么需求,是极其困难的。消息系统在处理过程中间插入了一个隐含的、基于数据的接口层,两边的处理过程都要实现这一接口。这允许你独立的扩展或修改两边的处理过程,只要确保它们遵守同样的接口约束。
冗余
有些情况下,处理数据的过程会失败。除非数据被持久化,否则就造成丢失。消息队列把数据进行持久化直到它们已经被完全处理,通过这一方式规避了数据丢失风险。许多消息队列采用的“插入-获取-删除”范式中,在把一个消息从队列中删除之前,需要你的处理系统明确的指出该消息已经被处理完毕,从而确保你的数据被安全的保存直到你使用完毕。
扩展性
因为消息队列解耦了你的处理过程,所以增大消息入队和处理的频率是很容易的,只要另外增加处理过程即可。不需要改变代码、不需要调节参数。扩展就像调大电力按钮一样简单。
灵活性 & 峰值处理能力
在访问量剧增的情况下,应用仍然需要继续发挥作用,但是这样的突发流量并不常见;如果为以能处理这类峰值访问为标准来投入资源随时待命无疑是巨大的浪费。使用消息队列能够使关键组件顶住突发的访问压力,而不会因为突发的超负荷的请求而完全崩溃。
可恢复性
系统的一部分组件失效时,不会影响到整个系统。消息队列降低了进程间的耦合度,所以即使一个处理消息的进程挂掉,加入队列中的消息仍然可以在系统恢复后被处理。
顺序保证
在大多使用场景下,数据处理的顺序都很重要。大部分消息队列本来就是排序的,并且能保证数据会按照特定的顺序来处理。Kafka 保证一个 Partition 内的消息的有序性。
缓冲
在任何重要的系统中,都会有需要不同的处理时间的元素。例如,加载一张图片比应用过滤器花费更少的时间。消息队列通过一个缓冲层来帮助任务最高效率的执行——写入队列的处理会尽可能的快速。该缓冲有助于控制和优化数据流经过系统的速度。
异步通信
很多时候,用户不想也不需要立即处理消息。消息队列提供了异步处理机制,允许用户把一个消息放入队列,但并不立即处理它。想向队列中放入多少消息就放多少,然后在需要的时候再去处理它们。
扩展 - 消息系统的 Push 和 Pull
Push 和 Pull 各有优缺点,这里直接看一张图:
还可以将两者结合起来:
PUSH和PULL两种模式结合
将信息推送与拉取两种模式结合能做到取长补短,使二者优势互补。根据推、拉结合顺序及结合方式的差异,又分以下四种不同推拉模式:
- 先推后拉——先由信源及时推送公共信息,再由用户有针对性地拉取个性化信息;
- 先拉后推——根据用户拉取的信息,信源进一步主动提供(推送)与之相关的信息;
- 推中有拉——在信息推送过程中,允许用户随时中断并定格在感兴趣的网页上,以拉取更有针对性的信息;
- 拉中有推——根据用户搜索(即拉取)过程中所用的关键字,信源主动推送相关的最新信息。
参考
http://www.infoq.com/cn/articles/kafka-analysis-part-1
https://www.jianshu.com/p/d3e963ff8b70
https://leokongwq.github.io/2017/02/27/mq-kafka-producer-partitioner.html
https://blog.csdn.net/u013256816/article/details/80300225
http://forlan.iteye.com/blog/2372496
https://www.cnblogs.com/smartloli/p/6266453.html
https://www.biaodianfu.com/push-pull.html
https://www.jianshu.com/p/5b15cbb88b51