Redis生产实战:从序列化配置到缓存治理的完整排障指南
2026/9/16 17:11:44 网站建设 项目流程

前阵子我帮一个团队排查线上Redis问题,现场属实有点“惨烈”:某个计数接口突然报错,异常信息是ERR value is not an integer or out of range,一行业务代码都没变,数据量也没暴涨,报警却像多米诺骨牌一样倒。查到最后,问题居然出在RedisTemplate的序列化配置上——一个绝大多数人从装好Redis那刻起就没正眼看过的东西。

可能有人觉得,Redis嘛,背背八股文、敲几条命令就能应付面试,可真到生产环境里,一个序列化配置、一次主从切换、一条大Key扫描,都能让你从下午两点debug到凌晨。这篇东西我也不打算讲什么高深理论,就围绕我把Redis从下载安装到生产排障全流程吃透的经验,把那些真正决定成败的细节摊开聊一聊。

1. 先把Redis“快”的底裤扒干净:单线程模型与底层编码

很多人一说Redis为什么快,张口就是“基于内存”“单线程避免上下文切换”,这没错,但面试或者实际排查问题的时候,只有这种程度远远不够。Redis快不只是因为数据放内存里,它的IO模型、数据结构设计、甚至一次命令执行的路径,都藏着大量刻意为之的优化。

1.1 单线程模型为什么能扛住高并发

我见过不少人的理解是“Redis从头到尾都是单线程”,这话放在Redis 6.0之前勉强算对,但严格说Redis从6.0开始引入了IO多线程。真正的命令执行逻辑(也就是在内存里干活的那些操作)依然是单线程,多线程只用于网络读写、协议解析这些IO密集环节。为什么执行命令要死守单线程?因为所有命令都在单一线程里顺序执行,天然就没有并发竞争问题,根本不需要加锁,也不会有死锁,还会顺带把上下文切换的开销省掉。

那单线程怎么满足高并发?关键在于Redis用了多路复用机制。你可以把Redis想象成一个大堂经理,他不用一次性接待所有客人,而是把每个人到访的信息先记在小本本上,哪个客人有动静了再去处理。操作系统层面的epoll(Linux下)、kqueue(BSD/macOS下)就是这个小本本,Redis把N个连接的文件描述符一股脑交给内核监听,内核发现有数据可读了再通知Redis,Redis再逐个处理。这样单线程也能轻松支撑每秒几万到十几万的命令执行,瓶颈基本都卡在网络带宽和应用端,Redis自身很少成为短板。

1.2 五大数据类型的底层内存编码(附转换阈值表)

这个点我觉得是开发者和面试官最容易聊出共鸣的部分:Redis的五大数据类型(老版本叫五种,现在其实还有Bitmap、HyperLogLog、Geo等扩展类型)在底层并不是只有一种存储形态。String、Hash、List、Set、ZSet在不同条件下会用不同的内部编码,目的就一个——在数据量小的时候用紧凑结构省内存,数据量大了再切换到适合查询的结构。

数据类型内部编码(旧版本)切换条件新版本变化(7.0+)
Stringint、embstr、raw整数用int;长度<=44字节用embstr;超过则用raw基本不变,阈值曾是39字节,后因内存分配器调整改为44
Hashziplist、hashtablefield数量<=512且value长度<=64字节用ziplistziplist逐渐被listpack替代,阈值逻辑类似
Listziplist、linkedlist、quicklist小数据量用ziplist,大用quicklist7.0起quicklist节点改为listpack
Setintset、hashtable全部为整数且元素数<=512用intset基本不变
ZSetziplist、skiplist+hashtable元素数<=128且member长度<=64用ziplist7.0起用listpack

这里随便列一个细节:String里的int编码,意味着你执行INCRDECR这类命令时有直接的内存级原子操作,不需要先把值取回来在应用层加减,再写回去。INCRGETSET三个命令的使用场景完全不同,前者在高并发下既能保证原子性又能省掉一次网络往返,这就是为什么计数器场景Redis是天然的首选。

