Redis INCR命令深度解析:高并发计数器原理与实战避坑
2026/9/17 17:56:11 网站建设 项目流程

1. 项目概述:为什么一个简单的INCR命令,值得我们花一整篇笔记去深挖?

你有没有遇到过这样的场景:电商大促秒杀页面,库存数字在用户疯狂点击下必须实时扣减,且绝不能出现“超卖”;或者直播打赏榜单,每个用户的点赞数、礼物数要毫秒级更新,成千上万并发请求同时涌向同一个 key;又或者一个短链服务,每生成一次访问都要给目标 URL 的访问计数器加 1,而这个短链可能被嵌入到千万级流量的公众号推文里——这些场景背后,都站着一个看似朴素、实则扛起高并发重压的核心指令:Redis 的INCR

它不是什么炫酷的新特性,也不是某个复杂框架的封装,就是一条命令:INCR counter:order:20240520。但正是这条命令,成了无数高并发系统里最沉默也最可靠的“计数中枢”。它解决的不是一个功能问题,而是一个工程底线问题:当并发量从几百飙升到几万甚至几十万 QPS 时,如何保证一个数字的增减既快、又准、又不丢?这背后牵扯的,是 Redis 单线程模型的精妙设计、内存操作的极致优化、原子性语义的底层保障,以及——更重要的是——开发者对“原子性”二字真正含义的深刻理解。

很多人以为“原子性”就是“不可分割”,于是放心大胆地用INCR去做订单号生成、库存扣减、甚至分布式 ID 分配。但现实很骨感:我亲眼见过一个用INCR实现的“全局唯一订单号生成器”,在压测时因网络抖动导致客户端重试逻辑缺陷,最终生成了重复 ID;也调试过一个用INCR做“用户今日签到次数”的接口,在 Redis 主从切换瞬间,因为INCR命令的异步复制特性,导致从节点短暂返回了旧值,前端显示“您已签到”,后端却因幂等校验失败而拒绝了实际签到动作。这些都不是INCR的 bug,而是我们对它能力边界的误判。

所以这篇笔记,不讲怎么安装 Redis,也不罗列所有数据类型。我们就死磕INCR这一个命令,把它放在高并发、原子性、计数器这三个关键词的聚光灯下,一层层剥开它的皮囊:它到底在 CPU 和内存里做了什么?为什么它能扛住每秒十万次的递增?它的原子性边界在哪里?哪些场景它能稳如泰山,哪些场景它会悄悄埋雷?以及,当业务规模再往上走,INCR的单点瓶颈又该如何破局?如果你正在设计一个需要实时计数的系统,或者正为线上偶发的计数错乱而焦头烂额,那么接下来的内容,就是你该抄在小本本上的实战手册。

2. 核心原理拆解:INCR不是魔法,它是 Redis 单线程模型下的精密齿轮

2.1 从一条命令到一次内存操作:INCR的完整生命周期

当你在 Redis 客户端输入INCR counter:page:views并按下回车,这条命令的旅程远比你想象的更短、更直接。它不像数据库 SQL 那样需要经过复杂的解析、优化、执行计划生成。在 Redis 里,INCR是一个被硬编码进核心命令表(redisCommandTable)的原生命令,其执行路径可以被极度简化为三个阶段:

  1. 网络层接收与解析:Redis 的主线程(event loop)通过epoll(Linux)或kqueue(macOS)监听 socket 事件。当客户端数据包到达,主线程将其读入缓冲区,并调用processInputBuffer进行协议解析(RESP 协议)。对于INCR,解析结果就是一个redisCommand结构体,其中c->argv[0]"INCR"c->argv[1]"counter:page:views"

  2. 键查找与类型校验:Redis 调用lookupKeyWrite在全局哈希表server.db[i].dict中查找counter:page:views对应的redisObject。如果 key 不存在,Redis 会根据配置(auto-create)决定是否创建一个新对象;如果存在,则检查其type是否为REDIS_STRING。如果不是(比如是个 list 或 hash),则立即返回(error) ERR value is not an integer or out of range。这一步非常关键——INCR只作用于字符串类型的 key,且该字符串必须能被解析为一个 64 位有符号整数。

  3. 原子性内存操作与返回:这是INCR的灵魂所在。Redis 直接调用getLongLongFromObjectOrReplyredisObjectptr(指向 SDS 字符串)转换为long long类型的数值。然后,执行val++(C 语言的自增操作),再将新值通过addReplyLongLong(c, val)序列化为 RESP 协议格式,写入客户端输出缓冲区c->bufc->reply。整个过程,全部发生在主线程的单次事件循环内,没有上下文切换,没有锁竞争,没有 I/O 等待。

