使用Lua脚本通过原子减防止超卖
需求
双十二要搞一个一分钱门票抢购的活动。
分析
性能分析,抢购时会发生高并发,如果仅仅依靠Mysql数据库,有可能因为大量的请求频繁访问数据库造成服务器雪崩,所以考虑通过Redis减库存,最终的数据落地到DB中。
在高并发的情况下,还要考虑到超卖的问题,因而打算使用Lua脚本完成原子减的操作。
在这里,我们只针对减库存的操作进行分析。
实现
不使用原子操作,出现超卖的情况。第一步:先从redis中查出库存进行判断,第二步:如果库存>0,则进行减库存的操作。
代码实现:
// 第一步:从redis中查出库存
Integer stock = (Integer) RedisUtils.get("stock"); // 第二步:如果库存>0,则进行减库存的操作
if (stock > 0) {
long spareStock = RedisUtils.decr("stock", 1);
System.out.println(getName() + "抢到了第" + spareStock + "件");
} else {
System.out.println("库存不足");
}
用多线程模拟并发请求:库存为500,创建505个线程去抢购。
for(int i =1;i<=505;i++){
MyThread2 thread =new MyThread2("线程"+i);
thread.start();
}
执行结果:出现超卖问题,原因是:查询库存及减库存不是原子性操作。

使用原子性操作:直接减库存。
public void run() {
long stock = RedisUtils.stock("stock");
if (stock > 0) {
System.out.println(getName() + "抢到了第" + stock + "件");
} else {
System.out.println("库存不足");
}
}
Lua脚本实现减库存操作:
/**
* 库存不足
*/
public static final int LOW_STOCK = 0;
/**
* 不限库存
*/
public static final long UNINITIALIZED_STOCK = -1L; /**
* 执行扣库存的脚本
*/
public static final String STOCK_LUA; static {
// 初始化减库存lua脚本
StringBuilder sb = new StringBuilder();
sb.append("if (redis.call('exists', KEYS[1]) == 1) then");
sb.append(" local stock = tonumber(redis.call('get', KEYS[1]));");
sb.append(" if (stock == -1) then");
sb.append(" return 1;");
sb.append(" end;");
sb.append(" if (stock > 0) then");
sb.append(" redis.call('incrby', KEYS[1], -1);");
sb.append(" return stock;");
sb.append(" end;");
sb.append(" return 0;");
sb.append("end;");
sb.append("return -1;"); STOCK_LUA = sb.toString();
} /**
* 扣库存
*
* @param key 库存key
* @return 扣减之前剩余的库存【0:库存不足; -1:库存未初始化; 大于0:扣减库存之前的剩余库存】
*/
public static Long stock(String key) {
// 脚本里的KEYS参数
List<String> keys = new ArrayList<>();
keys.add(key);
// 脚本里的ARGV参数
List<String> args = new ArrayList<>(); Long result = (Long)redisTemplate.execute(new RedisCallback<Long>() {
@Override
public Long doInRedis(RedisConnection connection) throws DataAccessException {
Object nativeConnection = connection.getNativeConnection();
// 集群模式和单机模式虽然执行脚本的方法一样,但是没有共同的接口,所以只能分开执行
// 集群模式
if (nativeConnection instanceof JedisCluster) {
return (Long) ((JedisCluster) nativeConnection).eval(STOCK_LUA, keys, args);
} // 单机模式
else if (nativeConnection instanceof Jedis) {
return (Long)((Jedis) nativeConnection).eval(STOCK_LUA, keys, args);
}
return UNINITIALIZED_STOCK;
}
});
return result;
}
执行结果:505个线程去抢500个商品,有五个线程会抢不到,测试结果与预期一致,解决了超卖的问题。

