本篇文章选自作者在 GitChat 的分享,若有什么问题,可在公众号回复「小助手」添加小助手微信,邀请你进入技术交流群。1. 前言

随着 QQ 会员用户的日益增涨,每周都要上线大量各种玩法的 H5 活动来满足产品和运营的需求。大概在 2014 年,那时手游非常火爆,我们部门有一个游戏特权小组(大概 10 多个人),就是专门每天开发这种游戏相关的 H5 活动的。上线一个活动,从需求评审到设计重构,再到开发,到测试,最后上线。整个流程下来,大概一周左右,效率非常低下,已经无法满足运营快速上线的诉求,并且重复性工作也非常多,对开发同学不能得到好的技术提升。于是,我们整个开发团队开始思考着怎么去提升开发和运营的效率。因此我们需要设计一套能快速上线、扩展性好、可复用性高的运营系统。于是,QQ 会员活动运营系统(以下简称 AMS)应运而生。

这里说的“道”,包含两层意思:一是这套系统设计的过程,二是从这套系统总结出来的方法思路。

2. QQ 会员活动运营系统简介(AMS)2.1 AMS 简介

AMS 是一个包括重构、开发、测试、发布、运营、监控等全生命周期的活动运营平台。

AMS 最新统计数据大概如下:

  • 每年为部们运营的活动带来的收益为 100+ 亿;
  • 最新峰值PV为 10 亿 +,日均PV为 5 亿;
  • 每天 600+ 个活动在现网运营;
  • 4000 多个运营人员,10 多个业务部门在使用。

下图为 AMS 近 30 天的 PV 请求,10 月 28 日有活动推广,请求量达到峰值 10.61 亿。

整个公司内部使用 AMS 的业务部们主要有:QQ 会员、QQ 浏览器、QQ 动漫、QQ 阅读、游戏中心、互娱、QQ 钱包、财付通、鹅漫 U 品等。

如下图所示:

2.2 什么是活动?

1. 活动的定义

简单来说,就是由一系列逻辑规则组合而成的一个 H5 页面。比如下面的截图,都是一个个活动。

2. 这些活动的规则一般比较多(这里的规则你可以理解为参与的条件,我们简称为 Rule),比如下面是我们业务比较常见的条件 Rule:

  • 是否为会员
  • 是否为超级会员
  • XX 游戏的注册时间是否大于 XX 时间
  • XX 游戏的游戏角色等级是否大于 XX 级
  • 最近是否登陆过 XX 游戏
  • 是否有抽奖机会
  • ……

3. 只要用户满足了上面指定的一些条件后,就能获得指定的奖励(一般是虚拟奖励),并给用户发货(这里的奖励发货我们简称为 OP),比如下面是我们业务比较常见的发货:

  • 游戏道具
  • 游戏礼包
  • 会员成长值
  • 1 个月的会员
  • Q 币
  • ……

当然,更多时候,这些活动的条件 Rule 都是 N(N > 1)个组合在一起的。比如,要满足的条件 Rule 为:

  • 条件一,是会员;
  • 条件二,最近登陆过天天酷跑游戏。

要满足上面 2 个条件,用户才能获得一个游戏礼包,那么这里的发货 OP 为:游戏礼包。

想当初,我们要实现上面的逻辑,我们会写出一个大的 API 接口,然后在这个接口里实现上面 2 个条件的判断,如果满足条件,就进行发货操作。这种开发模式持续了一大段时间后,我们发现重复性的工作太多,很多代码冗余,代码也越来越难以维护。为了避免这种问题,于是我们把上面这些条件 Rule 和发货 OP 进行抽象,并封装成一个个 Rule 和 OP 组件。然后,由 AMS API 引擎来进行加载并执行。

那么我们又是如何设计这套 API 引擎的呢?

3. API 引擎的设计3.1 设计目标

经过自身业务场景的详细分析,我们的 API 引擎的设计目标为:

  • 每个接口请求都有一个唯一的活动号(actid);
  • 接口输入输出结构要统一,这样在前端才能做统一的解析处理;
  • 可灵活装载任意个规则组件(Rule);
  • 可灵活装载一个发货组件(OP)。
