百万弹幕下的礼物系统:海量并发 强事务系统实践
本文根据熊猫 TV 高级应用开发工程师 沈冠璞在 MDCC 2016 移动开发者大会上的演讲整理而成,PPT 下载地址:http://download.****.net/detail/sinat_14921509/9642194。
大家好,我是来自熊猫 TV 的沈冠璞,有 7 年的后端开发经验,是熊猫直播的第二行的作者,经历熊猫直播从 0 到 1 海量 PV 和存储的快速扩张发展场景,有大中型互联网网站高可用设计架构经验。礼物系统做了近 1 年,踩过不少的坑,经历了产品、DBA,然后才有了现在的发展。今天跟大家分享的是百万弹幕下的礼物系统,也是借礼物系统这个典型的案例给大家分享一下熊猫直播在海量并发和强事务系统下实践。
目前熊猫 TV 礼物系统的现状,部署了两个集群,每个集群是六台服务器,QPS 峰值 2 万+,平均响应时间 10ms,服务热部署 99.99% 可用,具体的服务架构如下图所示,其中采用了双机房的水平部署,四层采用通用的 KOS,接入层就使用 NDS,平台服务层实现了 APC,缓冲使用 Redis,是存储排行榜的信息,礼物分组,使用存储礼物属性,存储消费流水和主播的每日消费每日的收益统计。
那么如何通过具体的实践来构建百万在线的礼物系统,熊猫直播选择使用高并发加上最终一致性保证事务的方式,首先进入 NoSQL 系统设计,该礼物系统要始于去年冬天,上线 2 个月后,产品王说:“兄弟,没有礼物的直播就是耍流氓”,接而我先对礼物系统做了简单的分析,大概分成四个大的模块:礼物列表;礼物赠送;礼物排行;礼物消费流水,重点分析核心流程。作为一个观众,进入到直播间,首先会获取礼物列表,然后送主播礼物,观众开始点礼物图标,进入到扣费环节,扣费成功就给用户加经验,加完经验,在我们平台就是类似于不错的等级,比较青铜、黄金、王者,给用户荣誉感,然后会弹出弹幕,关心付费实践的业务,这就是一个完整的送礼物流程。
分析完核心流程进入到立项和编码阶段,礼物系统首先对于一个程序员来说给项目起名字也是比较纠结的事情,我们的礼物系统叫做 magi(来源于外企的礼物这篇小说)。作为一个文艺企业,礼物系统的语言上选择性能卓越、部署方便、开发迅速、风格统一。缓存上选择数据灵活,可用做redis,DB则选择灵活文档结构,支持数十亿单秒存储,一个月之后,终于上线了第一个礼物系统。
从界面上来看是3个部分,最左边一小排是礼物列表,右边靠上则是用户的排行榜,送主播的礼物多,那么就可以上光荣榜,然后是送完礼物之后就会出现弹幕效果,用户可以在后排查看消费记录,主播可以查收益统计就进行体现。但做完一周之后,产品王又说,“兄弟我们是一个有逼格网站,每个主播都要有个性,每个直播间都要有个性化的礼物”。
因此礼物系统如下图所示,一个分组下有多个房间,一个分组会有礼物,进到房间就找对应的分组就在去找对应的礼物,就多了几道周转,房间的礼物才能够显示出来。
接而进入到了系统的优化阶段。有程序员说礼物消息太长,长得服务器都被占满了。怎么回事呢?是因为发了一个墙,在线的百万用户都可以收到礼物消息,礼物消息在做的时候比较简单,一条礼物消息就达到 500、600 个字节,百万人同时在线,如果每秒是有一条礼物消息,就能够占到 60G 的带宽。这个还不算完,DBA 也说,QPS 超标、网卡吃紧,这是怎么回事?之前做了礼物的多样化,前端就不去存储礼物列表,上百万用户的房间一卡,所以用户没有办法,所以只能够全部拉一遍礼物列表,礼物列表要去区分,又要走多次请求,确定这个礼物分组是哪一个,礼物分组是不是在线,礼物分组在线也是产品王的坑爹需求,所以就导致我多查一下,礼物分组之下的礼物是什么?通过这么几次的周转,Redis QPS 又上了几倍,所以网卡就开始吃紧。
针对于这些问题,很容易想到,应用服务器需要扩容,redis 需要增加 slave,我们就用到相应的方式去分析,最终确定了系统瓶颈是在于应用服务器的网卡,应用服务器的 QPS 都到了上线,但是作为有 7 年工作经验的资深人员,有没有更大的优化空间呢?分析了礼物列表之后,大概是 4K,肯定是有压缩空间,因为里面是有很多东西,于是就决定对礼物列表 ZIP 进行压缩,通过测试对比发现,当时对比了几种压缩算法,发现 GZIP 的压缩效果最高,主要的操作是应用服务器的解压,解压的效果,解压效率在我们的技术范围之内,第二也可以通过加机器水平扩展,所以就选择了境内的压缩,优化之后效果明显。带宽的压力也减少到原来的 1/4,所以 redis 的问题得到了比较良好的解决。
再来看一下消息过大的问题,通过削减礼物消息的冗余,之前的礼物消息做得比较的简单,安卓等图片的信息都放到礼物之中,这些部分是可以通过礼物的方式去获取的,于是对礼物信息进行的削减,从原来的 500 字节减到了 300 多个字节,减少了 168 个字节,假如 100 多万人同时在线,每秒发 100 个礼物,能够减少多少呢?每秒能够减少 16GB 的带宽,你自己没有想到有那么的夸张,但是事实就是这么的夸张,做完这两个优化之后,然而这是万里长征的第一步。
礼物系统,作为一个和金钱打交道的系统,事务的重要性不用多说,当选型又是高并发的系统,怎么样去实现事务呢?我们采取了折中的方案,从一个重大的问题开始入手分析,这个重大的问题,是谁提出来的?就是首席产品经理王思聪先生,“我连接了100个佛跳墙,你消费记录竟然就给我记了100条”。之后发现佛跳墙成立 100 个站,对他来说是一个个都要看,要解决这个问题,先来理解一下连机的概念。三分钟之内,送给同一个主播,同样的礼物,这就算是连击,这需要改变消费记录的数量,超过 3 分钟就算一次新的记录,需要插入一条新的记录,比如用户送了 4928 个加油票,如果 3 分钟之内再送,那么就会变成 4929,消费的记录就要变成 4929,要不然超过了 3 分钟就不行。所以这个难点在于,我是需要增加一条新技术还是更新上一条消费记录,用户付款点击的时候,更新消费记录有没有可能失败?或者是最终,它生成的数不是 4929,变成小的一个数。在我们系统,礼物小到只有 2 毛钱,遇到高峰期,粉丝同时疯狂的去刷礼物。如何在这种情况下做好消费记录的更新?这里面我们没有用排队的方式去做更新,而是使用了这些方法,考虑到大部分 CAS房间,只能够一个个的单击,增加点击数。举个例子,有一连击到 200 连击如何的保证 DB 到 200,这个时候就需要存一个基数,当等于 1 的时候,就是加入到数据,当大于 1,发现不在,那么可能就是数据没有插入进去,然后就开始做这种更新。高并发的时候,因为 DB 的技术转变,那么需要是这样的,最终发现,当更新 8 连击的时候,库里边的数字都变成了 9 连击,那么这个就更新失败更新失败怎么办?就从新的在来做,如果发现 DB 里面的数比当前的连击数还要大,那么就丢弃本次的操作,否则就继续的做。
解决完常见直播间的方案之后,还有很多的专题页,专题页的赠送就比较复杂,可以选择送100 个、送 500 个、送 1000 个、甚至 1 万个,这个时候就需要维持两个技术,连击数和当前的总数,保证这 2 个技术 3 分钟内同时的过期,其中有一个注意点,要注意到惰性过期,这已经过期了,但是仍然是有这样的记录,就需要把主库加入到从库的读取之中或者说选择升级版本。就是 golang redis lib 需要设置 MAXactive 大小。Golang mongo lib 需要设置 maxpoolsize。调优做完参数,就对账。
连接的另外一个难点在于,收益被累积到第二天,无法和支付记录按天对账。仔细思考一下怎样做好对账?熊猫 TV 当时是有两个表,收益详情和收益每日统计,每日统计是有的,如果主播去查的话是来不及的,可能一个大主播一天收几十万的礼物,这也是不太现实的,如何做到两个表的同时更新,开始就直接想要用事务,之后发现事务非常的慢,这肯定不行。经过之前的设计,可以保证明细表的准确性,于是我们根据相关的内容设计了一套自动对账系统,采取了比较常见的一致性的做法,销售的队列,如图所示通过消息队列和对账的补贴机制来实现了对账的系统,消息可以进入队列,然后在 mopreduce 来进行处理,然后通过 mongo 来执行,然后生成准确的统计结果,进入到每日的收益统计表,终于解决了问题,然而这只是万里长征的第二步。
礼物系统不免要跟多种外部系统进行交互,熊猫 TV 每个业务都是一个单独的系统,单独的部署就需要考虑 failover 的情况,支付完之后需要礼物系统去进行工作,就需要数值系统还有调长连接系统,还要发送弹幕、跳抽奖系统,这一部分出错,理论上都会结束,继续的往下执行,如何的做到 failover 呢?这里就需要各个系统隔离部署,对 failover 在紧急的时刻打开开关降级,还要做好超时控制,还有异步执行的状态,failover 的好处是提高了系统的可能性,但是却带来了跨系统事务,可能没有办法完整的执行和没有办法秽闻的问题,比如扣费成功了,但是没有加上经验,没有弹出弹幕效果,没有返竹子的奖励,消费的经验也查不到,怎么样解决?就是做了自己的跨系统事物。实务需要加上引号,这个和我们平常所理解的系统不一样,支付完了就没有退款,所以这个事务是只能够成功的,如果付钱了没有出效果,我们尽量帮你把这个效果达成,但是不能够退钱。
状态跟踪回补系统,回补系统会记录每一步的状态,出现异常就会把消费流水写入到回复队列,然后会通过处理机去尽快的消费,实现数值的增加,长链接消费的赠送和抽奖功能,在特殊的情况下,就需要状态表进行人工回复。如果我送给你的礼物,在最近的 1 分钟或者 10 分钟之内都没有回复成功,就需要人工去做干预,通过这样的消费队列,加上延时补偿机制,就实现了跨系统的事务就实现了跨系统的最终一致性。由此礼物系统就算做完整了,这当中就有了礼物列表、礼物分组管理、礼物赠送、礼物排行、收益统计对账、礼物消费流水和最重要的状态跟踪回补系统。那么就会进入到我们最终的环节。
礼物的系统,在熊猫的海量并发的场景下,礼物系统跟金钱打交道,对礼物的系统的灵活性以及优化有较高要求,所以我们使用最终一致性的方式,完成了对账的功能,以及礼物订单的高吞吐和高度的事务性,通过今天的分享,也希望给大家做类似的系统一些帮助。
了解最新移动开发相关信息和技术,请关注mobilehub公众微信号(ID: mobilehub)。