参考:https://blog.csdn.net/xiaolyuh123/article/details/79208959
使用Lua脚本通过原子减防止超卖的更多相关文章
- 记一次项目中解决 -- 并发减库存超卖问题过程(Java)
起因:项目中要做预约功能,首先每天的余票都是有上限的,自然不能出现超卖的情况 基于我们项目是单体分布式的springcloud部署,我想了下 第一种方法,直接mysql加行锁,要update这条库存数 ...
- 基于Lua脚本解决实时数据处理流程中的关键问题
摘要 在处理实时数据的过程中需要缓存的参与,由于在更新实时数据时并发处理的特点,因此在更新实时数据时经常产生新老数据相互覆盖的情况,针对这个情况调查了Redis事务和Lua脚本后,发现Redis事务并 ...
- Redis进阶实践之十九 Redis如何使用lua脚本
一.引言 redis学了一段时间了,基本的东西都没问题了.从今天开始讲写一些redis和lua脚本的相关的东西,lua这个脚本是一个好东西,可以运行在任何平台上,也可以嵌入 ...
- redisTemplate的spring配置以及lua脚本驱动
最近在使用spring-data-redis的redisTemplate,所以写篇使用记录吧. 1.不用多说,使用maven引入相关依赖,因为项目已经引入其他的 <dependency> ...
- Redis Lua脚本原理
2.6版本之后支持嵌入Lua脚本,客户端使用Lua脚本,直接在服务器端原子的执行多条命令 Lua脚本执行过程 创建并修改Lua环境 1 创建基础Lua环境 2 载入函数库 3 创建全局表格Lua 4 ...
- Redis学习笔记六:独立功能之 Lua 脚本
Redis 2.6 开始支持 Lua 脚本,通过在服务器环境嵌入 Lua 环境,Redis 客户端中可以原子地执行多个 Redis 命令. 使用 eval 命令可以直接对输入的脚本求值: 127.0. ...
- Lua脚本之语法基础快速入门
要 1.基本数据类型 2.Lua中的常用语句结构以及函数 3.Lua中的常用语句结构介绍 4.Lua中的库函数 目录[-] 一.基本数据类型 二.Lua中的常用语句结构以及函数 1.Lua中的常用语句 ...
- Lua脚本在redis分布式锁场景的运用
目录 锁和分布式锁 锁是什么? 为什么需要锁? Java中的锁 分布式锁 redis 如何实现加锁 锁超时 retry redis 如何释放锁 不该释放的锁 通过Lua脚本实现锁释放 用redis做分 ...
- Redis结合Lua脚本实现高并发原子性操作
从 2.6版本 起, Redis 开始支持 Lua 脚本 让开发者自己扩展 Redis … 案例-实现访问频率限制: 实现访问者 $ip 在一定的时间 $time 内只能访问 $limit 次. 非脚 ...
随机推荐
- C#读写设置修改调整UVC摄像头画面-曝光
有时,我们需要在C#代码中对摄像头的曝光进行读和写,并立即生效.如何实现呢? 建立基于SharpCamera的项目 首先,请根据之前的一篇博文 点击这里 中的说明,建立基于SharpCamera的摄像 ...
- kubernetes(k8s)集群安装calico
添加hosts解析 cat /etc/hosts 10.39.7.51 k8s-master-51 10.39.7.57 k8s-master-57 10.39.7.52 k8s-master-52 ...
- chrome截屏的方法
原文本文链接:https://blog.csdn.net/xiaofengzhiyu/article/details/94652057 Chrome保存整个网页为图片保存为图片右键检查快捷键Ctrl+ ...
- Django---Django的中间件
Django---Django的中间件 一丶中间件介绍 什么是中间件 官方的说法:中间件是一个用来处理Django的请求和响应的框架级别的钩子.它是一个轻量.低级别的插件系统,用于在全局范围内改变Dj ...
- C# 方法执行超时处理
封装了一个方法,用于处理一些需要判断是否执行超时了的操作 internal static T TimeoutCheck<T>(int ms, Func<T> func) { v ...
- 马哥docker听课记录
容器技术:chroot.namespaces.cgroups docker平时用户空间只运行一个进程,只运行在一个namespaces中 镜像:分层构建.联合挂载 容器编排工具:kubernetes ...
- k8s 初识pod (二)
kubernetes中调用pod到哪个节点上是无关紧要的,但由于实际情况,每台node的硬件环境不一致,所以某些情况要求将不同pod调到指定节点上运行.也可以通过label实现. kubectl la ...
- Python装饰器与闭包
闭包是Python装饰器的基础.要理解闭包,先要了解Python中的变量作用域规则. 变量作用域规则 首先,在函数中是能访问全局变量的: >>> a = 'global var' & ...
- Docker版zabbix
1. docker-compose docker-compose :容器自带的编排工作,可以通过yaml编排文件,将容器要启动的命令写入文件,然后再利用docker-compose run file. ...
- datagrid 溢出文本显示省略号 转载
http://www.jeasyuicn.com/?sort=3 .datagrid-cell, .datagrid-cell-group, .datagrid-header-rownumber, . ...