文章首发于公众号:松花皮蛋的黑板报
做者就任于京东,在稳定性保障、敏捷开发、高级JAVA、微服务架构有深刻的理解linux
1、介绍
Kafka在世界颇负盛名,大部分互联网公司都在使用它,那么它究竟是什么呢?算法
Kafka由LinkedIn公司于2011年推出,自那时起功能逐步迭代,目前演变成一个完整的平台级产品,它容许您冗余地存储巨大的数据量,拥有一个具备巨大吞吐量(数百万/秒)的消息总线,而且支持实时流任务处理。总的来讲,Kafka是一个分布式,可水平扩展,容错的日志提交系统数据库
这些描述都很是抽象,让咱们一个接一个地理解它们的意思,随后深刻探讨其工做原理apache
2、分布式
分布式系统意味着不一样机器上的服务实例一块儿工做成一个总体为用户提供完整的服务,Kafka的分布式体如今存储、接收信息、发送信息在不一样的节点上,它带来的好处是可扩展性和容错性数组
3、水平可扩展
咱们先给垂直可扩展下一个定义,好比说,你的传统数据库服务开始变得超负载,能够经过简单地扩充该服务器资源(CPURAMSSD)缓存这个问题,这就叫垂直扩展-单点增长资源,不过有两大体命的缺点:底层硬件资源有限、须要停机操做。反之,水平扩展经过增长更多的机器部署服务解决相似问题缓存
4、容错
分布式系统被设计成可允许必定程序的错误,不像单点部署发生异常时总体服务都将不可用,有五个节点的Kafka实例,即便有2个节点宕机了仍能继续工做服务器
5、commit日志
一个commit日记相似预写式日记(WAL)和事务日记,它是可追加的有序的持久化数据,没法进行修改或者删除网络
这种结构是Kafka的核心,它具有排序功能,而排序则能够保证肯定性的处理,这二者都是分布式系统中的重要问题session
Kafka一般会将消息持久化到磁盘上,它充分利用磁盘的有序读取特性,读写的时间复杂度都为O(1),这是至关了不得的,另外读取和写入操做不会相互影响,写入不会加锁阻塞读取操做架构
6、如何工做的
生产者发到消息至Kafka Node节点,存储在主题Topic中,消费者订阅主题以接收消息,这是一个生产订阅模式。为了使一个节点Topic的数据量不至过大,Kafka引入分区的概念,从而具有更好的性能和伸缩性。Kafka保证分区内的全部消息都按照到达顺序排序,区分消息的方式是经过其偏移量offset,你能够将其理解为普通数组的下标索引
Kafka中Broker服务节点是愚蠢的,消费者是聪明的,Kafka不会记录消费者读取的操做和删除消息,相反,数据被存储一段时间或者达到必定的大小阈值,消费者能够自由调整偏移量offset以重复获取他们想要的消息或者舍弃
值得注意的是为了不进程两次读取相同的消息,Kafka引入了消费者组的概念,其中包含一个或者多个消息者实例,约定每一个组只能同时有一个实例消费分区的消息。不过这引来了一个麻烦,连社区也无力解决,也就是Kafka中的重平衡Rebalance问题,它本质是一种协议,规定一个消费者组下的全部消费者实例如何达成一致,来分配订阅主题的每一个分区,当组成员数发生变动、订阅主题数发生变动、订阅主题的分区数发生变动时都会触发Rebalance,从而达到最公平的分配策略,不过他和GC的STW相似,在Rebalance期间,全部的消费者实例都会中止消费,而后从新分配链接。咱们应该尽可能避免这种状况的发生,尽可能让消费实例数等于分区数
7、持久化至磁盘
正如前面说起的,Kafk将消息存储至磁盘而不是内存RAM,你或许会惊讶它是如何作出这种选择的,背后应该有许多优化使其可行,没错,事实上优化点包括:
一、Kafka的通讯协议支持消息合并,减小网络流量传输,Broker节点一次持续存储大量数据,消费者能够一次获取大量的消息
二、操做系统经过提早读入(read-ahead)和write-behind缓存技术,使得磁盘上的线性读写速度快,现代磁盘速度慢的结论是基于须要磁盘搜索的场景
三、现代操做系统引入页面缓存(Page cache)技术,页缓冲由多个磁盘块构造,在linux读写文件时,它用于缓存文件的逻辑内容,从而加块对磁盘映射和数据的访问
四、Kafka存储消息使用的是不可变的标准二进制格式,能够充分利用零拷贝技术(zero-copy),将数据从页缓存直接复制到socket通道中
8、数据分布式和复制
咱们来谈谈Kafka如何实现容错以及如何在节点间分配数据
Kafka将分区数据拷贝复制到多个Brokers节点上,避免某个Broker死亡致使数据不可达。每时每刻,一个Broker节点”拥有”一个分区,而且是应用程序从该分区读取写入的节点,这称为分区leader,它将收到的数据复制到其余N个Broker节点上,它们称为follower,并准备好在leader节点死亡时被选举为leader。这种模式使得消息不易丢失,你能够根据消息的重要程序合理调整replication factor参数,下图是4个Broker节点,拥有3个复制副本的示例
你或许会有疑问,生产者或者消费者是如何正确得知分区的leader是哪一个节点的?事实上,Kafka将这些信息保存到Zookeeper服务中
9、Zookeeper服务
Zookeeper是一个分布式KV对目录存储系统,特色是可靠性高、读取性能高,可是写入性能差,常被用于存储元数据和保存集群状态,包括心跳、配置等等
Kafka将如下消息保存至Zookeeper中:
一、消费者组的每一个分区的偏移量,不事后来Kafka将其保存至内部主题__consumer_offsets中
二、访问权限列表
三、生产者和消费者速率限定额度
四、分区leader信息和它们的健康状态
10、Controller控制器
一个分布式系统确定是可协调的,当事件发生时,节点必须以某种方式作出反应,控制器负责决定集群如何作出反应并指示节点作某事,它是功能不能过于复杂的Broker节点,最主要的职责是负责节点下线和从新加入时重平衡和分配新的分区leader
控制器从ZooKeeper Watch事件中能够得知某个Broker节点实例下线(或者节点过时,通常发生于Broker长时间繁忙致使心跳异常)的状况,而后作出反应,决定哪些节点应成为受影响分区的新leader,而后通知每一个相关的follower经过leaderAndlsr请求开始重新的leader复制数据
从上面能够得知,本来做为分区leader的Broker节点实例重启后,它将再也不担任任何分区的leader,消费者也不会从这个节点上读取消息,这致使了资源的浪费,幸运的是,Kafka有一个被称为优先副本(preferred leader replica)的概念-你能够理解成原先为该分区leader节点(经过broker id区分)的副本,若是该副本可用,Kafka会将集群恢复成以前状态,经过设置auto.leader.rebalance.enabled=true可使得这个过程自动触发,默认值为true
Broker节点下线一般都是短暂的,这意味着一段时间后会恢复,这就是为何当一个节点离开集群时,与之关联的元数据不会被删除,若是它是一个分区的跟随者,系统也不会为此分区从新分配新的跟随者
可是须要注意的是,恢复加入的节点不能当即拿回其上次的leader地位,它尚未资格
11、ISR
副本同步队列ISR(in-sync replicas),它是由leader维护的,follower从leader同步数据是有延迟的,任意一个超过阈值都会被剔除出ISR列表, 存入OSR(Outof-Sync Replicas)列表中,新加入的follower也会先存放在OSR中
一个follower想被选举成leader,它必须在ISR队列中才有资格,不过,在没有同步副本存在而且已有leader都下线的边缘状况下,能够选择可用性而不是一致性
ISR列表维护标准以下:
一、它在过去的X秒内有完整同步leader消息,经过replica.lag.time.max.ms配置约定
二、它在过去的X秒内向Zookeeper发送了一个心跳,经过zookeeper.session.timeout.ms配置约定
12、生产者acks设置
明显,存在一系列意外事件会致使leader下线,假如leader节点接收到生产者的消息,在存储而且响应ack后节点崩溃了,此时Kafka会从ISR列表中选举一个新的leader,可是因为生产者ack配置默认为1,意思是只考虑leader接收状况不考虑follower同步状况,最终致使部分消息丢失了,因此咱们应该在生产者端设置acks=all,要求每条数据必须是写入全部副本以后,才能认为是写成功,另一层意思是起码有一个leader和一个follower。不过这种设置影响集群性能,下降了吞吐量,使得生产者须要在发送下一批消息以前等待更多时间
十3、水位
经过ack=all约定了leader节点在消息没有同步到全部的ISR列表前不会有任何返回,另外,节点会跟踪全部同步副本具备的最大偏移量,也就是高水位偏移量HW(high watermark offset),consumer没法消费分区下leader副本中偏移量大于分区HW的任何消息。当某个副本成为leader副本时、broker出现崩溃致使副本被踢出ISR时、producer向leader写入消息后、leader处理follower fetch请求时,都会尝试更新分区HW,从而保证了数据一致性和正常消费时不会出现读取到旧值
十4、脑裂
想象一下,当正常存活的controller控制器因为长时间GC-STW致使不可用,而后Zookeeper会认为/controller节点(节点3)已通过期随即删除并发送通知到其余broker节点,其余每一个broker节点都尝试升级为控制器节点,假设节点2从竞争中胜出成功新的控制器节点并在ZK中建立/controller节点
而后其余节点接收到通知,了解到节点2成为了新的控制器节点,除了还在GC暂停的节点3,或者通知压根没到达的节点3,也就是说节点3不知道leadership已经发生了变化,它还觉得本身是控制器节点。此时,同时存在两个控制器,并行发出可能存在冲突的命令,致使严重的后果
幸运的是,Kafka提供了epoch number的方式能够轻松区分出真实的控制器,它是自增加的序列号,信息存储在ZooKeeper中,显然序列号最大的那个节点才是真实的
十5、何时应该使用Kafka
从上面几点可知,Kafka能够成为事件驱动架构的中心部分,使你能够真正将应用程序彼此分离
你或许对Kafka中的时间轮算法、Kafka中的刚好一次交付等主题也感兴趣的话,欢迎前往www.liangsonghua.me阅读
文章翻译整理自
一、https://hackernoon.com/thorou...
二、https://hackernoon.com/apache...
文章来源:http://www.liangsonghua.me
做者介绍:京东资深工程师-梁松华,长期关注稳定性保障、敏捷开发、JAVA高级、微服务架构