ZSet底层用跳表加哈希表的组合也是个经典设计:哈希表用来O(1)按member查分数,跳表用来按分数范围做有序遍历。跳表结构如果没接触过,你可以理解成“多层索引的链表”,每一层都是下一层的快速通道,查找时从最高层开始逐层往下跳,复杂度能做到O(logN),比普通链表的O(N)高效得多。有些团队面试官喜欢把跳表和红黑树对比,Redis没选红黑树很重要的一个原因是跳表实现简单、范围查询友好,而且并发场景下更容易控制。

1.3 过期删除与内存淘汰:不光要知道,还得能说出取舍逻辑

过期键的清理不是大家想的那样“到点就删”,Redis采用的是惰性删除加定期删除的组合策略。惰性删除是访问到一个已过期的key时才把它删掉,好处是CPU零开销,坏处是过期key可能一直占着内存不被发现;定期删除则是每隔一段时间随机抽一批key检查过期比例,抽到过期比例高的会继续多抽几轮。这套组合拳的意图不难理解:既不想每次访问时都扫描所有key拖慢请求,又不想让过期key长期霸占内存。

但内存终究有限,如果写入速度超过清理速度,或者根本没设置过期时间,Redis就会触发淘汰策略。maxmemory-policy就是那个必须提前想清楚的配置项,默认的noeviction在内存满了以后直接给写命令报错,很多团队生产环境第一次OOM都是因为这个默认值。实际选型时:

  • 如果缓存里有大量可以丢弃的数据,用allkeys-lruallkeys-lfu
  • 如果只希望对设置了过期时间的key做淘汰,用volatile-lruvolatile-lfu
  • 如果是做严格意义上的DB前置缓存,建议全部key都设过期时间,然后用volatile-lru

LRU和LFU的区别也很关键:LRU维护的是“最近最少使用”,LFU统计的是“访问频率”。比如一个每天定时任务高频访问、但平时没人查的key,LRU可能因为它最近被访问过就不淘汰,LFU则能更准确识别出“长期热度低”的key。我在C端热点数据缓存里,更倾向用LFU,因为能避免周期性任务把LRU的淘汰逻辑带偏。

2. 环境搭建与连接工具:Windows、Docker主从与客户端选型

Redis的安装看起来是件小事,但恰恰是很多线上事故的起点。我见过有人在Windows上装了一个来路不明的Redis绿色版,数据量一大频繁丢内存,还有人连上Redis第一件事就是点客户端里的“命令行”敲FLUSHALL。环境这块如果不较真,后面怎么排障都别扭。

2.1 下载安装:Windows版本的坑与容器化推荐路径

先纠正一个误区:Redis官方从来没有正式支持过Windows。官网上能直接下载的是Linux源码包,Windows那套要么是微软团队维护的一个老分支(停在3.x/5.x附近),要么是社区爱好者自己编译的版本。本地做学习、做Demo问题不大,但拿到生产用Windows版Redis,你就是在给自己埋雷——主从切换、持久化行为、内存管理都跟Linux版有细微差异,一旦现网出问题,连官方文档都对不上号。

我现在的做法是:本机学习用Docker跑一个官方镜像,生产环境一律用Linux裸机或K8s部署官方源码包。Docker方式不需要在Windows上折腾各种依赖,一条命令就能拉起来:

docker run -d --name redis-dev -p 6379:6379 redis:7.2

如果实在想在Windows下装原生版做测试,优先去微软开源的归档仓库找那个带Windows维护分支的版本,或者找知名的第三方构建产物(比如tporadowski维护的Redis 5.x Windows版)。但注意这只是“能跑”,不是“推荐跑”。你到了生产环境要面对的是redis.conf里那一串性能参数,用一个跟生产不一致的本地环境调试,很多问题根本复现不出来。

2.2 用Docker快速搭一套主从架构

