收藏
评论

通过实时数据推送承载千人活动官方

某年的腾讯互娱市场体系年会, tgideas 团队制作了一款以线下服务为主的微信小程序,参与到员工大会以及晚宴的多项环节中,应用的技术包括:公司智能网关+iBeacon 判定身份,小程序拉起导航,小程序云函数,云数据库,云储存,多场景使用实时数据库,小程序支付能力,内嵌 H5 小游戏,小程序红包能力。



背景

本小程序是服务于 TIEM 市场体系年会的辅助工具和互动工具。年会参与员工 1200+人,还有几十个外部嘉宾,年会分下午的员工大会,红地毯以及晚宴节目表演等环节。而小程序在其中的具体功能点,以及对应的核心技术如下:

  1. 智能网关判定是否员工,iBeacon判断是否现场嘉宾。
  2. 实时数据库更新会议+红地毯+晚宴节目的进程。
  3. 现场辩论赛环节,实时数据库展示投票并实时呈现在舞台侧屏(类奇葩说)。
  4. 实时数据库更新红地毯进程(部门每一个团队排队入场的过程)。
  5. 精彩现场通过云储存以瀑布流方式,提供员工上传照片和预览照片功能。
  6. 晚宴节目里,可以对员工表演的节目进行免费支持和付费打赏,并通过实时数据库在小程序以及舞台侧屏呈现。
  7. 舞台大屏每次的 web 抽奖结果通过HTTP API同步到小程序云数据库。
  8. 晚宴期间全场 H5 互动小游戏,每个人的游戏分数以HTTP API汇总在他选择的桌总分中,以桌为单位角逐第一,并在舞台大屏通过实时数据库能力呈现前三名的桌号和总分。
  9. 每一个体验过游戏的人,在回到小程序的游戏大厅后会分配到一个由小程序红包能力的现金红包。



云开发

第一次接触云开发,1 个月内要从无到有并完成任务,在任务重、时间紧的双压下,使得开发者对于云开发的能力担心起来。结果证明小程序·云开发的开箱即用、原生的微信能力集成、云函数自带鉴权以及可以获取 openid 等强大的能力,对于没有后台开发经验的前端开发来说是一个很好的方案选择。



实时数据库

本次分享的就是腾讯云联合微信小程序推出的能力--实时数据推送。 

微信小程序提供的实时推送能力,很简单,只有一个 API,就是 watch[1] 记住这个关键词,它就是实时数据推送的代名词 官方自研能力,开箱即用,无需管理长连,无需编写服务端代码,而且不占用数据库连接数,简而言之就是官方赠送的。从官方的云开发 demo 中包含的一个聊天场景就能看出,实时数据推送能力对聊天室、聊天模块等的需要即时通讯功能天生友好。对于年会小程序“打赏后即时反馈”的功能以及小程序里的游戏大厅功能也很契合。接下来具体说说,如何应用实时数据推送能力。 


watch 应用——年会全程节奏掌控 

在小程序 relaunch 的时候,起了一个 watch,监听了 adminConfig,并把监听到的数据变化,写入到当前页面的 data 里,这样就可以改变界面状态。

