
1. 项目概述为什么需要了解多种Redis客户端在基于Spring Boot的后端开发中Redis几乎是缓存、分布式锁、会话存储等场景的标配。但很多开发者尤其是刚入门的同学常常会陷入一个误区认为Spring Boot整合Redis就是简单引入一个spring-boot-starter-data-redis依赖然后无脑使用RedisTemplate。直到项目上线遇到连接池耗尽、分布式锁失效、序列化乱码或者性能瓶颈时才手忙脚乱地去查资料。实际上Spring Boot生态下的Redis客户端选择远不止一个RedisTemplate。从底层的驱动到高层的封装我们至少有四种主流选择Jedis、Lettuce、RedisTemplate和Redisson。它们各有各的“脾气”和适用场景。用错了轻则性能不佳重则引发线上故障。我经历过一个项目初期为了图省事用了Jedis的同步阻塞模式处理大量短连接结果在流量高峰时Redis连接数暴涨直接把服务拖垮。后来切换到Lettuce利用其异步非阻塞的特性才稳定下来。所以这篇文章的目的不是简单地罗列API而是从一个有踩坑经验的开发者角度带你深入理解这四种客户端的核心差异、适用场景并给出在Spring Boot项目中整合它们的具体方案和避坑指南。无论你是想为现有项目选择最合适的客户端还是想彻底搞懂手里的工具都能在这里找到答案。2. 四大客户端核心特性与选型决策在动手写代码之前我们必须先搞清楚这四位“选手”的出身、特点和赛场。盲目选型是项目后期维护的噩梦之源。2.1 底层驱动Jedis vs. Lettuce首先RedisTemplate本身不是一个独立的网络客户端它更像一个“壳”其底层需要依赖一个真正的Redis驱动来干活。Spring Boot默认提供了两个选择Jedis和Lettuce。这是最根本的二选一。Jedis可以看作是Redis客户端的“老前辈”。它是一个直连、同步阻塞的客户端。当你调用jedis.get(“key”)时当前线程会一直等待直到从Redis服务器拿到结果或者超时。它的优点是API非常直观与Redis命令几乎一一对应学习成本低且在低并发、连接数可控的场景下非常稳定可靠。但其同步阻塞的特性意味着每个连接在同一时刻只能处理一个请求。在高并发场景下为了维持吞吐量你就需要维护一个较大的连接池如使用commons-pool2这带来了额外的内存开销和连接管理复杂度。我早期很多项目都用它直到遇到需要处理上万QPS的实时数据推送场景连接池参数调到头秃最终不得不考虑换方案。Lettuce则是后来居上的“新星”。它是一个基于Netty的异步、非阻塞客户端。其核心是“反应式”的它通过少量连接甚至可以是一个单连接就能处理大量并发请求。请求被发出后当前线程不会阻塞而是可以去处理其他任务等Redis返回结果后再由Netty的事件驱动机制回调处理。这对于高并发、低延迟的应用是巨大的优势。从Spring Boot 2.0开始Lettuce已经取代Jedis成为默认的底层驱动。除非你有历史包袱或非常特殊的理由否则在新项目中我强烈建议使用Lettuce。这里有一个简单的对比表格帮助你快速决策特性维度JedisLettuce通信模型同步阻塞异步非阻塞 (基于Netty)连接管理依赖连接池如Commons Pool支持连接池但基于共享连接更高效线程安全连接Connection非线程安全需从池中获取连接StatefulConnection是线程安全的性能高并发下连接池开销大易成为瓶颈高并发下性能卓越资源利用率高学习曲线简单命令式API稍复杂支持同步、异步、反应式多种API适用场景传统应用连接数可控对异步无要求高并发、微服务、云原生、需要低延迟响应的应用实操心得如果你从Spring Boot 1.x升级到2.x发现Redis相关配置不生效或报错很可能是默认驱动从Jedis切换到了Lettuce。检查一下依赖如果只想用Jedis需要排除Lettuce并显式引入Jedis。2.2 高层封装RedisTemplate vs. Redisson选好了底层驱动我们来看上层的封装。这里是我们日常打交道的对象。RedisTemplate是Spring Data Redis项目提供的“官方”模板类。它最大的价值在于抽象和集成。它帮我们做了两件大事1.连接管理自动集成底层的Jedis或Lettuce管理连接的生命周期。2.序列化将Java对象与Redis中存储的二进制数据自动转换。它提供了一组高度封装的、与Redis数据结构对应的操作接口如opsForValue(),opsForHash()让我们可以像操作Java集合一样操作Redis无需关心底层命令和连接细节。它的缺点是不够“原生”有时为了执行一个复杂的Lua脚本或者一个RedisTemplate未封装的原生命令你需要绕点弯子。Redisson是一个独立的Redis客户端目标不仅仅是操作Redis更是为了将Redis作为一个分布式服务平台来使用。它在实现Redis协议的基础上提供了大量分布式的Java对象和服务例如分布式锁RLock、分布式集合RSet、分布式队列RQueue、分布式信号量RSemaphore等。你可以把它理解为一个“Redis之上的分布式框架”。如果你项目中大量使用分布式锁、限流器、延迟队列等高级功能Redisson提供的现成实现远比你自己用RedisTemplate写Lua脚本要可靠和方便得多。它的API设计非常面向对象几乎让你感觉不到在直接操作Redis。两者的对比如下特性维度RedisTemplateRedisson定位数据访问模板提供CRUD抽象分布式服务框架提供分布式对象核心功能数据结构的操作、序列化、连接管理分布式锁、集合、队列、信号量、闭锁等使用方式基于Spring容器注入使用可独立使用也可与Spring集成锁实现需自己基于set nx ex命令和Lua脚本实现提供可重入锁、公平锁、联锁等开箱即用复杂度中低适合常规缓存和数据结构操作中高适合复杂的分布式协调场景最佳场景常规缓存、会话存储、简单计数等秒杀库存扣减、分布式任务调度、多节点协同注意事项RedisTemplate和Redisson并不是互斥的。在一个大型项目中你完全可以同时使用它们。例如用RedisTemplate处理简单的字符串缓存用Redisson的分布式锁来保护核心交易流程。关键在于根据场景选择正确的工具。3. 整合配置与核心细节解析理论说完了我们进入实战环节。我会分别展示在Spring Boot中整合这四种客户端的标准姿势和关键配置这些都是我多年项目积累下来的“标配”。3.1 基础环境与依赖准备无论选择哪种方式首先创建一个Spring Boot项目这里以Spring Boot 3.x为例。在pom.xml中基础的依赖是spring-boot-starter-data-redis它默认会引入Lettuce。dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-redis/artifactId /dependency !-- 如果需要使用Jedis作为底层驱动需要排除Lettuce并引入Jedis -- !-- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-redis/artifactId exclusions exclusion groupIdio.lettuce/groupId artifactIdlettuce-core/artifactId /exclusion /exclusions /dependency dependency groupIdredis.clients/groupId artifactIdjedis/artifactId /dependency -- !-- 如果需要整合Redisson -- dependency groupIdorg.redisson/groupId artifactIdredisson-spring-boot-starter/artifactId version3.27.0/version !-- 请使用最新稳定版本 -- /dependency在application.yml中配置Redis连接信息这是通用的spring: data: redis: host: localhost port: 6379 password: yourpassword # 如果没有密码可省略或留空 database: 0 # 默认DB索引 # Lettuce 特定配置 lettuce: pool: max-active: 8 # 连接池最大连接数 max-idle: 8 # 连接池最大空闲连接 min-idle: 0 # 连接池最小空闲连接 # 如果使用Jedis则配置jedis.pool # jedis: # pool: # max-active: 8 # max-idle: 8 # min-idle: 03.2 配置与使用RedisTemplate默认Lettuce驱动默认情况下Spring Boot会自动配置一个RedisTemplateString, Object和一个StringRedisTemplate。但自动配置的RedisTemplate的序列化器是JdkSerializationRedisSerializer会导致存到Redis里的key和value都是乱码不便于可视化工具查看。因此我们通常需要自定义一个配置。import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; Configuration public class RedisConfig { Bean public RedisTemplateString, Object redisTemplate(RedisConnectionFactory connectionFactory) { RedisTemplateString, Object template new RedisTemplate(); template.setConnectionFactory(connectionFactory); // 设置Key的序列化器为String StringRedisSerializer stringSerializer new StringRedisSerializer(); template.setKeySerializer(stringSerializer); template.setHashKeySerializer(stringSerializer); // 设置Value的序列化器为Jackson可以序列化对象为JSON GenericJackson2JsonRedisSerializer jsonSerializer new GenericJackson2JsonRedisSerializer(); template.setValueSerializer(jsonSerializer); template.setHashValueSerializer(jsonSerializer); // 开启事务支持按需 // template.setEnableTransactionSupport(true); template.afterPropertiesSet(); return template; } }这样配置后存入Redis的对象会被序列化为JSON字符串key是普通字符串非常清晰。使用起来也很简单import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; Service public class CacheService { Autowired private RedisTemplateString, Object redisTemplate; public void demo() { // 操作字符串 redisTemplate.opsForValue().set(user:1001, 张三); String name (String) redisTemplate.opsForValue().get(user:1001); // 操作Hash redisTemplate.opsForHash().put(product:2001, name, 手机); redisTemplate.opsForHash().put(product:2001, price, 2999); // 操作List redisTemplate.opsForList().leftPush(taskQueue, task1); // 设置过期时间 redisTemplate.expire(user:1001, Duration.ofMinutes(30)); } }注意事项GenericJackson2JsonRedisSerializer会在JSON中插入一个class属性来记录类型信息以便反序列化。这会导致存储空间稍大。如果对空间敏感且类型固定可以考虑自定义序列化器或者使用StringRedisTemplate只存字符串自己手动用Jackson转换对象。3.3 配置与使用RedissonRedisson的整合更为独立。除了引入starter依赖你还需要一个配置类来定义RedissonClientBean。Redisson支持单节点、主从、哨兵、集群等多种模式这里以单节点为例。import org.redisson.Redisson; import org.redisson.api.RedissonClient; import org.redisson.config.Config; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; Configuration public class RedissonConfig { Value(${spring.data.redis.host}) private String host; Value(${spring.data.redis.port}) private int port; Value(${spring.data.redis.password}) private String password; Bean(destroyMethod shutdown) public RedissonClient redissonClient() { Config config new Config(); // 单节点模式 String address redis:// host : port; config.useSingleServer() .setAddress(address) .setPassword(password.isEmpty() ? null : password) // 处理空密码 .setDatabase(0) .setConnectionPoolSize(10) // 连接池大小 .setConnectionMinimumIdleSize(5); // 最小空闲连接数 return Redisson.create(config); } }使用Redisson的分布式锁是它的核心亮点其实现非常严谨支持自动续期解决了锁过期而业务未执行完的经典问题。import org.redisson.api.RLock; import org.redisson.api.RedissonClient; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.concurrent.TimeUnit; Service public class OrderService { Autowired private RedissonClient redissonClient; public void createOrder(String orderId) { String lockKey order_lock: orderId; RLock lock redissonClient.getLock(lockKey); try { // 尝试加锁最多等待10秒锁持有时间30秒后自动失效 boolean isLocked lock.tryLock(10, 30, TimeUnit.SECONDS); if (isLocked) { try { // 核心业务逻辑如扣减库存 System.out.println(执行业务逻辑: orderId); Thread.sleep(5000); // 模拟耗时操作 } finally { // 必须在finally块中释放锁 if (lock.isHeldByCurrentThread()) { lock.unlock(); } } } else { System.out.println(获取锁失败可能有其他进程正在处理); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); System.out.println(锁等待被中断); } } }实操心得Redisson的锁实现了java.util.concurrent.locks.Lock接口用法和Java原生锁很像学习成本低。它的tryLock方法比单纯用SETNX命令强大太多包含了等待时间、锁超时时间并且有watchdog机制自动续期生产环境强烈推荐。4. 五大常见场景的解决方案与避坑指南掌握了基本整合我们来看看在实际开发中最常遇到的几个场景以及如何用最合适的工具去解决。4.1 场景一对象缓存与序列化方案需求将用户信息、商品详情等复杂对象缓存到Redis。方案选择RedisTemplateGenericJackson2JsonRedisSerializer是最通用、最便捷的方案。关键点与避坑序列化器选择如前所述默认的JDK序列化会产生乱码。优先选择JSON序列化器Jackson2或Fastjson。如果缓存对象类型非常固定且单一可以考虑更高效的序列化方案如Kryo或Protobuf但这会增加复杂度。空值缓存这是一个经典的缓存穿透问题。如果从数据库查不到数据缓存一个空值如“NULL”并设置一个较短的过期时间如30秒可以有效避免大量请求直接穿透到数据库。缓存更新策略是更新数据库后删除缓存Cache-Aside还是先更新缓存再更新数据库通常推荐“先更新数据库再删除缓存”。虽然可能存在极短时间的数据不一致但实现简单并发问题少。在Spring中可以使用CacheEvict注解。Service public class UserService { Autowired private RedisTemplateString, Object redisTemplate; Autowired private UserRepository userRepository; public User getUserById(Long id) { String key user: id; // 1. 从缓存查 User user (User) redisTemplate.opsForValue().get(key); if (user ! null) { // 如果是我们约定的空值标记直接返回null if (NULL.equals(user.getName())) { // 假设用一个特殊字段判断 return null; } return user; } // 2. 缓存没有查数据库 user userRepository.findById(id).orElse(null); // 3. 写入缓存 if (user ! null) { redisTemplate.opsForValue().set(key, user, Duration.ofHours(1)); } else { // 缓存空对象防止缓存穿透 User nullUser new User(); nullUser.setName(NULL); // 设置一个特殊标识 redisTemplate.opsForValue().set(key, nullUser, Duration.ofSeconds(30)); } return user; } CacheEvict(value user, key #id) // 使用Spring Cache抽象删除指定key的缓存 public void updateUser(Long id, User user) { userRepository.save(user); } }4.2 场景二分布式锁的终极实现需求在集群环境下保证一段代码如扣减库存的绝对互斥执行。方案选择首选Redisson。如果你不想引入Redisson再用RedisTemplate执行Lua脚本。Redisson方案上面已经演示过其RLock接口非常完善。这里强调几个关键点锁的粒度锁的key要能精确标识要保护的资源如“stock_lock:product_1001”。锁的持有时间一定要设置一个合理的超时时间防止线程挂掉导致锁永远不释放。Redisson的watchdog会自动续期只要业务线程还在运行。释放锁的时机必须在finally块中判断当前线程是否持有锁再进行释放。RedisTemplate Lua方案备选public class RedisLock { Autowired private RedisTemplateString, Object redisTemplate; private static final String LOCK_SCRIPT if redis.call(setnx, KEYS[1], ARGV[1]) 1 then\n return redis.call(pexpire, KEYS[1], ARGV[2])\n else\n return 0\n end; private static final String UNLOCK_SCRIPT if redis.call(get, KEYS[1]) ARGV[1] then\n return redis.call(del, KEYS[1])\n else\n return 0\n end; public boolean tryLock(String lockKey, String requestId, long expireMillis) { DefaultRedisScriptLong script new DefaultRedisScript(LOCK_SCRIPT, Long.class); Long result redisTemplate.execute(script, Collections.singletonList(lockKey), requestId, String.valueOf(expireMillis)); return result ! null result 1; } public boolean unlock(String lockKey, String requestId) { DefaultRedisScriptLong script new DefaultRedisScript(UNLOCK_SCRIPT, Long.class); Long result redisTemplate.execute(script, Collections.singletonList(lockKey), requestId); return result ! null result 1; } }注意事项自己实现分布式锁要处理很多边界情况比如“设置值”和“设置过期时间”必须是原子操作用Lua脚本保证释放锁时必须验证请求IDvalue防止误删其他线程的锁。除非万不得已否则直接使用Redisson是更稳妥的选择。4.3 场景三发布订阅与消息通知需求实现服务间的轻量级消息通信如订单创建后通知物流系统。方案选择RedisTemplate提供的发布订阅API足够简单场景使用。对于复杂消息模式考虑专业的消息队列如RocketMQ, Kafka。使用RedisTemplateComponent public class MessageService { Autowired private RedisTemplateString, Object redisTemplate; // 发布消息 public void publish(String channel, Object message) { redisTemplate.convertAndSend(channel, message); } } Component public class OrderCreateListener implements MessageListener { Override public void onMessage(Message message, byte[] pattern) { String channel new String(message.getChannel()); String body new String(message.getBody()); System.out.println(收到频道[ channel ]的消息: body); // 处理订单创建后的逻辑如通知物流 } } Configuration public class RedisPubSubConfig { Bean public RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory, OrderCreateListener listener) { RedisMessageListenerContainer container new RedisMessageListenerContainer(); container.setConnectionFactory(connectionFactory); // 订阅指定的频道 container.addMessageListener(listener, new ChannelTopic(order:created)); return container; } }实操心得Redis的Pub/Sub是“即发即弃”的没有消息持久化机制。如果订阅者在消息发布时不在线它将永远收不到这条消息。所以它只适用于对消息可靠性要求不高的实时通知场景不能用于核心业务解耦。4.4 场景四热点数据缓存与击穿预防需求一个极热门的Key如首页爆款商品信息在缓存过期的瞬间大量请求同时涌入数据库造成瞬时压力。方案选择使用互斥锁Mutex Lock或逻辑过期。互斥锁方案第一个发现缓存失效的线程去加锁如用分布式锁然后查询数据库并重建缓存其他线程等待锁释放后重新读取缓存。public Product getHotProduct(Long id) { String cacheKey hot_product: id; Product product (Product) redisTemplate.opsForValue().get(cacheKey); if (product null) { String lockKey lock_product: id; String requestId UUID.randomUUID().toString(); try { // 尝试获取分布式锁 boolean locked redisLock.tryLock(lockKey, requestId, 5000); if (locked) { // 获取锁成功再次检查缓存Double Check product (Product) redisTemplate.opsForValue().get(cacheKey); if (product null) { // 查询数据库 product productRepository.findById(id); // 写入缓存设置较长过期时间 redisTemplate.opsForValue().set(cacheKey, product, Duration.ofHours(2)); } } else { // 没拿到锁等待一小段时间后重试或返回降级数据 Thread.sleep(50); return getHotProduct(id); // 简单重试生产环境需控制次数 } } finally { redisLock.unlock(lockKey, requestId); } } return product; }逻辑过期方案缓存的值里不仅包含数据还包含一个逻辑过期时间。即使物理缓存未过期如果判断逻辑时间已到则异步发起缓存重建当前线程返回旧数据。这种方式用户体验更好但实现更复杂。4.5 场景五排行榜与限流器实现需求实现一个实时游戏分数排行榜或者对某个API接口进行限流如每秒10次。方案选择利用Redis的有序集合ZSet和令牌桶/滑动窗口算法。排行榜ZSetpublic void addScore(String player, double score) { // 添加或更新玩家分数 redisTemplate.opsForZSet().add(game:leaderboard, player, score); } public ListString getTop10() { // 获取前10名按分数从高到低 SetZSetOperations.TypedTupleObject set redisTemplate.opsForZSet() .reverseRangeWithScores(game:leaderboard, 0, 9); ListString topList new ArrayList(); for (ZSetOperations.TypedTupleObject tuple : set) { topList.add(tuple.getValue() : tuple.getScore()); } return topList; }限流器使用Redisson的RRateLimiter Redisson直接提供了分布式限流器这是最省事的方式。Autowired private RedissonClient redissonClient; public boolean tryAcquire(String apiKey) { RRateLimiter rateLimiter redissonClient.getRateLimiter(rate_limit: apiKey); // 设置速率每1秒钟产生10个令牌 rateLimiter.trySetRate(RateType.OVERALL, 10, 1, RateIntervalUnit.SECONDS); // 尝试获取1个令牌 return rateLimiter.tryAcquire(1); }如果不用Redisson可以用RedisTemplateLua脚本实现滑动窗口限流但复杂度高很多。对于这种成熟的分布式协调需求使用Redisson这类专业工具能极大提升开发效率和系统可靠性。5. 性能调优、监控与问题排查整合完成并应用后线上环境的稳定运行离不开调优和监控。这里分享几个关键点。5.1 连接池配置优化无论是Lettuce还是Jedis连接池配置都至关重要。Lettuce Pool虽然Lettuce基于Netty连接复用效率高但在高并发场景下适当配置连接池仍有好处。主要参数是max-active最大连接数和max-idle最大空闲连接。一个经验公式是max-active≈ (最大QPS * 平均响应时间(秒))。例如目标QPS为1000平均RT为10ms则理论需要10个连接。实际配置时可留出余量设置为16或32。Jedis Pool由于是同步阻塞模型连接池需求更大。除了max-activemax-wait获取连接最大等待时间也很关键设置过短会导致大量JedisConnectionException。5.2 序列化性能考量序列化/反序列化是Redis操作的主要CPU开销之一。评估使用JdkSerializationRedisSerializer速度最快但兼容性差、体积大。Jackson2JsonRedisSerializer兼容性好体积适中性能不错是通用选择。如果缓存对象结构极其简单且固定StringRedisSerializer配合手动JSON转换可能更快。监控可以通过APM工具如SkyWalking, Pinpoint监控Redis操作的耗时如果发现序列化占了大头可以考虑性能更高的序列化库如Kryo或FST。但要注意这些库可能需要预先注册类且不同版本间可能存在兼容性问题。5.3 常见问题排查实录连接超时ConnectionTimeoutException检查网络是否网络不通或防火墙拦截。检查Redis状态redis-cli ping。检查配置spring.redis.timeout配置是否过短默认通常是2000ms在高负载或慢查询时可能不够。检查连接池是否max-active设置过小导致连接耗尽线程在max-wait时间后超时。序列化错误SerializationException类路径不一致存数据和取数据的服务其类路径特别是自定义类的全限定名必须完全一致。版本不一致Jackson等序列化库的版本不一致可能导致字段无法识别。解决方案使用JsonTypeInfo注解或统一序列化器配置。对于微服务建议缓存值使用简单的、跨语言的格式如纯JSON字符串由消费者自行反序列化。内存飙升OOM大Key问题单个Key对应的Value过大如一个List存了百万条数据。使用redis-cli --bigkeys扫描。解决方案是拆分大Key。Key数量过多没有设置过期时间或过期时间过长。为缓存Key设置合理的TTL并使用随机值避免同一时间大量Key同时过期缓存雪崩。监控务必配置Redis的内存监控告警。Redisson看门狗Watchdog不续期导致锁提前释放原因业务逻辑耗时超过了锁的leaseTime看门狗超时时间且业务线程阻塞如Full GC导致看门狗线程无法续期。排查检查JVM GC日志优化业务逻辑减少锁内耗时适当调大lockWatchdogTimeout默认30秒。6. 总结与个人建议经过上面从选型、整合、场景实践到问题排查的完整梳理相信你对Spring Boot下的Redis客户端生态有了更立体的认识。最后分享几点我个人的实战建议第一无脑选型组合对于绝大多数Spring Boot新项目我的建议是LettuceRedisTemplateRedisson组合。用Lettuce做底层驱动保障高性能和高并发用RedisTemplate处理90%的常规缓存和数据操作享受Spring生态的便利用Redisson来解决那10%复杂的分布式协调问题如分布式锁、限流。这个组合能覆盖99%的场景。第二配置标准化将Redis的配置地址、密码、连接池参数统一放在配置中心。为不同的业务场景定义不同的RedisTemplateBean比如一个用Jackson序列化对象一个用String序列化做简单缓存通过Qualifier注入使用让配置清晰可管理。第三监控告警先行在上线前就把Redis的核心监控指标内存使用率、连接数、QPS、慢查询、Key数量接入你的监控系统如PrometheusGrafana。设置合理的告警阈值比如内存使用率超过80%就告警。很多问题在酿成故障前监控曲线已经给出预警了。第四面向失败设计缓存不是银弹Redis也可能挂掉。重要的业务逻辑一定要有降级策略。例如使用Spring Cache时可以配置Cacheable的unless条件当Redis不可用时跳过缓存直接查库或者返回预置的降级数据。记住“有损服务”优于“完全不可用”。技术选型没有绝对的好坏只有适合与否。希望这篇融合了原理、实战和踩坑经验的长文能帮你构建起关于Spring Boot与Redis整合的完整知识图谱在下次做技术决策时心里更有底。