spring boot redis 商品秒杀设计

商品秒杀多数发生在高并发的时候,本文利用redis单线程原子性解决并发,减库存;

思路方案:

1. 模拟100个用户下单抢10个商品;

2.使用redis加锁,来实现减去库存;

3.其他用户一直等待,直到解锁后后面用户再加锁减库存,依次操作,直到库存为0;

4.用户等待,设置一下超时时间,防止一直等待下去;

5.判断库存是否小于等于0;

 

其中超时代码中有不要使用System.currentTimeMillis,高并发下性能很差,解决方案写了一个定时任务,1毫米间隔定时执行,然后去获取,代码SystemTimeUtil.now;

1.自定义SystemTimeUtil类,实现获取当前时间戳

package shop.seckill.example.utils;

import org.apache.commons.lang3.time.DateFormatUtils;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;

/**
 * @author IT006448
 * @date 2022/01/17
 */
public class SystemTimeUtil {
    private final int period;

    private final AtomicLong now;

    private static class InstanceHolder {
        private static final SystemTimeUtil INSTANCE = new SystemTimeUtil(1);
    }

    //定时任务设置1毫秒
    private SystemTimeUtil(int period) {
        this.period = period;
        this.now = new AtomicLong(System.currentTimeMillis());
        scheduleClockUpdating();
    }

    private static SystemTimeUtil instance() {
        return InstanceHolder.INSTANCE;
    }

    private void scheduleClockUpdating() {
        //周期执行线程池
        ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(runnable -> {
            Thread thread = new Thread(runnable, "System Clock");
            //守护线程
            thread.setDaemon(true);
            return thread;
        });
        //任务,开始时间,间隔时间=周期执行,时间单位
        scheduler.scheduleAtFixedRate(() -> now.set(System.currentTimeMillis()), 0, period, TimeUnit.MILLISECONDS);
    }

    private long currentTimeMillis() {
        return now.get();
    }

    /**
     * 用来替换原来的System.currentTimeMillis()
     */
    public static long now() {
        return instance().currentTimeMillis();
    }

    public static String nowTime() {
        long now = now();
        return DateFormatUtils.format(now, "yyyy-MM-dd hh:mm:ss");
    }

    public static void main(String[] args) throws InterruptedException {
        String now = SystemTimeUtil.nowTime();
        System.out.println(now);
        Thread.sleep(1000);
        now = SystemTimeUtil.nowTime();
        System.out.println(now);
        Thread.sleep(2000);
        now = SystemTimeUtil.nowTime();
        System.out.println(now);
    }
}

 

2.秒杀核心

创建一个秒杀服务接口

public interface SecKillService {
    void buy(String goodsId, String userId);
}

 

package shop.seckill.example.service.imp;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import scf.frame.starter.cache.redis.JedisUtil;
import shop.seckill.example.service.SecKillService;
import shop.seckill.example.utils.SystemTimeUtil;

import java.util.Date;
import java.util.Map;

/**
 * @author IT006448
 * @date 2022/01/14
 */
@Service
@Slf4j
public class SecKillServiceImpl implements SecKillService {
    @Autowired
    private JedisUtil jedisUtil;
    public static Map<String, Integer> inventoryMap = Maps.newLinkedHashMap();

    {
        inventoryMap.put("10001", 10);
    }


    @Override
    public void buy(String goodsId, String userId) {
        String key = "SECKILL_USER_".concat(goodsId);

        //用户开抢时间
        //long startTime = System.currentTimeMillis();
        //System.currentTimeMillis并发性能太差,用定时任务去获取,性能更好
        long startTime = SystemTimeUtil.now();
        int timeout = 10000;
        //未抢到的情况下,10秒内继续获取锁
        while ((startTime + timeout) >= SystemTimeUtil.now()) {
            String strInventory = jedisUtil.getStr(goodsId + "_INVENTORY_NUM");
            //商品是否剩余
            System.out.println(getDate() + " 库存数量:" + strInventory);
            if (strInventory != null && Integer.parseInt(strInventory) <= 0) {
                System.out.println("商品没有剩余了,跳出");
                break;
            }

            //获取锁
            boolean lock = jedisUtil.setIfAbsent(key, userId);
            if (lock) {
                //加锁成功
                try {
                    jedisUtil.expire(key, 60);
                    parallelBuy(goodsId, userId);
                    return;
                } finally {
                    jedisUtil.del(key);
                }
            } else {
                System.out.println(String.format("用户:%s锁了,等待买", userId));
            }
        }
    }

    public void parallelBuy(String goodsId, String userId) {
        Integer inventory = inventoryMap.get(goodsId);
        try {
            //模拟减去库存等操作耗时
            Thread.sleep(1000);
        } catch (Exception e) {
        }
        if (inventory <= 0) {
            System.out.println("商品已经卖完");
            return;
            //throw new RuntimeException("商品:".concat(goodsId).concat("已经卖完"));
        }
        --inventory;
        jedisUtil.set(goodsId + "_INVENTORY_NUM", inventory.toString(), 60);
        System.out.println(getDate() + " 还剩下:".concat(inventory.toString()));
        System.out.println(String.format(getDate() + " 用户:%s买了一件", userId));
        inventoryMap.put(goodsId, inventory);
    }

    public String getDate() {
        return JSON.toJSONString(new Date(), SerializerFeature.UseISO8601DateFormat);
    }
}

3.模拟创建100个随机用户,并并发执行

package shop.seckill.example.service.imp;

import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StopWatch;
import shop.seckill.example.service.SecKillService;

import java.util.LinkedList;
import java.util.List;
import java.util.stream.IntStream;

/**
 * @author IT006448
 * @date 2022/01/14
 */
@Service
@Slf4j
public class ShopServiceImpl implements ShopService {
    @Autowired
    private SecKillService secKillService;

    @Override
    public void buy() {
        List<String> user = createUser();
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        log.info("用户:{}", JSON.toJSONString(user));
        user.parallelStream().forEach(x -> {
            secKillService.buy("10001", x);
        });
        stopWatch.stop();

        double totalTimeSeconds = stopWatch.getTotalTimeSeconds();
        System.out.println("运行总时间:" + totalTimeSeconds);
        System.out.println("库存数量:" + SecKillServiceImpl.inventoryMap.get("10001"));

    }

    public List<String> createUser() {
        List<String> user = new LinkedList<>();
        IntStream.range(0, 100).parallel().forEach(x -> {
            user.add("用户" + x);
        });
        return user;
    }
}

  

 

posted @ 2022-01-17 16:38  归去如风  阅读(287)  评论(0编辑  收藏  举报