打开网易新闻 查看精彩图片

2010年某个凌晨,Salvatore Sanfilippo在Redis 1.2版本里塞进去一个命令。当时没人想到,这个叫INCR的东西会在15年后成为每秒处理千万级请求的标配——而实现它,开发者只需要敲一行代码。

分布式系统里最讽刺的事:数数比算微积分还难。

两个进程同时读到"10",各自加1,都想写回"11"。结果你丢了数据,用户看了假统计,老板看了假报表。这种race condition(竞态条件)不是新手专利,Twitter早期就栽过,某次活动计数偏差直接让运营团队通宵返工。

Redis的解法粗暴得让人怀疑人生:既然并发读写会打架,那就别让它们同时发生。

单线程不是缺陷,是设计

单线程不是缺陷,是设计

Redis把所有命令塞进一个线程排队执行。INCR的读取-计算-写入被打包成原子操作,中间插不进任何其他命令。这相当于给每个数字配了个专属柜台柜员,而不是让顾客自己从货架上拿货、算账、再放回去。

对比传统方案:数据库行锁需要维护锁表,乐观锁需要版本号碰撞重试,分布式锁需要操心网络分区。INCR的代价是零——没有锁超时,没有重试风暴,没有脑裂。

打开网易新闻 查看精彩图片

一组数字能说明问题。某内容平台用INCR替换自研计数服务后,P99延迟从23ms降到0.8ms,CPU占用下降67%。不是优化,是降维。

INCR家族:你以为的1个命令,实际是5个

INCR家族:你以为的1个命令,实际是5个

基础版INCR每次加1,适合浏览量、点赞数这种"来一个算一个"的场景。但业务需求从来不这么乖。

INCRBY让你指定步长,比如统计带宽消耗每次加1024字节。DECR和DECRBY做减法,库存扣减、余额消费直接复用同一套原子语义。最冷门的是INCRBYFLOAT,浮点数计数——某金融科技公司用它做实时汇率累计,精度问题在Redis内部解决,业务代码不用碰BigDecimal的坑。

一个细节很多人漏掉:INCR对不存在的键会自动初始化为0。这意味着你不需要先SET再INCR,少一次网络往返,也少一个竞态窗口。

生产环境的三个暗礁

生产环境的三个暗礁

第一个坑是TTL。INCR创建的键默认永不过期,计数器跑一年能把内存撑爆。正确做法是在第一次INCR后判断返回值,如果是1就设过期时间——代码里那个if (count == 1)不是洁癖,是救命。

打开网易新闻 查看精彩图片

第二个坑是集群模式。Redis Cluster的key被哈希到不同槽位,如果想按用户ID分片统计全局总量,得用Hash Tag把相关key强制落到同一节点。否则你的"实时总榜"会变成跨节点聚合的分布式事务, latency直接起飞。

第三个坑最隐蔽:INCR返回的是增量后的值,不是增量本身。某电商系统曾用这个返回值做库存校验,结果高并发下两个请求都读到"还剩1件",都以为抢到了。应该用INCR配合判断returnedValue <= stockLimit,而不是等INCR完再查。

谁在靠这行代码吃饭

谁在靠这行代码吃饭

微博热搜的实时热度、抖音的播放进度条、滴滴的派单计数、Coinbase的成交笔数——底层全是INCR或其变体。不是因为它们技术选型保守,而是这个问题域里,简单方案就是最优解。

有个反直觉的事实:Redis作者antirez在2019年博客写过,INCR的实现代码15年没变过。单线程原子性这个设计决策,比后续所有性能优化都长寿。

你现在打开任意一个App,大概率有某个数字正在Redis里被INCR。它可能是你这条内容的阅读数,可能是某个红包的剩余个数,也可能是系统正在监控的QPS阈值。

问题是:你的计数场景,还在用锁吗?