提示:这里没有“事务”概念,也没有“锁”。INCR的原子性,源于 Redis 的单线程模型。在任意时刻,只有一个命令在执行,因此对内存中同一个变量的读-改-写(Read-Modify-Write)操作,天然就是原子的。这不是靠加锁实现的“逻辑原子性”,而是由执行模型保证的“物理原子性”。

2.2 为什么单线程反而能撑起高并发?CPU 缓存行与内存屏障的隐秘协作

很多人第一反应是:“单线程?那不是性能瓶颈吗?” 这恰恰是 Redis 设计最反直觉也最精妙的地方。我们来算一笔账:假设一台现代服务器 CPU 主频为 3.0 GHz,即每秒可执行约 30 亿条简单指令。INCR的核心操作(读内存、加 1、写内存)在汇编层面,通常只需要不到 10 条指令。这意味着,理论上,单线程每秒可以处理3 亿次INCR操作。当然,现实中有网络 I/O、协议解析、内存分配等开销,但即便打个 10% 的折扣,3000 万 QPS 依然是一个惊人的数字。

而真正的瓶颈,从来不在 CPU 指令执行上,而在内存带宽和缓存一致性上。当大量INCR命令反复操作同一个 key 时,该 key 对应的内存地址会成为热点。CPU 的 L1/L2 缓存会努力将这块数据保留在高速缓存中。但问题来了:如果多个 CPU 核心(虽然 Redis 主线程只在一个核上跑,但网络中断、后台线程等会占用其他核)都在访问同一块内存,就需要通过MESI 协议来维护缓存一致性。每次INCR写入,都会触发一次“缓存行失效”(Cache Line Invalidation),迫使其他核的缓存副本作废,这会产生可观的延迟。

Redis 的应对策略是极致的“内存友好”:

  • 紧凑的数据结构redisObject+sds的组合,让一个整数 key 的内存布局极其紧凑,通常不超过 64 字节,完美适配一个 CPU 缓存行(64 字节)。
  • 避免指针跳跃INCR操作全程在redisObject和其ptr指向的 SDS 内存块内完成,没有跨 cache line 的指针跳转,极大减少了 cache miss。
  • 无锁编程:整个过程不涉及任何pthread_mutex_lockatomic操作,避免了锁带来的 cacheline bouncing(缓存行在多核间来回传递)。

注意:INCR的高性能,是 Redis 整体架构协同的结果。它依赖于高效的内存分配器(jemalloc)、零拷贝的网络 I/O(sendfile,splice)、以及事件驱动的非阻塞模型。单独把INCR拿出来,它只是一个普通的 C 函数;但把它放进 Redis 这个精密的单线程引擎里,它就成了高并发的基石。

2.3INCR的原子性边界:它能保证什么,又不能保证什么?

