Kafka原理和实践
Kafka部署方案选择
- 操作系统
- I/O模型的选择:I/O模型:阻塞IO、非阻塞IO、IO多路复用、信号驱动IO、异步IO几种模型。Java Socket对象的阻塞&非阻塞模式对象前两种;Linux的select函数属于IO多路复用;epoll系统调用介于第三四种模型之间;第五种模型Linux少有支持,Windows由OCP线程模型。Kafka客户端底层使用了Java的selector实现,selector在Linux上实现机制是epoll,Windows上实现机制是select。因此Kafka部署在Linux上更有优势,I/O性能更强。
- 零拷贝支持:从磁盘读取数据发送到网络当中去经过几个阶段,磁盘→内核缓存区→应用程序内存→socket缓冲区→网络,使用内存映射文件技术可以用应用程序逻辑地址映射内核缓冲区地址,避免内核缓冲区和应用程序内存间的数据拷贝。使用零拷贝技术,可以直接把内核缓冲区的数据映射到Socket缓冲区,网卡读取数据发送到网络中时,直接读取的是内核缓冲区数据。Linux平台支持零拷贝机制,Windows在后期才支持。
- 社区支持:社区对Window支持比较差,对bug修复没有承诺。
- 磁盘类型:机械硬盘还是固态硬盘,机械硬盘就可以了,Kakfa的顺序读写操作避免了机械硬盘随机读写慢的问题
- 磁盘容量:需要根据新增消息数量、消息留存时间、平均消息大小、备份数、压缩方式估算容量
- 带宽:一般是1Gbps千兆网络或者10Gbps万兆网络,根据sla(多长时间处理多少大小的消息)估算服务器数量,跨机房传输另算
分区策略
轮询策略
随机策略
按消息键保序策略
保证消息不丢失
kafka对已提交的消息做有限度的持久化保证
已提交的消息:kafka的若干broker成功接收到消息写入文件后,会告诉生辰着已经成功提交。
有限度的持久化保证:N个broker至少有一个存活
消息丢失的场景
生产者丢失数据:
- 由于生产者逻辑,网络等原因导致发送失败,可以使用带有callback的send接口做异常重试。
- 由于broker故障导致发送失败,需要处理broker端问题
不管怎么说,上述场景的消息都是未提交的,kafka无法保证消息不丢失
消费者丢失数据: - 先处理消息,再提交offset的的模式,如果消息处理到一半失败了,offset已经提交了就无法再消费该消息了,消息没有完整地被处理,即被丢失了
保证消息不丢失的最佳实践
- 使用带有callback的send方法,做好重试
- ack级别由三种,分别是0,1,all,0是不等待服务器确认,1(默认模式)是等待leader确认,all是等待ISR中所有副本写入成功确认
- broker端设置落后太多的follower不允许竞选成为leader
- replication.factor副本因子多配置一些,最好大于等于3
- 消费端关闭自动提交,自动提交会以一定的频率定时在后台做提交
- 自动提交的问题
- 重复消费:处理完了还未ack,服务崩溃或者rebalance,消息被重新拉取处理
- 消息丢失:排队处理未处理就commit,此时服务崩溃,消息已经commit不会再被消费
- 自动提交的问题
- 引申:ISR机制,ack级别设置为all的情况下,如果有一台broker挂了,那么生产者生产消息可能由于一台机器无法应答而一直无法ack,为了避免这种情况,采用了ISR机制,如果某台follower长时间无响应会被踢出ISR
todo:kafka批量消费的设计方式,多线程+滑动窗口;kafka延迟队列的设计方式
生产者TCP连接管理
为什么选择TCP而不是HTTP?
- 能够使用TCP提供的高级功能,比如多路复用请求(复用长连接)及同时轮询多个连接的能力
- TCP协议头更小,高吞吐下消耗更小
- TCP可自定义序列化方式,HTTP只能使用纯文本格式(如json)
管理TCP连接的方式
- KafkaProducer实例创建的时候启动Sender线程,创建与boostrap.servers中所有broker的tcp连接(虽然不那么合理)
- KafkaProducer实例首次更新元数据信息之后,还会再次创建与集群中所有Broker的TCP连接
- Producer端发送消息到某台Broker时发现没有连接也会建连
- 如果Producer端connections.max.idle.ms参数大于0,1中创建的TCP连接会被自动关闭;如果该参数=-1,1中创建的TCP连接不会关闭,会成为僵尸连接
Kafka生产者幂等&事务
幂等和事务是同两个能力,Kafka默认只支持至少一次的语义
幂等
效果:保证topic的一个分区上不出现重复消息
方式:设置producer的enable.idempotence为true
原理:
- 给每个producer分配唯一的producerId
- 每条消息附带上一个递增的sequence number
- Broker端检查sequence number,重复的消息被丢弃
事务
效果:跨分区,跨topic的原子性写入,确保“要么都成功,要么都失败”
方式:
- 开启幂等
- 设置事务id
transaction.id - 使用
initTransaction() → beginTransaction() → send → commitTransaction()方式发送消息
原理:Kafka会记录每个事务的状态,并写入一个内部事务日志,只有提交成功的事务,消费者才能读取对应的消息