首页/数据/Redis 进阶速查/计数器设计:INCR 与热点分摊
数据结构与选型42 条

计数器设计:INCR 与热点分摊

INCR/INCRBY 是原子计数基础;单键计数在集群中天然集中在一个分片,高并发计数可拆成多个子键再求和,或改用 Hash 把维度合并进一个键。

Redis 进阶速查数据更新于 2026-10-13

计数器与热点分摊

INCR 与 INCRBY 是 Redis 最常用的原子计数原语:键不存在时按 0 处理,操作在服务端单线程内完成,因此天然不会出现读改写丢失更新。局限不在原子性而在热点:一条计数键始终由同一个节点处理,在分片集群里就是单节点负载上限。高并发计数有两种常用解法,把计数拆成多个子键再求和,或把多个维度合并进一个 Hash 降低键数量。

基本信息

基础命令INCR key 加一,INCRBY key n 加指定整数,INCRBYFLOAT 支持小数
存储编码纯整数值的 String 使用 int 编码,内存开销很小
溢出行为按 64 位有符号整数回绕,需要上限保护时应自行判断
生命周期计数键通常需要配合 EXPIRE 控制,否则会长期累积
读取GET 单键取值,MGET 批量取多个子键后由应用求和

典型使用场景

  • 页面访问量与接口调用次数统计,按天建键并设置过期时间
  • 库存扣减与配额控制:INCR 后判断是否超限,超限则回退
  • 限流计数:固定窗口内累加,窗口到期自动重置
  • 任务进度跟踪:多个工作节点各自累加已完成数量,汇总方读取总量
  • 排行榜的原始分累加:先用计数键累积,定时写入 ZSet 生成榜单

基础与分片计数

基础与分片计数
redis-cli INCR page:view:home                 # 加一并返回新值
redis-cli EXPIRE page:view:home 86400         # 按天过期,避免键无限累积
redis-cli INCRBY page:view:home 10
# 分片计数:写入时按随机数分散到多个子键
redis-cli INCR page:view:home:{shard1}
redis-cli INCR page:view:home:{shard2}
redis-cli MGET page:view:home:{shard1} page:view:home:{shard2}   # 应用侧求和
redis-cli GET page:view:home

几种计数方案对照

方案优点代价
单键 INCR实现最简单,读值无额外步骤单键热点,分片集群里受单节点限制
拆分子键再求和写入压力分散到多个节点读取需合并,存在一致性窗口
Hash 合并维度键数量大幅下降,字段级原子递增单键规模上升,需要注意编码与阻塞
位图或 HLL内存极省,适合去重与基数只能给近似或布尔结果,无法精确累加
分片计数的读取语义:多条子键分别递增,读取时求和得到的是「某个时刻的近似总量」,求和过程中发生的写入可能落在窗口之外。对总额精度要求高的场景应把定期落库的持久值作为对账依据,Redis 计数只作实时展示。
给未知用途的键补 EXPIRE 前先确认它是纯计数键:误给业务主键设置过期时间会造成数据静默消失,且这类操作难以回滚。规范做法是计数键统一命名前缀,便于安全地批量治理 TTL。

易错点

  • 先 GET 再 INCR 的两步写法在并发下丢失更新,计数结果偏小且难以复现,必须直接使用 INCR 系列原子命令
  • 计数键不设过期时间,日积月累占用大量内存且无人清理,按天或按小时建键并设置 TTL 是常规做法
  • 依赖 INCR 做库存扣减却不处理回退与超限,并发下出现超卖或负数,判断与扣减应放在 Lua 脚本内完成
  • 分片计数后忘记在读取路径做求和,业务侧看到的是单个子键的值,量级明显偏小却查不出原因

数据来源与参考

  • Redis 官方文档:INCR / INCRBY 命令
  • Redis 官方文档:Key 过期时间相关命令
​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