这是最容易被误解的一点。“原子性”这个词,在不同语境下含义不同。INCR提供的是一种命令级别的原子性(Command-level Atomicity),具体来说,它保证:

  • 单个INCR命令的执行是不可分割的:从读取旧值、加 1、写入新值、到返回结果,这四步作为一个整体,要么全部完成,要么完全不发生。不会出现“只读了没写”或“写了没返回”的中间状态。
  • 对同一个 key 的并发INCR是线性一致的:假设有 100 个客户端同时对counter执行INCR,初始值为 0。最终结果一定是 100,且每一个INCR返回的值都是唯一的、递增的(1, 2, 3, ..., 100)。这是单线程模型的必然结果。

但它绝不保证以下几点:

  • 跨命令的原子性INCR本身不提供事务。如果你需要“先INCR,再EXPIRE”,这两个命令之间没有任何隔离。在它们之间,另一个客户端完全可以GET到旧值,或者执行DEL删除这个 key。这就是为什么 Redis 提供了INCRBYINCRBYFLOAT等变种,以及WATCH+MULTI/EXEC的事务机制,但后者在高并发下性能损耗巨大,通常不推荐用于计数器场景。
  • 主从复制的强一致性INCR命令会先在主节点执行并返回结果,然后才异步地将命令本身(而不是结果)发送给从节点。这意味着,在主从同步的微小窗口期(通常是毫秒级),从节点上的值会落后于主节点。如果你的应用读写分离,且从节点读取计数器,就可能看到“滞后”的值。
  • 持久化的即时性INCR修改的是内存中的值。RDB 快照和 AOF 日志都是异步刷盘的。如果 Redis 在INCR后、AOF 写入前崩溃,这次递增就会丢失。对于计数器这种“丢了就丢了”的场景,这通常是可接受的;但对于订单号这种“绝对不能丢”的场景,就必须结合AOF fsync always或者更严格的方案。

实操心得:我在一个金融风控系统里,曾用INCR记录某类风险事件的当日发生次数。上线后发现,每天凌晨 0 点的统计报表,总比实时监控面板少几条。排查后发现,是因为INCR后没有立刻BGREWRITEAOF,而 RDB 快照恰好在 0 点前几分钟生成,导致最后几分钟的增量未被持久化。解决方案很简单:对这类关键计数器,启用appendonly yesappendfsync everysec,并在业务低峰期手动触发BGREWRITEAOF。记住,INCR的原子性,只负责“内存里不出错”,不负责“硬盘上不丢数据”。

3. 高并发场景下的实操要点与避坑指南

3.1 性能压测:如何真实测出你的INCR瓶颈?

别信网上的 benchmark 数据,你的网络、你的 Redis 版本、你的硬件、你的客户端库,共同决定了你的真实上限。我推荐一套极简但有效的压测方法:

  1. 环境准备:使用redis-benchmark工具,它本身就是 Redis 源码的一部分,最贴近真实场景。

    # 测试单 key INCR 的极限 QPS redis-benchmark -h 127.0.0.1 -p 6379 -n 1000000 -c 100 -t incr -r 100000000 # -n: 总请求数;-c: 并发连接数;-t incr: 只测试 incr 命令;-r: 随机 key 的范围,这里设为 1 亿,确保几乎不命中同一个 key
  2. 关键指标解读

    • Requests per second:这是最直观的吞吐量。在我的 8 核 16G 云服务器上,单 keyINCR能稳定跑到 12-15 万 QPS。
    • Latency (ms):看50%,95%,99%的延迟。如果99%延迟超过 5ms,说明你的网络或 Redis 配置可能有问题(比如tcp-backlog太小)。
    • Failed requests:如果有失败,基本是连接数打满了(maxclients限制)或内存不足。
  3. 模拟真实业务压力:单 key 测试只是理论峰值。真实业务往往是“少量热 key + 大量冷 key”。这时,你需要测试INCRGETEXPIRE的混合负载:

    # 混合命令压测:70% INCR, 20% GET, 10% EXPIRE redis-benchmark -h 127.0.0.1 -p 6379 -n 1000000 -c 100 -q -r 100000000 \ -e "incr __rand_int__" \ -e "get __rand_int__" \ -e "expire __rand_int__ 3600"

