PvP实时抢红包游戏设计与实现

写在前面

之前一篇文章讨论关于PvP真人竞赛的需求,这次一起来探讨PvP抢红包游戏场景。

众所周知,最为熟悉的抢红包就属微信红包了。微信的抢红包是一个非常深入人心的功能,抢红包的抢字体现在红包个数是有限制的,不抢就没了。刺激的地方在于还有“拼手气”这个点,抢到的金额随机不定,和先来后到也没有太大关系。

还有一个典型的抢红包场景-王者荣耀的世界红包。红包由那些“大佬”发出,然后全世界的人都可以看到,手速快立刻抢到,至于抢到多少咱也没抢到过不知道机制。这种场景相比于前者其实是抢的人范围进行扩大。

需求背景

实时的抢红包,本身具有很高的游戏性,提升用户的兴趣,甚至激发用户的攀比竞技心理。在数据价值上体现为能够增长产品UV(DAU)、停留时长等,用户使用产品时间增加了,自然会产生其他行为,那么产品的其他数据理应也有所上涨。

因此,产品想在阳光养猪场中增加一个实时抢红包的场景,能够每隔一段时间,出现一个世界红包,凡是此刻在线的用户都能参与抢夺,红包数量没有限制,用户能否抢到取决于抢的耗时,超过一定时间便无法抢到红包。用户抢到红包或者没有抢到,都需要展示手气排行榜单(Top10),展示完关闭即可。

需求分析

从需求描述看,抢红包游戏主要分为三个环节:红包生成、用户抢和参与排行。

从红包的定义来看,其是固定时间间隔进行生成的,每个用户在指定时间都可以抢到,本身红包是无限制的。这样一个属性其实是非常重要的,想当然我们会把抢红包当做一种“秒杀”行为,然后开始想红包一定不能“超抢”之类的,进而有关“秒杀”的一些技术都开始联想起来,一顿操作。

但其实,这种无限制的红包,其实用户是在和“时间”赛跑,用户能不能抢到完全取决于抢的时间,抢的额度也完全取决于耗时,这样就可以看作每个用户都在抢自己独立的红包,能不能抢到取决于自己速度。这样将模型转换,是不是发现用户与用户之间根本不存在竞争,那么和“秒杀”行为也就八竿子打不到一处。

用户抢的环节自然也变得简单起来,我们只要记录红包开始时间,然后再计算用户抢的耗时,根据耗时来计算抢到的额度。而排行榜其实是这个需求的难点,因为它涉及所有用户的行为,必然有“并发”行为。

技术选型

由于该场景的红包具有时效性,因此首选也是redis作为存储。用户触发的红包写在各自的key中,例如:prefix+uid,内容则包含了红包的倒计时。所以,当存在redis key且倒计时中,则认为用户存在有效的红包待抢。

排行榜需要存储每一轮红包手气前10的用户,实现排行榜的方案百度一下,都是倾向于选择redis的zset数据结构实现。这种方案的优点在于数据结构已经很好的满足业务目的,而且redis本身是分布式存储中间件,对应用服务集群的节点来讲,数据是共享的,因此实现出的排行榜是全局的,完全达到了目的。那么redis key可以设置为:prefix+timestamp,timestamp是红包倒计时时间戳,可以理解为红包轮次。相同轮次的红包参与同一场排行。

redis实现排行榜的方案也有显著的缺点,就是会产生热key问题。因为抢红包行为集中在红包开始的前几秒钟,产品要求每个用户抢完后立刻要展示排行榜,所以抢完红包参与排行是一个同步的过程,无法异步。因此,在每轮红包开始的瞬间,会有大量请求打到redis进行读写,造成热key。

那么,采取常用热key的解决方案,我们可以进行key打散,例如分128的子榜,每个子榜分别存Top10,这样热度理论上降低了128倍,redis也不会有单节点压垮的风险。最后,如果需要显示世界榜的Top10,需要将128个子榜merge。这样的merge操作虽然逻辑简单,但其实业务代码实现也挺难受的。

既然redis能实现排行,我们能不能考虑用本地缓存来做呢?本地缓存的优势显而易见,操作本地对象,速度快,没有网络IO,不存在什么热key问题等等,但是好像百度排行榜的方案没有人提及本地缓存实现,那么就要思考本地缓存为什么不合适?

首先,本地缓存有单机性的特点,用户请求路由到机器是随机的,排行榜缓存数据不共享,导致用户看的榜单可能张冠李戴。同时排行榜也自然而然被分割成机器数量的子榜。如果一定要解决这些问题,很可能还是要借助redis来进行辅助,那样问题似乎并没有得到简化。

值得注意的是产品的需求,红包排行仅需要在抢完红包后展示一次即可,意味着这个排行榜只读一次。所以用户不需要重复查看榜单,那么用户请求被随机路由也就没关系了。其次,产品接受榜单可以并非完全意义上的世界榜单,分成多个个子榜没有关系,每个子榜认为是对应用户的世界榜单。

这样,本地缓存完全能够胜任,不需要使用昂贵的redis存储,也不用担心热key,热key打散到何种程度,以及避免担心redis可用性,读写网络io耗时等。

