这两天同事们去国外旅游了,我这个宅货不喜欢出门就没去。但没有想到的是我还要按时来上班,在公司闲来无事,了解点关于lua的内容,现简单作个笔记。
redis是什么,自不必多言。lua脚本语言,胶水语言。redis在 2.6版本开始就支持lua。在安装了redis服务器之后,就可以执行lua。那么redis为什么需要lua呢?简单来说为了性能以及事务的原子性。因为redis帮我们提供的事务功能太差。下面以电商秒杀场景作简要说明。
用户秒杀,后端服务接收到请求之后的操作步骤:
在上面所描述的步骤中 校验库存与扣库存,存在先后顺序,但是并没有原子性。在关系数据库中,可以通过事务来解决这个问题,但是关系数据库性能有瓶颈。当然在请求量可以控制的情况下,使用关系数据库的乐观锁,也是可以的。就像现在我在公司所使用的方案为秒杀单接口进行限流+数据库的乐观锁就完全解决这个问题。但是这个通过限流来解决了请求的峰值。
如果我们将校验库存,扣库存这段逻辑放在redis上执行,也存在原子性问题。redis帮我们也提供了事务功能,但是这个事务功能太差强人意了。所以在本文中,我们希望通过redis+lua的方式解决这个问题.
使用redis执行lua的原子性,将上面的步骤在redis中执行,前提条件即秒杀商品及库存数据全部都要推到redis中。准备工作:
业务代码逻辑:
lua脚本需要完成的业务逻辑:
lua脚本内容
--判断该用户是否秒杀过,如果已秒则不允许再秒
local hasSecKill=redis.call('sismember',ARGV[1],KEYS[1])
if hasSecKill ~=0 then
return 0;
end
--设置抢单标识
--redis.call('set',KEYS[1],1);
--设置过期时间
--redis.call('expire',KEYS[1],30000);
--check库存
for goodsNum=2,#KEYS do
local goodsStock=redis.call('get',KEYS[goodsNum]);
if goodsStock< ARGV[goodsNum] then
return 2;
end
end
--扣库存
for goodsNum=2,#KEYS do
redis.call('DECRBY',KEYS[goodsNum],ARGV[goodsNum]);
end
-- 所有抢单成功的用户
redis.call('sadd',ARGV[#ARGV],KEYS[1]);
return 1;
当然你可以将这段lua放在java代码中:
public boolean orderSecKill(SecKillPara para, Jedis jedis) {
/* 0,已抢过单
* 2.库存不足
* 1.抢单成功
*/
Long customerId = para.getCustomerId();
String script =
" local ismeber=redis.call('sismember',ARGV[1], KEYS[1]) "
+" if ismeber ~= 0 then " // --判断该用户是否秒杀过,如果已秒则不允许再秒
+ " return 0 "
+ " end "
// + " redis.call('set',KEYS[1],1) " // --设置抢单标识
// + " redis.call('expire',KEYS[1],2000) " // --设置过期时间
// --check库存
+ " for goodsNum=2,#KEYS do "
+ " local goodsStock=redis.call('get',KEYS[goodsNum]) "
+ " if goodsStock < ARGV[goodsNum] then "
+ " return 2;"
+ " end "
+ " end "
// --扣库存
+ " for goodsNum=2,#KEYS do "
+ " redis.call('DECRBY',KEYS[goodsNum],ARGV[goodsNum]) "
+ " end "
// -- 所有抢单成功的用户
+ " redis.call('sadd',ARGV[1],KEYS[1])"
+ " return 1;";
List keys = new ArrayList();
List args = new ArrayList();
keys.add(customerId.toString());
args.add("all_order_user");// 所有抢单的用户
for (Map.Entry goods : para.getGoodsWithAmount().entrySet()) {
keys.add(goods.getKey().toString());
args.add(goods.getValue().toString());
}
Object o = jedis.eval(script, keys, args);
Long result=Long.valueOf(o.toString());
if(result==1){
return true;
}
return false;
}
public static final class SecKillPara{
private final Long customerId;//抢单人
private final Map goodsWithAmount;
public SecKillPara(Long customerId, Map goodsWithAmount) {
super();
this.customerId = customerId;
this.goodsWithAmount = goodsWithAmount;
}
public Long getCustomerId() {
return customerId;
}
public Map getGoodsWithAmount() {
return goodsWithAmount;
}
}
这个只是我作的一个小demo,并没有进行强测试,如果需要使用的同学则需要好好的测试哦。