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安装教程

数据库表结构

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

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

编写代码

配置pom.xml文件


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
1    <dependencies>
2            <dependency>
3                <groupId>org.springframework.boot</groupId>
4                <artifactId>spring-boot-starter-web</artifactId>
5            </dependency>
6            <dependency>
7                <groupId>org.springframework.boot</groupId>
8                <artifactId>spring-boot-starter-test</artifactId>
9                <scope>test</scope>
10                <exclusions>
11                    <exclusion>
12                        <groupId>org.junit.vintage</groupId>
13                        <artifactId>junit-vintage-engine</artifactId>
14                    </exclusion>
15                </exclusions>
16            </dependency>
17            <dependency>
18                <groupId>org.projectlombok</groupId>
19                <artifactId>lombok</artifactId>
20                <optional>true</optional>
21            </dependency>
22            <dependency>
23                <groupId>org.springframework.boot</groupId>
24                <artifactId>spring-boot-starter-data-jpa</artifactId>
25            </dependency>
26            <dependency>
27                <groupId>org.springframework.boot</groupId>
28                <artifactId>spring-boot-devtools</artifactId>
29                <scope>runtime</scope>
30                <optional>true</optional>
31            </dependency>
32            <dependency>
33                <groupId>mysql</groupId>
34                <artifactId>mysql-connector-java</artifactId>
35                <scope>runtime</scope>
36            </dependency>
37            <dependency>
38                <groupId>org.apache.commons</groupId>
39                <artifactId>commons-lang3</artifactId>
40                <version>3.8.1</version>
41            </dependency>
42            <dependency>
43                <groupId>org.springframework.boot</groupId>
44                <artifactId>spring-boot-starter-amqp</artifactId>
45            </dependency>
46            <dependency>
47                <groupId>io.jsonwebtoken</groupId>
48                <artifactId>jjwt</artifactId>
49                <version>0.7.0</version>
50            </dependency>
51            <dependency>
52                <groupId>org.springframework.boot</groupId>
53                <artifactId>spring-boot-starter-data-redis</artifactId>
54            </dependency>
55            <dependency>
56                <groupId>org.mybatis.spring.boot</groupId>
57                <artifactId>mybatis-spring-boot-starter</artifactId>
58                <version>2.1.0</version>
59            </dependency>
60            <dependency>
61                <groupId>org.springframework</groupId>
62                <artifactId>spring-tx</artifactId>
63            </dependency>
64            <dependency>
65                <groupId>tk.mybatis</groupId>
66                <artifactId>mapper-spring-boot-starter</artifactId>
67                <version>2.0.3-beta1</version>
68            </dependency>
69            <dependency>
70                <groupId>tk.mybatis</groupId>
71                <artifactId>mapper</artifactId>
72                <version>4.0.0</version>
73            </dependency>
74  </dependencies>
75
76

配置application.properties


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
1    spring.devtools.restart.enabled=false
2    server.port=8443
3    ##配置数据库连接
4    spring.datasource.username=root
5    spring.datasource.password=root
6    spring.datasource.url=jdbc:mysql://localhost:3306/ktoa?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&allowMultiQueries=true
7    spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
8
9    ##配置rabbitmq连接
10    spring.rabbitmq.host=localhost
11    spring.rabbitmq.port=5672
12    spring.rabbitmq.username=guest
13    spring.rabbitmq.password=guest
14
15    ##配置连接redis --都记得打开服务
16    spring.redis.host=localhost
17    spring.redis.port=6379
18    spring.redis.jedis.pool.max-active=1024
19    spring.redis.jedis.pool.max-wait=-1s
20    spring.redis.jedis.pool.max-idle=200
21    spring.redis.password=123456
22
23

添加实体类

