问题背景
目前在做的游戏项目,使用了HTTP短连接网关,登录鉴权方式则选择了 JWT 。得益于JWT的无状态特性,Gate网关可以非常容易地分布式部署/水平扩展,但存在多端登录的问题。
- 需求:单账号仅允许最后登录设备保持在线,旧设备需被踢下线。
- 挑战:JWT无状态特性无法主动失效旧Token,需额外机制实现账号互斥登录。
解决方案
比较容易想到维护一个字典 /映射 ,key为account,value为 jwt token,一个account始终只对应它最新签发的jwt token,gate就根据这个字典判断当前请求是否为该账号最新登录的用户,如果是,则正常处理消息,否则返回特定错误码,客户端收到错误码后弹窗提示”该账号已在其他设备登录“。而关于这个字典的存储,又有两种方案。
1 . Redis缓存
2. 所有Gate同步进程内缓存(每个Gate的本地缓存都有一份相同的字典)
方案对比:Redis缓存 vs 进程内缓存同步
维度 | Redis方案 | 进程内缓存同步 |
---|---|---|
数据一致性 | 强一致性(所有节点读取同一Redis实例) | 最终一致性(同步延迟可能导致短暂不一致) |
扩展性 | ✅ 新增节点无需同步,直连Redis即可 | ❌ 新节点需全量同步数据,规模大时开销高 |
故障恢复 | ✅ 数据持久化,重启后不丢失 | ❌ 节点宕机导致部分缓存丢失,需重新同步 |
实现复杂度 | ✅ 简单(依赖Redis原子操作,如SET+EXPIRE ) | ❌ 复杂(需实现分布式同步协议,如Gossip) |
性能 | ⚠️ 依赖Redis吞吐量(需集群优化) | ✅ 本地读取速度快,但同步有网络开销 |
外部依赖 | ❌ 需维护Redis高可用集群 | ✅ 无外部依赖,纯逻辑实现 |
关键设计细节
- Token有效性校验
- 流程:客户端请求携带Token → Gateway校验JWT签名 → 查询Redis/缓存中该账号的最新Token → 匹配则放行,否则返回
401-Kicked
错误码。 - 优化:Token生成时附加递增版本号(如时间戳),减少内存占用(仅存储版本号而非完整Token)。
- 流程:客户端请求携带Token → Gateway校验JWT签名 → 查询Redis/缓存中该账号的最新Token → 匹配则放行,否则返回
- 登录互斥逻辑
- 登录时:更新账号对应Token至Redis/广播同步到所有节点,旧Token立即失效。
- 客户端响应:旧设备收到
401-Kicked
时,客户端提示“账号异地登录”并跳转至登录页。
- 容灾与降级
- Redis方案:需部署Redis哨兵/集群,避免单点故障;故障时可短暂降级为允许多设备登录(牺牲安全性保可用性)。
- 进程内缓存:需设计数据恢复机制(如节点重启后从其他节点拉取全量数据)。
最后选择了Redis缓存作为落地方案。
