Redis实战:从缓存原理解析到Spring Boot集成全攻略

发布时间:2026/10/7 10:45:09
Redis实战:从缓存原理解析到Spring Boot集成全攻略 很多人学Redis第一步往往就错了——上来就敲几个set/get把它当成一个远程Map来玩。结果一到真实项目缓存穿透、序列化乱码、连接超时一批问题全炸出来然后再回头补原理代价就大了。今天我想按自己实际做项目的经验把Redis从缓存原理到Spring Boot集成的完整链路重新走一遍。这篇内容适合刚接触后端、或者已经在用Redis但一直停留在“存取字符串”阶段的朋友也适合准备面试前想系统梳理一遍缓存体系的人。我尽量不讲废话原理部分讲清楚“为什么”实操部分给到可以直接复制的配置和代码。你看完照着做一遍再回来看自己的项目很多之前含糊的地方会突然通掉。1. 先别急着敲命令把缓存原理想清楚1.1 缓存到底在解决什么问题后端业务里最典型的性能瓶颈就是数据库扛不住高频读。每次用户打开商品详情页后端都要查一次数据库SQL虽然不复杂但QPS一上来数据库的CPU和连接数就会受不了。这个场景里商品信息是典型的“读多写少、允许短时间不一致”的数据把它放到缓存里是最划算的。缓存不是Redis的发明。在单机时代大家会在应用进程里维护一个HashMap或者用Guava、Caffeine做本地缓存。本地缓存的好处是访问速度极快零网络开销但坏处也很明显应用多节点部署时每个节点各存一份数据改了一台机器其他节点还是旧值。而且进程重启缓存就没了。Redis这类外部缓存把数据挪到了独立进程里所有应用节点共享同一份天然解决了多节点一致性问题。加上Redis自带过期时间、持久化、主从复制、集群扩展这些能力它从一个简单的KV存储慢慢变成了后端架构里的“缓存中台”。但外部缓存有它的代价多一次网络Round Trip序列化开销。所以用Redis不是无脑地把所有数据都塞进去只有“热点数据”“读多写少的数据”“可以容忍短暂不一致的数据”才值得缓存。这个判断标准比任何技术细节都重要。1.2 为什么Redis能做到这么快很多初学者有个疑惑数据库也有内存缓冲Redis凭什么比MySQL快这么多关键不在于“内存”这两个字而是Redis的设计目标就是极简。第一数据完全放在内存里读写走内存不走磁盘。MySQL哪怕有Buffer Pool最终仍需处理磁盘页的刷盘和回滚事务里的redo log、undo log都是有代价的。Redis做纯内存操作天然把最慢的链路砍掉了。第二Redis的核心工作线程是单线程事件循环。这不是落后而是刻意设计。单线程意味着没有锁竞争没有线程切换开销也没有并发修改同一份数据的问题。配合I/O多路复用Linux上的epoll一个线程可以同时处理成千上万个客户端的读写请求事件来了才处理没有事件就休眠。这个模型让Redis在大多数场景下都能跑到十万级QPS。第三Redis内置的五种数据类型很多操作本身就是O(1)或O(logN)的原子操作不需要服务端再配合Lua脚本去保证多步操作的原子性。这也是为什么Redis能当中间件用比如实现分布式锁、简单消息队列、限流器。一条命令就是原子操作省掉了网络交互次数等于把性能留在了自己手里。有一点需要说明Redis 6.0之后引入多线程只是把网络数据的读写和协议解析放到线程池里并行处理。真正的命令执行依然是单线程。你理解成“接待前台多了几个窗口办事窗口还是一个”就对了。理解了这些你再看Redis的配置项比如maxmemory、持久化策略、慢日志心里就有数了——所有调优本质上都是在保护那个单线程的事件循环别让它被BigKey、慢命令、fork阻塞拖住。1.3 先建立一份“缓存使用的底层心智模型”我建议你在动手写Spring Boot代码之前先想清楚三件事什么数据可以缓存缓存怎么更新缓存失效了怎么办最经典的更新方式是Cache Aside旁路缓存。读的时候先查缓存缓存没有就去查数据库查到后回填缓存再返回。写的时候先更新数据库然后删除缓存而不是更新缓存。为什么要删缓存而不是更新缓存因为一次热点更新可能引发大量并发写每次都去更新缓存代价高还容易产生不一致删掉缓存让下次读取时再回填反而是最省事的做法。缓存失效的问题则聚焦在“穿透、击穿、雪崩”三个词上。穿透是查询一个根本不存在的数据每次都会绕过缓存打到数据库击穿是某个热点key刚好过期的一瞬间大量请求同时涌到数据库雪崩是大量key在同一时间段失效数据库瞬间压力陡增。这三个问题我在后面专门展开因为它们是面试高频题也是线上事故的主要来源。记住一个核心观念缓存是数据库的保护层不是业务正确性的兜底。任何时刻都要假设缓存可能丢失、可能过期、可能数据不一致所以缓存适合的场景一定是“丢了还能从数据库捞回来、短暂不一致可以接受”的业务。2. 零基础安装RedisWindows、macOS、Docker三条路都走一遍2.1 Windows用户别折腾官方安装包先说一个让很多人困惑的点Redis官网其实没有官方Windows安装包。你在Windows上装的Redis基本都是开源社区维护的移植版本其中最常用的是tporadowski/redis这个项目在GitHub上可以直接下载zip发布包。Windows下用免安装版最省事。下载zip包后解压进目录看到redis-server.exe和redis-cli.exe双击redis-server.exeRedis就起来了。默认端口6379然后打开另一个命令行窗口执行redis-cli.exe ping返回PONG就说明服务正常。注意Windows版的配置文件名是redis.windows.conf系统服务安装命令是redis-server --service-install redis.windows.conf。有个细节很多人忽略Windows版的Redis默认不是后台运行而是保持前台模式命令行窗口不能关。如果想让它在后台跑可以用redis-server --service-start启动系统服务。另外Windows版在生产环境不建议使用文件系统、网络栈和Linux差异比较大官方都不能保证性能但用于学习、开发联调完全够。2.2 macOS一条命令装好macOS用户最幸福Homebrew一句搞定brew install redis装完之后配置文件一般在/opt/homebrew/etc/redis.conf。前台启动用redis-server如果想让它在后台常驻可以执行brew services start redis。启动后同样用redis-cli ping验证。macOS本地调试还需要注意一点新版Redis默认绑定的配置如果你之前装过老版本bind和protected-mode可能不一样连不上时先看配置文件里bind和requirepass别一上来就怀疑网络。2.3 还是推荐用Docker装最接近生产环境如果你是想正经学Redis而不是只想在本机跑个demo我强烈建议用Docker。因为生产环境里Redis基本都是容器化或云化部署Docker方式能让你顺便学会数据卷、配置文件挂载、主从这些概念。单机版一条命令docker run -d --name redis \ -p 6379:6379 \ -v redis-data:/data \ redis:7.2 redis-server --appendonly yes这里-v redis-data:/data是持久化数据卷--appendonly yes开启AOF持久化。容器删了数据还在这个习惯从学习阶段就要养成。更规范一点用docker-compose管理version: 3 services: redis: image: redis:7.2 container_name: redis restart: always ports: - 6379:6379 volumes: - ./redis/conf:/usr/local/etc/redis - ./redis/data:/data command: [redis-server, /usr/local/etc/redis/redis.conf]很多人在Docker拉取镜像时遇到过类似“docker search redis request returned 500 internal server error”的报错。这个大概率不是Redis的问题是你的Docker引擎访问镜像仓库不稳定。解决办法是给Docker配置registry mirror国内主流云厂商都有镜像加速地址配置好之后重新拉取问题就消失了。不要指望换个搜索引擎或者用乱七八糟的第三方源把镜像源配好才是最稳的。2.4 装完怎么确认它真的没问题启动之后第一步别急着写代码先做三个验证。第一个是基本连通性redis-cli ping返回PONG。第二个是性能基线在容器里跑一下redis-benchmarkredis-benchmark -h 127.0.0.1 -p 6379 -n 100000 -q这个命令会打印各类操作的QPS。我这里单机一般能到十几万甚至更高如果你的结果特别低先怀疑网络和虚拟机环境再怀疑配置。第三个是确认持久化目录里真的生成了文件。执行了--appendonly yes之后数据目录里会出现appendonly.aof文件。很多人忽略了这一步后来排障时才发现自己根本没用持久化。可视化管理工具方面Redis官方有Redis Insight社区流行的有Another Redis Desktop Manager两者都能连接、查看key、执行命令行。我的建议是刚学习头两周别依赖图形工具先用redis-cli敲命令。等你对Redis命令有肌肉记忆了再用图形工具提升排错效率否则你会变成“只会点点点”的使用者原理一问三不知。3. 背下这五种数据结构才能不被面试官问倒3.1 五大数据类型本质上对应了五种解题思路Redis基础知识的核心就是这五大数据类型。我见过太多人只在面经上背概念真让写命令就手忙脚乱。这里我按“场景反推数据类型”的方式给你整理一遍比死记命令要高效得多。类型底层实现经典场景常用命令String简单动态字符串(SDS)缓存对象、计数器、分布式ID、Sessionset/get/mset/incr/decr/expireListquicklist(双向链表压缩列表)消息队列、时间线、最新文章列表lpush/rpush/lpop/rpop/lrangeHashlistpack/hashtable商品对象、用户信息、属性频繁修改的场景hset/hget/hgetall/hincrbySetintset/hashtable去重、抽奖、共同好友、标签sadd/srem/sismember/sinter/sunionZSetskiplistdict排行榜、延迟队列、积分排名zadd/zrangebyscore/zrevrange/zremString不只存字符串。计数器就是典型的String应用比如微博点赞数用incr命令一行搞定比SQL的update之后再select不知道快多少。很多人面试时被问“缓存一个对象用String还是Hash”容易慌其实标准答案是如果对象属性会频繁改动用Hash一次只改一个字段代价小如果对象整体读多改少直接用String存JSON简单直接。List适合做消息队列因为lpush加brpop可以模拟阻塞队列。现在Redis官方也推荐List做消息队列的轻量方案但要注意如果消息需要可靠投递、多消费者回溯还是得上专业的M Q。ZSet是面试常客。它的底层是跳表加哈希表跳表保证排序和范围查询哈希表保证O(1)的单值查找。排行榜是最典型的应用比如直播打赏榜zadd一次写入zrevrange取TopN一个命令搞定。延迟队列也能用ZSet实现member是任务内容score是时间戳定期zrangebyscore取出到期任务执行。3.2 过期时间、内存淘汰和缓存治理Redis缓存区别于本地缓存的另一个关键是每个key都可以设置过期时间。命令是expire key secondsttl key可以查看剩余时间。缓存治理的第一步就是把每个缓存key的TTL想清楚验证码5分钟登录token按业务定商品详情页可能10分钟热点榜单可能只有1分钟。TTL看起来简单背后却是“惰性删除加定期删除”配合工作的机制。惰性删除是每次访问key时检查它是否过期过期了就删掉定期删除是后台周期性地抽查一批过期key并清理。这里就有个坑如果大量key同时过期惰性删除根本来不及处理内存一直在涨所以要合理错开过期时间。内存淘汰策略是缓存治理的另一个核心。当你给Redis设置了maxmemory上限写入新数据后内存超限Redis会根据maxmemory-policy策略淘汰。常见的几个策略noeviction不淘汰直接报错写操作失败。allkeys-lru从所有key中按LRU最近最少使用淘汰。volatile-lru只在设置了过期时间的key中按LRU淘汰。allkeys-lfu按LFU最不经常使用淘汰。volatile-ttl优先淘汰剩余TTL最短的key。实际选型记住一个原则只把Redis当缓存用allkeys-lru或allkeys-lfu把Redis当存储用了那要结合业务谨慎选volatile相关的策略否则数据库恢复时会发现非热数据全没了。现在可以讲一个新的点Redis 6以后建议优先考虑LFU策略它对热点数据的判断更精确能在缓存命中率上表现更好。命令上可以用config set maxmemory 256mb config set maxmemory-policy allkeys-lru运行期动态修改后记得用config rewrite把配置持久化到配置文件里否则重启就丢了。3.3 持久化机制详解RDB和AOF怎么选才对Redis持久化是热词里的高频主题也是很多项目“重启丢数据事故”的根源。Redis持久化分为两种RDB快照和AOF日志。RDB快照是把整个内存数据在某一个时刻存到磁盘的二进制文件里。触发方式有手动bgsave、save配置规则、shutdown时自动保存。它的优点很突出文件紧凑、恢复速度快适合备份和主从初始同步。缺点是它做不到实时持久化因为生成快照的成本不低。Redis会自动fork子进程来生成RDB文件fork的瞬间如果内存里的数据量很大父进程短暂阻塞在所难免。AOF则是把每一条写命令追加到日志文件里类似数据库的redo log。appendfsync配置有三个选项always每写一条命令就同步磁盘最安全性能最差。everysec每秒同步一次性能和安全的均衡。no交给操作系统决定同步时机性能最好但可能丢秒级数据。生产环境最常用的是everysec丢失量在1秒以内性能基本不受影响。AOF的问题在于文件会不断膨胀所以Redis提供AOF重写机制把日志压缩成只包含当前数据状态的最小命令集。重写过程同样是fork子进程完成。现在Redis默认开启混合持久化aof-use-rdb-preamble yes具体说就是AOF文件头部用RDB格式记录全量数据后面追加增量命令。这样兼顾了恢复速度和数据完整性。我的建议是纯缓存场景可以只开RDB甚至不持久化有业务数据的场景开AOF everysec加混合持久化同时别忘了主从复制和定期备份才是数据安全的最后一道防线。3.4 缓存穿透、击穿、雪崩一次讲透这三个词不像理论更像事故模型。每个都需要你说清楚“是什么、怎么解决、有什么代价”。缓存穿透是查询不存在的key比如用户查一个不存在的商品ID缓存没有数据库也没有每次请求都打到数据库。解决办法两个主流一是布隆过滤器把所有存在的数据ID提前写入布隆过滤器查不到直接返回但这有误判率而且数据变化时更新麻烦二是空值缓存把不存在的key也缓存一个空值TTL设短一点比如5分钟代价是Redis里会有大量空key可能被攻击者利用。我的经验是小项目用空值缓存大流量防护必须上布隆过滤器。缓存击穿是热点key过期瞬间大量请求打入数据库。最常见的做法是互斥锁缓存没查到数据时先抢一把分布式锁抢到锁的线程去数据库加载并回填缓存其他线程拿不到锁只能短暂等待或重试查缓存。另一个方案是逻辑过期缓存里存一个“逻辑过期时间”读线程发现逻辑过期先返回旧数据同时异步线程去刷新缓存用户无感知。缓存雪崩比击穿更难防因为规模不一样。大量key同一时间过期或者Redis实例宕机导致数据库被打挂。解决办法过期时间加随机值比如TTL设置成base加随机0到300秒多级缓存兜底本地缓存挡一层做熔断和限流数据库扛不住时直接返回降级数据高可用部署哨兵加集群别让单点Redis成为雪崩的起点。4. Spring Boot集成Redis从配置到缓存注解再到分布式锁4.1 加依赖和基础配置Spring Boot集成Redis核心依赖只有一个dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-redis/artifactId /dependency如果打算用连接池还需要commons-pool2dependency groupIdorg.apache.commons/groupId artifactIdcommons-pool2/artifactId /dependency这里要留意Spring Boot版本差异。旧版2.x的配置前缀是spring.redis从Spring Boot 3.x开始改成了spring.data.redis。很多老程序员看新项目的配置文件容易在这上面栽跟头。以Spring Boot 3.x为例spring: data: redis: host: 127.0.0.1 port: 6379 password: database: 0 timeout: 3s lettuce: pool: max-active: 16 max-idle: 8 min-idle: 4很多初学者不加连接池高并发一上来就报connection exhausted或者timeout。因为Spring Boot默认用Lettuce作为Redis客户端Lettuce本身是异步非阻塞的但不配置连接池高并发下创建连接的开销会被放大。上面的max-active控制最大活跃连接数建议结合你的实际并发调整16这个值适合多数中低并发项目压测时如果超时明显可以试着往上加。4.2 序列化最常见的乱码和类型转换问题Spring Boot集成Redis后你很快会遇到一个新问题用RedisTemplate存取value发现key变成了“\xac\xed\x00\x05t\x00...”这种眼晕的乱码。原因是RedisTemplate默认使用JDK序列化序列化结果不是纯文本而且体积大、可读性差、跨语言基本没戏。正确做法是自定义RedisTemplate的序列化器。核心方案key用StringRedisSerializervalue用JSON序列化器。Configuration public class RedisConfig { Bean public RedisTemplateString, Object redisTemplate(RedisConnectionFactory factory) { RedisTemplateString, Object template new RedisTemplate(); template.setConnectionFactory(factory); StringRedisSerializer keySerializer new StringRedisSerializer(); GenericJackson2JsonRedisSerializer valueSerializer new GenericJackson2JsonRedisSerializer(); template.setKeySerializer(keySerializer); template.setHashKeySerializer(keySerializer); template.setValueSerializer(valueSerializer); template.setHashValueSerializer(valueSerializer); template.afterPropertiesSet(); return template; } }GenericJackson2JsonRedisSerializer有一个特点它写入Redis时会附带一个class字段记录原始类型。好处是反序列化时能恢复成正确的具体类坏处是带来额外空间开销而且当你的DTO类改了包名或字段后旧数据可能反序列化失败。所以更稳妥的做法是自定义ObjectMapper把default typing功能控制住或者干脆在业务层统一用StringRedisTemplate手动做JSON序列化。我的建议是中大型项目中直接custom一个RedisTemplatevalue序列化用Jacksonkey这一侧严格用String。同时规范key的命名统一加前缀比如user:info:123、order:lock:456。前缀的主要作用不是好看是让不同业务的key天然隔离也方便用scan按前缀排查和清理。没有前缀的Redis过一个月再看就是一座“垃圾山”。4.3 用Spring Cache注解简化缓存代码手动使用RedisTemplate在业务里写get/set代码会越来越碎。Spring Cache把缓存操作包装成了注解可以少写很多样板代码。启用方式很简单启动类或配置类加EnableCaching。然后定义一个RedisCacheManager把默认TTL、序列化方式设好Bean public RedisCacheManager cacheManager(RedisConnectionFactory factory) { RedisCacheConfiguration config RedisCacheConfiguration.defaultCacheConfig() .entryTtl(Duration.ofMinutes(30)) .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())) .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer())) .disableCachingNullValues(); return RedisCacheManager.builder(factory) .cacheDefaults(config) .build(); }之后在Service方法上加注解Cacheable(cacheNames user, key #userId) public User getUserById(Long userId) { return userMapper.selectById(userId); } CacheEvict(cacheNames user, key #userId) public void updateUser(Long userId, User user) { userMapper.updateById(user); }Cacheable是“先查缓存没有就执行方法并回填缓存”适合查询场景。CacheEvict是“执行方法后删除缓存”适合更新和删除场景。CachePut则适合“执行方法并强制更新缓存”但实际用的少因为大部分业务场景删除缓存就够了。这里有几个坑必须提醒你。第一个Cacheable默认只缓存非空返回值拿回来如果是null说明方法返回了null缓存没生效得配合if判断。第二个key的SpEL表达式写错了会直接运行时抛异常比如key #user.getId()如果你没写括号整个User对象被拼进key结果还是可以跑但效率很低因为序列化大对象做key。第三个缓存注解不能解决缓存穿透如果存在大量查不存在的ID的请求每个请求都会执行方法穿透问题依旧存在。底层要靠前面说的空值缓存或布隆过滤器单靠注解是挡不住的。4.4 分布式锁从手写SET NX到RedissonSpring Boot项目里用Redis做分布式锁是面试和实战都绕不开的高频题。先说手写版的思路。最核心的是加锁姿势要原子化典型的命令是SET lockKey requestId NX EX 30NX表示只有key不存在时才设置成功EX表示过期时间30秒。这个一个命令就完成了加锁比“先SETNX再EXPIRE”两步操作安全得多后者如果中间进程崩溃会导致锁没有过期时间永远死锁。requestId建议用UUID后面的解锁要通过它校验锁的占有者。释放锁不能直接del因为有可能你刚加完锁锁到期自动释放了另一个线程又成功加了锁这时你执行del就把别人的锁给删了。所以解锁必须用Lua脚本先比对requestId再删除保证原子性if redis.call(get, KEYS[1]) ARGV[1] then return redis.call(del, KEYS[1]) else return 0 endJava侧调脚本String script if redis.call(get, KEYS[1]) ARGV[1] then return redis.call(del, KEYS[1]) else return 0 end; stringRedisTemplate.execute(new DefaultRedisScript(script, Long.class), Arrays.asList(lockKey), requestId);手写锁的硬伤在于锁过期时间不好定。业务没执行完锁先到期了后续代码就会被并发访问锁设太长一旦机器宕机别的线程等着也进不去。所以生产项目建议直接用Redisson它内置了看门狗机制加锁后自动续期默认每10秒续一次业务执行完自动释放整个过程不用你关心过期时间。RLock lock redissonClient.getLock(order:lock: orderId); boolean locked lock.tryLock(3, 30, TimeUnit.SECONDS); if (locked) { try { // 业务逻辑 } finally { lock.unlock(); } }分布式锁只解决互斥不解决缓存一致性。你锁定了操作但其它没有获取锁的请求还是会读到旧缓存数据。另外不要把所有操作都加分布式锁锁本质上是串行化滥用会直接把Redis的性能优势抵消掉。能用原子命令解决的比如incr、sadd根本不需要锁。4.5 用Spring Boot Admin把Redis盯起来问题出现之前最好把它扼杀在监控里。Spring Boot Admin配合Actuator可以观察Redis的连接和状态。先加依赖dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-actuator/artifactId /dependency dependency groupIdde.codecentric/groupId artifactIdspring-boot-admin-starter-client/artifactId /dependency配置暴露端点management: endpoints: web: exposure: include: health,info,metrics然后你自己的Spring Boot Admin服务端注册客户端后在Admin界面里能看到应用的health信息其中就包括Redis的连通状态。但这个深度不太够更推荐直接在redis-cli里定期执行INFO命令关注几个核心指标connected_clients当前连接数暴涨可能说明连接泄漏。used_memory内存占用接近maxmemory时要注意淘汰策略是否生效。total_commands_processed累计命令数用于计算QPS趋势。keyspace_hits和keyspace_misses命中率命中率突然下降通常是大面积缓存失效。instantaneous_ops_per_sec瞬时QPS排查慢操作时的辅助指标。慢日志也是排查利器。执行下面的命令看当前哪些命令超过阈值redis-cli slowlog get 20 redis-cli config get slowlog-log-slower-than生产环境把slowlog-log-slower-than设置成10毫秒比较合理超过这个值的命令就是重点怀疑对象。执行slowlog get后输出里有耗时、命令参数和客户端地址按这个线索再去找对应的业务代码效率会高很多。5. 生产中高频问题排查与经验速查5.1 Lettuce超时Redis command timed out到底怎么解把Spring Boot项目放到生产环境后最常见的异常之一就是Redis command timed out; nested exception is io.lettuce.core.RedisCommandTimedOutException这个报错想表达的其实很简单Redis命令在指定时间内没有返回。但根因排查起来可能有很多方向。按我的经验排查顺序一般是这样的。首先查Redis服务端有没有阻塞。执行redis-cli info stats看blocked_clients字段大于0说明有命令在长时间阻塞。再执行slowlog get看有没有超过几十毫秒甚至几百毫秒的慢命令。最典型的元凶是BigKey操作比如对一个几百万元素的list执行lrange 0 -1或者对一个超大字符串执行get网络传输和序列化都会很慢锁住事件循环之后后续所有客户端都会超时。然后看Redis的CPU和内存。如果Redis所在机器的CPU被打满单线程事件循环一样会被拖慢。再看应用这边的连接池如果max-active太小高并发下的请求全部在等待连接也会表现为command timed out。这时的现象可能是时好时坏过一会儿自己恢复。还有一类容易被忽略的是客户端GC停顿。Java应用Full GC时整个JVM会暂停Redis请求发出去了但没有线程去接收结果也会判断成超时。这类问题看应用监控里的GC日志和停顿时间就能确认。如果服务端没问题只是单纯并发量大那先调大连接池参数再加大客户端的超时时间比如从3秒调到5秒。还是不行就评估换JedisJedis的连接模型更简单同步阻塞出现问题更好排查。这不是说Lettuce不好而是Lettuce基于Netty底层异步模型在一些连接长期空闲、断线重连的场景下表现没有同步模型直观。5.2 BigKey和热点Key治理Redis缓存治理里BigKey值得专门讲。所谓BigKey通常指单个String类型的value过大比如几MB的JSON串或者单个Hash/List集合里有几十万条数据。BigKey的危害不是内存占用高而是它会把Redis的单线程事件循环拖下水。一个几MB的value通过网络传给客户端传输期间Redis不能处理其他命令。更危险的是在Redis Cluster下BigKey会导致某个节点内存倾斜存储不是均匀分散的。删除BigKey也可能阻塞RedisDEL一个大key会同步回收内存别人的命令就得等。定位BigKey用这个命令redis-cli --bigkeys它会扫描所有key并按类型列出最大的key。注意它不能精确到字符串的字节数需要更深入排查时再用scan配合memory usage key单独查。治理BigKey的核心思路就两个字拆分。String类型的大value拆分业务维度比如一个用户的所有数据拆成多个小key。集合类型的大key可以按业务逻辑分片比如Hash按日期或用户ID取模拆成多个小Hash。还有一个冷门技巧删除大key时用unlink替代delunlink是异步删除先把key从键空间摘掉内存回收交给后台线程不会阻塞主线程。热点Key也是治理重点。当一个key的访问量占掉Redis的大部分QPS比如大促的秒杀商品、爆款话题单节点Redis会带宽打满。解决办法热点key加本地缓存把压力挡在应用进程内或者做读写分离把读流量分摊到多个Redis从节点。但要注意加了本地缓存后数据一致性窗口会变长需要业务上接受。5.3 常见问题速查表我把这几年带团队和做项目时经常遇到的Redis相关报错和解决思路整理成了一张速查表哪里遇到问题翻哪一行现象可能原因定位思路解决方案Connection refused端口未监听、防火墙拦截本机telnet ip 6379检查redis进程、防火墙放行、容器端口映射NOAUTH Authentication required服务端设置了requirepass客户端没带密码看redis.confSpring Boot配置中加password字段Redis key显示“\xac\xed”乱码RedisTemplate默认JDK序列化查看key的二进制内容自定义序列化器key用StringRedisSerializerOOM command not allowed when used memory maxmemory内存达到上限且淘汰策略是noevictioninfo memory看used_memory调整maxmemory-policy清理无意义keyMISCONF Redis is configured to save RDB snapshotsRDB写入失败磁盘空间不足或权限问题查看日志中rdb相关报错清理磁盘、调整持久化路径和权限CROSSSLOT Keys in request dont hash to the same slot集群模式下多key操作不在同一个槽位了解cluster keyslot使用Hash Tag让相关key落在同一槽READONLY You cant write against a read only replica客户端连到了从节点但执行写命令查看replication信息写请求走主节点配置分库分流这个表不是让你死记而是要形成条件反射看到报错先确认是客户端问题还是服务端问题再确认是配置问题还是数据问题。排查时多用info和slowlog少用keys *。5.4 我自己的排查习惯踩过太多坑之后我形成了一套固定的排查习惯。先说最简单的顺序先看Redis进程活着没活着就执行redis-cli info看内存、连接数、命中率然后slowlog get看有没有慢命令再看客户端连接池有没有被打满最后才怀疑代码写得有问题。这个顺序的价值在于它从代价最小的排查开始。查进程一秒钟看info十秒钟而定位代码问题可能需要翻日志加调试耗时差着数量级。还有一个容易被忽略的小技巧遇到疑似超时问题时先在Redis所在机器上执行redis-cli -h 127.0.0.1 ping。如果本机ping都慢那是Redis服务端有问题如果本机秒回但应用慢那就是网络和应用客户端的问题。这个二分法在绝大多数场景下都能快速缩小排查范围。另外我建议每个项目都配一套简单的Redis运维脚本比如定时执行bigkeys扫描、慢日志拉取、内存快照保存。这些脚本不复杂但线上出问题时历史数据能帮你论证“是不是突然出现了BigKey”“是不是内存在某一段时间波动了”比抓现场瞎猜要靠谱得多。