主要概念
- 生产者push消息到kafka,消费者pull拉取消息
- kafka集群中的每个实例作为kafka的一个broker
- kafka中topic为逻辑概念,topic可分为多个partition
- partition为物理概念,表现为一台broker上面的一个文件夹,一个partition中的消息是分segment存储的
- 每个segment包含一个log文件和一个index文件,log文件为真正的消息存储文件,index文件为稀松索引
生产及持久化
流程
- 生产者生成消息key
- 生产者根据消息key获取partition-leader所在的broker
- 生产者push消息到partition-leader,artition-leader持久化成功
- 其余partition在partition-leader拉取消息同步到本地,如果成功,通知到partition-leader
- 所有的partition均成功了,partition-leader回复生产者成功
对于上述步骤,partition-leader回复生产者成功的触发条件是可以配置的,有三种策略:只要leader持久化成功就返回、至少一个从partition成功就返回、全部partition成功才返回
潜在的安全问题
- 重复提交消息1
如果采取的成功触发条件为至少一个从partition成功就返回或者全部partition成功才返回。
上述步骤中,假设步骤4进行到通知的partition-leader时候partition-leader所在的broker挂掉了,同时选取了已经持久化成功的从partition作为新的leader,就会出现重复消息,因为这时生产者会重新上报这条消息。
解决方式:kafka提供了配置,同一个生产者提交的重复key对应的消息会被忽略。
- 重复提交消息2
问题1的解决方式看起来很不错,但是如果生产者本身挂掉了再重启,kafka会为它生成新的PID,这样就有概率出现重复数据。
解决方式:kafka提供了事务功能,我们可以为生产者指定一个事务ID,生产者重启后去根据事务ID获取自身的PID。
- 消息遗漏
如果选择策略为只要leader持久化成功就返回同时从partition未同步partition-leader就挂掉了,这条消息就遗漏了。
解决方式:没有太好的办法,尽量不选择这个策略吧。
消费
- 每个消费者都在一个消费者组中,同一个消费者组中的消费者无法消费同一个partition,所以kafka支持广播和独播。
- 每个partition仅可供一个消费者消费,这就意味着partition的数量要至少比消费者多几倍才能尽可能均匀分布。
- 消费者每次pull一批数据。
- 消费者消费数据后可选择是否自动提交偏移量。如果选择是,那么kafka会定时提交偏移量,如果存在未到提交时间就挂了重启,会带来重复消费的问题;如果选择否,那么手动提交,如果拿到数据就提交偏移量存在消息未消费的问题,如果执行完成再提交存在重复消费的问题。
这个问题只能通过幂等性解决。
- 消费者如果发生变更,或者partition变更,或者消费者对应的topic变更,都会触发rebalance,过程中消费是停止的。
- 如果生产者开启了事务,消费者可以配置是否读取具备事务的生产者push但没有commit的数据。
为什么kafka快
- 写操作都是磁盘顺序写
当然,这个概念并不是说磁头不用动,每次写直接写磁盘就行了,实际上第一次寻址是避免不了的,只不过后续的写操作都是顺序io,不能神话这个特性,其实本身就是不需要随机写操作的,毕竟只是消息队列不用改消息。 - 读操作都是索引加顺序读
这个是正经的顺序读,首先读取一个序号为n的消息会现在稀松索引二分查找到小于等于n的索引,然后直接用文件的初始地址加上索引增量作为寻找起始点,后续只要顺序读,知道找到n对应的消息为止即可,这里可以用上磁盘的页缓存,是非常快的。 - 零拷贝
零拷贝是不可能的,严格来讲应该说是用户态无需拷贝,直接内核态直接拷贝至socket,java中的api为FileChannal.transferTo()。
为什么选择kafka
目前主流的队列是kafka和rocketmq。
kafka | rocketmq | |
---|---|---|
横向拓展 | 支持 | 支持 |
持久化性能 | 非常快 | 一般 |
广播 | 支持 | 支持 |
单播 | 支持 | 支持 |
分区支持 | 支持较少分区 | 支持大量分区 |
可靠性 | 一般 | 优秀 |
消费失败重试 | 不支持 | 支持 |
消息有序 | 单partition | 全局 |
消息查询 | 不支持 | 支持 |
消息者数量 | 小于等于partition数量 | 无限制 |
可以看到kafka除了大数据量的场景其它方面都是被rocket爆金币的,所以一般是用在数据真的非常大,比如日志这种场景下。