kafka如何保证有序性?看资料
需要保证有顺序的消息进入到一个分区中(这个用key来保证),要保证同一个分区中的消息是有序的(链表,新来的放后面就是了),同一个分区中的消息是顺序被消费的(分区分配与offset来保证)。
生产者
分区。每个主题topic有自己的分区。给业务定个topic。
分区器。根据key来确定进入哪个分区。一个key对应一个分区,保证有同样的key,就能保证进入到同一个分区中。分区数不变的时候,一个key对应一个分区,分区数变化会发生在手动修改配置更改分区数、数据迁移扩容的时候,这个时候一个key可能会分到别的分区,就不好保证顺序性了。
消费者:
分区分配策略。范围分配策略(Range Assignor,默认),轮询分配策略(RoundRobinAssignor)或粘性分配策略(StickyAssignor)等。 做分区与消费者组的映射,每个分区每次分配的时候,只能被一个消费者组消费。但是如果消费者没法送心跳,broker会认为消费者宕机,就会重新分配,那这个分区可能会被其他消费者消费。
offset偏移量。分区会保留消费者返回的偏移量。如果消费者切换了,从消费者1切换到消费者2,消费者2可以从offset开始消费,避免重复消费之前的消息,保证有序性。
分区大小管理
分区的清理策略有删除策略(Delete Policy)、压缩策略(Compact Policy)和日志清理策略(Log Cleaning Policy)等。默认是删除策略,删除策略有时间保留策略(默认是删除7天前的)、大小保留策略等。分区由多个片段组成,片段大小最大默认是1g,如果分区满了,会删除片段,从而释放内存。
集群化体现
Kafka其实天生就是为了集群而生,即使单个节点运行Kafka,他其实也是作为一个集群运行的。而Kafka为了保证在各种网络抽风,服务器不稳定等复杂情况下,保证集群的高性能,高可用,高可扩展三高,做了非常多的设计,核心都是为了保持整个集群中Partition内的数据一致性。
zookeeper中关于kafka集群最为主要的状态信息有两个。
一个是在多个Broker中,需要选举出一个Broker,担任Controller角色。由Controller角色来管理整个集群中的分区和副本状态。
另一个是在同一个Topic下的多个Partition中,需要选举出一个Leader角色。由Leader角色的Partition来负责与客户端进行数据交互。
ControllerBroker选举机制
Controller Broker负责选举和管理所有的Partition Leader
kafka集群启动时,broker会往zookeeper中创建controller,只有一个broker能注册成功。
LeaderPartition选举机制
Leader负责处理所有的读写请求,Follower进行数据的同步和备份。
在选举Leader Partition时,会按照AR中的排名顺序,靠前的优先选举。只要当前Partition在ISR列表中,也就是是存活的,那么这个节点就会被选举成为Leader Partition。
LeaderPartition自动平衡
尽量避免一个broker中有多个leaderPartition的情况,尽量保证partitionLeader是均匀分布到各个broker中的,避免单个broker性能过载。LeaderPartition选举机制后,容易出现不均衡的情况,kafka有提供自平衡操作,controller会定期去看各个broker的情况,然后进行平衡。
但自平衡的过程是一个非常重的操作,因为要涉及到大量消息的转移与同步。并且,在这个过程中,会有丢消息的可能。所以在很多对性能要求比较高的线上环境,会选择将参数auto.leader.rebalance.enable设置为false,关闭Kafka的Leader Partition自平衡操作,而用其他运维的方式,在业务不繁忙的时间段,手动进行Leader Partiton自平衡,尽量减少自平衡过程对业务的影响。
Partition故障恢复机制
保证broker主从数据一致性,不能保证数据安全,会丢失数据。rocketMQ在数据安全这一块就做的比较好。
每个partition都有一个LEO(Log End Offset):,就是每个Partition的最后一个Offset,当一个消息放入partition中时,leo会加一。一个broker中有多个partition,所有partiion中最小的leo就是HW(High Watermark)。hw之前的数据才能被消费者消费,并且是持久化了的。只有当所有follower完成数据同步后,leader才会向生产者发送确认ack。故障恢复与一致性就是以这个hw为基准的。
如果follwer宕机,follwer会获取hw,然后向leader同步hw后的数据,至于hw之前的数据,已经持久化甚至被消费了,可以不予理会。
如果leader宕机,zookeeper会监测到,然后通过watch机制通知controller,controller会选出新的leader,新的leader,新的leader获取hw,然后所有follower中hw之后的数据都会删除,旧的leader恢复后hw后的也会删除,然后作为新的follower。所以旧leader中hw之后的数据都会丢失。但这保证了数据一致性
Epoch更新机制
如何保证hw的一致性?hw是leader维护的,follower拉取的,如果leader宕机,有的follower还没拉取最新的hw,新的leader出现,可能会出现hw不一致的情况。所以用Epoch机制。每个leader有一个epoch版本号,并记录到本地文件中,新来的leader的epoch会加一。follower同步的时候,会对比自己的epoch和leader的epoch是否相等,如果小于,则说明leader更新了,要重新获取hw。
Epoch 英文是纪元的意思
Kafka的文件高效读写机制
文件结构
.log(用来存partition内容的,最大1g);.timeindex/.index(索引文件,方便快速定位)
顺序写
对每个Log文件,Kafka会提前规划固定的大小,这样在申请文件时,可以提前占据一块连续的磁盘空间。然后,Kafka的log文件只能以追加的方式往文件的末端添加(这种写入方式称为顺序写),这样,新的数据写入时,就可以直接往直前申请的磁盘空间中写入,而不用再去磁盘其他地方寻找空闲的空间(普通的读写文件需要先寻找空闲的磁盘空间,再写入。这种写入方式称为随机写)。由于磁盘的空闲空间有可能并不是连续的,也就是说有很多文件碎片,所以磁盘写的效率会很低。
零拷贝
sendfile:在broker把消息给consumer发过去的时候使用。数据可以直接从磁盘缓冲区通过网络传输,而不需要先将数据拷贝到应用程序的内存空间。
mmap:虽然Kafka的官方文档可能不直接提及mmap
,但Kafka确实通过类似mmap
的机制(如利用操作系统的页面缓存)来访问存储在磁盘上的日志文件和索引文件。这种方式允许应用程序直接访问内核缓冲区中的数据,而无需将数据从内核复制到应用程序的内存中,从而避免了不必要的数据复制。
日志清理
查看过期日志:每五分钟(默认,可自己设置)检查日志,如果日志保存时间超过7天(默认,可自己设置),就会认为过期
过期日志处理:删除或者压缩。压缩会导致消息丢失,一般不建议。删除的话还可以进行选择,如果所有文件大小大于域值(可设置),才进行删除操作。
操作系统来决定何时刷盘
partition等信息进行持久化。信息先到内存,内存是多久才fsync到磁盘呢?
kafka提供了3中方式来触发刷盘。一是消息追加到某个阈值,二是定时刷盘,三是创建新日志时触发刷盘。但kafka默认情况下,没有用上面的一二设为空。也就是说,Kafka采用异步刷盘方式,即将数据写入页缓存后,由操作系统决定何时将其写入磁盘。这种方式可以提高写入性能,但可能会降低数据的实时持久化能力。
优化建议
topic:1、尽量不要使用过多的Topic,通常不建议超过3个Topic。过多的Topic会加大索引Partition文件的压力。2、每个Topic的副本数不要设置太多。大部分情况下,将副本数设置为2就可以了。
partition数量:设置多一些可以一定程度增加Topic的吞吐量。但是过多的partition数量还是同样会带来partition索引的压力。因此,需要根据业务情况灵活进行调整,尽量选择一个折中的配置。
合理对数据进行压缩:有4中压缩策略,如果压缩了,消费者消费时需要解压
生产者:一般是acks设为1,表示leaderPartition收到消息后,无需等待同步到follower就会返回给生产者。然后开启幂等性,防止重复生产
消费者:一般要解决重复消费的问题,然后会在redis中存offset,如果redis的offset比当前消息的offset大,表示这个消息已经消费过了。
最好不要异步消费,因为如果业务逻辑异步进行,而消费者已经同步提交了Offset,那么如果业务逻辑执行过程中出现了异常,失败了,那么Broker端已经接收到了消费者的应答,后续就不会再重新推送消息,这样就造成了业务层面的消息丢失。