十. Redis 事务和 “锁机制”——> 并发秒杀处理的详细阐明
十. Redis 事务和 “锁机制”——> 并发秒杀处理的详细阐明@
目录
[*]十. Redis 事务和 “锁机制”——> 并发秒杀处理的详细阐明
[*]1.Redis 的事务是什么?
[*]2. Redis 事务三特性
[*]3. Redis 关于事务相关指令 Multi、Exec、discard和 “watch & unwatch”
[*]3.1 快速入门(演示 Redis 事务控制)
[*]3.2 注意事项和细节
[*]4. Redis 事务冲突及解决方案(悲观锁,乐观锁) watch & unwatch
[*]4.1 “悲观锁” 解决
[*]4.2 “乐观锁” 解决
[*]4.3 watch & unwatch
[*]5. 案例演示:火车票-抢票(解决超卖,库存遗留)问题
[*]5.1 案例思绪分析:
[*]5.2 完成基本购票流程,暂不考虑事务和并发问题
[*]5.3 抢票并发模拟,出现超卖问题
[*]5.4 Redis 连接池技能
[*]5.5 利用 Redis 的事务机制,解决超卖问题(使用 watch,multi )
[*]5.6 抢票并发模拟,分析出现库存遗留问题
[*]5.7 运用 LUA 脚本(解决超卖,和库存遗留问题)
[*]6. 最后:
1.Redis 的事务是什么?
[*]Redis 事务时一个单独的隔离操作 :事务中的所有命令都会序列化,按顺序地执行。
[*]事务在执行的过程中,不会被其他客户端发送来的命令请求所打断,中停。
[*]Redis 事务的主要作用就是串联 多个命令防止别的命令插队 。
2. Redis 事务三特性
[*]单独的隔离操作:
[*]Redis 事务时一个单独的隔离操作 :事务中的所有命令都会序列化,按顺序地执行。
[*]事务在执行的过程中,不会被其他客户端发送来的命令请求所打断,中停。
[*]没有隔离级别的概念:
队列中的命令(指令),在没有提交前都不会实际被执行。
[*]不保证原子性:
事务执行过程中,如果有指令执行失败,其他的指令仍然会被执行,没有回滚 。
MySQL中的事务是支持回滚的,而 Redis 中的事务是不支持回滚的。
3. Redis 关于事务相关指令 Multi、Exec、discard和 “watch & unwatch”
https://img2024.cnblogs.com/blog/3084824/202502/3084824-20250205215542626-198868970.png
https://img2024.cnblogs.com/blog/3084824/202502/3084824-20250205215542686-1923425859.png
https://img2024.cnblogs.com/blog/3084824/202502/3084824-20250205215542517-2144818928.png
https://img2024.cnblogs.com/blog/3084824/202502/3084824-20250205215542892-646912047.png
https://img2024.cnblogs.com/blog/3084824/202502/3084824-20250205215542753-314798000.png
https://img2024.cnblogs.com/blog/3084824/202502/3084824-20250205215542574-602289357.png
Redis 事务指令示意图:
https://img2024.cnblogs.com/blog/3084824/202502/3084824-20250205215542644-383068154.png
上图解读:
[*]从输入 multi 命令开始,输入的命令都会依次进入命令队列 中,但不会执行雷同(MySQL的 start transaction 开始事务)。
[*]输入 Exec 命令后,Redis 会将之前的命令队列中的命令依次执行(雷同于 MySQL的 commit 提交事务)。
[*]组队的过程中可以通过 discard 来放弃组队(雷同 MySQL的 rollback 回滚事务)
[*]阐明:Redis 事务和 MySQL 事务本质是完全不同的。 ——> MySQL中的事务是支持回滚的,而 Redis 中的事务是不支持回滚的。
3.1 快速入门(演示 Redis 事务控制)
https://img2024.cnblogs.com/blog/3084824/202502/3084824-20250205215542654-1974137990.png
127.0.0.1:6379> multihttps://img2024.cnblogs.com/blog/3084824/202502/3084824-20250205215542684-162812350.png
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)> https://img2024.cnblogs.com/blog/3084824/202502/3084824-20250205215542780-351805217.png
127.0.0.1:6379(TX)> exechttps://img2024.cnblogs.com/blog/3084824/202502/3084824-20250205215542675-1311609932.png
3.2 注意事项和细节
[*]组队的过程中,可以通过 discard来放弃组队。注意是在 队列当中,还没有执行 exce 命令之前,才有用。
https://img2024.cnblogs.com/blog/3084824/202502/3084824-20250205215542761-2065470154.png
127.0.0.1:6379(TX)> discardhttps://img2024.cnblogs.com/blog/3084824/202502/3084824-20250205215542611-1751946793.png
[*]如果在组队阶段报错(这里的报错信息指的是,命令输入错误,语法错误,编译上的错误) 会导致 exec 失败 那么事务的所有指令都不会被执行。
https://img2024.cnblogs.com/blog/3084824/202502/3084824-20250205215542587-1807129262.png
https://img2024.cnblogs.com/blog/3084824/202502/3084824-20250205215542715-1505610826.png
https://img2024.cnblogs.com/blog/3084824/202502/3084824-20250205215543134-77587121.png
[*]如果组队成功(multii ), 但是指令有不能正常执行的,那么 exec 提交,会出现有成功有失败环境,也就是事务得到部分执行, 这种环境下, Redis 事务不具备原子性。
https://img2024.cnblogs.com/blog/3084824/202502/3084824-20250205215543186-1078525252.png
https://img2024.cnblogs.com/blog/3084824/202502/3084824-20250205215543511-126628099.png
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> https://img2024.cnblogs.com/blog/3084824/202502/3084824-20250205215543367-131575721.png
4. Redis 事务冲突及解决方案(悲观锁,乐观锁) watch & unwatch
我们先来看一个经典的抢票 问题。
[*]一个请求(用户)想购买 6 张票
[*]一个请求(用户)想购买 5 张票
[*]一个请求(用户)想购买 1 张票
https://img2024.cnblogs.com/blog/3084824/202502/3084824-20250205215543108-675525834.png
上述解图:
一共只有10张票,但是并发开始,三个用户(三个请求),买 6 张,买 5 张,买 1 张票的。同时进入购票系统,并发同时候进入判定,都显示还剩10张票(还没有减),最后执行减票,超卖了 2张票。
4.1 “悲观锁” 解决
https://img2024.cnblogs.com/blog/3084824/202502/3084824-20250205215543074-694908745.png
上图解图:
[*]悲观锁(Pessimistic Lock),顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁。
[*]这样别人/其他请求想要拿到这个数据都会被(block 锁上,由于被锁了就无法修改/拿到数据了),只有直到在他前面的人拿到数据/修改数据后,将锁释放了,它才气将数据拿到。
[*]悲观锁是锁设计理念 ,传统的关系型数据库里面就用到了很多这种锁机制,比如行锁,表锁,读锁,写锁等等,都是在做操作之前先上锁(防止被其他的人/请求操作,修改了数据,导致数据不同等。)
4.2 “乐观锁” 解决
https://img2024.cnblogs.com/blog/3084824/202502/3084824-20250205215542678-1045615241.png
上图解读:
[*]乐观锁(Optimistic Lock),顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁。
[*]但是在更新的时候会判定一下,在此期间别人/请求是否有去更新了这个数据,可以使用版本号等机制。版本号机制:就是当这个数据被修改了,那么就会产生一个版本信息,如果这个版本信息,与你一开始对应,并应该获取的版本信息不同等,那么就修改失败/无法修改数据(或者说获取的版本信息不同等,拿不到该数据信息)
[*]乐观锁适用于多读的应用范例,这样可以提高吞吐量。 Redis 就是利用这种 check-and-set 机制实现事务的。
[*]乐观锁是锁设计理念。
4.3 watch & unwatch
https://img2024.cnblogs.com/blog/3084824/202502/3084824-20250205215542952-1228093605.png
[*]基本语法: watch key
[*]在执行 multi 之前,先执行 watch key1 ,可以监视一个(或多个) key,如果在事务执行之前这个(或这些) key 被其他命令所改动过,那么事务将被打断,停止执行 。
[*]这里就可以结合乐观锁机制举行理解。
演示实操:
https://img2024.cnblogs.com/blog/3084824/202502/3084824-20250205215543430-1848789230.png
https://img2024.cnblogs.com/blog/3084824/202502/3084824-20250205215543393-319516988.png
https://img2024.cnblogs.com/blog/3084824/202502/3084824-20250205215543137-1355542522.png
# 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:
https://img2024.cnblogs.com/blog/3084824/202502/3084824-20250205215543376-754491985.png
[*]unwatch : 取消 watch 命令对所有 key 的监视。
[*]如果在执行 watch 命令后, exec 命令和 discard 命令先被执行了的话,那么久不必要再执行 unwatch 了。
5. 案例演示:火车票-抢票(解决超卖,库存遗留)问题
5.1 案例思绪分析:
这里我们使用 WEB 项目来演示
https://img2024.cnblogs.com/blog/3084824/202502/3084824-20250205215542955-1552899061.png
https://img2024.cnblogs.com/blog/3084824/202502/3084824-20250205215543055-1664862787.png
思绪分析:
[*]一个 user 只能购买一张票,即不能复购。
[*]不能出现超购,也就是多卖的环境。
[*]不能出现火车票遗留问题/库存遗留,即火车票不能留下
https://img2024.cnblogs.com/blog/3084824/202502/3084824-20250205215543205-1017085221.png
5.2 完成基本购票流程,暂不考虑事务和并发问题
[*]创建 Java Web 项目,参照从前讲过搭建 Java Web 项目流程即可
[*]引入相关的 jar 包和jquery
[*]创建 D:sec_kill_ticket\web\index.jsp
https://img2024.cnblogs.com/blog/3084824/202502/3084824-20250205215543030-1935397556.png
https://img2024.cnblogs.com/blog/3084824/202502/3084824-20250205215542584-551231363.png
https://img2024.cnblogs.com/blog/3084824/202502/3084824-20250205215542720-708848094.png
https://img2024.cnblogs.com/blog/3084824/202502/3084824-20250205215542779-1730112504.png
https://img2024.cnblogs.com/blog/3084824/202502/3084824-20250205215542622-1466563723.png
https://img2024.cnblogs.com/blog/3084824/202502/3084824-20250205215543141-1398311143.png
https://img2024.cnblogs.com/blog/3084824/202502/3084824-20250205215542883-38878093.png
https://img2024.cnblogs.com/blog/3084824/202502/3084824-20250205215542693-253897196.png
https://img2024.cnblogs.com/blog/3084824/202502/3084824-20250205215542745-704937860.png
https://img2024.cnblogs.com/blog/3084824/202502/3084824-20250205215542784-165446251.png
https://img2024.cnblogs.com/blog/3084824/202502/3084824-20250205215542889-1062929468.png
https://img2024.cnblogs.com/blog/3084824/202502/3084824-20250205215542793-341307170.png
https://img2024.cnblogs.com/blog/3084824/202502/3084824-20250205215542741-1895872743.png
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>https://img2024.cnblogs.com/blog/3084824/202502/3084824-20250205215542678-958825322.png
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企服之家,中国第一个企服评测及商务社交产业平台。
页:
[1]