剑客
关注科技互联网

他趣已有平台引入直播的实战之路|架构师实践日

编者按: 直播作为一种更实时的交互方式,正在被越来越多地应用到各大行业之中。那么,已有平台在引入直播技术的过程中,有哪些经验值得借鉴又该如何合理避坑呢?9 月 3 日,在七牛云主办的架构师实践日上,他趣首席架构师 张嵩,带来了他趣在已有平台上引入直播的实战经验。以下是对他现场内容的整理。

张嵩 他趣首席架构师

演讲嘉宾:厦门大学计算机硕士, 14 年加入他趣,负责业务服务化改造,现主要负责微服务体系健全及持续交付体系建设。近两个月花费较大精力于直播相关 IM 后端、运维结构等,个人爱好研究在不同情况下对后端代价与收益的平衡与抉择。

做直播前的背景 

他趣开始做直播时遇到了 5 个问题:

1.开发周期短

他趣从今年 4 月份开始做直播,开发过程很短。当时市场上已经有很多的视频直播平台了,所以团队有人认为,做直播会很容易。

2.没有任何直播流及 IM 的技术积累

他趣之前没有做过推流或录播,IM 是通过第三方来提供私信和客服的,并没有技术积累。

3.现有系统全服务化结构

他趣不是一个单一性的系统,它总共有三十多个子系统来支撑商城、社区等各种平台。

4.物理机房无机柜可扩容

5.设计目标为至少能容纳当前日活用户同时观看

从现有产品加入直播,不能重新调整设计,所以必须把容纳量设计为推送高峰期的用户数。

面临的问题与解决方法

Q1 : 没有直播流和 IM的技术积累

A1: 使用七牛云直播流  

针对没有直播流和 IM 技术积累的问题,可以通过采用七牛的直播流解决。早期参考过很多家的方案,但最终决定使用七牛,主要是因为把直播流交给第三方做,可以节约大量时间来做业务开发。当时评估发现,虽然自己搭建和优化可以实现直播,但客户端和服务端之间的推流优化很耗费时间,使用第三方则可以解决这个问题。  

IM 自研较容易处理在线排行榜等需要长连接处理的业务。几年前使用 C++ 写过长连接服务,但耗时较长并且坑比较多,还需要招聘团队来完成这个工作,这样可能会赶不上计划。IM 基于 GO 语言本身的特性,则不需要临时招聘人员,可以减少人力成本,可以边学边写。同时他趣本身已在服务器上应用了一些 GO 的相关服务,坑已经踩过一次。

当时在 IM 的选择上,其实可以选择采用第三方,但是这会产生一个问题,当第三方连接到第三方时,如果要推送排行榜或业务数据就会比较麻烦,因为每隔几秒就要通过大量的 http 请求推送给第三方,第三方的压力会很大。但如果由团队自己完成,内部服务调用就可以变得很快。这里有一个坑,由于 GO 语言相对高并发些,如果直接调用其他语言,很容易把其他语言拖垮,所以我们主要都是通过异步调用的方式来使用其他语言的服务。

当然,直接使用第三方服务也会存在一些问题:服务的稳定性全压在第三方身上,遇到的问题自己没有办法解决,只能等待七牛解决。

而 IM 自研方面,由于时间问题,当时的 IM 最后被压缩到三天内完成;虽然要求一上线就可以支撑所有的线上用户,但仍然没有时间做优化。

Q2 : 物理机房已没有可用的机柜以及机器

A2 : 使用云服务

事实上机房有备用的机器,但直播需要用到的机器数量远大于此,并且保留的机器需要用来应对类似双十一、双十二(节日)的高峰期,所以现有的机器是不能动的。针对这个问题,考虑使用云服务来解决。

这样做有 3 个优势:

1. 已经有一些非核心的业务在云平台上,比如他趣的 HTTPDNS 服务、错误日志分析等,大都布置在云平台上,当然这些服务本身跟原 IDC 机房的交流比较少。不过优势在于各个机房之间已经打通了 IPsec 隧道;

2. 如果按照公司现有的流程来采购机器及扩展机柜,整个过程会很耗费时间,使用云服务则可以避免这个问题;

3. 碰到推送高峰期,扩容方便。

但也有 2 个劣势:

1.部分未提供的服务需要自己在云平台的虚拟主机上搭建,例如一些类似 public 的 redis 命令,由于集群的原因本身没有服务可以支持,因此需要自己在云平台上搭建;

2.涉及多机房时,会遇到服务跨机房的调用问题。早期他趣在厦门有一些服务器托管在 IDC 中,有时厦门调用北京机房的延迟会达到三秒以上,SLA 无法保证。

Q3 : 跨机房的调用使得服务 SLA 无法保证

A3 : 预同步数据、强制同步数据、异步化调用

在他趣内部,每个较大的服务几乎都有自己的库,实现数据库的隔离,例如商城系统的库与交易系统的库不在同一个数据库上。这时会涉及到分布式事务相关的问题,之前团队内部讨论过,考虑到没有能力去实现它,所以会尽量避免分布式事务。  

如果有分布式的事务需求时,可以通过三种方式来做最终一致性的保证:

第一种是可以通过事务型的消息队列来保证,队列会保证最终处理成功,这样保证消息的正常处理;

第二种是无事务的弱消息的队列加上业务的定时补偿;

第三种是异步的 double-check 机制。

他趣主要采用第二种方式,即弱消息队列+ 定时补偿。队列只保证三次重试的机会,如果三次都没有成功,会直接报警并丢消息。

他趣现在主要通过预同步数据、强制同步数据、异步化调用三种方式来保证用户不受跨机房 SLA 影响。

优势:

1.成本低,无需修改机房调用结构;

2.SLA 基本可控,大部分情况下用户一打开就可以取到相关的数据;

3.当前已有服务可实现复用,例如直播的推送服务,可以调用已有的 IDC 机房的系统来实现。

劣势:

1.没有专线简单;

2.需要思考哪些业务需要特殊处理,编写较为复杂。同时还需要考虑各种各样的情况,例如到底是哪一端丢消息、是否需要保证幂等性等。

IM 结构

他趣已有平台引入直播的实战之路|架构师实践日

图 1

如果要实现 IM 分布式及无限制地扩容,需要一个很重要的条件,即 comet 节点本身不能带有状态。  

如图 1 所示是目前 IM 系统的结构图,最上面是客户端,客户端的第一步是获取 IM 服务器的 IP,这相当于做了一层类似于 DNS 负载均衡的节点(用处其实很多),因为单个负载节点的包量有上限,而每个 IP 也都对应着一层 LB。

在这一步会做一些跨机房的同步数据。例如,两个机房共用一个用户系统,如果用户在远程机房登录,可以通过这个把当时的状态同步到直播机房。用户登陆后也会做一个异步队列,把数据同步到直播机房,同时直播机房会做一个补偿,确认是否有数据,如果没有就再继续做同步。

最终,当用户访问到直播机房的接口,但没有检测到用户登录时,才会到其他机房进行同步,否则会直接访问直播机房,这样可以保证用户几乎不用等待。

获取完 IP 之后,用户的连接会打到一台主 LB(负载均衡) 上,通过 ip-hash 的方式负载均衡到各个 comet 节点,comet 节点是部署 GO 语言的IM 业务的节点。comet 节点不保存任何状态,状态均存在 redis 集群中。按照这个结构,comet 节点可以无限制部署,但这样会有两个极限,一个是负载均衡节点的包量极限,达到 15w 包量时负载均衡节点会无法连接;另一个是 redis 的极限,但这个可以通过扩容来实现。

房间与服务器

他趣已有平台引入直播的实战之路|架构师实践日

图 2

如图 2 所示,消息被分成了各种类型,这里对消息进行了优先级的划分。第一种是高优先级的消息,包括礼物消息、聊天消息以及弹幕消息等等。第二种是普通优先级消息,包括禁言消息、前 10 排行、房间状态。第三种是更低优先级消息,它包括点赞消息、用户升级消息和房管消息。

这里划分的原因是:例如,房间状态在大部分情况下都是处于可播放的状态,只有一些露点的情况会被禁播,这时候房间状态才会改变。  

图 2 左下角是服务器的结构,要实现房间可在多服务器上扩容。因为每个服务器负载的人数是有限的,所以每个服务器上的房间都是类似于 MAP 的结构,这样可以无限容纳进入房间的人数。右下角是房间的切割,当用户和发出的消息量越来越多时,例如一个房间消耗了三十几兆赫带宽,用户一直在疯狂地刷屏,这时会做一个虚拟房间来切割用户的聊天消息,同样是在房间 A,其中三人可以互相看到,另外三人也可以互相看到,但会保证主播可以看到所有的消息。

