返回介绍

消息 时序 与 一致性 为何这么难?

发布于 2025-02-23 22:52:39 字数 6199 浏览 0 评论 0 收藏 0

分布式系统中,很多业务场景都需要考虑消息投递的时序,例如:

(1)单聊消息投递,保证发送方发送顺序与接收方展现顺序一致

(2)群聊消息投递,保证所有接收方展现顺序一致

(3)充值支付消息,保证同一个用户发起的请求在服务端执行序列一致

消息时序是分布式系统架构设计中非常难的问题,ta 为什么难 ,有什么常见 优化实践 ,是本文要讨论的问题。

一、为什么时序难以保证,消息一致性难?

为什么分布式环境下,消息的时序难以保证,这边简要分析了几点原因:

【时钟不一致】

分布式环境下消息时序
分布式环境下,有多个客户端、有 web 集群、service 集群、db 集群,他们都分布在不同的机器上,机器之间都是使用的本地时钟,而没有一个所谓的“全局时钟”,所以 不能用“本地时间”来完全决定消息的时序

【多客户端(发送方)】

多客户端时序

多服务器不能用“本地时间”进行比较,假设只有一个接收方,能否用接收方本地时间表示时序呢?遗憾的是,由于多个客户端的存在, 即使是一台服务器的本地时间,也无法表示“绝对时序”

如上图,绝对时序上,APP1 先发出 msg1,APP2 后发出 msg2,都发往服务器 web1,网络传输是不能保证 msg1 一定先于 msg2 到达的,所以即使以一台服务器 web1 的时间为准,也不能精准描述 msg1 与 msg2 的绝对时序。


【服务集群(多接收方)】
多接收方时序

多发送方不能保证时序,假设只有一个发送方,能否用发送方的本地时间表示时序呢?遗憾的是,由于多个接收方的存在, 无法用发送方的本地时间,表示“绝对时序”

如上图,绝对时序上,web1 先发出 msg1,后发出 msg2,由于网络传输及多接收方的存在,无法保证 msg1 先被接收到先被处理,故也无法保证 msg1 与 msg2 的处理时序。


【网络传输与多线程】
网络传输与多线程

多发送方与多接收方都难以保证绝对时序,假设只有单一的发送方与单一的接收方,能否保证消息的绝对时序呢?结论是悲观的,由于网络传输与多线程的存在,仍然不行。

如上图,web1 先发出 msg1,后发出 msg2,即使 msg1 先到达(网络传输其实还不能保证 msg1 先到达),由于多线程的存在,也不能保证 msg1 先被处理完。

【怎么保证绝对时序】

通过上面的分析,假设只有一个发送方,一个接收方,上下游连接只有一条连接池,通过阻塞的方式通讯,难道不能保证先发出的消息 msg1 先处理么?

回答:可以,但 吞吐量会非常低 ,而且单发送方单接收方单连接池的假设不太成立,高并发高可用的架构 不会允许这样的设计出现

二、优化实践

【以客户端或者服务端的时序为准】

多客户端、多服务端导致“时序”的标准难以界定,需要一个标尺来衡量时序的先后顺序,可以根据业务场景,以客户端或者服务端的时间为准,例如:

(1) 邮件展示顺序 ,其实是以客户端发送时间为准的,潜台词是,发送方只要将邮件协议里的时间调整为 1970 年或者 2970 年,就可以在接收方收到邮件后一直“置顶”或者“置底”

(2) 秒杀活动时间判断 ,肯定得以服务器的时间为准,不可能让客户端修改本地时间,就能够提前秒杀

【服务端能够生成单调递增的 id】

这个是毋庸置疑的,不展开讨论,例如利用单点写 db 的 seq/auto_inc_id 肯定能生成单调递增的 id,只是说性能及扩展性会成为潜在瓶颈。对于严格时序的业务场景,可以利用服务器的单调递增 id 来保证时序。

【大部分业务能接受误差不大的趋势递增 id】

消息发送、帖子发布时间、甚至秒杀时间都没有这么精准时序的要求:

(1)同 1s 内发布的聊天消息时序乱了

(2)同 1s 内发布的帖子排序不对

(3)用 1s 内发起的秒杀,由于服务器多台之间时间有误差,落到 A 服务器的秒杀成功了,落到 B 服务器的秒杀还没开始,业务上也是可以接受的(用户感知不到)

所以,大部分业务,长时间趋势递增的时序就能够满足业务需求,非常短时间的时序误差一定程度上能够接受。

关于绝对递增 id,趋势递增 id 的生成架构,详见文章《 细聊分布式 ID 生成方法 》,此处不展开。

【利用单点序列化,可以保证多机相同时序】

