
2018年初,直播答题通过答题分奖金,以其参与门槛低,奖池高,流量裂变传播的互动模式迅速引爆网络热点,成为了一种较低成本的拉新推广活动。转转基于腾讯的视频直播和im答题方案在两周内成功上线了直播答题的业务。本文主要介绍转转直播答题的实现方案。
整体架构
直播答题是在视频直播的基础上增加了答题的玩法,直播答题的玩法很简单,每天定时举行几场答题直播,每场设置几万到几百万的奖金,1场12道题,每次下发一道题,答题时间10s,作答时间结束几秒后下发答案,全部答对即可平分奖金,答错或者超时未作答不可继续答题,答错后可使用1个或者n个复活币复活继续答题。
直播答题主要有以下几个业务功能:
视频直播:通过腾讯的视频直播通道推送视频流
题目下发:由答题管理后台触发,通过腾讯的im通道下发。题目不能在客户端缓存,每次下发一道题。
提交答案:由用户在题目下发后的10s内提交给服务端,通常用户需要用2-3s的时间读题,真正提交答案的时间最主要集中在7-8s的时间,该接口的并发压力较大。
答案下发:由答题管理后台触发,跟题目同一个通道下发,包括正确答案,每个选项回答的人数,复活的人数等,客户端收到答案后,判断用户回答的是否正确。如果答错或者未作答,则自动使用复活币复活。
支付奖金:每个全部答对的用户,发送一条mq消息,最后由mq消息处理服务计算并近实时的打款给获奖用户。
下图是转转直播答题系统的整体架构:

用户信息获取
用户在答题前,需要获取用户复活币、已获奖金等信息,如果都在倒计时为0时的一刻请求服务端获取复活币信息,这个接口的并发会特别大。由于在倒计时完成进入直播后,还有一段的暖场时间,一般至少大于60s,所以我们可以在这60s中让客户端随机的请求用户的信息,以此来降低该接口的并发量。
答案提交
题目下发后,有10s的作答时间,一般需要使用2-3s读题的时间,也就是真正作答时间只有7-8s,大量用户提交答案时间集中在5s内,对于百万在线的用户量来说每秒需要大概需要处理20w的并发请求,因此提交答案的性能至关重要。提交答案的处理需要进行以下几个步骤:
1、 判断提交答案的题目是否是当前题目,是否在作答时间内
2、 获取用户上次答题的信息,判断是否重复答题、是否跳题
3、 如果跳题或者答案错误,判断是否可用复活币
4、 如果使用复活币,扣减复活币,复活人数+1
5、 写入用户本次答题信息
6、 增加相应选项的回答人数+1
步骤1中的当前题目信息,数据量比较小,我们缓存在进程内,启动一个线程每100ms从数据库同步一次,由于客户端提交答案到服务端经过网络是有一定的延时的,所以服务端的可作答时间要适当的比客户端的10s长一些。
步骤4中的复活币扣减异步进行,当需要扣减复活币的时候,发送一条mq消息,由mq接收服务接收消息后异步写入数据库。
在分布式场景下全局的计数信息只能集中存储,存在本地服务中不可取,我们把计数信息存储在redis中,每次计数更新时,只是更新本地的一个原子计数器,另外启动一个线程每100ms或者当计数到达100的时候写入redis并把本地的计数器清0,这样大大的减少了计数对redis的请求的次数。当服务崩溃时最多丢失100ms时间的计数或者最多100个数。(实际上如果在直播答题的过程中服务挂了,其他问题比计数会更严重)。
通常来说我们需要把用户的答题及复活币信息存储在redis中来提高性能,这样每次答案提交我们都要通过网络访问2次redis。在分布式的环境下我们如果把答题信息缓存在进程内,需要保证用户的每次请求都落在同一台机器上,限于目前的框架我们无法保证用户的两次请求落在同一台机器上,就算能保证,在临时需要增加机器的时候处理也相当麻烦。为了提高性能我们的解决方案是让客户端帮我们存储用户上次的答题信息,用户在进入直播答题获取个人用户信息时,服务端把用户id,复活币数量,可用复活币数量,上一题题号,上一题回答是否正确等信息通过AES算法加密生成一个token串返回给客户端,客户端在下次提交答案时带上服务端上次返回的token信息,服务端解密token获取到相应的信息,做逻辑处理,处理后生成一个新的加密token返回给客户端。客户端不用关注token里的内容,不需要解密token。对于每场直播答题每个用户可以设置一个不同的密钥。通过上面的描述我们知道在正常情况下是可以很好的处理的,我们来看下异常情况是否可以正常处理:
超时未作答:如果能用复活币,客户端扣减复活币,下一题答题时把上上题返回的token带上,服务端处理时发现跳1题,能使用复活币的话使用复活币,并异步扣减复活币。
不能答题时继续作答:服务端在收到请求时,从token中获取到上次答题信息时会发现是失败的,本题的token也会写入回答失败。如果客户端不管,一直作答到第12题,没关系,第12题在处理的时候也会认为错误,不会把用户算到全答对的人群中。
重复提交相同答案:我们每次处理是独立的,只是计数会增加,影响不大。
多次提交不同的答案:无法处理,影响比较大,如果接口泄漏,用户把每个答案选项都提交一次,总有一个是对的,用户就很容易作弊。
那这个问题怎么解决呢?我们的处理方案是:答题时不管,事后处理。
每个作弊的题都至少会发送1个以上不同的选项,我们可以把提交的答案通过mq发送出去由后台的服务做统计,把发送1个以上选项答案的用户加入黑名单,在最后打款的时候过滤掉,并且不允许参与以后的答题活动。
思考下作弊用户的行为,作弊可能会有以下几种情况:
每道题都作弊
不会的题作弊
前面题都答对了,最后一道题作弊
实际处理时,根据以上的分析,为减少mq服务的压力,我们只需要把每场比较难的题,最后一道题,再加上随机选择的一道题这些题的答案通过mq提交给后台服务甄别。
按照这种方式处理,每次提交答案的同步处理部分全部是在内存中完成,没有网络和数据库操作。接口的处理时间在90%的情况下都低于1ms。
Tomcat的优化:
TomcatConnector(Tomcat连接器)有三种运行模式
Bio:阻塞式I/O操作,Tomcat在默认情况下就是以bio模式运行的
Nio:非阻塞I/O操作。nio是一个基于缓冲区并能提供非阻塞I/O操作的Java API,它比bio并发运行性能更好。
APR:在apr模式下,Tomcat将以JNI(JavaNative Interface)的形式调用Apache HTTP服务器的核心动态链接库来处理文件读取或网络传输操作,从而大大提高Tomcat对静态文件的处理性能。Tomcat apr是在Tomcat上运行高并发应用的首选模式。
实际压测中发现,对我们这种应用场景APR和Nio性能差别不大,略好于Nio模式,最终我们设置tomcat的运行模式为APR模式。
总结
对于直播答题这种瞬间高并发的场景,我们主要采用的优化方式有以下几点:
分散请求
使用进程内缓存
异步处理
借助于mq削峰处理
redis计数聚合处理
让客户端在多次请求间帮服务器存储信息
作者简介:
杨永刚,转转平台服务技术部。