具体实现

对于红包生成,需求需要对当前所有在线的用户生成红包。当前所有在线的用户如何判断呢?这里简单的处理为用户“养猪”的时候生成红包,因为用户已经在“养猪”了,必然是一个在线用户。至于那些停留在养猪场,没有任何动作的用户,就暂且忽略。

红包生成规则较为简单,redis中记录红包开抢的时间戳即可。

抢红包也比较简单,根据用户调用抢红包接口的时间减去redis中记录红包开始时间来算出耗时,从而计算出抢到的奖励额度。

用户抢完红包后,需要根据手速和奖励额度参与排行。设计一个size固定为10的小顶堆(Java中有界优先队列)来存储排行榜,每个用户先比较堆顶最小元素,如果比最小元素大则入榜,同时也会T掉旧的最小元素;如果比最小元素小,这说明没资格入榜,直接返回给用户当前榜单即可。

当然,排行榜的小顶堆是一个共享成员变量,同时有并发读写操作,所以需要一个线程安全的优先队列。目前并没有现成的线程安全的有界优先队列,需要我们自己去实现。分析我们的场景,其实我们写操作相对于读操作是较少的,因为写操作集中在最开始的几秒钟,一开始进来的用户手速快、奖励高,排名也自然高,有资格入榜。之后的用户大多其实是没有资格入榜的。所以这样的场景用copy-on-write的方式实现线程安全非常适合。当判断用户真正有资格入榜(堆未满,或者比堆顶元素大)时,才会获取锁,然后copy一份榜单进行入榜写操作,写完再释放锁。千万不能暴力的读写操作直接synchronized,这样会带来很多无谓的抢锁,线程阻塞,消耗性能。

不同时间轮次的红包参与不同轮次的排行榜,意味着单机同一时间可能存在多个红包的排行榜。我们可以将这些排行榜按时间存在一个大Map缓存中,读取的时候按时间进行访问。当然,这会涉及到缓存膨胀的问题,因为没有失效的策略,所以直接借助一些本地缓存框架,如Guava Cache、Caffeine等,设置缓存写入后一定时间过期,同时限制最多存一定数量的排行榜即可。

  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

