秒杀案例(无并发秒杀、模拟并发秒杀)

前端传一个产品id过来,并且与servlet做ajax交互,前端得到产品库存数量

servlet

String userid = new Random().nextInt(50000) +"" ;
		String prodid =request.getParameter("prodid");
		boolean isSuccess=SecKill_redis.doSecKill(userid,prodid);
		response.getWriter().print(isSuccess);

redis

模拟整个秒杀过程,但是是在没有并发情况下

	//秒杀过程
	public static boolean doSecKill(String uid,String prodid){
		//1、uid和proid非空判断
		if (uid==null||prodid==null)
			return false;
		//2、连接redis
		Jedis jedis =new Jedis("192.168.111.130",6379);
		//3、拼接key
		//库存key
		String kcKey="sk:"+prodid+":qt";
		//秒杀成功用户key
		String userKey="sk:"+prodid+":user";
		//4、获取库存,如果库存为null,秒杀还没开始
		String kcNums=jedis.get(kcKey);
		if (kcNums==null) {
			System.out.println("秒杀还未开始");
			jedis.close();
			return false;
		}
		//5、判断用户是否重复秒杀操作
		if (jedis.sismember(userKey,uid)){
			System.out.println("不能重复秒杀!!");
			jedis.close();
			return false;
		}
		//6、判断商品数量,库存小于1,秒杀结束
		kcNums=jedis.get(kcKey);
		if (Integer.parseInt(kcNums)<1){
			System.out.println("秒杀已经结束了!!");
			jedis.close();
			return false;
		}
		//7、秒杀过程:
		// 库存-1
		jedis.decr(kcKey);
		//添加秒杀成功的用户到清单里面
		jedis.sadd(userKey,uid);
		System.out.println("秒杀成功了");
		return true;
	}

在Centos7下安装模拟并发的软件

yum install httpd-tools

使用 ab -n 1000 -c 100 -p ~/postfile -T 'application/x-www-form-urlencoded' http://10.16.23.139:8080/Seckill/doseckill 模拟
解释:-n 1000 表示当前请求1000-c 当前 请求中的并发次数  就是在1000里面有多少个操作是并发的
		   -p表示请求方式为Post,~/postfile表示的是请求所需参数放置的当前目录的文件,通过vi postfile创建并填写内容即可(以key=value&key=value方式)
		   -T如果为Post或Put需设置这个类型
		   10.16.23.139为本机ip
		   Seckill/doseckill为自己的请求路径

模拟高并发后出现超卖情况,还有可能出现连接超时问题,因为redis不能同时处理很大量的数据

解决超卖情况和连接超时问题

  1. 创建连接池解决超时问题
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
				 	//基本配置  ip   redis端口号  连接超时时间
					jedisPool = new JedisPool(poolConfig, "192.168.111.130", 6379, 60000 );
				}
			}
		}
		return jedisPool;
	}

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

}
  1. 获取Jedis对象,从连接池获取
		//2、连接redis
//		Jedis jedis =new Jedis("192.168.111.130",6379);
		//通过连接池得到Jedis对象 解决连接超时问题
		JedisPool jedisPool=JedisPoolUtil.getJedisPoolInstance();
		Jedis jedis= jedisPool.getResource();
  1. 添加乐观锁(事务解决超卖)
    结合之前的redis代码
		//在进行对库存操作前进行监视库存
		jedis.watch(kcKey);
		//在秒杀成功时添加事务
		Transaction tx=jedis.multi();//获取事务对象
		// 组队
		tx.decr(kcKey);//库存-1
		tx.set(userKey,uid);//添加秒杀成功的用户到清单里面
		//执行
		List<Object> result=tx.exec();
		if (result.size() == 0) {
			System.out.println("秒杀失败了");
			jedis.close();
			return false;
		}
				System.out.println("秒杀成功了");
		jedis.close();
		return true;

加了乐观锁和连接池后的整个秒杀代码
这样会存在库存遗留

//秒杀过程
	public static boolean doSecKill(String uid,String prodid){
		//1、uid和proid非空判断
		if (uid==null||prodid==null)
			return false;
		//2、连接redis
//		Jedis jedis =new Jedis("192.168.111.130",6379);
		//通过连接池得到Jedis对象 解决连接超时问题
		JedisPool jedisPool=JedisPoolUtil.getJedisPoolInstance();
		Jedis jedis= jedisPool.getResource();


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

		//监视库存
		jedis.watch(kcKey);
		//4、获取库存,如果库存为null,秒杀还没开始
		String kcNums=jedis.get(kcKey);
		if (kcNums==null) {
			System.out.println("秒杀还未开始");
			jedis.close();
			return false;
		}
		//5、判断用户是否重复秒杀操作
		if (jedis.sismember(userKey,uid)){
			System.out.println("不能重复秒杀!!");
			jedis.close();
			return false;
		}
		//6、判断商品数量,库存小于1,秒杀结束
		kcNums=jedis.get(kcKey);
		if (Integer.parseInt(kcNums)<1){
			System.out.println("秒杀已经结束了!!");
			jedis.close();
			return false;
		}
		//添加事务
		Transaction tx=jedis.multi();
		// 组队
		tx.decr(kcKey);//库存-1
		tx.set(userKey,uid);//添加秒杀成功的用户到清单里面
		//执行
		List<Object> result=tx.exec();
		if (result.size() == 0) {
			System.out.println("秒杀失败了");
			jedis.close();
			return false;
		}
		//7、秒杀过程:
		// 库存-1
//		jedis.decr(kcKey);
		//添加秒杀成功的用户到清单里面
//		jedis.sadd(userKey,uid);
		System.out.println("秒杀成功了");
		jedis.close();
		return true;
	}

库存遗留问题

乐观锁会造成库存遗留的问题,但是redis不支持悲观锁,利用LUA来实现。
LUA脚本代码

	/**
	 * 拼接LUA脚本
	 */
	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" +
			//调用redis里面的sismember方法
			"local userExists=redis.call(\"sismember\",usersKey,userid);\r\n" + 
			"if tonumber(userExists)==1 then \r\n" +
			//约定 秒杀过的就返回2
			"   return 2;\r\n" + 
			"end\r\n" +
			//调用redis里面的get方法获取库存
			"local num= redis.call(\"get\" ,qtkey);\r\n" + 
			"if tonumber(num)<=0 then \r\n" +
			//约定 卖完了就返回0
			"   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" ;

悲观锁秒杀过程

/**
	 * 秒杀过程
	 * @param uid 用户id
	 * @param prodid 产品id
	 * @return 返回是否抢购成功   
	 */
	public static boolean doSecKill(String uid,String prodid) {
		//连接池获取Jedis对象
		JedisPool jedispool =  JedisPoolUtil.getJedisPoolInstance();
		Jedis jedis=jedispool.getResource();
		 //String sha1=  .secKillScript;
		//通过scriptLoad方法加载脚本 secKillScript
		String sha1=  jedis.scriptLoad(secKillScript);
		//通过evalsha方法加载,固定写法
		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;
	}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

syf_wfl

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值