3.2 工作原理

根据上面设定的目标,API 引擎的运行流程图大概如下(注:这里进行了简化,实际情况要复杂得多):

每一个请求接口用参数活动号 actid 来标识,先用 actid 查询出相应的配置信息(包括基本信息、Rule 信息、OP 信息),然后读取其中的 Rule 组件信息,并一一执行相应的 Rule 组件。如果其中一个 Rule 不满足条件,则直接返回错误的 JSON;如果全部 Rule 都满足了,则加载发货组件 OP 并执行,最后返回成功的 JSON。下面我再详细说明。

1. 每个接口请求都有一个唯一的活动号

这里的活动号参数,标识了一个唯一的业务逻辑,你也可以理解为接口名。API 引擎会根据当前的 actid 从 CMEM 存储里获取当前 actid 的配置信息(包括所有的 Rule、OP 组件信息)。

假如有个活动的业务场景为:只有会员,且会员等级大于 4 的用户点击参与后可以获得一个虚拟货币。那么这个 actid 对应存储的信息可能如下所示:

  1. {
  2. "actid":302620,
  3. "name":"领取礼包",
  4. "status":0,
  5. "start_time":1502726400,
  6. "end_time":1606780800,
  7. "rulecfg":{
  8. "rulecfg":[
  9. [
  10. {
  11. "rule":{
  12. "name":"club"
  13. },
  14. "type":"==",
  15. "value":"1"
  16. },
  17. {
  18. "rule":{
  19. "name":"viplvl"
  20. },
  21. "type":">",
  22. "value":"4"
  23. }
  24. ]
  25. ],
  26. "rule":[
  27. "club==1&viplvl>4"
  28. ]
  29. },
  30. "opcfg":{
  31. "op":"add_money",
  32. "plist":{
  33. "money_id":"302620",
  34. "num":"1"
  35. }
  36. }
  37. }
  • rulecfg 结构: 表示存储了 Rule 名为 club 和 viplvl 两个组件,type 是运算符,value 是比较的值 。
  • Rule 结构: 表示最终生成的运算规则。
  • opcf 结构: 表示执行的 OP 信息。

2. 接口输入输出结构要统一

还是上面的例子。其对外 API 请求的接口地址为:

http://iyouxi.vip.qq.com/ams3.0.php?c=page&gtk=e4b0b73337ae43e4c62b40528c258b7b&actid=302620

执行成功后的返回 JSON 结果如下:

  1. {
  2. "ret": 0,
  3. "data": {
  4. "act": {
  5. "start_time": 1502726400,
  6. "end_time": 1606780800,
  7. "op": "add_money"
  8. },
  9. "rule": {
  10. "club": "1",
  11. "viplvl": "4"
  12. },
  13. "op": {
  14. "num": 1
  15. },
  16. "actname": "领取礼包"
  17. },
  18. "time": "1542180520",
  19. "actid": 302620,
  20. "msg": "success"
  21. }
  • ret 返回码: ret=0 表示用户满足条件,参与成功;反之表示用户不满足条件,参与失败。
  • act 结构体:表示该活动的基本信息,如开始时间,结束时间等。
  • Rule 结构:存储了的 Rule 组件信息。
  • OP 结构体:存储了发货 OP 信息。 统一了这些主要的结构信息,前端的 JS 才能做统一的解析。

3. 灵活装载任意个规则组件 Rule

我们又是如何设计的呢?

首先,我们先了解下规则组件 Rule 是怎么设计的。这里用一个是否为会员的规则 Rule 为例子,这个 Rule 组件的代码大概如下所示:

  1. /**
  2. * 是否会员(0:非会员;1:会员;2:会员但非有效)
  3. *
  4. */
  5. class Rule_club extends CellBaseRule implements IfCellRule
  6. {
  7. public function run( $rulePregResult = array() ) {
  8. $this->runValue = ClubService::isClub($this->uin);
  9. return Code::SUCCESS;
  10. }
  11. }

