一、 背景:一个 Token 的两难境地
在微服务或前后端分离架构中,Access Token是用户身份的唯一凭证。关于它的存储,我们面临两个看似矛盾的需求:
- 极速验证:每个接口请求(QPS 可能高达数万)都要验证 Token,延迟必须在毫秒级。
- 数据安全与管理:Token 关系到用户安全,不能丢;管理员需要在后台查询、踢人下线,需要复杂的查询能力。
解决方案:采用“Redis (热数据) + MySQL (冷数据)”的双存储架构。
二、 核心问题 1:后端鉴权时,该读 Redis 还是 MySQL?
结论:只读 Redis,绝不读 MySQL。
当用户发起请求GET /order/list并携带Bearer Token时,后端的拦截器(Filter)执行流程如下:
Redis 是“地铁闸机”:
- 拦截器直接去 Redis 查询该 Token。
- 有数据:放行。耗时< 1ms。
- 无数据:直接拦截,返回
401 Unauthorized。不进行MySQL 回源查询。
MySQL 是“户籍档案室”:
- MySQL 的作用不是用来给代码做实时鉴权的。
- 如果每个请求都查 MySQL,数据库连接池瞬间就会被耗尽,整个系统会因为鉴权逻辑而崩溃。
原则:在高频的鉴权场景下,Redis 是唯一的“权威(Authority)”。
三、 核心问题 2:Redis 和 MySQL 的一致性如何保持?
既然两边都存了,如何保证数据不打架?我们按 Token 的生命周期分为四个阶段来看:
1. 登录阶段(创建:双写)
当用户登录成功生成 Token 时:
- 动作 A:将 Token 信息插入MySQL(作为持久化备份,供管理员查询)。
- 动作 B:将 Token 信息写入Redis,并设置TTL (过期时间)(如 30 分钟)。
- 一致性评价:强一致。
2. 鉴权阶段(读取:Redis 主导)
- 动作:只读 Redis。
- 异常情况:如果 Redis 数据丢了(如宕机)但 MySQL 还在,怎么办?
- 策略:认栽。系统视作用户“未登录”,前端会触发刷新 Token 或重新登录流程。绝不回源查 DB,防止缓存击穿导致数据库雪崩。
3. 过期阶段(自然消亡:最终不一致)
- 动作:
- Redis:TTL 一到,自动删除 Key。用户访问报 401。
- MySQL:数据依然存在,变成了“历史垃圾数据”。
- 策略:允许不一致。这种不一致对业务无害(因为鉴权只看 Redis),MySQL 的历史数据可以通过定时任务清理。
4. 登出/踢人阶段(销毁:双删)
这是最关键的一步,必须保证安全。
- 动作:显式删除 MySQL+显式删除 Redis。
- 顺序建议:
删 DB->删 Redis(符合 Cache Aside Pattern)。 - 策略:必须确保 Redis 被删掉,否则用户在 Token 过期前还能非法访问。