事务的定义
Redis事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断
Redis事务的主要作用就是串联多个命令防止别的命令插队
Multi、Exec、discard
从输入Multi命令开始,输入的命令都会依次进入命令队列中(Multi),但不会执行,直到输入Exec后,Redis会将之前的命令队列中的命令依次执行。exec后事务执行完成。组队的过程中可以通过discard来放弃组队

事务的错误处理
组队中某个命令出现了报告错误,执行时整个的所有队列都会被取消
如果执行阶段某个命令报出了错误,则只有报错的命令不会被执行,而其他的命令都会执行,不会回滚
组队过程中错误
执行过程中错误

事务的冲突问题
悲观锁(Pessimistic Lock)
每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁
乐观锁(Optimistic Lock)
每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量。Redis就是利用这种check-and-set机制实现事务
乐观锁演示
//监视一个或者多个keyWATCH key [key...]//取消 WATCH 命令对所有 key 的监视。unwatch//如果在执行 WATCH 命令之后,EXEC 命令或DISCARD 命令先被执行了的话,那么就不需要再执行UNWATCH 了。
在执行multi之前,先执行watch key1 [key2],监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断
先执行
后执行
所监视的key被改变,版本不一致,事务执行打断
Redis事务三特性
单独的隔离操作
事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断
没有隔离级别的概念
队列中的命令没有提交之前都不会实际被执行,因为事务提交前任何指令都不会被实际执行
不保证原子性
事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚
案例——秒杀
使用事务和监视解决超卖问题
@RestControllerpublic class TestSecKill {//重写了bean就要使用相对应的类型 不然 就会用成原生的bean@Resourceprivate RedisTemplate<String,Object> redisTemplate;@GetMapping("/seckill")public String secKill() {String uid = UUID.randomUUID().toString().substring(1, 10);Boolean isSucc = doSecKill(uid);if (isSucc) {System.out.println("秒杀成功");return "秒杀成功";} else {System.out.println("==秒杀失败==");return "==秒杀失败==";}}private Boolean doSecKill(String uid) {//判空处理if (uid == null) {return false;}String skUser = "sk:user:"+uid;String skQt = "sk:1001:qt";//开启监视redisTemplate.watch(skQt);//库存判断//1 库存为空(未开始秒杀) 2 库存为0(秒杀结束)String redisQt = String.valueOf(redisTemplate.opsForValue().get(skQt));if (redisQt == null || Integer.parseInt(redisQt) <= 0) {System.out.println("========秒杀结束======");return false;}//是否重复抢购Set<Object> members = redisTemplate.opsForSet().members(skUser);if (members.size() != 0) {return false;}//记录库存、秒杀成功人员//开启事务防止多线程情况下 其它请求影响当前redis命令redisTemplate.setEnableTransactionSupport(true);try {redisTemplate.multi();redisTemplate.opsForValue().decrement(skQt);redisTemplate.opsForSet().add(skUser, uid);List<Object> execList = redisTemplate.exec();if (execList.size() == 0) {return false;}} catch (Exception e) {//开启回滚redisTemplate.discard();}return true;}}
#linux下用 ab 对单接口压测,模拟多并发 (简单的DDOS攻击)ab -i -n 2000 -c 200 http://192.168.31.228:9090/seckill
库存遗留问题:因为乐观锁操作数据时会修改数据的版本号,就会造成数据虽然存在但是版本号不一致不能购买。
通过lua脚本解决争抢问题,实际上是redis 利用其单线程的特性,用任务队列的方式解决多任务并发问题。一次提交给redis执行,减少反复连接redis的次数。提升性能。
