Jack Jiang

我的最新工程MobileIMSDK:http://git.oschina.net/jackjiang/MobileIMSDK
posts - 539, comments - 13, trackbacks - 0, articles - 1

2021年11月10日

本文由云音乐技术团队燕十三分享,有修订和改动。

1、引言

本文分享的是网易云音乐技术团队基于实时消息总线技术,解决了直播活动系统的模块灵活组合、消息治理与异步履约等问题,希望能给你带来启发。

cover-opti

2、系列文章

本文是系列文章中的第 10 篇:

  1. 直播系统聊天技术(一):百万在线的美拍直播弹幕系统的实时推送技术实践之路
  2. 直播系统聊天技术(二):阿里电商IM消息平台,在群聊、直播场景下的技术实践
  3. 直播系统聊天技术(三):微信直播聊天室单房间1500万在线的消息架构演进之路
  4. 直播系统聊天技术(四):百度直播的海量用户实时消息系统架构演进实践
  5. 直播系统聊天技术(五):微信小游戏直播在Android端的跨进程渲染推流实践
  6. 直播系统聊天技术(六):百万人在线的直播间实时聊天消息分发技术实践
  7. 直播系统聊天技术(七):直播间海量聊天消息的架构设计难点实践
  8. 直播系统聊天技术(八):vivo直播系统中IM消息模块的架构实践
  9. 直播系统聊天技术(九):千万级实时直播弹幕的技术实践
  10. 直播系统聊天技术(十):基于实时消息总线的活动系统架构设计》(* 本文

3、技术背景

所谓组装,就离不开老生常谈的复用,我们可以对大部分认为比较共性的场景做好系统级别的封装,封装成一个个复用度较高的服务,然后通过接口和扩展点的方式进行一部分的能力开放,但是有一种场景是解决不了的,就是当一个功能级别的代码执行结束后,希望触发到另外一个功能,同时希望这个功能是可以通过配置去解决的,并且不需要通过开发的手段去解决这类问题。

例如,用户送了一个礼物给一个主播,直播间的贡献榜上对该用户做了积分 +1,这是一个很典型的「履约」类场景,与我们在直播间内下单购买,履约给仓储系统道理是一样的,但是这都建立在这个流程模式是固化的。

而活动往往不是这样的,活动相比这些固化的流程是更为灵活的。

笔者的团队曾经开发过这样一个场景:

用户通过送礼,去帮助主播完成一个直播间虚拟能量条的冲击,而每充满这个能量条都希望做一件事情,这个事情就彰显出业务侧的脑洞大开,第一次的活动是希望给某个榜单加上分数,第二次的活动希望是给主播掉落一个虚拟的宝箱,第三次的活动是给用户发送一些抽奖券,依次类推,只要是做过的功能,他都希望这个事情可以去用,时间久了,就会面临频繁去修改这个模块的代码,当它「结束」后,if 条件式的去触发各个代码,或者策略模式的去魔改代码,对于长期建设来看这种方式并不友好,就我看来这只是这个模块结束后要做这么多事情,那么下一个模块如果从这么多事情挑两件岂不是还要再写一堆代码?

因此我们想到了一个相对比较原始的解决方案:总线式服务。

4、信鸽服务的组合能力

与其说组合,更倾向于用「履约」这个词形容会比较恰当一点,因为对于活动来说,信鸽只做「履约」这一侧的功能比较重要,他要解决的是异步场景的分发类问题,而不是对一些系统做一些 adapter 组合集成的能力。

这里以一个活动场景来做示例:

1

音乐人活动的一个简单的页面主要包含了几个模块功能:

  • 1)用户通过送礼、观看等行为完成相关任务;
  • 2)为喜爱的音乐人加进度条积分;
  • 3)当进度条积分完成某个档位后,触发宝箱的掉落、飘屏送礼等。每个档位的类型是不同的;
  • 4)幸运锦鲤的模块。

对于这样一个活动,从开发的角度来看,其实它是由几个模块进行组装的,除了 4 是需要独立开发的,1、2、3 都可以通过现有的系统进行组装,这里 1 抽象为任务系统,2 抽象为进度条系统,3 也可以抽象为宝箱系统、送礼系统等。

