🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
> 为什么需要消息队列 使用消息队列最主要的三个原因:异步、削峰、解耦。 - 异步:一个下单流程,你需要扣积分、扣优惠卷、发短信等,有些耗时又不需要立即处理的事,可以丢到队列里延迟处理。 - 削峰:平常流量不高,突然推出一个优惠活动,请求量极速上升。由于服务器 Redis、MySQL 承受能力不一样,如果请求全部接收,服务器就会出问题。这时可以将请求放到队列里,按照服务器的能力去消费。 - 解耦:一个订单流程,你扣积分、扣优惠券、发短信等需要调用多个接口,出现问题不好排查。像发短信有很多地方需要调用,如果哪天修改了短信接口参数,用到的地方都得修改。这时可以将要发送的内容放到队列里,起一个服务去消费,统一发送短信。 ![](https://img.kancloud.cn/ce/02/ce0259fec11f4c68de14106cfc15bbfe_709x566.png) > 高吞吐、高可用 MQ 对比分析 看了各大招聘网站,提到较多的消息队列有:RabbitMQ、RocketMQ、Kafka 以及 Redis 的消息队列和发布订阅。 Redis 队列是用 List 数据结构实现的,一端 push,另一端 pop,一条消息只能被一个程序所消费。如果要一对多消费,可以用 Redis 的发布订阅。Redis 发布订阅是实时消费的,服务端不会保存生产的消息,也不会记录客户端消费到哪一条。如果客户端宕机,消息就会丢失。这时就需要用到高级的消息队列,如 RocketMQ、Kafka 等。 ZeroMQ 只有点对点模式和 Redis 发布订阅差不多,如果不是对性能要求特别高,我会用其它队列代替,毕竟关解决开发所需的依赖库就够折腾的。 RabbitMQ 多语言支持比较完善,特性的支持也比较齐全,适合在消息量级不是很高的情况下。 RocketMQ 和 Kafka 性能差不多,基于 Topic 的订阅模式。RocketMQ 支持分布式事务,但在集群下主从不能自动切换,导致了一系列小问题。相比于其它集群是用 Master-Slave 模式,在 Master 没有宕机时,Slave 作为灾备,空闲着机器。 Kafka 采用的是 Leader-Slave 模式,每台服务器既是 Master 也是 Slave。 | ActiveMQ | RabbitMQ | RocketMQ | Kafka | ZeroMQ | | :--------- | :------------------------------- | :----------------------------------------------------------- | :----------------------------------------------------------- | :--------------------------------------------------- | | 吞吐量 | 比 RabbitMQ 低 | 2.6w/s(消息做持久化) | 11.6w/s | 17.3w/s | | 开发语言 | Java | Erlang | Java | Scala/Java | | 主要维护者 | Apache | Mozilla/Spring | Alibaba | Apache | | 订阅形式 | 点对点(P2P)、广播(发布—订阅) | 提供了 4 种:direct、topic、Headers 和 fanout。fanout 就是广播模式 | 基于 topic/messageTag 以及按照消息类型、属性进行正则匹配的发布订阅模式 | 基于 topic 以及按照 topic 进行正则匹配的发布订阅模式 | | 持久化 | 支持少量堆积 | 支持少量堆积 | 支持大量堆积 | 支持大量堆积 | | 顺序消息 | 不支持 | 不支持 | 支持 | 支持 | | 消息回溯 | 不支持 | 不支持 | 支持指定时间点的回溯 | 支持指定分区 offset 位置的回溯 | > Kafka 相关概念 在高可用下,Kafka 会部署多台,避免 Kafka 宕机后,服务无法访问,其中每一台 Kafka 机器就是一个 Broker。Kafka 节点的信息和 Leader 的选举等等操作需要依赖 ZooKeeper。 同样地,为了避免 ZooKeeper 宕机导致服务无法访问,ZooKeeper 也需要部署多台。生产者的数据是写入到 Kafka 的 Leader 节点,Follower 节点的 Kafka 从 Leader 中拉取数据同步。在写数据时,需要指定一个 Topic,也就是消息的类型。一个 Topic 下可以有多个 Partition,将数据分散存储在 Partition 下。一个 Topic 下一个也可以有多个 Replica,每一个 Replica 都是这个 Topic 的完整数据备份。Producer 生产消息,Consumer 消费消息。在没给 Consumer 指定 Consumer Group 时会创建一个临时消费组。Producer 生产的消息只能被同一个 Consumer Group 中的一个 Consumer 消费。 - broker:Kafka 集群中包含的服务器。 - zookeeper:Kafka 通过 ZooKeeper 来存储集群的 meta 信息。 - leader:replica 中的一个角色,producer 和 consumer 只跟 leader 交互。 - follower:replica 中的一个角色,从 leader 中复制数据。 - controller:Kafka 集群中的其中一个服务器,用来进行 leader election 以及 各种 failover。 - topic:主题:每条发布到 Kafka 集群的消息属于的类别 - partition:分区,partition 是物理上的概念,每个 topic 包含一个或多个 partition。Kafka 分配的单位是 partition。 - replica:副本,partition 的副本,保障 partition 的高可用。 - producer:消息的生产者。 - consumer:消息的消费者。 - consumer group:消费组,每个 consumer 都属于一个 consumer group,每条消息只能被 consumer group 中的一个 consumer 消费,但可以被多个 consumer group 消费。 > 分区、副本、消费组 - 分区 主题的分区数有多少个,这个主题的数据就会被分成多少份。这个分区数最好就是消费者数的整数倍。 如果分区数小于消费者数,前面的消费者会分到一个分区的数据消费,后面超过分区数的消费者将无消费消费,除非前面的消费者宕机了。如果分区数大于消费者数,每个消费者至少分配到一个分区的数据,两个消费者间分区数相差不超过一,如果有新的消费者加入,会把那些分区数多的消费者分区分配给新的消费者。 分区数可以设置成 6、12 等数值。比如 6,当消费者只有一个时,这 6 个分区都归这个消费者,后面再加入一个消费者时,每个消费者都负责 3 个分区,后面再加入一个消费者时,每个消费者就负责 2 个分区。每个消费者分配到的分区数是一样的,可以均匀地消费。另外同一个分区写入是有顺序的,如果要保证全局有序,那只能设置一个分区。如果要消费者也有序,消费者也只能有一个。 - 副本 主题的副本数即数据备份的个数,如果副本数为 1 , 即使 Kafka 机器有多个,当该副本所在的机器宕机后,对应的数据将访问失败。 集群模式下创建主题时,如果分区数和副本数都大于 1,主题会将分区 Leader 较均匀的分配在有副本的 Kafka 上。这样客户端在消费这个主题时,可以从多个台机器上的 Kafka 消息数据,是实现分布式的关键。 副本数不是越多越好,从节点需要从主节点拉取数据同步,一般设置成和 Kafka 机器数一样即可。如果只需要用到高可用的话,可以采用 N+1 策略,副本数设置为 2,专门弄一台 Kafka 来备份数据。然后数据分布存储在 N 台 Kafka 上,+1 台 Kafka 保存着完整的主题数据。 Replicas 表示在哪些 Kafka 机器上有主题的副本,Isr 表示当前有副本的 Kafka 机器上还存活着的 Kafka 机器。主题分区中所涉及的 Leader Kafka 宕机时,会将宕机 Kafka 涉及的分区分配到其它可用的 Kafka 节点上。如下: ``` Topic:demo_service PartitionCount:4 ReplicationFactor:2 Configs: Topic: demo_service Partition: 0 Leader: 171 Replicas: 171,170 Isr: 171,170 Topic: demo_service Partition: 1 Leader: 170 Replicas: 170,171 Isr: 170,171 Topic: demo_service Partition: 2 Leader: 171 Replicas: 171,170 Isr: 171,170 Topic: demo_service Partition: 3 Leader: 170 Replicas: 170,171 Isr: 170,171 ``` - 消费组 每一个消费组记录者各个主题分区的消费偏移量,在消费的时候,如果没有指定消费组,会默认创建一个临时消费组。消费指定消费组时,主题的分区只能被某个客户端消费,即生成者生产的消息只能被相同消费组下某个消费者消费。如果想要一条消息可以被多个消费者消费,可以加入不同的消费组。 ``` TOPIC PARTITION CURRENT-OFFSET LOG-END-OFFSET LAG CONSUMER-ID HOST CLIENT-ID demo_service 0 2003 2005 2 client1-7b05d48b-b2ae-4b31-89e2-010ce84b1c11 /175.191.15.200 client1 demo_service 1 1916 1918 2 client1-7b05d48b-b2ae-4b31-89e2-010ce84b1c11 /175.191.15.200 client1 demo_service 2 1972 1996 24 client2-116fcb66-b7f4-4162-bb46-6784e501a36a /175.191.11.89 client2 demo_service 3 2002 2022 20 client2-116fcb66-b7f4-4162-bb46-6784e501a36a /175.191.11.89 client2 ``` > 重复消费和数据丢失问题 重复消费和数据丢失问题主要分三种情况: 生产者发送消息成功后不等 Kafka 同步完成的确认继续发送下一条消息,在发的过程中如果 Leader Kafka 宕机了,但 Producer 并不知情,发出去的信息 Broker 就收不到导致数据丢失。解决方案是将 request.required.acks 设置为 -1,表示 Leader 和 Follower 都收到消息才算成功。 - request.required.acks=0 表示发送消息即完成发送,不等待确认(可靠性低,延迟小,最容易丢失消息) - request.required.acks=1 表示当 Leader 收到消息返回确认后才算成功 消费者有两种情况,一种是消费的时候自动提交偏移量导致数据丢失:拿到消息的同时将偏移量加一,如果业务处理失败,下一次消费的时候偏移量已经加一了,上一个偏移量的数据丢失了。 另一种是手动提交偏移量导致重复消费:等业务处理成功后再手动提交偏移量,有可能出现业务成功,偏移量提交失败,那下一次消费又是同一条消息。怎么解决呢? 这是一个 or 的问题,偏移量要么手动提交要么自动提交,对应的问题是要么数据丢失要么重复消费。如果消息要求实时性高,丢个一两条没关系的话可以选择自动提交偏移量。如果消息一条都不能丢的话可以将业务设计成幂等,不管消费多少次都一样。 > 如何确保一条消息只被一个服务消费 前面已经讲到,同个主题的一个分区只能被消费组里某个消费者消费。在消费主题时,将这些消费者都加入相同的消费组,生成的消息就只能被一个客户端服务消费。 > 操作命令 - 查看 Kafka 中 Topic: ``` [root@VM_0_17_centos bin]# ./kafka-topics.sh --zookeeper 127.0.0.1:2181 --list __consumer_offsets demo_service ``` - 查看 Topic 详情: ``` [root@VM_0_17_centos bin]# ./kafka-topics.sh --zookeeper 127.0.0.1:2181 --describe --topic demo_service Topic:demo_service PartitionCount:4 ReplicationFactor:2 Configs: Topic: demo_service Partition: 0 Leader: 171 Replicas: 171,170 Isr: 171,170 Topic: demo_service Partition: 1 Leader: 170 Replicas: 170,171 Isr: 170,171 Topic: demo_service Partition: 2 Leader: 171 Replicas: 171,170 Isr: 171,170 Topic: demo_service Partition: 3 Leader: 170 Replicas: 170,171 Isr: 170,171 ``` - 消费主题: ``` ./kafka-console-consumer.sh --bootstrap-server localhost:9092,localhost:9093 --group test_group --topic demo_service--from-beginning this is a good test - 1 this is a good test - 2 this is a good test - 3 ``` - 查看 Kafka 中 Groups: ``` [root@VM_0_17_centos bin]# ./kafka-consumer-groups.sh --bootstrap-server localhost:9092,localhost:9093 --list test-group ``` - 查看 Group 消费情况: ``` [root@VM_0_17_centos bin]# ./kafka-consumer-groups.sh --bootstrap-server localhost:9092,localhost:9093 --describe --group test_group TOPIC PARTITION CURRENT-OFFSET LOG-END-OFFSET LAG CONSUMER-ID HOST CLIENT-ID demo_service 0 2003 2005 2 client1-7b05d48b-b2ae-4b31-89e2-010ce84b1c11 /175.191.15.200 client1 demo_service 1 1916 1918 2 client1-7b05d48b-b2ae-4b31-89e2-010ce84b1c11 /175.191.15.200 client1 demo_service 2 1972 1996 24 client2-116fcb66-b7f4-4162-bb46-6784e501a36a /175.191.11.89 client2 demo_service 3 2002 2022 20 client2-116fcb66-b7f4-4162-bb46-6784e501a36a /175.191.11.89 client2 ``` > 总结 - Kafka 高可用 副本是数据的备份,有多个副本分布在不同的机器上时,如果有一台 Kafka 宕机了,会自动切换到其它可用的 Kafka 上。如果你的 Kafka 有多台,但是主题的副本数只有一个,那么当 Kafka 机器有宕机时,对应的数据将不可访问。 - Kafka 高并发 分区是实现高并发的关键,当然还需要多个副本。分区数有多个并且分区 Leader 分布在不同的机器上,这样主题生产的数据可以均匀的保存在多台机器上,消费这个主题时可以从多台机器上获取数据。