注意:redis-benchmark默认使用 pipeline,这会极大提升 QPS,但掩盖了单命令的真实延迟。如果要测单命令延迟,务必加上-P 1参数(pipeline size = 1)。很多团队用默认 pipeline 测出 50 万 QPS,结果上线后单请求延迟飙升,就是因为没看清这个参数。

3.2 热点 Key 问题:当所有流量都涌向同一个counter:total

这是INCR在高并发下最经典的陷阱。想象一个全站 PV 统计,所有页面都执行INCR counter:total。随着 QPS 上升,这个 key 会成为整个 Redis 实例的“木桶短板”。

现象:Redis CPU 使用率飙升至 100%,INFO commandstats显示cmdstat_incrcalls暴涨,但usec_per_call也显著升高,latency doctor报告command类型的延迟异常。

根本原因:所有INCR请求都排队等待同一个 CPU 核心(Redis 主线程)的处理。虽然单线程避免了锁竞争,但无法避免队列等待。

解决方案不是“换技术”,而是“分而治之”

  • 方案一:分片计数器(Sharded Counter)
    将一个逻辑计数器,拆分成 N 个物理计数器。例如,counter:total拆成counter:total:0,counter:total:1, ...,counter:total:9。客户端在INCR时,用hash(key) % N决定操作哪个分片。最终总数,通过MGET获取所有分片值再求和。N 的选择很关键:太小(如 N=2)分摊效果差;太大(如 N=1000)会导致MGET的网络开销和聚合计算成本上升。我通常建议从 N=16 开始,根据压测结果调整。

  • 方案二:本地缓存 + 批量写入(Local Cache + Batch Flush)
    在应用层(如 Java 的ConcurrentHashMap或 Go 的sync.Map)维护一个本地计数器。每次请求,先对本地计数器incrementAndGet(),然后定时(如每秒)或定量(如累计 1000 次)将本地增量INCRBY到 Redis。这极大地降低了 Redis 的 QPS,代价是计数器的“实时性”下降为秒级。适用于 PV、UV 等对精确度要求不苛刻的场景。

  • 方案三:Redis Cluster 分片
    如果你已经使用 Redis Cluster,那么天然就具备分片能力。只要确保你的计数器 key 的 hash tag(如{counter:total})能让所有相关 key 落在同一个 slot,就可以利用集群的水平扩展能力。但要注意,INCR本身不支持跨 slot 操作,所以MGET聚合依然需要客户端完成。

实操心得:在一个社交 App 的“热门话题榜”项目中,我们最初用单 keyINCR记录每个话题的热度。当一个明星八卦话题爆发时,QPS 瞬间突破 8 万,Redis 主节点 CPU 拉满,整个排行榜接口超时。我们紧急上线了分片方案(N=32),并将INCR改为INCRBY(每次加一个随机小值,如 1~5),进一步平滑了请求分布。上线后,CPU 降至 30%,延迟稳定在 0.5ms 以内。记住,没有银弹,只有权衡:分片带来复杂性,本地缓存牺牲实时性,Cluster 增加运维成本。选哪个,取决于你的业务 SLA。

3.3 数据安全与可靠性:INCR不是万能的“保险柜”

INCR的原子性,只保证了“内存里不出错”。但生产环境的不确定性远不止于此。我们必须为各种“意外”做好预案。

风险类型影响应对方案
Redis 进程崩溃INCR后未持久化的数据丢失启用AOF持久化,设置appendfsync everysec。对极端重要数据,考虑always(性能损失约 2x)。
主从切换切换瞬间,从节点可能返回旧值;若主节点在切换前崩溃,未同步的INCR丢失避免读写分离的计数器查询;或使用WAIT命令(WAIT 1 1000)强制等待至少 1 个从节点确认。
网络分区客户端与 Redis 之间的连接断开,INCR命令未送达或响应未收到客户端必须实现幂等重试。关键:重试前,先GET当前值,判断是否已成功。避免盲目重试导致重复计数。
误操作DEL运维人员手抖DEL counter:total,计数器归零为关键计数器设置EXPIRE(即使很长,如 365 天),并配合KEYS命令的禁用和rename-command重命名DEL

