• ▶【SecKill】U6 接口优化


    ▶【SecKill】U6 接口优化

    一、集成RabbitMQ

    1、安装erlang(一种通用的面向并发的编程语言,可以应对大规模并发活动的编程语言和运行环境)

    (1)下载Erlang安装包

    https://github.com/rabbitmq/rabbitmq-server/releases/tag/v3.9.13

    (2)安装包上传到服务器tmp目录下,进入到tmp目录进行安装

    cd /tmp
    mkdir -p /usr/local/erlang
    tar -xzvf otp_src_24.2.1.tar.gz
    cd otp_src_24.2.1
    ./configure --prefix=/usr/local/erlang --with-ssl --enable-threads --enable-smp-support --enable-kernel-poll --enable-hipe --without-javac
    make -j8
    make install

    (3)设置环境变量

    vim /etc/profile

    在末尾加入以下内容

    #set erlang environment
    export PATH=$PATH:/usr/local/erlang/bin

    (4)使环境变量生效

    source /etc/profile

    (5)验证是否安装成功

    erl -version

     安装成功!

    2、安装RabbitMQ

    (1)环境准备:阿里云centos8.4服务器

    cat /etc/redhat-release

    (2)安装RabbitMQ依赖包

    yum install -y gcc gcc-c++ glibc-devel make ncurses-devel openssl-devel autoconf

    (3)安装Erlang

    RabbitMQ和Erlang / OTP兼容性列表

    下表提供了当前支持的RabbitMQ版本系列的Erlang兼容性列表。对于已到期的RabbitMQ版本,请参阅不支持的系列兼容性列表。

    下载源文件:

    wget http://erlang.org/download/otp_src_23.1.tar.gz

    (4)解压

    tar -zxvf otp_src_23.1.tar.gz

    (5)进入otp_src_23.1,安装

    cd otp_src_23.1
     
    ./otp_build autoconf
     
    ./configure && make && make install

    安装成功!

    (6)查看是否安装成功

    erl -v

    (7)安装Socat

    yum install -y socat

    (8)安装RabbitMQ

    rpm -Uvh rabbitmq-server-3.8.9-1.el7.noarch.rpm --nodeps

    (9)启动RabbitMQ

    启用Rabbit MQ服务

    systemctl enable rabbitmq-server

    启动Rabbit MQ服务

    systemctl start rabbitmq-server

    查看服务状态

    systemctl status rabbitmq-server

    (10)Web插件安装

    rabbitmq-plugins enable rabbitmq_management

    3、SpringBoot集成RabbitMQ

    (1)添加依赖

    <!-- 11.添加RabbitMQ依赖 -->
    <dependency>  
        <groupId>org.springframework.boot</groupId>  
        <artifactId>spring-boot-starter-amqp</artifactId>  
    </dependency>

    (2)添加配置信息

    #rabbitmq
    spring.rabbitmq.host=192.168.xx.xx
    spring.rabbitmq.port=5672
    spring.rabbitmq.username=guest
    spring.rabbitmq.password=guest
    spring.rabbitmq.virtual-host=/
    spring.rabbitmq.listener.simple.concurrency= 10
    spring.rabbitmq.listener.simple.max-concurrency= 10
    spring.rabbitmq.listener.simple.prefetch= 1
    spring.rabbitmq.listener.simple.auto-startup=true
    spring.rabbitmq.listener.simple.default-requeue-rejected= true
    spring.rabbitmq.template.retry.enabled=true
    spring.rabbitmq.template.retry.initial-interval=1000
    spring.rabbitmq.template.retry.max-attempts=3
    spring.rabbitmq.template.retry.max-interval=10000
    spring.rabbitmq.template.retry.multiplier=1.0

    (3)RabbitMQ的4种交换机模式

    A. Direct Exchange:任何发送到Direct Exchange的消息都会被转发到RouteKey中指定的Queue

    ① com.kirin.miaosha.rabbitmq / MQSender.java:消息发送器

    package com.kirin.miaosha.rabbitmq;//消息发送器
    @Service
    public class MQSender {
    
        private static Logger log = LoggerFactory.getLogger(MQSender.class);
        
        @Autowired
        AmqpTemplate amqpTemplate ;
    
        public void send(Object message) {
            String msg = RedisService.beanToString(message);
            log.info("send message:"+msg);
            amqpTemplate.convertAndSend(MQConfig.QUEUE, msg);
        }
    }

    ② com.kirin.miaosha.rabbitmq / MQReceiver.java:消息接受器

    package com.kirin.miaosha.rabbitmq;//消息接受器
    @Service
    public class MQReceiver {
    
            private static Logger log = LoggerFactory.getLogger(MQReceiver.class); //设置控制台输出
            
            @Autowired
            RedisService redisService;
            
            @Autowired
            GoodsService goodsService;
            
            @Autowired
            MiaoshaService miaoshaService;
        
            //1.接受消息
            @RabbitListener(queues=MQConfig.QUEUE)
            public void receive(String message) {
                log.info("receive message:"+message);
            }        
    }

    ③ com.kirin.miaosha.rabbitmq / MQConfig.java:配置类

    package com.kirin.miaosha.rabbitmq;
    
    @Configuration
    public class MQConfig {
        public static final String QUEUE = "queue";
    
        @Bean
        public Queue queue() {
            return new Queue(QUEUE, true);
        }
    }

    ④ com.kirin.miaosha.redis / RedisService.java:

    package com.kirin.miaosha.redis;
    
    @Service
    public class RedisService {
        
        @Autowired
        JedisPool jedisPool;
        
        //获取当前对象
        public <T> T get(KeyPrefix prefix, String key, Class<T> clazz) {
            Jedis jedis = null;
            try {
                jedis = jedisPool.getResource();
                String realKey = prefix.getPrefix() + key;
                String str = jedis.get(realKey);
                T t = stringToBean(str, clazz);
                return t;
            }finally {
                returnToPool(jedis);
            }
        }
        
        //设置对象
        public <T> boolean set(KeyPrefix prefix, String key,  T value) {
            Jedis jedis = null;
            try {
                jedis = jedisPool.getResource();
                String str = beanToString(value);
                if(str == null || str.length() <= 0) {
                    return false;
                }
                String realKey = prefix.getPrefix() + key;
                int seconds = prefix.expireSeconds();
                if(seconds <= 0) {
                    jedis.set(realKey, str);
                }else {
                    jedis.setex(realKey, seconds, str);
                }
                return true;
            }finally {
                returnToPool(jedis);
            }
        }
        
        public <T> boolean exists(KeyPrefix prefix, String key) {
            Jedis jedis = null;
            try {
                jedis = jedisPool.getResource();
                String realKey = prefix.getPrefix() + key;
                return jedis.exists(realKey);
            }finally {
                returnToPool(jedis);
            }
        }
        
        public <T> Long incr(KeyPrefix prefix, String key) {
            Jedis jedis = null;
            try {
                jedis = jedisPool.getResource();
                //生成真正的key
                String realKey = prefix.getPrefix() + key;
                return jedis.incr(realKey);
            }finally {
                returnToPool(jedis);
            }
        }
    
        public <T> Long decr(KeyPrefix prefix, String key) {
            Jedis jedis = null;
            try {
                jedis = jedisPool.getResource();
                //生成真正的key
                String realKey = prefix.getPrefix() + key;
                return jedis.decr(realKey);
            }finally {
                returnToPool(jedis);
            }
        }
        
        public boolean delete(KeyPrefix prefix, String key) {
             Jedis jedis = null;
             try {
                 jedis =  jedisPool.getResource();
                //生成真正的key
                String realKey  = prefix.getPrefix() + key;
                long ret =  jedis.del(realKey);
                return ret > 0;
             }finally {
                  returnToPool(jedis);
             }
        }
    public static <T> String beanToString(T value) { if(value == null) { return null; } Class<?> clazz = value.getClass(); if(clazz == int.class || clazz == Integer.class) { return ""+value; }else if(clazz == String.class) { return (String)value; }else if(clazz == long.class || clazz == Long.class) { return ""+value; }else { return JSON.toJSONString(value); } }private void returnToPool(Jedis jedis) { if(jedis != null) { jedis.close(); } } }

    ⑤ com.kirin.miaosha.controller / DemoController.java:

    package com.kirin.miaosha.controller;
    
    @Controller
    public class DemoController {
        @Autowired
        MQSender sender;
        
        @RequestMapping("/mq")
        @ResponseBody
        public Result <String> mq() {
            sender.send("hello,imooc");
            return Result.success("helo,kirin");
        }
    }

    运行:

    B. Topic Exchange:任何发送到Topic Exchange的消息都会被转发到与routingKey匹配的队列上

    C. Fanout Exchange广播交换机:任何发送到Fanout Exchange的消息都会被转发到与之绑定的队列上

    D. Headers Exchange:任何发送到Headers Exchange的消息,都会和其中存储的条件进行匹配,有whereall和whereAny的区别(全部匹配/任何匹配)

    二、Redis预减库存减少数据库访问

    ① com.kirin.miaosha.controller / MiaoshaController.java:

    package com.kirin.miaosha.controller;
    
    @Controller
    @RequestMapping("/miaosha")
    public class MiaoshaController implements InitializingBean { //实现InitialzingBean接口,重写afterProperties方法
    
        @Autowired
        MiaoshaUserService userService;
        
        @Autowired
        RedisService redisService;
        
        @Autowired
        GoodsService goodsService;
        
        @Autowired
        OrderService orderService;
        
        @Autowired
        MiaoshaService miaoshaService;
        
        @Autowired
        MQSender sender;
    
        @Override
        public void afterPropertiesSet() throws Exception {
            List<GoodsVo> goodsList = goodsService.listGoodsVo();
            if(goodsList == null) {
                return;
            }
            for(GoodsVo goods : goodsList) {
                redisService.set(GoodsKey.getMiaoshaGoodsStock, ""+goods.getId(), goods.getStockCount()); //放入缓存
            }
        }
        
        //秒杀表单提交
        @RequestMapping(value="/do_miaosha", method=RequestMethod.POST)
        @ResponseBody
        public Result<Integer> miaosha(Model model,MiaoshaUser user,
                @RequestParam("goodsId")long goodsId) {
            model.addAttribute("user", user);
            if(user == null) { 
                return Result.error(CodeMsg.SESSION_ERROR);
            }
            
            long stock = redisService.decr(GoodsKey.getMiaoshaGoodsStock, ""+goodsId); 
            if(stock < 0) {
                return Result.error(CodeMsg.MIAO_SHA_OVER);
            }
            MiaoshaOrder order = orderService.getMiaoshaOrderByUserIdGoodsId(user.getId(), goodsId);
            if(order != null) {
                return Result.error(CodeMsg.REPEATE_MIAOSHA);
            }
            //入队
            MiaoshaMessage mm = new MiaoshaMessage();
            mm.setUser(user);
            mm.setGoodsId(goodsId);
            sender.sendMiaoshaMessage(mm);
            return Result.success(0); 
            
        }
        
        
        /**
         * 前端轮询服务端
         * */
        @RequestMapping(value="/result", method=RequestMethod.GET)
        @ResponseBody
        public Result<Long> miaoshaResult(Model model,MiaoshaUser user,@RequestParam("goodsId")long goodsId) {
            model.addAttribute("user", user);
            if(user == null) {
                return Result.error(CodeMsg.SESSION_ERROR);
            }
            long result = miaoshaService.getMiaoshaResult(user.getId(), goodsId); //通过用户和订单查询是否生成订单
            return Result.success(result);
        }
    }

    ② com.kirin.miaosha.redis / GoodsKey.java:

    package com.kirin.miaosha.redis;
    
    public class GoodsKey extends BasePrefix{
    
        private GoodsKey(int expireSeconds, String prefix) { //expireSeconds:设置有效期
            super(expireSeconds, prefix);
        }public static GoodsKey getMiaoshaGoodsStock= new GoodsKey(0, "gs");
    }

    ③ com.kirin.miaosha.rabbitmq / MQSender.java:

    package com.kirin.miaosha.rabbitmq;//消息发送器
    @Service
    public class MQSender {
    
        private static Logger log = LoggerFactory.getLogger(MQSender.class); //设置控制台输出
        
        @Autowired
        AmqpTemplate amqpTemplate ;
        
        public void sendMiaoshaMessage(MiaoshaMessage mm) {
            String msg = RedisService.beanToString(mm);
            log.info("send message:"+msg);
            amqpTemplate.convertAndSend(MQConfig.MIAOSHA_QUEUE, msg); //队列名称,信息
        }
       
    }
    • 用SpringBoot框架提供的AmqpTemlplate实例来为我们的秒杀队列发送消息

    ④ com.kirin.miaosha.rabbitmq / MQReceiverpackage com.kirin.miaosha.rabbitmq;

    //消息接受器
    @Service
    public class MQReceiver {
    
        private static Logger log = LoggerFactory.getLogger(MQReceiver.class);
        
        @Autowired
        RedisService redisService;
        
        @Autowired
        GoodsService goodsService;
        
        @Autowired
        OrderService orderService;
        
        @Autowired
        MiaoshaService miaoshaService;
        
        @RabbitListener(queues=MQConfig.MIAOSHA_QUEUE) 
        public void receive(String message) {
            log.info("receive message:"+message);
            MiaoshaMessage mm  = RedisService.stringToBean(message, MiaoshaMessage.class);
            MiaoshaUser user = mm.getUser();
            long goodsId = mm.getGoodsId();
            
            GoodsVo goods = goodsService.getGoodsVoByGoodsId(goodsId); 
            int stock = goods.getStockCount();
            if(stock <= 0) { 
                return;
            }
            MiaoshaOrder order = orderService.getMiaoshaOrderByUserIdGoodsId(user.getId(), goodsId);
            if(order != null) {
                return;
            }
            //减库存 下订单 写入秒杀订单
            miaoshaService.miaosha(user, goods);
        }
    
    }

    ⑤ com.kirin.miaosha.service / MiaoshaService.java:

    package com.kirin.miaosha.service;
    
    @Service
    public class MiaoshaService {
        
        @Autowired
        GoodsService goodsService;
        
        @Autowired
        OrderService orderService;
        
        @Autowired
        RedisService redisService;
    
        @Transactional
        public OrderInfo miaosha(MiaoshaUser user, GoodsVo goods) {
            //减库存 下订单 写入秒杀订单
            boolean success = goodsService.reduceStock(goods);
            if(success) {
                return orderService.createOrder(user, goods);
            }else {
                setGoodsOver(goods.getId()); //标记商品已被秒杀完
                return null;
            }
        }
    
        //通过用户和订单查询是否生成订单
        public long getMiaoshaResult(Long userId, long goodsId) {
            MiaoshaOrder order = orderService.getMiaoshaOrderByUserIdGoodsId(userId, goodsId);
            if(order != null) {
                return order.getOrderId();
            }else {
                boolean isOver = getGoodsOver(goodsId);
                if(isOver) {
                    return -1; //秒杀失败
                }else {
                    return 0; //排队中,继续轮询
                }
            }
        }
           
    }

    ⑥ com.kirin.miaosha.service / GoodsService.java:

    package com.kirin.miaosha.service;
    
    @Service
    public class GoodsService {
        
        @Autowired
        GoodsDao goodsDao;
        public boolean reduceStock(GoodsVo goods) {
            MiaoshaGoods g = new MiaoshaGoods();
            g.setGoodsId(goods.getId());
            int ret = goodsDao.reduceStock(g);
            return ret > 0;
        }
    }

    ⑦ com.kirin.miaosha.service / OrderService.java:

    package com.kirin.miaosha.service;
    
    @Service
    public class OrderService {
        
        @Autowired
        OrderDao orderDao;
        
        @Autowired
        RedisService redisService;
        
        public MiaoshaOrder getMiaoshaOrderByUserIdGoodsId(long userId, long goodsId) {
            return redisService.get(OrderKey.getMiaoshaOrderByUidGid, ""+userId+"_"+goodsId, MiaoshaOrder.class); //查缓存
        }
        
        public OrderInfo getOrderById(long orderId) {
            return orderDao.getOrderById(orderId);
        }
    
        //生成订单
        @Transactional
        public OrderInfo createOrder(MiaoshaUser user, GoodsVo goods) {
            OrderInfo orderInfo = new OrderInfo();
            orderInfo.setCreateDate(new Date());
            orderInfo.setDeliveryAddrId(0L);
            orderInfo.setGoodsCount(1);
            orderInfo.setGoodsId(goods.getId());
            orderInfo.setGoodsName(goods.getGoodsName());
            orderInfo.setGoodsPrice(goods.getMiaoshaPrice());
            orderInfo.setOrderChannel(1); 
            orderInfo.setStatus(0);
            orderInfo.setUserId(user.getId());
            orderDao.insert(orderInfo);
            MiaoshaOrder miaoshaOrder = new MiaoshaOrder();
            miaoshaOrder.setGoodsId(goods.getId());
            miaoshaOrder.setOrderId(orderInfo.getId());
            miaoshaOrder.setUserId(user.getId());
            orderDao.insertMiaoshaOrder(miaoshaOrder);
    
            redisService.set(OrderKey.getMiaoshaOrderByUidGid, ""+user.getId()+"_"+goods.getId(), miaoshaOrder);
            
            return orderInfo;
        }
    }

    ⑧ com.kirin.miaosha.redis / MiaoshaKey.java:

    package com.kirin.miaosha.redis;
    
    public class MiaoshaKey extends BasePrefix{
    
        private MiaoshaKey(String prefix) {
            super(prefix);
        }
        public static MiaoshaKey isGoodsOver = new MiaoshaKey("go");
    }

    运行:

     

    三、内存标记减少Redis访问

      MiaoshaController.java:在收到秒杀请求,在缓存中预减库存时,要访问Redis数据库,则访问Redis会产生网络的开销

    com.kirin.miaosha.controller / MiaoshaController.java:

    package com.kirin.miaosha.controller;
    
    @Controller
    @RequestMapping("/miaosha")
    public class MiaoshaController implements InitializingBean {
    
        @Autowired
        MiaoshaUserService userService;
        
        @Autowired
        RedisService redisService;
        
        @Autowired
        GoodsService goodsService;
        
        @Autowired
        OrderService orderService;
        
        @Autowired
        MiaoshaService miaoshaService;
        
        @Autowired
        MQSender sender;
        
        private HashMap<Long, Boolean> localOverMap =  new HashMap<Long, Boolean>();
        //秒杀表单提交
        @RequestMapping(value="/do_miaosha", method=RequestMethod.POST)
        @ResponseBody
        public Result<Integer> miaosha(Model model,MiaoshaUser user,
                @RequestParam("goodsId")long goodsId) {
            model.addAttribute("user", user);
            if(user == null) {
                return Result.error(CodeMsg.SESSION_ERROR);
            }
            
            boolean over = localOverMap.get(goodsId); //判断标记商品id是否结束
            if(over) {
                return Result.error(CodeMsg.MIAO_SHA_OVER);
            }
            
            long stock = redisService.decr(GoodsKey.getMiaoshaGoodsStock, ""+goodsId); //10
            if(stock < 0) {
                localOverMap.put(goodsId, true); 
                return Result.error(CodeMsg.MIAO_SHA_OVER);
            }
            MiaoshaOrder order = orderService.getMiaoshaOrderByUserIdGoodsId(user.getId(), goodsId);
            if(order != null) {
                return Result.error(CodeMsg.REPEATE_MIAOSHA);
            }
            MiaoshaMessage mm = new MiaoshaMessage();
            mm.setUser(user);
            mm.setGoodsId(goodsId);
            sender.sendMiaoshaMessage(mm);
            return Result.success(0); //0:排队中
    
        }
    }

     

    五、压测

    1、JMeter打开之前的 2.miaosha.jmx

    2、在com.kirin.miaosha.util / UserUtil.java,重新生成一些 tokens.txt

    3、项目打jar包

    4、将 miaosha_4.jar、2.miaosha.jmx 和 2.tokens.txt 导入虚拟机 /tmp 文件夹下

    5、将这个进程的输出结果放进nohup.out文件中

    nohup java -jar miaosha_4.jar &

    启动

    tail -f nohup.out

    6、压测

    ./apache-jmeter-5.4.3/bin/jmeter.sh -n -t 2.miaosha.jmx -l result4.jtl

    【SecKill】U6 接口优化

  • 相关阅读:
    JVM全面分析之程序计时器
    JVM全面分析之类加载系统
    问题
    -----------------------算法学习篇:斐波拉契数列------------------------
    -----------------------------------A Tour of C++ Chapter8-------------------------------------------
    -----------------Clean Code《代码整洁之道》--------------------
    -----------------------------------A Tour of C++-------------------------------------------
    -----------------Clean Code《代码整洁之道》--------------------
    http响应状态码大全
    DOM之通俗易懂讲解
  • 原文地址:https://www.cnblogs.com/kirin1105916774/p/16384023.html
Copyright © 2020-2023  润新知