如何从零开始手写一个消息中间件(从宏观角度理解消息中间件的技术原理)
什么是消息中间件
消息中间件,也就是俗称的MQ。消息中间件是一个在分布式环境下提供消息收发能力的服务,消息生产者把消息发送到消息中间件,然后消息中间件存储生产者发送的消息,消息消费者请求消息中间件拉取消息,拉取到后进行消费。通过消息中间件,两个服务间可以异步的传递消息,满足了服务间消息传递的需求的同时,又避免了服务间的强依赖,达到了异步和解耦的效果。

消息中间件的作用
首先消息中间件的第一个功能就是可以做到异步。如果我们一个接口的处理逻辑比较复杂,调用链路比较长,耗时比较久,但是客户端又不需要马上得到响应结果,那么我们可以先把请求相关的信息存到MQ,然后再由后面的消费者去消费这个消息,处理这个请求,这样就能达到快速响应客户端的效果。把实时性要求不高的请求通过MQ来滞后请求的处理,可以在一定程度上提升接口的性能。

消息中间件的第二个作用就是解耦合。一个服务接收到请求,需要通知其他服务来进行相应的处理,如果采用同步通知的方式的话,就只能通过调用接口的方式,假如后面又增加了几个服务,也需要收到通知,那么就只能通过改代码的方式增加服务调用,这种方式是很不利于代码的维护的,要频繁的修改代码然后重新打包部署,非常的麻烦。但是如果我们通过MQ来实现异步的通知,那么生产者只需要发送一次消息到MQ,其他服务需要收到通知的服务,则作为消费者去监听MQ对应的消息,收到消息时进行相应的处理,后续如果有其他服务也需要接收到通知,不需要找生产者加代码,而是通过监听MQ,就能收到同样的消息,这样就不需要频繁的修改代码,一定程度上提升了可维护性。

消息中间件还有一个作用就是削峰。假如我们的服务某一时刻接收大量的请求,达到了一个非常高的峰值,如果使用同步的方式来处理,服务器很可能扛不住压力而挂掉。如果我们先把请求存到MQ,快速响应客户端,这样就能减轻服务器的压力。然后存到MQ的消息由消费者异步慢慢的处理,这样就可以快速地削掉并发请求的峰值,有效的缓解服务器的压力。

逐一拆解消息中间件的核心技术
消息中间件被应用在分布式环境中提供高性能、高可靠的消息收发服务。它通常不是一个单一的简单服务,如果它是一个单一的服务,首先就不满足高可靠,一个服务挂了,生产者和消费者间的通信就断了;其次它也不是一个简单服务,里面涉及到许多技术,如果它是一个简单服务的话,比如消息的收发都是简单的http请求,然后数据存到数据库,虽然这样也可以实现消息收发的功能,但是这种消息中间件性能并不高,只能用来玩一玩,没用什么实际的作用,因此正规的消息中间件它的内部组成都是非常复杂的。
消息中间件核心技术总览
首先消息要发送到MQ,就要经过网络,因此就会有网络IO,所以IO是消息中间件需要考虑的一个技术点。
在网络间传递消息,通常都会有固定的协议,比如http协议,dns协议,不同协议有不同的报文格式,使用http协议的话性能太低,所以消息中间件还应该考虑实现自己的协议,定义自己的报文格式。
然后我们的消息在内存中通常是一个对象,如果要网络发送出去,必须要进行序列化。
当消息中间件接收到消息后,需要进行存储,因此消息中间件要设计自己的消息存储格式。
消息中间件接收生产者发送来的消息,然后消费者请求消息中间件拉取消息,因此消息中间件要设计自己的消息读写策略,一个高性能的MQ它的读写必须是高性能的。
消息中间件通常应该在分布式环境下,它本身应该也是分布式的,所以消息中间件的地址应该要支持动态的发现,不能写死在生产者和消费者的配置文件里面,因此消息中间件还要设计自己的服务注册与发现的机制。
如果消息中间件要提高自己的吞吐量的话,必须支持消息分区并行消费,消息分区通常伴随着消费者组一起出现,因此消息中间件还要考虑如何实现分区并行以及如何给消费者分配自己负载的分区,也就是rebalance。
消息中间件还要考虑如何实现消息的可靠性,存到消息中间件的消息,应该尽量保证不丢失,因此消息中间件还要考虑主从复制。
最后,存到消息中间件中的消息不能无限堆积,要定时清理,所以消息中间件还要设计自己的消息定时清理的机制。

因此,消息中间件涉及到的技术点就有以下几项:
- IO
- 协议
- 序列化
- 消息的存储
- 消息的读写
- 服务注册与发现
- 分区并行与消费者组rebalance机制
- 主从复制
- 消息定时清理
下面我们对它们进行详细的分析。
IO
消息中间件是在生产者和消费者之间提供消息收发能力的服务,消息的传递涉及到网络IO。一款高性能的消息中间件,它的底层使用的必然是高性能的IO模型。

IO模型有BIO、NIO、IO多路复用、AIO。

BIO
BIO就是传统的阻塞式IO,在BIO的模式下,如果服务器端调用Socket的accept()方法监听连接,如果此时没有客户端连接,当前线程会一直阻塞,等待连接的到来;当调用Socket的read()函数读取消息时,如果此时没有消息,当前线程会一直阻塞等待,直到有消息到达,拷贝到用户空间,当前线程才会返回。

可以看到无论是等待连接建立、还是等待数据的到来,都是阻塞式的等待,性能是非常低的。一旦没有客户端连接,或者客户端不

最低0.47元/天 解锁文章
431






