对于请求处理,先入库,再发消息,没你想得这么简单

聊技术,不止于技术

微服务架构下,各个微服务间的通信方式是首先需要决定的事。微服务间的通信方式主要有REST、RPC和消息这三种。这三种通信方式各有优缺点,各有其适合的场景,关于它们的比较及分析今天就先不讲了。

今天主要讲的是基于消息的通信方式下,先入库在发送消息的问题。

基于消息的通信方式下,各个微服务间通过消息驱动来完成业务逻辑。一个典型的例子如下:

上例中,用户服务处理用户注册请求,先入库,然后发送用户注册事件,邮件服务监听用户注册事件,然后发送欢迎邮件。

那么就上述场景而言,对于用户服务,我们的业务代码该如何写呢?为什么我说先入库再发消息,没你想得这么简单呢?下面一起来看看。

1

先入库,再发消息,简单又直接的方式

简单又直接的入库发消息伪代码如下:

1. var content = processRequest(httpRequest); 2. var message = prepareMessage(httpRequest); 3. DB.insert(content); 4. Message.publish(message);

其实上述问题的本质是分布式事务问题,数据库和消息总线实际是两个资源。想要保持两个或者多个资源间的数据一致性,以及操作的原子性,这正是分布式事务要解决的问题。

让我们尝试解决此类问题。

首先要问的一个问题是,我们的系统需要强一致性吗?在上述例子中如果数据库和消息总线中的数据需要保持强一致,则在任一时刻数据库与消息总线中的数据都需要保持一致。

显然并不需要。

实际在分布式系统中,只要保持弱一致性就可以了,也就是说终一致性,对应上例,也就是说在任一时刻,数据库与消息总线中的数据可以暂时不一致,但终需要一致,只不过中间有一些间隔。

所以现在我们只要让系统具备终一致性就可以了,那么如何具备终一致性呢?

在上例中,把问题具体化,其实就是处理完请求并且入库后,必须发送消息,也就是数据库中有的数据,消息总线中也必须有。问题进一步抽象定义,即解决数据库入库和发送消息的原子性问题,这两个操作要么都成功,要么都失败。并且现在我们的系统只需要满足弱一致性就可以,所以问题可以更进一步定义为这两个操作要么终都成功,要么终都失败。

看到这里,有一个方案应该能够浮现出来——本地消息表。

本地消息表的伪代码如下:

1. var content = processRequest(httpRequest); 2. var message = prepareMessage(httpRequest); 3. DB.begin(); 4. DB.insert(content); 5. DB.insert(message); 6. DB.commit(); 7. Message.publish(message); 8. DB.delete(message); // 定时任务,补偿发送消息,这里查询的消息注意避免时间存在过短的问题,以防重复发送 Executor.execute(new Task() {             public void run() {                 while (true) {                     var message = DB.selectMessage();                     Message.publish(message);                     DB.delete(message);                     }                 } });

那是不是此场景下所有的应用都需要按照此类方案来呢?

我这里的建议是看具体业务需求。

大流量大规模的分布式系统,从可靠性及可维护性来讲,必须这么做。至于那些用户少,规模小的应用,从故障发生的概率、发生故障后人员维护的成本来考虑,你可以不遵守上述方案。

当然,能够看到这些问题然后选择一个适合的方案,和不知道自己在做什么完全是两码事。

先知道规则,然后再知道什么时候可以打破规则。

写在后

对于请求处理,先入库再发消息的场景并没有看起来的这么简单。该问题实际是一个分布式事务问题,涉及到两个资源间的数据一致性,入库与发消息原子性问题。

对于大多数分布式应用,能够满足数据终一致性就可以。

所以上述场景可以采用本地消息表的方案,本地消息表实质上是利用了本地数据库的事务特性,保证业务处理与消息存储的事务特性。

本地消息表可能会存在消息重复发送的问题,所以需要实现消费端的幂等。

先知道规则,然后再知道什么时候可以打破规则。

后留一个问题,对于消费端来说,接收消息,然后处理入库,如何保持幂等?如果是接收消息,然后处理入库,再然后再发消息的场景呢?如果是接收消息,然后远程调用的场景呢?

如果能够回答清楚上述问题,不光光是对这些场景有很深的理解,相信你对整个分布式系统的设计与实现都有很深的理解。

后面的文章,说一说我对上述场景以及分布式系统设计与实现的理解。欢迎大家关注。

推荐阅读:《重拾面向对象软件设计:把复杂的事情做简单》

聊技术,不止于技术。

在这里我会分享技术文章、管理知识以及个人的思想感悟,欢迎点击关注。