目录
实现一个基础的秒杀逻辑
1.扣减库存
2.记录秒杀成功人员
java
public static boolean doSecKill(String uid, String prodid) {
// 拼接向Redis中保存库存和秒杀成功人员名单的key
String kcKey = "sk:" + prodid + ":qt";
String userKey = "sk:" + prodid + "usr";
// 创建Jedis对象
Jedis jedis = new Jedis("8.160.170.127", 6379);
// 获取库存
String count = jedis.get(kcKey);
// 11判断库存的数量
if ("0".equals(count)) {
System.err.println("秒杀结束");
return false;
}
// 扣库存
jedis.decr(kcKey);
// 添加人员记录
jedis.sadd(userKey, uid);
System.out.println(uid + ": 秒杀成功");
return true;
}
验证秒杀逻辑
需要使用ab工具模拟并发

安装ab工具
使用如下命令进行安装
yum -y install httpd-tools
安装完成
启动项目
java -jar -d seckill-demo-0.0.1-SNAPSHOT.jar
准备ab测试命令
准备testfile
ab指定文件内容
ab -n 2000 -c 200 -k -p testfile -T application/x-www-form-urlencoded http://localhost:8080/seckill
或
ab -n 2000 -c 200 -k -p testfile -T application/x-www-form-urlencoded http://yourIP:8080/seckill
ab命令参数说明
-n 2000:总请求数,一共发送 2000 次请求-c 200:并发数,同时 200 个并发连接(模拟 200 个用户同时访问)-k:开启 HTTP KeepAlive,复用 TCP 连接,减少建立连接开销,更贴近真实浏览器行为-p testfile:POST 请求,从 testfile 文件读取 POST 参数 ,文件内就是表单参数,例如seckillId=1001&userId=123-T application/x-www-form-urlencoded:POST 请求的Content-Type,表单提交格式(和-p配套使用)http://localhost:8080/seckill:压测目标秒杀接口地址
第一次验证


执行完后,查看库存

出现了超卖问题
使用redis事务解决
java
public static boolean doSecKill2(String uid, String prodid) {
// 拼接向Redis中保存库存和秒杀成功人员名单的key
String kcKey = "sk:" + prodid + ":qt";
String userKey = "sk:" + prodid + ":usr";
// 创建Jedis对象
Jedis jedis = new Jedis("8.160.170.127", 6379);
// // 获取库存
// String count = jedis.get(kcKey);
//增加乐观锁
jedis.watch(kcKey);
//3.判断库存
String kcKeystr = jedis.get(kcKey);
if (kcKeystr == null || "".equals(kcKeystr.trim())) {
System.out.println("未初始化库存");
jedis.close();
return false;
}
int kc = Integer.parseInt(kcKeystr);
if (kc <= 0) {
System.err.println("已经秒光。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。");
jedis.close();
return false;
}
//增加事务
Transaction multi = jedis.multi();
// 扣库存
// jedis.decr(kcKey);
multi.decr(kcKey);
// 添加人员记录
// jedis.sadd(userKey, uid);
multi.sadd(userKey, uid);
//执行事务
List<Object> list = multi.exec();
//判断事务提交是否失败
if (list == null || list.size() == 0) {
System.out.println("秒杀失败");
jedis.close();
return false;
}
System.out.println(uid + ": 秒杀成功");
jedis.close();
return true;
}
设置库存为100

第二次验证
重新使用ab模拟并发


查看库存结果

出现了库存遗留问题
使用lua脚本解决
lua脚本
java
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";
业务代码
java
public static boolean doSecKill(String uid, String prodid) {
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;
}
第三次最终验证成功
库存恢复100,再次使用ab调用,查看redis库存,正常了




