问题简述
消息总线目前为Java编程语言提供了SDK,同时针对其他语言提供了一个称之为httpBridge的http代理。这基本可以满足大部分主流编程语言对消息总线的使用需求,但这也仅仅是对技术层面上的需求的满足。在业务层面上,尤其是面对老的业务系统的适配一直都是个难题,这篇文章谈谈面对一个在线上运行的业务系统,如何使得引入消息总线的总体成本尽可能得低。
就消息总线的两种使用方式而言,无论是SDK的方式还是httpBridge的方式,都需要往第三方系统引入对消息总线的依赖,这些依赖包括但不仅限于:SDK(jar)本身的依赖、API调用接口的依赖以及了解、熟悉或学习的成本。我们说尽量将总体成本降到最低,其实这些依赖就是所谓的成本,当然还有维护的成本等(当你对原有系统引入新的技术组件,就必须接受引进新Bug的风险)。
依赖转移
上面谈到了依赖,第三方对消息总线的依赖,我们想看看能否对这种现状进行“依赖转移”。这个思维的变换对程序员来说是比较容易过渡的,作为Java程序员重度依赖的框架——Spring,主要就是用来针对性得处理类似问题(当然在Spring里称之为Ioc即控制反转)。
要想进行依赖转移,我们首先需要清楚第三方为什么会对消息总线产生依赖性?因为消息总线只是个中间件。它不知道通信双方的消息内容以及通信时机(生产、消费的时机,有可能会存在触发条件)。因此第三方如果想通过消息总线跟其他组件通信,它们必须主动调用消息总线来生产消息或者消费消息。
通过上面的分析,我们知道第三方组件对消息总线的依赖性是因为它们必须主动跟消息总线交互。就消费而言,假设消费的时机没那么重要(只要我关注的队列里有消息,我就可以消费掉),那么我们是否可以让消息总线主动把消息推送给我?
这里有两个在表达上很类似,其实差别很大的问题,必须首先澄清一下:
- 第三方接收消息总线主动推送的消息
- 第三方以推送的模型从消息总线上消费消息
第一个问题的主要想表达的意思是:第三方系统不主动与消息总线或其他任何组件连接,就能接收到消息;
第二个问题的主要想表达的意思是:通过消息总线消费消息的Push模型(另一个模型是pull),这只是消费机制,这样的机制建立在主动调用消息总线接口的前提下。
思路
很明显我们想要解决的是第一个问题。如何让消息总线主动推送消息?如果你把容纳消息的容器看做是数据源,那么首先消息总线的队列就是数据源,现在相当于要把一个数据源里的数据写入到另一个数据源,而这里的“另一个数据源”以及“写入”这一行为要能够很轻易得跟第三方系统衔接。可行的一种方式是第三方系统单独开发一个接收消息的http请求处理程序(考虑到大部分系统都是web系统,那么这里就是一个单独的controller或者如果是Java Web技术,那么就是一个Servlet)。那么“另一个数据源”直接就可以是第三方系统,而“写入” 这一行为的技术是通过http这种平台无关的通信协议。
实现方式
消息总线如何知晓第三方数据源的接口?这需要引入一个registry(注册表),也就是说当第三方系统在申请队列的时候,它可以往该注册表内登记一条信息以表明:如果该队列接收到消息,请代为转发到我注册的这个接口。因此,它其实是个key-value形式的映射关系的容器来衔接消息总线(队列)以及第三方系统(接口),这成功得进行了依赖转移!
有了这个对应关系,我们就可以给消息总线开一个专门扫描该注册表的cron job,我们暂且将其称之为:consume proxy service。该Job以一定的时间间隔扫描该registry里的所有队列-接收接口的映射关系,然后以pull的模式尝试从相应的队列里消费消息,当消费到消息之后,将其发送到已被注册的接口,而该接口是第三方一个专门用于接收消息总线消息的请求处理器,大致流程如下:
见图中所有虚线部分。
现在第三方系统只需知道消息总线的消息格式即可,而无需直接对消息总线产生较强的依赖——因为它不再需要主动跟消息总线通信,而是被动接收。
风险应对
其实依赖对哪一方都会产生影响,这也是软件开发中一直在强调“低耦合”的原因。一旦消息总线“依赖”第三方系统——因为它要将消息通过http请求发送个第三方系统。http请求的特征:请求响应模型,如果第三方系统不给响应,或者恶意hold住请求不释放,那么消息总线的这个代理消费并转发服务很容易被拖死。
解决这个问题可以有如下的一些手段:
- 给第三方接口建议:如果处理消息的逻辑很耗时,可以暂时将消息缓存后进行异步处理,先让请求返回
- 对第三方接口进行严格的审核:对可访问性以及请求的响应速度给出严格的审核标准
- 消息总线提供的转发服务被实现为“尽力而为”的服务:何为尽力而为?也即非核心服务,它的稳定状态不会影响消息总线提供的核心服务。并且它应该被设计为“可降级”的。当发现它严重不稳定时,可将其关闭(因为消息如果没被转发出去,它仍然被存储在队列中,并不会丢失,后续仍可消费)。甚至可以将整个虚线部分转移到消息总线的外部实现。
- 可以开发一些“watch dog”服务,专门监控第三方接口,一旦发现异常,即将其从registry中移除
以上手段可以多条并用来保障代理消费&转发服务的稳定性。
上面我们主要谈到了对消费方而言,通过依赖转移的方式来使得第三方系统对消息总线的依赖降到最低,从而减少了第三方系统对消息总线的接入成本、维护成本以及引入的复杂性。其实能够做到这一点主要是因为我们假设了:消费方对消费的时机没有要求,队列里有数据就可以被消费走。所以这种依赖转移的方式,无法被应用在想要生产消息的系统中。因此生产者对消息总线的依赖无法降低!
应用场景
消费者对消息总线依赖性降低的好处主要体现在需要应用Pub/Sub模型的系统群中。在这个系统群中,消息的生产者所对应的业务系统较少,而消息的消费者对应的业务系统较多。这种降低依赖性手段还有一个前提:第三方系统都是web系统。
这种消费模式即可视为消息总线的主动推送模式。只是一个设想,目前还没有正式实现,如果消息总线提供这个功能,那么它将在“消费模式”以及“消费消息的接口”(API)上同时提供“推拉结合”的方式。