1. Redis List操作实战:从基础到JSON序列化
Redis作为高性能的内存数据库,其List数据结构在实际开发中应用广泛。今天我将结合自己多年使用Redis的经验,详细讲解如何在Java中通过RedisTemplate操作List集合,并分享一些实际项目中积累的实用技巧。
2. 环境准备与基础配置
2.1 项目依赖配置
首先确保你的Spring Boot项目中已经添加了Redis相关依赖。在Maven项目中,pom.xml需要包含以下依赖:
xml复制<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
如果你需要使用Fastjson进行JSON序列化(后文会用到),还需要添加:
xml复制<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.78</version>
</dependency>
2.2 RedisTemplate配置
在Spring Boot应用中,通常会在配置类中对RedisTemplate进行自定义配置。这里分享一个我常用的配置模板:
java复制@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
// 使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值
Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
mapper.activateDefaultTyping(mapper.getPolymorphicTypeValidator(), ObjectMapper.DefaultTyping.NON_FINAL);
serializer.setObjectMapper(mapper);
template.setValueSerializer(serializer);
template.setKeySerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(serializer);
template.afterPropertiesSet();
return template;
}
}
注意:在实际项目中,根据你的对象复杂度选择合适的序列化方式。简单场景可以使用JDK序列化,复杂对象推荐使用JSON序列化。
3. 基础List操作实战
3.1 基本List存取操作
让我们先看一个完整的List存取示例,这也是项目正文中提供的第一个案例:
java复制@RestController
@RequestMapping("/test")
@Slf4j
public class TestController {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@PostMapping("/redis/save/list")
public void redisSaveList() {
// 1. 准备测试数据
List<Person> list = getPersonList();
// 2. 清空原有List(避免重复数据)
while (redisTemplate.opsForList().size("personList") > 0) {
redisTemplate.opsForList().leftPop("personList");
}
// 3. 存储List到Redis
redisTemplate.opsForList().rightPushAll("personList", list);
// 4. 从Redis获取List
List<Object> personList = redisTemplate.opsForList().range("personList", 0, -1);
// 5. 遍历输出
personList.forEach(person ->
log.info("Person: {}", person.toString())
);
}
private List<Person> getPersonList() {
List<Person> list = new ArrayList<>();
list.add(new Person(1L, "张一", 11));
list.add(new Person(2L, "张二", 22));
list.add(new Person(3L, "张三", 33));
return list;
}
}
3.2 关键操作解析
3.2.1 清空List的正确姿势
项目中使用了while循环配合leftPop来清空List,这种方式在小数据量时可行,但在生产环境中可能会遇到性能问题。更高效的方式是直接删除key:
java复制redisTemplate.delete("personList");
3.2.2 批量插入数据
rightPushAll方法可以一次性插入整个List,其底层实现是循环调用rightPush,但减少了网络往返时间。对于大数据量(超过1000条)建议分批插入。
3.2.3 范围查询参数
range方法的第二个和第三个参数分别表示开始和结束索引:
- 0表示第一个元素
- -1表示最后一个元素
- -2表示倒数第二个元素,以此类推
4. JSON序列化方案实战
4.1 为什么需要JSON序列化
直接存储Java对象到Redis List虽然方便,但存在以下问题:
- 不同语言客户端无法读取
- 序列化后的数据可读性差
- 对象结构变更可能导致反序列化失败
使用JSON序列化可以解决这些问题,下面是一个完整示例:
java复制@RestController
@RequestMapping("/test/json")
@Slf4j
public class JsonRedisController {
@Autowired
private StringRedisTemplate stringRedisTemplate;
private static final String BUSINESS_LIST_KEY = "freeCarriage:businessIds";
@PostMapping("/save")
public void saveBusinessList() {
// 模拟从数据库获取的业务ID列表
List<Long> businessIdList = Arrays.asList(1001L, 1002L, 1003L);
// 序列化为JSON字符串存储
stringRedisTemplate.opsForValue().set(
BUSINESS_LIST_KEY,
JSON.toJSONString(businessIdList)
);
}
@GetMapping("/get")
public List<Long> getBusinessList() {
String json = stringRedisTemplate.opsForValue().get(BUSINESS_LIST_KEY);
return JSON.parseArray(json, Long.class);
}
}
4.2 JSON方案的优势与注意事项
优势:
- 数据可读性强,便于调试
- 跨语言兼容性好
- 对字段变更更宽容(新增字段不会导致反序列化失败)
注意事项:
- 大对象JSON序列化性能开销较大
- 需要处理JSON解析异常
- 字段名变更会影响已有数据
提示:对于复杂的嵌套对象,建议在序列化前进行扁平化处理,可以提高查询效率。
5. 性能优化与生产实践
5.1 批量操作优化
Redis是单线程模型,大量小操作会导致性能瓶颈。应该尽量使用批量操作:
java复制// 不推荐 - 多次网络往返
for (Person person : personList) {
redisTemplate.opsForList().rightPush("list", person);
}
// 推荐 - 单次批量操作
redisTemplate.opsForList().rightPushAll("list", personList);
5.2 管道(Pipeline)技术
对于超大规模数据(万级以上),可以使用Redis管道:
java复制List<Object> results = redisTemplate.executePipelined(new RedisCallback<Object>() {
@Override
public Object doInRedis(RedisConnection connection) throws DataAccessException {
for (int i = 0; i < 10000; i++) {
connection.rPush("pipelineList".getBytes(), ("value"+i).getBytes());
}
return null;
}
});
5.3 内存优化建议
- 对于纯数字列表,考虑使用Redis的ZSet结构,可以节省内存
- 定期清理过期数据,使用
EXPIRE命令设置合理TTL - 大List考虑分片存储,如
list:part1,list:part2
6. 常见问题排查
6.1 序列化异常
问题现象:
code复制org.springframework.data.redis.serializer.SerializationException: Cannot serialize
解决方案:
- 确保所有存储的对象实现了Serializable接口
- 检查自定义序列化器配置是否正确
- 对于第三方类,考虑使用JSON序列化
6.2 数据不一致
问题现象:
读取到的List与写入的不一致
排查步骤:
- 检查是否有多线程并发修改
- 确认使用的RedisTemplate实例是否一致
- 检查网络延迟是否导致读写不同步
6.3 性能问题
问题现象:
Redis操作响应缓慢
优化建议:
- 使用Redis监控工具分析慢查询
- 将大List拆分为多个小List
- 考虑使用Redis集群分担压力
7. 最佳实践总结
经过多个项目的实践验证,我总结了以下Redis List操作的最佳实践:
- 键命名规范:使用业务前缀+冒号分隔,如
order:pending:list - 序列化选择:简单数据用JDK序列化,复杂数据用JSON
- 批量操作:优先使用rightPushAll等批量方法
- 内存控制:单个List不宜超过1MB,超大列表考虑分片
- 异常处理:总是处理Redis访问异常,做好降级方案
- 连接管理:使用连接池并合理配置参数
对于特别重要的业务数据,建议实现双写机制:先写数据库,再写Redis,并做好失败补偿。