十. 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”
Redis 事务指令示意图:
上图解读:
[*]从输入 multi 命令开始,输入的命令都会依次进入命令队列 中,但不会执行类似(MySQL的 start transaction 开始事务)。
[*]输入 Exec 命令后,Redis 会将之前的命令队列中的命令依次执行(类似于 MySQL的 commit 提交事务)。
[*]组队的过程中可以通过 discard 来放弃组队(类似 MySQL的 rollback 回滚事务)
[*]说明:Redis 事务和 MySQL 事务本质是完全不同的。 ——> MySQL中的事务是支持回滚的,而 Redis 中的事务是不支持回滚的。
3.1 快速入门(演示 Redis 事务控制)
127.0.0.1:6379> multi
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)>
127.0.0.1:6379(TX)> exec
3.2 注意事项和细节
[*]组队的过程中,可以通过 discard来放弃组队。注意是在 队列当中,还没有执行 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
[*]在执行 multi 之前,先执行 watch key1 ,可以监视一个(或多个) 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 项目来演示
https://img2024.cnblogs.com/blog/3084824/202502/3084824-20250205215543055-1664862787.png
思路分析:
[*]一个 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 的内容,大家可以移步至:
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
页:
[1]