redis随计——秒杀

秒杀案例

①用户提交:userId,prodId

//秒杀过程
 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);
 	
 	//3 拼接key
 	// 3.1 库存key
 	String kcKey = "sk:"+prodid+":qt";
 	// 3.2 秒杀成功用户key
 	String userKey = "sk:"+prodid+":user";

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

 	// 5 判断用户是否重复秒杀操作
 	if(jedis.sismember(userKey, uid)) {
 		System.out.println("已经秒杀成功了,不能重复秒杀");
 		jedis.close();
 		return false;
 	}

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

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

 	System.out.println("秒杀成功了..");
 	jedis.close();
 	return true;

③注意超卖、超时、库存遗留问题

并发模拟

①ab工具
②gum install httpd-tools
③-n:请求数;-c:请求中有多少是并发请求;-T:Post/put请求,要添加content-type,默认text/plain;-p:请求参数存放的文件(-u:put的)
eg:
ab -n 2000 -c 200 -k -p ~/postfile -T application/x-www-form-urlencoded http://ip:port/test

超卖和超时

①超时:请求过多导致的等待超时,可以通过连接池解决

		//2 通过连接池得到jedis对象,解决超时问题
		JedisPool jedisPoolInstance = JedisPoolUtil.getJedisPoolInstance();
		Jedis jedis = jedisPoolInstance.getResource();
		
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.44.168", 6379, 60000 );
				}
			}
		}
		return jedisPool;
	}

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

②超卖:并发的情况下会发现库存卖成负数了,使用事务,乐观锁解决

	//3.3 监视库存
	jedis.watch(kcKey);
	//7 7.1、7.2 使用事务,解决超卖问题
	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("秒杀失败了....");
		jedis.close();
		return false;
	}

?:使用事务时,在同批获得同一个库存的请求只有一个能成功的,所以秒杀是在进入付款页时就成功还是在付完款时?

库存问题

①乐观锁导致的库存遗留问题,版本号变更导致的同一批请求只有一个成功的
②悲观锁可以解决,但是redis本身不支持悲观锁,可以使用lua脚本达到类似效果
②LUA脚本(Redis 2.6以上支持),是一个小巧的嵌入式脚本语言,很多应用程序、游戏使用LUA来实现可配置性、可扩展性,包括魔兽争霸地图、魔兽世界等众多游戏插件或外挂。

LUA脚本是类似redis事务,有一定的原子性,不会被其他命令插队,可以完成一些redis事务性的操作。将复杂的或者多步的redis操作,写为一个脚本,一次提交给redis执行,减少反复连接redis的次数,提升性能。通过lua脚本解决争抢问题,实际上是redis 利用其单线程的特性,用任务队列的方式解决多任务并发问题。

public class SecKill_redisByScript {
	
	private static final 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='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=  .secKillScript;
		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;
	}
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值