06 六个常用 Redis 业务功能
六个常用功能,学会迁移思路
Section titled “六个常用功能,学会迁移思路”学习目标 · 能为验证码、登录态、点赞、排行榜、计数和限流选择 Key、命令及失败策略。
6.1 验证码:短期状态,加一次性消费
Section titled “6.1 验证码:短期状态,加一次性消费”验证码由服务端用安全随机数生成,设置 2–5 分钟有效期只是示例,具体由业务决定。不要记日志,也不要把真实手机号直接暴露在 Key 中。可用 auth:otp:login:手机号摘要 为 Key,Value 保存服务端带密钥的验证码摘要,避免六位数字普通哈希被快速枚举。验证码发送与校验都要限流,校验次数要有上限。
校验成功后消费凭证,避免重复使用。GET、Java 比较、DEL 三步之间有竞态;下面用 Lua 在服务端比较并删除,ARGV[1] 是后端算好的待比对摘要。
1 代表成功且消费;0 代表不存在或过期;-1 代表不匹配。这个脚本不包含发送流程和次数限制,要结合后面的限流逻辑;真实校验失败达到上限应锁定本轮验证码。Redis 故障时不能把校验结果当成功。验证码消费后用户创建失败,需重新验证或设计幂等流程,不假装两种存储已经处于同一个事务。
6.2 登录态:Token 是索引,不是用户 ID
Section titled “6.2 登录态:Token 是索引,不是用户 ID”服务端认证成功后生成难以猜测的随机 Token,Redis 保存 auth:session:token摘要 → 最小用户信息,配置 TTL。浏览器每次请求携带 Token,后端查询登录态,再检查具体资源权限。退出时删除对应 Key。
如果需要滑动过期,可在合法请求时用 GETEX(Java 的 getAndExpire)读取并续期,另设绝对最长会话期限;避免把不存在的会话凭空创建回来。Token 不放 URL;根据跨站需求选择 HttpOnly/Secure Cookie 并处理 CSRF,或合理的 Authorization 方案。纯 JWT 不一定需要每次查 Redis,但注销、撤销和权限即时变化是另一套取舍。
6.3 点赞:不要“先判断,再决定加减”
Section titled “6.3 点赞:不要“先判断,再决定加减””上面三行是三个独立操作示例,不是一次请求全部执行。将“点赞”和“取消点赞”设计为两个明确动作:SADD / SREM 重复执行都不会累积错误。一个“切换点赞”接口重试两次会把状态翻回去。热门内容的成员集合要控制规模、按需求分页;长期点赞事实可落数据库并通过唯一约束保证用户与内容关系唯一,Redis 负责加速,双方同步需单独设计。
6.4 排行榜:累计积分与展示分离
Section titled “6.4 排行榜:累计积分与展示分离”按赛季或周划分 Key,并设置归档和过期策略。积分更新事件如果重投,ZINCRBY 会重复加分;需要事件 ID 去重,或以事实分数执行 ZADD。若通过“去重 Key + 更新积分”做 Lua 原子处理,在 Cluster 中相关 Key 还必须同槽,且去重信息保留期限不能短于重放窗口。
6.5 计数:INCR 解决并发,不解决重复请求
Section titled “6.5 计数:INCR 解决并发,不解决重复请求”两个请求都 GET 到 10,再 SET 11,最后只增加一次。INCR 将读取、增加、写入合为一个操作,两个成功请求后为 12。但是客户端超时后重试 INCR,有可能第一次已执行,导致多加一次。原子性与幂等性是不同问题。 普通 PV 可以接受一定统计误差,账务扣减不能套同一策略。
6.6 固定窗口限流:用短脚本绑定计数与过期
Section titled “6.6 固定窗口限流:用短脚本绑定计数与过期”下面表示每个 Key 从首次请求开始的一段窗口,窗口内最多放行 limit 次;不是精确对齐自然分钟。参数由后端配置校验,不能让终端用户任意提供。
脚本只使用一种受控类型的 Key,参数先校验再写入。1 放行,0 拒绝。首次 INCR 与 EXPIRE 在一段脚本中执行,消除应用两次调用之间退出的窗口;Lua 并不提供运行错误后的回滚。
先不用 Java,也可以验证脚本。 把上方 Lua 正文保存为当前文件夹的 limit.lua(不是 limit.lua.txt),然后在 PowerShell 执行下面两行。第一行把练习文件复制进学习容器,第二行运行脚本;逗号前是 Key,后面是参数,逗号两边要有空格。在 10 秒内连续执行第二行 4 次,观察返回值。
实际项目将脚本对象复用为 Bean 或常量,避免每次请求创建。身份应取自可信鉴权信息;匿名接口可按 IP 等维度,但需处理代理与共享出口。Redis 超时时,登录/验证码类保护优先拒绝或采用严格本地兜底;普通浏览可用较宽松本地限额,必须事先明确策略。
| 算法 | 适合 | 局限 |
|---|---|---|
| 固定窗口 | 简单 API 次数上限 | 窗口交界附近可能集中放行,短时间接近两倍限额 |
| 滑动窗口 | 更平滑地控制近期请求数 | 常用 ZSet,需裁剪历史和控制成员量 |
| 令牌桶 | 允许受控突发、限制长期速率 | 需要计算补充令牌,校准时间和突发上限 |
- 用明文用户 ID 当登录 Token,任何人都能猜到。
- 用 Redis 的过期与淘汰机制代替业务确认,导致关键状态意外失效。
- 自动重试所有写操作,认为“单命令原子”就不怕重复。
用 learn:limit:test 测试 10 秒窗口最多 3 次,连续运行限流脚本 4 次;等待窗口结束再试。再分析:点赞重复请求、排行榜加分重复请求,哪一个天然幂等?
自测与答案 · 限流为什么不每次请求都 EXPIRE?
每次刷新 TTL 会让高频请求一直延长窗口,变成另一种语义。当前脚本只在第一次计数为 1 时设置过期,四次预期 1、1、1、0,过期后再次允许。明确的 SADD 点赞幂等,ZINCRBY 加分不幂等。限流脚本要测试非法参数不会先写入数据;不要把计数 Key 与其他用途混用。
依据:INCR 与限流模式 · GETEX · Lua 脚本