带你详细了解Redis事务锁机制-加实列演示-上

Redis_事务_锁机制_秒杀

Redis 的事务是什么?

1、Redis 事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行

2、事务在执行的过程中,不会被其他客户端发送来的命令请求所打断

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

Redis 事务三特性

一单独的隔离操作

1、事务中的所有命令都会序列化、按顺序地执行

2、事务在执行的过程中,不会被其他客户端发送来的命令请求所打断

二没有隔离级别的概念

队列中的命令(指令), 在没有提交前都不会实际被执行

三不保证原子性

事务执行过程中, 如果有指令执行失败,其它的指令仍然会被执行, 没有回滚

事务相关指令Multi、Exec、discard

示意图

Redis 事务指令示意图

带你详细了解Redis事务锁机制-加实列演示-上_第1张图片

解读上图
  1. 从输入Multi 命令开始,输入的命令都会依次进入命令队列中,但不会执行(类似Mysql的start transaction 开启事务)
  2. 输入Exec 后,Redis 会将之前的命令队列中的命令依次执行(类似Mysql 的commit 提交事务)
  3. 组队的过程中可以通过discard 来放弃组队(类似Mysql 的rollback 回顾事务)
  4. 说明: Redis 事务和Mysql 事务本质是完全不同的, 用Mysql 的做类似说明, 是为了好理解

快速入门

1、需求: 请依次向Redis 中, 添加三组数据, k1-v1 k2-v2 k3-v3, 要求使用Redis 的事务完成

带你详细了解Redis事务锁机制-加实列演示-上_第2张图片

注意事项和细节

1、组队的过程中, 可以通过discard 来放弃组队

带你详细了解Redis事务锁机制-加实列演示-上_第3张图片

2、如果在组队阶段报错, 会导致exec 失败, 那么事务的所有指令都不会被执行
带你详细了解Redis事务锁机制-加实列演示-上_第4张图片

带你详细了解Redis事务锁机制-加实列演示-上_第5张图片

3、如果组队成功, 但是指令有不能正常执行的, 那么exec 提交, 会出现有成功有失败情况,也就是事务得到部分执行, 这种情况下, Redis 事务不具备原子性.

带你详细了解Redis事务锁机制-加实列演示-上_第6张图片

带你详细了解Redis事务锁机制-加实列演示-上_第7张图片

事务冲突及解决方案

先看一个问题

经典的抢票问题

  1. 一个请求想购买6

  2. 一个请求想购买5

  3. 一个请求想购买1
    带你详细了解Redis事务锁机制-加实列演示-上_第8张图片

解读上图

  1. 如果没有控制, 会造成超卖现象
  2. 如果3 个指令, 都得到执行, 最后剩余的票数是-2

悲观锁

工作示意图

带你详细了解Redis事务锁机制-加实列演示-上_第9张图片

解读上图

  1. 悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁
  2. 这样别人/其它请求想拿这个数据就会block 直到它拿到锁。
  3. 悲观锁是锁设计理念, 传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁.

乐观锁

工作示意图

带你详细了解Redis事务锁机制-加实列演示-上_第10张图片

解读上图

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

watch & unwatch

watch

1、基本语法: watch key [key …]

2、在执行multi 之前,先执行watch key1 [key2],可以监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断.

3、这里可以结合乐观锁机制进行理解.

实验:

unwatch

1、基本语法unwatch

2、取消watch 命令对所有key 的监视。

3、如果在执行watch 命令后,exec 命令或discard 命令先被执行了的话,那么就不需要再执行unwatch 了

火车票-抢票

需求分析/图解

创建一个web项目

带你详细了解Redis事务锁机制-加实列演示-上_第11张图片

思路分析

1、一个user 只能购买一张票, 即不能复购

2、不能出现超购,也是就多卖了.

3、不能出现火车票遗留问题/库存遗留, 即火车票不能留下

带你详细了解Redis事务锁机制-加实列演示-上_第12张图片

版本1:完成基本购票流程, 暂不考虑事务和并发问题

1、创建Java Web 项目, 参照以前讲过搭建Java Web 项目流程即可

2、引入相关的jar 包和jquery

创建index

sec_kill_ticket\web\index.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
         pageEncoding="UTF-8" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  <title>Insert title here</title>
  <base href="<%=request.getContextPath() + "/"%>">
</head>
<body>
<h1>北京-成都 火车票 ! 秒杀!
</h1>


<form id="secKillform" action="secKillServlet" enctype="application/x-www-form-urlencoded">
  <input type="hidden" id="ticketNo" name="ticketNo" value="bj_cd">
  <input type="button" id="seckillBtn" name="seckillBtn" value="秒杀火车票【北京-成都】"/>
</form>

</body>
<script type="text/javascript" src="script/jquery/jquery-3.1.0.js"></script>
<script type="text/javascript">
  $(function () {
    $("#seckillBtn").click(function () {
      var url = $("#secKillform").attr("action");
      console.log("url->" , url)// secKillServlet,完整的url http://localhost:8080/seckill/secKillServlet
      console.log("serialize->", $("#secKillform").serialize())
      //
      $.post(url, $("#secKillform").serialize(), function (data) {
        if (data == "false") {
          alert("火车票 抢光了:)");
          $("#seckillBtn").attr("disabled", true);
        }
      });
    })
  })
</script>
</html>

创建SecKillRedis

src\com\seckill\redis\SecKillRedis.java

