基于reids的秒杀系统

版权声明:本文为博主原创文章,未经博主允许不得转载。 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;
        }
    }
}

猜你喜欢

转载自blog.csdn.net/u011411993/article/details/81613242