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 // "\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