▶【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 接口优化

posted @ 2022-06-16 23:08  淇凌  阅读(249)  评论(0编辑  收藏  举报