下图是对场景抽象化模块的概念:

2

那么难点来了,通过什么手段可以来将这些模块进行组装呢?

通过图中可以看到,当一个模块完成了他的生命周期,可以发送一份数据,路由系统收到了这个数据之后,会帮我们做一层路由的转发,决定数据会路由到下一个系统上去。下一个系统可能是「奖励系统」也可以是「进度条系统」。

同时可以对这个行为进一步细化的抽象,我们希望这个路由系统充当一个「总线」的角色,「信件」代表了每一个系统希望收到的数据,同时对每一个系统都抽象化成一个目的地,如果我们配置了一份路由关系(抽象为信鸽配置),那么这只「信鸽」可以作为将数据信件为我们带到我们想去的任何目的地,那么对于系统的好处是,系统只需要提供自己接入这个路由系统的能力即可,下一次随便是什么活动,可以直接做一些组合关系。

有了信鸽路由这种思路,针对音乐人活动这种场景,我们可以将一个流程的完整链路梳理出来:

3

图中可以看到,我们对任务、进度条等基础模块,做了一些扩展点(EXT-POINT)来满足业务流程的编排,这在中台里是比较常规的解决问题的手段之一,而在系统模块组组合的场景中,多了一层信鸽服务的概念,在【2】、【3】、【4】的处理流程中,都可以由信鸽来决定数据流向哪个系统,事实证明这个方案是可行也并且有效的。

5、整体架构设计

那么如何设计这样一个组合能力的架构呢?

研发的重心可以分为四个层面:

  • 1)履约能力:当代码结束后做到末端的触发。
  • 2)SDK 接入能力:Interface 级别的包装,天然的 Autoconfigure 能力。
  • 3)全局的标识字典:一份数据如何让所有接入的系统都可以达成共识。
  • 4)系统的自动注册能力:接入 SDK 后,自动上报到信鸽服务的统一管理,自动激活,不需要人工的介入开启。

基于这四个层面,架构设计如下图:

4

从图中可以看到:左侧虚线框代表了一个系统的触发,右边虚线框代表了最终另外一个系统的触达。信鸽系统充当了代理的服务,触发的系统只需要接入信鸽 SDK,当执行完自己的职责之后,对数据做一层组装发送给信鸽系统,信鸽系统会根据发送的信鸽 id,寻找到信鸽的相关配置,信鸽 id 决定了数据会流转到那个接入的子系统上,这个流转可以是同步的也可以是异步的(当然大部分都是异步场景),异步主要依赖「RocketMQ」的投递能力,当转发失败后,会对投递的数据结果集进行备份存储,用于定时步长的重试操作。在以往的实践过程中,大部分场景都是异步的链路,不需要获得下一个子系统提供的返回的结果集,并且「RocketMQ」本身投递消息的出错率也是小概率事件(毕竟4台 broker,出错3台的可能性是极低的),相比 RPC 这类通信级别的接口有绝对的优势。

6、提供的SDK能力

6.1 路由触发

在上图中,我们可以看到对于子系统分成了大概 2 类场景,一种是活动业务域,另外一种是非活动业务域,这本身与业务的场景有关,我们希望所有的子系统都可以按照标准去接入 SDK,但是并不能保证每个子系统提供能力都依靠 SDK,对于一些非活动业务域使用了定制化开发的模式来进行桥接工作,这种桥接工作更像是传统的 adapter 和 ESB 总线思路。

而活动业务域的子系统都可以采取接入 SDK 的模式,这里主要会介绍一下异步的设计思路,当一个子系统接入 SDK 后,会在 Spring 容器创建 bean 的时候,默认创建一个 PushConsumer 的 bean ,添加监听信鸽 「fly」 的 「Listener」 ,这样能做到自动消费到路由的消息,对消息进行解析,假设这个系统可以承担的模块能力分别是 S1 / S2 ..., SN 等功能,那么整体实现图如下图所示。