不能直接用 composer require predis/predis 或 phpredis/phpredis 得到开箱即用的原子计数器,因二者仅提供连接能力,计数逻辑、重试、过期、兜底需自行实现;推荐 orhanerday/redis-counter 或自建封装,它整合 incr/expire 等为原子操作,内置前缀、ttl、3次重试,且支持 laravel 连接复用与 lua 脚本兜底。

直接用 composer require predis/predis 或 composer require phpredis/phpredis 不能得到“原子计数器组件”——它只是提供了连接能力,真正的计数逻辑、重试机制、过期策略、错误兜底都得你自己写。想开箱即用的高性能原子计数器,得选对库、配对方式、再补上关键封装。
为什么不能直接 new Redis()->incr() 就完事
原生 Redis 类或 Predis\Client 确实能调 INCR,但生产环境里这远远不够:
- 没超时重试:网络抖动导致
INCR返回 false 或抛异常,业务就卡住 - 没键前缀隔离:多个模块共用一个 Redis 实例时,
INCR user:123和INCR order:456容易命名冲突 - 没自动过期:计数器如限流场景必须带
EXPIRE,但INCR本身不支持,得额外发命令,非原子 - 没返回值校验:比如
INCRBY溢出(Redis 里是 int64)后静默回绕,前端可能误判
推荐用 orhanerday/redis-counter 或自建轻量封装
社区里真正专注“计数器语义”的 Composer 库极少,orhanerday/redis-counter 是少数几个明确以原子计数为唯一目标的包(非缓存、非 session),它把 INCR/INCRBY/GET/EXPIRE 组合成原子操作,并内置了前缀、默认 TTL、失败重试(最多 3 次)。
- 安装:
composer require orhanerday/redis-counter - 初始化时传入
Predis\Client或Redis实例,不绑定具体驱动 - 用法简洁:
$counter = new RedisCounter($client, 'order:seq', 3600); $counter->incr();—— 自动带前缀、设过期、失败重试 - 注意:它不处理分布式锁场景(如“先查再 incr”),纯递增类计数器才适用
Laravel 项目里怎么接入并避免配置错位
很多人在 config/database.php 里配好 Redis,却在计数器代码里手动 new Client,导致连接池、密码、database index 全部不复用,还可能连错库。
- 优先从 Laravel 的
Redis::connection()拿连接实例,而不是 new Predis\Client - 若用
orhanerday/redis-counter,传参时用app('redis')->connection('default')(返回的是Predis\Client) - 别在 .env 里写
REDIS_PREFIX=counter:然后到处拼字符串——统一在封装层加前缀,配置和业务解耦 - 如果项目已用 phpredis 扩展,
app('redis')->connection()返回的是Redis对象,而该库只认Predis\Client,此时需换用自建封装或改用phpredis/phpredis+ 手动组合eval脚本保证原子性
高并发下必须用 Lua 脚本兜底的场景
当计数器要实现“条件递增”(例如库存扣减:仅当当前值 ≥ 1 才 DECR)或“读-改-写”复合逻辑时,单靠 INCR 不够,必须用 EVAL 原子执行 Lua。
- 示例脚本:
EVAL "if redis.call('GET', KEYS[1]) >= ARGV[1] then return redis.call('DECR', KEYS[1]) else return -1 end" 1 stock:1001 1 - 不要手拼 Lua 字符串——用
Redis::eval()(Laravel)或$client->eval()(Predis)传入预编译脚本哈希,减少网络开销 - 注意:phpredis 扩展对 Lua 支持更稳;Predis 在 pipeline 中 eval 可能丢上下文,建议单次调用
- 线上压测发现,纯
INCRQPS 能到 8w+,但带 Lua 的复合操作会掉到 2w 左右,这点吞吐差异得提前评估
真正难的不是“怎么调 INCR”,而是“怎么让 INCR 在失败时不拖垮业务、在扩容时不改一行代码、在审计时能追溯每次变更”。计数器的可靠性藏在重试策略、前缀管理、Lua 封装和连接复用里,而不是那一行 $redis->incr($key)。











