资讯动态

Redis高并发实战:从核心数据结构到分布式锁与性能调优

发布时间:2026/10/21 12:19:34 来源:尧图企业网站定制
1. 为什么你的项目越做越大反而越需要Redis做后端这些年不少同事对Redis的态度其实分两种一种人只把它当成一个“临时缓存”存完就完事另一种人用得很花却也经常在半夜被线上告警吵醒。Redis这东西说简单也简单说深也深。如果你正从“能跑起来”往“扛得住高并发”这个阶段过渡这份实战记录应该能帮你省掉不少翻车的时间。先说清楚Redis在高并发场景下到底解决什么问题。关系型数据库的数据落在磁盘每一次读写都要经过文件系统、缓冲区、锁机制还要处理事务日志天然不适合扛超高并发。举个例子MySQL单机在普通配置下能撑住每秒几千次的简单查询就不错了一旦到了秒杀、活动页、热点榜单这种动辄每秒几万次请求的场景连接池先被打满然后响应变慢接着是雪崩式的超时。Redis不同它的数据放在内存里单线程模型配合IO多路复用纯读场景能跑到十万级别QPS写操作也能到几万级别整体响应通常在毫秒以内。你当然可以说“我用缓存中间件也能做”但Redis真正厉害的地方在于它不是一个单一的KV缓存而是一整套基础组件集合字符串可以做分布式锁和计数器有序集合可以做排行榜列表和流可以做轻量队列布隆过滤器可以挡缓存穿透。所以当你面对高并发系统时Redis基本是绕不开的标配问题只是你怎么把它用好。这篇实战指南里讲的东西全部来自我实际开发和排障过程中反复验证过的经验不会只贴官方文档那种冷冰冰的说明。适合正在用Redis做业务开发的人也适合想系统了解高并发缓存设计的小白。学完你能得到什么一套能直接照抄的缓存规范、一套在故障现场的排查思路以及一份不再被面试官问倒的Redis知识地图。1.1 Redis与关系型数据库的定位差异我习惯把存储体系比作超市数据库是仓库Redis是收银台旁的货架。仓库里放的是全部商品容量大但取货慢货架上放的是最热卖的商品容量有限但拿起来快。一个合理的系统不可能把仓库里的所有东西都搬到货架上也不可能因为有了货架就把仓库拆了。所以Redis最标准的定位是“数据库前面的热数据层”。它缓存的是高频读取、低频繁变化的数据比如用户登录信息、商品详情、配置项、验证码、活动状态。真正需要持久化保存、有强事务要求的数据最终还是应该落在关系型或文档型数据库里。不要指望Redis去替代数据库也别把数据库的活全甩给Redis两者的职责边界划清楚后面很多架构问题都不会发生。1.2 高并发下Redis真正解决了什么问题除了扛住读流量Redis在高并发系统里的价值主要集中在这几个方向读多写少场景的流量卸载。热点数据从数据库搬到Redis后后端数据库的查询量大幅下降原来需要几十台应用实例分摊的流量可能几台就够了。分布式环境下的共享状态。多个服务实例各自独立但分布式锁、计数器、排行榜这类状态必须全局一致Redis天然就是干这个的。削峰填谷。瞬时流量来临时通过Redis做限流计数把超出的请求快速拒绝或排队保护后端系统不被打垮。简单说Redis在系统里的作用是“让你用最少机器扛住最多流量”而不是“帮你把业务复杂度抹平”。如果你只把它当缓存用大约只发挥了三成功力把分布式锁、限流、计数这些能力用起来才谈得上高并发优化。1.3 这篇实战指南适合谁读如果你是刚接触Redis的开发者建议从第2章的数据结构开始看把命令亲手打一遍再往下走如果你已经在项目里用过Redis但被缓存穿透、击穿、雪崩这些词困扰过直接跳到第4章那是全篇最核心的部分如果你已经在维护线上集群第6章和第7章可以当作排障手册遇到问题时翻开对照处理。2. 基础实操五种核心数据结构一次讲透Redis之所以叫“数据结构服务器”是因为它真正的价值不在“Key-Value”这几个字而在于value的形态丰富。选对数据结构能让你的代码量减少一半性能提升数倍。这一章我用生产场景来拆解五种结构每个都配上命令示例和选型理由。2.1 String计数、缓存、验证码的全能选手String是Redis里最基础的结构value可以是字符串、整数或者二进制数据最大512MB。基础命令就这几个SET、GET、SETNX、SETEX、INCR、DECR。平时用String踩坑最多的是不了解它的原子自增能力结果自己用“读出来改回去”的方式实现了计数器并发一高全是脏数据。 SET login:count:20250301 0 OK INCR login:count:20250301 (integer) 1 GET login:count:20250301 1INCR、DECR是原子操作非常适合做登录次数统计、库存扣减、接口限流的基础计数。一个典型场景是验证码发送短信前用SETEX存验证码并设置60秒有效期校验时用GET读取发送前再用SETNX判断是否已有未过期的验证码防止用户反复触发短信接口刷成本。String存对象也很常用一般会把对象序列化成JSON后SET进去。这里要提醒一句大对象反复整体序列化、反序列化CPU开销不小。如果业务只是频繁更新对象里的某一个字段就别用String换成Hash更划算。2.2 Hash对象存储场景比String更节省内存Hash结构相当于一个小型Map可以单独读写key底下的某个字段。命令主要有HSET、HGET、HGETALL、HDEL、HINCRBY。比如存用户信息 HSET user:1001 name 小A age 25 city 北京 (integer) 3 HGET user:1001 name 小A HINCRBY user:1001 age 1 (integer) 26Hash最怕的就是用错命令把整个对象拉出来再改那就失去了它“字段级操作”的意义。我见过P99延迟从5ms涨到50ms的案例起因就是有人用HGETALL取了整个大Hash再在应用层修改。正确做法是只HGET需要的字段只HSET改动的字段。还有一个小细节当Hash内部元素数量较少、字段值较短时Redis会使用紧凑编码listpack存储内存占用远小于正常哈希表。所以不要一开始就担心Hash费内存大多数场景它比String更省。2.3 List消息队列与最新列表的旧派选择List是链表结构操作两端很快。常用命令LPUSH、RPUSH、LPOP、RPOP、BRPOP、LRANGE。经典场景是“最新消息列表”上线一个内容LPUSH到列表头部再用LRANGE 0 9取最新十条性能远好过SQL里的ORDER BY LIMIT。真正需要注意的是很多人拿List当消息队列用。LPUSH BRPOP确实能实现一个阻塞队列我当时也这么干过但用久了问题就出来了消费者取走了消息却没来得及处理进程崩溃的话消息直接丢失没有ack机制也不能重复消费。所以List适合做简单的任务分发如果你需要可靠投递、消费组、消息追溯Redis 5.0的Stream更合适。选型时别用List硬撑后面会遭罪。2.4 Set与ZSet去重、交集与排行榜的正确姿势Set是无序无重复集合适合做去重、共同好友、随机抽奖。常用命令SADD、SREM、SISMEMBER、SCARD、SINTER、SRANDMEMBER、SPOP。 SADD follow:1001 2001 2002 2003 (integer) 3 SADD follow:1002 2002 2003 2004 (integer) 3 SINTER follow:1001 follow:1002 1) 2002 2) 2003一行SINTER就拿到了两个用户的共同关注这在粉丝关系链类系统里是高频操作。抽奖场景用SPOP随机弹出成员天然不重复。ZSet在Set基础上给每个元素加了分数按分数排序。命令ZADD、ZSCORE、ZINCRBY、ZRANGE、ZREVRANGE、ZRANGEBYSCORE。排行榜就是它的典型主场 ZADD leaderboard:game1 8000 playerA ZADD leaderboard:game1 15000 playerB ZINCRBY leaderboard:game1 1000 playerA ZREVRANGE leaderboard:game1 0 9 WITHSCORESZSet还能做延时队列把任务ID作为member执行时间戳作为score定时用ZRANGEBYSCORE取score小于当前时间的任务来处理。这个技巧实现简单又不需要额外引入消息中间件适合中小规模场景。2.5 数据结构选型对照与选错后的修复思路我经常用一个口诀帮人记忆字符串存单值Hash存对象List存队列Set存去重ZSet存排行。下面是详细的选型对照表数据结构典型场景核心优势容易踩的坑String缓存、计数、验证码、分布式锁简单直接性能最高大对象反复读写浪费CPUHash用户资料、商品详情、配置信息支持字段级更新省内存用HGETALL取大对象List最新列表、简单任务队列两端操作O(1)当可靠消息队列用Set去重、共同好友、抽奖天然去重集合运算方便大数据量下SINTER较重ZSet排行榜、延时队列、限流窗口按分数排序范围查询强分数设计不合理导致误差如果上线后才发现选型不对千万别直接大范围修改代码。稳妥的做法是先双写一段时间新老逻辑并存确认新方案的数据一致和性能没问题后再切换读路径。这个“灰度迁移”的思路在任何中间件选型调整里都适用。3. 数据安全持久化、过期策略与内存淘汰很多人以为Redis只是缓存丢了就丢了重启恢复不恢复无所谓。这个认知在缓存场景勉强说得过去但如果你用Redis存了分布式锁、未支付订单状态、限流计数重启丢数据就会直接引发线上事故。所以持久化配置不是可有可无而是要看场景认真设计。3.1 RDB快照与AOF日志的取舍Redis持久化有两条路RDB和AOF。RDB是内存数据的二进制快照通过fork子进程生成文件紧凑、加载快适合做备份和灾难恢复。但它有个天然缺陷两次快照之间的数据会丢。比如每5分钟生成一次快照第4分59秒写入的数据在宕机后就会丢。AOF则记录每次写操作的命令像操作日志一样追加到文件恢复时重放命令。它丢失数据的窗口小很多但由于记录的是命令文本文件体积比RDB大恢复速度也慢。实际使用时我通常建议两者都开RDB作为快速恢复的底子AOF作为精确到秒的补充。3.2 appendfsync刷盘策略怎么选AOF的可靠性核心在那个刷盘策略上。配置项是appendfsync三个可选值always每次写命令都强制刷盘最安全性能牺牲最大一般不会这么用。everysec每秒刷一次盘最多丢一秒数据性能和可靠性比较均衡生产上大多数时候选这个。no交给操作系统决定什么时候刷盘性能最好但丢数据风险不可控。我在生产实践中直接写成everysec。如果你做的是账户余额、交易订单这类强一致业务我劝你谨慎评估Redis是否适合作为唯一存储如果只是缓存与状态标记everysec完全够用。还有一个配置容易被忽略AOF文件会越来越大需要定期执行重写。Redis提供了auto-aof-rewrite-percentage和auto-aof-rewrite-min-size来触发自动重写默认配置基本够用但如果你的写操作很频繁要把重写阈值调小防止AOF文件膨胀到几个G后拖垮启动恢复。3.3 过期删除机制与内存淘汰策略所有设了TTL的keyRedis并不是到点就立即物理删除而是用了惰性删除加定期删除的混合机制。惰性删除是访问到某个key时才发现它过期了顺手删掉定期删除是后台周期性地抽样检查一部分过期key并清理。这个设计的理由是为了精确到点的删除而扫描全量key代价太大没必要。如果你只设了过期时间却没有配置内存淘汰策略Redis默认是noeviction——内存满了之后所有写请求直接报错。生产环境里绝对不能用默认值否则高峰期一个缓存写操作失败连锁反应能把业务打崩。常用的淘汰策略就几个allkeys-lru所有key按LRU淘汰、volatile-lru只淘汰设置了过期时间的key、allkeys-lfu按访问频率淘汰、volatile-random。对纯缓存场景我一般用allkeys-lfu或allkeys-lru对“某些key必须常驻但可以淘汰冷门缓存”的混合场景用volatile-lru更适合。3.4 持久化与淘汰策略的实战组合建议我通常按业务类型分两套配置。第一类纯缓存数据。可以关闭持久化或仅开启RDB目的是故障后快速加载热数据丢失了能重新从数据库加载。淘汰策略直接allkeys-lfu保证最热的key留在内存里。第二类带业务状态的短时数据比如验证码、分布式锁、限流计数、交易幂等标记。开启AOFeverysec RDB混合持久化淘汰策略用noeviction或者volatile-lru但要给key设置足够合理的过期时间防止意外丢写失败。具体配置示例appendonly yes appendfsync everysec save 900 1 save 300 10 maxmemory 8gb maxmemory-policy allkeys-lfu这套组合我实际用了很久既能扛突发流量又不会在重启后出现大面积缓存冷启动导致数据库被打爆。4. 高并发三大坑缓存穿透、缓存击穿、缓存雪崩这三个词面试里总出现但很多人背了概念到线上真出事时依然手忙脚乱。我的经验是每一个坑都有明确的形成路径和对应解法你只要按图索骥就行。4.1 缓存穿透压根不存在的数据怎么挡穿透的本质是请求查询的数据在缓存和数据库里都不存在导致每次请求都打到数据库。比如有人恶意遍历不存在的商品ID或者前端传了一个被删除的历史IDRedis里查不到数据库里也没有查询就一路穿过所有缓存防护。最直接的解法是空值缓存查不到数据时也在Redis里存一个空对象并设置较短的过期时间比如30到60秒。这样大量重复请求不会重复穿透到数据库。更专业一点的解法是布隆过滤器。它用多个哈希函数把已存在的key映射到一个很长的bit数组里查询时如果某个key映射的bit位中有任何一个为0那这个key一定不存在如果都为1可能存在也可能误判。布隆过滤器适合“数据集合相对固定”的场景比如判断用户ID是否存在、URL是否合法。// 伪代码示意 if (!bloomFilter.mightContain(requestId)) { return 无效请求; } Object cacheValue redis.get(key); if (cacheValue null) { Object dbValue db.query(key); if (dbValue null) { redis.set(key, , 60); } else { redis.set(key, dbValue, 300); } }布隆过滤器的缺点是难以删除元素所以数据删除频繁的业务要对它做定期重建或者使用带删除能力的计数布隆过滤器。4.2 缓存击穿热点key一瞬间被压垮击穿和穿透很容易混。击穿指的是某个key非常热比如秒杀商品的详情页、微博热搜榜所有请求在极短时间内集中打到这个key上。如果这时候这个key刚好过期Redis里查不到请求就会同时打到数据库数据库瞬间被大流量冲击。两个主流解法互斥锁和逻辑过期。互斥锁的思路是请求发现缓存过期后不是每个人都去数据库查询而是先尝试获取分布式锁只有拿到锁的线程才能去查数据库并回填缓存其他线程短暂等待后再次读取缓存。逻辑过期更巧妙value里不只存业务数据还要拼一个过期时间字段。读请求发现逻辑时间已过期立刻返回旧数据同时异步开一个线程去更新缓存。这种方式不会阻塞请求用户体验更好但代价是数据会有一段时间不一致适合能接受短暂脏读的业务。实际工作中这两个方案不冲突强一致、必须防穿透的场景优先用互斥锁高并发、容忍短时旧数据的场景用逻辑过期更顺手。4.3 缓存雪崩大面积key同时失效的连锁反应雪崩有两种触发方式一是大量key的过期时间集中在同一时刻缓存大面积失效流量全部压到数据库二是Redis实例宕机所有缓存瞬间不可用数据库被直接打穿。对付第一种办法是让过期时间随机化。不要都用3600秒而是3600加一个300到600的随机数让key的过期时间错开。这个改动成本极低效果却非常明显。我接手的某电商项目之前每次整点活动开始时MySQL连接数都会突然飙升到接近上限查下来发现是因为所有活动banner的缓存都设了3600秒的整数倍时间一到整点就整批过期。后来把所有缓存TTL都加了一个随机偏移这个现象再没出现过。对付第二种就得靠多级缓存和高可用架构。在Redis前面加一层本地缓存比如Caffeine或Guava Cache即使Redis挂了本地缓存还能挡一阵。同时Redis自身要部署主从加哨兵避免单点故障。真实场景里还要配合熔断降级当后端检测到缓存异常率过高直接返回兜底数据或快速失败而不是让雪崩继续放大。4.4 预防三连坑的一揽子方案我在项目里落地了一套组合方案基本能把这三种问题一次性挡住接口层做参数校验非法ID直接拒绝挡掉一部分穿透。访问热点数据前先查布隆过滤器不存在就直接返回持久挡住穿透。所有缓存设置过期时间时加入随机因子打散批量过期防雪崩。对单点热点key用逻辑过期或互斥锁做防护防击穿。Redis部署主从和哨兵建立本地缓存兜底防实例宕机引发的雪崩。这套方案不需要每一条都上但至少要做到防穿透靠空值缩短TTL防雪崩靠随机过期防击穿靠热点保护。顺序是先低成本做到位再逐步加高成本方案。5. 分布式锁与限流高并发下的两个关键组件当系统从单机部署变成多实例部署原来用synchronized就能解决的并发问题就失效了因为不同进程之间没法共享锁状态。这时候Redis分布式锁成了简单又高效的替代方案。但网上讲分布式锁的文章很多版本也杂我直接说生产环境里经过验证的正确写法。5.1 分布式锁的正确写法与常见翻车现场最早流行的写法是用SETNX加锁再单独EXPIRE设置过期时间。这个写法有严重问题SETNX和EXPIRE不是原子操作如果SETNX成功之后进程正好GC停顿或重启锁就永远没有过期时间其他线程全部死等。所以正确姿势是一条命令同时完成加锁和设置过期时间 SET lock:order:1001 unique_value NX PX 30000 OK这个unique_value必须是每个线程唯一的标识比如UUID或线程ID加随机数不能只是简单的1。为什么因为释放锁时要校验是不是自己的锁。假设线程A的锁因为执行时间过长自动过期了线程B拿到了锁这时A终于执行完并调用DEL释放锁就会把B的锁误删掉。要避免这个翻车现场释放锁必须用Lua脚本做“先比较、再删除”的原子操作if redis.call(get, KEYS[1]) ARGV[1] then return redis.call(del, KEYS[1]) else return 0 end还有一个生产上必须考虑的细节锁过期时间到底设多长。设太短业务没执行完锁就过期了设太长一旦持有锁的实例崩溃其他线程要等很久。成熟的做法是用自动续期机制比如Redisson客户端的看门狗默认锁30秒每隔10秒会自动续期只要业务没结束锁就不会被提前释放。这个设计我强烈建议直接复用不要自己写续期逻辑自己写的续期脚本出错概率高得多。5.2 Redis限流的两种实用方案限流本质上就是控制单位时间内的请求数量。Redis做限流的第一个方案是固定窗口计数核心就两条命令INCR和EXPIRE。但这两条命令组合在一起也不是原子操作所以直接用Lua脚本包一层local current redis.call(INCR, KEYS[1]) if current 1 then redis.call(EXPIRE, KEYS[1], ARGV[1]) end if current tonumber(ARGV[2]) then return 0 end return 1ARGV[1]是窗口大小秒数ARGV[2]是最大数量。这个方案的缺点是固定窗口边界处可能涌进双倍流量比如限制1分钟100次第59秒来了100次第61秒又能来100次。如果业务对流量平滑度要求高要用滑动窗口。滑动窗口的经典实现是ZSet。每个请求到来时把时间戳作为score写入ZSet然后删除窗口之前的历史记录最后统计剩余数量。命令是这样 ZADD rate:123 1680000000 req1 ZREMRANGEBYSCORE rate:123 0 1679999400 ZCARD rate:123 (integer) 1如果ZCARD超过阈值就拒绝当前请求。这个方案精确度高但每个请求都要写ZSet写入量较大不适合超高压场景。实际项目里我通常把固定窗口限流用于接口级粗粒度限流滑动窗口用于核心接口的精细限流。5.3 Lua脚本带来的原子性保证为什么Redis限流、释放锁、扣减库存都要用Lua?因为Redis执行Lua脚本时是原子性的脚本执行期间不会有其他命令插进来也不会被其他客户端的命令打断。用多个普通命令组合实现同一功能中间一旦被打断数据就出问题。举个最常见的库存扣减场景。伪代码如下local stock tonumber(redis.call(GET, KEYS[1])) if stock 0 then return -1 end redis.call(DECR, KEYS[1]) return stock - 1检查库存、扣减库存、返回结果三步在一个脚本里完成没有任何并发间隙。这是我在秒杀类接口里最常用的写法简单可靠还省掉了应用层事务的复杂度。6. 集群架构从单机到Redis Cluster的演进单机Redis哪怕性能再好也有两个天花板一是容量单机内存是有限的二是可用性一台机器宕机对于线上系统就是事故。我见过太多小团队一开始只用单机等用户量涨起来后只能连夜迁移。与其被动不如主动了解清楚演进路线。6.1 主从复制与哨兵先解决可用性主从复制解决的是“数据备份和读写分离”问题。一个主节点负责写入多个从节点复制主节点的数据并提供读服务。主节点挂了人肉切一台从节点上来继续服务。但人肉切换太慢。Redis 2.8版本之后有了哨兵机制它能自动监控主节点状态发现主节点挂了会从从节点中选举一个提升为新的主节点并通知客户端更新连接地址。部署哨兵需要注意的是哨兵自身也要高可用一般部署3个或5个形成奇数集群配合quorum配置才能正确判断故障避免误切主节点。主从复制的坑也不少我踩过最典型的是复制中断和主从延迟。主从延迟会导致读取从节点时拿到旧数据对一致性敏感的业务写后读请求应该强制走主节点或者短期缓存写操作的mark规避延迟。6.2 Redis Cluster槽位路由与扩展性主从加哨兵解决了单点问题但数据总量仍然受制于单机内存。当数据量达到几十G甚至上百G时就要考虑Redis Cluster了。Cluster把整个Key空间分成16384个槽位每个节点负责一部分槽位。写入或读取某个key时客户端通过CRC16计算它的哈希值再对16384取模得到这个key应该落在哪个槽位上。比如现在有一个三主三从的集群三个主节点大致各分管约5461个槽位。有一个细节很有意思为什么槽位数是16384而不是65536或者更大Core团队考虑的是每个节点在心跳包里要用bitmap形式传播自己的槽位信息16384个槽位对应2KB的bitmap传输开销小而65536个槽位的bitmap要8KB心跳包就太大了。这个设计是用传输带宽换路由精度理解不了也没关系知道这个数字背后有传输成本考量就够了。集群部署后有几点要特别注意客户端必须支持Cluster协议遇到MOVED和ASK重定向要能正确处理不然数据路由错了自己还不知道多key操作时如果key不在同一槽位上会直接报错需要用Hash Tag把相关key强制落到同一槽位。6.3 大Key与热Key集群时代的头号隐患我把大Key和热Key放在集群章节讲是因为它们在单机时代容易掩盖一旦上集群会暴露得非常明显。大Key是指单个value过大比如一个Hash里有几百万个字段或者一个Set里有上千万元素。它的危害是读写耗时高会阻塞Redis主线程占用内存不均衡导致集群节点间负载漂移删除时如果直接DEL瞬间阻塞所有请求。处理大Key的标准方式是拆分和压缩但更重要的一点是提前发现。生产环境里我习惯用SCAN遍历所有key配合检查每个key的value长度和容器元素数量把超过阈值的大Key自动预警出来。热Key是指某个key的访问量特别高比如明星突然上了热搜或者某件商品直播爆单。它会打爆单个节点。解决办法可以是在应用层加本地缓存用空间换时间或者把热Key拆成多个副本key分散到不同节点比如在原始key后面加1到N的后缀读取时随机访问一个副本也可以直接对热点key做多副本只读写时同步到所有副本。6.4 容量规划与部署排坑建议容量规划的核心原则是Redis内存不要用满顶点。如果你给实例分配了16GB物理内存建议maxmemory设置在10到12GB左右因为fork生成RDB快照、AOF重写都需要额外内存操作系统页缓存也需要留余地。一旦内存被打到接近物理边界系统开始使用swap性能会断崖式下降。部署时的几个排坑点都是我用真实教训换来的生产环境禁用KEYS命令它会全表扫描Redis并阻塞主线程需要遍历用SCAN命令配合游标。开启protected-mode时记得配置正确的访问白名单否则客户端会被拒之门外。rename-command把高危命令重命名或禁用防止被外部恶意执行。集群节点数量、副本数量决定容错能力三主三从是常见起步配置但别只图省事建一个“伪集群”。7. 线上问题排查实录与性能调优清单Redis排障没有太多玄学关键在快速定位方向和减少试错成本。我平时排查线上的顺序是先看监控告警再查日志然后用Redis自身提供的诊断命令确认根因。这里把我用过最多、效率最高的几套方法整理成速查手册。7.1 慢查询日志定位延迟的起点Redis提供了慢查询日志记录超过指定耗时的命令。默认的slowlog-log-slower-than是10000微秒10毫秒生产环境这个阈值偏宽我一般调成1000也就是1毫秒。 CONFIG SET slowlog-log-slower-than 1000 OK SLOWLOG GET 20看到慢查询命令后重点分析是不是大Key引起的。比如HGETALL一个几万字段的Hash、ZRANGE一个几百万成员的有序集合、KEYS全表扫描这些命令时间消耗高要在业务层改掉。慢查询日志不是持久化的Redis重启后会丢所以建议配置完慢查询阈值后定期把日志拉取到监控系统做分析。7.2 INFO命令内存与持久化的体检报告INFO命令是Redis自带的“体检报告”我最关注这几个指标指标含义理想状态used_memoryRedis实际占用内存低于maxmemory的70%mem_fragmentation_ratio内存碎片率1.0到1.5之间connected_clients当前客户端连接数远低于maxclientsinstantaneous_ops_per_sec瞬时QPS与预估容量对比rdb_last_bgsave_status最近一次RDB是否成功okaof_last_write_status最近一次AOF写入是否成功okmem_fragmentation_ratio如果超过1.5说明内存碎片较多可以选低谷期重启实例或执行MEMORY PURGE压缩碎片。instantaneous_ops_per_sec结合慢查询一起看能快速判断当前是否处于高负载状态。我每次上线新版本Redis后都会第一时间看这份报告确认配置生效、内存水位正常、连接数没有异常增长。7.3 连接数被打满的应急处理线上最老套也是最常见的故障是连接数打满。一个Redis实例默认maxclients是10000当应用服务器连接池配置过大或者业务代码有连接泄漏很容易一夜之间把连接数打爆。现象是新的Redis客户端全部连接超时服务大面积报错。应急处理分两步第一步用CLIENT LIST查看当前连接来源找到连接数最多的那台应用服务器检查它的连接池配置第二步用CLIENT KILL杀掉疑似异常连接快速恢复服务。从根上解决问题要在应用层做好三件事使用连接池而不是每次请求新建连接给连接池设置合理的maxTotal和maxIdle避免无限制扩容配置空闲连接回收时间防止连接泄漏。我在某个项目里遇到过连接数持续上涨的怪事查了很久才发现是某个定时任务每次执行都没有归还连接。后来统一走连接池并在代码里加了异常归还的兜底逻辑连接数稳定回落到几百。7.4 一份可以直接抄走的性能调优参数表这里列一份我在生产环境中验证过的常用参数配置能覆盖大部分业务场景的基础调优配置项推荐值说明maxmemory-policyallkeys-lfu缓存场景优先淘汰冷keyappendfsynceverysec性能和可靠性均衡tcp-backlog511提升短连接突增的接纳能力timeout0不建议主动断开连接hz10默认即可不必频繁调整slowlog-log-slower-than1000单位微秒更容易发现慢命令list-max-listpack-size128小而紧凑的列表不转换编码set-max-intset-entries512小整数集合用紧凑编码千万不要看到参数就一堆改一次只改一个关键配置然后用监控数据验证效果。改多了出了问题都不知道是哪一条导致的这是我吃了不少亏之后最深的体会。8. 最后分享几个实战中摸出来的经验Redis的坑网上文章说得再多都不如自己亲手踩一次记得牢。这里分享几条我沉淀下来的小经验也是我平时带人必讲的内容。第一新项目接入Redis之前先用redis-benchmark在自己环境压一次。别轻信网上说的“单机十万QPS不解释”不同机器、不同网络、不同数据大小性能差异非常大。压测时看INFO stats里的instantaneous_ops_per_sec那才是你当前环境的真实值。这个习惯帮我挡掉过好多次想当然的架构设计。第二监控要先于问题存在。不要等出故障了才去看数据。至少要把used_memory、connected_clients、rejected_connections、慢查询数量这几个指标接到监控系统里设置合理的告警阈值。我自己见过太多线上事故都有数分钟的黄金挽救期监控到位就能避免后续的连锁反应。第三别把缓存一致性只停留在口头上。缓存和数据库的数据一致性最稳妥的做法是更新数据库后删除缓存也就是Cache Aside模式。高并发下还要配合延迟双删先删缓存更新数据库再短延迟后删一次缓存确保在极端并发下旧数据不会残留。如果想更严格还可以订阅数据库binlog异步删除缓存但这个方案工程复杂度高一般业务用不上。第四不要神话Redis。它就是一把锋利的刀可以切菜也可以伤人。没有慢查询排查习惯、没有容量规划、没有监控预警Redis迟早会在某次高并发里给你一记重拳。真正的高手不是Redis用得多花哨而是知道什么时候该用、什么时候不该用以及出了问题怎么快速恢复。做技术就是这样踩坑不可怕怕的是踩完坑不去总结。希望这份实战记录能让你少走一些我走过的弯路也希望你下次部署Redis时心里多一份笃定少几分忐忑。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