隐藏页面特效

redis的锁机制和秒杀问题实践

最近学习了redis的锁机制并且进行了秒杀案例解决超卖的实践

multi/exec/discard

Redis Multi 命令用于标记一个事务块的开始。

事务块内的多条命令会按照先后顺序被放进一个队列当中,最后由 EXEC 命令原子性(atomic)地执行。

总结的说,redis是单线程的,每一 个命令都是在队列中顺序执行的,而Multi会让一系列指令按照顺序执行,且不允许其他指令插队,让这一串指令具备了原子性

multi:开启一个事务,接下来输入的指令(本客户端内)将不会返回值,直到调用了exec命令

exec: 执行一个事务,返回数组,即multi后所有指令的返回值

discard:抛弃这个事务

乐观锁与watch命令

redis的锁机制是基于乐观锁,乐观锁即为一个字段增加了版本号,每次更新都会原子性地把版本号加一,监听这个版本号后如果发现最终更新与这个版本号不一致将会不执行这次更新。

watch与multi

redis中watch命令是用乐观锁实现的锁机制,需要和multi命令配合使用。

使用方法:

watch key multi command 1 command 2 ... exec

效果:watch key即用乐观锁监听这个key,如果multi中的指令执行时会修改这个key,若发现key被别人修改过就会放弃这次事务。总而言之就是watch和multi之间key被修改就会放弃这次事务。

秒杀问题的解决

背景

为了加快抢购速度,避免数据库系统压力瞬间增加,会在抢购时将数据库中商品库存读入redis,所有抢购操作在redis中操作,这个小demo就是模拟redis中抢购的过程。

设计思路

用户从redis中读取商品余额,如果不存在key则说明抢购还没开始。开始抢购时向redis中添加商品余额,用户抢购到商品后库存减一,并且将该用户的id加入抢购名单set集合中,我的代码是由redisTemplate写的

可能出现的问题有:

  1. 超卖:如果有两个线程同时进入了减少库存的代码且没做处理就可能导致库存卖到负值
  2. 重复买:同一个用户连续发送多个请求且同时进入了购买逻辑
  3. 剩余库存:如果用乐观锁做了控制导致用户很容易就抢购失败了,用户体验会很糟糕。这里用自旋的方式降低抢购失败发生的概率

代码如下

@PostMapping("/kill") public String testSecKill(@RequestBody SecKillDto secKillDto) { Integer prodId = secKillDto.getProdId(); Integer userId = secKillDto.getUserId(); String prodKey = "sk:prod:" + prodId + ":int"; String userKey = String.format("sk:buylist:%d:set:int", prodId); Integer prodNum = (Integer) redisTemplate.boundValueOps(prodKey).get(); if(prodNum == null) { return "还没有开始"; } for(int i = 0; i < casTime; i++) { Object exec = redisTemplate.execute(new SessionCallback() { @Override public Object execute(RedisOperations operations) throws DataAccessException { operations.watch(prodKey); if(redisTemplate.boundSetOps(userKey).isMember(userId)) { return "不能重复抢"; } if(((Integer) redisTemplate.boundValueOps(prodKey).get()).compareTo(0) <= 0) { return "已经抢光了"; } operations.multi(); operations.boundValueOps(prodKey).decrement(); operations.boundSetOps(userKey).add(userId); List exec1 = operations.exec(); return exec1; } }); if(exec instanceof List && (exec == null || ((List)exec).size() == 0)) { System.out.println("失败"); } else if(exec instanceof String) { return (String) exec; } else { return "抢购成功"; } } return "抢购失败"; }

说明:一定要在watch后判断是否重复抢和是否抢光,因为如果在watch之前做判断,判断和watch之间可能值被减少,实际watch的值比判断的值要小,导致有可能watch的就是0。watch之后再判断可以保证watch之后如果是0就不执行

使用JMeter做压测

1. 下载JMeter并解压

http://jmeter.apache.org/download_jmeter.cgi

2. 新建测试

打开JMeter的GUI,即JMeter.bat,新建测试,就不多赘述了

压测后看看结果,有没有超卖或者重复抢购,应该是没有的


__EOF__

本文作者秋雨清笛
本文链接https://www.cnblogs.com/PanYuDi/p/15406609.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   秋雨清笛  阅读(473)  评论(0编辑  收藏  举报
编辑推荐:
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
阅读排行:
· 全程使用 AI 从 0 到 1 写了个小工具
· 快收藏!一个技巧从此不再搞混缓存穿透和缓存击穿
· AI 插件第二弹,更强更好用
· Blazor Hybrid适配到HarmonyOS系统
· 支付宝 IoT 设备入门宝典(下)设备经营篇
点击右上角即可分享
微信分享提示