设计高并发秒杀系统:保障稳定性与数据一致性



✨✨谢谢大家捧场,祝屏幕前的小伙伴们每天都有好运相伴左右,一定要天天开心哦!✨✨ 
🎈🎈作者主页: 喔的嘛呀🎈🎈

目录

引言

一. 系统架构设计

1. 系统架构图

二、 系统流程

三、流程实现(简单代码示范)

1、用户访问秒杀页面,点击秒杀按钮发起秒杀请求

前端部分(HTML + JavaScript):

后端部分(Java + Spring Boot):

2、前端负载均衡器将请求分发到多个前端应用服务器上。

Nginx配置示例:

Apache配置示例:

3、前端应用服务器验证用户身份,检查秒杀活动是否开始,是否已经结束。

前端应用服务器(使用Java + Spring Boot):

4、前端应用服务器请求缓存服务器获取商品信息和库存数量

前端应用服务器(Java + Spring Boot):

5、缓存负载均衡器将请求分发到多个缓存应用服务器上。

Redis Cluster配置示例:

Memcached Cluster配置示例:

缓存应用服务器(Java + Spring Boot):

6、缓存应用服务器返回商品信息和库存数量给前端应用服务器

缓存应用服务器(Java + Spring Boot):

7、前端应用服务器根据库存数量判断是否可以参与秒杀,如果可以则生成订单。

前端应用服务器(Java + Spring Boot):

8、前端应用服务器将订单信息发送到后端负载均衡器

前端应用服务器(Java + Spring Boot):

8、后端负载均衡器将请求分发到多个后端应用服务器上

后端负载均衡器(简化示例):

9、后端应用服务器消费订单信息,根据订单信息生成订单,并更新库存数量。

后端应用服务器(Java + Spring Boot):

10、后端应用服务器将订单信息写入数据库集群

后端应用服务器(Java + Spring Boot):

11、 分布式锁用于保护对库存数量的操作,确保数据的一致性。

后端应用服务器(Java + Spring Boot):

 总结



引言

设计高并发秒杀系统是一个挑战性的任务,需要考虑到系统的性能、稳定性和数据一致性。本文将介绍如何设计一个高并发的秒杀系统,使用消息队列和分布式锁来确保系统的稳定性和数据一致性。

一. 系统架构设计

我们的高并发秒杀系统将采用以下架构:

  • 前端页面:提供秒杀活动的入口,展示商品信息和剩余库存数量。
  • 后端服务:处理用户请求,验证用户身份,检查库存,并生成订单。
  • 数据库:存储商品信息和订单信息。
  • 缓存:缓存商品信息和库存数量,减少对数据库的访问压力。
  • 消息队列:用于异步处理订单生成和库存扣减操作,确保系统的高可用性和稳定性。
  • 分布式锁:用于保护对库存数量的操作,确保数据一致性。

1. 系统架构图

                                        +-------------------------------------+|             前端负载均衡器                |+-----------+--------------+--------------+|                          |+-----------v--------------+--------------+|        前端应用服务器           |       CDN      |+-----------+--------------+--------------+|                          |+----------------------+---------v----------+--------------+-------v--------+-------------+|            缓存层                |         |          后端应用服务器           |       数据库集群      |+-----------+--------------+         +-----------+--------------+--------------+|                          |                          |+-----------v--------------+         +-----------v--------------+--------------+|          缓存服务器           |         |         消息队列              |       主数据库         |+-----------+--------------+         +-----------+--------------+--------------+|                          |                          |+-----------v--------------+         +-----------v--------------+|        缓存存储(Redis)    |         |     业务数据存储(MySQL)    |+---------------------------+         +---------------------------+

在这个架构中:

  • 前端负载均衡器:负责将用户请求分发到多个前端应用服务器和CDN上,实现请求的负载均衡,如Nginx。
  • 前端应用服务器:处理用户请求,包括验证用户身份、检查秒杀活动是否开始、是否已经结束等,如Tomcat。
  • CDN:用于加速网页内容传输,提高访问速度和用户体验。
  • 缓存层:使用缓存层存储热点数据,减轻数据库压力,如Redis。
  • 缓存服务器:用于存储缓存数据,如Redis服务器。
  • 消息队列:用于处理订单生成和库存扣减等业务逻辑,提高系统的并发处理能力,如RabbitMQ。
  • 业务数据存储:存储业务数据,如MySQL数据库集群。

