发明内容
为至少在一定程度上克服相关技术中存在的问题,本申请提供一种消息处理系统。
根据本申请的实施例,提供一种消息处理系统,包括:
生产模块,用于将待处理消息发送到当前版本的消息队列所在的代理模块;当代理模块拒绝接收所述待处理消息时,更新当前版本,并向更新后的当前版本的消息队列所在的代理模块发送所述待处理消息;
代理模块,用于缓存所述生产模块发送的待处理消息;
消费模块,用于依次从低版本到高版本的各个代理模块中取出缓存的待处理消息进行处理。
进一步地,所述代理模块的数量为多个,分别属于一个或多个版本;
属于某一个版本的一个或多个所述代理模块形成该版本的代理集群。
进一步地,所述系统还包括:
配置模块,用于对各个版本的所述代理集群进行配置,以及存储配置信息并下发版本变更通知。
进一步地,所述将待处理消息发送到当前版本的代理模块时,所述生产模块具体用于:
根据存储的版本信息确定当前版本的代理集群;
根据待处理消息的顺序值确定当前版本的代理集群中的一个代理模块;
将待处理消息发送到确定出的代理模块。
进一步地,所述更新当前版本时,所述生产模块具体用于:
从所述配置模块获取最新的配置信息;
根据获取的配置信息更新存储的版本信息。
进一步地,所述生产模块还用于:
在接收到所述配置模块下发的版本变更通知时,根据所述版本变更通知更新存储的版本信息。
进一步地,一个所述代理模块中至少包括一个消息队列;所述消息队列具有读过期属性和写过期属性,且读过期属性和写过期属性的初始值均为false;
所述代理模块还用于:
在接收到所述配置模块下发的版本变更通知时,判断最新版本是否高于自身版本;
如果是,则将自身的所有消息队列的写过期属性设置为true,并同步给所述配置模块。
进一步地,所述代理模块具体用于:
在接收到所述生产模块发送的待处理消息时,检查自身的消息队列的写过期属性;
如果写过期属性为false,则将所述待处理消息放入自身的消息队列中进行缓存;
如果写过期属性为true,则拒绝缓存,并将结果反馈给所述生产模块。
进一步地,所述代理模块还用于:
定期遍历写过期属性为true的消息队列,检查这些消息队列的消息深度;
当某个消息队列的消息深度为0,即无堆积消息时,将该消息队列的读过期属性设置为true,并同步给所述配置模块。
进一步地,所述消费模块具体用于:
确定读过期属性为false且版本最低的消息队列;
从确定出的消息队列中取出缓存的待处理消息进行处理;
直到该版本的全部代理模块的所有消息队列的读过期属性均为true之后,再处理下一个版本的代理模块上的消息队列。
本申请的实施例提供的技术方案具备以下有益效果:
本申请的方案引入了版本的概念,从而在不增设中心计算节点的情况下,保证在线动态扩缩容的情况下仍能保证消息的顺序性;避免引入第三方中心节点对消息的路由和存储,减少复杂性和风险。
应当理解的是,以上的一般描述和后文的细节描述仅是示例性和解释性的,并不能限制本申请。
具体实施方式
这里将详细地对示例性实施例进行说明,其示例表示在附图中。下面的描述涉及附图时,除非另有表示,不同附图中的相同数字表示相同或相似的要素。以下示例性实施例中所描述的实施方式并不代表与本申请相一致的所有实施方式。相反,它们仅是与如所附权利要求书中所详述的、本申请的一些方面相一致的系统的例子。
为了能够更清楚地阐明本申请的技术方案,首先具体解释消息系统的顺序性。
保障顺序消息涉及到三方,也是消息系统的三个基本概念:生产者(Producer)、Broker、消费者(Consumer)。
生产者:消息生产的顺序一般都是和业务息息相关的,比如购物流程,是由用户触发的,而用户是通过购物界面和系统交互的,每一步的操作都必须等到上一个操作的响应在页面上展示。其他的非购物流程也是类似的,都是和业务相关的,因此生产者的顺序是由业务保证,本方案不做深入谈论。
但是,必须明确的是,顺序消息的生产者也是分布式的,即生产者是多进程多线程的。并且每条消息具体要发到Broker的计算逻辑也是在生产者计算的(OrderId % M)。
消费者:因为单个队列本身保证先进先出,而分布式队列中的每个队列存储的是不同哈希值的消息,所以不同队列间的是没有顺序要求的。因此,只要保证针对每个队列的消费者并发数是一就可以保证消费消息的顺序和发送消息的顺序一致。
Broker端是本方案讨论的重点,首先分析在静态的情况下,哈希顺序消息的示例。如图1所示,扩缩容操作前V1版本Broker的数量为N。
生产者的并发顺序由业务保证,在静态Broker的情况下,需要保证顺序的一组消息根据其确保顺序的属性值X和Broker的数量N计算哈希后,得到需要放的Broker机器编号(S= X%N),即消息发到BrokerS上的OrderQueue里。
扩缩容一次完成后(V2),Broker数量变为M,新的消息分配Broker策略变为:S’=X%M,将消息发到BrokerS’的OrderQueue里。
扩缩容Z-1次完成后(VZ),Broker数量变为L,新的消息分配Broker策略变为:S’’=X%L,消息将发到BrokerS’’的OrderQueue里。
以上就是扩缩容完成后生产端将消息发送到正确Broker的基本流程和算法,但是每次扩缩容后所有生产者做不到同时感知到并向正确的版本发送消息。因此很有可能ProducerA发送“创建订单”在V1,ProducerB发送“付款”在V2,ProducerC发送“发货”在V1,那就可能钱货两空了。发生这个问题的原因是Producer分布式下一致性的问题。
为了解决这个问题,本申请的方案引入版本的概念,每次扩缩容都是一个新的版本(一个新的RealQueue:RealQueue_VN),并且哪个版本生效并不是由分布式的Producer决定而是由单点的Broker来决定。
图2是根据一示例性实施例示出的一种消息处理系统的架构图。该系统包括:
生产模块,用于将待处理消息发送到当前版本的代理模块;当代理模块拒绝接收所述待处理消息时,更新当前版本,并向更新后的当前版本的代理模块发送所述待处理消息;
代理模块,用于缓存所述生产模块发送的待处理消息;
消费模块,用于依次从低版本到高版本的各个代理模块中取出缓存的待处理消息进行处理。
图中,Producer集群即为多个生产模块的集群,Consumer集群即为多个消费模块的集群,Broker1~BrokerN分别为多个不同的代理模块。
本申请的方案引入了版本的概念,从而在不增设中心计算节点的情况下,保证在线动态扩缩容的情况下仍能保证消息的顺序性;避免引入第三方中心节点对消息的路由和存储,减少复杂性和风险。
一些实施例中,所述代理模块的数量为多个,分别属于一个或多个版本;
属于某一个版本的一个或多个所述代理模块形成该版本的代理集群。
本方案的系统需要部署多个Broker节点,用于针对动态扩缩容时指定不同的版本。每个Broker都有自己的对应的版本,多个同一版本的Broker形成一组对应版本的代理集群。
参照图2,一些实施例中,所述系统还包括:
配置模块,用于对各个版本的所述代理集群进行配置,以及存储配置信息并下发版本变更通知。图中ZK-Cluster即为配置模块。
本方案的系统需要部署一套ZK-Cluster(ZooKeeper集群),即Zookeeper部署5台或者7台,用于配置存储及配置变更下发通知。
此外,本方案的系统还需要部署一个管理台应用(JBOSS或tomcat或其他容器),用于给用户的代理集群Broker进行扩缩容动作。
一些实施例中,所述将待处理消息发送到当前版本的代理模块时,所述生产模块具体用于:
根据存储的版本信息确定当前版本的代理集群;
根据待处理消息的顺序值确定当前版本的代理集群中的一个代理模块;
将待处理消息发送到确定出的代理模块。
一些实施例中,所述更新当前版本时,所述生产模块具体用于:
从所述配置模块获取最新的配置信息;
根据获取的配置信息更新存储的版本信息。
一些实施例中,所述生产模块还用于:
在接收到所述配置模块下发的版本变更通知时,根据所述版本变更通知更新存储的版本信息。
一些实施例中,一个所述代理模块中至少包括一个消息队列;所述消息队列具有读过期属性和写过期属性,且读过期属性和写过期属性的初始值均为false;
所述代理模块还用于:
在接收到所述配置模块下发的版本变更通知时,判断最新版本是否高于自身版本;
如果是,则将自身的所有消息队列的写过期属性设置为true,并同步给所述配置模块。
一些实施例中,所述代理模块具体用于:
在接收到所述生产模块发送的待处理消息时,检查自身的消息队列的写过期属性;
如果写过期属性为false,则将所述待处理消息放入自身的消息队列中进行缓存;
如果写过期属性为true,则拒绝缓存,并将结果反馈给所述生产模块。
一些实施例中,所述代理模块还用于:
定期遍历写过期属性为true的消息队列,检查这些消息队列的消息深度;
当某个消息队列的消息深度为0,即无堆积消息时,将该消息队列的读过期属性设置为true,并同步给所述配置模块。
一些实施例中,所述消费模块具体用于:
确定读过期属性为false且版本最低的消息队列;
从确定出的消息队列中取出缓存的待处理消息进行处理;
直到该版本的全部代理模块的所有消息队列的读过期属性均为true之后,再处理下一个版本的代理模块上的消息队列。
下面结合具体的应用场景,对本申请的方案进行拓展说明。
因为生产者和消费者不是同时在线,消息也可能会堆积,因此对于生产者和消费者正在生效的版本可能是不一样。为此我们还要为某个队列的每个Broker的每个版本添加“读过期”和“写过期”两个属性用来区分生产者和消费者。
以下为简单示例:
RealQueue
- queueName
- broker
- version
- readExpired
- writeExpired
其中writeExpired(写过期)默认为false,在扩缩时(有更新版本时)低版本的RealQueue感知到有比自己版本高的RealQueue出现时,修改自身的writeExpired为true,发送到配置平台推送给客户端,并拒绝发送方发送的新消息。收到此状态变化的发送方进程,向扩缩容后即writeExpired为false的高版本RealQueue发送消息。没有收到此状态变化的发送方进程,继续向低版本的RealQueue发送消息就会收到过期异常,进而向下一个版本发送消息,直到发送至正确版本。最终所有Producer、Consumer、Broker、配置模块的队列信息一致,在这过程中也保证了发送成功的消息的顺序。
另外一个readExpired(读过期)默认为false,当writeExpired为true并且队列深度为0时,RealQueue修改自身readExpired为true。直到同一版本的所有RealQueue的readExpired属性都为true时,消费方才会向下一个高版本消费,否则仍然在此版本。即消费者消费消息的版本为至少有一个RealQueue的ReadExpired为false的最低版本。
下面用RealQueue示例演示动态扩缩容的过程,并确保消息的顺序性。
(1)初始状态(V1)
参照图3,OrderQueue的初始状态共有N组RealQueue对象。
其中,“RealQueue”指各个Broker上的消息队列,它是用于存储消息的容器。消息队列必须先配置好,才能发消息进去。
配置模块(ZK)中存储的以及所有Producer、Consumer客户端获取到都是以上相同的配置。按照静态的状态收发消息即可保证顺序。
(2)扩缩容一次(V2)
参照图4,OrderQueue的初始状态共有N+M组RealQueue对象。
(2.1)配置模块新加V2版本的队列,所有版本队列的读写过期权限全是false。
(2.2)V1版本的RealQueue的Broker端感知到有更高版本出现时,把自己的写过期置为true,并同步给配置模块。
(2.3)有的生产者Producer1没有收到版本状态变化,仍然按照V1版本进行hash并发消息,会被V1的RealQueue的Broker端拒绝,并收到版本过期异常。Producer收到此异常后向V2版本发送。
参照图4,虚线箭头代表Producer1按照V1版本进行hash并发消息,但是被拒绝,没有发送成功。后续的实线箭头向V1版本发送成功。
(2.4)有的生产者Producer2收到了版本状态变化,直接向最新的写过期为false的V2版本发送消息。
(2.5)V1版本的RealQueue的Broker定期遍历写过期为true的消息队列的消息深度,当深度为0即无堆积消息时,将此消息队列的读过期标志设置为true。
(2.6)消费者客户端消费至少一个Broker的读过期状态为false的最低版本的RealQueue,如图4,V1版本有一个RealQueue的读未过期,即V1版本的Broker1,因此还是要消费V1的N个Broker上的RealQueue。直到这个版本全部读过期后,才能消费下一版本。
其中,消费者客户端(消费模块)是从ZK(配置模块)上获取信息,从而确定些消息队列已过期哪些没过期,然后从没过期的消息队列里面去请求。
(3)扩缩容Z-1次(VZ)
OrderQueue的初始状态共有N+M+…+L组RealQueue对象。
(3.1)配置模块新加VZ版本的队列,VZ-1版本队列的读写过期权限当前仍为false。
(3.2)VZ-1版本的RealQueue的Broker端感知到有更高版本出现时,把自己的写过期置为true,并同步给配置模块。
(3.3)生产者从至少有一个RealQueue的写权限仍为false的最低版本进行hash并发消息,如果当前版本的将要发送的RealQueue已经写过期或收到Broker端的版本过期异常。生产者继续向高一个版本的RealQueue发送,直到发送成功。
(3.4)所有版本的RealQueue的Broker定期遍历写过期为true的队列消息深度,当深度为0即无堆积消息时,将此队列的读过期标志设置为true。
(3.5)消费者客户端消费至少一个Broker的读过期状态为false的最低版本的RealQueue。
在上述不断扩容和缩容的动态过程中,任意一个RealQueue处于以下状态:读写未过期、写过期、读写过期,或者分布式的生产者和消费者没有同步感知到这些状态变化,都不会影响消息的顺序性。最终,配置模块(ZK)中存储的以及所有Producer、Consumer客户端获都会达到最新版本的配置。
本申请的系统通过版本做到扩缩容时对消息顺序性的保证。引入读过期和写过期的控制,确保在动态扩缩容的并发状态下,顺序消息的路由策略。无需引入第三方中心节点来完成顺序性消息的计算编排和路由。
可以理解的是,上述各实施例中相同或相似部分可以相互参考,在一些实施例中未详细说明的内容可以参见其他实施例中相同或相似的内容。
需要说明的是,在本申请的描述中,术语“第一”、“第二”等仅用于描述目的,而不能理解为指示或暗示相对重要性。此外,在本申请的描述中,除非另有说明,“多个”的含义是指至少两个。
流程图中或在此以其他方式描述的任何过程或方法描述可以被理解为,表示包括一个或更多个用于实现特定逻辑功能或过程的步骤的可执行指令的代码的模块、片段或部分,并且本申请的优选实施方式的范围包括另外的实现,其中可以不按所示出或讨论的顺序,包括根据所涉及的功能按基本同时的方式或按相反的顺序,来执行功能,这应被本申请的实施例所属技术领域的技术人员所理解。
应当理解,本申请的各部分可以用硬件、软件、固件或它们的组合来实现。在上述实施方式中,多个步骤或方法可以用存储在存储器中且由合适的指令执行系统执行的软件或固件来实现。例如,如果用硬件来实现,和在另一实施方式中一样,可用本领域公知的下列技术中的任一项或他们的组合来实现:具有用于对数据信号实现逻辑功能的逻辑门电路的离散逻辑电路,具有合适的组合逻辑门电路的专用集成电路,可编程门阵列(PGA),现场可编程门阵列(FPGA)等。
本技术领域的普通技术人员可以理解实现上述实施例方法携带的全部或部分步骤是可以通过程序来指令相关的硬件完成,所述的程序可以存储于一种计算机可读存储介质中,该程序在执行时,包括方法实施例的步骤之一或其组合。
此外,在本申请各个实施例中的各功能单元可以集成在一个处理模块中,也可以是各个单元单独物理存在,也可以两个或两个以上单元集成在一个模块中。上述集成的模块既可以采用硬件的形式实现,也可以采用软件功能模块的形式实现。所述集成的模块如果以软件功能模块的形式实现并作为独立的产品销售或使用时,也可以存储在一个计算机可读取存储介质中。
上述提到的存储介质可以是只读存储器,磁盘或光盘等。
在本说明书的描述中,参考术语“一个实施例”、“一些实施例”、“示例”、“具体示例”、或“一些示例”等的描述意指结合该实施例或示例描述的具体特征、结构、材料或者特点包含于本申请的至少一个实施例或示例中。在本说明书中,对上述术语的示意性表述不一定指的是相同的实施例或示例。而且,描述的具体特征、结构、材料或者特点可以在任何的一个或多个实施例或示例中以合适的方式结合。
尽管上面已经示出和描述了本申请的实施例,可以理解的是,上述实施例是示例性的,不能理解为对本申请的限制,本领域的普通技术人员在本申请的范围内可以对上述实施例进行变化、修改、替换和变型。