提示:WAIT命令是 Redis 3.0+ 引入的,它可以让客户端阻塞,直到指定数量的从节点确认收到了当前命令。WAIT 1 1000表示等待至少 1 个从节点确认,超时时间为 1000 毫秒。这能在主从切换时,极大提高数据的强一致性,但会增加客户端延迟。是否启用,需在一致性与性能间做取舍。

4. 进阶应用与架构演进:当INCR不再够用时

4.1 从INCR到分布式 ID 生成器:Snowflake 的 Redis 替代方案

Twitter 的 Snowflake 算法是分布式 ID 的经典方案,但它依赖时间戳和机器 ID,对系统时钟漂移敏感。而INCR提供了一种更简单、更可靠的替代思路:基于 Redis 的全局序列号生成器

核心思想:用INCR生成一个全局递增的 long 型数字,然后通过一定的规则(如拼接时间戳、机器标识)将其“包装”成一个符合业务需求的 ID。

import redis import time class RedisIdGenerator: def __init__(self, redis_client, key="global:seq", step=1000): self.redis = redis_client self.key = key # step 是每次预分配的 ID 数量,减少对 Redis 的频繁调用 self.step = step self.current_range_start = 0 self.current_range_end = 0 self.local_counter = 0 def next_id(self): if self.local_counter >= self.current_range_end: # 需要向 Redis 申请新的号段 # 使用 INCRBY 一次性获取 step 个 ID new_start = self.redis.incrby(self.key, self.step) self.current_range_start = new_start - self.step + 1 self.current_range_end = new_start self.local_counter = self.current_range_start id_val = self.local_counter self.local_counter += 1 # 将 long 型 ID 包装成业务 ID,例如:20240520123456789 return int(f"{int(time.time())}{id_val % 100000000:08d}") # 使用 r = redis.Redis() gen = RedisIdGenerator(r) print(gen.next_id()) # 168456789012345678

这个方案的优势在于:

  • 绝对单调递增INCRBY的原子性保证了号段的严格顺序。
  • 高可用:只要 Redis 集群可用,ID 生成就可用。
  • 无单点故障:可以部署 Redis Sentinel 或 Cluster,ID 生成器本身是无状态的。

它的局限性也很明显:

  • 依赖 Redis:Redis 成为整个系统的单点瓶颈。
  • ID 无时间信息:纯数字 ID,不像 Snowflake 那样自带时间戳,排序需额外字段。

实操心得:我们在一个物流轨迹系统中采用了此方案。为了解决 Redis 单点问题,我们部署了一个三节点的 Redis Sentinel 集群,并将INCRBYstep设置为 10000。实测下来,单个 ID 生成器实例每秒能稳定生成 5 万个 ID,完全满足日均 10 亿轨迹点的写入需求。关键经验是:step的大小,是性能与容灾能力的平衡点step太小,Redis 调用频繁;step太大,一旦 ID 生成器进程崩溃,会浪费大量 ID。我们最终选择了 10000,因为它在我们的平均请求间隔(约 0.2ms)下,既能保证性能,又将 ID 浪费控制在可接受范围内。

4.2INCR与 Redis Streams 的结合:构建实时事件计数管道

INCR是一个“状态变更”操作,而现代应用往往需要“事件驱动”。Redis 5.0 引入的Streams,为INCR提供了一个完美的搭档。

