“秒杀”这一词多半出现在购物方面,但是又不单单只是购物,比如12306购票和学校抢课(大学生的痛苦)也可以看成一个秒杀。秒杀应该是一个“三高”,这个三高不是指高血脂,高血压和高血糖。而是指“高并发”,“高性能”和“高可用”。
假设有一百个库存商品需要抢购,可以试用mq进行削峰,防止宕机。
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>org.apache.rocketmqgroupId>
<artifactId>rocketmq-spring-boot-starterartifactId>
<version>2.2.3version>
dependency>
dependencies>
application.properties
相关配置#应用名
spring.application.name=seckill-server
server.port=8081
rocketmq.producer.groupName=${spring.application.name}
# mq的nameserver地址
rocketmq.producer.namesrvAddr=127.0.0.1:9876
@RestController
public class OpenOrderController{
int redundancy = 1000;
@Autowired
private RocketMQTemplate rocketMQTemplate;
@GetMapping("/secKill")
public String secKill(String id){
redundancy--;
if(redundancy > 0){
rocketMQTemplate.convertAndSend("seckill-topic", id);
return id+"正在抢购中请等待";
}else{
return "商品已售完";
}
}
}
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.apache.rocketmqgroupId>
<artifactId>rocketmq-spring-boot-starterartifactId>
<version>2.2.3version>
dependency>
application.properties
相关配置spring.application.name=seckill-consumer
server.port=8082
rocketmq.consumer.group=${spring.application.name}
rocketmq.name-server=127.0.0.1:9876
这里是真实的消息处理,springboot的监听处理极其简化了监听器的配置过程。
这里吧库存设置成一个简单的成员变量
,实际上在分布式项目中可能使用redis
同步真实库存。
在真实的场景中我们可以在这一步进行鉴权,是不是目标用户(黑户),生成订单等,发送短信(回调执行结果)等操作。由于已经由MQ进行了流量削峰,这一步可以进行更多的操作,有条不紊的进行业务逻辑的执行,
下面是示例代码:
@Component
@RocketMQMessageListener(topic = "seckill-topic", consumerGroup = "seckill-consumer-group")
public class SeckillConsumer implements RocketMQListener<String> {
int realInventory = 100;
@Override
public void onMessage(String id) {
// 处理秒杀请求
// 执行库存扣减和订单生成等操作
// 返回秒杀结果给用户
realInventory--;
if(realInventory >= 0){
System.out.println("当前商品剩余"+realInventory);
System.out.println(id + "抢到商品");
}else{
System.out.println("商品已售完");
}
}
}
public class HttpTest {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(1000);
for (int i = 0; i < 10000; i++) {
executorService.execute(() -> {
try {
URL url = new URL("http://localhost:8081/secKill?id=" + UUID.randomUUID().toString());
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.connect();
int responseCode = connection.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
System.out.println("Request success!");
} else {
System.out.println("Request failed!");
}
} catch (IOException e) {
e.printStackTrace();
}
});
}
executorService.shutdown();
}
}
这里抛出一个问题?
为什么会出现消息的乱序消费呢?如何实现顺序消费呢?
答:springboot默认是异步多线程消费的,无法保证顺序。
consumeMode = ConsumeMode.ORDERLY
ConsumeMode.ORDERLY的作用是让消费者单线程顺序接收消息,从而保证消息的全局顺序