Redis原子自增必须用INCR而非PHP自增,因其服务端单线程保证原子性;ThinkPHP的Cache::inc()需确认配置为Redis驱动,初始化应避免在请求链路中get判断,key设计须防注入且一般不设过期。

Redis原子自增为什么必须用 INCR 而不是 PHP 自增
直接在 PHP 里读出来、+1、再写回去,看似简单,但并发一上来就丢数据。两个请求同时读到 100,各自加到 101 再存,最终只涨了 1 次。Redis 的 INCR 是服务端原子操作,底层由单线程保证执行不被打断,这才是计数器可靠的根基。
- ThinkPHP 自带的
Cache::inc()底层就是调INCR,但要注意它默认走的是文件缓存驱动,不是 Redis —— 必须确认配置已切到 Redis - 如果手动用
Redis::command('INCR', ['article:123:views']),要确保 key 命名唯一且可预测(比如带业务前缀 + ID) - 别用
SET+GET组合模拟自增,哪怕加了锁,在高并发下仍可能因网络延迟或异常中断导致漏计
ThinkPHP 配置 Redis 缓存驱动后 Cache::inc() 不生效的常见原因
配置没生效是最多见的问题,Cache::inc() 看似调成功了,但数据根本没进 Redis,还在本地文件里“假增”。
- 检查
config/cache.php中'default' => 'redis'是否设置,且'stores' => ['redis' => [...]]下的'host'、'port'、'database'全部正确(特别注意database默认是 0,改过的话要同步) - 运行
php think clear清掉配置缓存,否则改了 config 也不生效 - 用
Cache::store('redis')->inc('test')显式指定 store,快速验证是否真连上了 Redis;再用redis-cli执行get test看值是否存在
如何安全地初始化计数器并避免首次访问为 null
INCR 对不存在的 key 会自动设为 1,看起来很省事,但如果你需要初始值是 0 或其他值(比如从数据库读出历史浏览量),就不能依赖它自动创建。
- 首次访问时,先用
Cache::store('redis')->get('article:123:views')判断是否存在;若为null,再用Cache::store('redis')->set('article:123:views', $initValue)初始化 - 不要在
inc()前反复get()判断——这又回到非原子问题;初始化动作应放在数据写入或后台任务中,而非用户请求链路里 - 如果允许误差,可以跳过初始化,直接
inc(),然后用get()读值时把null当作 0 处理(前端或展示层兼容)
计数器 key 设计与过期策略的实际取舍
key 不只是拼个字符串,它决定了能否清理、是否冲突、会不会爆内存。
立即学习“PHP免费学习笔记(深入)”;
- 避免用用户输入直接拼 key(如
'view:' . $_GET['id']),防止注入或非法字符;用sprintf('article:%d:views', (int)$id)更稳妥 - 不要给计数器设过期时间(
EXPIRE)——除非你明确接受“过期后重置”。浏览量这种数据一旦过期丢失就不可逆 - 如果要做日粒度统计(比如“今天看了多少次”),才考虑带日期的 key:
article:123:views:20240520,并配合定时任务归档或合并
真正难的不是调通 INCR,而是想清楚这个计数器在整个生命周期里谁负责初始化、谁负责清理、崩了怎么对账。Redis 很快,但人容易在 key 命名和边界判断上卡半天。


















