ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

▶【SecKill】U6 接口优化

2022-06-16 23:37:04  阅读:132  来源: 互联网

标签:SecKill U6 rabbitmq miaosha 接口 return com public kirin


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

标签:SecKill,U6,rabbitmq,miaosha,接口,return,com,public,kirin
来源: https://www.cnblogs.com/kirin1105916774/p/16384023.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有