
问题背景
游戏项目用 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,边际成本低)。


