“ID串行化”是如何保证消息顺序性的?

1,660 阅读3分钟

在《消息顺序性为何这么难?)》中,介绍了一种为了保证 “所有群友展示的群消息时序都是一致的” 所使用的 “ID 串行化” 的方法:让同一个群 gid 的所有消息落在同一台服务器上处理。

ID 串行化是如何实现的呢?

互联网高可用常见分层架构

客户端,反向代理层,接入层,服务层,存储层,这是互联网常见的高可用分层架构。

画外音:这个图用过好多次。

这里的 “服务层” 至关重要,ID 串行化保证的是,同一个群 gid 的消息落在同一个服务上。

画外音:服务集群有很多节点,如果能落在同一个服务节点上,就可以利用这个服务节点做消息串行化。

服务层上下游细节

服务一般由 RPC 框架实现,上游调用方是多线程程序,通过 RPC-client 访问服务,而 RPC-client 内部又通过连接池 connection-pool 来访问的。

画外音:为了保证高可用,连接池会对集群中的每个服务都建立连接。


如上图:

(1)上游是业务应用;

(2)下游是服务集群;

(3)业务应用,它又分为了这么几个部分:

 - 上层是任务队列 (粉色)

 - 中间是工作线程 (蓝色),每个工作线程完成实际的业务任务,典型的工作任务是通过服务连接池进行 RPC 调用;

 - 下层是服务连接池 (绿色),所有的 RPC 调用都是通过服务连接池往下游服务发请求执行;

画外音:橙色是连接池中的一条连接。

工作线程的典型工作流是这样的:

void work_thread_routine(){

// 获取任务

Task t = TaskQueue.pop();

// 任务逻辑处理,组成一个网络包 packet

Packet p = MakePacket(t);__

// 从 Service 连接池获取一个 Service 连接

ServiceConnection c = CPool.GetConnection();

// 通过 Service 连接发送报文执行 RPC 请求

c.Send(p);

// 将 Service 连接放回 Service 连接池

CPool.PutConnection(c);

}

如何保证同一个群 gid 的消息落在同一个服务上呢?

对连接池进行少量改动,获取连接时:

CPool.GetConnection()

画外音:返回任何一个可用服务连接。

升级为

CPool.GetConnection(long id)

画外音:返回 id 取模相关联的服务连接。

只要传入群 gid,就能够保证同一个群的请求获取到同一个连接,从而使请求落到同一个服务上。

需要注意的是,连接池不关心传入的 long id 是什么业务含义:

(1)传入群 gid,同 gid 的请求落在同一个服务上;

(2)传入用户 uid,同 uid 的请求落在同一个服务上;

(3)传入任何业务 xid,同业务 xid 的请求落在同一个服务上;

ID 串行化访问服务,同一个 id 访问同一个服务,当服务挂掉时,会不会受影响服务可用性?

不会,当有下游服务挂掉的时候,连接池能够检测到连接的可用性,取模时要把不可用的服务连接排除掉。

取模访问服务,是否会影响各连接上请求的负载均衡?

不会,只要数据访问 id 是均衡的,从全局来看,由 id 取模获取各连接的概率也是均等的,即负载是均衡的。

获取连接,ID 取模,希望大家有收获。

架构师之路 - 分享可落地的技术文章