如何设计一个牛逼的消息队列?

大家好,我是【架构摆渡人】,一只十年的程序猿。这是消息队列的第一篇文章,这个系列会给大家分享很多在实际工作中有用的经验,如果有收获,还请分享给更多的朋友。

通过前面文章的学习,我们对消息队列的作用以及目前主流的一些消息队列中间件有了更深刻的了解。但是那些优秀的中间件都是别人写出来的,如果你在面试的时候,面试官问你:如果让你去设计一个消息队列,你打算怎么做?如果你对消息队列了解的不彻底,那么很有可能被这个问题问懵掉,最后支支吾吾的说不知道。

服务端

我们从日常使用消息队列来入手,看设计一个消息队列到底要有哪些关键的点。当你要用消息队列的时候首先肯定是下载部署包,然后部署在服务器上。部署的这个程序我们就理解它是消息队列的服务端程序。在其他消息队列里面都有一个固定的名称:Broker

那么为什么需要Broker呢?

你的消息要发送出去,必然得有接收方,这个接收方就是Broker。Broker收到消息后不是直接转给消费方,而是要先落盘,存储起来。这样才能保证消息不丢失,不影响业务。同时还有一些其他的业务操作,比如消息的查询。

存储

既然说到存储,我们做业务的时候,都会用三方存储,也就是数据库,比如Mysql。但是MQ的存储,基本上都不会用三方存储,而是直接采用写磁盘的方式,也就是自己要设计要存储格式,自己写,自己解析等等一系列操作。

当然,也不是说不能用三方存储去实现,下篇文章我们再给大家讲讲如何用数据库做消息队列的存储。用数据库做存储其实也就是利用已有的实现来解决复杂度,涉及到底层存储这块,而且还要考虑高性能,其实对技术要求很高的。

像RocketMQ中的存储就涉及到CommitLog,ConsumeQueue,IndexFile等概念。最重要的是磁盘操作我们都知道很慢,而我们经常用的Mysql为了提高性能也是有一套很复杂的设计,比如redo log,buffer pool等,所以如果直接用数据库做存储,是不是相当于站在巨人的肩上去摘果实呢!

主从

我们设计了一个Broker,使用过程中万一这个Broker挂掉了怎么办?这里是不是得考虑下高可用性,所以Broker还需要有主从的设计。

主节点的数据会同步给从节点,主节点出问题后,从节点可以顶上来提供服务,同时从节点也可以提供读的操作,为主节点减轻压力。

分片

一个Broker是部署在某一台服务器上面,这个服务的磁盘存储空间是有限制的,不可能无限扩容。所以当消息量很大的时候,如果只是一直往机器的本地磁盘写数据,最终会写不进去的。

在设计的时候还要考虑数据分片的场景,一个Topic的数据可以分成很多份进行存储,分别存储在不同的Broker上,这样当磁盘不够的时候,可以通过增加Broker的节点来扩容。

那么问题来了,客户端写入的时候怎么知道这个Topic有哪些分片的存储信息,怎么知道有哪些Broker是在线的呢?这就要引入另一个设计:注册中心,在RocketMq中叫NameServer。

注册中心

NameServer叫注册中心或者路由中心都可以,本质上都一样。Broker启动的时候需要将自身的信息告诉NameServer,同时也要保持一个心跳检查,这样NameServer才能知道Broker当前是否处于正常状态。

NameServer也要支持水平扩展,这样才能保证高可用性。既然要支持水平扩展,那么必然得无状态才行,但是NameServer本身就会存储一些数据,比如Broker信息。

这里有几个实现方式:

Broker启动的时候轮流向所有的NameServer进行注册,这样每个NameServer中都有全量的信息,即使某个节点挂了也不影响。RocketMQ就是使用的这种方式。

Broker启动的时候只向某一台NameServer进行注册,立马返回,然后NameServer之间再进行相互同步,Eureka就是使用的这种方式。

Broker启动的时候只向某一台NameServer进行注册,NameServer会同步向其他的NameServer进行数据的同步操作,等待所有写入成功或者半数写入成功,然后再返回给客户端。Zookeeper就是使用的这种方式。

SDK

服务端有了,还有一个必须要有的设计就是SDK了。应用程序通过依赖SDK就可以直接发送消息和消费消息。SDK同时可以考虑支持多语言,这样使用场景更广泛。

SDK主要是用来跟Broker通信的,所以对于网络通信我们也要选择一个合适的框架,比如Netty就非常合适,你要是觉得太难,直接用Http协议也可以,或者直接支持多协议,这些都是需要考虑的场景。

后台管理

后台管理可以实现很多治理的工作,方便我们在使用消息队列的时候去排查各种问题。

核心功能点:

  • 当前集群状态的查看
  • 消息的查询
  • 消息的消费轨迹查询
  • 消息的重复投递
  • 消息生产的监控大盘
  • 消息消费的监控大盘
  • SDK消费线程数的动态调整
  • 等等

总结

本文只是简单的给大家介绍了下设计一个消息队列需要做哪些核心的工作,看起来就几个点而已。但是这几个点你要真正的去写代码实现难度是很大的。当然,我们其实也没必要自己去造轮子,因为你造了也不一定能比目前主流在用的好,但是整体的架构我们还是要了解的,至于细节就看自己需不需要深入了解了。比如消息存储那块,存储格式是怎样的?顺序写如何实现的?mmap技术如何应用的等等。

原创:架构摆渡人(公众号ID:jiagoubaiduren),欢迎分享,转载请保留出处。

posted @ 2022-05-01 20:14  架构摆渡人  阅读(1149)  评论(2编辑  收藏  举报