基於reids的秒殺系統

  主要思想還是限流。秒殺商品有開始時間和結束時間,庫存可以看成是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;
        }
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章