十. Redis 事务和 “锁机制”——> 并发秒杀处理的详细阐明 ...

打印 上一主题 下一主题

主题 900|帖子 900|积分 2700

十. Redis 事务和 “锁机制”——> 并发秒杀处理的详细阐明

@
目录

1.  Redis 的事务是什么?


  • Redis 事务时一个单独的隔离操作 :事务中的所有命令都会序列化,按顺序地执行。
  • 事务在执行的过程中,不会被其他客户端发送来的命令请求所打断,中停。
  • Redis 事务的主要作用就是串联 多个命令防止别的命令插队
2. Redis 事务三特性


  • 单独的隔离操作:

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

  • 没有隔离级别的概念:
队列中的命令(指令),在没有提交前都不会实际被执行。

  • 不保证原子性:
事务执行过程中,如果有指令执行失败,其他的指令仍然会被执行,没有回滚
MySQL中的事务是支持回滚的,而 Redis 中的事务是不支持回滚的。
3. Redis 关于事务相关指令 Multi、Exec、discard和 “watch &    unwatch”







Redis 事务指令示意图:

上图解读:

  • 从输入 multi 命令开始,输入的命令都会依次进入命令队列 中,但不会执行雷同(MySQL的 start transaction 开始事务)。
  • 输入 Exec 命令后,Redis 会将之前的命令队列中的命令依次执行(雷同于 MySQL的 commit 提交事务)。
  • 组队的过程中可以通过 discard 来放弃组队(雷同 MySQL的 rollback 回滚事务)
  • 阐明:Redis 事务和 MySQL 事务本质是完全不同的。 ——> MySQL中的事务是支持回滚的,而 Redis 中的事务是不支持回滚的。
3.1 快速入门(演示 Redis 事务控制)

  1. 127.0.0.1:6379> multi
复制代码
  1. 127.0.0.1:6379(TX)> set k1 v1
  2. QUEUED
  3. 127.0.0.1:6379(TX)> set k2 v2
  4. QUEUED
  5. 127.0.0.1:6379(TX)> set k3 v3
  6. QUEUED
  7. 127.0.0.1:6379(TX)>
复制代码
  1. 127.0.0.1:6379(TX)> exec
复制代码

3.2 注意事项和细节


  • 组队的过程中,  可以通过 discard  来放弃组队。注意是在 [TX] 队列当中,还没有执行 exce 命令之前,才有用。
  1. 127.0.0.1:6379(TX)> discard
复制代码


  • 如果在组队阶段报错(这里的报错信息指的是,命令输入错误,语法错误,编译上的错误)   会导致 exec 失败   那么事务的所有指令都不会被执行




  • 如果组队成功(multii ), 但是指令有不能正常执行的,那么 exec 提交,会出现有成功有失败环境,也就是事务得到部分执行, 这种环境下, Redis 事务不具备原子性。

  1. 127.0.0.1:6379> multi
  2. OK
  3. 127.0.0.1:6379(TX)> set k1 "v1"
  4. QUEUED
  5. 127.0.0.1:6379(TX)> incr k1
  6. QUEUED
  7. 127.0.0.1:6379(TX)> set k2 "v2"
  8. QUEUED
  9. 127.0.0.1:6379(TX)> exec
  10. 1) OK
  11. 2) (error) ERR value is not an integer or out of range
  12. 3) OK
  13. 127.0.0.1:6379>
复制代码

4. Redis 事务冲突及解决方案(悲观锁,乐观锁) watch &    unwatch

我们先来看一个经典的抢票 问题。

  • 一个请求(用户)想购买 6 张票
  • 一个请求(用户)想购买 5 张票
  • 一个请求(用户)想购买 1 张票

上述解图:
一共只有10张票,但是并发开始,三个用户(三个请求),买 6 张,买 5 张,买 1 张票的。同时进入购票系统,并发同时候进入判定,都显示还剩10张票(还没有减),最后执行减票,超卖了 2张票。
4.1 “悲观锁” 解决


上图解图:

  • 悲观锁(Pessimistic Lock),  顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁。
  • 这样别人/其他请求想要拿到这个数据都会被(block 锁上,由于被锁了就无法修改/拿到数据了),只有直到在他前面的人拿到数据/修改数据后,将锁释放了,它才气将数据拿到。
  • 悲观锁是锁设计理念 ,传统的关系型数据库里面就用到了很多这种锁机制,比如行锁,表锁,读锁,写锁等等,都是在做操作之前先上锁(防止被其他的人/请求操作,修改了数据,导致数据不同等。)
