共计 7044 个字符,预计需要花费 18 分钟才能阅读完成。
背景
Shopee 每年都会举办几场重要的大促流动。大促过程中,营销小游戏是吸引流量的次要渠道。
本文将介绍大促中最常应用,同时在线人数最多的游戏——Shopee Shake——如何应答大促的大流量冲击,保证系统的可用性,为用户提供稳固牢靠的服务。
1. 游戏与大促
每年 Shopee 会在五至十二月的每个大促节点举办电视直播流动。每次大促流动时,各市场的经营人员会与当地电视台单干,在节目直播过程中插入一段玩 Shopee 小游戏的互动环节。
1.1 大促游戏的抉择
在大促筹备阶段,当地经营人员会依据大促时间表,在游戏治理平台设置游戏流动的开始工夫、完结工夫、奖池及页面素材。待大促成行时,电视台主持人将疏导用户关上 Shopee APP 并进入小游戏页面。
当地经营人员会依据大促打算,从多款小游戏中抉择几款参加到电视直播大促当中,而 Shopee Shake 是被应用次数最多的大促小游戏,简直每次大促流动都会呈现它的身影。通过这款游戏,当地经营人员能够发放 Shopee 金币,吸引用户在 Shopee 下单购买商品。
每次大促时,Shopee Shake 都会带来大量用户流量。2021 年 5.5 大促时,该游戏接口最高 QPS 达到 30 万 +,在大促过程中施展了重要的引流作用。
1.2 Shopee Shake
Shopee Shake 是用户通过在游戏页面摇动手机,取得 Shopee 金币的相似摇一摇的小游戏。用户摇动次数越多,失去金币的概率越大。下图展现了三个不同阶段的游戏页面:
最左侧是 游戏预热阶段 的页面。游戏开始前,用户进入这个页面后,能够看到金币池的大小和游戏开始的倒计时,也能够将页面通过第三方或发送音讯的形式分享给好友,取得额定金币处分。
两头是 游戏过程页面。游戏过程中,该页面会不停掉落金币。用户须要在无限的工夫内摇动手机。摇动速度越快,失去金币的概率就越大。
最左边是 游戏后果页面。用户在当局游戏取得的金币数量会显示在这一页面。另外,因为每一局游戏会耗费一次机会,残余游戏机会在此页面也有直观出现,若用户还有机会,能够间接点击按钮,持续进行游戏。有时,当地经营人员还会配置一些抽奖机会,如果用户抽中了某项奖品,同样会在这个页面显示。
2. 技术计划
从游戏机制来看,Shopee Shake 的后端系统面临以下几项挑战:
- 刹时并发量大:因为游戏开始工夫是全局对立,所有用户都会在同一时间进入游戏,导致在短时间内产生大量申请。因而,后端系统须要在短时间内承载大量申请。
- 游戏工夫短:整场游戏环节个别继续 5 分钟,每局游戏通常在 10 秒到 30 秒之间。
- 并发扣减金币池:所有用户共享同一个游戏奖池,每一局游戏完结后都要并发扣减游戏奖池,后端系统容易因而呈现单点问题,从而影响到零碎性能。
因而,咱们的技术计划须要反对高并发申请,反对程度扩容,并解决潜在的奖池单点问题。
2.1 架构设计
从产品特点来看,Shopee Shake 零碎次要提供两大性能,别离是供当地经营人员进行大促流动配置,以及供 Shopee 终端用户玩游戏取得金币。
相应地,零碎也分成两端,即 Admin 治理端和用户端,别离面向当地经营人员和 Shopee 终端用户。
在大促前,当地经营人员会通过 Admin 端进行游戏相干配置。流动进行时,用户通过 Shopee 终端拜访游戏页面,申请后端。其中,最重要的两个申请是“游戏开始 ”及“ 游戏完结”申请:
- 游戏开始申请的业务逻辑是校验每个用户的机会是否足够,以后金币库存是否低于配置值等;
- 游戏完结申请的业务逻辑是计算用户失去的金币数、扣减金币库存、写排行榜、给用户发金币及获奖音讯。
综合以上思考,Shopee Shake 零碎整体架构分为三层,即接入层、应用层和资源层。
- 接入层:负责接入用户申请,用户登录态校验及协定转换。
- 应用层:外围业务逻辑解决零碎,包含流动配置加载、道具模块、机会模块、库存模块,以及很多微服务,如排行榜服务、组团逻辑服务、获奖音讯。
- 资源层:次要蕴含 MySQL、Codis 以及 Shopee 中台服务,如告诉发送服务、金币发送服务、聊天服务。
2.2 高并发技术
为了应酬高并发的状况,Shopee Shake 的后端系统采纳了很多高并发技术。这些技术并不是在最后设计时就被采纳,而是通过一直的“踩坑 - 优化”,缓缓迭代而成。优化计划可能不是最优的计划,但都是联合产品个性,综合衡量失去的后果。下文将着重介绍几项次要的优化计划。
2.2.1 程度扩容
为了撑持大流量,咱们的游戏零碎须要反对程度扩容。只有反对程度扩容,就能够通过减少机器数量来进步零碎可承载的吞吐量。
零碎反对程度扩容,这就要求零碎的接入层、应用层和存储层都反对程度扩容。
其中,接入层和应用层能够做成无状态服务,以反对程度扩容。但存储层反对程度扩容是不容易的,须要将存储数据均匀分布在不同存储节点上。
Shopee Shake 零碎采纳 Codis 作为存储层,次要思考到了以下几个因素:
- 程度扩容:Codis 反对程度扩容,只有减少 Codis 集群的 Redis 实例数,即可做程度扩容。
- 高性能读写数据:游戏中的用户机会、金币库存、排行榜等数据在游戏过程中会被频繁读写,因而须要能反对高性能读写的存储系统。
- 数据无需长久化:游戏数据只在流动过程中应用,流动完结后不须要保留。
由下面几点能够看出,Codis 作为该游戏的存储层是十分适合的。
但并不是说只有应用了 Codis 作为存储层,就反对程度扩容,还须要想方法将数据均匀分布在不同的存储节点上。咱们最后设计游戏时并没有留神到这一点,以致于零碎在大流量压测的时候呈现了瓶颈。后经剖析发现,Codis 集群有一台 Redis 实例的 CPU 使用率达到 100%,起因是游戏的金币库存应用的 Codis key 是单点 key,超出了单台 Redis 实例性能下限。在流量较小的时候,这并不是问题,但放在大流量场景下,会导致整个零碎的性能瓶颈。
在 Shopee Shake 游戏中,金币库存供所有用户共享,所有用户端在游戏完结时都会并发查问及扣减这个库存值。当库存低于指定值时,游戏会提前结束,目标是避免库存超发。因而,金币库存值的读写十分频繁,而且要求数值准确牢靠。
应用单点 key 尽管能保障金币库存值是实时且牢靠的,但却会带来性能瓶颈。
咱们解决这个问题采纳的办法是 分桶——将一个金币库存拆分成多个分库存,不同用户去扣减不同分库存。这样做,就能够将单点 key 拆分成多个 key,不同 key 的流量则扩散到不同的 Redis 实例。因而存储层能承载的 QPS = N * 单台 Redis OPS
,N 为分库存个数。
另外,分库存的数量也能够随着流量的增长而增长。示意图如下:
应用分桶的办法,会导致一种异常情况——分库存扣减不均,局部用户会因库存有余而提前结束游戏,但实际上另一个分库仍有库存。这种状况不可能完全避免,在大流量场景下,会呈现大量扣减库存的申请,只有将用户流量尽量平均分到不同分库存,则能够大大降低呈现这种状况的概率。
采取分桶的办法,能够无效地进步零碎的吞吐量。而因而引入的分库存扣减不均的问题,在大流量状况下,也绝对能够承受。
2.2.2 缓存
对于很多面对高并发、大流量的零碎来说,缓存是一种罕用的技术,Shopee Shake 零碎也应用了大量缓存。
在游戏过程中,有很多数据是读多写少的,甚至是只读的,如游戏配置、动态资源等。这些读多写少的数据非常适合应用缓存。利用缓存能够极大地缓解数据库压力,也能缩小接口响应延时。
缓存有个漏斗模型:通过层层过滤后,透传到前面的零碎流量越来越低。所以设计游戏零碎时,思考到数据的读写频率,该当尽量将数据放到更靠近用户的缓存。
Shopee Shake 在每一层零碎都应用了缓存,如 Web 缓存、过程缓存、Codis 缓存。对于热点数据,须要提前缓存,这样做能够避免当缓存不存在时,大流量霎时冲击到数据库的状况。Web 静态数据则应用 CDN 边缘缓存,以此进步游戏加载速度。
零碎后端应用缓存时,须要解决另一个问题——缓存如何更新?对于流量较小的零碎来说,当缓存过期后,间接尝试从数据库获取数据,这是比拟常见的计划。但在大流量场景下,这种计划会导致大量申请间接申请数据库,会造成数据库受到极大的冲击,甚至导致“雪崩”景象。
解决这个问题,能够应用锁的机制:多个并发申请发现缓存过期,须要去查询数据库时,先去获取“更新缓存锁”,只有获取到“更新缓存锁”的申请才会去查询数据库,当查询数据库实现且更新缓存胜利后,开释“更新缓存锁”,而其余申请则进入期待状态,直到缓存被更新时,间接返回缓存。示意图如下:
但该计划存在一个弊病:如果呈现网络抖动或后端数据库出现异常,导致查问后端数据库耗时过长,会有大量读申请因而被阻塞,最终占满内存。
Shopee Shake 曾在一次压测过程中暴露出这一问题。过后,游戏服务内存在短时间内大幅增长,且接口延时变大。通过剖析,咱们最终发现是缓存更新时后端数据库出现异常,无奈及时响应,导致大量申请被阻塞。
为了解决这一问题,咱们采纳了无限等待时间的办法,如果在设定的等待时间内无奈获取到最新的缓存,则应用旧缓存。但这种解决方案有可能会导致数据不统一的状况。对于 Shopee Shake 零碎来说,须要查询数据库的数据都是游戏的根本配置,而这些配置在游戏开始后的批改频率是非常低的,所以无限等待时间的计划比拟合乎咱们的业务场景。
2.3 异步
在高并发场景下,为了进步接口性能,有时会将一些耗时高,但不须要阻塞主流程的操作放到音讯队列,缩小在线零碎的压力。同时应用另一个异步解决的服务,一直解决音讯队列的数据。Shopee Shake 零碎也采纳了这样的计划。
在一次压测过程中,咱们发现游戏零碎的游戏完结接口延时相比其余接口要大,而该接口次要用于接管用户摇动手机的次数、计算用户取得的金币数以及给用户发放金币,是整个游戏中最重要的写接口,会间接影响整个零碎的吞吐量。
通过性能剖析及代码剖析发现,游戏完结接口在以下两个性能上耗时最大,大概占接口总延时的 30%:
- 将用户游戏得分数写入排行榜;
- 给用户发放金币及发送获奖告诉。
对用户场景进行剖析并和产品经理探讨后,咱们梳理了游戏完结接口中所有能够异步解决的性能,并将这些性能异步解决。
能够异步解决的性能有以下几项:
- 用户游戏得分写入排行榜:查看排行榜的入口比拟深,申请流量比拟低,且同时有大量用户正在玩游戏时,排行榜数据变动很快,用户并不需要看到榜单实时变动过程,因而只需在肯定工夫内输入最终的排行榜数据即可。
- 发放金币:派发金币并不需要实时到账,这样的体验对于用户来说,是能够承受的。
- 发送获奖告诉:获奖告诉用于告知用户取得金币,不须要实时告诉。
- 数据埋点上报:上报用于离线剖析的游戏数据。离线剖析的数据不须要实时上报。
异步解决要解决两个问题:一是 不能失落音讯 ;二是 幂等,即反复生产音讯,要保障后果统一。
第一点是因为异步解决无奈实时感知音讯解决的后果,所以须要保障音讯不会失落。第二点的起因是,音讯队列的音讯有可能会呈现反复生产的问题,则须要保障反复音讯不会产生副作用。
对于不能失落音讯这点,应用现有成熟音讯队列中间件,如 Kafka,是能保障的。但存在音讯队列呈现故障或音讯队列容量满载,无奈写入新音讯的状况,这时须要提取日志,从新生成音讯,发送到异步解决服务。所以对于音讯队列,须要增强监控,及时发现故障及进行数据补发。
对于反复生产的问题,能够在每一次游戏生成全局惟一的 ID,作为申请 ID 传给派奖零碎,派奖零碎依据申请 ID 作为惟一键,派奖前先查问以后申请 ID 是否已经派过奖,确保同一个申请不会被反复解决。
由此可见,引入异步解决会减少零碎的复杂度,但也能无效进步接口的性能。
3. 容量布局
在每次大促前,咱们须要评估零碎容量是否足够,是否能反对当地经营人员预估的用户量。这时就须要对系统的容量进行布局。
容量布局次要由上面几个步骤组成:
- 单容器容量评估。该数据通过全链路压测取得。通过部署单容器容量,压测失去零碎的极限容量。单容器容量粒度准确到每个接口可承载的 QPS。
- 将当地经营人员预估的 PCU(最大同时在线用户数)与 QPS 进行转换,计算得出零碎须要承载最大的 QPS。
计算部署的容器数量。
容器数量 = 最大 QPS / 单容器容量
- 预留局部机动资源,避免突发流量
计算上游容量要求。如 Codis OPS、上游服务 QPS。
Codis OPS = 最大 QPS * 每申请 Codis 操作次数
- 计算得出的容量后,按需要容量理论部署,蕴含业务容器数及上游中间件及游戏的需要部署。部署胜利后,再做一次全链路压测,验证容量是否满足预估的 QPS 需要。
4. 平面监控
为了能实时察看服务运行状况,及时发现异常,团队要对系统进行全方位、平面的监控。平面监控包含接入层、应用层、资源层、硬件层等监控,每层监控指标各不一样。只有做到残缺的平面监控,能力及时发现潜在问题。
下表展现了不同档次的监控指标:
5. 大促预案
凡事预则立,不预则废。在大促过程中,面对突发状况,如何应答?
突发状况可能会在零碎任何一个环节呈现。因而,在梳理预案的时候,咱们会依据零碎的要害链路寻找链路上的要害节点,并针对要害节点制订预案。
常见的突发状况包含:流动配置谬误、接入层不稳固、服务本身呈现 bug、依赖的存储层服务不稳固、依赖的上游服务不稳固、依赖的中间件不稳固等等。
依据这些突发状况呈现的阶段不同,预案也相应地分为前置预案、应急预案和复原预案三种类型,有针对地开展解决。
例如:针对“流动配置谬误”的突发状况,咱们筹备了相应的前置预案,在大促流动开始前,查看大促游戏各项配置是否正确。
6. 故障演练
有了预案,并不代表就居安思危。这些预案在故障产生时是否真的无效?解决问题的人是否纯熟?沟通机制是否顺畅?咱们并不心愿线上真正呈现故障时才去验证这些问题,这样危险太大,老本太大。所以应在线上环境隔离实在流量的状况下,提前模仿各种可能产生的故障,来察看零碎的反馈和人员解决状况,以验证预期策略。
于是,在大促前咱们都会进行故障演练,以低成本的形式发现预案的有余,裸露零碎的问题,一直进步人员及零碎的能力。
6.1 人员分工
故障演练不仅仅蕴含突发状况的应答预案,也蕴含不同职能人员的分工。不同职能人员汇集职能内的责任,同时又能保障信息的无效流转,进步发现问题、执行预案的效率。
上述职能中,除了毁坏组是为故障演练而设,其余都是在实在大促流动中切实存在的。在每次大促时,相干职能的人员都会值班待命,随时解决大促过程中呈现的异常情况。
6.2 演练过程
制订好人员分工及应答流程后,应该如何进行演练?下文将从故障演练前、故障演练中和故障演练后三个阶段来开展介绍。
6.2.1 故障演练前
- 查看必备根底能力:流量注入及流量染色等能力,保障流量不影响实在用户;
- 确定故障演练范畴、环境:跟当地经营人员确定可演练的实在线上环境,确保没有实在流程,或施行流量隔离,不影响实在用户;
- 确定故障及应答预案:确定要演练的故障,制订好故障剧本,并针对故障制订相应预案;
- 告诉波及的内部人员:将可能的影响面提前告诉相应内部人员。
6.2.2 故障演练中
被染色的流量注入零碎的那一刻起,故障演练就正式拉开序幕。据上图所示,整个演练流程分为八个次要步骤:
- 毁坏组按故障剧本,注入故障到演练零碎;
- 定位组察看各指标,及时发现故障,进行初步定位,排查问题;
- 定位组汇总要害信息上报故障给决策组;
- 决策组收到故障报告,依据要害信息决定是否执行预案,并告知执行组决策后果,要求执行某个预案;
- 执行组收到决策组的决策后果,执行某个预案;
- 执行组将执行后果反馈给决策组;
- 决策组同步预案执行后果给定位组;
- 定位组察看预案执行后的指标是否复原,并将后果反馈给决策组及其他人员。
6.2.3 故障演练后
- 现场清理:如流量敞开、撤销故障、敞开预案、清理演练的数据等;
- 告诉相干人员演练完结;
- 演练报告与总结:包含是否达到预期指标、预案有无失效、是否有意料之外的情况产生,并对要害指标(业务指标、机器负载指标)收集演绎,整顿后续改良点。
7. 总结
本文从游戏的逻辑、零碎架构、应用的高并发技术,和团队的平面监控、大促前的容量布局、大促预案以及故障演练等方面介绍了小游戏 Shopee Shake 如何应答大促。
在大流量冲击下,零碎保持稳定须要靠系统性思维,不仅仅要思考零碎本身状况,更重要的是技术团队要一直总结教训,积攒实践经验,进步本身能力。面对大促,团队应该要做到以下几点:
- 要充沛理解业务的特点,设计出合乎业务特点的技术构架,并且全面思考高并发场景,综合使用高并发技术;
- 要充沛理解大促流量散布状况、流量预估,做出正当的容量布局;
- 要通过各种内部零碎,如监控、日志等理解零碎的运行状况,及时发现问题、解决问题,进步可用性;
- 要有应急预案思维,提前做好各要害节点的预案,多做故障演练,进步应答各种突发状况的解决能力。
Shopee Shake 的现有零碎及预案还有不少能够更加欠缺的中央。为进一步晋升游戏零碎在大促中的响应能力,后续咱们将持续在以下几方面做出优化:
- 零碎接入层减少限流机制,避免零碎过载而解体;
- 减少更多的预案,扩充预案覆盖面。有些预案因为零碎架构或内部起因导致无奈执行,后续也会继续改良;
- 减少故障演练次数及演练的场景,进步团队应答能力。
要想做到从容应对大促,并非能欲速不达,这须要技术团队平时一直积攒与磨难,一直进步技术水平及应急能力。大促路上,咱们仍需致力。
本文作者
Zhiwang,后端工程师,趣味方向为高并发分布式系统,来自 Shopee Games 团队。
本文首发于微信公众号“Shopee 技术团队”。