2016年10月29日,由又拍云举办的Open Talk No.26在“魔都”上海3W空间成功举办,此次活动主要邀请直播领域开发一线的技术大神们聊一聊直播平台的架构与优化,看他们化解项目选型、开发上线、迭代过程、性能优化中遇到的挑战与经验。恺英网络程序经理张皓聪在Open Talk NO.26上,做了《直播平台IM系统实战》的主题分享,介绍了直播平台“IM系统”的搭建过程。

image.png

今天我要跟大家分享的是现在非常流行的直播平台里的“IM系统”。“板栗直播”IM系统从立项初期,到发展致今,已经成为一个功能复杂,拥有一定稳定性的系统。

1.0版本——速度要快

我们做IM系统1.0版本时,有以下几个需求:

  • 一周出Demo
  • 差不多能用
  • 演示的时候不能突然崩溃
  • 万一崩溃了不能被发现
  • 性能无所谓
  • 总之,要快速做出来

由于只有一周的时间,第一版的架构搭建得非常简单:

服务端由 PHP 和 NodeJS 组成。PHP 负责用户账户,注册和登录。NodeJS 负责长连以及进出房间和推送,做成简单的交互。

第一个版本的前端服务器的结构非常简单,功能很少,仅仅负责联接客户端,负责所有客户端的联接。为了保证 H5 也可以使用,因此在长连上选择了WebSocket。

image.png
为了确保客户端演示的时候,万一崩溃了也不能被察觉,我们使用 pm2 守护了 node进程。同时让客户端同学做了个自动断线重连。

然后登录流程是这样的:先从PHP获取一个token,接着带着这个token和node 建立连接,node 会在内网向php验证 token 是否有效,最后建立连接。

至于广播方面,使用了我称作组的形式进行广播,所有的房间和私信都是通过组的形式进行广播。

image.png
首先,用户在房间里发言的时候,实际上是往特定组发送广播。组的名字是用特定字符开头来归类的。

举例来说,需要向10000房间发送聊天信息时,实际上是向 r:10000 这个组发送信息。此时服务端会遍历该组里所有连接,并发送消息。

当需要进行全服广播的时候,我们使用一个写死的、特殊的 0 号房间,转换成 gid 也就是 r:0 发送消息。这将遍历所有连接,向他们发送消息。

接下来是推送私聊,这里我为每个用户都设置了一个私有的 gid,这样发送私信的时候也相当于发送了广播,服务端可以用相同逻辑处理。打个比方向 uid 20000 用户发送私信,相当于向 u:20000 广播。以后扩展成多进程多主机模式时,也不必知道这个用户目前在哪台服务器上,推送私信就会很方便。

在这个设计中,一位用户是允许加入多个组的。就像群聊一样,一开始就使用这样的设计,以后可以很容易的进行扩展。

由于采用了单点连接的形式,并不需要每次进房间都建立新连接,客户端只要首次进行连接,之后发送请求就可以进出房间,也节省了客户端的开发工作。

2.0版本——更高的可用性

2.0版本需求:

  • 内测版本
  • 支持扩容
  • 更高的可用性
  • 礼物信息比聊天优先级高
  • 特定消息要发送回执

当1.0版本完成之后,接下来就是要开发 2.0 版本。

这个版本里增加了一些必要的需求。

首先,这是一个内测版本。在内测版本中会邀请用户使用,并进行测试。

期间可能由于用户数增多,也可能因为我们代码本身的原因或是 bug 造成服务器压力增加,此时会有扩容方面的需求。

第二个是要增加广播优先级的功能,这个很简单,礼物肯定是比普通信息优先级更高的。

最后是特定消息的回执发送,这个我们目前也还没有实现,但是需求一直是在的。

image.png
上图中可以看到模块数增加了,但是总的来说客户端需求并没有变化,基本流程的连接流程仍然是先问 PHP 要 token,再去连接 WS。由于我们允许把 WS 部署在多个主机上,在这中间增加了一步就是访问 LB 获得 WS 地址。LB 会轮询的告知客户端本次应使用哪个 WS,并把 IP 返回给客户端。确保所有 WS 的压力是平均的。

另外还有一处增加的功能是,由于 WS 已经分离到多个进程了,那么需要一个地方可以用来处理广播。这边我们选择了 redis 的进行广播。

同时另外还部署了一台 redis 是专门用来保存在线列表等数据。

image.png

上图是一个带有优先级的推送消息。

大家可注意到,我们为每个组的gid增加了一个后缀,这个后缀是用来区分优先级的。

比如说用户进入房间 10000,那么我会把他放到 2 个组,分别是 r:10000._ 和 r:10000.n。

同时 node 也会向 redis 订阅两个同名频道,此时服务端就可以从 2 个频道收到推送消息,分别是 _(普通) 和 n (优先)。

后端会有聊天信息要推送,会根据优先级,是使用普通频道还是优先频道进行广播。

node 这边会开一个主循环,比如设置 12fps ,每一帧会优先转发优先组(频道)中的消息,然后才是普通消息,根据当时压力,将会选择抛弃普通消息。

3.0版本——业务量增大

3.0版本需求:

  • 业务逻辑越来越多,需要拆分
  • 需要支持热更
  • 更好的广播性能
  • 优化与其他服务的通信
  • 日志系统
  • 部署脚本

到了3.0版本时,需求越来越多,需要拆分。

线上有时候会有bug和做活动,会有热更的需求。

redis 广播有可能出现瓶颈,在这之前需要需要更好的广播性能。

同时还需要优化与其他服务器之间的通信。

最后还需要需要有一个简单的日志搜集器。

image.png
这是3.0版本的架构。大家可以看到下面有大量的服务了。其实这些服务原来是在WebSocket里面的,现在把它拆成不同的进程。

客户端连接这块没有变化,客户端仍然需要问 PHP 要 token,问 LB 要地址,最后再连接。

这个版本的 WS 功能上更加单一,只负责消息转发。

在这个版本里,我们做了一套 RPC 框架,用来方便调用内网各服务的接口。

增加了日志搜集器和API的服务。以及IM系统(私聊)。用户之间可以聊天和查看聊天记录等。

总的来说,功能上没有特别大的变化,尤其是接口方面是向前兼容的。

image.png

下面我介绍一下RPC服务框架。

我们的 RPC 框架是基于 ZeroMQ 的。利用他的里的Dealer和Router 进行消息的收发。

ZeroMQ 自带自动重连和负载均衡功能,这方面也不用太操心,节省了开发时间。

数据交换格式是JSON,明文传输,调试也会很方便。

image.png
还有就是我们不再使用 redis 做广播,而改用 ZeroMQ 的 pub 和 sub 做广播服务。这样广播性能就有了很大提升。

4.0版本——更高要求

IM系统4.0版本的需求

  • 广播和业务分离
  • 优化PRC协议
  • 针对个别地区进行网络优化

到了这个阶段,我们遇到了一些个别地区网络较慢的问题。

image.png

我们把 WS 和 LB 放到了代理服务器后面。LB 可以根据不同运营商用户,返回代理了的 WS 地址。

image.png
同时还优化了RPC 服务,我们为 RPC 服务增加了路由器,所有的 Worker 都隐藏在他后面,这样客户端调用的时候并不需要知道具体 Worker 的地址。Worker 的更新重启也不会影响其他客户端。广播服务也得以和广播分离,同时实现集群化。

image.png