打赏
文章很值,打赏犒劳作者一下
相关推荐
限时福利限时福利,15000+程序员的选择! 购课后添加学习助手(微信号:csdn590),按提示消息领取编程大礼包!并获取讲师答疑服务! 套餐中一共包含5门程序员必学的数学课程(共47讲) 课程1:《零基础入门微积分》 课程2:《数理统计与概率论》 课程3:《代码学习线性代数》 课程4:《数据处理的最优化》 课程5:《马尔可夫随机过程》 哪些人适合学习这门课程? 1)大学生,平时只学习了数学理论,并未接触如何应用数学解决编程问题; 2)对算法、数据结构掌握程度薄弱的人,数学可以让你更好的理解算法、数据结构原理及应用; 3)看不懂大牛代码设计思想的人,因为所有的程序设计底层逻辑都是数学; 4)想学习新技术,如:人工智能、机器学习、深度学习等,这门课程是你的必修课程; 5)想修炼更好的编程内功,在遇到问题时可以灵活的应用数学思维解决问题。 在这门「专为程序员设计的数学课」系列课中,我们保证你能收获到这些: ①价值300元编程课程大礼包 ②应用数学优化代码的实操方法 ③数学理论在编程实战中的应用 ④程序员必学的5大数学知识 ⑤人工智能领域必修数学课 备注:此课程只讲程序员所需要的数学,即使你数学基础薄弱,也能听懂,只需要初中的数学知识就足矣。 如何听课? 1、登录CSDN学院 APP 在我的课程中进行学习; 2、登录CSDN学院官网。 购课后如何领取免费赠送的编程大礼包和加入答疑群? 购课后,添加助教微信: csdn590,按提示领取编程大礼包,或观看付费视频的第一节内容扫码进群答疑交流!
后台技术选型: <ul style="color:#2F2F2F;"> <li> JDK8 </li> <li> MySQL </li> <li> Spring-boot </li> <li> Spring-data-jpa </li> <li> Lombok </li> <li> Freemarker </li> <li> Bootstrap </li> <li> Websocket </li> </ul> 小程序端技术选型 <ul style="color:#2F2F2F;"> <li> 微信小程序 </li> </ul> <div style="text-align:center;color:#2F2F2F;"> <div style="background-color:transparent;"> <div> </div> <div> <img alt="" src="https://upload-images.jianshu.io/upload_images/6273713-928017278f465cbd.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1000/format/webp" /> </div> </div> <div style="font-size:14px;color:#969696;"> <br /> </div> </div> 小程序端 <div style="text-align:center;color:#2F2F2F;"> <div style="background-color:transparent;"> <div> </div> <div> <img alt="" src="https://upload-images.jianshu.io/upload_images/6273713-8d6c2b81701d32cd.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1000/format/webp" /> </div> </div> <div style="font-size:14px;color:#969696;"> <br /> </div> </div> <ul style="color:#2F2F2F;"> <li> 扫码点餐 </li> <li> 菜品分类显示 </li> <li> 模拟支付 </li> <li> 评论系统 </li> <li> 购物车 </li> </ul> <p> <span><img alt="" src="https://img-bss.csdn.net/201907270119553529.png" /><br /> </span> </p> <p> <span><img alt="" src="https://img-bss.csdn.net/201907270120098756.png" /><br /> </span> </p> <p> <span><img alt="" src="https://img-bss.csdn.net/201907270120405331.png" /><br /> </span> </p> <p> <span><img alt="" src="https://img-bss.csdn.net/201907270120538298.png" /><img alt="" src="https://img-bss.csdn.net/201907270121012487.png" /><br /> </span> </p>
<p> <span style="color:#494949;font-size:14px;"><br /> </span> </p> <p> <span style="color:#494949;font-size:14px;">为什么人人都要学Git?</span><span style="color:#494949;font-size:14px;">Git的由来可不简单,最初是由Linux之父-林纳斯.托瓦兹为了更好管理Linux内核代码而编写的,</span><span style="color:#494949;font-size:14px;">Git用于版本控制,可以说是开发日常必备,很好的解决了代码合并的问题,经得起频繁多人修改的考验!</span> </p> <p class="ql-long-24357476" style="font-size:11pt;color:#494949;"> <span style="font-size:14.6667px;"><br /> </span> </p> <p class="ql-long-24357476" style="font-size:11pt;color:#494949;"> <span style="font-size:14px;">庞大的项目从来不是一个程序员可以搞定的,多人合作时代码版本管理显的尤为重要,千辛万苦改的Bug代码一合并没了怎么办?</span><span style="font-size:14px;">Git如此重要,相信作为程序员的你一定需要学习,2小时从根本理解Git!</span> </p> <p class="ql-long-24357476" style="font-size:11pt;color:#494949;"> <span style="font-size:14px;"><br /> </span> </p> <p class="ql-long-24357476" style="color:#494949;font-size:11pt;"> <span style="font-size:14px;color:#E53333;">学完即可轻松应对工作中 99% 以上的 日常代码管理 使用场景,实用性99.9999%!</span> </p> <p> <br /> </p> <p class="ql-long-24357476" style="font-size:11pt;color:#494949;"> <span class="ql-author-24357476"> </span> </p> <p class="ql-long-24357476" style="font-size:11pt;color:#494949;"> <span style="font-size:14px;">这门课程,绝对不会让你觉得亏!</span> </p> <p class="ql-long-24357476" style="font-size:11pt;color:#494949;"> <br /> </p> <p class="ql-long-24357476" style="font-size:11pt;color:#494949;"> <span style="font-size:14px;">Git基本用法+Git高级用法+Git原理+课程教辅</span> </p> <p style="font-size:11pt;color:#494949;"> <span style="font-size:14px;"> </span> </p> <p class="ql-long-24357476" style="font-size:11pt;color:#494949;"> <span style="font-size:14px;">本课程实用性极强,边学边用!<span style="background-color:#FF9900;">零基础</span>也能轻松入门~</span> </p> <p class="ql-long-24357476" style="font-size:11pt;color:#494949;"> <span style="font-size:14px;"><br /> </span> </p> <p class="ql-long-24357476" style="font-size:11pt;color:#494949;"> <span style="font-size:14px;color:#E53333;">在这门课中,我们保证你能收获到这些</span> </p> <p class="ql-long-24357476" style="font-size:11pt;color:#494949;"> <span style="font-size:14px;">1)Git最佳实践</span> </p> <p class="ql-long-24357476" style="font-size:11pt;color:#494949;"> <span style="font-size:14px;">2)版本管理</span> </p> <p class="ql-long-24357476" style="font-size:11pt;color:#494949;"> <span style="font-size:14px;">3)分支管理</span> </p> <p class="ql-long-24357476" style="font-size:11pt;color:#494949;"> <span style="font-size:14px;">4)标签管理</span> </p> <p class="ql-long-24357476" style="font-size:11pt;color:#494949;"> <span style="font-size:14px;">5)解决冲突</span> </p> <p class="ql-long-24357476" style="font-size:11pt;color:#494949;"> <span style="font-size:14px;">6)Git信息存储原理</span> </p> <p class="ql-long-24357476" style="font-size:11pt;color:#494949;"> <span style="font-size:14px;">7)深入理解Git三大分区</span> </p> <p class="ql-long-24357476" style="font-size:11pt;color:#494949;"> <span style="font-size:14px;"><br /> </span> </p> <p class="ql-long-24357476" style="color:#494949;font-size:11pt;"> <br /> </p> <p class="ql-long-24357476" style="color:#494949;font-size:11pt;"> <br /> </p> <p class="ql-long-24357476" style="color:#494949;font-size:11pt;"> <br /> </p> <p class="ql-long-24357476" style="color:#494949;font-size:11pt;"> --------------------------------------------------------------- </p>
©️2020 CSDN 皮肤主题: 数字20 设计师:CSDN官方博客 返回首页

打赏

等一杯咖啡

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者