RedisTemplate使用完全指南详解如何正确释放连接资源避免内存泄漏提升应用性能与系统稳定性确保高效运行
1. RedisTemplate概述
RedisTemplate是Spring Data Redis提供的核心类,用于简化Redis操作。它提供了丰富的操作接口,支持各种Redis数据类型的操作,并封装了连接管理、序列化等底层细节,使得开发者能够更加便捷地使用Redis。
RedisTemplate主要特点包括:
- 提供了丰富的操作接口(opsForValue、opsForList、opsForSet等)
- 支持多种序列化方式(JDK、JSON、String等)
- 封装了连接池管理,简化资源操作
- 支持事务操作
- 提供了回调机制,可以灵活操作Redis命令
@Configuration public class RedisConfig { @Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) { RedisTemplate<String, Object> template = new RedisTemplate<>(); template.setConnectionFactory(connectionFactory); // 设置key的序列化方式 template.setKeySerializer(new StringRedisSerializer()); // 设置value的序列化方式 template.setValueSerializer(new GenericJackson2JsonRedisSerializer()); // 设置hash key的序列化方式 template.setHashKeySerializer(new StringRedisSerializer()); // 设置hash value的序列化方式 template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer()); template.afterPropertiesSet(); return template; } }
2. RedisTemplate连接管理机制
RedisTemplate的连接管理是通过RedisConnectionFactory实现的,它负责创建和管理Redis连接。Spring Data Redis提供了多种连接工厂实现,如JedisConnectionFactory、LettuceConnectionFactory等。
2.1 连接池配置
合理配置连接池对于避免资源浪费和提高性能至关重要:
@Bean public LettuceConnectionFactory redisConnectionFactory() { RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration(); configuration.setHostName("localhost"); configuration.setPort(6379); configuration.setPassword(RedisPassword.of("yourpassword")); LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder() .useSsl() // 如果使用SSL .and() .commandTimeout(Duration.ofSeconds(5)) .shutdownTimeout(Duration.ZERO) .build(); // 连接池配置 LettucePoolingClientConfiguration poolConfig = LettucePoolingClientConfiguration.builder() .clientOptions(ClientOptions.builder() .autoReconnect(true) .build()) .poolConfig(new GenericObjectPoolConfig<RedisConnectionFactory>() { { setMaxTotal(20); // 最大连接数 setMaxIdle(10); // 最大空闲连接数 setMinIdle(5); // 最小空闲连接数 setMaxWaitMillis(3000); // 获取连接最大等待时间 setTestOnBorrow(true); // 获取连接时检查有效性 setTestWhileIdle(true); // 空闲时检查有效性 setTimeBetweenEvictionRunsMillis(30000); // 空闲连接检查周期 } }) .commandTimeout(Duration.ofSeconds(5)) .shutdownTimeout(Duration.ZERO) .build(); return new LettuceConnectionFactory(configuration, poolConfig); }
2.2 连接生命周期
RedisTemplate中的连接获取和释放是自动管理的,但了解其内部机制有助于正确使用:
- 当执行Redis操作时,RedisTemplate会从连接工厂获取连接
- 操作完成后,连接会被自动释放回连接池
- 在事务或管道模式下,连接会在整个操作完成后才释放
// RedisTemplate内部获取和释放连接的简化示例 public <T> T execute(RedisCallback<T> action) { RedisConnection connection = null; try { // 1. 获取连接 connection = RedisConnectionUtils.getConnection(getConnectionFactory()); // 2. 执行操作 return action.doInRedis(connection); } finally { // 3. 释放连接 RedisConnectionUtils.releaseConnection(connection, getConnectionFactory()); } }
3. 正确使用RedisTemplate避免连接泄漏
连接泄漏是使用RedisTemplate时常见的问题,可能导致连接池耗尽、性能下降甚至系统崩溃。以下是避免连接泄漏的最佳实践。
3.1 使用RedisTemplate的高级API
优先使用RedisTemplate提供的高级API,而不是直接操作RedisConnection:
// 推荐:使用高级API @Autowired private RedisTemplate<String, Object> redisTemplate; public void setValue(String key, Object value) { redisTemplate.opsForValue().set(key, value); } // 不推荐:直接操作连接 public void setValueDirectly(String key, Object value) { redisTemplate.execute(new RedisCallback<Void>() { @Override public Void doInRedis(RedisConnection connection) throws DataAccessException { connection.set(key.getBytes(), SerializationUtils.serialize(value)); return null; } }); }
3.2 正确使用RedisCallback和SessionCallback
当需要执行多个Redis操作或使用Redis特有功能时,可以使用RedisCallback或SessionCallback:
// 使用SessionCallback确保多个操作在同一连接中执行 public void transfer(String fromKey, String toKey, double amount) { redisTemplate.execute(new SessionCallback<Object>() { @Override public Object execute(RedisOperations operations) throws DataAccessException { // 开启事务 operations.multi(); // 执行多个操作 operations.opsForValue().get(fromKey); operations.opsForValue().get(toKey); // 提交事务 return operations.exec(); } }); }
3.3 确保资源释放
当直接使用RedisConnection时,必须确保资源被正确释放:
// 错误示例:可能导致连接泄漏 public void wrongExample() { RedisConnection connection = redisTemplate.getConnectionFactory().getConnection(); // 执行操作... // 如果发生异常,连接可能不会被释放 connection.close(); } // 正确示例:使用try-finally确保资源释放 public void correctExample() { RedisConnection connection = null; try { connection = redisTemplate.getConnectionFactory().getConnection(); // 执行操作... } finally { if (connection != null) { connection.close(); } } } // 更好的示例:使用RedisTemplate的回调机制 public void betterExample() { redisTemplate.execute(new RedisCallback<Void>() { @Override public Void doInRedis(RedisConnection connection) throws DataAccessException { // 执行操作... // 连接会自动释放 return null; } }); }
3.4 事务处理中的连接管理
在事务处理中,连接会在整个事务期间保持打开状态,直到事务提交或回滚:
// 事务处理示例 public void transactionalExample() { redisTemplate.execute(new SessionCallback<Object>() { @Override public Object execute(RedisOperations operations) throws DataAccessException { // 开启事务 operations.multi(); try { // 执行多个操作 operations.opsForValue().set("key1", "value1"); operations.opsForValue().set("key2", "value2"); // 模拟异常 if (someCondition) { throw new RuntimeException("Something went wrong"); } // 提交事务 return operations.exec(); } catch (Exception e) { // 回滚事务 operations.discard(); throw e; } } }); }
3.5 管道(Pipeline)模式下的连接管理
管道模式可以批量执行命令,减少网络往返时间,提高性能:
// 管道模式示例 public void pipelineExample() { List<Object> results = redisTemplate.executePipelined(new RedisCallback<Object>() { @Override public Object doInRedis(RedisConnection connection) throws DataAccessException { // 批量执行命令 for (int i = 0; i < 1000; i++) { connection.set(("key:" + i).getBytes(), ("value:" + i).getBytes()); } // 返回null,结果将通过executePipelined方法返回 return null; } }); // 处理结果 System.out.println("Pipeline executed, results size: " + results.size()); }
4. RedisTemplate性能优化策略
4.1 序列化优化
选择合适的序列化方式对性能有显著影响:
@Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) { RedisTemplate<String, Object> template = new RedisTemplate<>(); template.setConnectionFactory(connectionFactory); // 使用StringRedisSerializer序列化key,提高可读性和性能 template.setKeySerializer(new StringRedisSerializer()); // 根据场景选择value序列化方式 // 1. 对于简单对象,可以使用GenericJackson2JsonRedisSerializer template.setValueSerializer(new GenericJackson2JsonRedisSerializer()); // 2. 对于复杂对象,可以考虑自定义序列化器 // template.setValueSerializer(new CustomObjectSerializer()); // 3. 对于纯文本,使用StringRedisSerializer性能最佳 // template.setValueSerializer(new StringRedisSerializer()); // 4. 对于二进制数据,可以使用JdkSerializationRedisSerializer // template.setValueSerializer(new JdkSerializationRedisSerializer()); template.setHashKeySerializer(new StringRedisSerializer()); template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer()); template.afterPropertiesSet(); return template; }
4.2 批量操作优化
使用批量操作减少网络往返时间:
// 批量设置值 public void batchSet(Map<String, Object> keyValueMap) { // 使用mSet批量设置 redisTemplate.opsForValue().multiSet(keyValueMap); // 或者使用管道模式 redisTemplate.executePipelined(new RedisCallback<Object>() { @Override public Object doInRedis(RedisConnection connection) throws DataAccessException { for (Map.Entry<String, Object> entry : keyValueMap.entrySet()) { connection.set(entry.getKey().getBytes(), SerializationUtils.serialize(entry.getValue())); } return null; } }); } // 批量获取值 public Map<String, Object> batchGet(Collection<String> keys) { // 使用multiGet批量获取 return redisTemplate.opsForValue().multiGet(keys); }
4.3 连接池参数调优
根据应用负载情况调整连接池参数:
@Bean public LettuceConnectionFactory redisConnectionFactory() { // ... 其他配置 ... // 根据应用特点调整连接池参数 GenericObjectPoolConfig<RedisConnectionFactory> poolConfig = new GenericObjectPoolConfig<>(); // 高并发应用:增加连接数 poolConfig.setMaxTotal(50); poolConfig.setMaxIdle(20); poolConfig.setMinIdle(10); // 低延迟应用:减少等待时间 poolConfig.setMaxWaitMillis(1000); // 资源受限环境:减少连接数 // poolConfig.setMaxTotal(10); // poolConfig.setMaxIdle(5); // poolConfig.setMinIdle(2); // ... 其他配置 ... }
4.4 使用本地缓存减少Redis访问
对于频繁访问但不经常变化的数据,可以考虑使用本地缓存:
@Service public class DataService { @Autowired private RedisTemplate<String, Object> redisTemplate; // 使用Guava Cache作为本地缓存 private Cache<String, Object> localCache = CacheBuilder.newBuilder() .maximumSize(1000) .expireAfterWrite(5, TimeUnit.MINUTES) .build(); public Object getData(String key) { // 首先尝试从本地缓存获取 Object value = localCache.getIfPresent(key); if (value != null) { return value; } // 本地缓存没有,从Redis获取 value = redisTemplate.opsForValue().get(key); if (value != null) { // 放入本地缓存 localCache.put(key, value); } return value; } public void updateData(String key, Object value) { // 更新Redis redisTemplate.opsForValue().set(key, value); // 更新本地缓存 localCache.put(key, value); } }
5. 常见问题和解决方案
5.1 连接泄漏问题
问题表现:应用运行一段时间后,Redis操作变慢或超时,日志中出现连接池耗尽错误。
解决方案:
- 检查代码中是否有未正确释放的RedisConnection
- 使用连接池监控工具跟踪连接使用情况
- 配置连接池的空闲连接回收策略
// 连接池监控示例 @Bean public LettuceConnectionFactory redisConnectionFactory() { // ... 其他配置 ... GenericObjectPoolConfig<RedisConnectionFactory> poolConfig = new GenericObjectPoolConfig<>(); // ... 其他配置 ... // 启用JMX监控 poolConfig.setJmxEnabled(true); poolConfig.setJmxNamePrefix("redis-pool"); // ... 其他配置 ... } // 监控连接池状态 @Service public class RedisPoolMonitor { @Autowired private LettuceConnectionFactory connectionFactory; public void printPoolStatus() { GenericObjectPool<RedisConnectionFactory> pool = (GenericObjectPool<RedisConnectionFactory>) connectionFactory.getPool(); System.out.println("Active connections: " + pool.getNumActive()); System.out.println("Idle connections: " + pool.getNumIdle()); System.out.println("Waiters count: " + pool.getNumWaiters()); } }
5.2 序列化问题
问题表现:存入Redis的数据无法正确读取,或类型转换错误。
解决方案:
- 确保存入和读取使用相同的序列化方式
- 对于复杂对象,考虑使用JSON序列化
- 处理版本兼容性问题
// 自定义序列化器,处理版本兼容性 public class CustomObjectSerializer implements RedisSerializer<Object> { private final ObjectMapper objectMapper; public CustomObjectSerializer() { this.objectMapper = new ObjectMapper(); // 配置ObjectMapper处理版本兼容性 this.objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); } @Override public byte[] serialize(Object object) throws SerializationException { if (object == null) { return new byte[0]; } try { return objectMapper.writeValueAsString(object).getBytes(StandardCharsets.UTF_8); } catch (JsonProcessingException e) { throw new SerializationException("Could not serialize object", e); } } @Override public Object deserialize(byte[] bytes) throws SerializationException { if (bytes == null || bytes.length == 0) { return null; } try { return objectMapper.readValue(new String(bytes, StandardCharsets.UTF_8), Object.class); } catch (IOException e) { throw new SerializationException("Could not deserialize object", e); } } }
5.3 大键值问题
问题表现:操作大键值时Redis响应缓慢,甚至导致Redis阻塞。
解决方案:
- 避免存储过大的值,考虑分片存储
- 使用异步操作
- 优化数据结构
// 大值分片存储示例 @Service public class LargeValueService { private static final int CHUNK_SIZE = 1024 * 1024; // 1MB @Autowired private RedisTemplate<String, Object> redisTemplate; public void storeLargeValue(String key, byte[] value) { int chunkCount = (int) Math.ceil((double) value.length / CHUNK_SIZE); // 存储元数据 redisTemplate.opsForHash().put(key + ":meta", "chunks", chunkCount); // 分片存储 for (int i = 0; i < chunkCount; i++) { int start = i * CHUNK_SIZE; int end = Math.min(start + CHUNK_SIZE, value.length); byte[] chunk = Arrays.copyOfRange(value, start, end); redisTemplate.opsForHash().put(key + ":data", String.valueOf(i), chunk); } } public byte[] retrieveLargeValue(String key) { // 获取元数据 Integer chunkCount = (Integer) redisTemplate.opsForHash().get(key + ":meta", "chunks"); if (chunkCount == null) { return null; } // 计算总大小 int totalSize = 0; List<byte[]> chunks = new ArrayList<>(chunkCount); for (int i = 0; i < chunkCount; i++) { byte[] chunk = (byte[]) redisTemplate.opsForHash().get(key + ":data", String.valueOf(i)); if (chunk != null) { totalSize += chunk.length; chunks.add(chunk); } } // 合并分片 byte[] result = new byte[totalSize]; int offset = 0; for (byte[] chunk : chunks) { System.arraycopy(chunk, 0, result, offset, chunk.length); offset += chunk.length; } return result; } }
5.4 高并发下的性能问题
问题表现:高并发场景下Redis操作响应变慢,吞吐量下降。
解决方案:
- 使用连接池优化连接管理
- 采用批量操作和管道技术
- 考虑使用本地缓存减少Redis访问
- 实现请求限流和熔断机制
// 使用Hystrix实现熔断和限流 @Service public class RedisServiceWithCircuitBreaker { @Autowired private RedisTemplate<String, Object> redisTemplate; @HystrixCommand(fallbackMethod = "getValueFallback", commandProperties = { @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "100"), @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "50"), @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "5000"), @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1000") }) public Object getValue(String key) { return redisTemplate.opsForValue().get(key); } public Object getValueFallback(String key) { // 返回默认值或从备用数据源获取 return "DEFAULT_VALUE"; } @HystrixCommand(fallbackMethod = "setValueFallback", commandProperties = { @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1000") }) public void setValue(String key, Object value) { redisTemplate.opsForValue().set(key, value); } public void setValueFallback(String key, Object value) { // 记录日志或发送到消息队列稍后重试 System.err.println("Failed to set value for key: " + key); } }
6. 最佳实践和案例研究
6.1 统一Redis操作封装
创建一个统一的Redis操作工具类,封装常用操作,确保资源正确释放:
@Component public class RedisManager { @Autowired private RedisTemplate<String, Object> redisTemplate; // 字符串操作 public void set(String key, Object value) { redisTemplate.opsForValue().set(key, value); } public void set(String key, Object value, long timeout, TimeUnit unit) { redisTemplate.opsForValue().set(key, value, timeout, unit); } public Object get(String key) { return redisTemplate.opsForValue().get(key); } // 哈希操作 public void hSet(String key, String field, Object value) { redisTemplate.opsForHash().put(key, field, value); } public Object hGet(String key, String field) { return redisTemplate.opsForHash().get(key, field); } public Map<Object, Object> hGetAll(String key) { return redisTemplate.opsForHash().entries(key); } // 列表操作 public long lPush(String key, Object value) { return redisTemplate.opsForList().leftPush(key, value); } public List<Object> lRange(String key, long start, long end) { return redisTemplate.opsForList().range(key, start, end); } // 集合操作 public long sAdd(String key, Object... values) { return redisTemplate.opsForSet().add(key, values); } public Set<Object> sMembers(String key) { return redisTemplate.opsForSet().members(key); } // 有序集合操作 public boolean zAdd(String key, Object value, double score) { return redisTemplate.opsForZSet().add(key, value, score); } public Set<Object> zRange(String key, long start, long end) { return redisTemplate.opsForZSet().range(key, start, end); } // 事务操作 public List<Object> executeTransaction(SessionCallback<List<Object>> sessionCallback) { return redisTemplate.execute(sessionCallback); } // 管道操作 public List<Object> executePipeline(SessionCallback<List<Object>> sessionCallback) { return redisTemplate.executePipelined(sessionCallback); } // 键过期操作 public Boolean expire(String key, long timeout, TimeUnit unit) { return redisTemplate.expire(key, timeout, unit); } public Boolean persist(String key) { return redisTemplate.persist(key); } // 键删除操作 public Boolean delete(String key) { return redisTemplate.delete(key); } public Long delete(Collection<String> keys) { return redisTemplate.delete(keys); } }
6.2 缓存穿透、击穿、雪崩解决方案
@Service public class CacheService { @Autowired private RedisManager redisManager; @Autowired private DatabaseService databaseService; // 解决缓存穿透:使用布隆过滤器 private BloomFilter<String> bloomFilter = BloomFilter.create( Funnels.stringFunnel(Charset.defaultCharset()), 1000000, 0.01); // 解决缓存击穿:使用互斥锁 private Map<String, Lock> keyLocks = new ConcurrentHashMap<>(); // 解决缓存雪崩:设置随机过期时间 private Random random = new Random(); public Object getDataWithCacheProtection(String key) { // 1. 使用布隆过滤器防止缓存穿透 if (!bloomFilter.mightContain(key)) { return null; } // 2. 尝试从缓存获取 Object value = redisManager.get(key); if (value != null) { return value; } // 3. 使用互斥锁防止缓存击穿 Lock lock = keyLocks.computeIfAbsent(key, k -> new ReentrantLock()); try { lock.lock(); // 双重检查,防止其他线程已经加载了数据 value = redisManager.get(key); if (value != null) { return value; } // 4. 从数据库加载数据 value = databaseService.loadFromDatabase(key); if (value != null) { // 5. 添加到布隆过滤器 bloomFilter.put(key); // 6. 设置随机过期时间防止缓存雪崩 int expireTime = 3600 + random.nextInt(600); // 1小时+随机10分钟 redisManager.set(key, value, expireTime, TimeUnit.SECONDS); } else { // 对于不存在的数据,设置短过期时间 redisManager.set(key, "", 60, TimeUnit.SECONDS); } return value; } finally { lock.unlock(); keyLocks.remove(key); } } }
6.3 分布式锁实现
@Component public class RedisDistributedLock { @Autowired private RedisTemplate<String, String> redisTemplate; private static final String LOCK_PREFIX = "lock:"; private static final long DEFAULT_EXPIRE_TIME = 30; // 默认锁过期时间:30秒 /** * 尝试获取分布式锁 * @param lockKey 锁的key * @param requestId 请求标识 * @param expireTime 锁过期时间(秒) * @return 是否获取成功 */ public boolean tryLock(String lockKey, String requestId, long expireTime) { return redisTemplate.opsForValue().setIfAbsent( LOCK_PREFIX + lockKey, requestId, expireTime, TimeUnit.SECONDS); } /** * 尝试获取分布式锁(使用默认过期时间) * @param lockKey 锁的key * @param requestId 请求标识 * @return 是否获取成功 */ public boolean tryLock(String lockKey, String requestId) { return tryLock(lockKey, requestId, DEFAULT_EXPIRE_TIME); } /** * 释放分布式锁 * @param lockKey 锁的key * @param requestId 请求标识 * @return 是否释放成功 */ 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"; DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class); Long result = redisTemplate.execute( redisScript, Collections.singletonList(LOCK_PREFIX + lockKey), requestId); return result != null && result == 1; } /** * 带超时的锁获取 * @param lockKey 锁的key * @param requestId 请求标识 * @param timeout 获取锁超时时间(毫秒) * @return 是否获取成功 */ public boolean lockWithTimeout(String lockKey, String requestId, long timeout) { return lockWithTimeout(lockKey, requestId, timeout, DEFAULT_EXPIRE_TIME); } /** * 带超时的锁获取 * @param lockKey 锁的key * @param requestId 请求标识 * @param timeout 获取锁超时时间(毫秒) * @param expireTime 锁过期时间(秒) * @return 是否获取成功 */ public boolean lockWithTimeout(String lockKey, String requestId, long timeout, long expireTime) { long startTime = System.currentTimeMillis(); long remainingTime = timeout; while (remainingTime > 0) { if (tryLock(lockKey, requestId, expireTime)) { return true; } try { Thread.sleep(50); // 短暂休眠,避免CPU空转 } catch (InterruptedException e) { Thread.currentThread().interrupt(); return false; } remainingTime = timeout - (System.currentTimeMillis() - startTime); } return false; } }
6.4 实际应用案例:电商系统缓存方案
@Service public class ECommerceCacheService { @Autowired private RedisManager redisManager; @Autowired private ProductService productService; // 商品缓存 private static final String PRODUCT_CACHE_PREFIX = "product:"; private static final String PRODUCT_HOT_CACHE_PREFIX = "hot:product:"; private static final String PRODUCT_CATEGORY_CACHE_PREFIX = "category:product:"; // 缓存过期时间 private static final long PRODUCT_CACHE_EXPIRE = 24 * 60 * 60; // 24小时 private static final long HOT_PRODUCT_CACHE_EXPIRE = 1 * 60 * 60; // 1小时 private static final long CATEGORY_PRODUCT_CACHE_EXPIRE = 12 * 60 * 60; // 12小时 /** * 获取商品信息(使用缓存) */ public Product getProductById(Long productId) { String cacheKey = PRODUCT_CACHE_PREFIX + productId; // 尝试从缓存获取 Product product = (Product) redisManager.get(cacheKey); if (product != null) { return product; } // 缓存未命中,从数据库加载 product = productService.getProductById(productId); if (product != null) { // 放入缓存 redisManager.set(cacheKey, product, PRODUCT_CACHE_EXPIRE, TimeUnit.SECONDS); // 如果是热门商品,同时放入热门商品缓存 if (product.isHot()) { String hotCacheKey = PRODUCT_HOT_CACHE_PREFIX + productId; redisManager.set(hotCacheKey, product, HOT_PRODUCT_CACHE_EXPIRE, TimeUnit.SECONDS); } // 按分类缓存 String categoryCacheKey = PRODUCT_CATEGORY_CACHE_PREFIX + product.getCategoryId(); redisManager.opsForSet().add(categoryCacheKey, productId); redisManager.expire(categoryCacheKey, CATEGORY_PRODUCT_CACHE_EXPIRE, TimeUnit.SECONDS); } return product; } /** * 获取热门商品列表 */ public List<Product> getHotProducts(int limit) { // 使用管道批量获取热门商品 List<Object> results = redisManager.executePipeline(new SessionCallback<List<Object>>() { @Override public List<Object> execute(RedisOperations operations) throws DataAccessException { Set<String> keys = operations.keys(PRODUCT_HOT_CACHE_PREFIX + "*"); if (keys == null || keys.isEmpty()) { return Collections.emptyList(); } for (String key : keys) { operations.opsForValue().get(key); } return null; } }); // 转换结果并限制数量 return results.stream() .limit(limit) .map(obj -> (Product) obj) .collect(Collectors.toList()); } /** * 获取分类下的商品列表 */ public List<Product> getProductsByCategory(Long categoryId, int page, int size) { String categoryCacheKey = PRODUCT_CATEGORY_CACHE_PREFIX + categoryId; // 获取分类下的商品ID集合 Set<Object> productIds = redisManager.opsForSet().members(categoryCacheKey); if (productIds == null || productIds.isEmpty()) { return Collections.emptyList(); } // 分页处理 List<Long> pagedIds = productIds.stream() .map(id -> Long.valueOf(id.toString())) .skip((long) (page - 1) * size) .limit(size) .collect(Collectors.toList()); if (pagedIds.isEmpty()) { return Collections.emptyList(); } // 使用管道批量获取商品信息 List<String> cacheKeys = pagedIds.stream() .map(id -> PRODUCT_CACHE_PREFIX + id) .collect(Collectors.toList()); List<Object> products = redisManager.executePipeline(new SessionCallback<List<Object>>() { @Override public List<Object> execute(RedisOperations operations) throws DataAccessException { for (String key : cacheKeys) { operations.opsForValue().get(key); } return null; } }); // 转换结果 return products.stream() .map(obj -> (Product) obj) .filter(Objects::nonNull) .collect(Collectors.toList()); } /** * 更新商品信息(同时更新缓存) */ public void updateProduct(Product product) { // 更新数据库 productService.updateProduct(product); // 更新商品缓存 String cacheKey = PRODUCT_CACHE_PREFIX + product.getId(); redisManager.set(cacheKey, product, PRODUCT_CACHE_EXPIRE, TimeUnit.SECONDS); // 如果是热门商品,更新热门商品缓存 if (product.isHot()) { String hotCacheKey = PRODUCT_HOT_CACHE_PREFIX + product.getId(); redisManager.set(hotCacheKey, product, HOT_PRODUCT_CACHE_EXPIRE, TimeUnit.SECONDS); } else { // 如果不再是热门商品,从热门商品缓存中删除 String hotCacheKey = PRODUCT_HOT_CACHE_PREFIX + product.getId(); redisManager.delete(hotCacheKey); } } /** * 删除商品(同时删除相关缓存) */ public void deleteProduct(Long productId) { // 获取商品信息,以便知道分类ID Product product = productService.getProductById(productId); if (product == null) { return; } // 删除数据库中的商品 productService.deleteProduct(productId); // 删除商品缓存 String cacheKey = PRODUCT_CACHE_PREFIX + productId; redisManager.delete(cacheKey); // 删除热门商品缓存 String hotCacheKey = PRODUCT_HOT_CACHE_PREFIX + productId; redisManager.delete(hotCacheKey); // 从分类商品集合中删除 String categoryCacheKey = PRODUCT_CATEGORY_CACHE_PREFIX + product.getCategoryId(); redisManager.opsForSet().remove(categoryCacheKey, productId); } }
7. 总结
正确使用RedisTemplate并管理连接资源对于构建高性能、稳定的Redis应用至关重要。本文详细介绍了RedisTemplate的连接管理机制、避免连接泄漏的方法、性能优化策略以及常见问题的解决方案。
关键要点包括:
连接管理:理解RedisTemplate的连接获取和释放机制,合理配置连接池参数,避免连接泄漏。
资源释放:优先使用RedisTemplate提供的高级API,当直接使用RedisConnection时,确保在finally块中释放资源。
性能优化:选择合适的序列化方式,使用批量操作和管道技术,合理配置连接池参数,考虑使用本地缓存减少Redis访问。
问题处理:针对连接泄漏、序列化问题、大键值问题和高并发性能问题,提供相应的解决方案。
最佳实践:通过统一封装、缓存保护机制、分布式锁和实际应用案例,展示了如何在实际项目中正确使用RedisTemplate。
通过遵循这些指导原则和最佳实践,开发者可以构建出高效、稳定、可扩展的Redis应用,充分发挥Redis作为高性能内存数据库的优势。