转载本文请联系LoveSmile的架构师公众号。大型互联网公司一般要求消息最大程度的无损投递。例如,用户服务向凭证服务发送消息。如果消息丢失,用户将收不到应有的凭证,最终用户会投诉。为了避免上述类似情况的发生,除了采取补偿措施外,还需要在系统设计时充分考虑各种异常情况,设计一个稳定、高可用的消息系统。要了解Kafka,请看一下维基百科的定义。Kafka是一个分布式的发布-订阅消息系统。它最初由LinkedIn公司开发,后来成为Apache项目的一部分。Kafka是一种分布式、可分区、冗余和持久化的日志服务。它主要用于处理活跃的流数据。Kafka架构Kafka的整体架构非常简单,它是一个显式的分布式架构,主要由生产者、代理(kafka)和消费者组成。Kafka架构(简化版)生产者(producer)可以发布数据到选定的主题(topic)。生产者负责将记录分配给主题的哪个分区。负载均衡可以简单地使用循环方法来完成,也可以根据一些语义分区函数(例如记录中的键)来完成。消费者(consumer)使用一个消费组(consumptiongroup)名称来标识,发布到topic的每条记录都分配给订阅消费组中的一个consumer实例。消费者实例可以分布在多个进程或多台机器上。Kafka会丢失消息吗?在讨论Kafka是否丢失消息之前,我们先了解一下什么是消息传递语义。Messagedeliverysemanticsmessagedeliverysemantics也就是消息传递语义,简单的说就是消息传递过程中消息传递的保证。主要分为三种:atmostonce:最多一次。消息可能会丢失或被处理,但最多一次。至少一次:至少一次。消息不会丢失,但可能会被多次处理。可以重复,不会丢失。恰好一次:恰好通过一次。消息只被处理一次。不会丢失或重复一次。理想情况下,肯定希望系统的消息传递严格一次,即保证不丢失,只处理一次,但很难实现。回到主角Kafka,Kafka有3个消息传递流程:Producer向KafkaBroker发送消息。KafkaBroker消息同步和持久化KafkaBroker将消息传递给消费者。在这三个步骤中的每一个步骤中,消息都可能丢失。下面将详细分析为什么会丢失消息,以及如何最大程度地避免丢失消息。生产者丢失消息。先介绍一下producer发送消息的大致流程(部分流程与具体配置项强相关,这里忽略):producer直接和leader交互,所以先获取topic的leader元数据来自集群的相应分区;获取leader分区的元数据后,直接发送消息过去;KafkaBroker对应的leader分区接收到消息,写入文件进行持久化;follower拉取leader消息与leader的数据保持一致;follower消息拉取后需要给leader发送ReplyACK确认消息;KafkaLeader和Follower分区是同步的,Leader分区会回复ACK确认消息给生产者。Producer发送数据流程Producer采用push方式向broker发布数据,每条消息追加到partition上,顺序写入磁盘。消息写入Leader后,Follower主动与Leader同步。发送Kafka消息有两种方式:同步(sync)和异步(async)。默认是同步的,可以通过producer.type属性进行配置。Kafka通过配置request.required.acks属性来确认消息的产生:0表示没有确认消息是否收到成功;不能保证消息是否发送成功,生成环境基本没用。1表示Leader成功收到时确认;只要Leader存活下来,就可以保证不丢失,吞吐量也有保证。-1或all表示接收成功时Leader和Follower都确认;可以最大程度保证消息不丢失,但吞吐量较低。kafkaproducer的参数acks默认值为1,所以默认的producer级别是至少一次,而不是恰好一次。敲黑板,留言可能就丢在这里了!如果acks配置为0,会因为网络抖动导致消息丢失,生产者不验证ACK就不知道消息丢失了。如果acks配置为1,leader不会丢失,但是如果leader挂了,恰好选择了没有ACK的follower,就会丢失。all:保证leader和follower不丢失,但是如果网络拥塞没有收到ACK,就会出现重复传输的问题。KafkaBroker丢失了消息。KafkaBroker接收到数据后,会将数据持久化存储。你认为是这样的:消息持久化,无缓存。没想到是这样的:消息持久化,带缓存。操作系统本身有一层缓存。它被称为页面缓存。写入磁盘文件时,系统会先将数据流写入缓存中。至于何时将缓存数据写入文件,则由操作系统决定。Kafka提供了一个参数producer.type来控制是否主动flush。如果Kafka写入mmap,则立即flush,然后返回给Producer,称为同步(sync);写入mmap后,立即返回Producer,不调用flush。它被称为异步(async)。敲黑板,这里说不定有消息呢!Kafka通过多分区、多副本的机制,已经能够最大程度的保证数据不丢失。如果数据已经写入系统缓存,但还没有来得及刷入磁盘,此时突然死机,电脑或电源出现故障,就会丢失。当然,这种情况是非常极端的。消费者丢失消息消费者通过pull方式主动去kafka集群拉取消息。和生产者类似,消费者在拉取消息的时候也会找leader分区来拉取消息。多个消费者可以组成一个消费者组,每个消费者组都有一个groupid。同一个消费组的消费者可以消费同一个主题下不同分区的数据,但多个消费者不会消费同一个分区的数据。consumer组消费消息consumer消费的进度通过offset保存在kafka集群的topic__consumer_offsets中。消费消息时,主要分为两个阶段:1.识别消息已被消费,并提交偏移坐标;2.处理消息。敲黑板,消息可能就丢在这里了!场景一:先commit再处理消息。如果在处理消息的过程中发生了异常,但是offset已经提交了,这个消息对于消费者来说就丢失了,永远不会再被消费。场景二:先处理消息再commit。如果commit前发生异常,则下次消费该消息。可以通过保证消息的幂等性来解决重复消费的问题。那么问题来了,kafka会丢消息吗?答案是:是的!Kafka可能在三个阶段丢失消息:(1)producer发送数据;(2)KafkaBroker存储数据;(3)消费者消费数据;在生产环境中严格执行exactlyonce其实很难,同时会牺牲效率和吞吐量。最好的做法是在业务端做一个补偿机制,这样在消息丢失的情况下可以自己处理。
