当前位置: 首页 > 科技观察

为什么消息“定时”和“一致性”如此困难?

时间:2023-03-12 05:39:25 科技观察

在分布式系统中,很多业务场景需要考虑消息传递的时机,例如:(1)单聊消息传递,保证发送方发送的顺序与接收方显示的顺序一致(2)群聊消息传递,确保所有(3)充值和支付消息在接收方显示顺序相同,保证同一用户发起的请求在服务器上执行顺序一致。消息定时是分布式系统架构设计中一个非常难的问题。本文要讨论的问题。1、为什么时序和消息一致性难以保证?为什么在分布式环境中很难保证消息的时序?这里有几个原因:[时钟不一致]在分布式环境中,有多个客户端和web集群、服务集群、db集群都分布在不同的机器上。机器之间使用本地时钟,并没有所谓的“全局时钟”,所以不能用“本地时间”来完全确定消息的时间。.[多客户端(发送方)]多个服务器不能使用“本地时间”进行比较。假设只有一个接收者,是否可以用接收者的本地时间来表示计时呢?不幸的是,由于多个客户端的存在,即使是服务器的本地时间也不能代表“绝对时间”。如上图所示,从绝对时间上来说,APP1先发送msg1,APP2再发送msg2,都是发给服务器web1的。网络传输不能保证msg1一定在msg2之前到达,所以即使以某台服务器web1的时间为标准,也无法准确描述msg1和msg2的绝对时间。【服务集群(多个接收者)】多个发送者无法保证时序。假设只有一个发件人,可以用发件人当地时间来表示计时吗?不幸的是,由于多个接收者的存在,无法使用发送者本地时间进行“绝对计时”。如上图所示,从绝对时间上来说,web1先发送msg1,再发送msg2。由于网络传输和多个receiver的存在,不能保证msg1先接收到先处理,所以无法保证msg1和msg2的处理时序。[网络传输和多线程]多个发送者和多个接收者很难保证绝对定时。假设只有一个发送者和一个接收者,是否能保证消息的绝对时序?结论是悲观的,因为网络传输和多线程的存在还是不行。如上图所示,web1先发送msg1,再发送msg2。即使msg1先到(网络传输不能保证msg1先到),由于多线程的存在,也不能保证msg1先到。【如何保证绝对定时】通过上面的分析,假设只有一个发送者和一个接收者,上下游连接只有一个连接池,通过阻塞通信,是不是可以保证消息先发送的msg1会先处理?答:可以,但是吞吐量会很低,而且单发送方、单接收方、单连接池的假设是不成立的,高并发高可用的架构不会让这样的设计出现。2.优化实践【基于客户端或服务器的时序】多客户端多服务器,很难定义“时序”标准。需要一个尺子来衡量计时的先后顺序,可以根据业务场景以客户端为准,也可以以服务端为准。以服务器时间为准,例如:(1)邮件显示顺序实际上以客户端发送时间为准。当事人收到邮件后,一直“置顶”或“置顶”(2)秒杀活动时间的判断必须以服务器的时间为准。不可能让客户端修改本地时间,这样就可以提前秒杀【服务端可以生成一个单调递增的id】这个是毋庸置疑的,不讨论。比如使用单点写db的seq/auto_inc_id,肯定会生成一个单调递增的id。只是性能和可扩展性会成为潜在的瓶颈。对于时序严格的业务场景,可以使用服务端单调递增的id来保证时序。【大部分业务都能接受增量id,误差不大】消息发送、发布后时间,甚至秒杀时间都没有这么精确的计时要求:(1)1s内发布的聊天消息计时乱序(2)同上1s内发布的帖子顺序错误(3)1秒内发起的秒杀,由于多台服务器之间的时间误差,登陆A服务器的秒杀成功,但是登陆B服务器的秒杀还没有开始,这在业务上是可以接受的(用户无法感知)。因此,对于大多数业务而言,长期趋势上涨的时机可以满足业务需求,极短时间的时机误差在一定程度上是可以接受的。关于绝对增量id和趋势增量id的生成结构,请参考文章《细聊分布式ID生成方法》,这里不再展开。【使用单点序列化可以保证多台机器时序一致】为了保证高可用,需要实现数据冗余。相同的数据存储在多个地方。如何保证这些数据的修改消息是一致的呢?使用中最重要的是“单点序列化”:(1)先将一台机器上的操作序列化(2)将操作顺序分发到所有机器上,保证多台机器的操作顺序一致,最终数据为consistent典型场景一:数据库主从同步数据库的主从架构,上游发起op1、op2、op3三个操作,主库master序列化所有sql写操作op3、op1、op2,然后发送相同的sequence从库slave开始执行,保证所有数据库数据的一致性,就是利用了“单点序列化”的思想。典型场景二:GFS中文件的一致性为了保证文件的可用性,GFS(GoogleFileSystem)必须存储一个文件的多个副本。当多个upstream写入同一个文件时,也是由一个masterchunk-server先将写入操作序列化,然后将序列化后的操作发送给其他chunk-server,以保证冗余文件的数据一致性。【一对一聊天,如何保证发送顺序与接收顺序一致】针对单人聊天的需求,发送方A依次向接收方B发送了3条消息msg1、msg2、msg3。这三个消息能否保证相同的显示顺序?责任(按相同的顺序发送和显示)?答:(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}潜在问题:如果receiverB先收到msg3,则先显示msg3,收到msg1和msg2后,再显示在前面消息3。不管怎样,是按照receiver收到的timing显示,还是按照server收到的timing显示,还是按照sender发送的timing显示,是pm需要考虑的一点,可以技术上实现了(接收方根据发送的时间显示更合理)。总之,需要一把尺子来衡量这个时机。[群聊消息,如何保证所有接收者按相同顺序接收]群聊消息需求,N群好友在一个群里聊天,如何保证所有群友收到的消息按相同顺序显示?答:(1)没有那么用sender的seq来保证时序,因为sender不是单点的,时间也不一致(2)可以用server的单点来序列化。此时群聊的发送流程为:(1)sender1发送msg1,sender2发送msg2(2)msg??1和msg2连接到集群,服务集群(3)服务层到底layer得到一个***seq来确定receiver的显示时机(4)service获取到的msg2的seq为20,msg1的seq为30(5)通过delivery服务向多个群成员发送消息.即使群成员在不同时间收到msg1和msg2,也可以按照seq统一显示。该方法可以实现,所有群成员的消息显示时间相同。缺点是这种生成全局增量序列号的服务很容易成为系统瓶颈。有没有更进一步的优化方法?思路:其实群消息不需要保证全局的消息顺序是有序的,只需要保证一个群内的消息是有序的即可.在这个方案中,服务层不再需要到统一的后端去获取全局seq,而是在服务连接池层面做小改动,保证来自一个group的消息落在同一个服务上,这个服务可以使用本地的seq来序列化同一组的所有消息,以确保所有组成员看到消息的时序相同。id序列化详见《利用id串行化解决缓存与数据库一致性问题》,此处不再展开。3.总结(1)在分布式环境中,由于各种原因,消息的有序性很难:时钟不一致,多个发送者,多个接收者,多线程,网络传输的不确定性等(2)“有序”,首先有必须是衡量“有序”的尺子,可以是客户端尺子,也可以是服务器端尺子(3)大多数业务可以接受大范围的趋势顺序和小范围的误差;absolutelyorderedbusiness,可以利用服务器的绝对定时能力(4)单点序列化,这是保证多机统一定时的常用方法。典型场景包括db主从一致性,gfs多文件一致性(5)一对一聊天,只需要保证发送时序和接收时序一致即可。你可以使用客户端seq(6)群聊。你只需要保证所有接收者消息的时序是一致的。您需要使用服务器序列。有两种方式,一种是单点绝对计时,另一种id连载文章来源微信号:gh_10a6b96351a9,已授权转载