Order.java


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
1    import lombok.Data;
2    import javax.persistence.Column;
3    import javax.persistence.GeneratedValue;
4    import javax.persistence.GenerationType;
5    import javax.persistence.Id;
6    import javax.persistence.Table;
7    import java.io.Serializable;
8    @Data
9    @Table(name = "t_order")
10    public class Order implements Serializable {
11        private static final long serialVersionUID = -8867272732777764701L;
12
13        @Id
14        @Column(name = "id")
15        @GeneratedValue(strategy = GenerationType.IDENTITY)
16        private Long id;
17
18        @Column(name = "order_name")
19        private String order_name;
20
21        @Column(name = "order_user")
22        private String order_user;
23    }
24
25
26
27

Stock.java


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
1    import lombok.Data;
2    import javax.persistence.Column;
3    import javax.persistence.GeneratedValue;
4    import javax.persistence.GenerationType;
5    import javax.persistence.Id;
6    import javax.persistence.Table;
7    import java.io.Serializable;
8    @Table(name = "stock")
9    @Data
10    public class Stock implements Serializable {
11        private static final long serialVersionUID = 2451194410162873075L;
12
13        @Id
14        @Column(name = "id")
15        @GeneratedValue(strategy = GenerationType.IDENTITY)
16        private Long id;
17
18        @Column(name = "name")
19        private String name;
20
21        @Column(name = "stock")
22        private Long stock;
23    }
24
25

. 配置tkmybatis得接口

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


1
2
3
4
5
6
1    import tk.mybatis.mapper.common.Mapper;
2    import tk.mybatis.mapper.common.MySqlMapper;
3    public interface GenericMapper<T> extends Mapper<T>, MySqlMapper<T> {
4    }
5
6

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

新建mapper层

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

OrderMapper


1
2
3
4
5
6
7
8
9
1    import com.spbtrediskill.secondskill.base.service.GenericMapper;
2    import com.spbtrediskill.secondskill.pojo.Order;
3    import org.apache.ibatis.annotations.Mapper;
4    @Mapper
5    public interface OrderMapper extends GenericMapper<Order> {
6        void insertOrder(Order order);
7    }
8
9

StockMapper


1
2
3
4
5
6
7
8
1    import com.spbtrediskill.secondskill.base.service.GenericMapper;
2    import com.spbtrediskill.secondskill.pojo.Stock;
3    import org.apache.ibatis.annotations.Mapper;
4    @Mapper
5    public interface StockMapper extends GenericMapper<Stock> {
6    }
7
8

编写RabbitMQ和redis得配置类

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

