目录
Kafka为什么不支持减少分区
代码逻辑是可以实现,但是比较复杂,而且使用场景很少,完全可以新建一个topic去替代
第一,如果不保留原来分区的消息,可靠性得不到保障。
第二,把消息移到其他分区的话,如果直接存储到尾部则会破坏消息的时间戳排序,如果要做时间排序则会移动队列消息,消息很大的话,内部复制会占用很大资源。同时事务性,分区和副本问题都要考虑。
删除主题方式
第一,使用kafka.topics.sh脚本,会在zk生成一个标记节点,标记主题删除状态,然后kafka去删除。
第二,手动删除zk文件
Topic分区
两种方式操作topic:第一脚本,第二客户端api(可检验创建的topic是否符合要求)
分区follower不会提供服务,只是同步leader的数据
分区和leader的平衡,不代表集群的平衡。
分区平衡
1. 在生产环境中不建议将auto.leader.rebalance.enable设置为默认的true,因为会定时检查平衡率,自动做分区平衡,如果在业务高峰可能引起阻塞和超时。控制权在自己手里,通过监控然后手动平衡。
2. 正常情况下,优先副本作为leader节点,在机器宕机follower变成leader,重启后不会变成leader,造成分区不平衡。
3. 在实际生产环境中,一般使用 path-to-json-file 参数来分批、手动地执行优先副本的选举操作
下线机器或者扩容:执行分区重分配
分区限流控制节点复制数据的速率
设置分区数(分区数太多可能会超过系统文件句柄限制)
1. ulimit-n 65535 针对当前用户当前shell有效。
2. 在/etc/security/limits.conf文件中设置对所有用户所有shell有效,修改文件需要重启才能生效
3. 也可以通过在/etc/profile文件中添加ulimit的设置全局生效
分区数多少为好:不是越多越好,分区数少的时候增加会提升吞吐量,但到达一个阈值时不升反降。会受到磁盘,IO,系统调度的影响。太多分区可能超出文件句柄的限制,也会使Kafka的正常启动和关闭消耗更多时间,加重清除日志负担。
日志存储
每种文件同时切换Segment分片。一个topic的一个分区的一个副本有一个日志目录
日志索引
稀疏索引,每当写入一定量消息时(由broker端参数控制,默认4K),对应增加一个偏移量和时间戳索引项。
偏移量索引文件:偏移量到消息物理地址,单调递增,二分查找。找Segment分片文件是用跳跃表。
时间戳索引文件:时间戳到偏移量映射,单调递增,二分查找,需要回表查询。找Segment顺序查找,读每个时间戳文件的最大时间戳,没有就读文件更新时间
Segment分文件条件:日志文件和索引文件太大(默认日志文件是1G),分片文件最小时间戳与当前系统时间戳差值大于设置值,偏移量大于INT最大值
日志清理
有两个策略,可配置,delete和compact。有定时任务,默认每5分钟检查一次,要删除的话,在文件后面加.delete后缀,最后有一个延迟线程专门做日志删除
1.日志删除
1)基于时间。可以配置,默认是7天。
2) 基于日志大小。总的日志大小,.log文件,超过配置值则从第一个日志分段开始删除diff量。每个日志分段默认最大值是1G
3)基于日志起始偏移量。起始偏移量可配置,低于这个偏移量则删除
2.日志压缩
相同key,则保留最大版本的value,其余会删除。针对相同key的消息的合并清理。
日志存储直接使用磁盘,而不是内存,怎么保证速度
1,顺序读写,而不是随机读写,经过测试顺序读写速度比内存随机读写快
2,利用操作系统的磁盘页缓存功能实验高速读写。读写首先在缓存中操作,写时先写入缓存,读时从缓存查询,没有再读磁盘,读取磁盘后会放到页缓存。最后由操作系统刷盘,也可以手动异步或者强制刷盘,但是不建议手动。页缓存相比java虚拟机管理内存可以简化开发,也比虚拟机更安全,因为Kafka进程挂了重启还可以读取到页缓存的数据。而机器宕机导致数据未刷盘的问题,应该是由多副本来保障。但要警惕非活跃内存被交换到磁盘的情况,设置swap的积极程度不应该太大,也不应该是0。太小当内存不够用时,无法交换内存,系统不可用。设置为1可以保证系统内存不够用时跟磁盘做交换,也不会频繁做内存交换。
4种磁盘调度算法:
NOOP:FIFO队列,大致的先来后到顺序操作,相邻I/O地址会做请求合并
CFQ(默认):按照I/O请求的地址排序,最大限度地做顺序访问磁盘,但可能会带来“饥饿”
DEADLINE:在CFQ基础上,分别为读和写I/O提供了一个FIFO队列,读最大等待时间500ms,写最大等待时间5s,解决了“饥饿”问题。
ANTICIPATORY:上面几个是针对零散I/O,ANTICIPATORY为了满足随机I/O和顺序I/O的混合场景。为每个读设置6ms的等待时间窗口,如果6ms内收到了相邻位置的读I/O请求,则立即满足。通过读取/写入延迟换取最大的读写吞吐量。
3,零拷贝技术。零拷贝是说内核模式没有数据拷贝,使用DMA技术拷贝文件句柄到Buffer Socket里,然后直接把文件数据传递到网卡设备。如果走了拷贝数据到内核的方式,会产生四次数据拷贝,四次上下文切换。保存下图
深入服务器
定时任务:时间轮TimingWheel+延迟队列DelayQueue。时间轮复杂任务的增删,延迟队列负责时间推进,相辅相成。
控制器:监听主题、分区、broker的变化。更新集群元数据变化,读取和管理主题,分区,broker数据
优雅关闭:同步消息到磁盘,下一次上线就不用恢复日志。迁移leader副本,减少分区不可用时间。
可靠性探究(衡量可靠性:多少个9)
副本滞后Leader一定时间后会被移出ISR,默认是10秒,(或者滞后消息大小超过一定值,默认4000,由于很难界定大小,从0.9.x版本开始移除了),有参数可设置
引入LeaderEpoch也解决不了消息丢失,但可以避免消息不一致。
为什么不支持读写分离
因为有数据一致性和延迟问题。消息写入然后主从同步要经过:网络-主节点内存-主节点磁盘-网络-从节点内存-从节点磁盘。由于多副本设计,主读写已经具备负载均衡功能了,这样代码实现更简单,不容易出错,就没必要做主读从写了。可以通过运维,监控告警监控负载不均衡的情况。
负载不均的场景:
1.broker端分区分配不均,2.生产者写入消息不均,3.消费者消费消息不均,4.leader副本的切换不均。
针对第1种情况,创建主题时候尽量使分区均衡。第2和第3主写从读也无法解决。第4种可以使用优先副本选举来达到leader副本的平衡。
提高可靠性方法
1,生产者发送消息有三种方式:发后即忘(消息发送失败也不会收到通知),同步,异步。提高发送消息可靠性可以设置ack=-1或者all,让所有ISR里的副本都拉取成功到消息才返回生产者成功通知。ack=1代表主要leader写入成功就返回,如果此时leader宕机就有可能丢失消息。acks=0,不会等待服务器的响应。
2,发送消息时,对于可重试异常设置重试策略。比如NetworkException,如果设置了重试会自动做重试。重试次数参数retries,默认是0。重试间隔时间retry.backoff.ms。retries大于0可能引起负面影响,由于max.in.flight.requests.per.connection(该参数指定了生产者在收到服务器响应之前可以发送多少个消息。它的值越高,就会占用越多的内存,不过也会提升吞吐量。把它设为 1 可以保证消息是按照发送的顺序写入服务器的,即使发生了重试。)参数默认是5,重试影响消息的顺序性。如果retries大于0,那么max.in.flight.requests.per.connection要设置为1,这样放弃了吞吐。如果一些需要快速失败的场景,设置retries>0会造成延迟反馈。
3,提供isr副本数,min.insync.replicas。要求isr的副本数最少多少个,小于的话发送消息会抛出NotEnoughReplicasException。这里是可用性和可靠性的选择了,如果设置大于0,那么在isr为0,非ISR不为0的情况下也不可用。
4,是否可以从非ISR集合中选择leader:unclean.leader.election.enable。默认是false,不能从非ISR集合选择leader,选择了可靠性。
5,同步刷盘策略。log.flush.interval.messaages和log.fluseh.interval.ms。建议使用系统刷盘策略,同步刷盘操作损耗性能,可靠性由多副本来保障。
6,消费端参数。enable.auto.commit,开启自动位移提交。默认是true,可能会造成重复消费和消息丢失。为了高可靠,应该设置为false,只有消费完成才能提交位移,这样避免消息的丢失,但也可能会重复消费,要做幂等。如果某个消息一直消费不成功,为了不影响整体的消费进度,可以把这个消息放到死信队列。另外还有兜底功能:回溯消费,可以找回漏掉的消息。
数据导入导出,迁移:分区重分配是在单集群里做数据的复制,Mirror Maker是在不同集群之间做数据的迁移,通过消费和再生产的原理。可以从关系型数据库,NoSQL数据库,日志,文件中向Kafka导入和导出消息。
默认事务隔离级别是读未提交
计算消息滞后Lag
无事务或者事务隔离级别为未提交,则Lag = HW - ConsumerOffset
如果事务级别是读提交,则Lag = LSO - ConsumerOffset
LSO:对于未完成的事务,是事务消息的第一条消息偏移量。对于已完成的事务,等于HW
延迟队列的实现:Kafka原生不支持延迟,可以设置不同延迟级别队列来定时拉取消息,类似Rocketmq,或者用时间轮。现实中很少用到
死信队列:即消费者不能处理,或者不想处理,也或者是处理异常的消息。Kafka不存在,可以自己根据概念封装。
幂等性:Kafka 自 0.11 版本开始引入了幂等性和事务,Kafka的幂等性是指单个生产者对于单分区单会话的幂等,而事务可以保证原子性地写入多个分区,即写入多个分区的消息要么全部成功,要么全部回滚,这两个功能加起来可以让Kafka具备EOS (Exactly Once Semantic)的能力
参考:《深入理解Kafka核心设计与实践原理》