首都高赛车免安装正式中文版
6.7G · 2025-10-10
在 Spring Boot 项目中高效、合理地使用 Redis 同时执行多条命令,可以显著提升应用性能。下面我将为你介绍几种主要方式、它们的典型应用场景,以及如何在 Spring Boot 中实现。
首先,我们来通过一个表格快速了解这几种方式的特点和适用场景:
方式 | 原子性 | 主要优势 | Spring Boot 中的典型应用场景 |
---|---|---|---|
Pipeline (管道) | 否 | 大幅提升批量操作效率,减少网络往返次数 | 缓存预热、批量数据导入导出、无依赖关系的批量查询 |
事务 (Transaction) | 部分 | 命令队列化,保证连续执行(但不支持回滚) | 简单的原子操作序列,如库存扣减、更新多个相关键 |
Lua 脚本 | 是 | 复杂逻辑原子执行,避免中间状态,性能高 | 分布式锁、秒杀、需要原子性的复杂业务逻辑 |
Pipeline 允许客户端将多个命令打包后一次性发送给 Redis 服务器,服务器依次执行后再次将所有结果一次性返回给客户端。这能显著减少网络往返次数(RTT),从而大幅提升吞吐量,尤其在高延迟网络环境下效果明显。
Spring Boot 实现示例:
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class RedisPipelineService {
private final StringRedisTemplate stringRedisTemplate;
// 构造器注入
public RedisPipelineService(StringRedisTemplate stringRedisTemplate) {
this.stringRedisTemplate = stringRedisTemplate;
}
public void batchSetKeys(List<String> keys, List<String> values) {
// 使用 executePipelined 方法执行管道操作
List<Object> results = stringRedisTemplate.executePipelined((RedisCallback<Object>) connection -> {
for (int i = 0; i < keys.size(); i++) {
connection.set(keys.get(i).getBytes(), values.get(i).getBytes());
}
return null; // 回调中返回 null
});
// results 包含每个命令的执行结果
}
}
使用场景:
注意事项:
Redis 事务通过 MULTI
, EXEC
, DISCARD
, WATCH
等命令实现。它允许将多个命令放入一个队列,然后通过 EXEC
命令原子性地顺序执行这些命令。
Spring Boot 实现示例:
Spring Data Redis 提供了 SessionCallback
接口来支持在同一个连接中执行多个操作,这对于事务至关重要。
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class RedisTransactionService {
private final RedisTemplate<String, Object> redisTemplate;
public RedisTransactionService(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
public List<Object> executeInTransaction() {
// 使用 execute 方法并传递 SessionCallback 来执行事务
SessionCallback<List<Object>> sessionCallback = new SessionCallback<>() {
@Override
public List<Object> execute(org.springframework.data.redis.core.RedisOperations operations) {
operations.multi(); // 开启事务
operations.opsForValue().set("key1", "value1");
operations.opsForValue().increment("counter");
operations.opsForSet().add("setKey", "member1");
return operations.exec(); // 执行事务并返回结果
}
};
return redisTemplate.execute(sessionCallback);
}
}
使用场景:
WATCH
实现乐观锁:监控特定键,如秒杀场景中监控库存键,防止超卖。// 结合 WATCH 的乐观锁示例
public boolean watchAndExecute(String key, String expectedValue, String newValue) {
return redisTemplate.execute(new SessionCallback<Boolean>() {
@Override
public Boolean execute(RedisOperations operations) {
operations.watch(key); // 监视 key
String currentValue = (String) operations.opsForValue().get(key);
if (expectedValue.equals(currentValue)) {
operations.multi();
operations.opsForValue().set(key, newValue);
List<Object> execResult = operations.exec(); // 如果 execResult 为空,表示事务执行失败(键被修改)
return execResult != null && !execResult.isEmpty();
}
operations.unwatch();
return false;
}
});
}
注意事项:
Redis 事务不支持回滚 (Rollback)。如果在执行过程中某个命令失败,已执行的命令不会回滚,后续命令仍会继续执行。
错误类型:
EXEC
前,Redis 可能会检查出错误并放弃整个事务。EXEC
后执行中发生的错误,Redis 会记录错误信息但不会中断事务执行。Redis 支持执行 Lua 脚本。脚本中的所有命令会作为一个整体原子性地执行,期间不会被其他命令打断,是原子性最强的方案,同时还能减少网络往返。
Spring Boot 实现示例:
RedisTemplate
提供了 execute
方法用于执行 Lua 脚本。
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import java.util.Arrays;
@Service
public class RedisLuaService {
private final RedisTemplate<String, Object> redisTemplate;
public RedisLuaService(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
public String useLuaScript() {
// 定义 Lua 脚本字符串
String luaScript = """
local key1 = KEYS[1]
local value1 = ARGV[1]
redis.call('set', key1, value1)
local value = redis.call('get', key1)
redis.call('incr', 'counter')
return value
""";
DefaultRedisScript<String> script = new DefaultRedisScript<>();
script.setScriptText(luaScript);
script.setResultType(String.class); // 设置返回值类型
// 执行脚本,传入 keys 和 args 数组
String result = redisTemplate.execute(script, Arrays.asList("myKey"), "myValue");
return result; // 返回脚本执行结果
}
}
使用场景:
释放分布式锁:确保判断锁标识和删除锁是一个原子操作。
-- KEYS[1] 是锁的key,ARGV[1]是当前持有者的标识
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
秒杀/抢购:判断库存和扣减库存需要原子性。
复杂业务逻辑:需要多个命令的中间结果进行逻辑判断。
注意事项:
EVALSHA
通过脚本摘要来执行,减少带宽。DefaultRedisScript
对象通常会被配置为单例,Spring 会智能地处理 EVAL
和 EVALSHA
。在 Redis Cluster 模式下,使用事务 (Transaction) 或 Lua 脚本时,所有涉及的键必须在同一个哈希槽 (hash slot) 上,否则会报错。 可以通过 hash tag 确保不同的键分配到同一个槽。