
1. 先把Redis跑起来下载安装的几种场景配置Spring连接Redis其实有个前置条件很容易被忽略——你的Redis服务本身得是健康可用的。很多人在Spring配置里折腾半天最后发现连不上结果是因为Redis压根没起来或者装了个版本跟Spring Data Redis不兼容的实例。先解决最基础的安装问题。官方虽然不维护Windows版本但Redis团队在GitHub上长期维护了Windows移植版本地开发完全够用。直接去下载zip包解压后进入目录执行redis-server.exe redis.windows.conf看到那个经典的ASCII Logo和Server started日志说明服务已经跑起来了。默认端口6379默认无密码。生产环境或者Linux开发机我更推荐用Docker方式简化到一行命令docker run -d --name redis \ -p 6379:6379 \ -v /myredis/data:/data \ -v /myredis/conf/redis.conf:/etc/redis/redis.conf \ redis:7.0 redis-server /etc/redis/redis.conf手动编译源码安装make make install也是常见做法但说实话现在Docker和包管理器apt/yum/brew已经很成熟没必要自己编译除非你要改内核参数或做定制化编译。版本选择这块要注意Spring Boot 2.x系列适配Redis 5.x/6.x完全没问题Spring Boot 3.x因为底层换了Jakarta命名空间建议搭配Redis 6.x以上。别一上来就装最新的Redis 7.x除非你清楚新版在持久化格式和命令兼容性上的变化否则排查问题时会多一个变量。安装完成后强烈建议用一个GUI客户端连一下确认服务真正可用。Redis Desktop Manager新版已经收费了但开源的Another Redis Desktop Manager简称ARDM完全免费功能也不差能看key列表、执行命令、看内存统计日常调试够用。下载连接测试一下能PING通返回PONG这步就算过了。顺手在客户端里设置几个测试key后面Spring连上来之后对比数据格式是否一致方便排查序列化问题。2. Spring Boot整合Redis从依赖到第一行可运行配置先说明一点你要是还在用传统的Spring XML配置方式建议尽早升级到Spring Boot。Spring Boot的自动配置机制把Redis整合这件事简化到了极致你只需要做两件事——加依赖、写配置。2.1 依赖引入的两层考量Maven项目最基础的两个依赖dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-redis/artifactId /dependency dependency groupIdorg.apache.commons/groupId artifactIdcommons-pool2/artifactId /dependency第一个依赖是必须的包含了Spring Data Redis的核心库、Lettuce连接器Spring Boot 2.x之后默认用Lettuce替代了Jedis。第二个依赖很容易被忽略但如果你在配置里声明了连接池参数比如max-total、max-idle没有commons-pool2就会启动报错。因为Lettuce的连接池功能是基于Apache Commons Pool实现的。Gradle也一样implementation org.springframework.boot:spring-boot-starter-data-redis implementation org.apache.commons:commons-pool22.2 配置文件到底填什么application.yml里的配置项看起来不多但每个字段都有讲究spring: data: redis: host: 127.0.0.1 port: 6379 password: database: 0 timeout: 5000ms lettuce: pool: max-active: 8 max-idle: 8 min-idle: 0 max-wait: -1mshost和port不多说Redis服务地址和端口。password如果Redis没设密码就留空设置了就用redis-cli config set requirepass yourpassword命令临时设置或直接在配置文件里改。databaseRedis默认有16个逻辑库0-15按业务隔离缓存数据。默认0就够了多个服务共用一个Redis时建议按服务分配不同索引从1开始用。timeout连接超时默认单位是毫秒。这里有个细节——5000ms和5000效果一样但建议显式带单位可读性好。lettuce.pool.*这些参数只在上面引入commons-pool2后才会生效。max-active是连接池最大连接数max-idle是最大空闲连接min-idle是最小空闲连接max-wait是拿连接的最大等待时间。-1表示无限等待生产环境不建议这么设否则Redis挂了你的线程会全部阻塞在等待连接上。2.3 一个让你直接可跑的测试用例配置写完之后Spring Boot会自动装配好两个核心BeanRedisTemplateObject, Object和StringRedisTemplate。前者key和value都是Object类型后者key和value都是String类型。写个单元测试验证连通性SpringBootTest class RedisConnectionTest { Autowired private StringRedisTemplate stringRedisTemplate; Test void testConnection() { stringRedisTemplate.opsForValue().set(test:hello, world); String value stringRedisTemplate.opsForValue().get(test:hello); Assertions.assertEquals(world, value); } }跑通了说明Spring和Redis之间的链路已经打通。但到这里只是最基础的配置离真正能上生产还差一个关键环节——序列化器的选择和配置。3. 序列化器选择为什么默认JDK序列化会在生产环境坑你这是整个Redis配置里最容易被新手忽略、最容易踩坑的部分。3.1 问题现象存入Redis的数据全是乱码Spring Data Redis默认的序列化器是JdkSerializationRedisSerializer。什么意思就是它会把你的key和value先用Java序列化机制转成二进制字节流再存入Redis。问题在于你用redis-cli或ARDM客户端看到的key是这样的\xAC\xED\x00\x05t\x00\x0Euser:profile:1001value如果是对象会是一长串以\xAC\xED开头的二进制数据跟天书一样。这带来三个实际后果排查问题困难——肉眼根本看不出存的是什么git上review代码、查线上数据都不方便。跨语言不兼容——如果Java服务写入的数据要让Python、Go服务读取JDK序列化格式它们根本看不懂。数据体积膨胀——Java序列化会把完整的类结构信息写进去一个简单的{name:tom}可能变成几百字节浪费内存。3.2 解决方案自定义序列化器配置通用的做法是写一个配置类把key和value的序列化器都换成JSON格式Configuration public class RedisConfig { Bean public RedisTemplateString, Object redisTemplate(RedisConnectionFactory factory) { RedisTemplateString, Object template new RedisTemplate(); template.setConnectionFactory(factory); // key使用String序列化保证可读性 StringRedisSerializer stringSerializer new StringRedisSerializer(); template.setKeySerializer(stringSerializer); template.setHashKeySerializer(stringSerializer); // value使用JSON序列化 GenericJackson2JsonRedisSerializer jsonSerializer new GenericJackson2JsonRedisSerializer(); template.setValueSerializer(jsonSerializer); template.setHashValueSerializer(jsonSerializer); template.afterPropertiesSet(); return template; } }这套配置的含义key永远是字符串redis-cli里看起来清清楚楚value用JSON存储可读性好。Hash结构同理field名和key一样保持字符串。这里有个坑要提前告诉你GenericJackson2JsonRedisSerializer在反序列化时会在JSON里写入一个class字段来记录对象的全限定类名。比如{class: com.example.UserProfile, userId: 1001, name: tom}这样做的好处是反序列化时能还原成正确的Java类型坏处是JSON里有类名信息代码里改了类名或包路径后老数据反序列化直接报错。跨服务传递时如果对方没有这个类也无法正确解析。如果确定数据只是给自己服务用可以考虑Jackson2JsonRedisSerializerObject并配置ObjectMapper关闭默认类型信息但这会让反序列化只能得到LinkedHashMap需要手动转换。取舍看你的实际场景。3.3 什么时候可以直接用StringRedisTemplate如果你的数据本身就是String比如验证码、token、缓存JSON字符串直接用StringRedisTemplate最简单完全没有序列化问题。写入前用ObjectMapper把对象手动转成JSON字符串读取后手动转回对象。多几行代码但逻辑透明可控。我在实战中更倾向于缓存复杂对象用自定义的RedisTemplateJSON序列化缓存简单字符串直接用StringRedisTemplate。两个Bean可以并存Spring Boot自动配置已经帮你创建好StringRedisTemplate而RedisTemplate如果你自己定义了配置类会覆盖自动配置的那个。4. 真正跑业务时要处理的细节连接池参数、密码与大key配置完成后开发环境和生产环境之间的差距往往就藏在这些细节里。4.1 连接池参数怎么定才合理上面配置里的max-active: 8是Spring Boot的默认值。但8真的适合你的业务吗看一个实际场景你的服务有50个Tomcat线程处理请求每个请求里平均要执行2次Redis操作每次操作耗时约1ms。那高峰期并发300每秒的Redis操作数就是几百。连接池的max-active如果太小比如8就会出现大量线程排队等连接接口RT飙升。一个粗略的估算公式max-active ≥ 峰值QPS × 单次操作耗时(秒)。按上面例子假设峰值300 QPS单次操作1ms那max-active至少是300 × 0.001 0.3看起来很小但这是理想情况实际情况因为阻塞、重试等因素建议设成20~50这种量级再配合压测观察线程池的等待时间。max-wait不建议设成-1无限等待。可以设为3000ms或5000ms拿不到连接就快速失败抛出异常提示降级而不是让用户请求一直卡在那里。4.2 密码配置的几个注意点Redis密码在redis.conf里通过requirepass设置。Spring这边的配置就是spring.data.redis.password字段。但这里有个非常隐蔽的坑如果你用了Redis主从或哨兵模式密码要单独配置在spring.data.redis.sentinel.password里不是写在spring.data.redis.password。Spring Boot的自动配置对不同拓扑结构有不同的属性映射搞错了连不上是小事更麻烦的是日志里直接明文打印密码。还有个细节密码里如果包含特殊字符比如、:YAML里要加引号否则解析会出问题。4.3 别在Spring配置层忽略了Redis本身的操作规范这一步其实跟Spring配置无关但很多人在配完Spring之后直接把Redis当成关系型数据库用埋下性能炸弹。最典型的严禁在线上执行KEYS *——它会阻塞Redis单线程导致所有命令排队。真要遍历key用SCAN。大key问题——一个Hash里塞几百万个field或者一个String达到几MB删除和读取都可能导致阻塞。Spring代码里可以通过redisTemplate.getConnectionFactory().getConnection().serverCommands()拿到底层连接执行OBJECT ENCODING之类的命令做检查但更建议用现成的工具离线分析。注意命令超时——Lettuce默认的command timeout是60秒如果业务上某个命令慢可以单独在配置里调小spring.data.redis.timeout快速失败比让调用方干等更合理。5. 一个从配置到业务实现的完整案例带缓存的用户查询上面讲的都是配置层面的东西最后用一个实际业务场景串起来看看这些配置怎么配合使用。假设我们要实现一个用户信息查询接口先从Redis查查不到再查数据库然后回填Redis。Service public class UserService { private static final String USER_CACHE_KEY user:profile:; Autowired private RedisTemplateString, Object redisTemplate; Autowired private ObjectMapper objectMapper; Autowired private UserRepository userRepository; public UserProfile getUserProfile(Long userId) { String key USER_CACHE_KEY userId; // 1. 查缓存 Object cached redisTemplate.opsForValue().get(key); if (cached ! null) { // 因为用了GenericJackson2JsonRedisSerializer可以直接转 if (cached instanceof UserProfile) { return (UserProfile) cached; } } // 2. 缓存未命中查数据库 UserProfile profile userRepository.findById(userId).orElse(null); if (profile ! null) { // 3. 回填缓存设置过期时间10分钟 redisTemplate.opsForValue().set(key, profile, 10, TimeUnit.MINUTES); } return profile; } }这个代码看起来简单但有几点值得展开key设计用冒号分隔user:profile:1001这种格式在Redis里是约定俗成的。冒号虽然不会自动形成目录结构但客户端工具里显示时会按冒号分组方便浏览和管理。还可以给不同业务模块分别加前缀方便后续清理和监控。缓存过期时间用opsForValue().set(key, value, timeout, unit)这个带过期时间的重载别用不带过期时间的版本然后把过期策略丢给Redis的LRU。前者是精确淘汰后者是内存压力下的被动淘汰行为完全不同。缓存穿透如果数据库里也没有这个用户缓存永远不会有值每次请求都会打穿到数据库。解决方式通常有两种一是缓存空值存一个空对象过期时间设短一点二是用布隆过滤器做前置过滤。两者可以结合但实现复杂度不同根据你的业务量决定是否值得。5.1 缓存一致性与更新策略这个案例里最简单的更新策略就是数据库更新后直接删缓存。Transactional public UserProfile updateUserProfile(UserProfile profile) { UserProfile saved userRepository.save(profile); String key USER_CACHE_KEY saved.getUserId(); redisTemplate.delete(key); return saved; }删除比更新简单因为更新涉及序列化格式、并发写等多个问题。删除缓存不命中下次查询自然回源数据库虽然多一次DB查询但缓存最终一致性的窗口很小。这个策略在大多数业务场景下是合理的。真要追求强一致要么用分布式锁保证只有一个线程在更新缓存要么引入消息队列异步刷缓存但这些都是成本和复杂度业务没到那个量级时不要过度设计。6. 排错思路和我的配置习惯最后整理一份我在实际项目中反复用到的排错路径和配置习惯希望帮你少走弯路。6.1 常见踩坑现象与排查链路现象排查链路解决方案启动时报Unable to connect to Redis先ping一下6379端口通不通再确认Redis是否设置密码但Spring没配检查host/port/password用redis-cli -h host -p port ping验证项目能启动但访问缓存报RedisConnectionFailureException连接池是否耗尽Redis资源是否达到maxmemory限制看监控指标max-wait调短一点快速失败清理Redis内存ClassCastException: LinkedHashMap cannot be cast to UserProfile反序列化时类型信息丢失要么用了非Generic版本的JSON序列化器要么用了StringRedisTemplate手动转JSON换成GenericJackson2JsonRedisSerializer或者手动序列化/反序列化处理Redis里key是乱码RedisTemplate的key序列化器还是默认的JDK序列化按第3节的配置类改key为StringRedisSerializer缓存查询返回null但数据确实在Redis里检查反序列化是否成功如果class字段换过包名老数据会反序列化失败统一切换到字符串存储或迁移缓存数据排错的核心原则先确认Redis服务本身是好的再用redis-cli直接操作看是否正常最后才排查Spring配置层的问题。分层排查不会让你一头扎进代码里出不来。6.2spring.data.redis和spring.redis的区别这个问题我见过很多次了。Spring Boot 2.x之前用spring.redis前缀但从2.0开始改成了spring.redisSpring Boot 3.x时期又进一步改为spring.data.redis。如果你在网上搜索配置教程很可能会看到旧版本的写法。虽然2.x版本里spring.redis已经被标记为废弃但它在某些版本里依然能兼容生效这反而容易被忽略。在Spring Boot 3.x里spring.redis前缀不再被识别配置会静默失效然后你连不上Redis还莫名其妙为什么配置了等于没配置。最佳实践是确认你的Spring Boot大版本然后用对应文档里的配置前缀。直接翻Spring Boot官方文档的Data章节别背配置项。6.3 我在生产环境沉淀的配置习惯分享几个经过多年项目检验的默认习惯第一连接参数单独抽一个配置类。不要直接在业务代码里散落RedisTemplate的操作把key前缀、过期时间、序列化策略都收敛在一个地方后续调整不用全局搜索替换。第二Redis里所有的key都要有业务前缀。比如pay:order:,user:profile:,sms:code:。这样能给key自动带上命名空间避免多个服务共用一个Redis实例时互相覆盖排查问题也效率倍增。第三监控连接池和慢查询日志。Redis的SLOWLOG能告诉你哪些命令执行时间过长及时捕获大key和慢操作的元凶。配合Spring Boot Actuator暴露的Redis健康指标和连接池指标redis_lettuce_*在真正出问题之前就能发现隐患。第四每一个Redis操作都要考虑失败路径。Redis不是数据库挂了之后系统不能跟着挂。使用缓存时务必加上降级逻辑——Redis异常时直接访问数据库并打印日志报警。连接池的max-wait设置不要太长避免请求线程无限阻塞。第五合理使用Cacheable注解但别过度依赖。Spring Cache抽象用起来确实爽一行注解就完成了缓存读写但它隐藏了太多细节key生成策略、缓存穿透防护、空值处理、过期时间定制都需要额外配置。中小型项目、缓存逻辑简单的可以用注解快速开发涉及复杂缓存策略时用RedisTemplate手写反而更清晰可控。最后的最后配置Redis和Spring的整合表面看就是加依赖、填配置文件两件事但真正决定线上稳定性的是对序列化、连接池、key设计、异常降级这些细节的理解深度。从新手期一路踩坑过来最深刻的体会是任何配置都不是死记硬背的参数表而是对底层机制的认知投射——你越理解Redis本身的数据结构、单线程模型、持久化机制就越明白Spring配置文件里每个参数为什么是这个默认值以及在不同业务场景下该怎么调整。先把最简单的跑通再逐步进阶到分布式锁、缓存一致性、Redis Stream这些复杂场景。每一层往上走你都会对配置文件里有新认识。如果配置过程卡住了记住一个万能调试法用redis-cli手动执行一遍同样的操作如果redis-cli成功而Spring失败问题大概率在Spring的序列化或配置映射层而不是Redis本身。