高性能可伸缩的分布式消息中间件设计

消息中间件基本上是每一个大型互联网公司的标准基础技术组件配置,虽然有很多的开源消息中间件,功能也很强大,但是今天我还是想介绍一下怎样自主架构与设计并实现一套完整的分布式消息中间件。

开源的消息中间件或多或少存在一些所谓“坑”,没有遇到大家用得都很happy,遇到的同学就只有加班查资料、google搜索或者直接review开源代码寻找问题原因了。还有就是基本上开源的消息中间件一般都是大而全的功能,一般比较强调通用嘛。今天为大家介绍的是可以灵活横向扩展并且具有高性能的分布式消息中间件的架构设计,也会介绍一些实现的关键技术,也可能很多开源软件实现也有同样的功能,所以通过了解这些设计与实现的核心技术,也可以更好的理解和使用开源的消息中间件。

一、设计目标

先简单此设计需要达到的一些目标吧:

(1)高性能:应该能够充分利用cpu、内存和网卡等资源,通常情况下是把网卡(1000M网卡)撑满;

(2)消息转发实时性:延迟必须在一个设定目标内(可配置);

(3)消息不丢失:正常情况下不丢失(如果由于某一个消息接收到自己网络出现问题导致连接断开丢失的不算,这个需要消息中间件的客户端解决的问题);

(4)可以横向无缝扩展:当一个服务节点服务能力不足时,可以无缝的扩展服务节点,对其他服务节点和所有客户端透明;

(5)安全性:客户端需要通过用户名和密码才能和服务器建立连接、发送消息和订阅消息等;

(6)可以无缝升级系统:程序由于bug或者新增功能等上线是在所难免的,但是不应该影响正常的消息通信;

(7)可以无缝缩减服务节点:如果消息转发量不是很大使用过多的节点是资源的浪费(服务器、电费等);

(8)分布式,防单点;

(9)支持分组订阅:这个分组订阅的功能太好使用了,尤其在做任务的负载均衡调度的时候,后面我会用一篇单独的文章介绍基于分布式消息中间件的负载均衡调度系统设计;

(10)其他。。。。

 

二、基本架构图

先上图,在解释,如下图:

高性能可伸缩的分布式消息中间件设计_第1张图片

 

这个架构图够简单了吧,主要由三大模块组成,消息中间件的客户端、zk集群和消息中间件的服务器集群。具体他们三者之间是怎么交互和联系的,请看后面的的详细设计与讲解,当然重点还是服务器节点之间的交互。图中只画了一个客户端,其实是代表成千上万的客户端。

 

三、详细设计与关键技术分析

在具体讲解之前需要说明一些简单有容易理解前提,消息中间件需要传递消息那么肯定需要和订阅了消息的客户端进行长连接的保持,至于只需要发送消息的客户端那就无所谓了,但是如果需要频繁发送消息建议还是长连接吧。不过实际的应用中,基本上一个客户端既需要发送也需要接受消息(先订阅),那么建立一个长连接即可了。

(一)、消息中间件的服务器集群设计

(1)存活检查设计:首先服务器节点要能够保证对外提供服务的节点都是正常的(说简单一点就是存活的),所以服务器通过zk集群来保证(不懂zookeeper,google吧,分布式系统一般离不开它的)。所有服务器节点在启动的时候都必须注册在zookeeper集群上注册一个临时节点,这个临时节点就是来保证服务器节点的存活的。如果某一个服务器节点由于某种原因(程序bug、网络断掉、服务器宕机等)挂掉了,那么zookeeper会自动把这个临时节点删掉,那么其他服务器或者客户端就会看不到这个节点的地址了,保证了对外公布的服务器节点都是存活可用的。

