SpringBoot秒杀系统demo

我们在平时的开发中经常会遇到秒杀,抢单的一些需求,这些系统开发时如果考虑不全面就可能会产生库存不准,以及数据库压力大等问题。

本文将以springboot为基础,结合Redis 和 RabbitMQ做一个秒杀系统的demo,主要展示Redis分布式锁以及消息队列的使用。

秒杀系统的主要基于以下的原则去实现

1. 系统初始化时,把商品存库数量加载到redis中
2. 当收到秒杀请求后,redis预减库存,库存不足则直接返回
3. 秒杀成功的请求入rabbitMQ,立即返回“正在抢购页面…”,当异步下单成功后才返回订单。
4. 客户端轮询是否秒杀成功,服务器请求出队,生成订单,减少库存。

1. 配置

   1.1 pom文件

  1 <?xml version="1.0" encoding="UTF-8"?>
  2 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  4     <modelVersion>4.0.0</modelVersion>
  5     <parent>
  6         <groupId>org.springframework.boot</groupId>
  7         <artifactId>spring-boot-starter-parent</artifactId>
  8         <version>2.2.5.RELEASE</version>
  9         <relativePath/> <!-- lookup parent from repository -->
 10     </parent>
 11     <groupId>com.devin</groupId>
 12     <artifactId>order_grabbing_demo</artifactId>
 13     <version>1.0.0</version>
 14     <name>order_grabbing_demo</name>
 15     <description>Demo project for Spring Boot</description>
 16 
 17     <properties>
 18         <java.version>1.8</java.version>
 19     </properties>
 20 
 21     <dependencies>
 22 
 23         <dependency>
 24             <groupId>org.springframework.boot</groupId>
 25             <artifactId>spring-boot-starter-web</artifactId>
 26         </dependency>
 27 
 28 
 29         <dependency>
 30             <groupId>tk.mybatis</groupId>
 31             <artifactId>mapper-spring-boot-starter</artifactId>
 32             <version>2.0.4</version>
 33         </dependency>
 34 
 35         <dependency>
 36             <groupId>org.springframework.boot</groupId>
 37             <artifactId>spring-boot-starter-jdbc</artifactId>
 38             <version>2.0.0.RELEASE</version>
 39         </dependency>
 40 
 41         <dependency>
 42             <groupId>org.mybatis.spring.boot</groupId>
 43             <artifactId>mybatis-spring-boot-starter</artifactId>
 44             <version>2.0.1</version>
 45         </dependency>
 46 
 47         <dependency>
 48             <groupId>mysql</groupId>
 49             <artifactId>mysql-connector-java</artifactId>
 50             <version>5.1.17</version>
 51         </dependency>
 52 
 53         <dependency>
 54             <groupId>com.alibaba</groupId>
 55             <artifactId>druid</artifactId>
 56             <version>1.1.1</version>
 57         </dependency>
 58 
 59         <dependency>
 60             <groupId>org.projectlombok</groupId>
 61             <artifactId>lombok</artifactId>
 62             <version>1.16.22</version>
 63         </dependency>
 64 
 65 
 66         <dependency>
 67             <groupId>org.springframework.boot</groupId>
 68             <artifactId>spring-boot-starter-amqp</artifactId>
 69             <version>2.1.8.RELEASE</version>
 70         </dependency>
 71 
 72         <dependency>
 73             <groupId>org.springframework.boot</groupId>
 74             <artifactId>spring-boot-test</artifactId>
 75             <version>2.2.6.RELEASE</version>
 76         </dependency>
 77         <dependency>
 78             <groupId>junit</groupId>
 79             <artifactId>junit</artifactId>
 80             <version>4.12</version>
 81         </dependency>
 82         <dependency>
 83             <groupId>org.springframework</groupId>
 84             <artifactId>spring-test</artifactId>
 85             <version>5.2.5.RELEASE</version>
 86         </dependency>
 87 
 88 
 89 
 90         <!--  springboot整合 redis -->
 91         <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-redis -->
 92         <dependency>
 93             <groupId>org.springframework.boot</groupId>
 94             <artifactId>spring-boot-starter-data-redis</artifactId>
 95             <version>2.2.0.RELEASE</version>
 96             <exclusions>
 97                 <exclusion>
 98                     <groupId>io.lettuce</groupId>
 99                     <artifactId>lettuce-core</artifactId>
