Redis使用记录

接入 Redis 是我做 Java 后端绕不过去的一课。最开始只是单纯地用它做接口缓存,后来随着业务复杂度上升,开始用到分布式锁、Spring Cache 集成,也踩过几个生产环境里让人头疼的坑。这篇文章是当时的使用记录,整理成了速查手册的形式,方便之后翻阅。


基础命令

select 0              # 切换数据库(默认 16 个,编号 0~15)
keys * # 查看所有 key
set key value # 插入或更新
get key # 获取值
del key # 删除
exists key # 判断是否存在
expire key 60 # 设置过期时间(秒)
ttl key # 查看剩余过期时间(-1 永不过期,-2 已过期/不存在)
type key # 查看数据类型
flushdb # 清除当前库
flushall # 清除所有库(谨慎!)

ttl 在排查缓存问题时很常用,-2 说明 key 不存在或已过期,不要和 -1 搞混。


五种数据类型速查

String

最常用的类型,value 除了字符串还可以是数字(做计数器)。

append key value          # 追加,key 不存在则等同于 set
incr key # 值 +1(原子操作,适合计数/访问量统计)
incrby key 10 # 值加指定步长
setnx key value # key 不存在才设置(分布式锁的基础操作)
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 # 获取全部(-1 表示末尾)
lpop / rpop key # 弹出头/尾元素
llen key # 获取列表长度
lindex key 0 # 按下标取值
lrem key 2 value # 删除指定数量的 value
ltrim key 1 3 # 只保留指定区间(会修改原列表)
lset key 0 newvalue # 更新指定下标的值

Set

无序不重复集合,适合去重、关注关系、共同好友等场景。

sadd key v1 v2            # 添加
smembers key # 查看所有元素
scard key # 元素个数
srem key value # 删除指定元素
srandmember key 3 # 随机取 n 个(不删除)
spop key # 随机删除一个
sinter k1 k2 # 交集(共同好友)
sdiff k1 k2 # 差集
sunion k1 k2 # 并集

Hash

key-field-value 结构,适合存储对象(用户信息、商品详情等)。

hset key field value      # 存值
hget key field # 取值
hgetall key # 获取全部 field 和 value
hdel key field # 删除指定 field
hlen key # field 数量
hexists key field # 判断 field 是否存在
hkeys key # 所有 field
hvals key # 所有 value

相比把对象 JSON 序列化成 String 存储,Hash 的优势是可以单独更新某个字段,不需要全量读写。

Zset(有序集合)

带 score 的有序集合,适合排行榜、优先队列等场景。

zadd key 100 value        # 添加,score 用于排序
zrange key 0 -1 # 按 score 升序取全部
zrangebyscore key -inf +inf # 按 score 范围取值
zrevrangebyscore key +inf -inf # 降序取值
zrem key value # 删除
zcard key # 元素总数
zscore key value # 查询某个 value 的 score

持久化

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: # 默认使用 Lettuce 连接池(非 Jedis)
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);
// 注册 LocalDateTime 等 Java8 时间类型支持
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();
// key 用 String 序列化,保持可读
template.setKeySerializer(stringSerializer);
template.setHashKeySerializer(stringSerializer);
// value 用 JSON 序列化
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)); // 默认缓存 1 天
return new RedisCacheManager(
RedisCacheWriter.nonLockingRedisCacheWriter(factory), config);
}
}

Spring Cache 注解使用

开启缓存后,在 Service 层用注解标注即可,无需手动写 get/set:

// 查询时触发缓存,key 为 sid+name 组合,结果为 null 不缓存
@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

# 临时修改(重启失效,建议同步写到 redis.conf)
config set maxclients 30000 # 最大连接数
config set timeout 300 # 空闲连接超时时间(秒),0 表示不超时
config set tcp-keepalive 60 # TCP 心跳间隔(秒)

永久生效:在 redis.conf 中同步修改对应配置项即可。


小结

Redis 的使用门槛不高,但要用好还是有一些细节值得注意:

  • key 命名建议用 : 分隔层级,如 user:profile:1001,方便用 GUI 工具(RedisInsight)查看
  • 设置过期时间是必须的好习惯,避免内存无限增长
  • 生产环境 flushall 是高危操作,没有二次确认,执行前三思
  • Spring Cache 适合简单缓存场景,复杂的缓存策略(如多级缓存、手动失效)还是直接用 RedisTemplate 更灵活