SpringBoot+Mysql+Redis+RabbitMQ实现高并发秒杀

SpringBoot+Mysql+Redis+RabbitMQ实现高并发秒杀

应用场景

​ 商城对某一商品进行秒杀活动,该项目实例中,商品为watch,库存为10,使用jemter测试工具来模拟高并发场景

搭建项目环境

开始本次测试之前,先说本次我们项目需要安装得工具以及环境,下方我提供了安装工具得博客

安装RabbitMQ

关于RabbitMQ我得上篇博客介绍得比较详细,这里也给一个安装路径

RabbitMQ安装教程

RabbitMQ安装教程

Redis+RedisDesktopManager
关于Redis缓存,Redis是一个key-value存储系统。和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set --有序集合)和hash(哈希类型)。
看到有意思得一句话,不用redis得秒杀都是耍流氓!!
下方为Redis和RedisDesktopManager安装教程

Redis和RedisDesktopManager安装教程

Jmeter压力测试工具

Jmeter为基于java得压力测试工具,本文用它模仿40个不同得用户同时发送请求秒杀商品,下方为Jmeter得安装教程
Jmeter安装教程

数据库表结构

在这里插入图片描述

在这里插入图片描述

编写代码

配置pom.xml文件

    <dependencies>
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-webartifactId>
            dependency>
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-testartifactId>
                <scope>testscope>
                <exclusions>
                    <exclusion>
                        <groupId>org.junit.vintagegroupId>
                        <artifactId>junit-vintage-engineartifactId>
                    exclusion>
                exclusions>
            dependency>
            <dependency>
                <groupId>org.projectlombokgroupId>
                <artifactId>lombokartifactId>
                <optional>trueoptional>
            dependency>
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-data-jpaartifactId>
            dependency>
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-devtoolsartifactId>
                <scope>runtimescope>
                <optional>trueoptional>
            dependency>
            <dependency>
                <groupId>mysqlgroupId>
                <artifactId>mysql-connector-javaartifactId>
                <scope>runtimescope>
            dependency>
            <dependency>
                <groupId>org.apache.commonsgroupId>
                <artifactId>commons-lang3artifactId>
                <version>3.8.1version>
            dependency>
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-amqpartifactId>
            dependency>
            <dependency>
                <groupId>io.jsonwebtokengroupId>
                <artifactId>jjwtartifactId>
                <version>0.7.0version>
            dependency>
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-data-redisartifactId>
            dependency>
            <dependency>
                <groupId>org.mybatis.spring.bootgroupId>
                <artifactId>mybatis-spring-boot-starterartifactId>
                <version>2.1.0version>
            dependency>
            <dependency>
                <groupId>org.springframeworkgroupId>
                <artifactId>spring-txartifactId>
            dependency>
            <dependency>
                <groupId>tk.mybatisgroupId>
                <artifactId>mapper-spring-boot-starterartifactId>
                <version>2.0.3-beta1version>
            dependency>
            <dependency>
                <groupId>tk.mybatisgroupId>
                <artifactId>mapperartifactId>
                <version>4.0.0version>
            dependency>
	dependencies>

配置application.properties

    spring.devtools.restart.enabled=false
    server.port=8443
    ##配置数据库连接
    spring.datasource.username=root 
    spring.datasource.password=root
    spring.datasource.url=jdbc:mysql://localhost:3306/ktoa?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&allowMultiQueries=true
    spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

    ##配置rabbitmq连接
    spring.rabbitmq.host=localhost
    spring.rabbitmq.port=5672
    spring.rabbitmq.username=guest
    spring.rabbitmq.password=guest

    ##配置连接redis --都记得打开服务
    spring.redis.host=localhost
    spring.redis.port=6379 
    spring.redis.jedis.pool.max-active=1024
    spring.redis.jedis.pool.max-wait=-1s
    spring.redis.jedis.pool.max-idle=200
    spring.redis.password=123456

添加实体类

Order.java

    import lombok.Data;
    import javax.persistence.Column;
    import javax.persistence.GeneratedValue;
    import javax.persistence.GenerationType;
    import javax.persistence.Id;
    import javax.persistence.Table;
    import java.io.Serializable;
    @Data
    @Table(name = "t_order")
    public class Order implements Serializable {
        private static final long serialVersionUID = -8867272732777764701L;

        @Id
        @Column(name = "id")
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;

        @Column(name = "order_name")
        private String order_name;

        @Column(name = "order_user")
        private String order_user;
    }


Stock.java

    import lombok.Data;
    import javax.persistence.Column;
    import javax.persistence.GeneratedValue;
    import javax.persistence.GenerationType;
    import javax.persistence.Id;
    import javax.persistence.Table;
    import java.io.Serializable;
    @Table(name = "stock")
    @Data
    public class Stock implements Serializable {
        private static final long serialVersionUID = 2451194410162873075L;

        @Id
        @Column(name = "id")
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;

        @Column(name = "name")
        private String name;

        @Column(name = "stock")
        private Long stock;
    }

. 配置tkmybatis得接口

新建名为base得包,在base下面新建service得接口

    import tk.mybatis.mapper.common.Mapper;
    import tk.mybatis.mapper.common.MySqlMapper;
    public interface GenericMapper<T> extends Mapper<T>, MySqlMapper<T> {
    }

关于这个接口得作用你不需要了解太多,你只要知道我们得mapper层需要通过继承它来实现数据库操作,如果你接触过jpa或者mybatis-plus,tkmybatis方式跟它们相似。

新建mapper层

新建名为mapper得包,在这个包下面新建

OrderMapper

    import com.spbtrediskill.secondskill.base.service.GenericMapper;
    import com.spbtrediskill.secondskill.pojo.Order;
    import org.apache.ibatis.annotations.Mapper;
    @Mapper
    public interface OrderMapper extends GenericMapper<Order> {
        void insertOrder(Order order);
    }

StockMapper

    import com.spbtrediskill.secondskill.base.service.GenericMapper;
    import com.spbtrediskill.secondskill.pojo.Stock;
    import org.apache.ibatis.annotations.Mapper;
    @Mapper
    public interface StockMapper extends GenericMapper<Stock> {
    }

编写RabbitMQ和redis得配置类

新建config包,新建redis和RabbitMQ得类

