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

✨✨谢谢大家捧场,祝屏幕前的小伙伴们每天都有好运相伴左右,一定要天天开心哦!✨✨
🎈🎈作者主页: 喔的嘛呀🎈🎈
目录
引言
一. 系统架构设计
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、用户访问秒杀页面,点击秒杀按钮发起秒杀请求
流程如下:
前端部分(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实现分布式锁来保护对库存数量的操作。当多个请求同时到达时,只有一个请求能够获得锁并处理订单,其他请求需要等待锁释放后才能继续处理。这样可以保证对库存数量的操作是原子性的,从而确保数据的一致性。
总结
设计高并发的秒杀系统需要考虑到多个方面,包括系统架构、技术选型、流程设计和代码实现等。通过合理的架构设计和技术选型,可以实现一个稳定高效的秒杀系统,为用户提供良好的购物体验。
相关文章:

设计高并发秒杀系统:保障稳定性与数据一致性
✨✨谢谢大家捧场,祝屏幕前的小伙伴们每天都有好运相伴左右,一定要天天开心哦!✨✨ 🎈🎈作者主页: 喔的嘛呀🎈🎈 目录 引言 一. 系统架构设计 1. 系统架构图 二、 系统流程 三…...

从源码到成品:直播电商与短视频带货APP的开发之路
本篇文章,笔者将详细探讨从源码到成品,直播电商与短视频带货APP的开发过程。 一、市场调研与需求分析 通过用户调研,可以确定目标用户群体的需求,从而为产品的功能设计提供依据。 1.1市场分析 消费者对视频内容的偏好ÿ…...
C++OCR API减轻人们文字录入的负担
曾几何时,许多大企业会设立文字录入专员的岗位。相信有不少人第一份实习工作就是录入资料,文档、发票、证件等形形色色的文件堆积如山,日积月累的敲击键盘,一张一张的录入电脑系统。这种工作是枯燥的,可以练就文字录入…...

web安全基础名词概念
本节内容根据小迪安全讲解制作 第一天 域名: 1.1什么是域名? 网域名称(英语:Domain Name,简称:Domain),简称域名、网域,是由一串用点分隔的字符组成的互联网上某一台计算机或计算机组的名称&a…...

ctfshow-web入门-文件上传(web161、web162、web163)远程包含
目录 1、web161 2、web162 3、web163 1、web161 先传配置文件,可以上传成功 因为我前面给的 .user.ini 都是带了图片头 GIF89a 的,前面的题这个图片头可以去掉,但是在这里如果去掉是不行的。 因此后面上传的东西我们都带上这个图片头&…...

【Gradle】(三)详细聊聊依赖管理:坐标、依赖配置、依赖传递、依赖冲突
文章目录 1.概述2.依赖管理2.1.坐标2.2.依赖的基本概念2.3.依赖配置(Dependency configurations)2.3.1.依赖路径2.3.2.依赖配置与依赖路径的关联 2.4.依赖传递2.4.1.准备工作2.4.2.运行时依赖传递jar包生成与依赖配置依赖树打印使用 Dependency Analyzer…...
C#数据类型:object、var和dynamic的比较与应用
在C#中,object、var和dynamic虽然常被提及为可以处理多种数据类型的“万能”方式,但它们各自有不同的应用场景、特性和优缺点。下面我将用通俗易懂的方式结合示例来详细说明这三者的区别与应用。 1. object 定义与应用场景:object是C#中所有…...
【面试题】MySQL(第一篇)
1. MySQL是什么? MySQL是一种开源的关系型数据库管理系统(RDBMS),它使用结构化查询语言(SQL)进行数据管理。MySQL具有高性能、可靠性、可扩展性和兼容性等特点,广泛应用于Web应用开发中。 2. …...
SQL Server集成服务(SSIS):数据集成的瑞士军刀
SQL Server集成服务(SSIS):数据集成的瑞士军刀 在数据仓库和大数据处理领域,SQL Server集成服务(SSIS)扮演着至关重要的角色。作为微软SQL Server套件的一部分,SSIS提供了一套强大的工具&#…...

鸿蒙开发HarmonyOS NEXT (三) 熟悉ArkTs (上)
一、自定义组件 1、自定义组件 自定义组件,最基础的结构如下: Component struct Header {build() {} } 提取头部标题部分的代码,写成自定义组件。 1、新建ArkTs文件,把Header内容写好。 2、在需要用到的地方,导入…...

值传递与引用传递:理解Java中的参数传递机制
值传递与引用传递:理解Java中的参数传递机制 1、值传递(Call by Value)2、引用传递(Call by Reference)3、总结 💖The Begin💖点点关注,收藏不迷路💖 值传递和引用传递的…...

Qt常用基础控件总结—带边框的部件(QFrame和QLabel)
带边框的部件 框架控件QFrame类 QFrame类介绍 QFrame 类是带有边框的部件的基类,带边框部件的特点是有一个明显的边框,QFrame类就是用来实现边框的不同效果的(把这种效果称为边框样式),所有继承自 QFrame 的子类都可以使用 QFrame 类实现的效果。 部件通常是矩形的(其他…...

太多项会毁了回归
「AI秘籍」系列课程: 人工智能应用数学基础 人工智能Python基础 人工智能基础核心知识 人工智能BI核心知识 人工智能CV核心知识 多项式回归的过度拟合及其避免方法 通过添加现有特征的幂,多项式回归可以帮助你充分利用数据集。它允许我们甚至使用简…...
python的魔法方法
python类中的self是什么? 对象的方法都会有一个self参数,类比于c,self就相当于c的this指针。 由一个类可以生成无数个对象,当一个对象的方法被调用时,对象会讲自身的引用作为第一个参数传给该方法,那么pyt…...
[Vue3 + TS + Vite] 获取网页选中内容的字符串格式或HTML格式
获取网页选中内容的字符串格式 let selected_text_by_mouse: any// 获取选中的文字 const mouse_selected_text(event:MouseEvent)>{const selection window.getSelection();if(selection && selection.rangeCount > 0){const content selection.toString();s…...

线程安全的原因及解决方法
什么是线程安全问题 线程安全问题指的是在多线程编程环境中,由于多个线程共享数据或资源,并且这些线程对共享数据或资源的访问和操作没有正确地同步,导致数据的不一致、脏读、不可重复读、幻读等问题。线程安全问题的出现,通常是…...

微信零钱明细删除了还能恢复吗?图文教程解析
在日常使用微信支付的过程中,查看零钱明细是管理个人财务的一项重要操作。然而,有时候我们可能会不小心删除了这些明细,导致无法追踪资金流动和消费记录。那么,微信零钱明细删除了还能恢复吗?这是许多用户关心的问题。…...

mp4视频太大怎么压缩不影响画质,mp4文件太大怎么变小且清晰度高
在数字化时代,我们常常面临视频文件过大的问题。尤其是mp4格式的视频,文件大小往往令人望而却步。那么,如何在不影响画质的前提下,有效地压缩mp4视频呢?本文将为您揭秘几种简单实用的压缩技巧。 在分享和存储视频时&am…...

【线程同步-2】
同步方法及同步块 接上期三大不安全案例,本期将介绍同步方法和同步块,以期达到安全的目的。 车站买票:加入了synchronized 同步方法 package syn; //不安全的买票 //线程不安全,有负数 public class UnsafeBuyTicket {publi…...

【别再为可视化工具付费了!】财务报表免费制作软件,这款免费可视化工具的功能超乎想象
会计工作中,关键一步就是把那些繁杂的财务数据整理成清晰易懂的财务报表,这就像是把一堆拼图块变成一幅完整的图画。山海鲸可视化这款免费工具,支持实时数据刷新,能够随时随地更新你的财务数据,确保你拿到的永远是最新…...

linux之kylin系统nginx的安装
一、nginx的作用 1.可做高性能的web服务器 直接处理静态资源(HTML/CSS/图片等),响应速度远超传统服务器类似apache支持高并发连接 2.反向代理服务器 隐藏后端服务器IP地址,提高安全性 3.负载均衡服务器 支持多种策略分发流量…...

Spark 之 入门讲解详细版(1)
1、简介 1.1 Spark简介 Spark是加州大学伯克利分校AMP实验室(Algorithms, Machines, and People Lab)开发通用内存并行计算框架。Spark在2013年6月进入Apache成为孵化项目,8个月后成为Apache顶级项目,速度之快足见过人之处&…...

Redis相关知识总结(缓存雪崩,缓存穿透,缓存击穿,Redis实现分布式锁,如何保持数据库和缓存一致)
文章目录 1.什么是Redis?2.为什么要使用redis作为mysql的缓存?3.什么是缓存雪崩、缓存穿透、缓存击穿?3.1缓存雪崩3.1.1 大量缓存同时过期3.1.2 Redis宕机 3.2 缓存击穿3.3 缓存穿透3.4 总结 4. 数据库和缓存如何保持一致性5. Redis实现分布式…...

前端导出带有合并单元格的列表
// 导出async function exportExcel(fileName "共识调整.xlsx") {// 所有数据const exportData await getAllMainData();// 表头内容let fitstTitleList [];const secondTitleList [];allColumns.value.forEach(column > {if (!column.children) {fitstTitleL…...

Keil 中设置 STM32 Flash 和 RAM 地址详解
文章目录 Keil 中设置 STM32 Flash 和 RAM 地址详解一、Flash 和 RAM 配置界面(Target 选项卡)1. IROM1(用于配置 Flash)2. IRAM1(用于配置 RAM)二、链接器设置界面(Linker 选项卡)1. 勾选“Use Memory Layout from Target Dialog”2. 查看链接器参数(如果没有勾选上面…...
C++课设:简易日历程序(支持传统节假日 + 二十四节气 + 个人纪念日管理)
名人说:路漫漫其修远兮,吾将上下而求索。—— 屈原《离骚》 创作者:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊) 专栏介绍:《编程项目实战》 目录 一、为什么要开发一个日历程序?1. 深入理解时间算法2. 练习面向对象设计3. 学习数据结构应用二、核心算法深度解析…...

GO协程(Goroutine)问题总结
在使用Go语言来编写代码时,遇到的一些问题总结一下 [参考文档]:https://www.topgoer.com/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/goroutine.html 1. main()函数默认的Goroutine 场景再现: 今天在看到这个教程的时候,在自己的电…...

高分辨率图像合成归一化流扩展
大家读完觉得有帮助记得关注和点赞!!! 1 摘要 我们提出了STARFlow,一种基于归一化流的可扩展生成模型,它在高分辨率图像合成方面取得了强大的性能。STARFlow的主要构建块是Transformer自回归流(TARFlow&am…...

UE5 音效系统
一.音效管理 音乐一般都是WAV,创建一个背景音乐类SoudClass,一个音效类SoundClass。所有的音乐都分为这两个类。再创建一个总音乐类,将上述两个作为它的子类。 接着我们创建一个音乐混合类SoundMix,将上述三个类翻入其中,通过它管理每个音乐…...
FTXUI::Dom 模块
DOM 模块定义了分层的 FTXUI::Element 树,可用于构建复杂的终端界面,支持响应终端尺寸变化。 namespace ftxui {...// 定义文档 定义布局盒子 Element document vbox({// 设置文本 设置加粗 设置文本颜色text("The window") | bold | color(…...