(2)消息订阅及同步设计:任何需要收到指定主题消息的客户端都必须先和服务器集群建立长连接并且订阅消息,如果是单服务器节点那么很容易实现,只需要简单在服务器节点内存中保存一个订阅队列即可。但是在分布式的消息中间件中,由于服务器节点是多台,所以任何一个客户端的订阅信息都需要在每一个服务器节点上保持同步。所以增加了分布式的特性需要处理的内容和复杂度大大增加了,这也是分布式系统最大的难点(需要考虑每一个节点此时此刻的运行状态,然后根据这些状态做合适的处理)。消息订阅的同步绝对是需要强一致性的,所以如果一个客户端的订阅消息在同步到任何一个服务器节点失败那么此次消息订阅就是失败的。虽然所有服务器节点之间基本上都是通过内网进行通信的,但是网络异常依然不可避免,所以必须需要考虑。还有如果某一个服务器节点挂掉,那么其他服务器节点需要及时的知道,因为挂掉的节点不应该在消息订阅同步的范围内。

上面介绍了正常情况下消息订阅同步的处理,但是服务器节点由于某种原因的增加(例如扩容)或者挂掉的某台服务器节点重启,这些新增或者新启动的服务器节点也需要把以后的消息订阅同步过来。所以需要在服务器节点启动的时候先把以后的订阅信息从其他服务器节点成功同步过来以后才能对外提供服务。这里的设计依然依赖zookeeper提供的存活保证来实现,服务器节点启动的时候首先检查通过zookeeper集群获取已经注册的服务器节点,如果没有那么就不做任何处理,如果有那么就依次获取这些服务器节点的信息,然后通过服务器之间相互通信的端口进行通信,新启动的服务器节点会主动去连接所有已经成功注册并且对外提供服务的服务器节点,其他被连接的服务器节点在接受到一个服务器节点连接的时候把自己本地订阅通过建立的连接全部发送过去(也就是把自己当做客户端去订阅这些消息,消息在服务器之间同步的手段就是采用服务器之间进行二次订阅)。这样就保证了任何一个新启动(不论是新增扩容的还是挂掉重启的)服务器节点都能够有完整的订阅信息,那么有消息发送到新节点以后就可以进行正常的消息转发了。

重点说明:程序实现的时候要防止进行服务器直接的循环订阅(会造成死循环),也就是对订阅消息需要进行区分,如果是服务器之间的相互订阅就不需要在相互同步了,只有客户端的订阅消息才需要在所有服务器节点之间进行消息同步(同步的方法就是通过服务器自身二次订阅,获取其他服务器的地址信息都是通过zookeeper集群,因为通过zookeeper集群获取到的服务器节点信息都是正常工作的)。

(3)消息发布及转发设计:消息都已经成功的订阅了,那么就应该有消息进行发布了。消息发布功能其实比较简单,就是根据订阅列表里面的主题进行消息主题的匹配进行消息进行转发即可。这里唯一需要控制的就是需要根据消息发布者来进行区别进行转发,如果是客户端发布的消息,那么需要把这些消息同样发布给其他订阅此消息主题的服务器节点,如果是服务器二次转发的消息就不需要进行转发到其他服务器节点了,不然也会形成消息的死循环。分组订阅的消息发送需要有一点点的不同,他会从所有订阅次分组订阅信息的节点中随机选择一个进行转发,而不是像普通消息订阅进行所有的订阅转发。

(4)网络异常处理设计:所有分布式系统都不可避免的需要考虑网络异常的情况,针对服务器节点的网络异常处理设计需要考虑两点,一点是和zookeeper保持的心跳长连接情况;二是服务器节点之间相互建立的长连接(主要用于消息订阅同步和消息转发)。针对第一种情况,需要专门的一个线程来定期检查和zookeeper的连接情况,如果发现连接已经断开那么重新进行注册即可;第二种情况也需要定期检查,不过要复杂一点,就是每一个节点都需要考虑此时与自己保持连接的服务器节点是否和zookeeper上的一致,如果不一致需要处理成一致(以zookeeper上注册信息为准)。

