超卖,即在并发的情况下,所售商品数量大于商品的库存数量。在并发量大的情况下,用户请求同时到达,对数据库进行操作,在没有采取相应的处理的情况时从而导致出现超卖现象。
在redis中放入十件商品
使用20个线程抢商品
public void test () {
ExecutorService service = Executors.newFixedThreadPool(20);
for (int i = 0; i < 20; i++) {
int finalI = i;
service.execute(new Runnable() {
@Override
public void run() {
if (Integer.parseInt(String.valueOf(redisUtils.get("test"))) > 0) {
int execute = Integer.parseInt(String.valueOf(redisUtils.decr("test", 1)));
if (execute != 0) {
log.info("线程" + finalI + "抢到了商品!!!");
} else {
log.info("线程" + finalI + "未抢到商品");
}
} else {
log.info("商品数量不足");
}
}
});
}
}
运行代码发现已经超出了十个人抢到了商品。
此时redis的存值已经变为了负数,出现了超卖的情况。
public void test_lua () {
StringBuilder sb = new StringBuilder();
sb.append("if (redis.call('exists', KEYS[1]) == 1) then"); // 判断key是否存在
sb.append(" local stock = tonumber(redis.call('get', KEYS[1]));"); // 获取锁
sb.append(" if (stock == -1) then");
sb.append(" return 1;");
sb.append(" end;");
sb.append(" if (stock > 0) then");
sb.append(" redis.call('decrby', KEYS[1], 1);"); // 商品数量减1
sb.append(" return stock;");
sb.append(" end;");
sb.append(" return 0;");
sb.append("end;");
sb.append("return -1;");
String STOCK_LUA = sb.toString();
DefaultRedisScript<Long> objectDefaultRedisScript = new DefaultRedisScript<>();
objectDefaultRedisScript.setScriptText(STOCK_LUA);
objectDefaultRedisScript.setResultType(Long.class);
ArrayList<String> keys = new ArrayList<>(); // 脚本中的KEYS参数
keys.add("test");
ExecutorService service = Executors.newFixedThreadPool(20);
for (int i = 0; i < 20; i++) {
int finalI = i;
service.execute(new Runnable() {
@Override
public void run() {
int execute = Integer.parseInt(redisTemplate.execute(objectDefaultRedisScript, keys).toString());
if (execute != 0) {
log.info("线程" + finalI + "抢到了商品!!!");
} else {
log.info("线程" + finalI + "未抢到商品");
}
}
});
}
}
依然使用20个线程抢商品,运行代码只有10个线程抢到了商品
此时redis中的存值为0