设想一个场景:用户每完成一次支付,系统需要:

  1. 更新该用户的总支付金额(INCRBY user:123:total_amount 99.9
  2. 记录这笔支付事件,供风控、审计、BI 系统消费(XADD payments * user_id 123 amount 99.9 timestamp 1684567890

过去,这两步需要两个独立的 Redis 命令,存在一致性风险。现在,我们可以用XADDMAXLEN选项,配合INCR,构建一个“事件驱动的计数器”。

# 创建一个只保留最近 100 万条事件的流 XADD payments MAXLEN ~ 1000000 * user_id 123 amount 99.9 # 同时,用一个 Lua 脚本,保证事件写入和计数器更新的原子性 EVAL " local res = redis.call('XADD', KEYS[1], 'MAXLEN', '~', '1000000', '*', 'user_id', ARGV[1], 'amount', ARGV[2]) redis.call('INCRBY', 'user:' .. ARGV[1] .. ':total_amount', ARGV[2]) return res " 1 payments 123 99.9

这个 Lua 脚本在 Redis 服务端原子执行,确保了事件写入和金额累加的强一致性。下游消费者(如一个 Python 脚本)可以订阅payments流,实时处理每一条支付事件,进行复杂的风控计算,而不再需要轮询数据库或计数器。

注意:Lua 脚本的执行会阻塞 Redis 主线程,因此脚本必须极其轻量。上面的例子中,XADDINCRBY都是 O(1) 操作,完全没问题。但如果在脚本里做KEYS *HGETALL这样的 O(N) 操作,就会成为性能杀手。Lua 是利器,也是双刃剑

4.3 从单机INCR到云原生弹性:Kubernetes 上的 Redis 计数器服务

当你的业务从单体架构走向微服务,INCR的使用方式也需要进化。我们不再直接在业务代码里new Jedis(),而是将其封装成一个独立的、可伸缩的“计数器服务”。

架构图(文字描述)

[业务 Pod] --> [Service: counter-api] --> [Deployment: counter-service] | v [StatefulSet: redis-cluster]
  • Counter Service:一个轻量级的 HTTP 服务(Go/Java),暴露/incr/{key}/get/{key}等 REST 接口。它内部封装了 Redis 连接池、重试逻辑、熔断降级(如 Hystrix/Sentinel)。
  • Redis Cluster:部署在 Kubernetes StatefulSet 中,利用PersistentVolume保证数据持久化,并通过Headless Service实现节点发现。
  • 弹性伸缩:Counter Service 的 Deployment 可以根据cpucustom metrics(如counter_api_requests_total)自动扩缩容。而 Redis Cluster 的分片数,也可以通过 Helm Chart 的cluster.nodes参数动态调整。

这种架构的好处是:

  • 关注点分离:业务代码只关心“我要计数”,不关心 Redis 连接、重试、集群拓扑。
  • 统一治理:所有计数器操作,都经过同一个服务入口,便于统一监控(Prometheus)、日志(ELK)、限流(Sentinel)。
  • 平滑迁移:当未来需要将计数器迁移到其他存储(如 TiDB 的AUTO_INCREMENT),只需替换 Counter Service 的后端实现,业务代码零改动。

实操心得:我们在一个 SaaS 平台的“客户用量统计”模块中落地了此方案。平台有上千个租户,每个租户都有自己的usage:tenant_123:api_calls计数器。初期,我们直接在各个微服务里用 Jedis 操作 Redis,结果是:一个租户的 Redis 连接泄漏,导致整个平台的计数器服务雪崩。重构后,我们将所有计数逻辑收口到counter-service,并为其配置了独立的连接池(maxTotal=200)和熔断阈值(错误率 > 50% 时,自动 fallback 到本地内存计数,最多缓存 1 小时)。上线后,计数器服务的稳定性 SLA 从 99.5% 提升到了 99.99%。基础设施的抽象,是应对复杂性的终极武器

5. 常见问题与排查技巧实录:那些年,我们一起踩过的INCR

5.1 问题速查表:从现象到根因的快速定位

现象描述可能根因排查命令与技巧
INCR返回值突变,比如从 10000 跳到 10050客户端使用了INCRBY,且by值为 50;或SET了新值;或DECR了。DEBUG OBJECT counter:key查看对象的 refcount 和 encoding;OBJECT ENCODING counter:key确认是否为intMONITOR实时抓包,看是否有INCRBYSET命令。
INCR命令执行缓慢,latency报告高网络延迟高;Redis 内存不足触发 swap;slowlog中有其他慢命令阻塞了主线程。redis-cli --latency测试网络延迟;INFO memory查看used_memory_rssmem_fragmentation_ratioSLOWLOG GET 5查看最近 5 条慢日志。
INCRGET到的值与预期不符客户端连接了从节点(slave);或INCR命令被WATCH事务打断;或 key 被EXPIRE删除。INFO replication确认role:masterCLIENT LIST查看 client 的flags是否包含S(slave);TTL counter:key检查 key 是否即将过期。
INCR返回(error) ERR value is not an integerkey 存在,但其值不是整数字符串(如"abc""12.34""");或 key 是其他类型(list, hash)。TYPE counter:key查看类型;GET counter:key查看原始值;DEL counter:key清理后重试。
INCR在高并发下出现“计数丢失”客户端重试逻辑缺陷(未做幂等校验);或INCR后未及时GET,被后续DEL覆盖。在客户端日志中搜索INCRGET的时间戳,对比是否匹配;检查业务代码中是否有if (response == null) retry()这样的裸重试,应改为if (response == null) { val = GET(); if (val == null) INCR(); }

5.2 独家避坑技巧:来自生产环境的血泪教训

  • 技巧一:永远不要信任INCR的返回值做业务决策
    很多同学会这样写:

    Long newCount = jedis.incr("counter:user:123"); if (newCount > 100) { sendVipReward(); }

    这看起来天衣无缝。但问题在于,INCR返回的是“本次操作后的值”,而sendVipReward()是一个耗时的外部调用。在这段时间里,另一个线程可能又执行了一次INCR,导致counter:user:123的值已经大于 100。正确的做法是,用GET再次确认:

    jedis.incr("counter:user:123"); Long currentCount = jedis.get("counter:user:123"); // 再次 GET,确保是最新值 if (currentCount != null && currentCount > 100) { sendVipReward(); }
  • 技巧二:为INCR命令设置合理的timeout
    INCR本身不会超时,但网络 I/O 会。如果客户端socketTimeout设置过长(如 30 秒),在网络抖动时,一个INCR请求会卡住 30 秒,拖垮整个线程池。我的经验是:socketTimeout设为500msconnectionTimeout设为2000ms。对于INCR这种简单命令,500ms 是绰绰有余的,超时就意味着网络或 Redis 已经不可用,应该快速失败、降级。

  • 技巧三:用INFO commandstats做性能基线
    INFO commandstats会返回每个命令的调用次数 (calls)、总耗时 (usec) 和平均耗时 (usec_per_call)。你应该在系统上线前,记录下cmdstat_incr的基线值。当线上出现性能问题时,对比基线,如果usec_per_call突然翻倍,说明 Redis 本身出了问题;如果calls突然暴涨,说明上游业务流量异常。这是一个比top更精准的诊断入口。

  • 技巧四:INCR不是分布式锁,别把它当锁用
    我见过最危险的用法是:

    # 错误!这不是锁! if jedis.incr("lock:resource") == 1: do_something_critical() jedis.decr("lock:resource")

    这段代码的问题在于:INCR成功只代表“我拿到了 1”,但不保证“我是第一个拿到的”。因为INCRDECR之间没有原子性,如果do_something_critical()抛异常,DECR就永远不会执行,锁就永远得不到释放。INCR只能做计数,不能做互斥。要用锁,请老老实实用SET resource_name my_id NX PX 30000

最后分享一个小技巧:在 Redis 的redis.conf

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询