很多团队在开发环境只用单机Redis,等上了生产才发现主从复制没配过,然后慌慌张张上官网查文档。其实用Docker搭主从特别快,核心就是两件事:一份redis.conf里加一行主从关联配置,然后启动第二个节点。

假设主节点跑在6379端口,从节点跑在6380端口。从节点的配置文件里写:

replicaof 192.168.100.10 6379

然后分别启动:

docker run -d --name redis-master -p 6379:6379 \ -v /data/redis/master/redis.conf:/etc/redis/redis.conf \ redis:7.2 redis-server /etc/redis/redis.conf docker run -d --name redis-slave -p 6380:6379 \ -v /data/redis/slave/redis.conf:/etc/redis/redis.conf \ redis:7.2 redis-server /etc/redis/redis.conf

启动后进从节点执行INFO replication,能看到role:slave,并且master_link_status:up,就说明主从关系已经建立。这里有个容易踩的坑:Docker里各容器用localhost访问不到宿主机的Redis,从节点配置Master地址时必须填宿主机局域网IP或容器网络内的可达地址,而不是填127.0.0.1

主从架构的核心价值是读写分离和故障恢复冗余,但要注意Redis的主从复制默认是异步的,主节点写入成功后,从节点可能还差一小段数据。真到了主节点宕机、从节点升级为主的那一刻,这部分数据就丢了。所以涉及资金、订单这类强一致场景,不能只依赖Redis主从来兜底,数据落库才是最终依据。

2.3 可视化客户端怎么选:Redis Insight、Another Redis Desktop Manager与redis-cli的组合拳

可视化客户端这块,搜索量一直很高,因为键盘党不一定习惯看黑白终端。我三个工具都试过,简单说说取舍:

  • Redis Insight:Redis官方推出的图形化客户端,前身是Redis Desktop Manager桌面版,如今已经是免费开放。它对键的浏览、内存分析、慢日志展示都比较友好,还内置了Redis命令行,适合日常开发和排查。
  • Another Redis Desktop Manager(简称ARDM):开源免费,跨平台,连接配置管理做得更轻,启动速度快,适合多个环境的连接切换。
  • redis-cli:命令行的真相所在。生产环境我基本只认redis-cli,因为GUI客户端连生产库太容易误操作,而redis-cli敲命令时至少每一步都是你自己明确执行进去的。

我自己的习惯是:本地开发用ARDM看数据结构,连生产环境只用redis-cli,需要看趋势和慢日志再去Redis Insight里拉。别小看这个组合拳,很多“Redis连不上”的问题,其实是用GUI连的时候防火墙/安全组没放行6379端口,换个redis-cli -h ip -p 6379 ping一下立刻就能定位是不是网络问题。

3. Java集成最疼的一刀:RedisTemplate序列化与increment报错全解

Java后端用Spring Data Redis操作Redis是再常见不过的事,但坑也最多。尤其在RedisTemplate的序列化机制上,几乎每个团队都有人掉进去过。前面提到那个ERR value is not an integer or out of range报错,正好是理解整个序列化机制的最佳入口。

3.1 一个线上报错的完整排查链路

那天告警信息指向一段类似这样的代码:

ValueOperations<String, Long> ops = redisTemplate.opsForValue(); ops.increment("visit:count:goods:1001", 1);

这段逻辑单测是过的,本地跑也没问题,偏偏上了预发环境就报错。异常信息很长,核心是nested exception is org.springframework.dao.InvalidDataAccessApiUsageException: ERR value is not an integer or out of range

排查过程大概是这样的:

  1. 先用redis-cli连到预发Redis,查一下这个key的值是什么:

    redis-cli GET visit:count:goods:1001

    结果输出一串\xAC\xED\x00\x05t\x00...之类的二进制乱码。

  2. 再用TYPE命令看类型,确认它是String,但内容显然不是标准文本数字。

  3. 最后反查代码,发现这个预发环境的RedisTemplate没有自定义序列化器,Spring Boot默认用的JdkSerializationRedisSerializer,也就是把Java对象做了JDK原生序列化。Redis服务端拿到这一串二进制字节后,执行INCR命令想把它解析成整数,自然解析失败。

