kafka
设计目标
- 以时间复杂度为O(1)的方式提供消息持久化能力以及读取能力,即使对TB级以上数据也能保证常数时间复杂度的访问性能。
- 高吞吐率。即使在非常廉价的商用机器上也能做到单机支持每秒100K条以上消息的传输。
- 支持Kafka Server间的消息分区,及分布式消费,同时保证每个Partition内的消息顺序传输。
- 同时支持离线数据处理和实时数据处理(通过消费组实现)。
- Scale out:支持在线水平扩展。
相关知识点
Kafka以集群的方式运行,可以由一个或多个服务组成,每个服务叫做一个broker.
一个topic是对一组消息的归纳。对每个topic,Kafka 对它的日志进行了分区,分为多个partition。
每个分区在Kafka集群的若干服务中都有副本,这样这些持有副本的服务可以共同处理数据和请求,副本数量是可以配置的。副本使Kafka具备了容错能力。
每个分区都由一个服务器作为“leader”,零或若干服务器作为“followers”,leader负责处理消息的读和写,followers则去复制leader.如果leader down了,followers中的一台则会自动成为leader(通过zk实现)。
Producer将消息发布到它指定的topic中,并负责决定发布到哪个分区。通常简单的由负载均衡机制随机选择分区。
发布消息通常有两种模式:队列模式(queuing)和发布-订阅模式(publish-subscribe)。
队列模式一堆消费者消费队列中消息,只要有一个消费者消费了消息,该消息就算被消费完毕。
发布订阅模式,一个消息会被所有消费者订阅,被所有消费者消费一遍,不过实际应用中,一般都是订阅组,一个消息发给多个订阅组,每个订阅组中的一个消费者去处理一条消息。Kafka只能保证一个分区之内消息的有序性,在不同的分区之间是不可以的,这已经可以满足大部分应用的需求。如果需要topic中所有消息的有序性,那就只能让这个topic只有一个分区,当然也就只有一个consumer组消费它。
Kafka集群包含一个或多个服务器,这种服务器被称为broker。
对于传统的message queue而言,一般会删除已经被消费的消息,而Kafka集群会保留所有的消息,无论其被消费与否。但是在为了防止磁盘被耗尽,Kafka提供两种策略删除旧数据,一是基于时间,二是基于Partition文件大小。
高吞吐率保证
对于一条消息会被append到某一Partition中,顺序写磁盘,效率非常高(经验证,顺序写磁盘效率比随机写内存还要高,这是Kafka高吞吐率的一个很重要的保证)。Kafka在底层摒弃了Java堆缓存机制,采用了操作系统级别的页缓存,同时将随机写操作改为顺序写,再结合Zero-Copy的特性极大地改善了IO性能。当前消费消息的offset由Consumer控制。正常情况下Consumer会在消费完一条消息后递增该offset。当然,Consumer也可将offset设成一个较小的值,重新消费一些消息。因为offet由Consumer控制,所以Kafka broker是无状态的,它不需要标记哪些消息被哪些消费过,也不需要通过broker去保证同一个Consumer Group只有一个Consumer能消费某一条消息,因此也就不需要锁机制,这也为Kafka的高吞吐率提供了有力保障。
如果Partition机制设置合理,所有消息可以均匀分布到不同的Partition里,这样就实现了负载均衡。
消息重复相关
写消息
- 默认情况一条消息从Producer到broker是确保了At least once,可通过设置Producer异步发送实现At most once)
读消息
- 读完消息先commit再处理消息。这种模式下,如果Consumer在commit后还没来得及处理消息就crash了,下次重新开始工作后就无法读到刚刚已提交而未处理的消息,这就对应于At most once。
- 读完消息先处理再commit。这种模式下,如果在处理完消息之后commit之前Consumer crash了,下次重新开始工作时还会处理刚刚未commit的消息,实际上该消息已经被处理过了。这就对应于At least once。
- Kafka默认保证At least once,并且允许通过设置Producer异步提交来实现At most once。
高可用性
- Producer在发布消息到某个Partition时,先通过ZooKeeper找到该Partition的Leader,Leader会将该消息写入其本地Log。每个Follower都从Leader pull数据,为了提高性能,每个Follower在接收到数据后就立马向Leader发送ACK,而非等到数据写入Log中。因此,对于已经commit的消息,Kafka只能保证它被存于多个Replica的内存中,而不能保证它们被持久化到磁盘中,也就不能完全保证异常发生后该条消息一定能被Consumer消费。但考虑到这种场景非常少见,可以认为这种方式在性能和数据持久化上做了一个比较好的平衡。在将来的版本中,Kafka会考虑提供更高的持久性。
- Leader会跟踪与其保持同步的Replica列表,该列表称为ISR(即in-sync Replica)。如果一个Follower宕机,或者落后太多,Leader将把它从ISR中移除。
- Kafka只解决fail/recover,不处理“Byzantine”(“拜占庭”)问题。一条消息只有被ISR里的所有Follower都从Leader复制过去才会被认为已提交。
使用原因
- 解耦
- 冗余
许多消息队列所采用的”插入-获取-删除”范式中,在把一个消息从队列中删除之前,需要你的处理系统明确的指出该消息已经被处理完毕,从而确保你的数据被安全的保存直到你使用完毕。 - 扩展性
增大消息入队和处理的频率是很容易的 - 灵活性 & 峰值处理能力
- 可恢复性
即消费者挂了,恢复后继续消费。 - 顺序保证
kafka保证partition内有序。 - 异步通信
- 高吞吐率
其它
- push模式很难适应消费速率不同的消费者, push模式的目标是尽可能以最快速度传递消息。对于Kafka而言,pull模式更合适。pull模式可简化broker的设计,Consumer可自主控制消费消息的速率,同时Consumer可以自己控制消费方式——即可批量消费也可逐条消费。
RabbitMQ
ZeroMQ
ActiveMQ
Redis
参考
http://www.infoq.com/cn/articles/kafka-analysis-part-1/
http://www.infoq.com/cn/articles/kafka-analysis-part-2/