victory的博客

长安一片月,万户捣衣声

0%

Redis | 秒杀

秒杀

秒杀需要解决的问题

解决商品库存计数器和秒杀成功的用户记录的事务操作

秒杀遇到的三个问题

1.连接超时问题 —> 使用Redis连接池
2.超卖(卖出的商品数量超过商品库存数量) —> 使用事务
3.库存遗留问题(并发的请求中只有一个请求能够秒杀成功造成库存遗留) —> 使用lua脚本

1.index.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>iPhoneXsMAX !!!  1元秒杀!!!
</h1>


<form id="msform" action="${pageContext.request.contextPath}/doseckill" enctype="application/x-www-form-urlencoded">
    <input type="hidden" id="prodid" name="prodid" value="0101">
    <input type="button"  id="miaosha_btn" name="seckill_btn" value="秒杀点我"/>
</form>

</body>
<script  type="text/javascript" src="${pageContext.request.contextPath}/script/jquery/jquery-3.1.0.js"></script>
<script  type="text/javascript">
$(function(){
    $("#miaosha_btn").click(function(){     
        var url=$("#msform").attr("action");
         $.post(url,$("#msform").serialize(),function(data){
            if(data=="false"){
                alert("抢光了" );
                $("#miaosha_btn").attr("disabled",true);
            }
        } );    
    })
})
</script>
</html>

2.SeckillServlet.java

public class SecKillServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
       
    /**
     * @see HttpServlet#HttpServlet()
     */
    public SecKillServlet() {
        super();
        // TODO Auto-generated constructor stub
    }

    /**
     * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
     */
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // TODO Auto-generated method stub
 
        String userid = new Random().nextInt(50000) +"" ; 

        String prodid =request.getParameter("prodid");
        
        boolean if_success=SecKill_redisByScript.doSecKill(userid,prodid);
 
        response.getWriter().print(if_success);
    }
}

3.JedisPoolUtil.java

package com.atguigu;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

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);  // ping  PONG
                 
                    jedisPool = new JedisPool(poolConfig, "192.168.223.132", 6379, 60000 );
             
                }
            }
        }
        return jedisPool;
    }

    public static void release(JedisPool jedisPool, Jedis jedis) {
        if (null != jedis) {
            jedisPool.returnResource(jedis);
        }
    }

}

4.Seckill_redisByScript.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='Seckill:'..prodid..\":kc\";\r\n" + 
            "local usersKey='Seckill:'..prodid..\":user\";\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;         
    }
}