物种进化模拟器最新版
104.67MB · 2025-11-27
RedisTemplate 是 Spring Data Redis 提供的核心工具类,它极大简化了 Java 应用与 Redis 的交互。它封装了连接管理、序列化/反序列化,并提供了类型安全的 API 来操作 Redis 的各种数据结构,支持事务、管道、发布订阅等高级特性,同时将 Redis 异常转换为 Spring 的统一数据访问异常体系。
关键特性与设计:
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)(带分数获取排名) |
适用于缓存、计数器、分布式锁等场景。
// 设置值与过期时间
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");
非常适合存储对象,可以单独操作对象的字段。
// 设置单个字段
redisTemplate.opsForHash().put("user:1001", "name", "Alice");
redisTemplate.opsForHash().put("user:1001", "age", 30);
// 批量设置多个字段
Map<String, String> userProfile = new HashMap<>();
userProfile.put("email", "[email protected]");
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);
适用于消息队列、最新列表等场景。
// 从左侧插入(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);
适用于存储不重复元素,如标签、好友列表,支持集合运算。
// 添加元素
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");
适用于排行榜、带优先级的队列等场景。
// 添加元素(成员和分数)
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);
这些操作通常直接通过 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");
用于批量执行大量 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;
}
});
通过 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 事务是“部分原子性”的。命令在入队时出错(如语法错误)会导致整个事务被丢弃;而执行时出错(如对错误数据类型操作)则只会失败该命令,其他命令仍会执行。
用于实现简单的消息通知机制。
// 发布消息到指定频道
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;
}
保证复杂操作的原子性。
// 一个简单的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)。分布式锁
在分布式系统中协调对共享资源的访问。
@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注解。常见问题解决方案
RedisTemplate 是 Spring 生态中操作 Redis 的强大工具,通过合理的配置和使用,可以极大地提升应用的性能和开发效率。
核心要点回顾: