• 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;
        }
    }
    

      

  • 相关阅读:
    Flex4中panel拖拽
    jquery两边飘浮的对联广告
    javascript 无刷新上传图片之原理
    第十二周--servlet做一个逻辑处理!!!!!!!!!!!!!实现登录
    第十一周--邮件系统补充一个注册一个登陆验证码
    第十周--邮件系统全套(第二版)
    第九周--邮件系统2(全套增删改查)
    第八周-邮件系统1
    第七周JSP增删改查
    JSP第六周 还是session
  • 原文地址:https://www.cnblogs.com/zjtao/p/15814035.html
Copyright © 2020-2023  润新知