问题根因一句话就能说清楚:你以为是给Redis塞了个数字,实际塞进去的是一坨Java序列化字节流INCR要求value是文本形式的整数,JDK序列化后的二进制不规范,直接爆not an integer

3.2 序列化机制:为什么默认配置最容易出事

Spring Data Redis默认情况下,RedisTemplate用的是JdkSerializationRedisSerializer,key和value都以Java序列化的二进制形式存进Redis。这种方案最大的隐患是你用redis-cli或者任何可视化客户端看数据时,看到的全是乱码,因为人的肉眼不具备反序列化Java对象的能力。

更麻烦的是,它和Redis原生命令的交互非常别扭。比如上面那个increment(),还有opsForHash().increment(...)SETEXLPUSH等命令,它们要求value在Redis端能按字符串语义处理。JDK序列化流里包含类名、结构描述、对象头等一堆东西,根本不是普通文本,于是各种“未知”报错就来了。

比如说,使用Jackson2JsonRedisSerializer或者GenericJackson2JsonRedisSerializer时也要小心,如果存进去的是一个String类型的"123",序列化出来是带引号的JSON字符串"123",Redis服务端执行INCR时也会觉得这不是纯数字。换成StringRedisSerializer则没这个问题,因为String本身序列化后就等价于字符串本身。

正确配置长这样:

@Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) { RedisTemplate<String, Object> template = new RedisTemplate<>(); template.setConnectionFactory(factory); // key使用String序列化,避免出现 \xAC\xED 这种乱码前缀 template.setKeySerializer(new StringRedisSerializer()); template.setHashKeySerializer(new StringRedisSerializer()); // value用JSON序列化,可读性更好,但注意Integer等数字类型会被转成JSON数字/字符串 template.setValueSerializer(new GenericJackson2JsonRedisSerializer()); template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer()); template.afterPropertiesSet(); return template; }

如果你已经确定业务里只用String类型,直接用StringRedisTemplate是最省心的,它从构造到序列化全部用StringRedisSerializer,不会出这些幺蛾子。而一旦代码里已经用默认配置写入过线上key,就算改了序列化器,存量key也读不出来,因为新老序列化方式不兼容。只能写个脚本,把旧key读出来反序列化,再按新序列化方式重新写一遍,或者干脆让业务容忍一定时间的缓存重建。

3.3 正确配置与存量脏数据的处理

给团队做Redis规范的时候,我建议立两条规矩:

  1. 所有Redis里的商业数据,value能用String就用String;需要存对象的一律用JSON序列化,key统一带业务前缀,比如goods:detail:1001
  2. 涉及到计数器、自增ID、限流等数值操作,一律使用StringRedisTemplate或自定义好的RedisTemplate,绝不允许用默认序列化方式的模板。

存量脏数据的清理,我之前在实际项目里用过一个小脚本思路,先通过SCAN把符合前缀*的key全扫出来,再用客户端工具读出来反序列化后重建。这里重点提醒:别用KEYS *,线上数据量大时这是自杀命令,CPU会被直接拉满,其他请求全部卡住。用SCAN配合游标,一次扫几十个,循环处理。

4. 分布式锁的进化之路:从SETNX到Redisson看门狗

分布式锁这个话题,每次面试热度都很高,但很多人的理解还停留在“SETNX加锁、DEL解锁”这种幼儿园水平。我用一个真实案例说明白为什么有了SETNX还会翻车。

4.1 最原始的实现有多危险

大多数人的第一版分布式锁是这样写的:

Boolean success = redisTemplate.opsForValue().setIfAbsent(lockKey, "1"); if (success) { // 业务逻辑 redisTemplate.delete(lockKey); }

这版代码有三处刺眼的坑。第一,没有设置过期时间,业务逻辑一旦抛出异常或者进程卡死,锁永远不释放,后续所有请求全部阻塞。第二,就算后来有人加了expire(lockKey, 30, TimeUnit.SECONDS),这个操作和setIfAbsent是两步调用,如果setIfAbsent成功但进程在设置过期时间之前宕机了,锁依然是死锁。第三,删除锁的时候没判断持有者,A线程的锁可能被B线程直接删掉。

第二版有人会改成setIfAbsent(lockKey, value, 30, TimeUnit.SECONDS),这一步确实正确,官方提供了一个基于SET key value NX EX seconds的原子命令,写一条就能同时满足“不存在才设置”和“自动过期”。但释放锁的时候还是很多人直接DEL,这就要用到Lua脚本保证原子性。

4.2 SET NX PX原子命令 + Lua释放锁的正确姿势

我在项目中最终沉淀下来的原生Redis分布式锁逻辑是:加锁用SET key requestId NX EX 30000,释放锁时必须先校验requestId(一个UUID或者业务唯一标识),确认是自己持有的锁才删:

if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end

getdel两条命令不是原子的,如果先get发现是自己,还没来得及del锁就过期了,然后被另一个线程抢走锁,你再去del就会误删别人的锁。所以必须用Lua脚本把“校验+删除”合二为一,保证Redis服务端执行这段脚本期间不会插入其他命令。

requestId的作用是标识锁的持有者。没有这个标识时存在经典的“误删别人的锁”问题:线程A拿到锁,执行时间太长导致锁过期被自动释放,此时线程B获得锁并开始执行业务,A执行完老想着删锁,就把B的锁给删了,于是B和C同时进入临界区,分布式锁直接被击穿。

4.3 Redisson看门狗与RedLock的争议

原生方案虽然能用,但有个很痛苦的问题:如果业务执行时间超过锁的过期时间怎么办?设置30秒,业务跑了50秒,锁在20秒的时候就没了,谁来保证互斥?

Redisson解决这个问题的思路很巧妙——看门狗。当你调用lock.lock()且不传超时时间时,Redisson会启动一个后台任务,默认每10秒给锁续期一次,把过期时间重新拉回30秒。只要持有锁的线程还没释放,看门狗就持续续期;线程挂了,看门狗也跟着停,锁在30秒后自动过期释放。这就把“锁过期时间该设多长”这个问题彻底解决了,业务不用卡着时间拍脑袋定值。

RLock lock = redissonClient.getLock("order:pay:1001"); lock.lock(10, TimeUnit.SECONDS); try { // 核心业务逻辑 } finally { if (lock.isHeldByCurrentThread()) { lock.unlock(); } }

注意lock.lock(10, TimeUnit.SECONDS)这种带leaseTime的写法会禁用看门狗,到期后无论业务是否结束锁都会自动释放,适合明确不希望锁被长期持有的场景。不带leaseTime的lock.lock()才会触发看门狗机制。

RedLock这个话题属于“面试论道”级别:Martin Kleppmann提出分布式锁的正确用法,Antirez(Redis之父)给出了RedLock算法——向至少5个独立节点依次加锁,超过半数成功才认为加锁成功。但RedLock在工程界争议非常大,因为它依赖于系统时钟同步,而且一旦某个节点发生GC暂停、网络分区,仍然可能出现两个客户端同时拿到锁。我自己的态度很明确:绝大多数业务场景,比如秒杀防超卖、定时任务防重复执行、接口幂等,单节点Redis加锁配合看门狗已经足够,别为了追求理论上的绝对安全把高可用复杂度拉上天。

5. 缓存治理三板斧:穿透、击穿、雪崩的工程化解法

“缓存治理”这个词看着抽象,其实就是处理三个经典问题:缓存穿透、缓存击穿、缓存雪崩。这三个问题很多面试者能说出名字,但真要他们在代码里给出可落地的方案,就含含糊糊了。

5.1 缓存穿透:黑产最爱打的洞

缓存穿透指的是请求根本不存在的key,缓存查不到,直接打穿到数据库。比如一个商品详情接口,攻击者不停请求商品ID为负数或者不存在的编号,Redis里永远查不到,请求全部落到数据库,瞬间就能把库压垮。

解法有几层:

  • 参数校验:非法的ID在入口直接拦截,返回参数错误。
  • 缓存空值:即使数据库查不到,也把空值写进Redis,设置一个较短的过期时间(比如60秒)。这样短时间内的重复请求都能被缓存挡住,不会每次穿透到数据库。
  • 布隆过滤器:启动时把所有存在的ID加载到布隆过滤器里,查询前先判断ID是否存在,布隆过滤器判断“不存在”是绝对准确的,只是判断“存在”有极小概率误判。用Redis的bitmap自己实现也行,或者用Guava的单机布隆过滤器,数据量小直接应对。

我在实际项目里最常用“缓存空值+短TTL”这个方案,因为实现成本最低。布隆过滤器虽然优雅,但维护成本在业务数据变化频繁时也不小,适合本就不经常变化的ID集合。

5.2 缓存击穿:热点Key的生死时刻

击穿和穿透一字之差,问题完全不同。击穿针对的是热点key,比如一个爆款商品的详情页,平时成千上万请求都命中Redis。一旦这个key到了过期时间,在过期瞬间,大量请求同时发现缓存里没数据,于是一窝蜂打到数据库。

解决击穿有两个主流方向:

  • 互斥锁:当缓存miss时,先尝试获取一个分布式锁,只有拿到锁的线程能去查数据库并回填缓存,其他线程等待锁释放后再查缓存。这样数据库同一时刻只有一个查询压力,但缺点是一旦持锁线程慢,会拖慢整体响应。
  • 逻辑过期:不是真正给key设置TTL,而是把过期时间作为value的一部分存进去。查询时发现“逻辑上已过期”,先返回旧数据给调用方,同时异步触发一个任务去重建缓存并更新过期时间。这种方案的好处是用户体验无感,不会阻塞,缺点是一段时间内读到的数据不是最新的。

大促场景我倾向于“逻辑过期+异步重建”,尤其是热点详情页,宁可让用户看到几十毫秒前的数据,也别让他等一会儿。秒杀场景则要用互斥锁保护库存数据,不能出现短暂读旧值的情况。

5.3 缓存雪崩:批量过期与容灾

雪崩是同一时刻大量key一起过期,或者Redis集群整体故障,所有请求全部落到数据库,数据库被压垮,进而引发连锁故障。批量过期最典型的例子是:业务把缓存过期时间统一设成1小时,而且数据是集中生成的,于是每到整点就有大量key同时失效。

解法也不复杂:给过期时间加一个随机因子。比如基础过期时间60秒,实际设置为60 + RandomUtil.randomInt(0, 30)秒,让过期时间在60到90秒之间分散开。Redis集群层面要保证高可用,主从加哨兵是最基础的一层,防止单点宕机后请求全量落库。

前面提到的多级缓存也很有用:应用进程内加一层Caffeine本地缓存,Redis是第二级,数据库是第三级。本地缓存命中率虽然不如Redis高,但能扛住极端情况下的瞬时流量,尤其是Redis集群出故障时,本地缓存至少能给系统争取一个喘息的窗口。

6. 生产排障三板斧:日志、Big Key与慢查询定位

Redis在生产环境出了问题,最怕的是没有排查路径。很多人上来就redis-cli monitor一把梭,或者直接重启大法,问题复现了就靠猜。其实Redis自带的观测工具足够解决90%的问题,关键是怎么系统地用。

6.1 日志与监控参数:先看哪些指标

Redis的日志文件默认配置在redis.conflogfile参数,比如:

loglevel notice logfile /var/log/redis/redis-server.log

loglevel建议生产环境保持notice,除非你想被每天几百MB的debug日志灌爆磁盘。但日志文件只记录启动、关闭、持久化、主从切换这些重大事件,真正的性能问题要看运行时指标。

我用得最多的排查起点是INFO命令的几个关键段:

  • INFO stats里的keyspace_hitskeyspace_misses,看缓存命中率是否下降,命中率骤降往往意味着大量key同时过期或者被Flush。
  • INFO commandstats,能看到每类命令的调用次数和时间,如果发现某个命令的调用频率异常高,就能顺藤摸瓜找到代码里的问题。
  • INFO memory里的used_memorymem_fragmentation_ratio,观察内存占用与碎片情况,内存不够时会触发淘汰策略,大量的逐出会直接反映在evicted_keys计数上。

6.2 Big Key排查案例

有一次线上Redis CPU使用率频繁冲到90%多,我登录服务器后没有盲目重启,先执行:

redis-cli --bigkeys

这个命令会以游标方式遍历所有key,统计每种类型里个头最大的key。扫描结果发现有一个Set类型的key存储了几百万个元素,是某个活动页把用户ID全塞在同一个集合里,任何跟这个key相关的操作都极慢,还拖累了其他连接。

Big Key的危害在于,一次操作就可能阻塞Redis主线程很久。比如对一个几MB的String执行GET,传输给客户端的时间会占住线程,期间其他所有命令都在排队。删除Big Key也一样,DEL一个几千万元素的集合,可能阻塞服务好几秒,所以删除时要改成UNLINK命令,后台异步释放内存,Redis会立即返回响应。

6.3 慢查询定位:从命令统计到SQL化的Redis排查

Redis内置了慢查询日志,通过以下两个配置控制:

slowlog-log-slower-than 10000 slowlog-max-len 128

slowlog-log-slower-than的单位是微秒,10000微秒即10毫秒,超过这个时间的命令会被记录到慢查询日志。用SLOWLOG GET 5可以查看最近5条慢命令,里面会列出命令执行时间、命令参数。如果发现KEYSSMEMBERSHGETALL这类命令出现频率高,基本可以断定是代码层面用了全量扫描类操作,需要改成SCANSSCANHSCAN等游标遍历。

在定位到某个业务接口导致Redis变慢后,我通常会顺便看下redis-cli --latencyredis-cli --stat。前者能测试当前网络到Redis的延迟,后者用动态刷新的方式展示实时命令数、内存变化、客户端连接数、命中率等,几分钟内就能判断是网络问题、大Key问题还是客户端连接数打满了。

还有个容易被忽略的点:客户端连接数打满。默认maxclients是10000,如果某个服务没使用连接池,每次请求都新建连接,会让Redis频繁处理连接握手,INFO clients里的connected_clients会异常偏高。能用连接池就用连接池,比如Lettuce底层已经帮你管理了连接复用,不要再在业务代码里手动创建连接了。

排障这件事,我最大的心得是不要心急。Redis给出来的指标一定是为了让你看图说话,而不是让你靠猜。顺着commandstats找到具体命令,再顺着慢查询日志确定耗时命令,再结合--bigkeys--latency定位到对象和网络层,基本没有解不了的问题。我见过太多人一遇到Redis卡顿就盲改maxmemory-policy或者重启集群,这不叫排障,叫赌运气。

最后分享一个我个人的判断标准:使用Redis之前,先想清楚这个场景到底是在“加速读”还是在“临时存储”。Redis最擅长的是把大量读请求挡在数据库之前,但如果你发现业务里Redis的写入量甚至比数据库还大、过期时间设计得像玩笑,那问题大概率不是Redis慢,而是架构设计就没想明白。把上面这些原理和运维手段吃透,不是为了在面试时多拿几分,而是当你深夜被报警电话叫醒时,脑子里能立刻浮现出一条清晰的排查路径。

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

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

立即咨询