public class SecKillRedis {
    /**
     * 测试一下是否连通了Redis
     *
     * @param args
     */
    public static void main(String[] args) {
        Jedis jedis = new Jedis("192.168.198.130", 6379);
        System.out.println(jedis.ping());
        jedis.close();
    }
    //秒杀过程
    /**
     * @param uid 用户id
     * @param ticketNo 票编号, 比如北京-成都的ticketNo "bj_cd"
     * @return
     */
    public static boolean doSecKill(String uid, String ticketNo) {
        //1 uid 和ticketNo 非空判断
        if (uid == null || ticketNo == null) {
            return false;
        }
        //2.连接到redis
        //解读
        //1) 每一个来秒杀的用户, 都会连接一把Reids
        Jedis jedis = new Jedis("192.168.198.130", 6379);
        //3 拼接key
        // 3.1 库存key
        String stockKey = "sk:" + ticketNo + ":ticket";
        // 3.2 秒杀成功用户key=> 对应的值是set , 可以存放有多个秒杀成功用户的id
        String userKey = "sk:" + ticketNo + ":user";
        //4 获取库存,如果库存null,秒杀还没有开始
        String stock = jedis.get(stockKey);
        if (stock == null) {
            System.out.println("秒杀还没有开始,请等待..");
            jedis.close();
            return false;
        }
        // 5 判断用户是否重复秒杀操作
        if (jedis.sismember(userKey, uid)) {
            System.out.println(uid + " 不能重复秒杀...");
            jedis.close();
            return false;
        }
        //6 判断如果火车票数量,剩余数量小于1,秒杀结束
        if (Integer.parseInt(stock) <= 0) {
            System.out.println("票已经卖光, 秒杀已经结束了");
            jedis.close();
            return false;
        }
        //7.1 火车票数量- 1
        jedis.decr(stockKey);
        //7.2 把秒杀成功用户添加清单里面
        jedis.sadd(userKey, uid);
        System.out.println("秒杀成功了..");
        jedis.close();
        return true;
    }
}

创建SecKillServlet

src\com\seckill\web\SecKillServlet.java

public class SecKillServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        //请求时, 模拟生成一个userId
        String userId = new Random().nextInt(10000) + "";
        //获取用户要购买的票的编号
        String ticketNo = request.getParameter("ticketNo");
        //调用秒杀
        boolean isOK = SecKillRedis.doSecKill(userId, ticketNo);
        //返回结果
        response.getWriter().print(isOK);
    }
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws
            ServletException, IOException {
        doPost(request, response);
    }
}

向Redis 中, 增加测试数据

带你详细了解Redis事务锁机制-加实列演示-上_第13张图片

测试效果

带你详细了解Redis事务锁机制-加实列演示-上_第14张图片

版本2:抢票并发模拟, 出现超卖问题

安装工具ab 模拟测试

  1. 说明: 工具ab 可以模拟并发发出Http 请求, 老韩说明(模拟并发http 请求工具还有jemeter, postman,我们都使用一下, 开阔眼界, 这里老师使用ab 工具)
  2. 安装指令: yum install httpd-tools (提示: 保证当前linux 是可以联网的)
  3. 如果你不能联网, 可以使用rpm 安装, 这里使用yum 方式安装
  4. 另外, 使用rpm 方式安装我也给小伙伴说明一下, 如下:-先挂载centos 安装文件ios, 这个文件

带你详细了解Redis事务锁机制-加实列演示-上_第15张图片

带你详细了解Redis事务锁机制-加实列演示-上_第16张图片

–进入cd /run/media/root/CentOS 7 x86_64/Packages

–顺序安装

  1. apr-1.4.8-3.el7.x86_64.rpm
  2. apr-util-1.5.2-6.el7.x86_64.rpm
  3. httpd-tools-2.4.6-67.el7.centos.x86_64.rpm
    带你详细了解Redis事务锁机制-加实列演示-上_第17张图片

–测试是否安装成功

带你详细了解Redis事务锁机制-加实列演示-上_第18张图片

在ab 指令执行的当前路径下创建文件postfile

vi postfile

在这里插入图片描述

带你详细了解Redis事务锁机制-加实列演示-上_第19张图片

执行指令

注意保证linux 可以访问到Tomcat 所在的服务器.

–先查看Tomcat 所在Windows 的网络配置情况
带你详细了解Redis事务锁机制-加实列演示-上_第20张图片

–确认Linux 可以ping 通Windows

带你详细了解Redis事务锁机制-加实列演示-上_第21张图片

如果Ping 不通, 确认一下Windows 防火墙是否关闭
带你详细了解Redis事务锁机制-加实列演示-上_第22张图片

--指令, 测试前把Redis 的数据先重置一下
ab -n 1000 -c 100 -p ~/postfile -T application/x-www-form-urlencoded http://192.168.198.1:8080/seckill/secKillServlet

解读指令

(1) ab 是并发工具程序

(2) -n 1000 表示一共发出1000 次http 请求

(3) -c 100 表示并发时100 次, 你可以理解1000 次请求, 会在10 次发送完毕

(4) -p ~/postfile 表示发送请求时, 携带的参数从当前目录的postfile 文件读取(这个你事先要准备好)

(5) -T application/x-www-form-urlencoded 就是发送数据的编码是基于表单的url 编码

(6) ~的含义: https://blog.csdn.net/m0_67401134/article/details/123973115

带你详细了解Redis事务锁机制-加实列演示-上_第23张图片

(7)http://192.168.198.1:8080/seckill/secKillServlet 就是请求的url, 注意这里的IP:port/uri 必须写正确.

带你详细了解Redis事务锁机制-加实列演示-上_第24张图片
带你详细了解Redis事务锁机制-加实列演示-上_第25张图片

查看执行结果

带你详细了解Redis事务锁机制-加实列演示-上_第26张图片
带你详细了解Redis事务锁机制-加实列演示-上_第27张图片

注意我们这里先讲连接池然后在讲解决方法

你可能感兴趣的:(中间件,redis,java,数据库,缓存,服务器)