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;
	}
}

 

 

阅读剩余
THE END