MyRabbitMQConfig


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
1    import org.springframework.amqp.core.Binding;
2    import org.springframework.amqp.core.BindingBuilder;
3    import org.springframework.amqp.core.Exchange;
4    import org.springframework.amqp.core.ExchangeBuilder;
5    import org.springframework.amqp.core.Queue;
6    import org.springframework.amqp.core.QueueBuilder;
7    import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
8    import org.springframework.amqp.support.converter.MessageConverter;
9    import org.springframework.context.annotation.Bean;
10    import org.springframework.context.annotation.Configuration;
11    import java.util.HashMap;
12    import java.util.Map;
13    @Configuration
14    public class MyRabbitMQConfig {
15
16    ```
17    //库存交换机
18    public static final String STORY_EXCHANGE = "STORY_EXCHANGE";
19
20    //订单交换机
21    public static final String ORDER_EXCHANGE = "ORDER_EXCHANGE";
22
23    //库存队列
24    public static final String STORY_QUEUE = "STORY_QUEUE";
25
26    //订单队列
27    public static final String ORDER_QUEUE = "ORDER_QUEUE";
28
29    //库存路由键
30    public static final String STORY_ROUTING_KEY = "STORY_ROUTING_KEY";
31
32    //订单路由键
33    public static final String ORDER_ROUTING_KEY = "ORDER_ROUTING_KEY";
34    @Bean
35    public MessageConverter messageConverter() {
36        return new Jackson2JsonMessageConverter();
37    }
38    //创建库存交换机
39    @Bean
40    public Exchange getStoryExchange() {
41        return ExchangeBuilder.directExchange(STORY_EXCHANGE).durable(true).build();
42    }
43    //创建库存队列
44    @Bean
45    public Queue getStoryQueue() {
46        return new Queue(STORY_QUEUE);
47    }
48    //库存交换机和库存队列绑定
49    @Bean
50    public Binding bindStory() {
51        return BindingBuilder.bind(getStoryQueue()).to(getStoryExchange()).with(STORY_ROUTING_KEY).noargs();
52    }
53    //创建订单队列
54    @Bean
55    public Queue getOrderQueue() {
56        return new Queue(ORDER_QUEUE);
57    }
58    //创建订单交换机
59    @Bean
60    public Exchange getOrderExchange() {
61        return ExchangeBuilder.directExchange(ORDER_EXCHANGE).durable(true).build();
62    }
63    //订单队列与订单交换机进行绑定
64    @Bean
65    public Binding bindOrder() {
66        return BindingBuilder.bind(getOrderQueue()).to(getOrderExchange()).with(ORDER_ROUTING_KEY).noargs();
67    }
68
69    }
70
71

RedisConfig


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
1   import org.springframework.context.annotation.Bean;
2    import org.springframework.context.annotation.Configuration;
3    import org.springframework.data.redis.connection.RedisConnectionFactory;
4    import org.springframework.data.redis.core.RedisTemplate;
5    import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
6    import org.springframework.data.redis.serializer.StringRedisSerializer;
7
8    @Configuration
9    public class RedisConfig {
10        // 配置redis得配置详解
11        @Bean
12        public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
13            RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
14            template.setConnectionFactory(redisConnectionFactory);
15            template.setKeySerializer(new StringRedisSerializer());
16            template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
17            template.setHashKeySerializer(new GenericJackson2JsonRedisSerializer());
18            template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
19            template.afterPropertiesSet();
20            return template;
21        }
22    }
23
24

service层

OrderServiceImpl


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
1    import com.spbtrediskill.secondskill.mapper.OrderMapper;
2    import com.spbtrediskill.secondskill.pojo.Order;
3    import com.spbtrediskill.secondskill.service.OrderService;
4    import org.springframework.beans.factory.annotation.Autowired;
5    import org.springframework.stereotype.Service;
6
7    @Service
8    public class OrderServiceImpl implements OrderService {
9        @Autowired
10        private OrderMapper orderMapper;
11        @Override
12        public void createOrder(Order order) {
13            orderMapper.insert(order);
14        }
15    }
16
17
18

StockServiceImpl


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
1    import com.spbtrediskill.secondskill.mapper.StockMapper;
2    import com.spbtrediskill.secondskill.pojo.Stock;
3    import com.spbtrediskill.secondskill.service.StockService;
4    import org.springframework.beans.factory.annotation.Autowired;
5    import org.springframework.stereotype.Service;
6    import org.springframework.util.CollectionUtils;
7    import tk.mybatis.mapper.entity.Example;
8    import java.util.List;
9
10    @Service
11    public class StockServiceImpl implements StockService {
12        @Autowired
13        private StockMapper stockMapper;
14        // 秒杀商品后减少库存
15        @Override
16        public void decrByStock(String stockName) {
17           Example example = new Example(Stock.class);
18            Example.Criteria criteria = example.createCriteria();
19            criteria.andEqualTo("name", stockName);
20            List<Stock> stocks = stockMapper.selectByExample(example);
21            if (!CollectionUtils.isEmpty(stocks)) {
22                Stock stock = stocks.get(0);
23                stock.setStock(stock.getStock() - 1);
24                stockMapper.updateByPrimaryKey(stock);
25            }
26        }
27        // 秒杀商品前判断是否有库存
28        @Override
29        public Integer selectByExample(String stockName) {
30            Example example = new Example(Stock.class);
31            Example.Criteria criteria = example.createCriteria();
32            criteria.andEqualTo("name", stockName);
33            List<Stock> stocks = stockMapper.selectByExample(example);
34            if (!CollectionUtils.isEmpty(stocks)) {
35                return stocks.get(0).getStock().intValue();
36            }
37            return 0;
38        }
39    }
40
41

MQOrderService


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
1    import com.spbtrediskill.secondskill.config.MyRabbitMQConfig;
2    import com.spbtrediskill.secondskill.pojo.Order;
3    import lombok.extern.slf4j.Slf4j;
4    import org.springframework.amqp.rabbit.annotation.RabbitListener;
5    import org.springframework.beans.factory.annotation.Autowired;
6    import org.springframework.stereotype.Service;
7    @Service
8    @Slf4j
9    public class MQOrderService {
10        @Autowired
11        private OrderService orderService;
12        /**
13         * 监听订单消息队列,并消费
14         *
15         * @param order
16         */
17        @RabbitListener(queues = MyRabbitMQConfig.ORDER_QUEUE)
18        public void createOrder(Order order) {
19            log.info("收到订单消息,订单用户为:{},商品名称为:{}", order.getOrder_user(), order.getOrder_name());
20            /**
21             * 调用数据库orderService创建订单信息
22             */
23            orderService.createOrder(order);
24        }
25    }
26
27

MQStockService


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
1    import com.spbtrediskill.secondskill.config.MyRabbitMQConfig;
2    import lombok.extern.slf4j.Slf4j;
3    import org.springframework.amqp.rabbit.annotation.RabbitListener;
4    import org.springframework.beans.factory.annotation.Autowired;
5    import org.springframework.stereotype.Service;
6    @Service
7    @Slf4j
8    public class MQStockService {
9        @Autowired
10        private StockService stockService;
11        /**
12         * 监听库存消息队列,并消费
13         * @param stockName
14         */
15        @RabbitListener(queues = MyRabbitMQConfig.STORY_QUEUE)
16        public void decrByStock(String stockName) {
17            log.info("库存消息队列收到的消息商品信息是:{}", stockName);
18            /**
19             * 调用数据库service给数据库对应商品库存减一
20             */
21            stockService.decrByStock(stockName);
22        }
23    }
24
25

RedisService


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
1    import org.springframework.beans.factory.annotation.Autowired;
2    import org.springframework.data.redis.core.RedisTemplate;
3    import org.springframework.stereotype.Service;
4    import java.util.Date;
5    import java.util.concurrent.TimeUnit;
6    @Service
7    public class RedisService {
8        @Autowired
9        private RedisTemplate<String, Object> redisTemplate;
10        /**
11         * 设置String键值对
12         * @param key
13         * @param value
14         * @param millis
15         */
16        public void put(String key, Object value, long millis) {
17            redisTemplate.opsForValue().set(key, value, millis, TimeUnit.MINUTES);
18        }
19        public void putForHash(String objectKey, String hkey, String value) {
20            redisTemplate.opsForHash().put(objectKey, hkey, value);
21        }
22        public <T> T get(String key, Class<T> type) {
23            return (T) redisTemplate.boundValueOps(key).get();
24        }
25        public void remove(String key) {
26            redisTemplate.delete(key);
27        }
28        public boolean expire(String key, long millis) {
29            return redisTemplate.expire(key, millis, TimeUnit.MILLISECONDS);
30        }
31        public boolean persist(String key) {
32            return redisTemplate.hasKey(key);
33        }
34        public String getString(String key) {
35            return (String) redisTemplate.opsForValue().get(key);
36        }
37        public Integer getInteger(String key) {
38            return (Integer) redisTemplate.opsForValue().get(key);
39        }
40        public Long getLong(String key) {
41            return (Long) redisTemplate.opsForValue().get(key);
42        }
43        public Date getDate(String key) {
44            return (Date) redisTemplate.opsForValue().get(key);
45        }
46
47        /**
48         * 对指定key的键值减一
49         * @param key
50         * @return
51         */
52        public Long decrBy(String key) {
53            return redisTemplate.opsForValue().decrement(key);
54        }
55    }
56
57

controller

MQOrderService


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
1       import com.spbtrediskill.secondskill.config.MyRabbitMQConfig;
2    import com.spbtrediskill.secondskill.pojo.Order;
3    import com.spbtrediskill.secondskill.service.OrderService;
4    import com.spbtrediskill.secondskill.service.RedisService;
5    import com.spbtrediskill.secondskill.service.StockService;
6    import lombok.extern.slf4j.Slf4j;
7    import org.springframework.amqp.rabbit.core.RabbitTemplate;
8    import org.springframework.beans.factory.annotation.Autowired;
9    import org.springframework.stereotype.Controller;
10    import org.springframework.web.bind.annotation.PostMapping;
11    import org.springframework.web.bind.annotation.RequestMapping;
12    import org.springframework.web.bind.annotation.RequestParam;
13    import org.springframework.web.bind.annotation.ResponseBody;
14    @Controller
15    @Slf4j
16    public class SecController {
17        @Autowired
18        private RabbitTemplate rabbitTemplate;
19        @Autowired
20        private RedisService redisService;
21        @Autowired
22        private OrderService orderService;
23        @Autowired
24        private StockService stockService;
25        /**
26         * 使用redis+消息队列进行秒杀实现
27         *
28         * @param username
29         * @param stockName
30         * @return
31         */
32        @PostMapping( value = "/sec",produces = "application/json;charset=utf-8")
33        @ResponseBody
34        public String sec(@RequestParam(value = "username") String username, @RequestParam(value = "stockName") String stockName) {
35
36            log.info("参加秒杀的用户是:{},秒杀的商品是:{}", username, stockName);
37            String message = null;
38            //调用redis给相应商品库存量减一
39            Long decrByResult = redisService.decrBy(stockName);
40            if (decrByResult >= 0) {
41                /**
42                 * 说明该商品的库存量有剩余,可以进行下订单操作
43                 */
44                log.info("用户:{}秒杀该商品:{}库存有余,可以进行下订单操作", username, stockName);
45                //发消息给库存消息队列,将库存数据减一
46                rabbitTemplate.convertAndSend(MyRabbitMQConfig.STORY_EXCHANGE, MyRabbitMQConfig.STORY_ROUTING_KEY, stockName);
47
48                //发消息给订单消息队列,创建订单
49                Order order = new Order();
50                order.setOrder_name(stockName);
51                order.setOrder_user(username);
52                rabbitTemplate.convertAndSend(MyRabbitMQConfig.ORDER_EXCHANGE, MyRabbitMQConfig.ORDER_ROUTING_KEY, order);
53                message = "用户" + username + "秒杀" + stockName + "成功";
54            } else {
55                /**
56                 * 说明该商品的库存量没有剩余,直接返回秒杀失败的消息给用户
57                 */
58                log.info("用户:{}秒杀时商品的库存量没有剩余,秒杀结束", username);
59                message = "用户:"+ username + "商品的库存量没有剩余,秒杀结束";
60            }
61            return message;
62        }
63    }
64
65
66

编写springboot启动类


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
1    import com.spbtrediskill.secondskill.service.RedisService;
2    import org.springframework.beans.factory.annotation.Autowired;
3    import org.springframework.boot.SpringApplication;
4    import org.springframework.boot.autoconfigure.SpringBootApplication;
5    import org.springframework.boot.ApplicationArguments;
6    import org.springframework.boot.ApplicationRunner;
7    import tk.mybatis.spring.annotation.MapperScan;
8    @SpringBootApplication
9    @MapperScan("com.spbtrediskill.secondskill.mapper")
10    public class SecondskillApplication implements ApplicationRunner{
11        public static void main(String[] args) {
12            SpringApplication.run(SecondskillApplication.class, args);
13        }
14        @Autowired
15        private RedisService redisService;
16        /**
17         * redis初始化商品的库存量和信息
18         * @param args
19         * @throws Exception
20         */
21        @Override
22        public void run(ApplicationArguments args) throws Exception {
23            redisService.put("watch", 10, 20);
24        }
25    }
26
27

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);
}
}

给TA打赏
共{{data.count}}人
人已打赏
安全运维

OpenSSH-8.7p1离线升级修复安全漏洞

2021-10-23 10:13:25

安全运维

设计模式的设计原则

2021-12-12 17:36:11

个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索