事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。 事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。 事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行。
总结说:redis事务就是一次性、顺序性、排他性的执行一个队列中的一系列命令。
MULTI :开启事务,redis会将后续的命令逐个放入队列中,然后使用EXEC命令来原子化执行这个命令系列。 EXEC:执行事务中的所有操作命令。 DISCARD:取消事务,放弃执行事务块中的所有命令。 WATCH:监视一个或多个key,如果事务在执行前,这个key(或多个key)被其他命令修改,则事务被中断,不会执行事务中的任何命令。 UNWATCH:取消WATCH对所有key的监视。
redisTemplate.setEnableTransactionSupport(true);
不开启直接使用redis事务会报错提示 ERR EXEC without MULTI
org.springframework.data.redis.RedisSystemException: Error in execution; nested exception is io.lettuce.core.RedisCommandExecutionException: ERR EXEC without MULTI
java代码示例
java @Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* 在一个"事务"中执行多个操作。
*/
public void executeTransaction() {
// 开始事务
redisTemplate.multi();
try {
// 添加事务中的命令
redisTemplate.opsForValue().set("key1", "value1");
redisTemplate.opsForValue().set("key2", "value2");
// 执行事务(提交)
redisTemplate.exec();
} catch (Exception e) {
// 如果在事务执行过程中发生错误,可以回滚事务
redisTemplate.discard();
System.out.println("Transaction discarded due to error: " + e.getMessage());
}
}
使用 watch可以监视一个key有没有被改变,从而实现乐观锁的效果
java @Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* 使用WATCH、MULTI、EXEC实现乐观锁更新。
* 尝试更新key对应的值,如果期间key被其他客户端改变,则更新失败。
*
* @param key 锁定的键
* @param oldValue 期望的旧值
* @param newValue 新值
* @return true if the update was successful, false otherwise
*/
public boolean updateWithOptimisticLock(String key, String oldValue, String newValue) {
Boolean result = redisTemplate.execute((RedisCallback<Boolean>) connection -> {
connection.watch(key.getBytes()); // 监视key
byte[] currentVal = connection.get(key.getBytes());
if (currentVal != null && new String(currentVal).equals(oldValue)) { // 检查值是否被改变
connection.multi(); // 开始事务
connection.set(key.getBytes(), newValue.getBytes()); // 更新操作
return connection.exec().size() > 0; // 执行事务,如果有结果表示成功
} else {
connection.unwatch(); // 值已被改变,取消监视
return false;
}
});
return result != null && result;
}
java
private final RedisTemplate<String, Integer> redisTemplate;
public AtomicOperationExample(RedisTemplate<String, Integer> redisTemplate) {
this.redisTemplate = redisTemplate;
// 设置键和值的序列化方式
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new GenericToStringSerializer<>(Integer.class));
}
/**
* 原子性地对键对应的值加1。
* @param key 键
* @return 加1后的值
*/
public Integer atomicIncrement(String key) {
// Lua脚本,实现键值加1操作
String script = "if redis.call('exists', KEYS[1]) == 1 then " +
"return redis.call('incr', KEYS[1]) " +
"else " +
"redis.call('set', KEYS[1], ARGV[1]) " +
"return ARGV[1] " +
"end";
// 创建DefaultRedisScript对象
DefaultRedisScript<Integer> redisScript = new DefaultRedisScript<>(script, Integer.class);
// 执行Lua脚本,传入key和初始值1
return redisTemplate.execute(redisScript, Collections.singletonList(key), 1);
}
功能: 递增指定键的值。如果键不存在,它的值会被初始化为0,然后再执行递增操作。此命令仅适用于可以表示为整数的字符串值。 返回值: 递增后的值。
功能: 递减指定键的值。类似于INCR,如果键不存在,它的值会被初始化为0,然后再执行递减操作。同样,此命令也适用于可以表示为整数的字符串值。 返回值: 递减后的值。
功能: 将键的值设为新值,并返回键的旧值。这个命令可以在原子性地更新键的同时获取其旧值,常用于实现某些形式的计数器或状态更新,同时还能获取更新前的状态。 返回值: 键的旧值。
功能: 设置键的值。这是一个非常基础且多功能的命令,允许你设置键值对。从Redis 2.6.12版本开始,它还可以通过额外的参数来控制行为,比如设置键的过期时间、条件性地设置键值等。
EX seconds:设置键的过期时间为seconds秒。 PX milliseconds:设置键的过期时间为milliseconds毫秒。 NX:只有当键不存在时才设置值(相当于"set if not exists")。 XX:只有当键已经存在时才设置值(相当于"set if exists")。
返回值: 在普通模式下总是返回OK。如果使用了NX或XX选项,并且条件未满足,则返回nil。 这些命令在处理字符串数据时提供了基本的原子性操作,非常适合构建高性能的计数器、锁、简单的状态机等场景。
本文作者:Weee
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!