
1. RedisTemplate基础入门RedisTemplate是Spring Data Redis提供的核心工具类它封装了与Redis交互的各种操作。想象一下它就像是一个多功能瑞士军刀帮你处理所有与Redis打交道的脏活累活。我在实际项目中使用RedisTemplate已经有五年多时间今天就把最实用的经验分享给你。先来看一个最简单的配置示例。在Spring Boot项目中只需要在pom.xml中添加依赖dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-redis/artifactId /dependency然后在application.properties中配置Redis连接信息spring.redis.host127.0.0.1 spring.redis.port6379 spring.redis.database0这样基础环境就搭建好了。RedisTemplate会自动配置好你可以直接通过Autowired注入使用Autowired private RedisTemplateString, Object redisTemplate;这里有个小细节需要注意RedisTemplate是泛型类通常我们会指定key为String类型value为Object类型。这样设计的好处是value可以存储任意Java对象RedisTemplate会自动帮我们序列化和反序列化。2. 五种数据结构的操作指南2.1 字符串(String)操作字符串是最基础的数据类型但千万别小看它。在我的电商项目中90%的缓存场景都是用字符串实现的。比如商品详情缓存// 设置缓存 redisTemplate.opsForValue().set(product:1001, productDetail, 30, TimeUnit.MINUTES); // 获取缓存 ProductDetail detail (ProductDetail)redisTemplate.opsForValue().get(product:1001);这里有几个实用技巧键的命名建议用冒号分隔形成命名空间比如product:1001设置过期时间可以防止数据长期占用内存值可以是任意Java对象RedisTemplate会用JDK序列化2.2 哈希(Hash)操作哈希特别适合存储对象。比如用户信息// 存储用户信息 MapString, String userMap new HashMap(); userMap.put(name, 张三); userMap.put(age, 28); redisTemplate.opsForHash().putAll(user:1001, userMap); // 获取单个字段 String name (String)redisTemplate.opsForHash().get(user:1001, name);哈希的优势在于可以单独操作字段不用每次都读写整个对象。我在用户会话管理中就大量使用了哈希结构。2.3 列表(List)操作列表可以实现队列和栈的功能。比如消息队列// 左进右出实现队列 redisTemplate.opsForList().leftPush(message:queue, msg1); String message (String)redisTemplate.opsForList().rightPop(message:queue); // 获取列表范围 ListString messages redisTemplate.opsForList().range(message:queue, 0, -1);实际项目中我常用列表来实现简单的任务队列配合BLPOP命令可以实现阻塞式获取。2.4 集合(Set)操作集合的特点是元素唯一适合去重场景。比如抽奖活动去重// 添加抽奖用户 redisTemplate.opsForSet().add(lucky:draw, user1, user2, user3); // 随机抽取 String luckyUser redisTemplate.opsForSet().randomMember(lucky:draw);集合还支持交并差运算我在社交项目的共同好友功能中就用了这个特性。2.5 有序集合(ZSet)操作有序集合可以排序适合排行榜场景// 添加分数 redisTemplate.opsForZSet().add(leaderboard, player1, 100); redisTemplate.opsForZSet().incrementScore(leaderboard, player1, 5); // 获取前10名 SetString topPlayers redisTemplate.opsForZSet().reverseRange(leaderboard, 0, 9);游戏项目中我每天都会用ZSet来处理各种实时排行榜。3. 高级特性实战3.1 管道(Pipeline)优化管道可以大幅提升批量操作的性能。我做过测试批量插入1000条数据使用管道能快10倍以上ListObject results redisTemplate.executePipelined((RedisCallbackObject) connection - { for (int i 0; i 1000; i) { connection.stringCommands().set((key: i).getBytes(), (value: i).getBytes()); } return null; });管道原理是将多个命令一次性发送到Redis减少网络往返时间。但要注意管道中的命令没有原子性保证单个管道不宜包含太多命令建议分批处理3.2 事务(Transaction)处理Redis事务可以保证一组命令的原子性执行redisTemplate.setEnableTransactionSupport(true); redisTemplate.multi(); try { redisTemplate.opsForValue().set(key1, value1); redisTemplate.opsForValue().set(key2, value2); redisTemplate.exec(); } catch (Exception e) { redisTemplate.discard(); }我在订单系统中就用事务来处理库存扣减和订单创建的原子操作。注意Redis事务和数据库事务不同它不支持回滚只能在执行前检查。3.3 发布订阅(Pub/Sub)发布订阅模式可以实现简单的消息系统// 发布消息 redisTemplate.convertAndSend(order.channel, 新订单创建); // 订阅消息 redisTemplate.getConnectionFactory().getConnection().subscribe((message, pattern) - { System.out.println(收到消息 new String(message.getBody())); }, order.channel.getBytes());我在实时通知功能中就用了这个模式比如订单状态变更通知。3.4 Lua脚本执行Lua脚本可以实现复杂逻辑的原子操作String script local count redis.call(get, KEYS[1]) if count and tonumber(count) tonumber(ARGV[1]) then return redis.call(decrby, KEYS[1], ARGV[1]) else return -1 end; RedisScriptLong redisScript new DefaultRedisScript(script, Long.class); Long result redisTemplate.execute(redisScript, Collections.singletonList(product:stock), 5);我在秒杀系统中用Lua脚本处理库存扣减确保原子性。Lua脚本的优势是减少网络开销和保证原子性。4. 性能优化与最佳实践4.1 序列化方案选择RedisTemplate默认使用JDK序列化但实际项目中我推荐这样配置Bean public RedisTemplateString, Object redisTemplate(RedisConnectionFactory factory) { RedisTemplateString, Object template new RedisTemplate(); template.setConnectionFactory(factory); // 使用String序列化key template.setKeySerializer(new StringRedisSerializer()); // 使用JSON序列化value template.setValueSerializer(new GenericJackson2JsonRedisSerializer()); return template; }JSON序列化的好处是可读性好可以直接在Redis中查看兼容性强其他语言也能读取性能较好比JDK序列化更高效4.2 连接池配置合理的连接池配置对性能影响很大spring.redis.lettuce.pool.max-active50 spring.redis.lettuce.pool.max-idle20 spring.redis.lettuce.pool.min-idle10 spring.redis.lettuce.pool.max-wait2000根据我的经验最大连接数建议50-100根据业务量调整最小空闲连接保持10-20避免频繁创建最大等待时间设置1-3秒防止线程堆积4.3 缓存穿透与雪崩处理缓存穿透是指查询不存在的数据我的解决方案public Product getProduct(String id) { String key product: id; // 1. 先查缓存 Product product (Product)redisTemplate.opsForValue().get(key); if (product ! null) { return product; } // 2. 查数据库 product productDao.findById(id); if (product null) { // 3. 空值缓存防止穿透 redisTemplate.opsForValue().set(key, NullProduct.instance, 5, TimeUnit.MINUTES); return null; } // 4. 设置正常缓存 redisTemplate.opsForValue().set(key, product, 1, TimeUnit.HOURS); return product; }缓存雪崩是指大量缓存同时失效我的处理方法是给缓存过期时间加随机值// 基础过期时间 随机0-300秒 int expireTime 3600 new Random().nextInt(300); redisTemplate.opsForValue().set(key, value, expireTime, TimeUnit.SECONDS);4.4 热点Key处理对于热点Key我采用本地缓存Redis的多级缓存方案// 使用Caffeine作为本地缓存 private CacheString, Product localCache Caffeine.newBuilder() .maximumSize(1000) .expireAfterWrite(30, TimeUnit.SECONDS) .build(); public Product getHotProduct(String id) { // 1. 先查本地缓存 Product product localCache.getIfPresent(id); if (product ! null) { return product; } // 2. 查Redis product (Product)redisTemplate.opsForValue().get(product: id); if (product ! null) { // 3. 放入本地缓存 localCache.put(id, product); return product; } // 4. 查数据库... }5. 常见问题排查5.1 连接超时问题遇到连接超时我通常这样排查检查Redis服务是否正常运行检查网络连通性检查连接池配置是否合理检查是否有慢查询阻塞Redis可以在配置中增加超时设置spring.redis.timeout30005.2 内存溢出问题Redis内存不足时我的处理步骤检查最大内存配置maxmemory检查淘汰策略maxmemory-policy分析大Keyredis-cli --bigkeys优化数据结构比如用Hash代替多个String5.3 序列化异常遇到序列化异常时检查确保所有操作使用相同的序列化方式检查Java类是否实现了Serializable检查serialVersionUID是否一致考虑使用JSON等跨语言序列化方案5.4 集群模式问题Redis集群模式下需要注意事务和Lua脚本要求所有Key在同一个slotPipeline也需要考虑slot分布使用{}来强制将相关Key放到同一个slot比如{user}:1001和{user}:10026. 实际项目案例6.1 秒杀系统实现在秒杀系统中我这样使用Redis// 秒杀脚本 String script local stock redis.call(get, KEYS[1]) if not stock or tonumber(stock) 0 then return 0 end redis.call(decr, KEYS[1]) redis.call(sadd, KEYS[2], ARGV[1]) return 1; RedisScriptLong secKillScript new DefaultRedisScript(script, Long.class); public boolean secKill(String productId, String userId) { Long result redisTemplate.execute(secKillScript, Arrays.asList(sec: productId, sec:success: productId), userId); return result 1; }6.2 分布式锁实现我常用的分布式锁方案public boolean tryLock(String lockKey, String requestId, long expireTime) { return redisTemplate.opsForValue().setIfAbsent( lockKey, requestId, expireTime, TimeUnit.MILLISECONDS ); } public boolean releaseLock(String lockKey, String requestId) { String script if redis.call(get, KEYS[1]) ARGV[1] then return redis.call(del, KEYS[1]) else return 0 end; RedisScriptLong redisScript new DefaultRedisScript(script, Long.class); Long result redisTemplate.execute(redisScript, Collections.singletonList(lockKey), requestId); return result 1; }6.3 延迟队列实现使用ZSet实现延迟队列// 添加延迟任务 public void addDelayTask(String taskId, Object data, long delayTime) { redisTemplate.opsForZSet().add(delay:queue, data, System.currentTimeMillis() delayTime); } // 处理到期任务 public void processDelayTask() { SetObject tasks redisTemplate.opsForZSet().rangeByScore( delay:queue, 0, System.currentTimeMillis(), 0, 10); for (Object task : tasks) { // 处理任务 redisTemplate.opsForZSet().remove(delay:queue, task); } }7. 监控与维护7.1 监控指标我通常会监控这些Redis指标内存使用率连接数QPS慢查询Key数量7.2 常用命令一些实用的Redis命令# 查看内存信息 INFO memory # 查看慢查询 SLOWLOG GET 10 # 查看大Key redis-cli --bigkeys # 监控实时命令 MONITOR7.3 备份策略我的备份方案开启AOF持久化定时RDB快照重要数据双写数据库跨机房主从复制8. 未来发展与替代方案虽然RedisTemplate很好用但在某些场景下我也会考虑其他方案Redisson - 功能更丰富的Redis客户端Lettuce - 异步非阻塞的Redis客户端本地缓存 - 对于不常变化的数据RedisTemplate在Spring生态中仍然是首选特别是对于已经使用Spring的项目。它的优势在于与Spring的无缝集成丰富的功能API以及良好的社区支持。