100                 </exclusion>
101             </exclusions>
102         </dependency>
103 
104         <dependency>
105             <groupId>redis.clients</groupId>
106             <artifactId>jedis</artifactId>
107         </dependency>
108 
109 
110         <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
111         <dependency>
112             <groupId>com.alibaba</groupId>
113             <artifactId>fastjson</artifactId>
114             <version>1.2.57</version>
115         </dependency>
116 
117 
118         <dependency>
119             <groupId>org.apache.commons</groupId>
120             <artifactId>commons-lang3</artifactId>
121             <version>3.5</version>
122         </dependency>
123 
124         <dependency>
125             <groupId>commons-codec</groupId>
126             <artifactId>commons-codec</artifactId>
127             <version>1.10</version>
128         </dependency>
129 
130     </dependencies>
131 
132     <build>
133         <plugins>
134             <plugin>
135                 <groupId>org.springframework.boot</groupId>
136                 <artifactId>spring-boot-maven-plugin</artifactId>
137             </plugin>
138         </plugins>
139     </build>
140 
141     <repositories>
142         <repository>
143             <id>maven-ali</id>
144             <url>http://maven.aliyun.com/nexus/content/groups/public//</url>
145             <releases>
146                 <enabled>true</enabled>
147             </releases>
148             <snapshots>
149                 <enabled>true</enabled>
150                 <updatePolicy>always</updatePolicy>
151                 <checksumPolicy>fail</checksumPolicy>
152             </snapshots>
153         </repository>
154     </repositories>
155 
156 
157 </project>

   1.2. application.yml 配置 

    主要配置了数据库,Redis,RabbitMQ的配置

     

 1 server:
 2   port: 7999
 3 spring:
 4   servlet:
 5     multipart:
 6       max-request-size: 100MB
 7       max-file-size: 20MB
 8   http:
 9     encoding:
10       charset: utf-8
11       force: true
12       enabled: true
13   datasource:
14     platform: mysql
15     type: com.alibaba.druid.pool.DruidDataSource
16     initialSize: 5
17     minIdle: 3
18     maxActive: 500
19     maxWait: 60000
20     timeBetweenEvictionRunsMillis: 60000
21     minEvictableIdleTimeMillis: 30000
22     validationQuery: select 1
23     testOnBorrow: true
24     poolPreparedStatements: true
25     maxPoolPreparedStatementPerConnectionSize: 20
26     driverClassName: com.mysql.jdbc.Driver
27     url: jdbc:mysql://192.168.0.91:3306/order_db?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=utf-8&useAffectedRows=true&rewriteBatchedStatements=true
28     username: root
29     password: root
30   rabbitmq:
31     host: localhost
32     port: 5672
33     username: guest
34     password: guest
35   redis:
36     host: 192.168.0.91
37     port: 6379
38     password: myredis
39     timeout: 2000
40     pool:
41       max-idle: 100
42       min-idle: 1
43       max-active: 1000
44       max-wait: -1

 2. 订单model以及对应的mybatis配置

   本文只是做一个订单的记录,所以表的字段比较简单

     

