微信摇一摇摇奖设计

公司最近策划了一场运营活动,利用微信的摇一摇周边功能,在每天的下午三点一刻,接收用户通过摇周边来摇奖。对于周排名和月排名第一的公司,免费赠送100杯冠名咖啡(冠公司名称,且帮冠名公司向这100个用户推广其公司的产品)

需求

通过报名的方式来参与微信摇一摇摇奖活动,领取了带beacon的包菜仔的公司,在每天的15:15~15:30这段时间,打开蓝牙,通过微信的摇一摇周边功能来摇奖。

规则

用户在摇的时候,微信会发送摇一摇的事件到微信公众号的后台,在接收到用户的摇一摇事件后,后台根据一定的规则来判断是否中奖,同一用户每天最多可中奖三次。

用户在摇一摇的界面,摇到了配置的页面后,点击配置的链接进去,会看到自己是否中奖,奖品分为两大类:实物和虚拟物品,虚拟物品的好说,直接下发到用户的账户;对于实物奖品,还要生成订单,并推送到相应的店铺,出小票,店员看到订单后,制作出品并配送。

在参加摇一摇的这些用户中,有一部分用户是未关注过我们的微信公众号的,对这部分用户,必须在进到页面来的时候生成用户信息,这一点没问题,之前的系统已经支持了。

防作弊

有的用户不断的在刷那个页面,以为是那个页面通过刷新来参与抽奖的,实际则不然。摇一摇摇到的页面只是一个领取奖品的纯查询操作,而实际的抽奖逻辑是在后台接收到微信摇一摇事件的时候进行的。有点类似CQRS,摇到的页面只是读,和抽奖的逻辑(写)是分开的,所以用户除了摇,也没办法作弊。

系统设计

所有的奖品有大约七种:四种是实物的,三种是虚拟的。虚拟的奖品如优惠券,要能够支持配置金额,所有的奖品都需要支持一些可配置项,如:奖品的份数,每种奖品每个人可以中多少次(每天中一次?永远只能中一次?系统需要支持这种配置策略),奖品的开放时间段(可以支持不同时间段做不同的活动)。

除此以外,还需要记录用户的摇奖记录,每个参与的公司参与人数的排行榜,以及每家公司的中奖排行磅等等。

以上是功能需求,在系统的角度,还需要考虑一些问题:15分钟的时候,用户不断的摇微信,会不断的有摇一摇事件发送到服务器,当用户量上来的时候,系统的可用性是个问题。

所以在设计的时候,奖随机中奖的逻辑前置,记录完用户的摇一摇记录之后就进行随机校验,这样可以过滤掉很大一部分的请求往后面走,减轻服务器的压力;其次,把奖品、用户信息等不变的数据放入到缓存中,减少数据库的IO次数,这样一来,整个过程的平均响应时间基本上可以控制在200ms以内(这里发现了一个问题:测试环境在未加缓存的时候,平均响应时间已经不到200ms了,但是生产环境用了缓存平均响应时间却比测试环境长很多,我检查了两个环境下的所有配置,包括nigix和tomcat,完全一样,唯一有区别的地方就在于,测试环境的web服务器和数据库都在深圳区,生产环境的web服务器在深圳区,而数据库在杭州区,数据库的访问是通过内网地址访问,难道是这个原因导致?需要找机会确认)测试环境下使用了上述策略后,平均响应时间在100ms以内。

另外,部分代码的优化也是必要的,例如解析摇一摇事件等等。其实优化的原则和数据库的优化思路类似:

减少对象的创建次数,减少对数据库的访问次数,对于不变的数据使用缓存来存放,减少循环尤其是嵌套循环的次数,对整体的业务逻辑做梳理,尽量的把用户的请求靠前处理完成(这个就是快速失败的概念?),利用业务规则,不断的过滤掉请求,只有真正中奖的用户的请求才会走到流程的最后;对于没有依赖的数据,异步处理。

再极限一点:将数据插入到数据库成功后,同步更新缓存,然后在读的一端直接去从缓存中读,这样又可以将性能进一步提升,但这种需要构建同步更新缓存的模型,复杂度也会上升,必要的时候才做。

一致性的问题

如何保证总共10份奖品,不会被领取10份以上?一般可能会考虑利用事务。没错,事务是可以,事务会加大方法的执行时间,在这种业务场景下,流量量级上升的时候,直接影响着系统的响应能力,所以在这里,我没有采用事务的方式来处理,而是通过程序的设计来保证数据的一致性,而且,事实也证明,不使用事务,系统的响应能力也会提升很多。

可能有人会有一个疑问:这样做,岂不是可能会出面某用户摇完之后,打开领奖页面的时候,本来已经中奖了,但打开的时候,因为数据还没写入,页面中会显示未中奖?确实有这个可能,但是,概率非常低,只是方法执行得够快就不会出现这个情况;再者,万一出现了也没关系,用户下一次摇完之后,打开页面的时候还是可以再领到自己中到的奖品,因为前面的规则定了每人每天最多可中奖三次,所以这个问题也不会成为问题。

总结

代码的质量、规范、业务逻辑的组织(区分业务场景)等等,每一个环节对系统的性能都有一定的影响,不过早优化,但要任何时候要先思考再设计。

拆分不只是数据层面,业务逻辑层面也一样;分离变的和不变的部分。