消息队列
实习过程中用到了 kafka 作为消息队列,但是对于 kafka 的了解并不是特别多,特别学习部分内容作为记录。
消息队列的优缺点
优点:解耦,异步,削峰
缺点:系统可用性降低,系统复杂度提高(消息重复消费、消息丢失、消息传递的顺序性),一致性问题
Kafka
1. 定义
Apache Kafka是由 Apache 开发的一种发布订阅消息系统,它是一个分布式的、分区的和可复制的提交日志服务。
应用:
- 消息队列
- 数据处理:构建实时流处理程序来转换或处理数据流
2. Kafka 消息模型
发布订阅消息模型
Producer:消息发送者。负责将消息发送到 Kafka 集群的某一个 topic 中。
Consumer:消息消费者。订阅 topic,获取消息。
Broker:Kafka 集群中的每一台服务器。Kafka 是一个分布式集群,其中每一台服务器都叫做 Broker。
Topic:属于特定类别的消息流称为主题。可以理解为消息的标签🏷,订阅了该 topic 的消费者会接收到所有该类消息。
Partition:分区,属于 Topic 的一部分,一个 Topic 可以有多个 Partition,同一 Topic 下的 Partition 可以分布在不同的 Broker 上。
3. Kafka 工作流程及文件存储机制
kafka 工作流程
Kafka 中消息是以 topic 进行分类的,生产者生产消息,消费者消费消息,都是面向 topic 的。
topic 是逻辑上的概念,而 partition 是物理上的概念,每个 partition 对应于一个 log 文件,该 log 文件中存储的就是 producer 生产的数据。Producer 生产的数据会被不断追加到该 log 文件末端,且每条数据都有自己的 offset。消费者组中的每个消费者,都会实时记录自己消费到了哪个 offset,以便出错恢复时,从上次的位置继续消费。
Kafka 文件存储机制
生产者生产的消息会不断追加到 log 文件末尾,为防止 log 文件过大导致数据定位效率低下,Kafka 采取了分片和索引机制,将每个 partition 分为多个 segment。每个 segment 对应两个文件——“.index” 文件和 “.log” 文件。这些文件位于一个文件夹下,该文件夹的命名规则为:topic名称 + 分区序号。例如,这个 topic 有三个分区,则其对应的文件夹为 topic-0, topic-1, topic-2。
index 和 log 文件以当前 segment 的第一条消息的 offset 命名。下图为 index 文件和 log 文件的结构示意图。
“.index” 文件存储大量的索引信息,“.log”文件存储大量的数据,索引文件中的元数据指向对应数据文件中 message 的物理偏移地址。
4. Kafka 消息可靠传递
4.1 Kafka 顺序消费
Kafaka 能够保证一个 Partition 中的消息有序(消息被追加到 Partition 的时候,会有一个特定的偏移量 offset,Kafaka 通过偏移量保证消息在分区内的顺序性)。一个 Topic 对应多个 Partition,Kafaka 不能保证多个 Partition 有序。
解决方法:
- 一个 Topic 对应一个 Partition
- 发送消息的时候,指定 key/partition,让同一个 key 的消息发送到同一个 partition
4.2 Kafka 如何保证消息不丢失
消费端丢失数据
在 consumer 消费阶段,对 offset 的处理,关系到是否丢失数据。如果在消息处理完成前就提交了offset,那么就有可能造成数据的丢失。
Kafka 会自动提交 offset,那么只要关闭自动提交 offset,enable.auto.commit=false
,在处理完之后自己手动提交 offset,就可以保证数据不会丢。
Kafka 丢失数据
Kafka 某个 broker 宕机,然后重新选举 partition 的 leader。若此时其他的 follower 还有数据没有进行同步,此时 leader 挂了,选举某个 follower 成 leader 之后,就会丢失一些数据。
所以此时一般是要求起码设置如下 4 个参数:
- 给 topic 设置
replication.factor
参数:这个值必须大于 1,要求每个 partition 必须有至少 2 个副本。 - 在 Kafka 服务端设置
min.insync.replicas
参数:这个值必须大于 1,这个是要求一个 leader 至少感知到有至少一个 follower 还跟自己保持联系,没掉队,这样才能确保 leader 挂了还有一个 follower。 - 在 producer 端设置
acks=all
:这个是要求每条数据,必须是写入所有 replica 之后,才能认为是写成功了。 - 在 producer 端设置
retries=MAX
(很大很大很大的一个值,无限次重试的意思):这个是要求一旦写入失败,就无限重试,卡在这里了。
这样配置之后,至少在 Kafka broker 端就可以保证在 leader 所在 broker 发生故障,进行 leader 切换时,数据不会丢失。
生产者丢失数据
若设置好了 kafka,acks=all
,一定不会丢。此时要求 leader 接收到消息,所有的 follower 都同步到了消息之后,才认为本次写成功了。如果没满足这个条件,生产者会自动不断的重试,重试无限次。
4.3 Kafka 如何保证不重复消费?
Kafka 每个消息写进去,都有一个 offset,代表消息的序号,然后 consumer 消费了数据之后,每隔一段时间(定时定期),会把自己消费过的消息的 offset 提交一下,表示已经消费过了,下次要重启,继续从上次消费到的 offset 来继续消费。
但是当直接 kill 进程,再重启。这会导致 consumer 有些消息处理了,但是没来得及提交 offset。等重启之后,少数消息就会再次消费一次。
保证消息队列消费的幂等性:
结合业务,消息可以使用唯一 id 标识。
- 落表(主键或者唯一索引的方式,避免重复数据)
- 业务逻辑处理(选择唯一主键存储到 Redis 中,先查询是否存在,若存在则不处理;若不存在,先插入 Redis, 再进行业务逻辑处理)
5. kafka 的高可靠性实现
Kafka 0.8 以后,提供了 HA 机制,就是 replica(复制品) 副本机制。每个 partition 的数据都会同步到其它机器上,形成自己的多个 replica 副本。所有 replica 会选举一个 leader 出来,生产和消费都跟这个 leader 打交道,然后其他 replica 就是 follower。写的时候,leader 会负责把数据同步到所有 follower 上去,读的时候就直接读 leader 上的数据即可。另外,Kafka 会均匀地将一个 partition 的所有 replica 分布在不同的机器上,这样才可以提高容错性。
Kafka 的分区多副本架构是 Kafka 可靠性保证的核心,把消息写入多个副本可以使 Kafka 在发生崩溃时仍能保证消息的持久性
写数据的时候,生产者就写 leader,然后 leader 将数据落地写本地磁盘,接着其他 follower 自己主动从 leader 来 pull 数据。一旦所有 follower 同步好数据了,就会发送 ack 给 leader,leader 收到所有 follower 的 ack 之后,就会返回写成功的消息给生产者。
消费的时候,只会从 leader 去读,但是只有当一个消息已经被所有 follower 都同步成功返回 ack 的时候,这个消息才会被消费者读到。
Kafka、ActiveMQ、RabbitMQ、RocketMQ 比较
特性 | ActiveMQ | RabbitMQ | RocketMQ | Kafka |
---|---|---|---|---|
单机吞吐量 | 万级,比 RocketMQ、Kafka 低一个数量级 | 同 ActiveMQ | 10 万级,支撑高吞吐 | 10 万级,高吞吐,一般配合大数据类的系统来进行实时数据计算、日志采集等场景 |
topic 数量对吞吐量的影响 | topic 可以达到几百/几千的级别,吞吐量会有较小幅度的下降,这是 RocketMQ 的一大优势,在同等机器下,可以支撑大量的 topic | topic 从几十到几百个时候,吞吐量会大幅度下降,在同等机器下,Kafka 尽量保证 topic 数量不要过多,如果要支撑大规模的 topic,需要增加更多的机器资源 | ||
时效性 | ms 级 | 微秒级,这是 RabbitMQ 的一大特点,延迟最低 | ms 级 | 延迟在 ms 级以内 |
可用性 | 高,基于主从架构实现高可用 | 同 ActiveMQ | 非常高,分布式架构 | 非常高,分布式,一个数据多个副本,少数机器宕机,不会丢失数据,不会导致不可用 |
消息可靠性 | 有较低的概率丢失数据 | 基本不丢 | 经过参数优化配置,可以做到 0 丢失 | 经过参数优化配置,可以做到 0 丢失 |
功能支持 | MQ 领域的功能极其完备 | 基于 erlang 开发,并发能力很强,性能极好,延时很低 | MQ 功能较为完善,还是分布式的,扩展性好 | 功能较为简单,主要支持简单的 MQ 功能,在大数据领域的实时计算以及日志采集被大规模使用 |
参考:
- 本文作者: Kelly Liu
- 本文链接: http://tiantianliu2018.github.io/2020/09/15/消息队列/
- 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!