超卖问题(可以使用事务方式(乐观锁)通过对比本事务与数据库版本号进行控制(需要用watch进行监视) 也可以使用LUA脚本(包含在一个Lua脚本里面的redis命令具备原子性))
连接超时问题(可以使用jedis连接池)
库存遗留问题(就是事务经常失败,库存还有很多)(可以使用LUA脚本进行解决(原子性:挨着运行))
使用的是jsp加servlet
<%@ 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>
使用连接池,可以配置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(); } } }
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; } }
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; } }
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