Redis 之 秒杀案例

        通过 redis 的 事务 和 锁机制 完成秒杀案例

一、 Redis 事务

        redis 事务:串联多个命令执行,防止插队

        命令:multi(命令组队)、exec(命令执行)、discard(放弃组队)    

Redis 之 秒杀案例_第1张图片

        事务的错误处理:

        (1)组队中某个命令出错,执行时,整个队列会被取消

Redis 之 秒杀案例_第2张图片

Redis 之 秒杀案例_第3张图片

         (2)执行时,队列中某个命令出错,其他指令继续执行,整体不回回滚,即不保证原子性

Redis 之 秒杀案例_第4张图片

Redis 之 秒杀案例_第5张图片

        Redis 乐观锁 -- watch

        在执行 multi 之前,可以通过 watch key1 [key2],监视多个 key ,如果在事务执行之前这些 key 被改动,则该事务将被打断

Redis 之 秒杀案例_第6张图片

         Redis 事务特性

         (1) 单独的隔离操作、(2)没有隔离级别概念、(3)不保证原子性

二、 秒杀案例

2.1 第一阶段

public class SecKill_redis {

	public static void main(String[] args) {

	}

	//秒杀过程
	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("xxx.xxx.xxx.xxx",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;
	}
}

        出现问题 :(1)超卖,即秒杀成功的数量大于库存数量,最总库存呈负值

                           (2)连接超时,大量连接请求打到 redis 上,需要进行等待,但等待时间过长出现超时

        解决方案 : (1)超卖 --> 使用事务

                            (2)连接超时 --> 使用连接池

2.2 第二阶段

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.setMaxActive(1000);
					poolConfig.setMaxIdle(32);
					poolConfig.setMaxWait(100*1000);
					poolConfig.setTestOnBorrow(true);

					jedisPool = new JedisPool(poolConfig,"xxx.xxx.xxx.xxx",6379);
				}
			}
		}
		return jedisPool;
	}

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

Redis 之 秒杀案例_第7张图片

Redis 之 秒杀案例_第8张图片 

 Redis 之 秒杀案例_第9张图片

        出现问题 库存遗留,即虽然并发秒杀的请求有很多,但由于乐观锁机制,导致大量同时并发的请求失败,最终库存仍有剩余

        解决方案 : 使用 LUA 脚本

2.3 第三阶段

local userid=KEYS[1]; 
local prodid=KEYS[2];
local qtkey="sk:"..prodid..":qt";
local usersKey="sk:"..prodid..":usr'; 
local userExists=redis.call("sismember",usersKey,userid);
if tonumber(userExists)==1 then 
  return 2;
end
local num= redis.call("get" ,qtkey);
if tonumber(num)<=0 then 
  return 0; 
else 
  redis.call("decr",qtkey);
  redis.call("sadd",usersKey,userid);
end
return 1;
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" ;

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;
}

你可能感兴趣的:(Redis,redis)