十. 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 事务控制)
- 127.0.0.1:6379(TX)> set k1 v1
- QUEUED
- 127.0.0.1:6379(TX)> set k2 v2
- QUEUED
- 127.0.0.1:6379(TX)> set k3 v3
- QUEUED
- 127.0.0.1:6379(TX)>
复制代码
3.2 注意事项和细节
- 组队的过程中, 可以通过 discard 来放弃组队。注意是在 [TX] 队列当中,还没有执行 exce 命令之前,才有用。
- 127.0.0.1:6379(TX)> discard
复制代码
- 如果在组队阶段报错(这里的报错信息指的是,命令输入错误,语法错误,编译上的错误) 会导致 exec 失败 那么事务的所有指令都不会被执行。
- 如果组队成功(multii ), 但是指令有不能正常执行的,那么 exec 提交,会出现有成功有失败环境,也就是事务得到部分执行, 这种环境下, Redis 事务不具备原子性。
- 127.0.0.1:6379> multi
- OK
- 127.0.0.1:6379(TX)> set k1 "v1"
- QUEUED
- 127.0.0.1:6379(TX)> incr k1
- QUEUED
- 127.0.0.1:6379(TX)> set k2 "v2"
- QUEUED
- 127.0.0.1:6379(TX)> exec
- 1) OK
- 2) (error) ERR value is not an integer or out of range
- 3) OK
- 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 被其他命令所改动过,那么事务将被打断,停止执行 。
- 这里就可以结合乐观锁机制举行理解。
演示实操:
- # A 连接
- 127.0.0.1:6379> watch k1
- OK
- 127.0.0.1:6379> multi
- OK
- 127.0.0.1:6379(TX)> incrby k1 1
- QUEUED
- 127.0.0.1:6379(TX)> exec
- 1) (integer) 100
- 127.0.0.1:6379> get k1
- "100"
复制代码- # B 连接
- 127.0.0.1:6379> watch k1
- OK
- 127.0.0.1:6379> multi
- OK
- 127.0.0.1:6379(TX)> incrby k1 100
- QUEUED
- 127.0.0.1:6379(TX)> exec
- (nil)
- 127.0.0.1:6379> get k1
- "100"
- 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 代码编写- <%--
- Created by IntelliJ IDEA.
- User: 韩顺平
- Version: 1.0
- --%>
- <%@ 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>
- </html>
复制代码 - package com.rainbowsea.seckill.redis;
- import org.junit.Test;
- import redis.clients.jedis.Jedis;
- /**
- * 秒杀类
- */
- public class SecKillRedis {
- /**
- * 编写一个测试方法-看看是否能够连通指定的 Redis
- */
- @Test
- public void testRedis() {
- Jedis jedis = new Jedis("192.168.76.146", 6379);
- jedis.auth("rainbowsea"); // 设置了密码,需要进行一个验证
- System.out.println(jedis.ping());
- jedis.close(); // 关闭连接
- }
- }
复制代码 <blockquote>
关于更多对应:Java程序连接 Redis 的内容,大家可以移步至:
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |