Redis的事务和锁以及在SpringBoot中的使用

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("其他用户正在执行,请稍后.....");
		}
	}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值