redis简单秒杀实例(初学者)

redis简单秒杀实例(初学者)

 

超卖问题(可以使用事务方式(乐观锁)通过对比本事务与数据库版本号进行控制(需要用watch进行监视)          也可以使用LUA脚本(包含在一个Lua脚本里面的redis命令具备原子性))

 

连接超时问题(可以使用jedis连接池)

 

库存遗留问题(就是事务经常失败,库存还有很多)(可以使用LUA脚本进行解决(原子性:挨着运行))

 使用的是jsp加servlet

redis简单秒杀实例(初学者)
<%@ 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>iPhone 13 Pro !!!  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>
秒杀的jsp网页
使用连接池,可以配置redis的连接
redis简单秒杀实例(初学者)
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, "114.116.246.5", 6379, 60000,"Atcomsn1314");
                }
            }
        }
        return jedisPool;
    }

    public static void release(Jedis jedis) {
        if(null != jedis){
            jedis.close();
        }
    }

}
JedisPoolUtil类连接池类用于获取Jedis

 

redis简单秒杀实例(初学者)
public class SecKill_redis {

    public static void main(String[] args) {
        Jedis jedis =new Jedis("192.168.44.168",6379);
        System.out.println(jedis.ping());
        JedisPoolUtil.release(jedis);
    }

    //秒杀过程
    public static boolean doSecKill(String uid,String prodid) throws IOException {
        //1 uid和prodid非空判断
        if(uid == null || prodid == null) {
            return false;
        }

        //2 连接redis
        //Jedis jedis = new Jedis("192.168.44.168",6379);
        //设置密码
        //jedis.auth("123456");
        //通过连接池得到jedis对象
        JedisPool jedisPoolInstance = JedisPoolUtil.getJedisPoolInstance();
        Jedis jedis = jedisPoolInstance.getResource();

        //3 拼接key
        // 3.1 库存key
        String kcKey = "sk:"+prodid+":qt";
        // 3.2 秒杀成功用户key
        String userKey = "sk:"+prodid+":user";

        //监视库存
        jedis.watch(kcKey);

        //4 获取库存,如果库存null,秒杀还没有开始
        String kc = jedis.get(kcKey);
        if(kc == null) {
            System.out.println("秒杀还没有开始,请等待");
            //释放jedis(close)
            JedisPoolUtil.release(jedis);
            return false;
        }

        // 5 判断用户是否重复秒杀操作(sismember为判断set中是否存在,返回boolean类型)
        if(jedis.sismember(userKey, uid)) {
            System.out.println("已经秒杀成功了,不能重复秒杀");
            JedisPoolUtil.release(jedis);
            return false;
        }

        //6 判断如果商品数量,库存数量小于1,秒杀结束
        if(Integer.parseInt(kc)<=0) {
            System.out.println("秒杀已经结束了");
            JedisPoolUtil.release(jedis);
            return false;
        }

        //7 秒杀过程
        //使用事务
        Transaction multi = jedis.multi();

        //组队操作
        multi.decr(kcKey);
        multi.sadd(userKey,uid);

        //执行
        List<Object> results = multi.exec();

        if(results == null || results.size()==0) {
            System.out.println("秒杀失败了....");
            JedisPoolUtil.release(jedis);
            return false;
        }

        //7.1 库存-1
        //jedis.decr(kcKey);
        //7.2 把秒杀成功用户添加清单里面
        //jedis.sadd(userKey,uid);

        System.out.println("秒杀成功了..");
        JedisPoolUtil.release(jedis);
        return true;
    }
}
SecKill_redis类其中的秒杀方法

 

redis简单秒杀实例(初学者)
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");
    }

    //获取参数KEYS[i]
    static String secKillScript ="local userid=KEYS[1];\r\n" + 
            "local prodid=KEYS[2];\r\n" +
            //..prodid..类似拼接字符串
            "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;
    }
}
使用LUA脚本编写的秒杀方法(性能高)

 

ubuntu中安装apache ab命令进行简单压力测试

1.安裝ab命令

sudo apt-get install apache2-utils

 

 

ab命令需要参数,先编写postfile

vi postfile

编写以下内容

prodid=0101&

按下esc 输入:wq保存退出

 

ab命令用于压力测试(并发测试)(可以通过花生壳http转发获取公网ip)(http://4q2321204730s7.zicp.vip/Seckill/doseckill为网页使用的post方法)

-n 请求数   -c 请求数中的并发请求数      -T使用post,put请求需设置content-type

ab -n 100 -c 10 -p ~/postfile -T application/x-www-form-urlencoded http://4q2321204730s7.zicp.vip/Seckill/doseckill

 

 

 

redis简单秒杀实例(初学者)

上一篇:2021/08/07 模拟笔试复盘


下一篇:单例设计模式