数据为了保证高可用,需要做到进行数据冗余, 同一份数据存储在多个地方,怎么保证这些数据的修改消息是一致的呢? 利用的就是“单点序列化”:

(1)先在一台机器上序列化操作

(2)再将操作序列分发到所有的机器,以保证多机的操作序列是一致的,最终数据是一致的


典型场景一:数据库主从同步
数据库主从同步
数据库的主从架构,上游分别发起了 op1,op2,op3 三个操作,主库 master 来序列化所有的 SQL 写操作 op3,op1,op2,然后把相同的序列发送给从库 slave 执行,以保证所有数据库数据的一致性,就是利用“单点序列化”这个思路。

典型场景二:GFS 中文件的一致性

GFS 中文件的一致性

GFS(Google File System) 为了保证文件的可用性,一份文件要存储多份,在多个上游对同一个文件进行写操作时,也是由一个主 chunk-server 先序列化写操作,再将序列化后的操作发送给其他 chunk-server,来保证冗余文件的数据一致性的。

【单对单聊天,怎么保证发送顺序与接收顺序一致】

单人聊天的需求,发送方 A 依次发出了 msg1,msg2,msg3 三个消息给接收方 B,这三条消息能否保证显示时序的一致性(发送与显示的顺序一致)?

回答:

(1)如果利用服务器单点序列化时序,可能出现服务端收到消息的时序为 msg3,msg1,msg2,与发出序列不一致

(2)业务上不需要全局消息一致,只需要对于同一个发送方 A,ta 发给 B 的消息时序一致就行,常见优化方案,在 A 往 B 发出的消息中,加上发送方 A 本地的一个绝对时序,来表示接收方 B 的展现时序

msg1{seq:10, receiver:B,msg:content1 }
msg2{seq:20, receiver:B,msg:content2 }
msg3{seq:30, receiver:B,msg:content3 }
发送顺序与接收顺序一致

潜在问题:如果接收方 B 先收到 msg3,msg3 会先展现,后收到 msg1 和 msg2 后,会展现在 msg3 的前面。

无论如何,是按照接收方收到时序展现,还是按照服务端收到的时序展现,还是按照发送方发送时序展现,是 pm 需要思考的点,技术上都能够实现(接收方按照发送时序展现是更合理的)。

总之,需要一杆标尺来衡量这个时序。

【群聊消息,怎么保证各接收方收到顺序一致】

群聊消息的需求,N 个群友在一个群里聊,怎么保证所有群友收到的消息显示时序一致?

回答:

(1)不能再利用发送方的 seq 来保证时序,因为发送方不单点,时间也不一致

(2)可以利用服务器的单点做序列化

利用服务器的单点做序列化

此时群聊的发送流程为:

(1)sender1 发出 msg1,sender2 发出 msg2

(2)msg1 和 msg2 经过接入集群,服务集群

(3)service 层到底层拿一个唯一 seq,来确定接收方展示时序

(4)service 拿到 msg2 的 seq 是 20,msg1 的 seq 是 30

(5)通过投递服务讲消息给多个群友,群友即使接收到 msg1 和 msg2 的时间不同,但可以统一按照 seq 来展现

这个方法能实现,所有群友的消息展示时序相同。

缺点是,这个生成全局递增序列号的服务很容易成为系统瓶颈,还有没有进一步的 优化方法呢


思路 :群消息其实也不用保证全局消息序列有序,而只要保证一个群内的消息有序即可,这样的话,“id 串行化”就成了一个很好的思路。
保证一个群内的消息有序

这个方案中,service 层不再需要去一个统一的后端拿全局 seq,而是在 service 连接池层面做细小的改造, 保证一个群的消息落在同一个 service 上 ,这个 service 就可以用本地 seq 来序列化同一个群的所有消息,保证所有群友看到消息的时序是相同的。

关于 id 串行化的细节,可详见《 缓存与数据库一致性问题 》,此处不展开。

三、总结

(1)分布式环境下, 消息的有序性是很难 的,原因多种多样:时钟不一致,多发送方,多接收方,多线程,网络传输不确定性等

(2) 要“有序”,先得有衡量“有序”的标尺 ,可以是客户端标尺,可以是服务端标尺

(3) 大部分业务能够接受大范围趋势有序,小范围误差 ;绝对有序的业务,可以借助服务器绝对时序的能力

(4) 单点序列化 ,是一种常见的保证多机时序统一的方法,典型场景有 db 主从一致,gfs 多文件一致

(5) 单对单聊天 ,只需保证发出的时序与接收的时序一致,可以利用客户端 seq

(6) 群聊 ,只需保证所有接收方消息时序一致,需要利用服务端 seq,方法有两种,一种 单点绝对时序 ,另一种 id 串行化

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文