Redis:事务_秒杀案例
package com.tinstu.redis2.test;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import redis.clients.jedis.Jedis;
//秒杀过程
public class doSecKill {
public static boolean doSe(String uid,String prodid) {
//1.uid和prodid非空判断
if(uid == null || prodid == null ){
return false;
}
//2.链接redis
Jedis jedis = new Jedis("120.48.39.100",6379);
//3.拼接key
//3.1 库存key
String kcKey = "sk:"+prodid+":qt";
//3.2秒杀成功的用户key
String userKey = "sk:"+prodid+":user";
//4.获取库存,如何库存null,秒杀还没有开始
String kc = jedis.get(kcKey);
if(kc == null){
System.out.println("秒杀未开始!");
}
//5.判断用户是否重复秒杀操作
if(jedis.sismember(userKey,uid)){
System.out.println("你已经成功,无法再次秒杀");
jedis.close();
return false;
}
//6.判断如果商品数量,库存数量小于1,秒杀结束
if(Integer.parseInt(kc)<=0){
System.out.println("秒杀已经结束");
jedis.close();
return false;
}
//7.秒杀
//7.1 库存 -1
jedis.decr(kcKey);
//7.2 把秒杀成功用户添加到清单里面
jedis.sadd(userKey,uid);
System.out.println("秒杀成功");
jedis.close();
return true;
}
}
Redis事务--秒杀并发模拟
使用工具ab模拟测试
CentOS 6 默认安装了
centos 7 需要自己安装
yum install httpd-tools
通过ab进行测试
vim postfile 模拟表单提交参数,以&符号结尾;存放当前目录。
内容:prodid=0101& (传入商品id)
-n 请求的数量 -c 并发数量 -p ~/postfile(存放参数的文件名) -T (POST数据所使用的Content-type头信息
ab -n 2000 -c 200 -p ~/postfile -T application/x-www-form-urlencoded http://192.168.2.115:8081/Seckill/doseckill
超卖问题
通过ab进行高并发测试,发生超卖问题
通过乐观锁解决超卖问题
//增加乐观锁
jedis.watch(qtkey);
//3.判断库存
String qtkeystr = jedis.get(qtkey);
if(qtkeystr==null || "".equals(qtkeystr.trim())) {
System.out.println("未初始化库存");
jedis.close();
return false ;
}
int qt = Integer.parseInt(qtkeystr);
if(qt<=0) {
System.err.println("已经秒光");
jedis.close();
return false;
}
//增加事务
Transaction multi = jedis.multi();
//4.减少库存
//jedis.decr(qtkey);
multi.decr(qtkey);
//5.加人
//jedis.sadd(usrkey, uid);
multi.sadd(usrkey, uid);
//执行事务
List<Object> list = multi.exec();
//判断事务提交是否失败
if(list==null || list.size()==0) {
System.out.println("秒杀失败");
jedis.close();
return false;
}
System.err.println("秒杀成功");
jedis.close();
再次进行并发测试,没有发送超卖问题,但是发生了 库存遗留问题
已经秒光,可是还有库存。原因,就是乐观锁导致很多请求都失败。先点的没秒到,后点的可能秒到了。
库存遗留问题解决
使用lua脚本 https://www.w3cschool.cn/lua/
LUA脚本是类似redis事务,有一定的原子性,不会被其他命令插队,可以完成一些redis事务性的操作。
local userid=KEYS[1];
local prodid=KEYS[2];
local qtkey="sk:"..prodid..":qt";
local usersKey="sk:"..prodid.":usr';
local userExists=redis.call("sismember",usersKey,userid);
if tonumber(userExists)==1 then
return 2;
end
local num= redis.call("get" ,qtkey);
if tonumber(num)<=0 then
return 0;
else
redis.call("decr",qtkey);
redis.call("sadd",usersKey,userid);
end
return 1;
lua与java结合在一起
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;
}
}
阅读剩余
版权声明:
作者:Tin
链接:http://www.tinstu.com/1665.html
文章版权归作者所有,未经允许请勿转载。
THE END