文章目录
- 1、事务
- 2、监视锁
- 3、分布式锁
1、事务
Redis中事务的操作主要有三个:
# 1、开启事务
# 定事务的开启位置,此指令执行后,后续的所有指令均加入到事务中
1、multi
# 2、执行事务
# 设定事务的结束位置,同时执行事务。与multi成对出现,成对使用
# 加入事务的命令暂时进入到任务队列中,并没有立即执行,只有执行exec命令才开始执行
2、exec
# 3、取消事务
# 终止当前事务的定义,发生在multi之后,exec之前
3、discard
已经执行完毕的命令对应的数据不会自动回滚,需要程序员自己在代码中实现回滚。
接下来介绍下Redis事务在SpringBoot中的使用,这里就忽略掉Redis配置文档以及Dependency引用相关部分的内容了,如果需要可以参考博文:【Redis】 springboot集成Redis
这里先给出一个错误的使用示范
:
public void useTransaction() {
redisTemplate.setEnableTransactionSupport(true);
// 开启事务
redisTemplate.multi();
try {
redisTemplate.opsForValue().setIfAbsent("test0210", "0210");
int a=1/0;
redisTemplate.opsForValue().setIfAbsent("test0210_01", "0210_01");
redisTemplate.exec();
} catch (Exception e) {
redisTemplate.discard();
System.out.println(e.getMessage());
}
}
使用上述的代码来进行Redis事务的执行时,会报io.lettuce.core.RedisCommandExecutionException: ERR DISCARD without MULTI
的错误,具体的原因可以参考博文:SpringBoot项目中使用Redis事务出现ERR EXEC without MULTI的分析与总结
正确的使用方式
如下所示:
public void useTransaction() {
SessionCallback<Object> session=new SessionCallback<Object>() {
@Override
public List<Object> execute(RedisOperations operations) throws DataAccessException {
operations.multi();
operations.opsForValue().setIfAbsent("test0210", "0210");
int a=1/0;
operations.opsForValue().setIfAbsent("test0210_01", "0210_01");
return operations.exec();
}
};
try {
List<Object> results= (List<Object>) redisTemplate.execute(session);
System.out.println("Number of items added to set: " + results.get(0));
} catch (Exception e) {
System.out.println(e.getMessage()); // e.getMessage为/by zero
}
}
以上为正确的使用方式,如上所示,当有Exception触发时,会触发事务的回滚机制;官网说明地址:使用SessionCallback接口操作redis事务
SessionCallback与RedisCallback两者的关系:
SessionCallback可以看作是RedisCallback的封装
,但是SessionCallback更好用,可读性更高;RedisCallback更偏向于底层,操作相对复杂一些。
除了以上的方式,还有另外一种方式也可以实现Redis事务,来源于Spring官方文档-RedisTemplate对@Transactional的支持
2、监视锁
监视锁的操作命令如下:
# 对key添加监视锁,在执行exec前如果key发生了变化,中止事务执行
1、watch key1 [key2....]
# 取消对所有key的监视
2、unwatch
使用RedisTemplate客户端来实现监视锁的代码如下:
public void useWatchLock() {
String name="name",sex="sex";
List<String> keys=new ArrayList<>();
keys.add(name);
keys.add(sex);
SessionCallback<List<Object>> session=new SessionCallback<List<Object>>() {
@Override
public List<Object> execute(RedisOperations operations) throws DataAccessException {
operations.watch(keys);
operations.multi();
operations.opsForValue().set(name, "lisi");
operations.opsForValue().set(sex, "male");
return operations.exec();
}
};
List<Object> results=redisTemplate.execute(session);
results.stream().forEach(x->System.out.println(x));
}
执行上述代码两次,第一次正常执行;第二次在exec之前打断点,然后通过客户端修改key为name对应的value值,两次结果如下:
// 第一次结果:true true
// 第二次结果:
// 第二次结果为空说明,事务已经被中止执行,添加监视锁成功。
3、分布式锁
分布式锁的实现,如果要实现分布式锁,就要使用到如下命令:
# 使用setNX命令来设置一个公共锁
1、setnx lock-key value
具体的过程如下:
1、利用setNX命令的特征,有值则返回设置失败,无值则返回设置成功;
如果setNX命令返回设置成功,用户控制权,进行下一步具体的业务操作;
对于setNX命令返回设置失败,不具备控制权,排队或等待;
2、操作完毕通过del操作释放锁。
但是使用以上的实现方式存在一定的风险,由于锁操作由用户控制加锁解锁,必定会存在加锁后未解锁的风险
,可以使用一下方式来进行优化:
# 使用expire为锁key添加时间限定,到时不释放,则放弃锁
1、expire lock-key second
2、pexpire lock-key milliseconds
接下来介绍下分布式锁在SpringBoot中的使用:
public void useSetNX() {
String lock = "lock_nx";
String restoreKey="number";
Boolean isExisted=redisTemplate.hasKey(restoreKey);
if (!isExisted) {
redisTemplate.opsForValue().setIfAbsent(restoreKey, "10");
}
Integer number =Integer.valueOf((String) redisTemplate.opsForValue().get(restoreKey));
if (number <= 0) {
System.out.println("本场商品已清仓,请关注店铺公告,下次再来!!!");
return;
}
// 尝试加锁,設置超時時間
Boolean lockStatus = redisTemplate.opsForValue().setIfAbsent(lock, String.valueOf(Math.round(Math.random())), 2,
TimeUnit.SECONDS);
if (lockStatus) {
System.out.println("成功获得锁,开始购买商品!!!");
long result = redisTemplate.opsForValue().decrement("number");
System.out.println(result);
// 尝试解锁
lockStatus = redisTemplate.delete(lock);
System.out.println("解锁" + (lockStatus == true ? "成功" : "失败") + "!!!");
} else {
System.out.println("其他用户正在执行,请稍后.....");
}
}