重点说明:在处理网络异常的时候,如果两个服务器节点同时发现和对方的连接断开了,那么肯定会同时去连接对方,那么这个时候就可能建立两条连接,所有还需要有一种机制检查重复的连接建立情况,检查到以后关闭掉多余的重复连接。

(5)检查客户端连接设计:服务器节点的连接资源是非常宝贵的,因为需要支持更多的客户端,就需要更多的连接(需要占用socket套接字,对应linux的一个文件描述符等)。但是客户端很多情况下并不会主动的释放这些连接和资源,那么服务器节点自身就应该有一套能够检测客户端是否还存活或者还需要这个连接的情况,一旦检测到某一个客户端连接已经断掉,那么服务器段就需要主动关闭连接,是否资源。

重点说明:这里不仅仅需要考虑连接资源释放,还需要考虑此客户端的订阅信息,需要在关闭连接的时候同时取消这个客户端以前的订阅消息,如果不这样服务器会存在越来越多的垃圾订阅消息,占用内存不说,在查找和匹配消息的也会降低性能。这一点非常重要,因为分布式的消息中间件是需要7*24小时运行的基础服务系统。

(6)慢消费客户端检查与处理设计:为了保证服务器节点转发消息的及时性,需要保证客户端接收和处理消息的速度,因为一个公共的消息主题,可能被成千上万个客户端订阅,如果由于某一个客户端处理很慢,导致其他客户端也不能及时接收消息,服务器节点不能及时转发消息,那么就很得不偿失了。所以需要有机制检查出那些消费很慢的客户端,然后直接清除掉,通常的手段就是进行超时设置,如果发现哪一个客户端接收消息超时,那么就直接把这个客户端的连接关闭掉。从而保证以后的消息转发都是很畅通无阻的。

(7)分组订阅设计:分组订阅主要用于多个客户端处理同一个消息但是每一次只需要一个客户端处理的情况,那么就可以对这个消息设定一个分组,服务器节点在转发消息的时候发现时分组订阅那么就只需要随机选择一个客户端进行消息转发即可。分组订阅是一个很有用的功能,基于这个功能来做任务调度和负载均衡有很大的好处和便捷性。

(二)、客户端设计

客户端设计主要需要考虑到对某一个服务器节点的容错设计,因为客户端如果需要订阅消息那么就必须选择一个服务器节点建立长连接,但是选择的这一个服务器节点随时可能不可用,那么这个时候客户端需要有自动切换到其他正常服务器节点的能力。这个切换过程中需要处理重新订阅以前已经订阅过的消息,这个是需要客户端自动完成而不是需要具体某一个业务系统来完成,因为这是一个通用容错设计,让使用消息中间件的业务系统不必关心这些容错设计。

首先客户端选择一个服务器节点建立连接,因为消息中间件的所有服务器节点都注册到zookeeper系统上了,所以客户端可以通过访问zookeeper获取所有服务器节点的地址信息并且缓存起来(缓存起来的作用主要是做另外一个容错设计,就是当消息中间件客户端需要切换消息中间件服务器节点的时候,而这个时候zookeeper集群也恰好不可用,那么这个时候就可以也需要使用缓存的服务器节点地址,当然需要排除目前正在使用的这个服务器节点)。然后根据随机算法选择一个服务器节点建立长连接,以后客户端就可以订阅消息和发布消息了。

重点说明:客户端在切换服务器节点的时候那么以前所有的订阅信息都丢失了,需要重新把所有以前订阅过的消息重新在新切换的服务器节点中进行订阅,并且这一切对使用消息中间件的业务系统是透明的。

(三)、其他核心技术解析

(1)安全性:未完待续。。。

(2)支持应答的消息模型:未完待续。。。

(3)在线伸缩:未完待续。。。

四、待完善设计

(1)消息丢失:未完待续。。。

你可能感兴趣的:(开源,分布式,架构,架构设计,消息中间件)