4.2 “乐观锁” 解决


上图解读:

  • 乐观锁(Optimistic Lock),  顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁。
  • 但是在更新的时候会判定一下,在此期间别人/请求是否有去更新了这个数据,可以使用版本号等机制。版本号机制:就是当这个数据被修改了,那么就会产生一个版本信息,如果这个版本信息,与你一开始对应,并应该获取的版本信息不同等,那么就修改失败/无法修改数据(或者说获取的版本信息不同等,拿不到该数据信息)
  • 乐观锁适用于多读的应用范例,这样可以提高吞吐量。 Redis 就是利用这种 check-and-set 机制实现事务的。
  • 乐观锁是锁设计理念。
4.3 watch &    unwatch



  • 基本语法: watch key [key ...]
  • 在执行 multi 之前,先执行 watch key1 [key2],可以监视一个(或多个) key,如果在事务执行之前这个(或这些) key 被其他命令所改动过,那么事务将被打断,停止执行
  • 这里就可以结合乐观锁机制举行理解。
演示实操:



  1. # A 连接
  2. 127.0.0.1:6379> watch k1
  3. OK
  4. 127.0.0.1:6379> multi
  5. OK
  6. 127.0.0.1:6379(TX)> incrby k1 1
  7. QUEUED
  8. 127.0.0.1:6379(TX)> exec
  9. 1) (integer) 100
  10. 127.0.0.1:6379> get k1
  11. "100"
复制代码
  1. # B 连接
  2. 127.0.0.1:6379> watch k1
  3. OK
  4. 127.0.0.1:6379> multi
  5. OK
  6. 127.0.0.1:6379(TX)> incrby k1 100
  7. QUEUED
  8. 127.0.0.1:6379(TX)> exec
  9. (nil)
  10. 127.0.0.1:6379> get k1
  11. "100"
  12. 127.0.0.1:6379>
复制代码
unwatch:


  • unwatch : 取消 watch 命令对所有 key 的监视。
  • 如果在执行 watch 命令后, exec 命令和 discard 命令先被执行了的话,那么久不必要再执行 unwatch 了。
5. 案例演示:火车票-抢票(解决超卖,库存遗留)问题

5.1 案例思绪分析:

这里我们使用 WEB 项目来演示


思绪分析:

  • 一个 user 只能购买一张票,即不能复购。
  • 不能出现超购,也就是多卖的环境。
  • 不能出现火车票遗留问题/库存遗留,即火车票不能留下

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


  • 创建 Java Web 项目,  参照从前讲过搭建 Java Web 项目流程即可
  • 引入相关的 jar 包  和  jquery
  • 创建 D:sec_kill_ticket\web\index.jsp













index.jsp 代码编写
  1. <%--
  2.   Created by IntelliJ IDEA.
  3.   User: 韩顺平
  4.   Version: 1.0
  5. --%>
  6. <%@ page language="java" contentType="text/html; charset=UTF-8"
  7.          pageEncoding="UTF-8" %>
  8. <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
  9. <html>
  10. <head>
  11.   <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  12.   <title>Insert title here</title>
  13.   <base href="<%=request.getContextPath() + "/"%>">
  14. </head>
  15. <body>
  16. <h1>北京-成都 火车票 ! 秒杀!
  17. </h1>
  18. <form id="secKillform" action="secKillServlet" enctype="application/x-www-form-urlencoded">
  19.   <input type="hidden" id="ticketNo" name="ticketNo" value="bj_cd">
  20.   <input type="button" id="seckillBtn" name="seckillBtn" value="秒杀火车票【北京-成都】"/>
  21. </form>
  22. </body>
  23. </html>
复制代码
  1. package com.rainbowsea.seckill.redis;
  2. import org.junit.Test;
  3. import redis.clients.jedis.Jedis;
  4. /**
  5. * 秒杀类
  6. */
  7. public class SecKillRedis {
  8.     /**
  9.      * 编写一个测试方法-看看是否能够连通指定的 Redis
  10.      */
  11.     @Test
  12.     public void testRedis() {
  13.         Jedis jedis = new Jedis("192.168.76.146", 6379);
  14.         jedis.auth("rainbowsea");  // 设置了密码,需要进行一个验证
  15.         System.out.println(jedis.ping());
  16.         jedis.close(); // 关闭连接
  17.     }
  18. }
复制代码
<blockquote>
关于更多对应:Java程序连接 Redis 的内容,大家可以移步至:
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

傲渊山岳

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表