使用消息队列对应用解耦

解耦的故事

题图:from Google

引言

何谓解耦?

解耦就是让不同部分独立化,解耦的方式就是在各个模块之间加上一层“中介”,系统的变化、影响关系越多,就要保证依赖关系越少,解耦也就做的越好,“中介”也就越多,系统也就越复杂,解耦的意义与效果也就越明显。

解耦是一个比较大的概念,因此,上述也使用了一个比较宽泛的概念——“中介”。这个“中介”的客户当然就是系统的各个模块,而“中介”的表现形式就有很多了。

  1. 中介可以是一个Interface,通过统一接口将实现方式屏蔽,达到调用和实现的解耦。不论实现如何变化,调用都不受影响。
  2. 中介可以是一个消息队列,通过消息队列将业务逻辑拆分,达到主逻辑和附加逻辑的解耦。附加逻辑失败不会导致主逻辑失败。
  3. 中介可以是一个中间件,通过消息路由或者其他方式拆分业务逻辑。
  4. 中介可以使一种设计模式,通过观察者模式将依赖减少,其思想依然是事件机制,是某种意义上的伪队列。

今天,就简单说说使用消息队列对业务逻辑进行解耦。

使用消息队列进行解耦

为什么使用消息队列解耦

我们的目标是对主逻辑和附加逻辑进行解耦,针对这个需求,并非只有消息队列一种解决方案。启用新线程处理附加逻辑、定时任务处理附加逻辑等方案都可以完成。

因此,可以做一下横向对比:

  1. 消息队列
    • 优点:基于现有的模型(Pub/Sub)即可完成需求,一般来说系统内部除了解耦还有其他需求使用队列。并且Spring等框架对各类主流消息队列的支持都比较好,因此可以说是拿来就用。
    • 缺点:分布式部署有可能出现消息重复处理问题,使用队列会使追踪问题变得困难,因为缺少调用关系。需要考虑消息队列稳定性等问题。
  2. 定时任务
    • 优点:定时任务也是系统内部常用的组件,在拿来即用这一点上与消息队列类似;稳定,与业务代码部署在同一服务器上,只要服务不挂就不会有问题。
    • 缺点:当执行量大起来后,会创建大量定时任务,对服务器造成较大压力。
  3. 多线程
    • 优点:无需任何外部组件,Java原生支持以多线程方式执行任务
    • 缺点;线程不能无限增加,使用线程池同样存在执行量大时线程数是瓶颈的问题。

综上分析,还是选择以消息队列方式进行解耦。

如何使用消息队列解耦

主流消息队列框架(ActiveMQ,RabbitMQ等)都支持生产/消费者(queue)、发布/订阅模式(topic),这两种典型的消息模型。

面对我们的需求——将附加逻辑从主逻辑中剥离,显而易见,附加逻辑都是依赖于主逻辑的完成,也就是在主逻辑执行完后才去执行附加逻辑。

参照上述提到queue和topic,选择topic模式就是顺理成章的事了。在主逻辑完成后只需发布一条通知消息,各个订阅模块即可进行相应的附加逻辑的处理了。

分布式下的问题

在分布式部署下的Topic消息队列存在一个订阅者重复的问题。

分布式下的问题

有上图可清晰看到,原本代码的意图是一个发布者,两个订阅者;但是如果分布式部署就会出现一个发布者,四个订阅者的问题。

分布式问题的解决

只使用某台机器

一个最简单的解决方案,只使用某一台服务器,一次保证订阅者不重复。不过,这有点因噎废食的意味:),使用分布式部署就是为了减少单个服务器压力,如果只使用某一台服务区处理队列消息,显然是有违初衷的。

使用消息队列提供的VitualTopic特性

ActiveMQ、RabbitMQ都有提供VirtualTopic特性。该特性就是为了解决这一问题的。

VirtualTopic方案

简单来讲就是topic下又挂了几个queue。不需要订阅者关联关联订阅主题,VirtualTopic在接收到消息后会查找所有关联的消费队列,把消息在这些队列中各保存一份。

订阅者在自己的队列上进行消费,这样分布式部署的消费者可以共同消费一个queue中的消息。这样就避免了重复消费消息的问题。

使用路由框架

由于SpringBoot等框架对VirtualTopic方案的支持不是很好,虽然可以实现,但是还是比较复杂,我们还可以采用现有的一些消息路由框架,如Apache Camel。

这类框架的本质就是路由规则引擎,允许开发者自定义消息流转,举个简单的例子就是,可以手动实现截取发布者的消息,分发到指定的队列或者方法。

以实际案例进行说明

理论一定要通过实践才会得到认可,全文一直都在说理论,这就以一次简单的解耦案例进行说明。

首先,我们的已有结构:主逻辑与附加逻辑依次执行,附加逻辑执行异常会导致主逻辑失败回滚。

已有结构

根据上文理论,解耦后的结构:主逻辑与附加逻辑彻底拆分,附加逻辑模块自己保证执行成功,以此确保最终一致性。

解耦后逻辑

总结

本文从为什么要解耦说到如何使用消息队列解耦,从分布式部署可能遇到的问题讲到使用路由框架解决方案,并以一次真实的解耦小案例为例子说明了解耦的过程,到这里就算是结束了。希望这篇文章能对大家有所帮助。