MyRabbitMQConfig

    import org.springframework.amqp.core.Binding;
    import org.springframework.amqp.core.BindingBuilder;
    import org.springframework.amqp.core.Exchange;
    import org.springframework.amqp.core.ExchangeBuilder;
    import org.springframework.amqp.core.Queue;
    import org.springframework.amqp.core.QueueBuilder;
    import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
    import org.springframework.amqp.support.converter.MessageConverter;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import java.util.HashMap;
    import java.util.Map;
    @Configuration
    public class MyRabbitMQConfig {

    ```
    //库存交换机
    public static final String STORY_EXCHANGE = "STORY_EXCHANGE";

    //订单交换机
    public static final String ORDER_EXCHANGE = "ORDER_EXCHANGE";

    //库存队列
    public static final String STORY_QUEUE = "STORY_QUEUE";

    //订单队列
    public static final String ORDER_QUEUE = "ORDER_QUEUE";

    //库存路由键
    public static final String STORY_ROUTING_KEY = "STORY_ROUTING_KEY";

    //订单路由键
    public static final String ORDER_ROUTING_KEY = "ORDER_ROUTING_KEY";
    @Bean
    public MessageConverter messageConverter() {
        return new Jackson2JsonMessageConverter();
    }
    //创建库存交换机
    @Bean
    public Exchange getStoryExchange() {
        return ExchangeBuilder.directExchange(STORY_EXCHANGE).durable(true).build();
    }
    //创建库存队列
    @Bean
    public Queue getStoryQueue() {
        return new Queue(STORY_QUEUE);
    }
    //库存交换机和库存队列绑定
    @Bean
    public Binding bindStory() {
        return BindingBuilder.bind(getStoryQueue()).to(getStoryExchange()).with(STORY_ROUTING_KEY).noargs();
    }
    //创建订单队列
    @Bean
    public Queue getOrderQueue() {
        return new Queue(ORDER_QUEUE);
    }
    //创建订单交换机
    @Bean
    public Exchange getOrderExchange() {
        return ExchangeBuilder.directExchange(ORDER_EXCHANGE).durable(true).build();
    }
    //订单队列与订单交换机进行绑定
    @Bean
    public Binding bindOrder() {
        return BindingBuilder.bind(getOrderQueue()).to(getOrderExchange()).with(ORDER_ROUTING_KEY).noargs();
    }

    }

RedisConfig

	import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.data.redis.connection.RedisConnectionFactory;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
    import org.springframework.data.redis.serializer.StringRedisSerializer;

    @Configuration
    public class RedisConfig {
        // 配置redis得配置详解
        @Bean
        public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
            RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
            template.setConnectionFactory(redisConnectionFactory);
            template.setKeySerializer(new StringRedisSerializer());
            template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
            template.setHashKeySerializer(new GenericJackson2JsonRedisSerializer());
            template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
            template.afterPropertiesSet();
            return template;
        }
    }

service层

OrderServiceImpl

    import com.spbtrediskill.secondskill.mapper.OrderMapper;
    import com.spbtrediskill.secondskill.pojo.Order;
    import com.spbtrediskill.secondskill.service.OrderService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;

    @Service
    public class OrderServiceImpl implements OrderService {
        @Autowired
        private OrderMapper orderMapper;
        @Override
        public void createOrder(Order order) {
            orderMapper.insert(order);
        }
    }

StockServiceImpl

    import com.spbtrediskill.secondskill.mapper.StockMapper;
    import com.spbtrediskill.secondskill.pojo.Stock;
    import com.spbtrediskill.secondskill.service.StockService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.util.CollectionUtils;
    import tk.mybatis.mapper.entity.Example;
    import java.util.List;

    @Service
    public class StockServiceImpl implements StockService {
        @Autowired
        private StockMapper stockMapper;
        // 秒杀商品后减少库存
        @Override
        public void decrByStock(String stockName) {
           Example example = new Example(Stock.class);
            Example.Criteria criteria = example.createCriteria();
            criteria.andEqualTo("name", stockName);
            List<Stock> stocks = stockMapper.selectByExample(example);
            if (!CollectionUtils.isEmpty(stocks)) {
                Stock stock = stocks.get(0);
                stock.setStock(stock.getStock() - 1);
                stockMapper.updateByPrimaryKey(stock);
            }
        }
        // 秒杀商品前判断是否有库存
        @Override
        public Integer selectByExample(String stockName) {
            Example example = new Example(Stock.class);
            Example.Criteria criteria = example.createCriteria();
            criteria.andEqualTo("name", stockName);
            List<Stock> stocks = stockMapper.selectByExample(example);
            if (!CollectionUtils.isEmpty(stocks)) {
                return stocks.get(0).getStock().intValue();
            }
            return 0;
        }
    }

MQOrderService

    import com.spbtrediskill.secondskill.config.MyRabbitMQConfig;
    import com.spbtrediskill.secondskill.pojo.Order;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.amqp.rabbit.annotation.RabbitListener;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    @Service
    @Slf4j
    public class MQOrderService {
        @Autowired
        private OrderService orderService;
        /**
         * 监听订单消息队列,并消费
         *
         * @param order
         */
        @RabbitListener(queues = MyRabbitMQConfig.ORDER_QUEUE)
        public void createOrder(Order order) {
            log.info("收到订单消息,订单用户为:{},商品名称为:{}", order.getOrder_user(), order.getOrder_name());
            /**
             * 调用数据库orderService创建订单信息
             */
            orderService.createOrder(order);
        }
    }

MQStockService

    import com.spbtrediskill.secondskill.config.MyRabbitMQConfig;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.amqp.rabbit.annotation.RabbitListener;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    @Service
    @Slf4j
    public class MQStockService {
        @Autowired
        private StockService stockService;
        /**
         * 监听库存消息队列,并消费
         * @param stockName
         */
        @RabbitListener(queues = MyRabbitMQConfig.STORY_QUEUE)
        public void decrByStock(String stockName) {
            log.info("库存消息队列收到的消息商品信息是:{}", stockName);
            /**
             * 调用数据库service给数据库对应商品库存减一
             */
            stockService.decrByStock(stockName);
        }
    }

RedisService

    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.stereotype.Service;
    import java.util.Date;
    import java.util.concurrent.TimeUnit;
    @Service
    public class RedisService {
        @Autowired
        private RedisTemplate<String, Object> redisTemplate;
        /**
         * 设置String键值对
         * @param key
         * @param value
         * @param millis
         */
        public void put(String key, Object value, long millis) {
            redisTemplate.opsForValue().set(key, value, millis, TimeUnit.MINUTES);
        }
        public void putForHash(String objectKey, String hkey, String value) {
            redisTemplate.opsForHash().put(objectKey, hkey, value);
        }
        public <T> T get(String key, Class<T> type) {
            return (T) redisTemplate.boundValueOps(key).get();
        }
        public void remove(String key) {
            redisTemplate.delete(key);
        }
        public boolean expire(String key, long millis) {
            return redisTemplate.expire(key, millis, TimeUnit.MILLISECONDS);
        }
        public boolean persist(String key) {
            return redisTemplate.hasKey(key);
        }
        public String getString(String key) {
            return (String) redisTemplate.opsForValue().get(key);
        }
        public Integer getInteger(String key) {
            return (Integer) redisTemplate.opsForValue().get(key);
        }
        public Long getLong(String key) {
            return (Long) redisTemplate.opsForValue().get(key);
        }
        public Date getDate(String key) {
            return (Date) redisTemplate.opsForValue().get(key);
        }

        /**
         * 对指定key的键值减一
         * @param key
         * @return
         */
        public Long decrBy(String key) {
            return redisTemplate.opsForValue().decrement(key);
        }
    }

controller

MQOrderService

   	import com.spbtrediskill.secondskill.config.MyRabbitMQConfig;
    import com.spbtrediskill.secondskill.pojo.Order;
    import com.spbtrediskill.secondskill.service.OrderService;
    import com.spbtrediskill.secondskill.service.RedisService;
    import com.spbtrediskill.secondskill.service.StockService;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.amqp.rabbit.core.RabbitTemplate;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.bind.annotation.ResponseBody;
    @Controller
    @Slf4j
    public class SecController {
        @Autowired
        private RabbitTemplate rabbitTemplate;
        @Autowired
        private RedisService redisService;
        @Autowired
        private OrderService orderService;
        @Autowired
        private StockService stockService;
        /**
         * 使用redis+消息队列进行秒杀实现
         *
         * @param username
         * @param stockName
         * @return
         */
        @PostMapping( value = "/sec",produces = "application/json;charset=utf-8")
        @ResponseBody
        public String sec(@RequestParam(value = "username") String username, @RequestParam(value = "stockName") String stockName) {

            log.info("参加秒杀的用户是:{},秒杀的商品是:{}", username, stockName);
            String message = null;
            //调用redis给相应商品库存量减一
            Long decrByResult = redisService.decrBy(stockName);
            if (decrByResult >= 0) {
                /**
                 * 说明该商品的库存量有剩余,可以进行下订单操作
                 */
                log.info("用户:{}秒杀该商品:{}库存有余,可以进行下订单操作", username, stockName);
                //发消息给库存消息队列,将库存数据减一
                rabbitTemplate.convertAndSend(MyRabbitMQConfig.STORY_EXCHANGE, MyRabbitMQConfig.STORY_ROUTING_KEY, stockName);

                //发消息给订单消息队列,创建订单
                Order order = new Order();
                order.setOrder_name(stockName);
                order.setOrder_user(username);
                rabbitTemplate.convertAndSend(MyRabbitMQConfig.ORDER_EXCHANGE, MyRabbitMQConfig.ORDER_ROUTING_KEY, order);
                message = "用户" + username + "秒杀" + stockName + "成功";
            } else {
                /**
                 * 说明该商品的库存量没有剩余,直接返回秒杀失败的消息给用户
                 */
                log.info("用户:{}秒杀时商品的库存量没有剩余,秒杀结束", username);
                message = "用户:"+ username + "商品的库存量没有剩余,秒杀结束";
            }
            return message;
        }
    }

编写springboot启动类

    import com.spbtrediskill.secondskill.service.RedisService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.boot.ApplicationArguments;
    import org.springframework.boot.ApplicationRunner;
    import tk.mybatis.spring.annotation.MapperScan;
    @SpringBootApplication
    @MapperScan("com.spbtrediskill.secondskill.mapper")
    public class SecondskillApplication implements ApplicationRunner{
        public static void main(String[] args) {
            SpringApplication.run(SecondskillApplication.class, args);
        }
        @Autowired
        private RedisService redisService;
        /**
         * redis初始化商品的库存量和信息
         * @param args
         * @throws Exception
         */
        @Override
        public void run(ApplicationArguments args) throws Exception {
            redisService.put("watch", 10, 20);
        }
    }

tion.MapperScan;
@SpringBootApplication
@MapperScan(“com.spbtrediskill.secondskill.mapper”)
public class SecondskillApplication implements ApplicationRunner{
public static void main(String[] args) {
SpringApplication.run(SecondskillApplication.class, args);
}
@Autowired
private RedisService redisService;
/**
* redis初始化商品的库存量和信息
* @param args
* @throws Exception
*/
@Override
public void run(ApplicationArguments args) throws Exception {
redisService.put(“watch”, 10, 20);
}
}


你可能感兴趣的:(SpringBoot+Mysql+Redis+RabbitMQ实现高并发秒杀)