IM 相关优化 

实践时对 IM 这部分做了一些优化。

1.弱网络下的优化。

例如包不分片、SACK、SOCKET-BUFFER、断线重连发现等。要尽量保证包不分片,如果房间消息、IM 消息等全部放在一起,包很容易超过1460 。  

2.控制消息大小,正常MSS-头-websocket 分帧消耗。不用考虑过分小的情况,只要在这个值内进行控制就好。

3.心跳时间间隔的设置

他趣曾在很多场景下试过很多种心跳,发现如果使用较长心跳,例如在 2G 的环境下使用,运营商会较快速地回收 NAT 。而如果使用较短的心跳,手机便会因为无法进入休眠状态而发热,所以使用心跳前要考虑场景是否试用。由于直播本身就有视频流,所以较短的心跳是比较合适的。

业务消息拓展及业务优化 

1.合并点赞消息

他趣已有平台引入直播的实战之路|架构师实践日

图 3

业务层也做了一些优化,例如点赞部分做了一个合并的操作(如图 3 所示),如果用户点赞不足一秒,客户端会执行一个等待;如果距未发送的点赞超过一秒,就会聚合成统一事件发送给服务端。服务器会检测,如果一秒内点赞超过一定次数,就会认为这是一个假的操作。

2.消息异步化处理,多条线程同时处理

早期考虑使用用户发送的消息直接通过长连接来发送,但后来发现,发消息会产生很多业务逻辑上的处理(如图 3 所示),例如用户发送一个消息,需要检测是否被房间禁言、发送频率是否超过一秒等等,所以这部分全都放到了 HTTP 服务 ,这样子安全校验相关服务可以由HTTP 现有安全鉴权提供,comet 服务器无需做鉴权。用户通过 HTTP 发完消息后进入队列,队列之后会有很多消费者进行消费,这时再推到 comet 服务,comet 再下发给用户。

3.单用户不卡住广播消息的发布,也不影响其他用户

他趣已有平台引入直播的实战之路|架构师实践日

图 4 

在他趣中,正常状态下一秒钟最多发一条消息,但礼物不会限制,如果点击速度很快,甚至可以 1 秒送出 30 个礼物。如图 4 所示,一个消费者发布一个推送消息,GO 语言订阅服务消息。而在 comet 中每个用户都会生成一个消息的缓冲,同时也给每个用户开了一条 goroutine 来做消息的下发。

4.每个用户的缓冲队列应该有上限

这是针对心跳延迟问题提出的,他趣的正常心跳间隔 25 秒,3 次以上就会断掉连接。如果有一个连接断了,最快也需要 75 秒后才知道。由于移动 APP 会经常性断开连接,所以缓冲队列要有上限,目前上限为 200 条消息,是平均情况下四分之一秒的房间消息量。

业务优化

他趣已有平台引入直播的实战之路|架构师实践日

图 5

在实践中,电商的峰值比直播低,如果高峰期有 20 个人疯狂地点礼物,一秒钟的交易量将会达到五千以上,这对于以前的电商平台来说是难以想象的。如图 5 所示,送礼的交易流程被简化了。核心流程仅由扣豆和生成订单组成,所有后续处理都是通过队列来实现。  

例如送完礼物后发 IM 消息、做房间总消费统计、个人总消费统计等等,每笔交易都会做很多额额外的处理。而脚本服务这块,可能有 10 个消息消费者,也可能有 5 个排行榜的消费者单独跑队列消息,最后再把消息发送给用户。而日志服务的部分会归集所有服务器上所有的业务、日志等来做分析。

他趣已有平台引入直播的实战之路|架构师实践日

图 6

而在消息通过脚本服务来跑的情况下,可能会出现幂等性和原子性的问题。如图 6 所示,如果有两个消息消费者同时有一个消息礼物,这时会给用户推两条升级,即使是用事务也不好保证。而且整个队列只能保证三次容错,就可能存在这样的情况,即消息失败后,过了一次又回滚,这条消息会被再次消费,从而用户会再次送礼。

他趣针对上述情况做了原子与幂等的操作,如图 6 右边所示,连续操作多次的情况下,很难判断是哪一端错误,这时整个操作会重复执行。但是如果重复操作就会变成两个单。所以这里用redis做一些限制,消费成功之后会做一条记录,保证这条记录永远不会被再次消费。同时通过系统里自带的原子操作,例如 redis 的 incr 操作等,来保证原子性。

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址