问题背景
游戏项目使用 HTTP 短连接网关 + JWT 鉴权。JWT 无状态,网关可以轻松分布式部署,但随之而来的问题是多端登录:需求是单账号只允许最后登录的设备在线,旧设备要被踢下线。
挑战在于:JWT 无状态,服务端无法主动让旧 token 失效——必须引入额外机制实现账号互斥。
方案:账号 → 最新 Token 映射
核心思路:维护一张映射表,key 为账号,value 为该账号最新签发的 token(或版本号)。网关每收到请求:
验签通过 → 查映射表 → token 等于最新?放行
→ 否则返回 401-Kicked,客户端弹窗"该账号已在其他设备登录"
存储方案有两个:
方案 A:Redis 缓存
// 登录时写入最新 token(SET + EXPIRE 原子完成)
SET account:<uid> <token> EX 86400
// 请求时比较
token == GET account:<uid> ? 放行 : 踢下线
方案 B:所有网关进程内同步缓存

每个网关本地缓存一份完整映射,通过 Gossip 等协议同步。
方案对比
| 维度 | Redis 方案 | 进程内缓存同步 |
|---|---|---|
| 一致性 | 强一致(所有节点读同一 Redis) | 最终一致(同步延迟造成短暂不一致) |
| 扩展性 | ✅ 新节点直连 Redis 即可 | ❌ 新节点需全量同步,规模大开销高 |
| 故障恢复 | ✅ 持久化,重启不丢 | ❌ 节点宕机丢数据,需重新同步 |
| 实现复杂度 | ✅ 简单(SET+EXPIRE) | ❌ 需实现同步协议 |
| 性能 | 依赖 Redis 吞吐(需集群) | 本地读取快,但同步有网络开销 |
| 外部依赖 | 需维护 Redis 高可用 | 无外部依赖 |
关键设计细节
- 存版本号而非完整 token:token 里附加递增版本号(如签发时间戳),映射表只存版本号——内存占用小,比较更快。请求时校验 JWT 内的版本号与映射表一致;
- 登录互斥:新登录时更新映射(Redis SET / 广播同步),旧 token 立即失效;
- 客户端响应:收到 401-Kicked 弹”账号异地登录”并跳登录页;
- 容灾降级:Redis 故障时短暂降级为”允许多设备登录”(牺牲安全性保可用性),比全部拒绝更友好;
- 键过期:映射键设置与 token 有效期一致的 TTL,防止死账号数据堆积。
落地方案
最终选择了 Redis 缓存:实现简单、强一致、无扩展包袱,代价是引入一个 Redis 依赖(游戏网关本身常用 Redis,边际成本低)。