1 CREATE TABLE `order_t` (
2   `order_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键 订单ID',
3   `user_id` varchar(128) DEFAULT NULL COMMENT '用户Id',
4   `product_id` varchar(128) DEFAULT NULL COMMENT '产品Id',
5   `create_time` bigint(20) DEFAULT NULL COMMENT '时间',
6   PRIMARY KEY (`order_id`)
7 ) ENGINE=InnoDB AUTO_INCREMENT=36255 DEFAULT CHARSET=utf8mb4;

  

 1 package com.devin.order.model;
 2 
 3 import lombok.Data;
 4 
 5 import javax.persistence.*;
 6 import java.io.Serializable;
 7 
 8 /**
 9  * @author Devin Zhang
10  * @className Order
11  * @description TODO
12  * @date 2020/4/25 11:03
13  */
14 @Data
15 @Table(name = "order_t")
16 public class Order implements Serializable {
17 
18     @Id
19     @Column(name = "order_id")
20     @GeneratedValue(strategy= GenerationType.IDENTITY)
21     private Integer orderId;
22 
23     private String userId;
24     private String productId;
25     private Long createTime;
26 
27 }

  

  本项目中使用了mybatis的通用mapper tkmybatis,所以配置文件中都是空的

   

OrderMapper.java
 1 package com.devin.order.mapper;
 2 
 3 import com.devin.order.model.Order;
 4 import tk.mybatis.mapper.common.Mapper;
 5 import tk.mybatis.mapper.common.MySqlMapper;
 6 
 7 /**
 8  * @author Devin Zhang
 9  * @className OrderMapper
10  * @description TODO
11  * @date 2020/4/22 16:24
12  */
13 
14 public interface OrderMapper extends Mapper<Order>, MySqlMapper<Order> {
15 }

 OrderMapper.xml

1 <?xml version="1.0" encoding="UTF-8" ?>
2 <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
3 <mapper namespace="com.devin.order.mapper.OrderMapper" >
4 
5 </mapper>

 3.  Redis工具类

   

  1 package com.devin.order.util;
  2 
  3 import lombok.extern.slf4j.Slf4j;
  4 import org.apache.commons.lang3.StringUtils;
  5 import org.springframework.data.redis.core.RedisTemplate;
  6 import org.springframework.data.redis.serializer.GenericToStringSerializer;
  7 import org.springframework.stereotype.Component;
  8 
  9 import javax.annotation.PostConstruct;
 10 import javax.annotation.Resource;
 11 
 12 /**
 13  * @author Devin Zhang
 14  * @className RedisClient
 15  * @description TODO
 16  * @date 2020/4/24 17:51
 17  */
 18 
 19 @Slf4j
 20 @Component
 21 public class RedisClient {
 22 
 23     @Resource
 24     private RedisTemplate<String, Object> redisTemplate;
 25 
 26     @PostConstruct
 27     public void init() {
 28         redisTemplate.setKeySerializer(new GenericToStringSerializer<>(String.class));
 29     }
 30 
 31 
 32     /**
 33      * redis存值
 34      *
 35      * @param key   键
 36      * @param value 值
 37      */
 38     public void set(String key, Object value) {
 39         redisTemplate.opsForValue().set(key, value);
 40     }
 41 
 42     /**
 43      * hash存
 44      *
 45      * @param key   键
 46      * @param hash  hash
 47      * @param value 值
 48      */
 49     public void set(String key, String hash, String value) {
 50         redisTemplate.opsForHash().put(key, hash, value);
 51     }
 52 
 53 
 54     /**
 55      * redis获取值
 56      *
 57      * @param key 键
 58      * @return 返回值
 59      */
 60     public Object get(String key) {
 61         return redisTemplate.opsForValue().get(key);
 62     }
 63 
 64     /**
 65      * hash取值
 66      *
 67      * @param key  键
 68      * @param hash hash
 69      * @return 返回redis存储的值
 70      */
 71     public String get(String key, String hash) {
 72         return (String) redisTemplate.opsForHash().get(key, hash);
 73     }
 74 
 75 
 76     /**
 77      * 获取redis的锁
 78      *
 79      * @param key   键
 80      * @param value 值为当前毫秒数+过期时间毫秒数
 81      * @return 返回true/false
 82      */
 83     public boolean lock(String key, String value) {
 84         if (redisTemplate.opsForValue().setIfAbsent(key, value)) {
 85             //加锁成功就返回true
 86             return true;
 87         }
 88         //不加下面这个可能出现死锁情况
 89         //value为当前时间+超时时间
 90         //获取上一个锁的时间,并判断是否小于当前时间,小于就下一步判断,就返回true加锁成功
 91         //currentValue=A 这两个线程的value都是B 其中一个线程拿到锁
 92         String currentValue = (String) redisTemplate.opsForValue().get(key);
 93         //如果锁过期
 94         if (!StringUtils.isEmpty(currentValue)
 95                 && Long.parseLong(currentValue) < System.currentTimeMillis()) {
 96             //存储时间要小于当前时间
 97             //出现死锁的另一种情况,当多个线程进来后都没有返回true,接着往下执行,执行代码有先后,而if判断里只有一个线程才能满足条件
 98             //oldValue=currentValue
 99             //多个线程进来后只有其中一个线程能拿到锁(即oldValue=currentValue),其他的返回false
100             //获取上一个锁的时间
101             String oldValue = (String) redisTemplate.opsForValue().getAndSet(key, value);
102             if (!StringUtils.isEmpty(oldValue) && oldValue.equals(currentValue)) {
103                 //上一个时间不为空,并且等于当前时间
104                 return true;
105             }
106 
107         }
108         return false;
109     }
110 
111 
112     /**
113      * redis释放锁
114      *
115      * @param key   键
116      * @param value 值
117      */
118     public void unlock(String key, String value) {
119         //执行删除可能出现异常需要捕获
120         try {
121             String currentValue = (String) redisTemplate.opsForValue().get(key);
122             if (!StringUtils.isEmpty(currentValue) && currentValue.equals(value)) {
123                 //如果不为空,就删除锁
124                 redisTemplate.opsForValue().getOperations().delete(key);
125             }
126         } catch (Exception e) {
127             log.error("[redis分布式锁] 解锁", e);
128         }
129     }
130 
131 }

 4.  RabbitMQ配置

   

      4.1 定义消息队列的一些常量

     

 1 package com.devin.order.config;
 2 
 3 /**
 4  * @author Devin Zhang
 5  * @className RabbitConstants
 6  * @description TODO
 7  * @date 2020/4/25 10:11
 8  */
 9 
10 public class RabbitConstants {
11 
12     /**
13      * 分列模式
14      */
15     public final static String FANOUT_MODE_QUEUE = "fanout.mode";
16 
17     /**
18      * 日志打印队列
19      */
20     public final static String QUEUE_LOG_PRINT = "queue.log.recode";
21 
22     /**
23      * 主题模式
24      */
25     public final static String TOPIC_MODE_QUEUE = "topic.mode";
26 
27     /**
28      * 主题模式
29      */
30     public final static String TOPIC_ROUTING_KEY = "topic.*";
31 
32 }

     4.2 消息队列配置类

      

 1 package com.devin.order.config;
 2 
 3 /**
 4  * @author Devin Zhang
 5  * @className RabbitMqConfig
 6  * @description TODO
 7  * @date 2020/4/25 10:12
 8  */
 9 
10 
11 import lombok.extern.slf4j.Slf4j;
12 import org.springframework.amqp.core.*;
13 import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
14 import org.springframework.amqp.rabbit.core.RabbitTemplate;
15 import org.springframework.context.annotation.Bean;
16 import org.springframework.context.annotation.Configuration;
17 
18 @Slf4j
19 @Configuration
20 public class RabbitMqConfig {
21 
22     @Bean
23     public RabbitTemplate rabbitTemplate(CachingConnectionFactory connectionFactory) {
24         connectionFactory.setPublisherConfirms(true);
25         connectionFactory.setPublisherReturns(true);
26         RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
27         rabbitTemplate.setMandatory(true);
28         rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> log.info("消息发送成功:correlationData[{}],ack[{}],cause[{}]", correlationData, ack, cause));
29         rabbitTemplate.setReturnCallback((message, replyCode, replyText, exchange, routingKey) -> log.info("消息丢失:exchange[{}],route[{}],replyCode[{}],replyText[{}],message:{}", exchange, routingKey, replyCode, replyText, message));
30         return rabbitTemplate;
31     }
32 
33     /**
34      * 日志打印队列
35      */
36     @Bean
37     public Queue logPrintQueue() {
38         return new Queue(RabbitConstants.QUEUE_LOG_PRINT);
39     }
40 
41     /**
42      * 分列模式队列
43      */
44     @Bean
45     public FanoutExchange fanoutExchange() {
46         return new FanoutExchange(RabbitConstants.FANOUT_MODE_QUEUE);
47     }
48 
49     /**
50      * 分列模式绑定队列
51      *
52      * @param logPrintQueue  绑定队列
53      * @param fanoutExchange 分列模式交换器
54      */
55     @Bean
56     public Binding fanoutBinding(Queue logPrintQueue, FanoutExchange fanoutExchange) {
57         return BindingBuilder.bind(logPrintQueue).to(fanoutExchange);
58     }
59 
60     /**
61      * 主题队列
62      */
63     @Bean
64     public Queue topicQueue() {
65         return new Queue(RabbitConstants.TOPIC_ROUTING_KEY);
66     }
67 
68     /**
69      * 主题模式队列
70      * <li>路由格式必须以 . 分隔,比如 user.email 或者 user.aaa.email</li>
71      * <li>通配符 * ,代表一个占位符,或者说一个单词,比如路由为 user.*,那么 user.email 可以匹配,但是 user.aaa.email 就匹配不了</li>
72      * <li>通配符 # ,代表一个或多个占位符,或者说一个或多个单词,比如路由为 user.#,那么 user.email 可以匹配,user.aaa.email 也可以匹配</li>
73      */
74     @Bean
75     public TopicExchange topicExchange() {
76         return new TopicExchange(RabbitConstants.TOPIC_MODE_QUEUE);
77     }
78 
79     /**
80      * 主题模式绑定队列2
81      *
82      * @param topicQueue    主题队列
83      * @param topicExchange 主题模式交换器
84      */
85     @Bean
86     public Binding topicBinding(Queue topicQueue, TopicExchange topicExchange) {
87         return BindingBuilder.bind(topicQueue).to(topicExchange).with(RabbitConstants.TOPIC_ROUTING_KEY);
88     }
89 
90 }

 5.  抢单逻辑service

    5.1  OrderService

      

 1 package com.devin.order.Service;
 2 
 3 import com.devin.order.config.RabbitConstants;
 4 import com.devin.order.mapper.OrderMapper;
 5 import com.devin.order.model.Order;
 6 import com.devin.order.util.RedisClient;
 7 import org.springframework.amqp.rabbit.core.RabbitTemplate;
 8 import org.springframework.stereotype.Component;
 9 
10 import javax.annotation.PostConstruct;
11 import javax.annotation.Resource;
12 
13 /**
14  * @author Devin Zhang
15  * @className OrderService
16  * @description TODO
17  * @date 2020/4/25 11:14
18  */
19 
20 @Component
21 public class OrderService {
22 
23     public static final String PRODUCT_ID_KEY = "PID001_";
24     private static final Integer PRODUCT_COUNT = 5000;
25 
26     private static final String HAS_BUY_USER_KEY = "HAS_BUY_USER_KEY_";
27 
28     private static final String LOCK_KEY = "LOCK_KEY_";
29 
30 
31     private static final String FAIL_BUYED = "已经买过了";
32     private static final String BUYE_SUCCESS = "抢到了,订单生成中";
33     private static final String FAIL_SOLD_OUT = "没货了";
34     private static final String FAIL_BUSY = "排队中,请重试!";
35 
36     @Resource
37     private RedisClient redisClient;
38 
39     @Resource
40     private OrderMapper orderMapper;
41 
42     @Resource
43     private RabbitTemplate rabbitTemplate;
44 
45 
46     @PostConstruct
47     public void initOrder() {
48         redisClient.set(PRODUCT_ID_KEY, PRODUCT_COUNT);
49         System.out.println("商品已经初始化完成:数量:" + PRODUCT_COUNT);
50     }
51 
52     /**
53      * 下单
54      *
55      * @param userId
56      */
57     public String insertOrder(String userId) {
58 
59         //判断用户是否已买
60         Object hasBuy = redisClient.get(HAS_BUY_USER_KEY, userId);
61         if (hasBuy != null) {
62             return FAIL_BUYED;
63         }
64 
65         //10s自动过期
66         int redisExpireTime = 10 * 1000;
67         long lockValue = System.currentTimeMillis() + redisExpireTime;
68         //后去redis锁,只有获取成功才能继续操作
69         boolean getLock = redisClient.lock(LOCK_KEY, String.valueOf(lockValue));
70         System.out.println(userId + " getLock:" + getLock);
71         if (getLock) {
72             Integer productCount = (Integer) redisClient.get(PRODUCT_ID_KEY);
73             System.out.println("productCount:" + productCount);
74             //库存大于0才能继续下单
75             if (productCount > 0) {
76 
77                 rabbitTemplate.convertAndSend(RabbitConstants.TOPIC_MODE_QUEUE, "topic.queue", userId);
78 
79                 //减库存
80                 redisClient.set(PRODUCT_ID_KEY, (productCount - 1));
81                 //记录用户已买
82                 redisClient.set(HAS_BUY_USER_KEY, userId, "1");
83                 //手动释放锁
84                 redisClient.unlock(LOCK_KEY, String.valueOf(lockValue));
85                 return BUYE_SUCCESS;
86             } else {
87                 System.out.println("亲," + FAIL_SOLD_OUT);
88                 //手动释放锁
89                 redisClient.unlock(LOCK_KEY, String.valueOf(lockValue));
90                 return FAIL_SOLD_OUT;
91             }
92         } else {
93             return FAIL_BUSY;
94         }
95     }
96 
97 
98 }

    5.2  消息队列处理订单入库

       

 1 package com.devin.order.Service;
 2 
 3 /**
 4  * 【消息队列处理器】
 5  *
 6  * @author Devin Zhang
 7  * @className RabbitMqHandler
 8  * @description TODO
 9  * @date 2020/4/25 10:54
10  */
11 
12 
13 import com.devin.order.config.RabbitConstants;
14 import com.devin.order.mapper.OrderMapper;
15 import com.devin.order.model.Order;
16 import com.devin.order.util.RedisClient;
17 import lombok.extern.slf4j.Slf4j;
18 import org.springframework.amqp.rabbit.annotation.RabbitListener;
19 import org.springframework.stereotype.Component;
20 
21 import javax.annotation.Resource;
22 
23 
24 @Slf4j
25 @Component
26 public class RabbitMqHandler {
27 
28     @Resource
29     private RedisClient redisClient;
30 
31     @Resource
32     private OrderMapper orderMapper;
33 
34     /**
35      * 日志打印处理handler
36      *
37      * @param message 待处理的消息体
38      */
39     @RabbitListener(queues = RabbitConstants.QUEUE_LOG_PRINT)
40     public void queueLogPrintHandler(String message) {
41         log.info("接收到操作日志记录消息:[{}]", message);
42     }
43 
44     /**
45      * 主题模式处理handler
46      *
47      * @param message 待处理的消息体
48      */
49     @RabbitListener(queues = RabbitConstants.TOPIC_ROUTING_KEY)
50     public void queueTopicHandler(String message) {
51         log.info("主题模式处理器,接收消息:[{}]", message);
52 
53         //todo
54 
55         String userId = message;
56 
57         //产生订单
58         System.out.println("userId:" + userId);
59         Order order = new Order();
60         order.setProductId(OrderService.PRODUCT_ID_KEY);
61         order.setUserId(userId);
62         order.setCreateTime(System.currentTimeMillis());
63         orderMapper.insert(order);
64 
65 
66         System.out.println("用户:" + userId + "下单成功");
67 
68     }
69 
70 }

   6. 启动类和Controller

      6.1 启动类

      

 1 package com.devin.order;
 2 
 3 import org.springframework.boot.SpringApplication;
 4 import org.springframework.boot.autoconfigure.SpringBootApplication;
 5 import tk.mybatis.spring.annotation.MapperScan;
 6 
 7 
 8 @MapperScan("com.devin.Order.mapper")
 9 @SpringBootApplication
10 public class OrderGrabbingApplication {
11 
12     public static void main(String[] args) {
13         SpringApplication.run(OrderGrabbingApplication.class, args);
14     }
15 
16 }

   6.2  抢单Controller

      OrderController

      

package com.devin.order.controller;

import com.devin.order.Service.OrderService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

/**
 * @author Devin Zhang
 * @className JobController
 * @description TODO
 * @date 2020/4/22 16:36
 */
@RestController
@RequestMapping("/order")
public class OrderController {

    @Resource
    private OrderService orderService;

    @GetMapping("/addOrder")
    public String addOrder(String userId) {
        return orderService.insertOrder(userId);
    }

}

 后续只需要在controller中添加方法用于查询用户对应订单,前台定时轮询即可

     7. 测试

     写一个多线程程序进行测试,可以看到最后的数据完全正确

      

 1 import java.io.BufferedReader;
 2 import java.io.IOException;
 3 import java.io.InputStreamReader;
 4 import java.net.HttpURLConnection;
 5 import java.net.URL;
 6 import java.util.UUID;
 7 import java.util.concurrent.ExecutorService;
 8 import java.util.concurrent.Executors;
 9 
10 /**
11  * @author Devin Zhang
12  * @className Mythread
13  * @description TODO
14  * @date 2020/4/20 14:02
15  */
16 
17 public class OrderThreadTest implements Runnable {
18     @Override
19     public void run() {
20         try {
21             httpURLGETCase();
22         } catch (Exception e) {
23             e.printStackTrace();
24         }
25 
26     }
27 
28     private void httpURLGETCase() {
29         String userId = UUID.randomUUID().toString().replaceAll("-", "");
30         String methodUrl = "http://192.168.0.91:7999/order/addOrder?userId=" + userId;
31 
32         System.out.println("开始访问:" + methodUrl);
33 
34         HttpURLConnection connection = null;
35         BufferedReader reader = null;
36         String line = null;
37         try {
38             URL url = new URL(methodUrl);
39             connection = (HttpURLConnection) url.openConnection();
40             // 根据URL生成HttpURLConnection
41             connection.setRequestMethod("GET");
42             // 默认GET请求
43             connection.connect();
44             // 建立TCP连接
45             if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {
46                 reader = new BufferedReader(new InputStreamReader(connection.getInputStream(), "UTF-8"));
47                 // 发送http请求
48                 StringBuilder result = new StringBuilder();
49                 // 循环读取流
50                 while ((line = reader.readLine()) != null) {
51                     result.append(line).append(System.getProperty("line.separator"));
52                     // "
"
53                 }
54                 System.out.println("结果" + result.toString());
55                 if (result.toString().contains("没货了")) {
56                     long endTine = System.currentTimeMillis();
57                     long useTime = endTine - beginTime;
58                     //共耗时:102041毫秒
59                     //共耗时:82159毫秒
60                     System.out.println("共耗时:" + useTime + "毫秒");
61                     System.exit(0);
62                 }
63             }
64         } catch (IOException e) {
65             e.printStackTrace();
66         } finally {
67             try {
68                 reader.close();
69             } catch (IOException e) {
70                 e.printStackTrace();
71             }
72             connection.disconnect();
73         }
74     }
75 
76     static long beginTime;
77 
78     public static void main(String[] args) {
79 
80         beginTime = System.currentTimeMillis();
81 
82         ExecutorService es = Executors.newFixedThreadPool(10000);
83         OrderThreadTest mythread = new OrderThreadTest();
84         Thread thread = new Thread(mythread);
85         for (int i = 0; i < 1000001; i++) {
86             es.execute(thread);
87         }
88     }
89 }

  git地址 https://github.com/devinzhang0209/order_grabbing_demo.git

原文地址:https://www.cnblogs.com/DevinZhang1990/p/12795908.html