秒杀案例描述
现在有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;
}
}
测试结果
库存遗留问题
当我们加大并发量再去测试时,可能会出现连接超时的问题
-
当连接数超过了redis的最大连接数,可能就会导致部分请求在等待,直到超时,这个我们可以写1个连接池解决问题。节省每次连接 redis 服务带来的消耗,把连接好的实例反复利用。通过参数管理连接的行为
-
当并发量过大,还有可能存在库存遗留问题
已经秒光,可是还有库存,原因:乐观锁导致很多请求都失败。先点的没秒到,后点的可能秒到了。
LUA脚本解决库存遗留问题
解决库存遗留问题,我们首先想到使用悲观锁,但在redis中不能直接使用悲观锁,所以使用LUA脚本
LUA 脚本在 Redis 中的优势
-
将复杂的或者多步的 redis 操作,写为一个脚本,一次提交给 redis 执行,减少反复连接 redis 的次数,提升性能。
-
LUA 脚本是类似 redis 事务,有一定的原子性,不会被其他命令插队,可以完成一些 redis 事务性的操作。
-
但是注意 redis 的 lua 脚本功能,只有在 Redis 2.6 以上的版本才可以使用。
-
利用 lua 脚本淘汰用户,解决超卖问题,redis 2.6 版本以后,通过 lua 脚本解决争抢问题,实际上是 redis 利用其单线程的特性,用任务队列的方式解决多任务并发问题。