通过以上架构设计,可以实现一个高并发的秒杀系统,保证系统的性能、稳定性和数据一致性,为用户提供良好的秒杀体验。

二、 系统流程

  1. 用户访问秒杀页面,点击秒杀按钮发起秒杀请求。
  2. 前端负载均衡器将请求分发到多个前端应用服务器上。
  3. 前端应用服务器验证用户身份,检查秒杀活动是否开始,是否已经结束。
  4. 前端应用服务器请求缓存服务器获取商品信息和库存数量。
  5. 缓存负载均衡器将请求分发到多个缓存应用服务器上。
  6. 缓存应用服务器返回商品信息和库存数量给前端应用服务器。
  7. 前端应用服务器根据库存数量判断是否可以参与秒杀,如果可以则生成订单。
  8. 前端应用服务器将订单信息发送到后端负载均衡器。
  9. 后端负载均衡器将请求分发到多个后端应用服务器上。
  10. 后端应用服务器消费订单信息,根据订单信息生成订单,并更新库存数量。
  11. 后端应用服务器将订单信息写入数据库集群。
  12. 分布式锁用于保护对库存数量的操作,确保数据的一致性。

三、流程实现(简单代码示范)

1、用户访问秒杀页面,点击秒杀按钮发起秒杀请求

流程如下:

前端部分(HTML + JavaScript):

