我们在 5 月 正式发布了实时消息(RTM)SDK。在 5 月 27 日举行的 Qcon 广州站上,声网 Agora 资深技术架构师吉奇 以《高并发场景下分布式实时信令系统的架构实践》作为话题,分享了 RTM SDK 背后的架构设计经验。
以下为演讲实录:
大家好!我叫吉奇,来自声网。现在负责声网 RTM 实时信令云服务后台及 SDK 技术架构设计。这次演讲会按照 RTM 的系统架构上的分布或子系统的层级关系来展开。
首先,RTM 是一个通用的消息系统,主要是为了解决实时场景下信令的低延迟和高并发问题。我们声网是业务遍布全球的平台,因此在所有的后台设计中,把分区作为一个比较重要的事情来看。目前 RTM 有几个大区域,有美洲、亚洲、东南亚、中国大陆,还有欧洲、非洲几个大区。区与区之间相对独立,每个区会有跨区传输网络。每个区之间由三个子系统组成,首先是消息核心(Message Core),还有事件中心(Event Center),最后是应用服务(Application Services)。我会分别讲一下各个子系统内部的架构实现,即消息核心、事件中心、应用服务和跨区网络。
消息核心(Message Core)
首先是消息核心,它是目前成熟度最高,也是最复杂的子系统。在该系统里面有几个主要的组件,首先有接入服务器、点对点消息转发服务、频道消息的转发服务、简单的状态管理(包括用户状态和频道状态),还有频道分布状态服务器。
在消息核心,所有的服务都是分布式,没有一个单点或者中心式的情况,因此可以保证高可用,并且性能方面可以支持高吞吐量和低延迟。Messaging Core 有一个特点,具有非常大的扩展性,但是它的问题是只支持基本核心的功能,剩下的都要放在其它子系统中。
分布式的信息核心有几个优势特性:
完全排除单点故障
接近 100%可用
端到端延迟 < 100ms
任何节点都可水平扩展
支持数百万人同频道(无理论上限)
大型活动中支持数百万 QPS 消息下发
核心功能超高响应
所谓核心功能,目前消息核心支持的功能是点对点消息、频道消息,可以加入频道、退出频道。用户也可以同时加入多个频道,使用一些频道管理的功能,比如获取用户属性、频道状态,能查询频道中有多少人,其他用户是否在线等基本功能。
在此,以点对点消息为例,和大家分享一下扩展性是怎么样做的。首先 SDK 登录系统的时候,会通过 DNS 来访问我们的 AP 服务,AP 知道附近的边缘节点 R 的地址,会根据当前的客户端的地理分布,包括边缘节点的负载情况来给 SDK 回一组地址。SDK 在拿到地址之后,可以登录连接边缘节点,然后发消息。这些消息到达边缘节点后会投递给本区的点对点消息转发节点 F。F 知道本区内所有用户登录在哪个边缘节点,这是由本区所有边缘节点 R 上报给转发节点 F 的。图中的 U 是用户在线状态服务器,那么一个用户给另外的用户发消息,有三种情况,第一种情况,对端在线并且在同一个区里面,F 可以直接投递;第二种情况对端在线但在别的区里面;第三种情况对端不在线。在后两种情况中,消息转发服务器 F 不知道该用户的信息,也不知道在哪个节点上。这时候就可以通过 U 来获取这些用户状态,因为 U 知道全网跨区情况下的用户生命周期,也知道这个用户是否在线,F 去问 U 是否在线,如果在线在哪个区里面,可以通过跨区投入到别的用户。
这里的可扩展体现在哪里呢?首先,所有的节点都是可以水平扩展的,随着业务量增长,可以增加部署。边缘节点是可以随意增加的,而核心节点 F 和 U 不能做任意的水平扩展,因为他们保留了一定的状态,我们用了一个一致性哈希的分片方法,所以把所有用户的账号哈希之后产生一个 32 位的随机数,想象把这些数放到一个环上,每个服务器各自产生一组随机数,在环上均匀分布。这样所有的消息会被映射到比自己的哈希值小的那一个服务器上面。所有的节点的 partition 都是可以动态地增加和减少的。假如说有一个核心服务器故障或者下架了,那么它可以重新分布到别的服务器上,实际上我们地消息核心中除了边缘节点 R 之外还有十几种核心节点,它们都是做了分片的。这就是所谓的可扩展性。
高可用怎么样做呢?首先如上图所示介绍一下频道消息简单的流程。假定边缘服务器收到用户的频道消息,会把该消息投递给 F,F 是点对点消息的转发服务器,它看到是频道消息的话会自动抛给 D,D 专门负责频道消息分发,D 采用是级联的模式,每一个区都有一组总的频道消息分发服务器,在每个数据中心会有一组机房级别的代理。区域级根服务器发消息到机房级别的代理服务器,机房级服务器往该机房所有的边缘节点 R 转发,这样可以保证在超大频道下面的性能。现在有一个问题,之前我说了 U 是保存用户的生命周期的,而频道的生命周期与用户不一样,频道不是一个特定的个体。比如说用户要么在中国或美国,不可能同时在中国和美国,但频道可以。尤其当频道比较大的时候,分布会非常广,很有可能是跨区频道,甚至在中国、美国、欧洲都有用户处于同一频道。那么你该怎样获取某频道的用户分布呢?我们用频道分布服务器 O 来处理。所有的 R 都会在本地频道创建、销毁的时候,把该事件通知给 O。O 把频道分布的信息告诉频道消息转发服务 D,D 会从中获得两个信息,第一个信息是对于某频道来说,在本区内该频道的用户分布在哪几个边缘服务器上,第二个信息是可以知道该频道是否跨区,如果跨区的话,又是哪几个区域。D 通过第一个信息可以判断在本区投递给哪些用户,通过第二信息可以知道需要通过跨区传输网络投递给哪些别的区域的 D,让它们在别的区域来负责下发。
在这里高可用主要体现在 O 是对等部署的。我们每一条消息或者每一次状态改变或者每一个查询请求都会有一个全局唯一的 ID,这个 ID 由两部分组成,第一部分保证其唯一性,第二部分保证在某一个 session 之内前后的请求有一个单调递增的大小关系。这样的话,从多台对等部署的 O 同步给 D 的频道分布信息,就相当于要保证一个单一来源但多路径的信息同步的一致性问题,我们是可以通过这个 ID 来做到版本控制和除重从而保证一致的。当然对等部署只是其中一个手段,还有很多别的模式用到不同的服务上面,比如事件中心的高可用就是由双数据中心主备切换来保证的。但消息核心中的服务一般都是采用的比较激进的对等部署的方式,这样的好处是任何一个服务器挂了都不会有切换的事件,保证服务 100% 可用。
事件中心(Event Center)
Messaging Core 下面是 Event Center。就像我在开头说到的,Messaging Core 有一个限制,它是靠多重冗余和相对激进的策略来保证低延迟和高可靠的系统,因此很多扩展的功能没有办法做,所以会通过 Event Center 来支持这些扩展功能。
举个例子,比如用户属性是在消息核心中完成的,而频道属性在消息核心中就做不了。因为频道属性和用户属性不一样的地方在于,对于某一个用户,他的用户属性只有他自己能够编辑,他是该属性的主人,由该用户的客户端来保证属性的一致性。所以就算在服务端有多重冗余的情况下,该属性也可以达到最终一致。但频道属性不同。频道里可能同时有多个人在同时编辑频道属性,也可能同时有多个人在读该属性,怎样达到一致性?这里就需要对频道消息的编辑操作有一个统一的来源。但这个来源又不能是单点,否则很容易出故障也很容易成为瓶颈。
因此我们决定将所有的事件,包括状态改变、消息的投递都统一写到 Event Center 里面。Event Center 分为两个部分,Event Storage 和 Event Queue。我们的实现原则是传输与状态隔离,数据与索引隔离。传输是 Messaging Core 和跨区传输网络来负责,状态是存在 Event Center,而 Application Services 是消费的状态,这样可以做到传输与状态的隔离。
那什么叫数据与索引隔离呢?对于所有的事件来说我们都会把它的 meta data,或者叫事件的 header 放到 Event Queue 里,这样消费者去消费事件队列的话就会很快,而事件的内容本身则放在 Event Storage。我之前说过对于 RTM 的所有消息、事件、查询都有一个 ID,这样的话就能建立一个事件 Header - 事件 ID - 事件 Body 之间的映射。消费者可以通过 Event Queue 建立对事件 Header 的索引,通过这个索引来做各种业务逻辑,然后再通过 ID 来找到对应的事件 Body。比如对于历史消息的条件查询就是这么做的。在这种模式下我们可以做到比如查询当前在线的所有用户里属性属性满足 “gender:female”,“age:24” 的用户。
应用服务(Application Services)
Application Services 是一个微服务的架构,在 Event Center 的支持下可以支持很多的业务逻辑。还包括实时的监控、计费、问题调查、分析等。它的好处是易于开发,我们通过 Event Center 把传输和事件解耦了,让我们可以更容易地实现更多的功能。目前已经落地的功能包括频道属性和历史消息,还有很多其他的功能在开发中。
下面讲一下跨区传输网络,它负责所有区域到区域之间的通信。我们有去中心化地实时路由计算策略,会根据延迟和负载来动态挑选跨区路由。实际上你发现在很多场景下面,跨境传输是最难的问题,尤其是在教育场景下。例如,老师在东南亚某个地方,学生在国内,他们之间建立连接、收发一些消息的过程中,稳定性和到达率会遇到很多问题。声网全球有 200 多个数据中心,我们通过智能路由来进行实时传输,比如中国到菲律宾,当前网络不好的时候,我们可能会通过新加坡进行中转,如果新加坡到菲律宾好但是到国内不好,我们会也许会通过国内某个机房先中转到新加坡。RTM SDK 今年上线后,从运营数据来看高峰期的跨洋平均 RTT 是 250ms,该数据已经比较接近实际网络传输延迟。
如上图所示是简化版的跨区传输网络,这个算法有点类似于 BGP 算法。自治域与自治域之间全连接,每个节点都有自己的路由表,每个节点会定期广播自己的路由表到别的节点。比如 A 知道到自己到 B、C、D 的延迟是多少,一轮广播之后 B、C、D 就会知道自己如果通过 A,到其他节点的延迟会有多少。各节点会选择延时较短的路线传输。当然,实际策略肯定不会这么简单,因为如果所有节点都采用相同策略,流量可能会汇集到某一些节点上去,在流量高峰期时会对这些节点造成冲击。因此我们有一套很复杂的策略来进行负载均衡。
本文转载自公众号声网 Agora(ID:shengwang-agora)。
原文链接:
https://mp.weixin.qq.com/s/ZgJwvT5sHl0HKDjE4kmS9g
评论