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

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

今天主要讲的是基于消息的通信方式下,先入库在发送消息的问题。基于消息的通信方式下,各个微服务间通过消息驱动来完成业务逻辑。一个典型的例子如下:

image.png

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

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

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

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

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

对于先入库,再发消息的业务逻辑,最简单直接的代码如上,那么上述代码有什么问题吗?

考虑以下场景:

(1) 数据库入库成功,发送消息失败,即步骤3成功,步骤4失败


image.png

数据可能不一致。因为步骤4失败的原因总的来说有两个,一是消息发送失败(消息总线未接收到消息),此时数据不一致,因为数据库中有数据,但消息未发送。二是消息发送成功(消息总线接收到),但回包的时候失败,对于系统来说,此时数据反而是一致的,数据入库,消息发送了。

(2) 数据库入库失败


image.png

数据可能不一致。有人可能会想数据库操作失败,数据库的事务ACID特性可以保证数据一致性。但实际这里也可能有两种情况,一是数据库操作失败,事务未提交,此时数据一致,数据库会回滚事务。二是事务已提交,数据库回包失败,数据不一致(数据库和消息总线中的数据不一致),数据库中有数据,消息却没发送。

2 最简单直接的方式并不好使,那该怎么办?

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

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

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

显然并不需要。

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

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

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

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

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

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);}
                }
        });

该方案本质上是利用了本地数据库事务的特性,将消息和业务逻辑处理放在一个事务里持久化,利用事务特性可以保证业务处理和消息能够同时存储成功或失败,然后在发送消息。

同样,让我们考虑下述场景:

(1)数据库入库成功,消息发送失败,即步骤3~6成功,步骤7失败


image.png

数据最终一致。定时任务会补偿消息投递,当然这里也可能会存在消息重复发送的问题。

(2)数据库入库失败,即步骤3~6失败。


image.png

数据最终一致。数据库在事务提交前失败,数据一致。数据库在事务提交后,但回包前失败,数据最终一致,数据已存在,定时任务会补偿消息投递。

(3)数据库入库成功,消息发送成功,消息删除失败,即步骤3~7成功,步骤8失败。


image.png

数据最终一致。消息未删除,定时任务补偿发送消息,会导致消息重复发送。消息已删除,但数据库回包前失败,补偿任务不做处理,数据最终一致。

可以看到本地消息表除了会导致消息重复投递,几乎没有别的问题。

那么消息重复投递怎么办?

如果消息重复投递,这里只看数据库跟消息总线,其实数据是不一致的,消息总线中数据多了。但从整个系统的层面来看呢?如果消费端能够实现幂等,那么整个系统的数据还是最终一致的。所以采用本地消息表,下游消费端需要实现幂等。而且现在有的消息中间件也能够实现发送消息的幂等(比如Kafka 0.11版本以上,Broker可以通过发送的消息id进行去重,保证发送消息的幂等),即重复投递的消息在消息中间件中只会存在一份,这样系统也是没有问题的。

3 先入库,再发消息原来不简单,那么符合上述场景的业务是不是都得这么做呢?

有同学看到这里可能会觉得原来处理请求,先入库再发消息的场景原来不是这么简单呀,看来以前用错了?

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

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

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

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

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

写在最后

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

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

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

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

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

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

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

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

希望今天的内容对大家有所帮助,更多精彩文章欢迎关注微信公众号:WU双。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容