我們在平時的開發中經常會遇到秒殺,搶單的一些需求,這些系統開發時如果考慮不全面就可能會產生庫存不准,以及數據庫壓力大等問題。
本文將以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 // "\n" 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