incr 能防并发是因为 redis 单线程模型保证其原子性:读取、加1、写回不可分割,避免竞态;要求 value 必须为整数字符串,否则报错;带过期需条件设置(首次 incr 返回1时才 expire);推荐 key 前缀隔离与维度拆分防热点。

INCR 为什么能防并发?不是靠锁,是靠 Redis 单线程原子性
Redis 的 INCR 命令本身不依赖外部锁机制,它在服务端是原子执行的:读取当前值 → 加 1 → 写回,整个过程不可打断。哪怕 1000 个客户端同时发 INCR counter,最终结果一定是准确的 1000,不会出现“读到相同旧值、各自加 1、写回覆盖”这类竞态问题。
关键点在于:INCR 要求 key 对应的 value 必须是可解析为 64 位有符号整数的字符串(比如 "123"),否则会报错 (error) ERR value is not an integer or out of range。所以不能对一个刚用 SET 存了 JSON 字符串的 key 直接调 INCR。
计数器带过期时间的正确写法:INCR + EXPIRE 要分两步,但必须保证原子性
常见错误是先 INCR,再 EXPIRE,中间若服务崩溃或网络中断,key 就永久存在了。更稳妥的做法是:首次递增时才设过期,且用条件逻辑控制。
- 用
INCR获取当前计数值,如果返回1,说明这是该 key 的第一次写入,此时立刻执行EXPIRE key seconds - 不要用
SET key 0 EX 60初始化再INCR,因为SET和EXPIRE不是原子的;INCR自带初始化语义(key 不存在就当0处理) - Java 中用
redisTemplate.opsForValue().increment(key)后,需手动判断返回值是否为1,再调expire(key, 60, TimeUnit.SECONDS) - 如果用原生命令,可组合
EVAL脚本避免竞态,但多数场景下“先 INCR 再条件 EXPIRE”已足够安全
INCRBY 和 DECR 的使用边界:别在非数字 value 上硬用
INCRBY 和 DECR 同样要求目标 key 的 value 是合法整数字符串。它们不是“安全兜底”的命令 —— 如果你之前存的是 SET user:1001 '{"name":"a"}',后续任何 INCR 类命令都会失败。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
典型误用场景:
- 把计数器 key 和业务数据 key 混用(比如都用
user:1001),导致类型冲突 - 没清空测试环境残留 key,某个 key 值是
"init",一跑INCR就报错 - 用
GET拿到值后在客户端做加法再SET,彻底放弃原子性
建议给计数器 key 加统一前缀,比如 counter:sms:138****1234、counter:api:ip:192.168.1.100,和业务数据 key 物理隔离。
高并发下性能瓶颈不在 INCR,而在 key 设计和过期策略
INCR 本身极快(微秒级),但实际压测中发现卡顿,往往是因为:
- 所有请求打到同一个 key(如固定用
counter:total),形成单点热点;应按维度拆分,比如按用户、IP、接口路径哈希 - 大量 key 设置了相同过期时间(如全设 60 秒),到期时 Redis 集中清理,引发短时 CPU 尖峰;可用随机偏移,比如
EXPIRE key 60 + random(0, 10) - 没做连接复用或 pipeline,每个
INCR都走一次网络往返;高频场景建议用pipeline批量提交 - Redis 实例内存不足触发淘汰,
INCR可能因写入磁盘或阻塞而变慢
真正容易被忽略的是 key 的生命周期管理 —— 计数器 key 不是“用了就扔”,它可能长期堆积,尤其限流类场景(如每分钟请求次数)。定期扫描 + TTL 检查 + 按需清理,比单纯依赖自动过期更可控。










