7.Redis6的事务操作

文章详细介绍了Redis的事务特性,包括其作为单独的隔离操作,Multi、Exec、Discard命令的使用,以及错误处理方式。在事务冲突方面,提出了悲观锁和乐观锁的概念,并以秒杀场景为例说明了在并发情况下可能遇到的问题。Redis通过Watch命令提供了一种乐观锁的解决方案,监控key的变化以防止事务冲突。最后,文章给出了一个简单的秒杀系统示例,展示了在没有并发控制下的问题。
摘要由CSDN通过智能技术生成

1. Redis的事务定义

Redis事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。

Redis事务的主要作用就是串联多个命令防止别的命令插队。

2. Multi、Exec、discard

从输入Multi 命令开始(相当于开启事务),输入的命令都会依次进入命令队列中,但不会执行,直到输入Exec后,Redis会将之前的命令队列中的命令依次执行。组队的过程中可以通过discard来放弃组队。

在这里插入图片描述

案例如下:

在这里插入图片描述

3. 事务的错误处理

组队过程中某个命令出现了报告错误,执行时整个队列的命令都会被取消,即都不执行:

在这里插入图片描述

如果执行阶段某个命令报出了错误,则只有报错的命令不会被执行,而其他正确的命令都会执行(不会回滚):

在这里插入图片描述

4. 事务冲突问题及解决方法

4.1 事务冲突案例

假如一个账户上只有 10000 块钱,现在有三个人来操作这个账户,他们查询账户的时候显示的都是 10000(从数据库查询),一个人想买一个 8000 的东西,另一个人想买一个 5000 的东西,一个想买一个 1000 的东西。如果只按照他们刚查询时的余额来判断,那么最终结果账户里剩余 -4000,这肯定是不对的:

在这里插入图片描述

对于上述问题,可以通过悲观锁和乐观锁来解决。

4.2 悲观锁

悲观锁 (Pessimistic Lock),顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block(阻塞) 直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。悲观锁的缺点是效率低,不能多人同时操作。

在这里插入图片描述

上图中:A拿到数据就给数据上锁(橙色部分),B就拿不到数据了(绿色部分),直到A用完后释放锁,B才能拿到数据并且同时给数据上锁。

4.3 乐观锁

乐观锁 (Optimistic Lock),顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候(增删改)会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量。Redis 就是利用这种 check-and-set 机制实现事务的。

在这里插入图片描述

上图中:A和B同时拿到了数据,A是橙色,B是绿色,只不过拿到的是原始数据v1.0版本,A先操作了数据,操作后数据变成了2000,版本号变成了v1.1版本。此时B再想操作数据,操作前会检查B手里的数据v1.0是不是现在的数据v1.1,如果版本号不一致,B就不能再进行操作了。

4.4 watch key [key…]

在执行 multi 之前,先执行 watch key1 [key2],可以监视一个(或多个)key ,如果在事务执行之前这个(或这些)key 被其他命令所改动,那么事务将被打断。使用了 watch key,就表示给这个 key 加上了乐观锁,那么在一个事务的执行中,每次操作这个 key 都会检查 “版本号”,如果事务没有执行前该 key 对应的值被改动,那么次事务就失败。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

4.5 unwatch

取消 WATCH 命令对所有 key 的监视。

如果在执行 WATCH 命令之后,EXEC 命令或 DISCARD 命令先被执行了的话,那么就不需要再执行UNWATCH 了。

5. Redis 事务三特性

1、单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。

2、没有隔离级别的概念:队列中的命令没有提交之前都不会实际被执行,因为事务提交前任何指令都不会被实际执行

3、不保证原子性:事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚

6. Redis 事务秒杀案例

代码在老师提供的资料 Seckkill 项目里面。

6.1 解决计数器和人员记录的事务操作

假如有一批商品要拿出来做秒杀活动,在秒杀的过程中实际上就是两个操作,一个是商品数减一,另一个就是秒杀成功的用户加入到数据库:

在这里插入图片描述

6.2 秒杀案例——无并发

代码模板在老师提供的资料里面。首先找到入口程序 SecKillServlet:

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

   //生成userID和商品ID
   String userid = new Random().nextInt(50000) +"" ;
   String prodid =request.getParameter("prodid");
   //传入userID和商品ID,调用秒杀方法
   boolean isSuccess=SecKill_redis.doSecKill1(userid,prodid);
   response.getWriter().print(isSuccess);
}

初版秒杀方法 在 SecKill_redis 类里面如下:

//秒杀过程(初版)
public static boolean doSecKill1(String uid, String prodid) throws IOException {//传入一个userID和一个商品ID
   //1 uid和prodid非空判断
   if(uid == null || prodid == null) {
      return false;
   }

   //2 连接redis
   Jedis jedis = new Jedis("192.168.88.130",6379);

   //3 拼接key
   // 3.1 库存key
   String kcKey = "sk:"+prodid+":qt";
   // 3.2 秒杀成功用户key
   String userKey = "sk:"+prodid+":user";

   //4 获取库存,如果库存null,表示秒杀还没有开始
   String kc = jedis.get(kcKey);
   if(kc == null) {
      System.out.println("秒杀还没有开始,请等待");
      jedis.close();
      return false;
   }

   // 5 判断用户是否重复秒杀操作
   if(jedis.sismember(userKey, uid)) {
      System.out.println("已经秒杀成功了,不能重复秒杀");
      jedis.close();
      return false;
   }

   //6 判断如果商品数量,库存数量小于1,秒杀结束
   if(Integer.parseInt(kc)<=0) {
      System.out.println("秒杀已经结束了");
      jedis.close();
      return false;
   }

   //7 秒杀过程
   //7.1 库存-1
   jedis.decr(kcKey);
   //7.2 把秒杀成功用户添加清单里面
   jedis.sadd(userKey, uid);

   System.out.println("秒杀成功了..");
   jedis.close();
   return true;
}

配置好Tomcat后启动:

在这里插入图片描述

页面如下:

在这里插入图片描述

点击“秒杀点我”:

在这里插入图片描述

首先手动在 redis 中添加商品库存:

在这里插入图片描述

然后再次点击“秒杀点我”按钮,控制台输出:

在这里插入图片描述

在这里插入图片描述

连续点击十次后:

在这里插入图片描述

注意,此时只是单机在操作秒杀,但是实际中秒杀会有很多人同时进行,因此会产生并发的问题,但是在并发场景中,上面的代码是有问题的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值