先上一个动图,可以感受得到左边的小程序管理端只要有所操作,右边的所有用户的小程序端都会即时产生对应的变化:

 1//业务逻辑太复杂,这里写伪代码
 2//app.js
 3App({
 4  loadConfig:function(){
 5    //可以把它当成是一个setInterval,设置一个ID给它,方便关闭它
 6    this.globalData.adminWatch = db.collection("adminConfig").watch({
 7      onChange: function(res{
 8        let {
 9          _id,
10          ...adminConfig
11        } = res.docs[0];
12        console.log('配置改变:', adminConfig)
13        const _page = getCurrentPages();
14        if (_page) {
15          _page[_page.length - 1].setData({ //这里很关键,把监听到的变化驱动当前界面的变化
16            adminConfig
17          })
18        }
19  },
20  onShow:function(){
21    this.loadConfig()    //每次唤起小程序都开启监听
22  },
23  onHide:function(){
24    this.globalData.adminWatch.close()  //每次隐藏小程序都关闭监听
25  }
26})
27
28//某个具体页面的比如game.xml,就可以依赖以上的adminConfig做状态判断了。
29游戏正式开始
30游戏试玩


前文说过,该小程序管理端页面,其实都是在对一个名为 adminConfig 的 colletion 做修改。而在打开小程序的第一时间就开启监听此 adminConfig,当它有什么变化,则会驱动我们的界面相应变化。而亦需要设计小程序的每一个需要被控制的页面,结构与逻辑都想办法依赖这个 adminConfig,不然没办法驱动此页面的界面变化。比如下图,在管理端点击“小游戏入口开启”后,大家的小程序界面瞬间多了一个 banner 入口;点击“游戏开始”后,大家的小程序界面则会瞬间从“开始试玩”变成“点击进入”。

通过实时数据推送,做到在每一个员工/嘉宾的小程序界面上,实时更改他们的界面,改变员工大会晚宴节目进程。

在 watch 面前,连 setData 都有可能成为性能瓶颈

打赏页面的逻辑是,用户在打赏的时候,就在 colletion 为 rewards 的集合里 add 一条记录。然后实时数据推送 watch 将监听整个 rewards 的数据改变,watch 的 onChange 事件给出了什么数据,就把这些数据洗一遍,然后直接 setData 到界面上。

看起来没问题,测试的时候也没问题,每次送上小爱心,界面的打赏记录也会瞬间反馈我的打赏行为。这一切都如此合理。

但其实这里存在了一个很隐蔽的问题,如果不是利用年会演习的机会测试了一次,如果不是总PM心细如发体验到了此问题,估计到了现场将会是一个灾难。

存在的危机:当有很多人同时在打赏的时候,watch 的 onChange 几乎是无微(每条记录)不至(推送到达)的,在每一次 onChange 都会反馈到小程序端,也就是每次都会触发 setData 去驱动界面渲染一次。watch 并不是我们想象的,会缓存一波数据改变再推送过来,它的反馈是如此直接暴力,用户 add 一条,它就推送一条,数据落盘 DB 到数据从服务端推送出去,这里仅仅是 5-10ms,也就是说除非在这 5-10ms 内有多个用户同时 add 数据,才会出现返回的记录 length 是 2 的情况。试想如果每秒都有 10 个记录反馈,那 setData 就得操作 10 次。而 setData 是一个异步行为,也就意味着它的执行是需要时间的。如果操作唤起“立即打赏”面板(其实也是一次 setData 操作),则会面临,需要在 1 秒 10 次的 setData 间隙中,插入这次的唤起界面的操作,但是试想怎么能有设备快?setData 一旦空闲,立刻就被 onChange 事件的反馈自动执行并占用了。而唤起界面的操作就被阻挡在外了,连排队的资格都没有(setData 没有排队的设定)。

如果在现场大家想打赏却唤不起打赏面板,那将是很尴尬的事情。如果涉及到收入什么的(比如此处涉及到了微信支付),那就是运营事故了。

问题已经发现并抛出来了,解决起来就简单了。解决方式是:

1onChange推送过来的数据堆在一个arr变量里:
2onChange:function(res){
3 _this.arr.push(res.doc)
4}
5然后setInterval一个函数,每秒执行一次:
6  {
7    把arr的值setData
8    然后再清空arr
9  }

以这种自然语言描述代码,是不是更直观?这样就很难在点击唤起界面的 setData 执行的时候,watch 正在占用 setData 了。

这里还要补充一个注意事项,watch 有一个能力限制,只能监视一个 collection 的 5000 条记录。比如上面的打赏数据库,设计的是每一次打赏都 add 一条记录的话,如果超过 5000 条后,watch 事件就会报错。

所以我们一定要注意数据库的设计,避免 watch 的 collection 超过 5000 条记录的情况! watch 走另一个通道,不占用套餐里标识的同时连接数,但是默认最高支持1W 的监听数。比如我之前说的 adminConfig 里的设计,只有一条记录,即便是全局 watch,也毫无压力。


watch 应用——游戏大厅

游戏大厅可能是我在整个小程序里花费最多的一个环节。这里先上个界面:

刚开始进到这个界面,自己是没有入座的,自己现场就餐的桌号,对应此处的桌号,选择桌号入座(主持人会宣读);入座后如果发现自己桌号不对,可以直接点击正确的桌号进行再次入座,也可以先点离开,再选择正确桌号入座。这里就涉及到 2 次云函数的调用了,在弱网环境可能造成 1 秒的延迟,而如果延迟过程有人跟你选择同样的桌号,而此桌已经 11 人了(每桌限制 12 人),则会造成数据错误。所以这里的查询条件以及 update 方式都有讲究。经过了 3 次半的改版,才将体验优化到勉强能让大家接收的地步。

第一次:表的设计本身不合理,只是为了能跑通整个交互,达到加入,离开,直接开始的基础目标,但是不光是性能不行,点击入座到界面展示完成需要 1.5+秒,前端的表现也出现不一致性,主要体现在头像已经在桌位上了,但是延迟 0.5 秒后才显示“直接开始”和“离开”按钮。

第二次:优化了表结构,每桌作为一个固定的表记录,每一个玩家只是这个记录里的一个 member 字段数组里的一个对象,但是这次优化出现,当频繁点击“加入”别的桌,会有多个自己头像的 BUG。也就是一个用户可以占多桌,这明显是不合理的。而且换桌的逻辑涉及到了 4 次云函数的调用计算,云函数计算时间 800ms 左右。

第三次:对云数据库的了解更深入后,对云函数的优化从换桌的 4 次调用变成 2 次。之前是先查询目标桌是否满座,然后再进行插入数据;优化后则是直接插入,如果插入不了,则返回 status='0'的状态码通知前端。避免了换桌出现多个自己头像的 bug,避免 11 人一桌,2 人同时抢桌造成数据异常的情况。云函数计算时间也缩短到 400ms 以内。在跳桌的时候,先做目标桌加入本人信息的操作,再做当前桌 delete 本人信息操作,让 delete 的时间不需要等待。

第四次:优化了交互,点击加入的时候增加一个 showLoading,避免静态等待的同时,也避免了弱网下反复跳桌造成的渲染 bug。

补充,以上的截图为云函数调试界面的控制台截图,其中因为是开发工具的缘故,耗时远大于实体机器上的真正耗时。开发者可以参考数据,达到优化目的。

总结一下游戏大厅的关键优化点:

  1. 数据表的结构设计,最好以可以估算上限的单位作为记录本身,比如桌子数量;
  2. 加入桌子的数据操作是 update,member 的增加用数据库 API 自增_.inc,多个用户同时写,对数据库来说都是将字段自增,不会有后来者覆写前者的情况;
  3. 如果是跳桌,先运行加入桌逻辑,再走离开当前桌逻辑;
  4. 要有针对防止多次点击的设计;
  5. 分区(1-30 桌,31-60 桌)渲染,分区 watch,细节做到位,就不需要一直 watch 所有(1-120 桌)的桌子了;
  6. 更少的查询次数,更详细的查询条件;
  7. 在适当的环节加入带 mask 的 showLoading,可以避免用户行为的互相干扰。

对这种多人参与,彼此会互相关系,即时性要求也比较高,对数据一致性也要保障的小程序场景,后续建议给自己评估多一倍的时间去应对,这样才能保障最终的落地,带来良好的体验。一味的从一个方向去优化,也许并不是一个好办法。比如云函数的操作瓶颈是,至少 150+ms 的回调时间,那么你再怎么优化体验,也不会超出这个范畴;而如果通过加入适当的 loading 动画,屏蔽其他操作,则无形中让用户少了尴尬的等待,也提高了交互的稳定性。



数据

在 3 小时的年会活动中该小程序承载了86175的 PV 和1577的 UV,在这么短的时间内如此大的访问量下云开发非常轻松的完成了此次任务。



总结

此次 TIEM 的年会小程序,从无到有,从设计到研发到测试,大概就是 1 个月的时间,当团队怀着忐忑的心情,直到年会结束,顺利完成了这个稍稍超出预估范畴的任务后,大伙才放下了一颗悬着的心。毕竟复盘的时候,才发现还有很多没做到位,比如线下活动的弱网环境测试。所以一些同事在游戏大厅,开启不了 H5 游戏。

而领导和同事们给予的肯定以及提出一些中肯的建议,这都是整个团队的收获。

最后一次编辑于  2020-11-30
赞 2
收藏
登录 后发表内容

【专题课程】企业案例

课程标签