Redis基础知识三

1.1 秒杀代码逻辑

秒杀逻辑:如果库存key为null,则秒杀未开始,接着判断用户是否重复秒杀,继续判断库存如果小于等于0则秒杀结束,然后使用事务组队,执行,校验事务执行结果对应的返回给用户。

1.2 秒杀可能存在的问题

/**
1、在并发场景下,连接超时的问题
2、在并发场景下,会存在超卖的问题。
3、在并发场景下,库存遗留问题
# -----------------------------解决方案-------------------------
1、使用JedisPool连接池很好的解决了连接超时问题
2、使用事务+乐观锁,可以解决超卖问题
3、使用lua脚本,可以解决库存遗留问题

----------------乐观锁解释-------------
在并发场景下:例如现在连接池最大设置的500个连接,那并发场景下500个人获取到连接开始事务组队,假如第一个人提交了事务,修改了key的版本号,其他499个人提交事务的时候发现版本号被修改,则不会提交事务,那这499个人都不会秒杀成功。所以会造成库存遗留问题。2000个请求进来,300个库存,可能只卖了150个还不到。(具体数值只是简单举例哈)
*/

1.3 代码演示

	//秒杀过程
	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对象
		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();
			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 秒杀过程
		//使用事务
		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;
		}
		System.out.println("秒杀成功了..");
		jedis.close();
		return true;
	}

1.4 并发测试

使用ab并发测试工具: 1、安装 yum install httpd-tools 2、测试案例:

/**
ab -n 1000 -c 100 -p ./postfile -T 'application/x-www-form-urlencoded' http://172.24.0.66:8080/doseckill
# -n 1000表示1000个请求
# -c 100 表示并发数是100
# -p ./profile 表述请求参数所在的文件
# -T 'application/x-www-form-urlencoded'  post或者get请求需要加这个参数
后面接着请求的路径
*/

1.5 解决库存遗留问题

1.5.1 lua脚本了解

Lua 是一个小巧的脚本语言,Lua脚本可以很容易的被C/C++ 代码调用,也可以反过来调用C/C++的函数,Lua并没有提供强大的库,一个完整的Lua解释器不过200k,所以Lua不适合作为开发独立应用程序的语言,而是作为嵌入式脚本语言。 很多应用程序、游戏使用LUA作为自己的嵌入式脚本语言,以此来实现可配置性、可扩展性。 这其中包括魔兽争霸地图、魔兽世界、博德之门、愤怒的小鸟等众多游戏插件或外挂。 https://www.w3cschool.cn/lua/

1.5.2 lua脚本了解

将复杂的或者多步的redis操作,写为一个脚本,一次提交给redis执行,减少反复连接redis的次数。提升性能。LUA脚本是类似redis事务,有一定的原子性,不会被其他命令插队,可以完成一些redis事务性的操作。但是注意redis的lua脚本功能,只有在Redis 2.6以上的版本才可以使用。利用lua脚本淘汰用户,解决超卖问题。

redis 2.6版本以后,通过lua脚本解决争抢问题,实际上是redis 利用其单线程的特性,用任务队列的方式解决多任务并发问题.

1.5.3 代码演示

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");
	}
	
	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=  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;
	}
}
end
  • 作者:旭仔(联系作者)
  • 发表时间:2022-06-05 00:07
  • 版权声明:自由转载-非商用-非衍生-保持署名
  • 转载声明:如果是转载栈主转载的文章,请附上原文链接
  • 公众号转载:请在文末添加作者公众号二维码(公众号二维码见右边,欢迎关注)
  • 评论