![null f7416074e26028020408573d9dc9a533.jpeg](https://img-blog.csdnimg.cn/img_convert/f7416074e26028020408573d9dc9a533.jpeg)
什么是消息队列
为了保护 B 服务,我们很容易想到可以在 B 服务的内存中加入一个队列。
![null 6f333c83c3eb7c0a278550c20e64c49a.jpeg](https://img-blog.csdnimg.cn/img_convert/6f333c83c3eb7c0a278550c20e64c49a.jpeg)
说白了,它其实是个链表,链表的每个节点就是一个消息。每个节点有一个序号,我们叫它 Offset,记录消息的位置。B 服务依据自己的处理能力,消费链表里的消息。能处理多少是多少,不断更新已处理 Offset 的值。
![null a9ec80f290a0db2c64a3685f9da86c9d.jpeg](https://img-blog.csdnimg.cn/img_convert/a9ec80f290a0db2c64a3685f9da86c9d.jpeg)
但这有个问题,来不及处理的消息会堆积在内存里,如果 B 服务更新重启,这些消息就都丢了。这个好解决,将队列挪出来,变成一个单独的进程。就算 B 服务重启,也不会影响到了队列里的消息。
![null b702e8cfce04f606bc13531d6dba04c9.jpeg](https://img-blog.csdnimg.cn/img_convert/b702e8cfce04f606bc13531d6dba04c9.jpeg)
这样一个简陋的队列进程,其实就是所谓的消息队列。而像 A 服务这样负责发数据到消息队列的角色,就是生产者,像 B 服务这样处理消息的角色,就是消费者。
![null 602e0639f2c4ac7b6b5627f39ea8b99a.jpeg](https://img-blog.csdnimg.cn/img_convert/602e0639f2c4ac7b6b5627f39ea8b99a.jpeg)
但这个消息队列属实过于简陋,像什么高性能,高扩展性,高可用,它是一个都不沾。我们来看下怎么优化它。
高性能
B 服务由于性能较差,消息队列里会不断堆积数据,为了提升性能,我们可以扩展更多的消费者, 这样消费速度就上去了,相对的我们就可以增加更多生产者,提升消息队列的吞吐量。
![null aea72103a0740d4d8ebc02ea1613137a.jpeg](https://img-blog.csdnimg.cn/img_convert/aea72103a0740d4d8ebc02ea1613137a.jpeg)
随着生产者和消费者都变多,我们会发现它们会同时争抢同一个消息队列,抢不到的一方就得等待,这不纯纯浪费时间吗!有解决方案吗?有!首先是对消息进行分类,每一类是一个 topic,然后根据 topic 新增队列的数量,生产者将数据按 topic 投递到不同的队列中,消费者则根据需要订阅不同的 topic。这就大大降低了 topic 队列的压力。
![null 0a9a773991a0936f88246abcd89ba2f7.jpeg](https://img-blog.csdnimg.cn/img_convert/0a9a773991a0936f88246abcd89ba2f7.jpeg)
但单个 topic 的消息还是可能过多,我们可以将单个队列,拆成好几段,每段就是一个 partition分区,每个消费者负责一个 partition。这就大大降低了争抢,提升了消息队列的性能。
![null 53d1c85d5f09fca445698fe58bbaa71d.jpeg](https://img-blog.csdnimg.cn/img_convert/53d1c85d5f09fca445698fe58bbaa71d.jpeg)
高扩展性
随着 partition 变多,如果 partition 都在同一台机器上的话,就会导致单机 cpu 和内存过高,影响整体系统性能。
![null e49143ed54d1efb37aa07455d8ddee65.jpeg](https://img-blog.csdnimg.cn/img_convert/e49143ed54d1efb37aa07455d8ddee65.jpeg)
于是我们可以申请更多的机器,将 partition 分散部署在多台机器上,这每一台机器,就代表一个 broker。我们可以通过增加 broker 缓解机器 cpu 过高带来的性能问题。
![null dea1e661833fab77213ea7e49e236400.jpeg](https://img-blog.csdnimg.cn/img_convert/dea1e661833fab77213ea7e49e236400.jpeg)
高可用
到这里,其实还有个问题,如果其中一个 partition 所在的 broker 挂了,那 broker 里所有 partition 的消息就都没了。这高可用还从何谈起?有解决方案吗?有,连你喜欢的女生都知道手机里多聊几个沸羊羊,你却不知道要给 partition 加备胎吗?我们可以给 partition 多加几个副本,也就是 replicas,将它们分为 Leader 和 Follower。Leader 负责应付生产者和消费者的读写请求,而 Follower 只管同步 Leader 的消息。
![null 32906c7b19711480e8110e7915bf6ff1.jpeg](https://img-blog.csdnimg.cn/img_convert/32906c7b19711480e8110e7915bf6ff1.jpeg)
将 Leader 和 Follower 分散到不同的 broker 上,这样 Leader 所在的 broker 挂了,也不会影响到 Follower 所在的 broker, 并且还能从 Follower 中选举出一个新的 Leader partition 顶上。这样就保证了消息队列的高可用。
![null b1a6fd009483dce2822ee4e5bbb59806.jpeg](https://img-blog.csdnimg.cn/img_convert/b1a6fd009483dce2822ee4e5bbb59806.jpeg)
持久化和过期策略
刚刚提到的是几个 broker 挂掉的情况,那搞大点,假设所有 broker 都挂了,那岂不是数据全丢了?为了解决这个问题,我们不能光把数据放内存里,还要持久化到磁盘中,这样哪怕全部 broker 都挂了,数据也不会全丢,重启服务后,也能从磁盘里读出数据,继续工作。
![null 25e70675821f2a94f40db20107f29fee.jpeg](https://img-blog.csdnimg.cn/img_convert/25e70675821f2a94f40db20107f29fee.jpeg)
但问题又来了,磁盘总是有限的,这一直往里写数据迟早有一天得炸。所以我们还可以给数据加上保留策略,也就是所谓的 retention policy
,比如磁盘数据超过一定大小或消息放置超过一定时间就会被清理掉。
consumer group
到这里,这个消息队列好像就挺完美了。但其实还有个问题,按现在的消费方式,每次新增的消费者只能跟着最新的消费 Offset 接着消费。如果我想让新增的消费者从某个 Offset 开始消费呢?听起来这个需求很刁钻?我举个例子你就明白了。
哪怕 B 服务有多个实例,但本质上,它只有一个消费业务方,新增实例一般也是接着之前的 offset 继续消费。假设现在来了个新的业务方,C 服务,它想从头开始消费消息队列里的数据,这时候就不能跟在 B 服务的 offset 后边继续消费了。
所以我们还可以给消息队列加入消费者组(consumer group)的概念,B 和 C 服务各自是一个独立的消费者组,不同消费者组维护自己的消费进度,互不打搅。
![null 38791ddde445fb04d3c23a6c12b98285.jpeg](https://img-blog.csdnimg.cn/img_convert/38791ddde445fb04d3c23a6c12b98285.jpeg)
ZooKeeper
相信你也发现了,组件太多了,而且每个组件都有自己的数据和状态,所以还需要有个组件去统一维护这些组件的状态信息,于是我们引入 ZooKeeper 组件。它会定期和 broker 通信,获取 整个 kafka 集群的状态,以此判断 某些 broker 是不是跪了,某些消费组消费到哪了。
![null c7fb851c718be99893afd4ac5cdf50a1.jpeg](https://img-blog.csdnimg.cn/img_convert/c7fb851c718be99893afd4ac5cdf50a1.jpeg)
Kafka 是什么
好了,到这里,当初那个简陋的消息队列,就成了一个高性能,高扩展性,高可用,支持持久化的超强消息队列,没错,它就是我们常说的消息队列 Kafka,上面涉及到各种概念,比如 partition 和 broker 什么的,都出自它。
![null f516c94e2c18d67e727d2a86786ec671.jpeg](https://img-blog.csdnimg.cn/img_convert/f516c94e2c18d67e727d2a86786ec671.jpeg)
kafka 的应用场景
消息队列是架构中最常见的中间件之一,使用场景之多,堪称万金油!比如上游流量忽高忽低,想要削峰填谷,提升 cpu/gpu 利用率,用它。又比如系统过大,消息流向盘根错节,想要拆解组件,降低系统耦合,还是用它。再比如秒杀活动,请求激增,想要保护服务的同时又尽量不影响用户,还得用它。当然,凡事无绝对,方案还得根据实际情况来定,做架构做到最后,都是在做折中。
![null 7adb2b1e51c6934748608f44fff3213a.jpeg](https://img-blog.csdnimg.cn/img_convert/7adb2b1e51c6934748608f44fff3213a.jpeg)
总结
• kafka 是消息队列,像消息队列投递消息的是生产者,消费消息的是消费者。增加生产者和消费者的实例个数可以提升系统吞吐。多个消费者可以组成一个消费者组,不同消费者组维护自己的消费进度,互不打搅。
• kafka 将消息分为多个 topic,每个 topic 内部拆分为多个 partition,每个 partition 又有自己的副本,不同的 partition 会分布在不同的 broker 上,提升性能的同时,还增加了系统可用性和可扩展性。