<!DOCTYPE html>
<html>
<head><title>秒杀页面</title>
</head>
<body><h1>欢迎参加秒杀活动!</h1><button id="seckillButton">秒杀按钮</button><div id="result"></div><script>document.getElementById("seckillButton").addEventListener("click", function() {// 模拟用户ID和商品IDvar userId = 123;var productId = 456;fetch("/seckill", {method: "POST",headers: {"Content-Type": "application/json"},body: JSON.stringify({ userId: userId, productId: productId })}).then(response => response.json()).then(data => {document.getElementById("result").innerText = data.message;});});</script>
</body>
</html>

后端部分(Java + Spring Boot):

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;@SpringBootApplication
public class SeckillApplication {private boolean seckillStarted = true; // 模拟秒杀活动是否开始private int stock = 10; // 模拟商品库存public static void main(String[] args) {SpringApplication.run(SeckillApplication.class, args);}@RestControllerpublic class SeckillController {@PostMapping("/seckill")public String seckill(@RequestBody SeckillRequest request) {if (!seckillStarted) {return "秒杀活动未开始";}if (stock <= 0) {return "商品已售罄";}// 模拟生成订单stock--;return "秒杀成功";}}public static class SeckillRequest {private Long userId;private Long productId;// 省略getter和setter方法}
}

2、前端负载均衡器将请求分发到多个前端应用服务器上。

前端负载均衡器将请求分发到多个前端应用服务器上,可以通过配置负载均衡器(如Nginx、Apache等)来实现。以下是一个简单的示例:

假设有两台前端应用服务器,分别运行在不同的端口上(假设为8001和8002)。

Nginx配置示例:

upstream frontends {server 127.0.0.1:8001;server 127.0.0.1:8002;
}server {listen 80;server_name example.com;location / {proxy_pass http://frontends;}
}

在这个示例中,Nginx配置了一个名为frontends的upstream,其中包含两台前端应用服务器的地址和端口。当收到用户请求时,Nginx会根据一定的负载均衡算法(如轮询、权重等)将请求转发到这些服务器上。

Apache配置示例:

<Proxy balancer://frontends>BalancerMember http://127.0.0.1:8001BalancerMember http://127.0.0.1:8002
</Proxy><VirtualHost *:80>ServerName example.comProxyPass / balancer://frontends/ProxyPassReverse / balancer://frontends/
</VirtualHost>

在这个示例中,Apache配置了一个名为frontends的负载均衡器,其中包含两台前端应用服务器的地址和端口。当收到用户请求时,Apache会将请求转发到这些服务器上,实现负载均衡。

3、前端应用服务器验证用户身份,检查秒杀活动是否开始,是否已经结束。

以下是一个简单的示例,展示前端应用服务器验证用户身份,检查秒杀活动是否开始或结束的过程:

前端应用服务器(使用Java + Spring Boot):

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;@SpringBootApplication
public class FrontendApplication {private boolean seckillStarted = true; // 模拟秒杀活动是否开始private boolean seckillEnded = false; // 模拟秒杀活动是否结束public static void main(String[] args) {SpringApplication.run(FrontendApplication.class, args);}@RestControllerpublic class SeckillController {@PostMapping("/seckill")public String seckill(@RequestBody SeckillRequest request) {// 验证用户身份,假设用户身份验证通过if (!isUserAuthenticated(request.getUserId())) {return "用户身份验证失败";}// 检查秒杀活动是否开始或结束if (!isSeckillStarted()) {return "秒杀活动未开始";}if (isSeckillEnded()) {return "秒杀活动已结束";}// 处理秒杀请求return "秒杀请求处理中";}private boolean isUserAuthenticated(Long userId) {// 省略实际的用户身份验证逻辑return true;}private boolean isSeckillStarted() {// 省略实际的秒杀活动开始检查逻辑return seckillStarted;}private boolean isSeckillEnded() {// 省略实际的秒杀活动结束检查逻辑return seckillEnded;}}public static class SeckillRequest {private Long userId;private Long productId;// 省略getter和setter方法}
}

在这个示例中,前端应用服务器通过Spring Boot框架实现了一个简单的/seckill接口,接收用户的秒杀请求。在处理请求之前,先验证用户身份,然后检查秒杀活动是否开始或结束。如果用户身份验证失败,则返回"用户身份验证失败";如果秒杀活动未开始,则返回"秒杀活动未开始";如果秒杀活动已结束,则返回"秒杀活动已结束";否则处理秒杀请求。

4、前端应用服务器请求缓存服务器获取商品信息和库存数量

前端应用服务器(Java + Spring Boot):

首先,需要在前端应用服务器中配置缓存服务器的地址和端口信息,以便发送请求。

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;@SpringBootApplication
public class FrontendApplication {private static final String CACHE_SERVER_URL = "http://cache-server:8080"; // 缓存服务器地址public static void main(String[] args) {SpringApplication.run(FrontendApplication.class, args);}@RestControllerpublic class SeckillController {private final RestTemplate restTemplate = new RestTemplate();@PostMapping("/seckill")public String seckill(@RequestBody SeckillRequest request) {// 请求缓存服务器获取商品信息和库存数量String url = CACHE_SERVER_URL + "/product/" + request.getProductId();ProductInfo productInfo = restTemplate.getForObject(url, ProductInfo.class);if (productInfo == null) {return "获取商品信息失败";}// 处理秒杀请求return "请求处理中";}}public static class SeckillRequest {private Long userId;private Long productId;// 省略getter和setter方法}public static class ProductInfo {private Long productId;private String productName;private int stock;// 省略getter和setter方法}
}

在这个示例中,前端应用服务器使用RestTemplate发送GET请求到缓存服务器的/product/{productId}路由,获取商品信息和库存数量。如果成功获取到商品信息,则继续处理秒杀请求;否则返回"获取商品信息失败"。

5、缓存负载均衡器将请求分发到多个缓存应用服务器上。

缓存负载均衡器将请求分发到多个缓存应用服务器上,可以通过配置缓存负载均衡器(如Redis Cluster、Memcached Cluster等)来实现。以下是一个简单的示例:

假设有两台缓存应用服务器,分别运行在不同的地址和端口上(假设为cache-server1:6379和cache-server2:6379)。

Redis Cluster配置示例:

# 启动redis-server节点1
redis-server --port 6379# 启动redis-server节点2
redis-server --port 6380# 创建Redis Cluster
redis-cli --cluster create cache-server1:6379 cache-server2:6379 --cluster-replicas 1

在这个示例中,我们创建了一个包含两个主节点和一个从节点的Redis Cluster。缓存负载均衡器可以将请求分发到这个Redis Cluster上,实现缓存的负载均衡。

Memcached Cluster配置示例:

# 安装memcached
sudo apt-get update
sudo apt-get install memcached# 启动memcached节点1
memcached -p 11211 -d# 启动memcached节点2
memcached -p 11212 -d# 配置memcached集群
echo "add 127.0.0.1 11212" | nc 127.0.0.1 11211

在这个示例中,我们创建了一个包含两个节点的Memcached Cluster。缓存负载均衡器可以将请求分发到这个Memcached Cluster上,实现缓存的负载均衡。

在实际生产环境中,需要根据具体需求和负载情况来配置缓存负载均衡器,并保证缓存服务器之间的数据同步和一致性。

6、缓存应用服务器返回商品信息和库存数量给前端应用服务器

缓存应用服务器(Java + Spring Boot):

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;import java.util.HashMap;
import java.util.Map;@SpringBootApplication
public class CacheServerApplication {private static final Map<Long, ProductInfo> productCache = new HashMap<>();public static void main(String[] args) {SpringApplication.run(CacheServerApplication.class, args);}@RestControllerpublic class CacheController {@GetMapping("/product/{productId}")public ProductInfo getProductInfo(@PathVariable Long productId) {// 从缓存中获取商品信息return productCache.getOrDefault(productId, new ProductInfo(productId, "Unknown", 0));}}public static class ProductInfo {private Long productId;private String productName;private int stock;public ProductInfo(Long productId, String productName, int stock) {this.productId = productId;this.productName = productName;this.stock = stock;}// 省略getter和setter方法}
}

在这个示例中,缓存应用服务器通过Spring Boot框架实现了一个简单的/cache接口,接收前端应用服务器的商品信息请求。根据商品ID从缓存中获取商品信息,如果缓存中不存在该商品信息,则返回一个未知商品信息。

6、缓存应用服务器返回商品信息和库存数量给前端应用服务器

缓存应用服务器(Java + Spring Boot):

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;import java.util.HashMap;
import java.util.Map;@SpringBootApplication
public class CacheServerApplication {private static final Map<Long, ProductInfo> productCache = new HashMap<>();public static void main(String[] args) {SpringApplication.run(CacheServerApplication.class, args);}@RestControllerpublic class CacheController {@GetMapping("/product/{productId}")public ProductInfo getProductInfo(@PathVariable Long productId) {// 从缓存中获取商品信息return productCache.getOrDefault(productId, new ProductInfo(productId, "Unknown", 0));}}public static class ProductInfo {private Long productId;private String productName;private int stock;public ProductInfo(Long productId, String productName, int stock) {this.productId = productId;this.productName = productName;this.stock = stock;}// 省略getter和setter方法}
}

在这个示例中,缓存应用服务器通过Spring Boot框架实现了一个简单的/cache接口,接收前端应用服务器的商品信息请求。根据商品ID从缓存中获取商品信息,如果缓存中不存在该商品信息,则返回一个未知商品信息。

7、前端应用服务器根据库存数量判断是否可以参与秒杀,如果可以则生成订单。

前端应用服务器(Java + Spring Boot):

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;import java.util.HashMap;
import java.util.Map;@SpringBootApplication
public class FrontendApplication {private static final Map<Long, Integer> stockMap = new HashMap<>(); // 模拟商品库存private static final Map<Long, Boolean> seckillMap = new HashMap<>(); // 模拟秒杀活动是否开始private static final Map<Long, Boolean> seckillEndMap = new HashMap<>(); // 模拟秒杀活动是否结束private static final Map<Long, Long> userOrders = new HashMap<>(); // 模拟用户订单public static void main(String[] args) {// 初始化商品库存stockMap.put(1L, 10);// 初始化秒杀活动状态seckillMap.put(1L, true);// 初始化秒杀活动结束状态seckillEndMap.put(1L, false);SpringApplication.run(FrontendApplication.class, args);}@RestControllerpublic class SeckillController {@PostMapping("/seckill")public String seckill(@RequestBody SeckillRequest request) {Long productId = request.getProductId();Long userId = request.getUserId();// 判断秒杀活动是否开始或结束if (!seckillMap.getOrDefault(productId, false)) {return "秒杀活动未开始";}if (seckillEndMap.getOrDefault(productId, true)) {return "秒杀活动已结束";}// 判断库存是否足够Integer stock = stockMap.getOrDefault(productId, 0);if (stock <= 0) {return "商品已售罄";}// 生成订单userOrders.put(userId, productId);// 更新库存stockMap.put(productId, stock - 1);return "秒杀成功";}}public static class SeckillRequest {private Long userId;private Long productId;// 省略getter和setter方法}
}

在这个示例中,前端应用服务器根据商品ID获取库存数量,判断秒杀活动是否开始或结束,以及库存是否足够。如果满足条件,则生成订单并更新库存,返回秒杀成功;否则返回相应的错误信息。

8、前端应用服务器将订单信息发送到后端负载均衡器

前端应用服务器(Java + Spring Boot):

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;@SpringBootApplication
public class FrontendApplication {private static final String LOAD_BALANCER_URL = "http://backend-load-balancer";public static void main(String[] args) {SpringApplication.run(FrontendApplication.class, args);}@RestControllerpublic class OrderController {private final RestTemplate restTemplate = new RestTemplate();@PostMapping("/order")public String placeOrder(@RequestBody OrderRequest request) {// 向后端负载均衡器发送订单信息String url = LOAD_BALANCER_URL + "/order";return restTemplate.postForObject(url, request, String.class);}}public static class OrderRequest {private Long orderId;private Long productId;private Long userId;// 省略getter和setter方法}
}

在这个示例中,前端应用服务器通过Spring Boot框架实现了一个简单的/order接口,接收订单信息,并使用RestTemplate将订单信息发送到后端负载均衡器的/order接口。

8、后端负载均衡器将请求分发到多个后端应用服务器上

后端负载均衡器(简化示例):

在实际应用中,后端负载均衡器可以使用诸如Nginx、HAProxy、AWS ELB等工具来实现。这里简化为直接在Java中模拟负载均衡器的行为。

import java.util.ArrayList;
import java.util.List;
import java.util.Random;public class LoadBalancer {private List<String> backendServers;public LoadBalancer() {backendServers = new ArrayList<>();backendServers.add("http://backend-server1");backendServers.add("http://backend-server2");// 添加更多后端服务器...}public String selectBackendServer() {// 模拟负载均衡算法,这里简单使用随机选择Random random = new Random();int index = random.nextInt(backendServers.size());return backendServers.get(index);}public static void main(String[] args) {LoadBalancer loadBalancer = new LoadBalancer();// 模拟请求分发给后端服务器for (int i = 0; i < 10; i++) {String backendServer = loadBalancer.selectBackendServer();System.out.println("Request sent to: " + backendServer);}}
}

在这个示例中,LoadBalancer类模拟了一个简单的负载均衡器,它维护了一个后端服务器列表,并实现了一个简单的随机选择算法来选择后端服务器。在实际应用中,需要根据实际情况选择合适的负载均衡算法。

9、后端应用服务器消费订单信息,根据订单信息生成订单,并更新库存数量。

后端应用服务器(Java + Spring Boot):

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;import java.util.HashMap;
import java.util.Map;@SpringBootApplication
public class BackendApplication {private static final Map<Long, Integer> stockMap = new HashMap<>();public static void main(String[] args) {// 初始化库存数量stockMap.put(1L, 10);SpringApplication.run(BackendApplication.class, args);}@RestControllerpublic class OrderController {@PostMapping("/order")public String createOrder(@RequestBody OrderRequest request) {Long orderId = request.getOrderId();Long productId = request.getProductId();Long userId = request.getUserId();// 判断库存是否足够Integer stock = stockMap.getOrDefault(productId, 0);if (stock <= 0) {return "商品已售罄";}// 生成订单String orderInfo = "订单信息:订单号-" + orderId + ",商品ID-" + productId + ",用户ID-" + userId;// 更新库存数量stockMap.put(productId, stock - 1);return "生成订单成功:" + orderInfo;}}public static class OrderRequest {private Long orderId;private Long productId;private Long userId;// 省略getter和setter方法}
}

在这个示例中,后端应用服务器通过Spring Boot框架实现了一个简单的/order接口,接收订单信息,并根据订单信息生成订单,并更新库存数量。如果库存不足,则返回"商品已售罄"。

10、后端应用服务器将订单信息写入数据库集群

将订单信息写入数据库集群是一个关键的步骤,需要考虑到数据的一致性和高可用性。下面是一个详细全面的示例,演示如何将订单信息写入数据库集群:

后端应用服务器(Java + Spring Boot):

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;@SpringBootApplication
public class BackendApplication {public static void main(String[] args) {SpringApplication.run(BackendApplication.class, args);}@RestControllerpublic class OrderController {@PostMapping("/order")public String createOrder(@RequestBody OrderRequest request) {Long orderId = request.getOrderId();Long productId = request.getProductId();Long userId = request.getUserId();// 生成订单String orderInfo = "订单信息:订单号-" + orderId + ",商品ID-" + productId + ",用户ID-" + userId;// 将订单信息写入数据库集群boolean success = writeToDatabase(orderInfo);if (success) {return "生成订单成功:" + orderInfo;} else {return "生成订单失败:" + orderInfo;}}private boolean writeToDatabase(String orderInfo) {// 连接数据库集群 这里为了方便演示没有用yaml进行配置String url = "jdbc:mysql://database-cluster:3306/database";String user = "user";String password = "password";try (Connection connection = DriverManager.getConnection(url, user, password)) {// 写入订单信息String sql = "INSERT INTO orders (order_info) VALUES (?)";try (PreparedStatement statement = connection.prepareStatement(sql)) {statement.setString(1, orderInfo);int rowsAffected = statement.executeUpdate();return rowsAffected > 0;}} catch (SQLException e) {e.printStackTrace();return false;}}}public static class OrderRequest {private Long orderId;private Long productId;private Long userId;// 省略getter和setter方法}
}

11、 分布式锁用于保护对库存数量的操作,确保数据的一致性。

分布式锁用于保护对库存数量的操作,确保数据的一致性。下面是一个详细全面的示例,演示如何使用Redis实现分布式锁来保护对库存数量的操作:

后端应用服务器(Java + Spring Boot):

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import redis.clients.jedis.Jedis;@SpringBootApplication
public class BackendApplication {private static final String REDIS_HOST = "localhost";private static final int REDIS_PORT = 6379;private static final String LOCK_KEY = "inventory_lock";private static final String INVENTORY_KEY = "inventory";public static void main(String[] args) {SpringApplication.run(BackendApplication.class, args);}@RestControllerpublic class OrderController {@PostMapping("/order")public String createOrder(@RequestBody OrderRequest request) {Long orderId = request.getOrderId();Long productId = request.getProductId();Long userId = request.getUserId();// 获取分布式锁try (Jedis jedis = new Jedis(REDIS_HOST, REDIS_PORT)) {boolean locked = false;while (!locked) {locked = jedis.setnx(LOCK_KEY, "locked") == 1;if (!locked) {try {Thread.sleep(100);} catch (InterruptedException e) {Thread.currentThread().interrupt();}}}// 获取锁成功,处理订单int stock = Integer.parseInt(jedis.get(INVENTORY_KEY));if (stock > 0) {// 生成订单String orderInfo = "订单信息:订单号-" + orderId + ",商品ID-" + productId + ",用户ID-" + userId;System.out.println("生成订单成功:" + orderInfo);// 更新库存数量jedis.set(INVENTORY_KEY, String.valueOf(stock - 1));} else {System.out.println("商品已售罄");}// 释放锁jedis.del(LOCK_KEY);}return "订单处理完成";}}public static class OrderRequest {private Long orderId;private Long productId;private Long userId;// 省略getter和setter方法}
}

在这个示例中,后端应用服务器通过Spring Boot框架实现了一个简单的/order接口,接收订单信息,并使用Redis实现分布式锁来保护对库存数量的操作。当多个请求同时到达时,只有一个请求能够获得锁并处理订单,其他请求需要等待锁释放后才能继续处理。这样可以保证对库存数量的操作是原子性的,从而确保数据的一致性。

 总结

设计高并发的秒杀系统需要考虑到多个方面,包括系统架构、技术选型、流程设计和代码实现等。通过合理的架构设计和技术选型,可以实现一个稳定高效的秒杀系统,为用户提供良好的购物体验。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/366930.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

简单实现Anaconda/Miniforge虚拟环境的克隆和迁移

简单实现Anaconda/Miniforge虚拟环境的克隆和迁移 一、问题描述一、方式一&#xff1a;使用命令克隆二、方式二&#xff1a;直接复制粘贴 欢迎学习交流&#xff01; 邮箱&#xff1a; z…1…6.com 网站&#xff1a; https://zephyrhours.github.io/ 一、问题描述 使用Anaconda…

昇思25天学习打卡营第7天|Pix2Pix实现图像转换

文章目录 昇思MindSpore应用实践基于MindSpore的Pix2Pix图像转换1、Pix2Pix 概述2、U-Net架构定义UNet Skip Connection Block 2、生成器部分3、基于PatchGAN的判别器4、Pix2Pix的生成器和判别器初始化5、模型训练6、模型推理 Reference 昇思MindSpore应用实践 本系列文章主要…

无忧易售升级:产品视频翻译支持,拓宽全球市场边界

在电商内容营销迈入视频时代的今天&#xff0c;无忧易售ERP推出针对OZON、Wish、TikTok、Wildberries&#xff08;野莓&#xff09;四大平台的产品视频翻译功能&#xff0c;彻底打破语言壁垒&#xff0c;让全球卖家的商品故事&#xff0c;以更生动、更直观的方式&#xff0c;触…

Linux指定文件权限的两种方式-符号与八进制数方式示例

一、指定文件权限可用的两种方式&#xff1a; 对于八进制数指定的方式&#xff0c;文件权限字符代表的有效位设为‘1’&#xff0c;即“rw-”、“rw-”、“r--”&#xff0c;以二进制表示为“110”、“110”、“100”&#xff0c;再转换为八进制6、6、4&#xff0c;所以777代表…

如何寻找一个领域的顶级会议,并且判断这个会议的影响力?

如何寻找一个领域的顶级会议&#xff0c;并且判断这个会议的影响力&#xff1f; 会议之眼 快讯 很多同学都在问&#xff1a;学术会议不是期刊&#xff0c;即使被SCI检索&#xff0c;也无法查询影响因子。那么如何知道各个领域的顶级会议&#xff0c;并对各个会议有初步了解呢…

Redis主从复制、哨兵以及Cluster集群

目录 1.Redis高可用 2.Redis主从复制 2.1 主从复制的作用 2.2 主从复制流程 2.3 搭建Redis 主从复制 ​3.Redis哨兵模式 3.1 哨兵模式概述 3.2 哨兵模式的作用 3.3 故障转移机制 ​3.4 主节点的选举 3.5 搭建Redis哨兵模式 4. Redis 群集模式 4.1 Redis集群的数据分…

VS2022+Qt+OpenCV Debug模式下,循环中格式转换引起的内存异常问题 debug_heap.cpp

文章目录 前言一、问题二、报错1.提示图片2.提示堆栈3.反汇编位置 三、解决办法总结 前言 最近在使用VS2022&#xff0c;C&#xff0c;OpenCV&#xff0c;Qt开发时&#xff0c;遇到了一个疑难杂症-在循环中执行字符串格式转换会触发内存异常&#xff0c;经过痛苦的排查过程&am…

python自动化运维--DNS处理模块dnspython

1.dnspython介绍 dnspython是Pyhton实现的一个DNS工具包&#xff0c;他几乎支持所有的记录类型&#xff0c;可以用于查询、传输并动态更新ZONE信息&#xff0c;同事支持TSIG&#xff08;事物签名&#xff09;验证消息和EDNS0&#xff08;扩展DNS&#xff09;。在系统管理方面&a…

从零开始实现大语言模型(二):文本数据处理

1. 前言 神经网络不能直接处理自然语言文本&#xff0c;文本数据处理的核心是做tokenization&#xff0c;将自然语言文本分割成一系列tokens。 本文介绍tokenization的基本原理&#xff0c;OpenAI的GPT系列大语言模型使用的tokenization方法——字节对编码(BPE, byte pair en…

认识一下HttpMessageHandler处理管道

[S1208]HttpClient的默认管道结构 接下来我们通过如下的演示程序使用IHttpClientFactory工厂创建了 一个HttpClient对象&#xff0c;并查看其管道依次由哪些类型的HttpMessageHandler对象组成。如代码片段所示&#xff0c;我们定义了一个辅助方法PrintPipeline方法以递归的形式…

C++ ariac2 Windows库编译

cd "F:\\aria2" gmp-6.1.2.tar.lz expat-2.2.0.tar.bz2 sqlite-autoconf-3160200.tar.gz zlib-1.2.11.tar.gz c-ares-1.12.0.tar.gz libssh2-1.8.0.tar.gz --enable-libaria2 --enable-static libgnutls-dev&#xff08;对于HTTPS&#xff0c;BitTorrent&#xff0…

vue+js实现鼠标右键页面时在鼠标位置出现弹窗

首先是弹窗元素 <div class"tanchuang move-win1"id"tanchuang1"><el-button>111</el-button></div>然后在需要弹窗的地方监听点击事件&#xff0c;可以将这个方法写在页面载入事件中 // 获取弹窗元素 var tanchuang document.…

【开发篇】明明配置跨域声明,为什么却仍可以发送HTTP请求

一、问题 在SpringBoot项目中&#xff0c;明确指定仅允许指定网站跨域访问&#xff1a; 为什么开发人员却仍旧可以通过HTTP工具调用接口&#xff1f; 二、为什么 在回答这个问题之前&#xff0c;我们首先要了解一下什么是CORS&#xff01; 1、什么是CORS CORS的全称为跨域资源…

springcloud-config服务器,同样的配置在linux环境下不生效

原本在windows下能争取的获取远程配置但是部署到linux上死活都没有内容&#xff0c;然后开始了远程调试&#xff0c;这里顺带讲解下获取配置文件如果使用的是Git源&#xff0c;config service是如何响应接口并返回配置信息的。先说问题&#xff0c;我的服务名原本是abc-abc-abc…

文生图功能介绍

Stable Diffusion WebUI&#xff08;SD WebUI&#xff09;及文生图功能介绍 一、引言 随着人工智能技术的飞速发展&#xff0c;AI绘画作为一种新兴的艺术形式&#xff0c;逐渐走入人们的视野。Stable Diffusion WebUI&#xff08;简称SD WebUI&#xff09;作为AI绘画领域的重…

Rust: polars行遍历,从dataframe到struct及Bar设计比较

pandas提供了iterrows()、itertuples()、apply等行遍历的方式&#xff0c;还是比较方便的。 polars的列操作功能非常强大&#xff0c;这个在其官网上有详细的介绍。由于polars底层的arrow是列存储模式&#xff0c;行操作效率低下&#xff0c;官方也不推荐以行方式进行数据操作。…

通过shell脚本创建MySQl数据库

通过shell脚本创建数据库 #!/bin/bashserverIP10.1.1.196 SERVER_NAMEecho $serverIP | cut -d . -f4cat<<EOF>db.sql drop database if exists ${SERVER_NAME}_scheduler; drop database if exists ${SERVER_NAME}_kms; drop database if exists ${SERVER_NAME}_uim…

修改CentOS7 yum源

修改CentOS默认yum源为阿里镜像源 备份系统自带yum源配置文件 mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup 下载ailiyun的yum源配置文件 CentOS7 yum源如下&#xff1a; wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun…

【0299】Postgres内核之哈希表(Hash Tables)

0. 哈希表(Hash Tables) 哈希表是 一种用于存储键值对的数据结构。与使用索引号访问元素的基本数组不同,哈希表使用键来查找表条目。这使得数据管理对于用户来说更易于管理,因为按属性对数据条目进行分类比按它们在一个巨大的列表中的数量更容易。 在 C++ 中,我们将哈希…

单向链表结构

链表结构简介 链表结构是一种用比较特殊的数据结构类型&#xff0c;它也是线性数据结构中的一种&#xff0c;但是与栈结构等线性数据结构不同&#xff0c;它的内部结构并不是一个简单的存储空间&#xff0c;而是一个带有指向性质的单元。要理解链表结构要弄清楚两个问题&#x…