这里的代码其实很简单,就是判断下当前用户是否为会员。并把结果返回给 runValue,然后交给 API 引擎进行运算。CellBaseRule 类 和 IfCellRule 接口类主要是定义了一些全局性的方法和属性,这里不再详细解说。

主引擎 API 通过获取该活动 actid 的 Rule 配置信息,依次加载相应的规则 Rule 文件,并一一执行相应的 Rule 组件。

4. 装载一个发货组件(OP)

原理其实和 Rule 差不多,唯一不一样的是,OP 只能有一个,而 Rule 可以有多个。这是为什么呢? 主要是因为,如果发货 OP 有多个,一旦某一个 OP 发货失败,我们没有好办法去处理事务回滚的问题。每个 OP 做的事情可能都是不一样的,请求的服务接口也是不一样的。基于这种考虑,系统只允许执行一个发货 OP。不过对我们业务来说,基本也满足了。

3.3 运营管理系统简介

讲到这里,有些人一定会问,我们如何在指定的这个活动号中加载那些 Rule 组件和 OP 组件呢? 答案很简单,我们不光设计了这套 API 引擎,我们还搭建了一套运营管理系统,在这套运营管理系统上可以自由的新建活动号(actid),然后在每个 actid 中配置相应的 Rule 和 OP 组件。

假如我们要开发如下的 H5 活动:

那么我们只要在运营管理系统中新建 3 个活动 id。其大致的界面如下:

界面 1

一个 H5 活动的所有 actid 活动列表,这里配置了 3 个 actid。对应:

界面 2

活动号 actid=30620 的 Rule 组件配置,这里配置了两个条件,一个条件是会员,另一个条件是会员等级大于 4。

界面 3

活动号 actid=30620 的 OP 组件配置,这里配置了一个添加虚拟货币的 OP。

有了这套系统,我们开发活动大大节省了人力,比如以上面这个活动 H5 的开发为例,我们从原来传统的开发流程为:

  • 写 3 个 API 接口,并编码实现所有的业务逻辑;
  • H5 图片中的三个按钮,分别请求相应的 API 接口。

演化后的开发流程:

  • 在运营管理系统新建 3 个活动号,并在每个活动号 actid 配置上相应的 Rule 和 OP,如果之前开发了相应的 Rule 和 OP,则直接配置使用;如果是新的 Rule 或 OP,则去扩展开发(实现上到目前为止,AMS 现成的组件 Rule+OP 已接近 2000 个了,大部分的活动基本都可以复用这些组件);
  • 在 H5 中的按钮绑定相应 actid 的 API 请求。

总结下 AMS API 引擎的设计特点,可以归纳为以下 3 点

  • 组件化,对重复的 Rule、OP 进行组件化设计;
  • 配置化,对执行流程中的 Rule、OP 进行可配置化;
  • 自动化,对人工配置的数据进行自动检查其正确性。

通过 AMS 系统一期的设计,大大降低了人力成本,原来一个人可能要一周完成的工作,现在基本 1~3 天就可以完成。开发一个活动的效率是提高了,但要开发的活动却是越来越多。因为现在是从原来的一个人一周开发一个活动,变为一个人一周要开发 2~3 个活动。虽然这里提高了开发的开发效率,但是整个活动的开发流程却没有得到根本性的改变,流程还是比较多,参与的人也比较多,周期也相对较长。能否再次缩短时间呢?于是,经过开发和产品一段时期的反复沟通,AMS 二期开始了。

3.4 AMS 二期的设计(又叫活动模板)

1. 目标:30 分钟配置一个 H5 活动

这个当时给团队的挑战其实是很大的。经过我们的详细分析,发现有些环节还是可以再次进行组件化、配置化、自动化。于是我们又砍掉了很多人工环节,其整个活动的开发流程原来是这样的:

演变后流程为:

从上面的 AMS 二期流程图我们可以看到,流程里直接砍掉了重构和开发两个最花时间的重要环节。这意味着,不需要重构和开发来参与了。还是直接上图看下 AMS 二期的设计效果吧。

