先把 Redis 跑起来
本地学 Redis,最省事的方式是用 Docker。下面这条命令启动一个 7.x 实例,开启 AOF 持久化并设置密码:
docker run -d --name redis7 \
-p 6379:6379 \
-v /data/redis/data:/data \
redis:7.2 \
redis-server --appendonly yes --requirepass "change-me"
启动后先验证:
docker exec -it redis7 redis-cli -a change-me ping
# PONG
如果要在测试环境长期使用,建议挂载配置文件,把 bind、protected-mode、maxmemory、appendfsync 这些参数显式写清楚,而不是全部靠命令行。容器里的默认配置和宿主机安装的 Redis 不完全一样,尤其是绑定地址和持久化目录。
Spring Boot 集成
引入依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
第二个依赖容易被忘掉。Lettuce 连接池的配置只有在类路径上有 commons-pool2 时才生效,没有它,spring.redis.lettuce.pool.* 写了也不会报错,只是不生效。
配置文件的写法要注意版本差异:Spring Boot 2.x 用 spring.redis.*,Spring Boot 3.0 改成了 spring.data.redis.*。从 2.7 升级到 3.0 时,配置没改会连到默认的 localhost:6379,启动不报错但连不上。
spring:
data:
redis:
host: 127.0.0.1
port: 6379
password: change-me
database: 0
timeout: 3s
lettuce:
pool:
max-active: 16
max-idle: 8
min-idle: 2
如果用的是 Spring Boot 2.7,把 spring.data.redis 换成 spring.redis 即可。
序列化的选择
RedisTemplate 默认使用 JDK 序列化,存进去的 key 和 value 都是二进制,用 redis-cli 看是一堆乱码。跨语言、跨服务读取时非常不方便。
更常见的做法是 key 用字符串、value 用 JSON:
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
GenericJackson2JsonRedisSerializer serializer =
new GenericJackson2JsonRedisSerializer();
template.setKeySerializer(RedisSerializer.string());
template.setHashKeySerializer(RedisSerializer.string());
template.setValueSerializer(serializer);
template.setHashValueSerializer(serializer);
template.afterPropertiesSet();
return template;
}
GenericJackson2JsonRedisSerializer 会写入 @class 类型信息,反序列化时能还原成原来的对象。代价是 JSON 里多一个字段,而且类名重构后会反序列化失败。如果不想暴露类名,可以用 Jackson2JsonRedisSerializer 指定具体类型,或者直接用 StringRedisTemplate 存 JSON 字符串,由业务自己序列化。
选择哪种方式,主要看使用方。只在 Java 服务内部读写、类结构稳定,用带类型信息的 JSON 省事;需要给其他语言的服务或脚本读取,建议用 StringRedisTemplate 存纯 JSON,把序列化规则留在业务层,数据结构更可控,跨语言也不会因为 @class 字段解析失败。
实体里如果有 LocalDateTime,需要给 ObjectMapper 注册 JavaTimeModule,否则读写会报 Java 8 date/time type not supported。
常见坑
1. 配置前缀写错版本
前面提过,Boot 3 必须用 spring.data.redis。升级后接口全部超时,日志里是 Unable to connect to localhost:6379,基本就是这个原因。
2. 密码含特殊字符
密码里有 @、#、: 时,写在 URL 形式的配置里需要转义。更稳妥的做法是用 host、port、password 分开配置,而不是拼一个 redis:// 地址。
3. 连接池配了没生效
只加了 starter,没加 commons-pool2,池化参数全部被忽略。压测时连接数上不去,就是这个原因。
4. 键名没有统一前缀
不同业务往同一个库塞 key,排查时根本分不清谁是谁。建议统一成 业务:模块:标识 的格式,比如 order:detail:1001,并给 key 设置合理的过期时间。
5. 用 keys * 查数据
keys * 会阻塞 Redis,生产环境数据量大时直接卡住。用 SCAN 游标遍历,或者通过业务前缀 + SCAN MATCH 查询。
6. 存了大对象
把一个几百 KB 的列表整体塞进一个 key,每次读写都要传输和反序列化,网络和 CPU 都吃不消。大 key 要拆分,或者改用 Hash 分字段存。
7. 缓存和数据库双写不一致
先更新数据库再删缓存、先删缓存再更新数据库,两种顺序都有并发问题。这个话题展开比较长,核心原则是:缓存只做加速,关键数据以数据库为准,删除缓存失败要有补偿。
排查步骤
连不上 Redis 时,按"网络—认证—序列化"的顺序排查:
- 容器里
redis-cli ping是否正常,确认 Redis 本身活着; - 宿主机
telnet 127.0.0.1 6379或nc -zv,确认端口通; - 看应用日志里的异常类型:连接拒绝、认证失败、超时,对应的问题完全不同;
- 用
redis-cli -a 密码 info看connected_clients、used_memory、keyspace_hits; - 序列化问题看
redis-cli get key的内容,乱码说明还在用 JDK 序列化。
redis-cli -h 127.0.0.1 -p 6379 -a change-me info memory
redis-cli -h 127.0.0.1 -p 6379 -a change-me slowlog get 10
slowlog 能看出有没有慢命令,info memory 能看出内存是否接近上限。把这两条养成习惯,很多问题在变成故障之前就能发现。
评论