十. 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”
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)> exec
3.2 注意事项与细节
- 组队阶段可通过
discard
放弃组队,需在[TX]
队列且未执行exec
命令前有效。 - 若组队阶段出现命令输入等错误,会导致
exec
失败,事务所有指令不执行。 - 组队成功但指令执行异常时,
exec
提交会出现部分成功部分失败情况,Redis事务不具原子性。
4. Redis 事务冲突及解决办法(悲观锁,乐观锁) watch & unwatch
以抢票问题为例:多个用户同时请求购票,若不处理会出现超卖。
4.1 “悲观锁” 解决
悲观锁认为每次获取数据时其他会修改,因此获取数据前上锁。传统关系型数据库的行锁、表锁等即属此类。
4.2 “乐观锁” 解决
乐观锁认为获取数据时其他不会修改,更新时检查版本等。Redis利用 check-and-set
机制实现事务。
4.3 watch & unwatch
- 基本语法:
watch key [key ...]
- 执行
multi
前用watch
监视key,若事务执行前key被修改,事务中断。 unwatch
取消对所有key的监视,若已执行exec
或discard
,无需再用。
5. 案例演示:火车票-抢票(解决超卖,库存遗留)问题
5.1 案例思路分析:
通过WEB项目演示,需保证一个用户只能购一张票、无超购、无库存遗留。
5.2 完成基本购票流程,暂不考虑事务和并发问题
编写Java Web项目,引入相关jar包和jquery,创建index.jsp等。
5.3 抢票并发模拟,出现超卖问题
使用ab工具模拟并发请求,测试超卖情况。
5.4 Redis 连接池技术
连接池节省连接消耗,配置MaxTotal、maxIdle等参数。
5.5 利用 Redis 的事务机制,解决超卖问题(使用 watch,multi )
通过 watch
监控库存,使用事务完成秒杀。
5.6 抢票并发模拟,分析库存遗留问题
大量并发请求可能导致库存遗留,因乐观锁机制部分请求被打断。
5.7 运用 LUA 脚本(解决超卖,和库存遗留问题)
LUA脚本具原子性,解决多任务并发问题。LUA脚本示例:
local userid=KEYS[1];
local ticketno=KEYS[2];
local stockKey='sk:'..ticketno..":ticket";
local usersKey='sk:'..ticketno..":user";
local userExists=redis.call("sismember",usersKey,userid);
if tonumber(userExists)==1 then
return 2;
end
local num= redis.call("get" ,stockKey);
if tonumber(num)<=0 then
return 0;
else
redis.call("decr",stockKey);
redis.call("sadd",usersKey,userid);
end
return 1
6. 总结:
在本部分,感谢读者的关注与支持,将继续在相关领域探索,期待与读者再次相遇。
文章整理自互联网,只做测试使用。发布者:Lomu,转转请注明出处:https://www.it1024doc.com/12530.html