Go在后端开发中的实际思路3 | 青训营笔记

110 阅读6分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的第5篇笔记

Kafka

接下来谈一谈消息队列,所以在这里只谈一谈Kafka。

消息队列的应用场景不用多讲了,上下游 解耦、流量削峰、异步处理等等根据实际场景去使用就好了。

先说一说消息队列会遇到的一些常见问题吧。比如消息丢失、消息重复发送、消息重试机制、消息顺序性、消息重复消费

在Kafka中消息出现丢失的情况极低,因为Kafka是保证了至少一次的发送机制。只要是在HW以内的offset,Kafka默认已经持久化到了硬盘中,所以在消费HW以内的offset消息,不会出现消息丢失的情况。

Kafka提供了消息发送的ACK机制,这个ACK机制有三个值可以选择。

  • 当ACK=0的时候,即消息发送到了leader即确认发送成功,此时并不知道其他replica是否已经将消息持久化了没有,这种情况下极有可能出现消息发送了但是丢失的情况。因为如果此时leader节点宕机,其他replica会竞选leader,当某一个replica竞选了leader以后,Kafka内部引入了leader epoach机制进行日志截断,此时如果该replica并没有同步到leader接收到这一条消息,那么这条消息就会丢失。

  • 当ACK=1的时候,即消息发送到了该partition下的ISR集合内的所有replica内。当ISR集合中有多个replica存在,即使此时leader所在的节点宕机,也不会存在消息丢失的情况。因为partition下的leader默认是从ISR集合中产生的,而此时ISR集合内的所有replica已经存储了该条消息,所以丢失的可能性几乎为零。

  • 当ACK=-1的时候,即消息发送到了该partition下的所有replica内。不管leader所在的节点是否宕机,也不管该ISR下的replica是否只有一个,只要该parition下的replica超过一个,那么该消息就不会丢失。

在日常情况下,我们默认ACK=1,因为ACK=0消息极有可能丢失,ACK=-1消息发送确认时间太长,发送效率太低。

对于消息重复发送的问题,我建议从消费端进行去重解决。因为对于producer端,如果出现了消息发送但是没有接收到ACK,但实际上已经发送成功却判断消息发送失败,所以重复发送一次的场景,Kafka也束手无策。不过可以开启事务机制,确保只发送一次,但是一旦开启事务,Kafka的发送消费能力将大打折扣,所以不建议开启事务。

在Kafka中,producer端每发送的一条消息,都会存在对应topic下的partition中的某个offset上。消息发送必须指定topic,可以指定某个partition,也可以不指定。当partition不指定时候,某个topic下的消息会通过负载均衡的方式分布在各个partition下。因为只有同一个parititon下的消息是有序的,所以在给有多个partition的topic发送消息的时候不指定partition,就会出现消息乱序的情况。

Kafka的通过topic对消息进行逻辑隔离,通过topic下的partition对消息进行物理隔离,在topic下划分多个partition是为了提高consumer端的消费能力。一个partition只能被一个consumer端消费,但是一个consumer端可以消费多个partition。每个consumer端都会被分配到一个consumer group中,如果该consumer group组中只有一个consumer端,那么该consumer group订阅的topic下的所有partition都会被这一个consumer端消费。如果consumer group组的consumer端个数小于等于topic下的partition数目,那么consumer group中的consumer端会被均匀的分配到一定的partition数,有可能是一个partition,也有可能是多个partition。相反,如果consumer group组的consumer端个数大于topic下的partition数目,那么consumer group中将会有consumer端分不到partition,消费不到数据。

在实际应用场景中,通常在consumer group中设置与partition数目对等的consumer端数。确保每个consumer端至少消费一个partition下的offset消息。

Kafka集群的每一个服务称作broker,多个broker中会通过zookeeper选举出一个controller处理内部请求和外部操作。但是数据真正的读写操作都发生在partition上,partition归属于某个topic下,为了防止数据丢失,partition一般会设置多个,每一个称作replica。每个partition都会从多个replica中选举出一个partition leader,负责处理数据的写操作和读操作。其他的replica负责于leader交互,进行数据的同步。同一个partition下的多个replica会均匀的分布在不同的broker中。因此在设计上,我们可以发现,实际上Kafka的消息处理是负载均衡的,基本上每个broker都会参与进来。partition的leader默认是从ISR集合中选举产生的。ISR全名是In Sync Replica,意思是已经于leader的消息保持一致的Replica。如果在一定时间内,或者一定数目的offset内,replica没有与leader的offset保持一致,那么就不能存在于ISR集合中,就算之前存在ISR集合中,也会被踢出去。等待一段时间后,消息及时同步了,才有机会加入到ISR集合中。因此,从ISR集合中选举leader在一定程度上是为了保证在leader重新选举的时候消息也能保证同步一致,不会丢失。

因为Kafka中引入了consumer group机制,所以能很大程度上提高consumer端的消费能力。但是也因为consumer group的rebalance机制,会让consumer端的消费产生短暂性的不可用。问题是这样的,因为consumer group中存在一个叫coordinate的均衡器,负责将partition均匀的分配到consumer group的每个consumer端中。如果consumer group中consumer端有添加或者减少,那么partition就需要重新分配,这个时候,该consumer group下的所有consumer端都会停止消费,等待coordinate给他重新分配新的partition。consumer端和partition越多,这个等待时间就越长。因此,不建议topic下的partition设置的过多,一般在20个以内。