2. 配置步骤

现在上线一个活动,完全由设计师和运营人员 2 人搞定,大概的步骤为:

  • 设计 H5 背景图片(角色:设计师);
  • 创建相应活动号,上传背景图,拖拉相应组件,给组件分配相应的活动号 actid(角色:运营或产品);
  • 发布测试、上线(角色:运营或产品)。

像上面这个抽奖活动,如果设计师已经设计好背景图的情况下,运营人员只要花个 20 分钟的样子就可以配置好并上线了

3. 工作原理

1) 自动切图

上传设计师设计好的背景图片,由于整张图下比较大(大部份都是大于 4M)。如果一次性加载一张 4M 的图片,在移动端打开速度是非常慢的。因此,我们把背景图自动切成若干张小图,并把切好的小图自动上传到图片 CDN。

2) H5 组件化

对 H5 页面中常用到的视图做成一个个 H5 组件,并且可以替换一些基本的元素(如文字,按钮图片,文字大小,颜色等)。

3) DOM 结点存储配置数据

每个 H5 组件可以绑定一个活动 id,并在该组件的 data-logic 属性里存储相应的配置逻辑。

比如,下面的 H5 活动其最终生成的 HTML 大概如下:

熟悉 HTML 和 CSS 的开发应该不难理解其中的含义。

4) 活动模板的设计特点

总结还是这 3 点:

  • 组件化,对 H5 元素组件化设计;
  • 配置化,对 H5 界面可配置化,对组件的外观可配置化;
  • 自动化,对上传的背景图片自动化切图,自动化生成 DOM 节点,自动化生成手机访问二维码等。

AMS 活动模板上线后,开发团队再也不用去做这些重复性的活动开发了,而是直接交给产品运营同学去配置了,终于解放了开发。

4. 系统架构

首先来看下 AMS 结构分层,简单的系统结构如下:

上面我们说的 API 引擎就是图中的业务层,而运营管理系统和活动模板对应图中的运营平台。

一个要支撑住峰值 10 亿 PV 的系统,光有一套代码层是远远不够的,其最重要的是要高可用。那么 AMS 是如何在高并发的情况下保证系统高可用的呢? 这里我分享 3 点 AMS 系统主要运用的设计原则。

1. 过载保护

主要是做了无效请求拦截、限流、限频、session 锁、安全打击等。

2. 柔性可性

AMS 系统的很多 OP 都是调用第三方的接口,执行时间也比较长,直接影响系统的 QPS。因此我们主要对服务进行降级处理,当有大流量推广时,我们把活动的发货方式由同步发货改为异步发货的方式,减少发货的时延,提高系统的 QPS。

同步方式流程如下:

异步发货方式流程如下,主要是我们使用了 MQ 消息队列来消费。

3. 大系统小做

拆分大的系统为一个个小系统,减少子系统间的耦合及相互影响。比如我们在 Web 服务层部署时,部署了多个集群。即使某一个集群挂掉了,也不影响整个系统。就像下面的图一样,哪个系统更稳定,大家一目了然。

5. 小结

以上就是我分享的设计之道。最后再做下个总结,这里说的“道”,包含两层意思:

一是设计过程,好的系统不是一开始就是设计得很好,而是经过了不断演化的过程。

二是方法思路,AMS 主要有组件化、配置化、自动化。这里我再抽象一下,变得更通用一些。

  • 组件化:对重复不变的事组件化
  • 配置化:对变化的事配置化
  • 自动化:能让程序做的事尽量让程序自动化处理

另外系统的架构上主要使用了以下 3 种常用的设计思维:

  • 过载保护
  • 柔性可性
  • 大系统小做

QQ 会员运营系统(AMS)结过多年的不断积累及迭代,其实际情况复杂得多,技术细节也非常之多,本文其实做了很多简化。本文重点在于分享这套系统的设计过程和设计思维,希望对你有所帮助!

本文首发于 GitChat,未经授权不得转载,转载需与 GitChat 联系。

相关文章