游戏登录排队( 限流)
文章目录
- 游戏登录排队( 限流)
- 前言
- 为什么要排队(限流)
- 限流算法
- 固定窗口实现的登录限流流程图:
- 代码实现
- 生产环境下的限流组件
- 参考
前言
能看到这篇帖子的兄弟,可能是想实现一个游戏登录排队的功能。游戏的登录排队机制,其实就是 服务的限流 、削峰机制,如果 你使用 “限流” 关键词可能会搜索到更多的相关资料。
为什么要排队(限流)
最近我司游戏要上线了,目前全平台总预约量有600多万。服务器目前的配额,压测目标是,允许250万玩家同时在线的情况下,服务依然稳定。但是游戏上线开服第一天,假设所有玩家(假设是压测的250万玩家)第一时间注册上线,那么带来的 DB写操作,将是噩梦。服务器本身可以支持250w玩家同时在线,但是db并不能满足250w玩家短时间的写操作。玩家注册操作的写db操作比较重。 所以就需要削峰、限流。也就是这里的登录排队。
假设数据库写操作最大支持 5000 次/秒,玩家注册时写 db 频率是 2 次/秒。那么 db能够承受的玩家登入速度 是 2500 人/秒。所以如果 一秒钟登录的用户超过2500人后,后来的用户登录就需要排队,至少要等到下一秒才能允许登录。
限流算法
- 固定窗口:
- 滑动窗口:
- 漏斗桶算法:
- 令牌桶算法:
固定窗口和滑动窗口都是计数法。
服务器架构图:
为了方便理解,这里先介绍一下我的项目简易架构图。
- 项目采用了身份认证系统与游戏业务系统分离,客户端(手机)使用http请求向 login服发起 身份认证请求。如果认证通过,login会在回包中告诉 客户端 gateway地址。
- 客户端获得gateway地址后,会去连接对应的gateway。 进入游戏逻辑。
- 客户端与 game服(游戏服)器通信必须进过gateway服。
登录排队机制应该在哪一步?
- 在client->login 的过程中
- 在 client -> gateway的过程中
不忘初心:前面“为什么需要限流” 中说明了,是因为服务器db扛不住压力,所以排队操作一般会放在数据库操作之前。
这里我们采用了方案一, 在login服上进行排队。登录验证使用的HTTP协议。短链接的HTTP 会有一个麻烦,当玩家排队结束时(轮到玩家登录了),服务器不能够主动告诉客户端 “到你了,轮到你登录了”。所以服务器只能在每次客户端登录请求的回复中 带上 一个等待时间。告诉客户端还需要等多久。 客户端收到这个时间后,自己在等待时间结束后重试。
如果采用方案二,客户端与服务器是长链接,可以在客户端的排队等待时间结束后,主动告诉客户端。(TCP是全双工通信,双方都可以发主动发送消息、接收消息。)
为什么要使用reids实现?
其实就是计数法,这里采用的固定窗口算法。每个时间片有一个计数。但是现在服务端是分布式的,会有多个login进程,它们可能就不在一个机器上。所以这个 计数 必须放在所有进程都可以访问的地方,这里就是采用的redis了。所有进程上的排队逻辑都访问同一个reids仓库,进行计数。redis实现的分布式锁也是这个原理,把锁放在所有进程都可以访问的redis上。
我实现的登录排队系统的功能:
- 固定时间片中只允许一定数量(窗口大小)的玩家登录
- 时间片和窗口大小可配置
- 玩家排队时,告诉客户端当前排名和等待时间
- 排队系统开启关闭可配置
固定窗口实现的登录限流流程图:
Created with Raphaël 2.2.0 start 获取当前时间片 是否在登录队列 获取在登录队列的排名 否有剩余窗口? 抢占登录名额是否成功 登录成功 end 返回等待时间,登录等待 加入登录队列 yes no yes no yes no
代码实现
生产环境下的限流组件
一般而言我们不需要自己实现限流算法来达到限流的目的,不管是接入层限流还是细粒度的接口限流其实都有现成的轮子使用,其实现也是用了上述我们所说的限流算法。
比如Google Guava
提供的限流工具类 RateLimiter
,是基于令牌桶实现的,并且扩展了算法,支持预热功能。
阿里开源的限流框架Sentinel
中的匀速排队限流策略,就采用了漏桶算法。
Nginx 中的限流模块 limit_req_zone
,采用了漏桶算法,还有 OpenResty 中的 resty.limit.req
库等等。
参考
限流算法概述springboot限流机制