深浅模式
解决库存遗留问题
什么是库存遗留问题

ab命令:模拟2000个请求 300个并发
shell
ab -n 2000 -c 300 -k -p /ab/postfile -T application/x-www-form-urlencoded http://192.168.10.1:8080/Seckill/doseckill
获取库存:发现还有476个没有卖出去

乐观锁造成库存遗留问题分析

解决库存遗留问题
LUA脚本
Lua 是一个小巧的脚本语言,Lua脚本可以很容易的被C/C++ 代码调用,也可以反过来调用C/C++的函数,Lua并没有提供强大的库,一个完整的Lua解释器不过200k,所以Lua不适合作为开发独立应用程序的语言,而是作为嵌入式脚本语言。 很多应用程序、游戏使用LUA作为自己的嵌入式脚本语言,以此来实现可配置性、可扩展性。 这其中包括魔兽争霸地图、魔兽世界、博德之门、愤怒的小鸟等众多游戏插件或外挂。 https://www.w3cschool.cn/lua
LUA脚本在Redis中的优势
将复杂的或者多步的redis操作,写为一个脚本,一次提交给redis执行,减少反复连接redis的次数。提升性能。 LUA脚本是类似redis事务,有一定的原子性,不会被其他命令插队,可以完成一些redis事务性的操作。 但是注意redis的lua脚本功能,只有在Redis 2.6以上的版本才可以使用。 利用lua脚本淘汰用户,解决超卖问题。 redis 2.6版本以后,通过lua脚本解决争抢问题,实际上是redis 利用其单线程的特性,用任务队列的方式解决多任务并发问题。 
相关代码
lua
local userid=KEYS[1]; --定义2个变量
local prodid=KEYS[2]; --定义2个变量
local qtkey="sk:"..prodid..":qt"; --拼接库存key
local usersKey='sk:"..prodid.":usr'; --拼接用户key
local userExists=redis.call("sismember",usersKey,userid); --调redis中的命令判断库存是否存在
if tonumber(userExists)==1 then --如果存在就返回2
return 2;
end
local num= redis.call("get" ,qtkey); --获取库存,如果库存小于等于0 返回0
if tonumber(num)<=0 then
return 0;
else
redis.call("decr",qtkey); --减库存
redis.call("sadd",usersKey,userid); --添加秒杀成功的用户
end
return 1; --秒杀成功返回1shell
package com.atguigu;
import java.io.IOException;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.slf4j.LoggerFactory;
import ch.qos.logback.core.joran.conditional.ElseAction;
import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.ShardedJedisPool;
import redis.clients.jedis.Transaction;
public class SecKill_redisByScript {
private static final org.slf4j.Logger logger =LoggerFactory.getLogger(SecKill_redisByScript.class) ;
public static void main(String[] args) {
JedisPool jedispool = JedisPoolUtil.getJedisPoolInstance();
Jedis jedis=jedispool.getResource();
System.out.println(jedis.ping());
Set<HostAndPort> set=new HashSet<HostAndPort>();
// doSecKill("201","sk:0101");
}
static String secKillScript ="local userid=KEYS[1];\r\n" +
"local prodid=KEYS[2];\r\n" +
"local qtkey='sk:'..prodid..\":qt\";\r\n" +
"local usersKey='sk:'..prodid..\":usr\";\r\n" +
"local userExists=redis.call(\"sismember\",usersKey,userid);\r\n" +
"if tonumber(userExists)==1 then \r\n" +
" return 2;\r\n" +
"end\r\n" +
"local num= redis.call(\"get\" ,qtkey);\r\n" +
"if tonumber(num)<=0 then \r\n" +
" return 0;\r\n" +
"else \r\n" +
" redis.call(\"decr\",qtkey);\r\n" +
" redis.call(\"sadd\",usersKey,userid);\r\n" +
"end\r\n" +
"return 1" ;
static String secKillScript2 =
"local userExists=redis.call(\"sismember\",\"{sk}:0101:usr\",userid);\r\n" +
" return 1";
public static boolean doSecKill(String uid,String prodid) throws IOException {
JedisPool jedispool = JedisPoolUtil.getJedisPoolInstance();
Jedis jedis=jedispool.getResource();
//String sha1= .secKillScript;
String sha1= jedis.scriptLoad(secKillScript);
Object result= jedis.evalsha(sha1, 2, uid,prodid);
String reString=String.valueOf(result);
if ("0".equals( reString ) ) {
System.err.println("已抢空!!");
}else if("1".equals( reString ) ) {
System.out.println("抢购成功!!!!");
}else if("2".equals( reString ) ) {
System.err.println("该用户已抢过!!");
}else{
System.err.println("抢购异常!!");
}
jedis.close();
return true;
}
}测试
设置库存

ab测试
lua
ab -n 2000 -c 300 -k -p /ab/postfile -T application/x-www-form-urlencoded http://192.168.10.1:8080/Seckill/doseckill再次获取库存:发现卖完了

总结
lua脚本 结合redis 实际上 就像一个悲观锁,做了一个悲观锁的作用,当第一个用户拿到key做修改时,后面所有用户都拿不到,只用用户一修改后,释放锁,后面用户才能往下操作。
