🧠 一、RedisTemplate 核心概述
RedisTemplate 是 Spring Data Redis 提供的核心工具类,它极大简化了 Java 应用与 Redis 的交互。它封装了连接管理、序列化/反序列化,并提供了类型安全的 API 来操作 Redis 的各种数据结构,支持事务、管道、发布订阅等高级特性,同时将 Redis 异常转换为 Spring 的统一数据访问异常体系。
关键特性与设计:
- 类型安全:所有操作都是泛型的,保证了编译时的类型检查。
- 丰富的 API:支持 String、Hash、List、Set、ZSet 等所有 Redis 数据结构。
- 连接管理:通过
RedisConnectionFactory配置连接,支持 Lettuce(Spring Data Redis 2.x 及以后版本的默认客户端)和 Jedis,并支持连接池。 - 序列化灵活:可自定义键和值的序列化方式,这是避免存储乱码和提升性能的关键。
⚙️ 二、基本配置与序列化
正确的序列化配置至关重要,推荐使用 StringRedisSerializer序列化键,使用 GenericJackson2JsonRedisSerializer或 JdkSerializationRedisSerializer序列化值。
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
// 使用 String 序列化 key,确保可读性
template.setKeySerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
// 使用 Jackson2JsonRedisSerializer 序列化 value,存储为 JSON
Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
template.setValueSerializer(serializer);
template.setHashValueSerializer(serializer);
template.afterPropertiesSet();
return template;
}
// 连接池配置示例 (以 Lettuce 为例)
@Bean
public LettuceConnectionFactory redisConnectionFactory() {
RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();
config.setHostName("localhost");
config.setPort(6379);
// config.setPassword("yourpassword"); // 如果需要密码
LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder()
.commandTimeout(Duration.ofSeconds(2))
.build();
return new LettuceConnectionFactory(config, clientConfig);
}
}
连接池配置(通常在 application.yml中)对于生产环境必不可少:
spring:
redis:
lettuce:
pool:
max-active: 8 # 最大连接数
max-idle: 8 # 最大空闲连接数
min-idle: 0 # 最小空闲连接数
max-wait: 100ms # 获取连接的最大等待时间
timeout: 2000ms # 连接超时时间
📊 三、数据结构操作详解
RedisTemplate 通过 opsForXxx()方法提供对不同数据结构的操作。
| 数据结构 | 获取操作接口 | 常用操作示例 |
|---|---|---|
| String | opsForValue() | set(key, value), get(key), setIfAbsent(key, value)(原子实现分布式锁), increment(key, delta)(原子计数) |
| Hash | opsForHash() | put(key, hashKey, value), get(key, hashKey), entries(key)(获取所有字段) |
| List | opsForList() | leftPush(key, value), rightPop(key), range(key, start, end) |
| Set | opsForSet() | add(key, values), members(key), isMember(key, value) |
| ZSet | opsForZSet() | add(key, value, score), range(key, start, end), reverseRangeWithScores(key, start, end)(带分数获取排名) |
1. String(字符串)操作
适用于缓存、计数器、分布式锁等场景。
// 设置值与过期时间
redisTemplate.opsForValue().set("user:1001:name", "Alice", 30, TimeUnit.MINUTES);
// 获取值
String userName = (String) redisTemplate.opsForValue().get("user:1001:name");
// 原子递增 - 非常适合计数场景
Long pageViews = redisTemplate.opsForValue().increment("page:views:home");
// 分布式锁的关键操作:仅在键不存在时设置
Boolean lockAcquired = redisTemplate.opsForValue().setIfAbsent("lock:order:123", "processing", 10, TimeUnit.SECONDS);
// 获取旧值并设置新值
String oldStatus = (String) redisTemplate.opsForValue().getAndSet("task:1001:status", "completed");
2. Hash(哈希)操作
非常适合存储对象,可以单独操作对象的字段。
// 设置单个字段
redisTemplate.opsForHash().put("user:1001", "name", "Alice");
redisTemplate.opsForHash().put("user:1001", "age", 30);
// 批量设置多个字段
Map<String, String> userProfile = new HashMap<>();
userProfile.put("email", "alice@example.com");
userProfile.put("city", "Beijing");
redisTemplate.opsForHash().putAll("user:1001", userProfile);
// 获取单个字段
String name = (String) redisTemplate.opsForHash().get("user:1001", "name");
// 获取所有字段和值
Map<Object, Object> userData = redisTemplate.opsForHash().entries("user:1001");
// 删除字段
redisTemplate.opsForHash().delete("user:1001", "tempData");
// 原子递增哈希字段的值
redisTemplate.opsForHash().increment("user:1001", "loginCount", 1);
3. List(列表)操作
适用于消息队列、最新列表等场景。
// 从左侧插入(LPUSH)
redisTemplate.opsForList().leftPush("task:queue", "task1");
// 从右侧插入(RPUSH)
redisTemplate.opsForList().rightPush("news:feed", "news1");
// 批量插入
List<String> tasks = Arrays.asList("task2", "task3", "task4");
redisTemplate.opsForList().leftPushAll("task:queue", tasks);
// 获取列表范围 (0到-1表示所有元素)
List<Object> pendingTasks = redisTemplate.opsForList().range("task:queue", 0, -1);
// 从左侧弹出元素(移除并返回)
String nextTask = (String) redisTemplate.opsForList().leftPop("task:queue");
// 带阻塞时间的弹出,常用于消息队列
String task = (String) redisTemplate.opsForList().leftPop("task:queue", 30, TimeUnit.SECONDS);
4. Set(集合)操作
适用于存储不重复元素,如标签、好友列表,支持集合运算。
// 添加元素
redisTemplate.opsForSet().add("user:1001:tags", "java", "redis", "spring");
// 获取所有元素
Set<Object> userTags = redisTemplate.opsForSet().members("user:1001:tags");
// 判断元素是否存在
Boolean hasJava = redisTemplate.opsForSet().isMember("user:1001:tags", "java");
// 求交集
Set<Object> commonTags = redisTemplate.opsForSet().intersect("user:1001:tags", "user:1002:tags");
// 求并集
Set<Object> allTags = redisTemplate.opsForSet().union("user:1001:tags", "user:1002:tags");
// 随机弹出元素
String randomTag = (String) redisTemplate.opsForSet().pop("user:1001:tags");
5. ZSet(有序集合)操作
适用于排行榜、带优先级的队列等场景。
// 添加元素(成员和分数)
redisTemplate.opsForZSet().add("leaderboard", "PlayerA", 95.0);
redisTemplate.opsForZSet().add("leaderboard", "PlayerB", 88.0);
// 按分数升序获取排名范围
Set<Object> topPlayers = redisTemplate.opsForZSet().range("leaderboard", 0, 2);
// 按分数降序获取排名范围(获取前3名)
Set<Object> topPlayersRev = redisTemplate.opsForZSet().reverseRange("leaderboard", 0, 2);
// 带分数获取元素
Set<ZSetOperations.TypedTuple<String>> playersWithScores = redisTemplate.opsForZSet().rangeWithScores("leaderboard", 0, -1);
// 获取元素的排名(升序排名,从0开始)
Long rank = redisTemplate.opsForZSet().rank("leaderboard", "PlayerB");
// 增加元素的分数
Double newScore = redisTemplate.opsForZSet().incrementScore("leaderboard", "PlayerB", 5.0);
6. 通用键操作
这些操作通常直接通过 redisTemplate调用。
// 删除键
redisTemplate.delete("some:key");
// 判断键是否存在
Boolean exists = redisTemplate.hasKey("some:key");
// 设置过期时间
redisTemplate.expire("user:1001", 30, TimeUnit.MINUTES);
// 获取剩余生存时间
Long ttl = redisTemplate.getExpire("user:1001");
// 移除过期时间,使键持久化
redisTemplate.persist("user:1001");
🚀 四、高级特性与实战应用
1. 管道(Pipeline)
用于批量执行大量 Redis 命令,减少网络往返次数(RTT),显著提升性能。
List<Object> results = redisTemplate.executePipelined(new SessionCallback<Object>() {
@Override
public Object execute(RedisOperations operations) throws DataAccessException {
for (int i = 0; i < 1000; i++) {
operations.opsForValue().set("product:view:" + i, "0");
}
return null;
}
});
2. 事务(Transaction)
通过 multi()和 exec()保证多个命令的原子性执行。
List<Object> txResults = redisTemplate.execute(new SessionCallback<List<Object>>() {
@Override
public List<Object> execute(RedisOperations operations) throws DataAccessException {
operations.multi(); // 开启事务
operations.opsForValue().increment("account:A:balance", -100);
operations.opsForValue().increment("account:B:balance", 100);
return operations.exec(); // 执行事务
}
});
注意:Redis 事务是“部分原子性”的。命令在入队时出错(如语法错误)会导致整个事务被丢弃;而执行时出错(如对错误数据类型操作)则只会失败该命令,其他命令仍会执行。
3. 发布/订阅 (Pub/Sub)
用于实现简单的消息通知机制。
// 发布消息到指定频道
redisTemplate.convertAndSend("news:channel", "New product launched!");
// 订阅消息需要配置消息监听容器(MessageListenerContainer)
@Bean
public MessageListenerContainer messageListenerContainer(RedisConnectionFactory connectionFactory) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
container.addMessageListener((message, pattern) -> {
System.out.println("Received: " + new String(message.getBody()));
}, new ChannelTopic("news:channel"));
return container;
}
4. Lua脚本执行
保证复杂操作的原子性。
// 一个简单的Lua脚本示例,用于检查并设置值
String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('set', KEYS[1], ARGV[2]) else return 0 end";
DefaultRedisScript<Long> script = new DefaultRedisScript<>();
script.setScriptText(luaScript);
script.setResultType(Long.class);
Long result = redisTemplate.execute(script, Collections.singletonList("myKey"), "oldValue", "newValue");
💡 五、实战应用场景
-
缓存加速(Cache Aside Pattern)
这是最经典的场景,能有效减轻数据库压力。
@Service public class ProductService { @Autowired private RedisTemplate<String, Object> redisTemplate; private static final String PRODUCT_KEY_PREFIX = "product:"; public Product getProductById(Long id) { String key = PRODUCT_KEY_PREFIX + id; // 1. 先从缓存中查询 Product product = (Product) redisTemplate.opsForValue().get(key); if (product != null) { return product; } // 2. 缓存中没有,则查询数据库 product = productRepository.findById(id).orElseThrow(...); // 3. 将数据库查询结果写入缓存,并设置过期时间 redisTemplate.opsForValue().set(key, product, Duration.ofMinutes(30)); return product; } // 更新或删除数据时,建议先操作数据库,然后使缓存失效(删除缓存键) public void updateProduct(Product product) { productRepository.save(product); String key = PRODUCT_KEY_PREFIX + product.getId(); redisTemplate.delete(key); // 让下次查询时重新加载缓存 } }缓存问题避坑指南:
- 缓存穿透:查询不存在的数据。解决方案:缓存空值(
set(key, null, shortTtl))或使用布隆过滤器。 - 缓存雪崩:大量缓存同时失效。解决方案:为缓存过期时间添加随机值(
baseTtl + randomTtl)。 - 缓存击穿:热点 key 过期瞬间大量请求涌入。解决方案:使用互斥锁(如 Redis 分布式锁)或逻辑过期(值中存储过期时间,由后台线程更新)。
- 缓存穿透:查询不存在的数据。解决方案:缓存空值(
📝 缓存穿透详解与代码实现
缓存穿透是指查询一个数据库中根本不存在的数据,导致请求直接穿透缓存到达数据库,可能使数据库承受巨大压力。
解决方案与代码实现:
-
缓存空值
核心思路是将数据库中不存在的事实也缓存一段时间,避免重复查询数据库。
public String getData(String key) { // 1. 尝试从缓存获取 String value = redisTemplate.opsForValue().get(key); // 2. 缓存中存在数据(包括空值) if (value != null) { // 判断是否是预设的空值标记 return "NULL".equals(value) ? null : value; } // 3. 缓存不存在,查询数据库 value = database.query(key); if (value == null) { // 4. 数据库也为空,缓存空值并设置较短过期时间 redisTemplate.opsForValue().set(key, "NULL", Duration.ofMinutes(5)); return null; } else { // 5. 数据库存在数据,缓存有效数据 redisTemplate.opsForValue().set(key, value, Duration.ofHours(1)); return value; } } -
布隆过滤器
布隆过滤器是一种概率型数据结构,用于快速判断一个元素肯定不存在或可能存在于某个集合中。
// 初始化布隆过滤器(通常会在应用启动时进行) public class BloomFilterService { private static BloomFilter<String> bloomFilter; @PostConstruct public void init() { // 参数1000000:预期元素数量;参数0.01:误判率 bloomFilter = BloomFilter.create(Funnels.stringFunnel(StandardCharsets.UTF_8), 1000000, 0.01); // 预热:将数据库中已存在的键加载到布隆过滤器 List<String> existingKeys = database.getAllExistingKeys(); for (String key : existingKeys) { bloomFilter.put(key); } } public String getDataWithBloomFilter(String key) { // 1. 布隆过滤器判断key是否可能存在 if (!bloomFilter.mightContain(key)) { return null; // 肯定不存在,直接返回,避免访问缓存和数据库 } // 2. 后续流程与缓存空值方案类似 return getData(key); // 这里可以接上方的getData方法逻辑 } }注意:布隆过滤器无法删除数据,适用于数据相对稳定的场景。对于数据频繁删除的场景,可以考虑使用可删除的布隆过滤器变种。
⚡️ 缓存击穿详解与代码实现
缓存击穿是指一个热点key在缓存过期的瞬间,大量并发请求无法从缓存中获取数据,同时去查询数据库,导致数据库瞬时压力激增。
解决方案与代码实现:
-
互斥锁
核心思想是当缓存失效时,只允许一个线程去查询数据库并重建缓存,其他线程等待。
public String getDataWithMutex(String key) { // 1. 尝试从缓存获取 String value = redisTemplate.opsForValue().get(key); if (value != null) { return value; } // 2. 缓存未命中,尝试获取分布式锁 String lockKey = "lock:" + key; boolean isLockAcquired = false; try { // 使用SETNX命令尝试获取锁,并设置锁的过期时间防止死锁 isLockAcquired = Boolean.TRUE.equals( redisTemplate.opsForValue().setIfAbsent(lockKey, "locked", Duration.ofSeconds(10)) ); if (isLockAcquired) { // 3. 成功获取锁,再次检查缓存(双重检查,因为可能已有其他线程重建了缓存) value = redisTemplate.opsForValue().get(key); if (value != null) { return value; } // 4. 查询数据库并重建缓存 value = database.query(key); if (value != null) { redisTemplate.opsForValue().set(key, value, Duration.ofMinutes(30)); } else { // 处理数据库也为空的情况,可缓存空值 redisTemplate.opsForValue().set(key, "NULL", Duration.ofMinutes(5)); } return value; } else { // 5. 未获取到锁,等待片刻后重试 Thread.sleep(50); return getDataWithMutex(key); // 递归重试 } } catch (InterruptedException e) { Thread.currentThread().interrupt(); return null; } finally { // 6. 释放锁 if (isLockAcquired) { redisTemplate.delete(lockKey); } } } -
逻辑过期
不依赖Redis的物理过期时间,而是将过期时间戳存储在value中。当发现数据逻辑过期时,由当前请求触发异步更新,并直接返回旧数据。
@Data public class RedisData { private Object data; // 存储的业务数据,如User对象 private LocalDateTime expireTime; // 逻辑过期时间 } public String getDataWithLogicalExpire(String key) { // 1. 从缓存中获取数据 String json = redisTemplate.opsForValue().get(key); if (json == null) { return null; // 缓存不存在,按需处理 } // 2. 反序列化,获取数据和逻辑过期时间 RedisData redisData = objectMapper.readValue(json, RedisData.class); String data = (String) redisData.getData(); LocalDateTime expireTime = redisData.getExpireTime(); // 3. 判断是否逻辑过期 if (expireTime.isAfter(LocalDateTime.now())) { // 未过期,直接返回数据 return data; } else { // 已过期,尝试获取锁进行缓存重建 String lockKey = "lock:refresh:" + key; boolean isLockAcquired = Boolean.TRUE.equals( redisTemplate.opsForValue().setIfAbsent(lockKey, "1", Duration.ofSeconds(30)) ); if (isLockAcquired) { // 成功获取锁,开启异步线程重建缓存 CompletableFuture.runAsync(() -> { try { // 查询最新数据 String newData = database.query(key); // 设置新的逻辑过期时间 RedisData newRedisData = new RedisData(); newRedisData.setData(newData); newRedisData.setExpireTime(LocalDateTime.now().plusMinutes(30)); // 写入缓存 redisTemplate.opsForValue().set(key, objectMapper.writeValueAsString(newRedisData)); } catch (Exception e) { e.printStackTrace(); } finally { // 释放锁 redisTemplate.delete(lockKey); } }); } // 无论是否获取锁,都返回旧的(已过期的)数据 return data; } }
❄️ 缓存雪崩详解与代码实现
缓存雪崩是指在某一时刻,缓存中大量的key同时失效,导致所有请求直接涌向数据库,造成数据库压力过大甚至崩溃。
解决方案与代码实现:
-
设置随机的过期时间
这是最直接有效的方法,避免大量key在同一时刻失效。
public void setCacheWithRandomExpire(String key, String value, long baseTtl, TimeUnit unit) { // 将基础过期时间转换为秒 long baseTtlInSeconds = unit.toSeconds(baseTtl); // 生成一个随机偏移量(例如,在0到10分钟之间随机) long randomOffsetInSeconds = new Random().nextInt(600); // 0~600秒 // 计算最终的过期时间 long finalTtlInSeconds = baseTtlInSeconds + randomOffsetInSeconds; redisTemplate.opsForValue().set(key, value, Duration.ofSeconds(finalTtlInSeconds)); } // 使用示例:基础过期时间为1小时,实际过期时间在1小时到1小时10分钟之间随机 setCacheWithRandomExpire("product:123", productJson, 1, TimeUnit.HOURS); -
构建多级缓存
引入本地缓存(如Caffeine、Ehcache)作为一级缓存,Redis作为二级缓存。即使Redis部分key失效,请求也可能在本地缓存命中,极大地减轻数据库压力。
@Component public class MultiLevelCacheService { @Autowired private RedisTemplate<String, String> redisTemplate; // 本地缓存(使用Caffeine) private Cache<String, String> localCache = Caffeine.newBuilder() .expireAfterWrite(5, TimeUnit.MINUTES) // 本地缓存过期时间 .maximumSize(10000) // 本地缓存最大容量 .build(); public String getDataWithMultiLevel(String key) { // 1. 先查本地缓存 return localCache.get(key, k -> { // 2. 本地缓存未命中,查询Redis String value = redisTemplate.opsForValue().get(k); if (value != null) { // 3. Redis命中,数据也会被自动放入本地缓存(由Caffeine的get方法完成) return value; } else { // 4. Redis也未命中,查询数据库(这里应结合互斥锁等机制防止击穿) String dbValue = database.query(k); if (dbValue != null) { // 随机设置Redis过期时间,防止雪崩 setCacheWithRandomExpire(k, dbValue, 1, TimeUnit.HOURS); } return dbValue != null ? dbValue : "NULL"; // 返回数据,本地缓存也会存"NULL" } }); } } -
缓存永不过期与异步更新
对极热点数据,可以设置为永不过期,通过后台任务或消息队列异步更新缓存。
@Scheduled(fixedRate = 1800000) // 每30分钟执行一次 public void refreshHotData() { List<String> hotKeys = getHotKeys(); // 获取热点key列表 for (String key : hotKeys) { String newData = database.query(key); if (newData != null) { // 永不过期,或者设置很长的过期时间 redisTemplate.opsForValue().set(key, newData); } } }
💎 核心要点对比与总结
为了让您更清晰地对比这三种问题,下表总结了它们的核心区别和应对策略。
| 问题类型 | 核心特征 | 根本原因 | 核心解决方案 |
|---|---|---|---|
| 缓存穿透 | 查询数据库中不存在的数据 | 恶意攻击或业务bug产生大量无效key | 1. 缓存空值 2. 布隆过滤器 3. 接口层参数校验 |
| 缓存击穿 | 某个热点key过期瞬间 | 高并发请求在热点key失效时同时到达 | 1. 互斥锁 2. 逻辑过期 3. 缓存预热 |
| 缓存雪崩 | 大量key在相近时间段失效 | 缓存集体失效或缓存服务宕机 | 1. 随机过期时间 2. 多级缓存 3. 集群高可用 |
在实际生产环境中,通常需要根据业务场景组合使用这些方案。例如,对于一个电商热点商品查询,可以同时采用:布隆过滤器防止穿透、互斥锁防止击穿、设置随机过期时间并配合多级缓存来预防雪崩。同时,建立完善的监控系统,关注缓存命中率和数据库QPS,也是保证系统稳定的关键环节。
-
分布式锁
在分布式系统中协调对共享资源的访问。
@Component public class RedisDistributedLock { @Autowired private RedisTemplate<String, String> redisTemplate; private static final String LOCK_PREFIX = "lock:"; public boolean tryLock(String lockKey, String requestId, long expireTime, TimeUnit unit) { // 使用SETNX命令,并设置过期时间防止死锁 return Boolean.TRUE.equals( redisTemplate.opsForValue().setIfAbsent( LOCK_PREFIX + lockKey, requestId, expireTime, unit ) ); } public void unlock(String lockKey, String requestId) { // 释放锁时需验证requestId,防止误删其他服务的锁 // 注意:此非原子操作,生产环境建议使用Lua脚本 String lockValue = redisTemplate.opsForValue().get(LOCK_PREFIX + lockKey); if (requestId.equals(lockValue)) { redisTemplate.delete(LOCK_PREFIX + lockKey); } } } -
原子计数与秒杀
Redis 的原子操作非常适合计数和高并发库存扣减场景。
public boolean trySeckill(Long productId) { String stockKey = "seckill:stock:" + productId; // 原子递减库存 Long remainingStock = redisTemplate.opsForValue().decrement(stockKey); if (remainingStock != null && remainingStock >= 0) { // 扣减成功,发送MQ消息异步创建订单等后续操作 return true; } else { // 库存不足,回滚库存 redisTemplate.opsForValue().increment(stockKey); return false; } } -
会话存储 (Session Storage)
使用 Hash 结构存储用户会话对象,支持字段级更新,非常高效。
public void saveUserSession(String sessionId, Map<String, Object> sessionData) { String key = "session:" + sessionId; redisTemplate.opsForHash().putAll(key, sessionData); // 批量写入Hash字段 redisTemplate.expire(key, Duration.ofHours(1)); // 设置过期时间 } public void updateSessionAttribute(String sessionId, String attributeName, Object attributeValue) { String key = "session:" + sessionId; redisTemplate.opsForHash().put(key, attributeName, attributeValue); // 只更新单个字段 }
🛠️ 六、企业级最佳实践与避坑
-
封装通用工具类
为避免代码冗余和统一异常处理,可封装
RedisUtils。@Component public class RedisUtils { @Autowired private RedisTemplate<String, Object> redisTemplate; public boolean set(String key, Object value, long timeout, TimeUnit unit) { try { if (timeout > 0) { redisTemplate.opsForValue().set(key, value, timeout, unit); } else { redisTemplate.opsForValue().set(key, value); } return true; } catch (Exception e) { log.error("Redis set error for key: {}", key, e); return false; } } // ... 类似地封装get、delete、expire、hGet、hSet等方法,并处理异常 } -
异常处理
实现统一的异常处理逻辑。
try { redisTemplate.opsForValue().set("key", "value"); } catch (RedisSystemException e) { // 处理Redis系统异常,如连接失败 log.error("Redis operation failed", e); } catch (DataAccessException e) { // 处理数据访问异常 log.warn("Data access error", e); } -
性能优化建议
- 对读密集型操作使用
@Cacheable注解。 - 对写密集型操作考虑使用管道或批量操作。
- 合理设置过期时间避免内存泄漏。
- 避免使用大Key( value 过大)和热Key(访问过于频繁的 key)。
- 集成监控(如 Spring Boot Actuator)来关注 Redis 连接状态和性能指标。
- 对读密集型操作使用
-
常见问题解决方案
- 序列化异常:确保键值对使用兼容的序列化器,混合使用不同序列化器会导致反序列化失败。
- 连接超时:检查网络配置,适当增加超时时间,配置重试机制。
- 内存不足:设置合理的 maxmemory 策略,监控内存使用情况。
- 集群模式问题:确保 HashTag 使用正确,避免数据分布不均。
💎 总结
RedisTemplate 是 Spring 生态中操作 Redis 的强大工具,通过合理的配置和使用,可以极大地提升应用的性能和开发效率。
核心要点回顾:
- 配置是基础:正确的序列化配置和连接池配置是稳定运行的基石。
- 数据结构是核心:熟练掌握五种数据结构的特性和应用场景。
- 高级特性提能力:管道、事务、Pub/Sub 和 Lua 脚本能解决复杂场景问题。
- 实践最佳实践:缓存策略、分布式锁、工具封装和异常处理是生产环境的保障。