版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u011411993/article/details/81613242
主要思想还是限流。秒杀商品有开始时间和结束时间,库存可以看成是token,所以本质上还是一个基于令牌桶限流的变种场景。每个限流的单位时间不是1秒,而是秒杀活动持续的时间长度,库存看作是的单位时间加入到令牌桶的令牌数。和令牌桶唯一的区别是秒杀只有一个单位时间内有令牌。
import redis.clients.jedis.Jedis;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.Arrays;
import java.util.Collections;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
public class Kill {
private Jedis client;
private static final String START_TIME_KEY = "start_time";
private static final AtomicInteger count = new AtomicInteger();
private static ConcurrentHashMap<Long, Goods> goodsMap = new ConcurrentHashMap<>();
public Kill() {
client = new Jedis("192.168.31.206");
}
// 准备,参与秒杀的商品入redis
private void addGoods(Goods goods) {
goodsMap.put(goods.getId(), goods);
client.hset(START_TIME_KEY, String.valueOf(goods.getId()), String.valueOf(goods.getStartTime().toEpochSecond(ZoneOffset.ofHours(8))));
client.set(goods.getId() + ":" + goods.getStartTime().toEpochSecond(ZoneOffset.ofHours(8)), String.valueOf(goods.getStock()));
}
/**
* @param goodsId
* @param permits
* @return 1为活动未开始,2为正常购买,3为库存不足,4为卖光了,5为活动已结束
*/
private Integer buy(long goodsId, int permits) {
assert permits > 0;
Long startTime = Long.valueOf(client.hget(START_TIME_KEY, goodsId + ""));
long current = System.currentTimeMillis() / 1000;
long difference = current - startTime;
long duration = goodsMap.get(goodsId).getDuration().toMillis() / 1000;
long time = (long) (startTime + Math.floor(difference / (duration * 1.0)) * duration);
String script = "local count=redis.call('get',KEYS[1])\n" +
"if type(count) == 'boolean' then\n" +
" return -1;\n" +
"end\n" +
"count=tonumber(count)\n" +
"if count-ARGV[1]>=0 then\n" +
" redis.call('decrBy',KEYS[1],ARGV[1])\n" +
" return 1\n" +
"end\n" +
"return 0";
Long count = (Long) client.eval(script, Collections.singletonList(goodsId + ":" + time), Collections.singletonList(permits + ""));
if (count < 0) {
if (current < startTime) {
return 1;
} else {
return 5;
}
} else if (count == 0) {
if (permits > 1) {
return 3;
} else {
return 4;
}
} else {
return 2;
}
}
public void refund(long goodsId, int permits) {
Long startTime = Long.valueOf(client.hget(START_TIME_KEY, goodsId + ""));
client.incrBy(goodsId + ":" + startTime, permits);
}
// 模拟秒杀过程
public static void main(String[] args) throws InterruptedException {
Kill kill1 = new Kill();
kill1.addGoods(new Goods(123L, 100, LocalDateTime.now(ZoneOffset.ofHours(8)), Duration.ofDays(1L)));
CountDownLatch countDownLatch = new CountDownLatch(100);
for (int j = 0; j < 100; j++) {
Random random = new Random();
new Thread(() -> {
Kill kill = new Kill();
int permits = 1 + random.nextInt(9);
Integer buy = kill.buy(123L, permits);
if (buy == 2) {
count.addAndGet(permits);
}
Arrays.stream(Flag.values()).filter(flag -> flag.value.equals(buy)).findFirst().ifPresent(flag -> System.out.println(flag.desc));
countDownLatch.countDown();
}).start();
}
countDownLatch.await();
}
enum Flag {
_1(1, "活动未开始"), _2(2, "正常买"), _3(3, "库存不足"), _4(4, "卖光了"), _5(5, "活动已结束");
private Integer value;
private String desc;
Flag(Integer value, String desc) {
this.value = value;
this.desc = desc;
}
}
static class Goods {
private Long id;
private Integer stock;
private LocalDateTime startTime;
private Duration duration;
public Goods(Long id, Integer stock, LocalDateTime startTime, Duration duration) {
this.id = id;
this.stock = stock;
this.startTime = startTime;
this.duration = duration;
}
public Long getId() {
return id;
}
public Integer getStock() {
return stock;
}
public LocalDateTime getStartTime() {
return startTime;
}
public Duration getDuration() {
return duration;
}
}
}