接入 Redis 是我做 Java 后端绕不过去的一课。最开始只是单纯地用它做接口缓存,后来随着业务复杂度上升,开始用到分布式锁、Spring Cache 集成,也踩过几个生产环境里让人头疼的坑。这篇文章是当时的使用记录,整理成了速查手册的形式,方便之后翻阅。
基础命令 select 0 keys * set key value get key del key exists key expire key 60 ttl key type key flushdb flushall
ttl 在排查缓存问题时很常用,-2 说明 key 不存在或已过期,不要和 -1 搞混。
五种数据类型速查 String 最常用的类型,value 除了字符串还可以是数字(做计数器)。
append key value incr key incrby key 10 setnx key value setex key 60 value mset k1 v1 k2 v2 mget k1 k2 getset key newvalue
setnx 是实现简单分布式锁的基础,但要注意原子性问题,生产建议配合 SET key value NX EX seconds 的完整写法。
List 可以当栈(lpush + lpop)、队列(lpush + rpop)、阻塞队列来用。
lpush key v1 v2 rpush key v1 v2 lrange key 0 -1 lpop / rpop key llen key lindex key 0 lrem key 2 value ltrim key 1 3 lset key 0 newvalue
Set 无序不重复集合,适合去重、关注关系、共同好友等场景。
sadd key v1 v2 smembers key scard key srem key value srandmember key 3 spop key sinter k1 k2 sdiff k1 k2 sunion k1 k2
Hash key-field-value 结构,适合存储对象(用户信息、商品详情等)。
hset key field value hget key field hgetall key hdel key field hlen key hexists key field hkeys key hvals key
相比把对象 JSON 序列化成 String 存储,Hash 的优势是可以单独更新某个字段,不需要全量读写。
Zset(有序集合) 带 score 的有序集合,适合排行榜、优先队列等场景。
zadd key 100 value zrange key 0 -1 zrangebyscore key -inf +inf zrevrangebyscore key +inf -inf zrem key value zcard key zscore key value
持久化 Redis 默认持久化方式是 RDB,两种方式可以同时开启。
RDB(数据快照) 在指定时间间隔内将内存数据以二进制快照写入磁盘(文件名默认 dump.rdb)。
默认触发规则:
900 秒内有 1 个 key 变化 300 秒内有 10 个 key 变化 60 秒内有 10000 个 key 变化 优点 :文件紧凑、恢复速度快,适合做定时备份。缺点 :两次快照之间的数据如果宕机会丢失。
AOF(追加日志) 将每次写命令追加到日志文件,重启时通过重放日志恢复数据。
三种同步策略:
策略 配置值 说明 每次写命令同步 always数据最安全,性能最差 每秒同步一次 everysec最多丢失 1 秒数据,推荐 由 OS 决定 no性能最好,安全性最差
项目中的选择 :对数据一致性要求不高的缓存场景用 RDB 就够,如果是重要业务数据(如积分、库存辅助计算),开启 AOF + everysec 是更稳妥的做法。
Spring Boot 接入 依赖和配置 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-data-redis</artifactId > </dependency >
spring: redis: host: localhost port: 6379 database: 0 password: timeout: 3000ms lettuce: pool: max-active: 8 max-idle: 8 min-idle: 0 max-wait: -1ms
自定义 RedisTemplate(解决序列化问题) 默认 RedisTemplate<Object, Object> 使用 JDK 序列化,存进去的 key 会带前缀乱码,建议自定义:
@Configuration @EnableCaching public class RedisCacheConfig extends CachingConfigurerSupport { @Bean public RedisSerializer<Object> redisSerializer () { Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer <>(Object.class); ObjectMapper objectMapper = new ObjectMapper (); objectMapper.activateDefaultTyping( LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL); objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false ); objectMapper.registerModule(new JavaTimeModule ()); serializer.setObjectMapper(objectMapper); return serializer; } @Bean public RedisTemplate<String, Object> redisTemplate (RedisConnectionFactory factory) { RedisTemplate<String, Object> template = new RedisTemplate <>(); template.setConnectionFactory(factory); StringRedisSerializer stringSerializer = new StringRedisSerializer (); template.setKeySerializer(stringSerializer); template.setHashKeySerializer(stringSerializer); template.setValueSerializer(redisSerializer()); template.setHashValueSerializer(redisSerializer()); template.afterPropertiesSet(); return template; } @Bean public RedisCacheManager redisCacheManager (RedisConnectionFactory factory) { RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig() .serializeValuesWith( RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer())) .entryTtl(Duration.ofDays(1 )); return new RedisCacheManager ( RedisCacheWriter.nonLockingRedisCacheWriter(factory), config); } }
Spring Cache 注解使用 开启缓存后,在 Service 层用注解标注即可,无需手动写 get/set:
@Cacheable(value = "schoolInfo", key = "#sid + '-' + #name", unless = "#result == null") public SchoolInfo getSchoolInfo (Long sid, String name) { ... }@CachePut(value = "schoolInfo", key = "#info.id") public SchoolInfo updateSchoolInfo (SchoolInfo info) { ... }@CacheEvict(value = "schoolInfo", key = "#sid") public void deleteSchoolInfo (Long sid) { ... }
注意 :@Cacheable 方法在同一个 Bean 内部调用时不会触发缓存(Spring AOP 代理限制),如果有内部调用场景要注意。
问题排查记录 Lettuce 连接重置异常 现象 :项目上线后偶发 Connection reset by peer 异常:
org.springframework.data.redis.RedisSystemException: Redis exception; nested exception is io.lettuce.core.RedisException: java.io.IOException: Connection reset by peer
原因排查 :Redis 服务端默认 timeout=0(永不超时),但某些网络环境(云服务、容器)中间会有 NAT 超时,导致空闲连接被网络设备强制断开,而 Lettuce 客户端没有感知到。
解决方案 :修改 Redis 服务端配置,让服务端主动发送心跳并回收空闲连接:
127.0.0.1:6379> config get timeout 127.0.0.1:6379> config get tcp-keepalive config set maxclients 30000 config set timeout 300 config set tcp-keepalive 60
永久生效 :在 redis.conf 中同步修改对应配置项即可。
小结 Redis 的使用门槛不高,但要用好还是有一些细节值得注意:
key 命名建议用 : 分隔层级,如 user:profile:1001,方便用 GUI 工具(RedisInsight)查看 设置过期时间是必须的好习惯,避免内存无限增长 生产环境 flushall 是高危操作,没有二次确认,执行前三思 Spring Cache 适合简单缓存场景,复杂的缓存策略(如多级缓存、手动失效)还是直接用 RedisTemplate 更灵活