资讯动态

Redis零基础到实战:缓存原理、Spring Boot集成与高并发避坑指南

发布时间:2026/10/4 3:42:29 来源:尧图企业网站定制
前两天一个刚转 Java 的朋友问我接口逻辑明明不复杂数据量一上来就慢得像蜗牛加索引也救不回来该怎么办我第一反应是反问他你查的数据真的需要每次都从数据库读吗很多业务场景压根不用让数据库扛全部压力在中间加一层缓存就能解决大半问题。而这个“缓存”绝大多数项目里选的都是 Redis。这篇文章我会从零开始把 Redis 的缓存原理、常用数据类型、安装方式到 Spring Boot 集成实战完整走一遍重点放在几个新手最容易踩坑的环节序列化配置、缓存失效场景、缓存一致性、分布式锁以及线上最常见的 timeout 异常排查。内容不追求大而全但每段都是我在实际项目里验证过的东西适合刚接触 Redis 的开发者也适合想系统整理一下缓存知识的后端工程师。1. 先搞懂 Redis 到底在解决什么问题1.1 内存和磁盘的速度差是缓存存在的根本原因很多人把缓存理解得很玄乎其实它的本质一句话就能说清把频繁读取的数据放到更快的地方用空间换时间。CPU 访问 L1 缓存大概 1ns 级别访问内存大概是 100ns 级别而一次 SSD 随机读大约 50μs 到 100μs机械硬盘更慢。数据库的瓶颈绝大多数不在计算而在磁盘 I/O。如果你的系统一天有 100 万次请求每次都穿透到数据库做磁盘操作压力自然大。Redis 之所以快是因为它把数据放在内存里单线程处理命令、避免上下文切换和锁竞争官方 benchmark 能跑到 10 万 QPS这种差距在业务上体现得非常明显。你可以类比一下日常出行手机里存了常用的公交卡你每次刷卡是直接调出手机上这张卡如果每次坐车都要回家翻出实体卡再出门效率就完全不一样了。缓存就是那张随身携带的卡。1.2 Redis 是中间件不是单纯的“内存数据库”很多人提到 Redis 只想到“缓存”但它实际是一个数据结构服务器支持字符串、哈希、列表、集合、有序集合等多种类型还能做分布式锁、排行榜、消息队列轻量实现、限流计数等用途。更重要的是Redis 是独立部署的中间件不是跑在应用进程里。这意味着多个应用实例可以共享同一份缓存数据这就是分布式缓存的核心价值。假如你把缓存放在应用内存里比如 Caffeine、Guava Cache单实例没问题一旦服务扩容成 3 个节点每个节点的缓存是独立的数据不一致命中率也被拆散了。所以项目里常见的组合是本地缓存做一级缓存Redis 做二级缓存既享受本地内存的超低延迟又保证跨节点的数据共享。入门阶段你可以先不管本地缓存直接把所有缓存压力交给 Redis理解它的定位比掌握命令更快。2. 五种最常用的数据类型选型才是核心功力2.1 String 和 Hash业务里用得最多的两种String 是最基础的类型value 最大 512MB。常见场景是缓存一个简单的值验证码、token、用户状态、计数器。它有一个非常实用的特性是原子自增用INCR命令就能实现文章浏览量统计不需要先 GET 再 SET天然防并发覆盖。Hash 适合存对象。如果用 String 缓存一个用户对象通常是把对象 JSON 序列化成字符串整体写入但如果你的业务需要频繁改对象里某一个字段比如刷新用户头像用 Hash 就能单独操作某个 field避免整存整取。下面这组操作你可以在 redis-cli 里直接试试HSET user:1001 name 张三 age 25 HGET user:1001 name HINCRBY user:1001 age 12.2 List、Set、ZSet从“存数据”到“做功能”List 底层是链表或者压缩列表适合做消息队列的简易版生产者LPUSH消费者RPOP。如果队列空了就BRPOP阻塞等待省去轮询。这个方案够轻量但可靠性不如专业 MQ消息可能丢失重试机制也需要自己设计。Set 是无序、去重的集合。典型场景是抽奖、点赞去重、共同好友。SADD加元素SCARD看数量SINTER取两个集合的交集比如“A 和 B 都关注了谁”这种需求一条命令就出来。ZSet 在 Set 基础上加了分数score按分数自动排序。排行榜就是这个类型的天下比如主播打赏榜、游戏积分榜。ZADD leaderboard 100 user1然后ZREVRANGE leaderboard 0 9取前 10 名配合ZINCRBY还能实时加分。2.3 选型建议不要什么都用 String我见过不少项目不管什么数据一律set(key, JSON字符串)这能用但会有几个问题一是无法利用 Redis 的原子操作读写需要反序列化二是哈希、集合这些类型的特性你完全用不上三是大字符串容易形成大 key后续排查和淘汰都很麻烦。给你一张我用下来的选型表数据类型底层核心结构典型命令推荐场景StringSDS 动态字符串SET / GET / INCR / EXPIRE验证码、计数、简单 KV 缓存Hash哈希表 / 压缩列表HSET / HGET / HINCRBY对象字段缓存、购物车List双向链表 / 压缩列表LPUSH / RPOP / LRANGE轻量队列、最新列表Set哈希表 / 整数集合SADD / SISMEMBER / SINTER去重、抽奖、好友关系ZSet跳表 哈希表ZADD / ZREVRANGE / ZINCRBY排行榜、积分排序选择时先问自己三个问题这个数据是单值还是对象要不要做成员关系判断要不要排序理清楚之后类型基本就定了。3. 环境准备本机安装和 Docker 两种方式3.1 用 Docker 跑 Redis 最省心我个人的习惯是开发环境用 Docker 跑 Redis几秒钟就能起一个实例不污染宿主机环境。下面这条命令可以快速启动一个最简 Redisdocker run -d --name redis \ -p 6379:6379 \ -e TZAsia/Shanghai \ redis:7.2-alpine解释一下关键点-d表示后台运行--name给容器命名方便管理-p 6379:6379把容器的 6379 端口映射到宿主机。这里没有挂载数据卷和配置文件适合快速体验如果要长期使用建议把数据和配置文件都挂载出来否则容器删掉数据就没了。如果你需要主从复制的环境可以用 docker-compose 起一个主节点加两个从节点关键配置是replicaof参数从节点指定主节点的 IP 和端口。入门阶段先不急着搭主从单机跑通功能更重要但你要知道 Redis 的主从架构是存在的后续高可用靠的是这个。3.2 Mac 本机安装和 Windows 简单体验Mac 用户可以直接用 Homebrewbrew install redis redis-serverWindows 官方没有正式支持 Redis但可以用 WSL2 跑 Linux 版本或者使用 Memurai 这类兼容实现做本地测试。个人更建议 Windows 用户直接用 Docker Desktop省去很多编译和兼容问题。无论哪种方式装完先用redis-cli -p 6379连上去输入ping返回PONG就说明服务正常。3.3 可视化客户端趁手的工具能省不少排查时间命令行redis-cli是基本功但看 key、检查内存、排查大 key 时图形界面效率更高。我常用的两个客户端工具是 Redis Desktop Manager简称 RDM和 Another Redis Desktop Manager简称 ARDM。RDM 是老牌工具界面成熟但新版有商业限制ARDM 是对新手更友好的免费替代品跨平台支持。配置连接时只需要填 host、port 和 password测试环境没有密码的话留空即可。工具的作用主要是可视化看 key 分布、查看单个 key 的 TTL、执行命令时自动提示。但生产环境操作我建议还是以redis-cli为主尤其删除 key、修改配置这类操作图形界面容易误点命令行反而更可控。4. Spring Boot 集成连接、序列化与 RedisTemplate4.1 引入依赖和基础配置这一步不算复杂但有不少新手在这就卡住了。Spring Boot 集成 Redis 官方推荐的方式是spring-boot-starter-data-redis它封装了 RedisTemplate 和连接管理。引入依赖dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-redis/artifactId /dependency然后在application.yml里配置连接信息spring: data: redis: host: 127.0.0.1 port: 6379 password: database: 0 timeout: 3s lettuce: pool: max-active: 8 max-idle: 8 min-idle: 0这里有个容易踩的版本坑Spring Boot 2.x 和 3.x 的配置路径不同。2.x 用spring.redis.*3.x 改成spring.data.redis.*。如果你升级了 Spring Boot 版本但配置没跟着改会发现连接不生效。社区里讨论过不少spring boot 2.3.x和2.6.x的配置差异本质都是这个原因。timeout是客户端读取超时时间建议设置一个 2-5 秒的值避免某个 Redis 操作卡住导致整个接口变慢。连接池的max-active要看你的并发量默认 8 一般够用但压测到高并发时最好重新评估。4.2 序列化大多数线上问题的根源之一用 RedisTemplate 写入数据后你在 RDM 里看到一堆类似\xAC\xED\x00\x05t...的乱码就是序列化方式没配好。Spring Boot 默认使用 JDK 序列化它有明显的缺陷产生的内容可读性差、体积大、跨语言不友好而且写入的 Java 对象如果没实现 Serializable 接口直接报错。我建议的配置方案是key 使用StringRedisSerializervalue 使用GenericJackson2JsonRedisSerializer。这样 key 是纯字符串可读性好value 是 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序列化对象时会在 JSON 里写入class类型信息反序列化时才能还原成正确的对象类型。但这也意味着如果有人篡改了 Redis 里的 JSON 内容反序列化时可能有类型安全问题。内部系统问题不大但如果你对安全性敏感建议改用自定义序列化方式或者使用 StringRedisTemplate 手动序列化。4.3 常用操作封装配置好 RedisTemplate 后日常操作主要围绕 opsForXxxAutowired private RedisTemplateString, Object redisTemplate; // 写入并设置过期时间 redisTemplate.opsForValue().set(user:1001, user, 10, TimeUnit.MINUTES); // 读取 User user (User) redisTemplate.opsForValue().get(user:1001); // 删除 redisTemplate.delete(user:1001); // 设置过期时间 redisTemplate.expire(user:1001, 30, TimeUnit.MINUTES); // Hash 操作 redisTemplate.opsForHash().put(user:1001, name, 张三);实战中我通常会给缓存 key 加一个统一的前缀比如user:1001、order:2001这样在 RDM 里可以通过前缀快速过滤同一类业务数据排查问题的时候非常有帮助。另一个细节是尽量在项目里统一一个缓存工具类不要每个 service 里直接redisTemplate.opsForValue().set否则缓存 key 的定义散落各处后续变更 key 格式的时候你会想骂人。5. 缓存实战从用户查询场景拆解完整链路5.1 不加缓存的查询为什么扛不住假设你有一个用户详情接口每天被调用几十万次每次都执行这么一段逻辑public User getUser(Long id) { // 每次都查数据库 return userMapper.selectById(id); }数据库连接资源是有限的一次查询 5ms如果把并发打满数据库连接池一耗尽接口延迟直接飙到秒级。这时候你调优 SQL、加索引的效果都有限因为瓶颈不在单条 SQL而在整体请求量。加入 Redis 缓存后绝大多数请求会在内存里直接拿到数据返回只有少量请求——比如缓存过期后的那一个——会落到数据库。这就是缓存对系统吞吐量的巨大提升。简单说把热点数据从“每次磁盘读”变成了“绝大多数内存读”。5.2 加缓存的正确姿势改造后的逻辑是标准的 Cache Aside 模式public User getUser(Long id) { String key user: id; // 1. 先查缓存 Object cached redisTemplate.opsForValue().get(key); if (cached ! null) { return (User) cached; } // 2. 缓存未命中查数据库 User user userMapper.selectById(id); if (user null) { return null; } // 3. 回填缓存设置过期时间 redisTemplate.opsForValue().set(key, user, 30, TimeUnit.MINUTES); return user; }这里有两个容易被忽略的细节。第一个查数据库之后一定要判断是否为空空值直接返回不要写入缓存否则会导致每次查询都穿透到数据库。当然如果某个 key 经常被查询且数据库中不存在可以考虑把空值也缓存几分钟专门应对缓存穿透问题。第二个回填缓存务必设置过期时间没有 TTL 的缓存 key 一旦写入就永久驻留内存时间久了会成为内存炸弹。5.3 缓存过期时间怎么定过期时间没有标准答案但有个原则和业务容忍度以及数据变更频率匹配。比如用户基本信息几天才变一次可以缓存 30 分钟甚至 1 小时库存量变化极快只适合缓存几秒到十几秒配置类数据变更频率低可以缓存 1 小时以上。我给你的建议是宁可短一点不要过长。缓存时间过长数据更新后用户看到旧信息的时间就越长特别是支付、库存这类强一致性场景缓存基本不敢放太久。另外过期时间一定要加随机值比如 30 分钟加上 0-5 分钟的随机偏移这是预防缓存雪崩的重要手段。后面第六章会细讲。5.4 集成 MyBatis 时要注意二级缓存的问题热词里相关的场景是spring boot mybatis。MyBatis 提供了一级缓存和二级缓存一级缓存是 SqlSession 级别默认开启但作用范围很小二级缓存开启后同一个 namespace 的查询结果会被缓存默认存在本地内存里。我的建议是不要用 MyBatis 的二级缓存。原因有两个。第一二级缓存存的是查询结果对象如果你的业务查询参数千变万化缓存命中率很低还占内存第二多实例部署时二级缓存是每个实例本地的一个节点更新了数据其他节点的缓存不会失效一致性完全不可控。更合理的做法是绕过 MyBatis 二级缓存自己在 Service 层操作 Redis 做业务缓存。这样缓存范围、失效时机、序列化方式都由你控制出了问题也好排查。MyBatis 的二级缓存适合非常简单的读多写少场景但大多数业务没那么简单。6. 缓存失效三兄弟穿透、击穿、雪崩6.1 缓存穿透查询一个压根不存在的数据缓存穿透指的是请求的数据在缓存和数据库中都不存在每次都直接打到数据库。比如一个恶意攻击者不断请求user:999999999这个用户不存在缓存没有每次都会查库数据库压力被拉满。解决思路有两个。第一个是空值缓存查询结果为空时把空对象写入缓存并设置较短的 TTL比如 3 分钟。第二个是布隆过滤器启动时把所有可能存在的数据 ID 加载到布隆过滤器请求进来先判断 ID 是否存在不存在直接返回不查库。布隆过滤器有一定的误判率但可以做到绝对不漏报适合数据总量大、ID 集合相对固定的场景。实际项目中我会先做空值缓存成本低、效果立刻可见如果穿透量还是大再上布隆过滤器。注意空值缓存的时间不要太长否则数据库新增数据后用户看到空值的时间会很长影响业务。6.2 缓存击穿一个热 key 过期瞬间被打爆缓存击穿和穿透容易混淆。击穿指的是一个高热度的 key 恰好过期这时大量请求同时发现缓存没数据全部打到数据库数据库瞬间压力巨大。解决击穿的标准方案是互斥锁查询缓存未命中时不直接查库而是先尝试获取分布式锁只允许一个线程查库并回填缓存其他线程等待锁释放后重新查缓存。伪代码如下public User getUserWithMutex(Long id) { String key user: id; Object cached redisTemplate.opsForValue().get(key); if (cached ! null) { return (User) cached; } String lockKey lock:user: id; boolean locked redisTemplate.opsForValue() .setIfAbsent(lockKey, 1, 3, TimeUnit.SECONDS); if (!locked) { // 没拿到锁等一会再查缓存 Thread.sleep(100); return getUserWithMutex(id); } try { // 可能拿到锁之前已有其他线程回填了缓存再次检查 cached redisTemplate.opsForValue().get(key); if (cached ! null) { return (User) cached; } User user userMapper.selectById(id); redisTemplate.opsForValue().set(key, user, 30, TimeUnit.MINUTES); return user; } finally { redisTemplate.delete(lockKey); } }另一个思路是逻辑过期缓存数据不设 TTL而是写入一个逻辑过期时间字段查询时发现逻辑过期立即开启异步线程重建缓存同时返回旧数据。适合能接受短暂旧数据的场景实现稍复杂。入门阶段先把互斥锁玩熟就够了。6.3 缓存雪崩大面积 key 同时过期雪崩是击穿的放大版不只是一个 key而是大量 key 在差不多同一时间过期导致请求全部穿透到数据库。最常见的成因是缓存设置了相同的时间比如全量缓存都是整点失效。解决办法很朴素。第一个过期时间加随机值比如基础 30 分钟再加上 0-5 分钟随机数让过期时间错开第二个热点数据可考虑永不过期通过后台定时任务主动更新第三个多级缓存兜底Redis 挂了还有本地缓存挡一下。最后一点尤其重要任何缓存方案都要假设 Redis 可能不可用应用层要有降级方案。我把三个问题整理成一张速查表面试和实际排查都能直接用问题特征核心原因主要解法缓存穿透查不存在的数据缓存和库都没有请求恶意或 ID 不存在空值缓存、布隆过滤器缓存击穿单个热 key 过期瞬间热点 key 过期 高并发互斥锁、逻辑过期缓存雪崩大量 key 同时过期或 Redis 宕机过期时间集中、无降级TTL 随机值、永不过期、多级缓存7. 缓存一致性改数据之后缓存怎么办7.1 缓存不一致是怎么产生的用了缓存就必然面临一致性问题。最典型的场景是数据库里用户手机号从 A 改成 B但缓存还是 A用户请求立刻变回旧数据。所有一致性策略围绕同一个问题更新数据库和更新缓存顺序怎么排失败了怎么办。健忘的读者可能会想那我更新数据库的同时更新缓存不就行了问题是这两个操作不是原子的中间任何一步失败两者就永久不一致。而且并发场景下先更新缓存再更新数据库会导致数据库和缓存中间态混乱很难收敛。7.2 四种常见策略对比第一种Cache Aside旁路缓存。更新操作只更新数据库然后删除缓存等下次查询时回填。这是最推荐的基础策略。为什么是删缓存而不是更新缓存因为更新缓存需要先把数据库里最新的数据查出来再写进去多一次查询而且并发写的时候写顺序可能错乱删除缓存成本低下次读自动回填天然收敛。第二种延迟双删。在 Cache Aside 基础上先删缓存、再更新数据库、隔一个短暂的延迟再删一次缓存。它解决的是“读线程在旧缓存删除后、数据库更新前读到了旧数据并回填”的并发竞争问题。延迟时间一般取 500ms 到 1s。这套方案能减小不一致窗口但不能完全消除主要看业务能否接受这个短暂延迟。第三种订阅数据库变更日志。通过 Canal 之类的工具监听 MySQL 的 binlog数据变更后异步删除对应缓存。这个方案对业务代码侵入极小更新数据库后什么都不用做缓存删除由旁路系统完成。代价是需要额外部署和运维一套组件适合对一致性要求高又不能改造业务代码的团队。第四种强一致思路不缓存或者短 TTL。如果业务绝对不允许读到旧数据最稳妥的方案就是不用缓存或者把 TTL 压到非常短比如 3-5 秒让不一致时间窗口小到用户感知不到。7.3 该选哪一种我个人的排序是常规业务用 Cache Aside先更新数据库再删除缓存并发竞争明显的场景上延迟双删公司有运维能力且对一致性要求高再考虑订阅 binlog。如果你的 Redis 删缓存失败可以加一个简单的重试机制或者把删除失败的 key 写入消息队列由消费者异步重试。值得说一句大实话缓存一致性没有完美方案只有适不适合。如果某个数据要求绝对不能出现旧值那它就不该进缓存。缓存本身就是用“短期的旧数据”换“更长的高性能”这是它的本质设计阶段要认清这一点。8. 分布式锁从 SETNX 到 Redisson8.1 为什么业务需要分布式锁单机环境下我们用 JVM 的synchronized或ReentrantLock就能控制并发但服务部署成多实例后每个实例有自己的锁两个实例可以同时执行同一段代码。典型场景是库存扣减两个实例同时读到库存 5各自减 1 再写回最后库存变成 4 而不是 3。Redis 实现分布式锁的原理很简单用一个 key 代表锁谁成功写入这个 key谁就拿到锁用完删除 key释放锁。但简单背后藏着不少坑下面从初级版本讲到可用版本。8.2 一个“能用但不能上线”的初级锁很多人第一版写的是这样// 加锁不存在则写入 Boolean locked redisTemplate.opsForValue() .setIfAbsent(lock:stock, 1); if (Boolean.TRUE.equals(locked)) { try { // 业务逻辑 } finally { redisTemplate.delete(lock:stock); } }问题一眼就能看出来如果业务逻辑执行到一半进程崩溃finally 里的释放代码根本没机会执行锁就永远不释放其他线程全部卡死。所以必须给锁设置过期时间Boolean locked redisTemplate.opsForValue() .setIfAbsent(lock:stock, 1, 30, TimeUnit.SECONDS);但这里还有一个隐藏问题如果业务执行时间超过 30 秒锁自动过期了另一个线程拿到锁开始执行业务此时第一个线程执行完执行 delete 把第二个线程的锁也删了。这就是误删锁问题。解决方式是把锁的 value 设置成唯一标识删除前先校验是不是自己的锁String token UUID.randomUUID().toString(); Boolean locked redisTemplate.opsForValue() .setIfAbsent(lock:stock, token, 30, TimeUnit.SECONDS); // 释放时校验 value这段逻辑必须用 Lua 保证原子性 String script if redis.call(get, KEYS[1]) ARGV[1] then return redis.call(del, KEYS[1]) else return 0 end;校验和删除不是原子操作就可能在中间插进来别的线程操作所以必须用 Lua 脚本把两步合并成原子操作。到了这一步这个锁已经能在中等并发下正常运行了但它还不能自动续期锁过期后业务还没结束怎么办依然没有好答案。8.3 直接用 Redisson别重复造轮子上面的问题Redisson 早就解决了。Redisson 封装的RLock可重入、可自动续期还实现了看门狗机制拿到锁后默认每 10 秒检查一次如果业务还在执行就自动延长锁的过期时间避免了“锁提前过期导致并发问题”和“锁永不释放导致死锁”两个极端。使用方式非常简单Autowired private RedissonClient redissonClient; public void deductStock() { RLock lock redissonClient.getLock(lock:stock); try { // 尝试拿锁最多等 3 秒锁自动过期时间 30 秒 if (lock.tryLock(3, 30, TimeUnit.SECONDS)) { // 扣减库存业务 } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { if (lock.isHeldByCurrentThread()) { lock.unlock(); } } }配置 RedissonClient 也不复杂Spring Boot 引入redisson-spring-boot-starter后在 application.yml 里配上 redis 地址即可。分布式锁这块我强烈建议直接上 Redisson不要自己造轮子踩过的坑也不少了生产环境里的分布式锁不是几行 SETNX 能搞定的。顺带提一句Redis 主从架构下分布式锁还有个极端缺陷主节点写入锁后还没同步到从节点主节点宕机从节点升主后锁丢失。Redis 官方提出了 RedLock 算法但社区对它争议很大入门阶段不展开你只要知道强一致场景下 Redis 分布式锁不是银弹就够了。9. 线上问题排查与缓存治理几个高频故障的定位过程9.1 先聊聊最常见的 timeout 异常用过 Spring Boot Redis 的人大概率见过这个报错RedisCommandTimeoutException: Command timed out after 3 second(s)。它表示客户端向 Redis 发送命令后在配置的 timeout 时间内没有收到响应。原因可能有这么几类。第一类网络问题。应用服务器和 Redis 不在同一个网段防火墙拦截或者 Redis 所在机器带宽被占满。排查第一步先用 ping 测网络延迟再用redis-cli -p 6379 ping直接验证连通性两步之内能排除掉大部分网络因素。第二类Redis 服务端阻塞。Redis 是单线程模型如果有个命令执行特别慢后续所有命令都要排队。最常见的大 key 操作比如对一个包含几百万元素的 list 执行LRANGE全量查询或者KEYS *这种全库扫描都会把 Redis 卡住。用redis-cli --bigkeys能找到大 key用SLOWLOG GET 10可以看到最近执行了哪些慢命令。第三类客户端连接池耗尽。spring-boot-starter-data-redis默认的 Lettuce 连接池有限如果某个连接卡住不返回池里的连接被占满新请求只能排队等待连接。排查时看监控里活跃连接数是不是长期打满。我给的排查顺序是先看 Redis 端redis-cli ping和 INFO 指标再看客户端连接池配置和 timeout 设置最后看网络链路。大多数时候问题出在服务端慢命令修好大 key 就好了。9.2 缓存治理不是把数据塞进 Redis 就完事很多团队把缓存设计当一次性工作上线后就不管了这是典型的隐患。我梳理了一份缓存治理清单建议定期检查。第一内存配额。Redis 默认没有内存上限数据无节制写入会导致内存耗尽。生产环境一定要设置maxmemory同时配置内存淘汰策略。常用的有allkeys-lru所有 key 按 LRU 淘汰和volatile-lru仅对设置了 TTL 的 key 按 LRU 淘汰。如果你主要用缓存且每个 key 都有过期时间allkeys-lru更省心。第二大量 key 无过期时间。用redis-cli --scan --pattern *配合脚本统计没有 TTL 的 key如果发现有业务 key 一直没设置过期时间尽快补上。我见过有项目半年时间 Redis 内存涨了 10 倍查下来全是没设置 TTL 的临时数据。第三热点 key 集中。某些 key 访问量特别高单个 Redis 实例可能出现 CPU 打满。治理手段包括给热点 key 加随机后缀分散到多个节点或者降级为本地缓存。第四监控和治理平台。Spring Boot 项目可以接入 Actuator把指标暴露出来再用 Spring Boot Admin 展示。热词里提到的 Spring Boot 监控需求本质上就是通过/actuator/health、/actuator/metrics这些端点观察应用健康度Redis 这块主要看连接数、内存使用和命令耗时。9.3 Redis 日志和慢查询怎么用Redis 的日志默认输出到 stdout如果用 Docker 运行直接docker logs redis就能看。日常排查慢操作我更喜欢用 SLOWLOG# 查看最近 10 条慢查询 SLOWLOG GET 10 # 查看慢查询阈值单位微秒 CONFIG GET slowlog-log-slower-than慢查询日志会告诉我们哪些命令耗时高结合客户端报错的时间点比对基本能锁定是哪类命令造成阻塞。这里给个小技巧大 key 不一定只在慢查询里出现--bigkeys扫描对线上有一定性能影响建议在业务低峰期执行。10. 最后说几句带过不少新人我观察到大家学 Redis 最容易犯的错是把精力全放在命令背诵上背完一堆命令却不知道真实项目里的坑在哪。这篇内容虽然叫“零基础入门”但我在写的时候故意把节奏放慢把每个技术点背后的原因讲清楚因为只有理解了“为什么”你才可能在环境变化时做出正确的判断。如果你把这个笔记当作学习路径我建议按这个顺序实操先用 Docker 起一个 Redis把五种数据类型都敲一遍然后搭一个最小的 Spring Boot 项目把缓存查询跑通再模拟一个高并发场景试试互斥锁和分布式锁的区别最后一定把 timeout 异常排查流程走一遍遇到类似问题你才不会慌。后面我还会写 Redis 持久化机制、主从复制与哨兵、Cluster 集群相关的实战内容如果你对哪一块有疑问可以直接在评论里留言我看到都会回复。

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

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

免费获取报价 →
↑