zoukankan      html  css  js  c++  java
  • springboot项目:Redis分布式锁的使用(模拟秒杀系统)

    模拟秒杀系统:

    第一步:编写Service

    package com.payease.service;
    
    /**
     * liuxiaoming
     * 2017-12-14
     */
    public interface SecKillService {
        /**
         * 查询秒杀活动特价商品的信息
         *
         * @param productId
         * @return
         */
        String querySecKillProductInfo(String productId);
    
        /**
         * 模拟不同用户秒杀同一商品的请求
         *
         * @param productId
         * @return
         */
        void orderProductMockDiffUser(String productId);
    }

    第二步:编写Redis加锁解锁工具类

    package com.payease.service;
    
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.redis.core.StringRedisTemplate;
    import org.springframework.stereotype.Component;
    import org.springframework.util.StringUtils;
    
    /**
     * liuxiaoming
     * 2017-12-14
     */
    @Component
    @Slf4j
    public class RedisLock {
    
        @Autowired
        private StringRedisTemplate stringRedisTemplate;
    
        /**
         * 加锁
         * @param key
         * @param value 当前时间+超时时间
         * @return
         */
        public boolean lock(String key, String value) {
            if (stringRedisTemplate.opsForValue().setIfAbsent(key, value)) {
                return true;
            }
            //currentValue=A 这两个线程的value都是B 其中一个线程拿到锁
            String currentValue = stringRedisTemplate.opsForValue().get(key);
            //如果锁过期
            if (!StringUtils.isEmpty(currentValue) && Long.parseLong(currentValue) < System.currentTimeMillis()) {
                //获取上一个锁的时间
                String oldValue = stringRedisTemplate.opsForValue().getAndSet(key, value);
                if (!StringUtils.isEmpty(oldValue) && oldValue.equals(currentValue)) {
                    return true;
                }
            }
            return false;
        }
    
        /**
         * 解锁
         * @param key
         * @param value
         * @return
         */
        public void unlock(String key, String value) {
            String currentVaule = stringRedisTemplate.opsForValue().get(key);
            try {
                if (!StringUtils.isEmpty(currentVaule) && currentVaule.equals(value)) {
                    stringRedisTemplate.opsForValue().getOperations().delete(key);
                }
            } catch (Exception e) {
                log.error("【redis分布式锁】解锁异常,{}" , e);
            }
    
        }
    }

    第三步:编写Service实现类

    package com.payease.service.impl;
    
    
    import com.payease.exception.SellException;
    import com.payease.service.RedisLock;
    import com.payease.service.SecKillService;
    import com.payease.utils.KeyUtil;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * liuxiaoming
     * 1027-12-14
     */
    @Service
    public class SecKillServiceImpl implements SecKillService {
    
        @Autowired
        private RedisLock redisLock;
    
    
        private static final int TIMOUT = 10 * 1000; //超时时间 10秒
    
        /**
         * 中秋活动 秒杀月饼 限量100000
         */
        static Map<String, Integer> products;
        static Map<String, Integer> stock;
        static Map<String, String> orders;
    
        static {
            products = new HashMap<>();
            stock = new HashMap<>();
            orders = new HashMap<>();
            products.put("abc123456", 10000);
            stock.put("abc123456", 10000);
        }
    
        private String queryMap(String productId) {
            return "中秋活动,月饼特价,限量份"
                    + products.get(productId)
                    + " 还剩:" + stock.get(productId) + " 份"
                    + " 该商品成功下单用户数目:"
                    + orders.size() + " 人";
        }
    
    
        @Override
        public String querySecKillProductInfo(String productId) {
            return queryMap(productId);
        }
    
        /**
         * 描述逻辑
         *
         * @param productId
         */
        @Override
        public void orderProductMockDiffUser(String productId) {
    
            //加锁
            long time = System.currentTimeMillis() + TIMOUT;
            if (!redisLock.lock(productId, String.valueOf(time))) {
                throw new SellException(110, "没抢到,换个姿势再试一遍呀");
            }
            //1. 查询该商品库存,为0则活动结束。
            int stockNum = stock.get(productId);
            if (stockNum == 0) {
                //库存不足
                throw new SellException(100, "活动已经结束,请留意下次活动");
            } else {
    
                orders.put(KeyUtil.getUniqueKey(), productId);
                stockNum = stockNum - 1;
                try {
                    Thread.sleep(100);
                } catch (InterruptedException ex) {
                    ex.printStackTrace();
                }
                stock.put(productId, stockNum);
            }
    
            //解锁
            redisLock.unlock(productId, String.valueOf(time));
        }
    
    
        /*@Override
        public synchronized void  orderProductMockDiffUser(String productId) {
            int stockNum = stock.get(productId);
            if (stockNum == 0) {
                //库存不足
                throw new SellException(100, "活动已经结束,请留意下次活动");
            } else {
    
                orders.put(KeyUtil.genUniqueKey(), productId);
                stockNum = stockNum - 1;
                try {
                    Thread.sleep(100);
                } catch (InterruptedException ex) {
                    ex.printStackTrace();
                }
                stock.put(productId, stockNum);
            }
    
        }*/
    }

    第四步:编写controller

    package com.payease.controller;
    
    import com.payease.service.SecKillService;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    @RequestMapping("/skill")
    @Slf4j
    public class SecKillController {
    
        @Autowired
        private SecKillService secKillService;
    
    
    
        /**
         * 查询秒杀活动特价商品的信息
         * @param productId
         * @return
         */
        @GetMapping("/query/{productId}")
        public String query(@PathVariable String productId)throws Exception
        {
            return secKillService.querySecKillProductInfo(productId);
        }
    
    
        /**
         * 秒杀,没有抢到获得"哎呦喂,xxxxx",抢到了会返回剩余的库存量
         * @param productId
         * @return
         * @throws Exception
         */
        @GetMapping("/order/{productId}")
        public String skill(@PathVariable String productId)throws Exception
        {
            log.info("@skill request, productId:" + productId);
            secKillService.orderProductMockDiffUser(productId);
            return secKillService.querySecKillProductInfo(productId);
        }
    }

    第五步:启动项目 查看浏览器 进行压测

    1.查看秒杀情况 http://127.0.0.1:8080/sell/skill/query/abc123456 

    2. 在Mac终端输入命令: ab -n 500 -c 100 http://127.0.0.1:8080/sell/skill/order/abc123456
    进行压测

    3.查看浏览器 http://127.0.0.1:8080/sell/skill/query/abc123456

    注:

    压测模拟并发:

  • 相关阅读:
    Chrome触发唤起IE, 注册唤起程序
    .net post请求过长 , 超过配置 maxQueryStringLength值
    eclipse 初探踩坑实录
    eslint 报error
    前端3小时配置空白机环境
    sql语句
    maven3.04管理jetty9.2.10启动web项目
    Oracle日期时间
    AngularJS向指令传递数据
    jetty和tomcat启动项目
  • 原文地址:https://www.cnblogs.com/liuxiaoming123/p/8037065.html
Copyright © 2011-2022 走看看