redis事物实现秒杀

使用Redis事物实现多人秒杀同一款商品
① 设计商品在Redis中的存储,key值为sk:"+prodid+":qt,商品的值为商品的数量(string类型)
② 设计秒杀成功的用户清单在Redis中的存储,key为sk:"+prodid+":usr,保存秒杀成功的用户使用set集合保存

1、前端页面index.jsp

前端jsp页面点击秒杀按钮调用后台doseckill
2、编写后台servlet(SecKillServlet)

public class SecKillServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    public SecKillServlet() {
        super();
    }

    /**
     * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
     */
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String userid = new Random().nextInt(50000) +"" ; 
        String prodid =request.getParameter("prodid");
        boolean if_success=SecKill_redis.doSecKill(userid,prodid);
        response.getWriter().print(if_success);
    }
}

3、在web.xml中配置servlet

    
    
      seckill
      
        
        doseckill
        doseckill
        com.atguigu.SecKillServlet
      
      
        doseckill
        /doseckill
      
    

4、SecKill_redis类中实现doSecKill方法
如果不加redis事务,会出现超卖问题

public class SecKill_redis {
    
    private static final  org.slf4j.Logger logger =LoggerFactory.getLogger(SecKill_redis.class) ;

    public static boolean doSecKill(String uid,String prodid) throws IOException {
        //通过连接池,解决连接超时问题
        JedisPool jedisPool = JedisPoolUtil.getJedisPoolInstance();
        
        Jedis jedis =jedisPool.getResource();//如果jedis是从连接池获取的,当执行close时回收到连接池中,不会关闭
        //1.获取redis存储相应key
        String qtKey="sk:"+prodid+":qt";
        String usrKey="sk:"+prodid+":usr";
        
        //判断用户是否秒杀成功过
        if(jedis.sismember(usrKey, uid)){
            System.out.println("已经秒杀过!!!");
            jedis.close();
            return false;
        }
        
        jedis.watch(qtKey);
        //对商品判断
        String qtValue=jedis.get(qtKey);
        if(qtValue==null){
            System.out.println("商品没有初始化!!!");
            jedis.close();
            return false;
        }else{
            int qt=Integer.parseInt(qtValue);
            if(qt==0){
                System.out.println("秒杀失败!");
                jedis.close();
                return false;
            }
        }
        Transaction t=jedis.multi();
        //2.商品减库存
        t.decr(qtKey);
        //3.增加相应用户
        t.sadd(usrKey, uid);
        List list = t.exec();
        if(list.size()==0 ||list==null){
            System.out.println("排队失败!!!");
            jedis.close();
            return false;
        }
        System.out.println("秒杀成功!!!");
        jedis.close();
        return true;
    }
}

当请求量太大时,会出现部分请求连接超时问题,所以采用redis连接池解决超时问题
5、JedisPoolUtil实现如下

public class JedisPoolUtil {
    private static volatile JedisPool jedisPool = null;

    private JedisPoolUtil() {
    }

    public static JedisPool getJedisPoolInstance() {
        if (null == jedisPool) {
            synchronized (JedisPoolUtil.class) {
                if (null == jedisPool) {
                    JedisPoolConfig poolConfig = new JedisPoolConfig();
                    poolConfig.setMaxTotal(200);
                    poolConfig.setMaxIdle(32);
                    poolConfig.setMaxWaitMillis(100*1000);
                    poolConfig.setBlockWhenExhausted(true);
                    poolConfig.setTestOnBorrow(true);
                    jedisPool = new JedisPool(poolConfig, "192.168.192.10", 6379, 60000 );
                }
            }
        }
        return jedisPool;
    }

}

采用redis事务解决超卖和超时问题,但是足够的用户去秒杀商品,出现商品过剩的问题,使用redis事务无法解决,所以会用到lua脚本
6、SecKill_redisByScript类实现doSecKill方法

public class SecKill_redisByScript {
    
    private static final  org.slf4j.Logger logger =LoggerFactory.getLogger(SecKill_redisByScript.class) ;
    
    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=  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;
    }
    
}

你可能感兴趣的:(redis事物实现秒杀)