redis(11)事务秒杀案例

秒杀案例描述

现在有1个秒杀的功能,1个原来价值5000元的手机现在搞活动,降价到1块钱,做秒杀活动。库存就10个,假设有10000人抢购。

目前逻辑是:抢到了商品库存就减1,然后把用户id加入到秒杀成功者清单中

Redis 事务(模拟秒杀并发)

模拟我们使用工具 ab 模拟测试:
CentOS6 默认安装

CentOS7 需要手动安装: yum install httpd-tools

执行以下命令

ab -n 2000 -c 200 -k -p ~/postfile -T application/x-www-form-urlencoded

postfile是1个我们自定义的文件,内容如下

prodid=0101&  # 以 & 号结尾,存放当前目录

 

超卖问题

秒杀的结果如下图所示:

明明10个名额抢完了,还提示秒杀成功,且redis中库存显示不为0

使用redis乐观锁解决超卖问题

利用乐观锁淘汰用户,解决超卖问题。

具体代码如下:

public class SecKill_redis {

	public static void main(String[] args) {
		Jedis jedis =new Jedis("192.168.44.168",6379);
		System.out.println(jedis.ping());
		jedis.close();
	}

	//秒杀过程
	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;
		}

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

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

测试结果

 

库存遗留问题

当我们加大并发量再去测试时,可能会出现连接超时的问题

  1. 当连接数超过了redis的最大连接数,可能就会导致部分请求在等待,直到超时,这个我们可以写1个连接池解决问题。节省每次连接 redis 服务带来的消耗,把连接好的实例反复利用。通过参数管理连接的行为

  2. 当并发量过大,还有可能存在库存遗留问题

已经秒光,可是还有库存,原因:乐观锁导致很多请求都失败。先点的没秒到,后点的可能秒到了。
 

LUA脚本解决库存遗留问题

解决库存遗留问题,我们首先想到使用悲观锁,但在redis中不能直接使用悲观锁,所以使用LUA脚本

LUA 脚本在 Redis 中的优势

  • 将复杂的或者多步的 redis 操作,写为一个脚本,一次提交给 redis 执行,减少反复连接 redis 的次数,提升性能。

  • LUA 脚本是类似 redis 事务,有一定的原子性,不会被其他命令插队,可以完成一些 redis 事务性的操作。

  • 但是注意 redis 的 lua 脚本功能,只有在 Redis 2.6 以上的版本才可以使用。

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

posted @ 2023-02-26 15:06  Silent丿丶黑羽  阅读(75)  评论(0编辑  收藏  举报