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




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

    目录

    引言

    一. 系统架构设计

    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. 系统架构图

    1. +-------------------------------------+
    2. | 前端负载均衡器 |
    3. +-----------+--------------+--------------+
    4. | |
    5. +-----------v--------------+--------------+
    6. | 前端应用服务器 | CDN |
    7. +-----------+--------------+--------------+
    8. | |
    9. +----------------------+---------v----------+--------------+-------v--------+-------------+
    10. | 缓存层 | | 后端应用服务器 | 数据库集群 |
    11. +-----------+--------------+ +-----------+--------------+--------------+
    12. | | |
    13. +-----------v--------------+ +-----------v--------------+--------------+
    14. | 缓存服务器 | | 消息队列 | 主数据库 |
    15. +-----------+--------------+ +-----------+--------------+--------------+
    16. | | |
    17. +-----------v--------------+ +-----------v--------------+
    18. | 缓存存储(Redis) | | 业务数据存储(MySQL) |
    19. +---------------------------+ +---------------------------+

    在这个架构中:

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

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

    二、 系统流程

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

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

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

    流程如下:

    前端部分(HTML + JavaScript):

    1. <!DOCTYPE html>
    2. <html>
    3. <head>
    4. <title>秒杀页面</title>
    5. </head>
    6. <body>
    7. <h1>欢迎参加秒杀活动!</h1>
    8. <button id="seckillButton">秒杀按钮</button>
    9. <div id="result"></div>
    10. <script>
    11. document.getElementById("seckillButton").addEventListener("click", function() {
    12. // 模拟用户ID和商品ID
    13. var userId = 123;
    14. var productId = 456;
    15. fetch("/seckill", {
    16. method: "POST",
    17. headers: {
    18. "Content-Type": "application/json"
    19. },
    20. body: JSON.stringify({ userId: userId, productId: productId })
    21. })
    22. .then(response => response.json())
    23. .then(data => {
    24. document.getElementById("result").innerText = data.message;
    25. });
    26. });
    27. </script>
    28. </body>
    29. </html>

    后端部分(Java + Spring Boot):

    1. import org.springframework.boot.SpringApplication;
    2. import org.springframework.boot.autoconfigure.SpringBootApplication;
    3. import org.springframework.web.bind.annotation.PostMapping;
    4. import org.springframework.web.bind.annotation.RequestBody;
    5. import org.springframework.web.bind.annotation.RestController;
    6. @SpringBootApplication
    7. public class SeckillApplication {
    8. private boolean seckillStarted = true; // 模拟秒杀活动是否开始
    9. private int stock = 10; // 模拟商品库存
    10. public static void main(String[] args) {
    11. SpringApplication.run(SeckillApplication.class, args);
    12. }
    13. @RestController
    14. public class SeckillController {
    15. @PostMapping("/seckill")
    16. public String seckill(@RequestBody SeckillRequest request) {
    17. if (!seckillStarted) {
    18. return "秒杀活动未开始";
    19. }
    20. if (stock <= 0) {
    21. return "商品已售罄";
    22. }
    23. // 模拟生成订单
    24. stock--;
    25. return "秒杀成功";
    26. }
    27. }
    28. public static class SeckillRequest {
    29. private Long userId;
    30. private Long productId;
    31. // 省略getter和setter方法
    32. }
    33. }

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

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

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

    Nginx配置示例:

    1. upstream frontends {
    2. server 127.0.0.1:8001;
    3. server 127.0.0.1:8002;
    4. }
    5. server {
    6. listen 80;
    7. server_name example.com;
    8. location / {
    9. proxy_pass http://frontends;
    10. }
    11. }

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

    Apache配置示例:

    1. <Proxy balancer://frontends>
    2. BalancerMember http://127.0.0.1:8001
    3. BalancerMember http://127.0.0.1:8002
    4. </Proxy>
    5. <VirtualHost *:80>
    6. ServerName example.com
    7. ProxyPass / balancer://frontends/
    8. ProxyPassReverse / balancer://frontends/
    9. </VirtualHost>

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

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

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

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

    1. import org.springframework.boot.SpringApplication;
    2. import org.springframework.boot.autoconfigure.SpringBootApplication;
    3. import org.springframework.web.bind.annotation.PostMapping;
    4. import org.springframework.web.bind.annotation.RequestBody;
    5. import org.springframework.web.bind.annotation.RestController;
    6. @SpringBootApplication
    7. public class FrontendApplication {
    8. private boolean seckillStarted = true; // 模拟秒杀活动是否开始
    9. private boolean seckillEnded = false; // 模拟秒杀活动是否结束
    10. public static void main(String[] args) {
    11. SpringApplication.run(FrontendApplication.class, args);
    12. }
    13. @RestController
    14. public class SeckillController {
    15. @PostMapping("/seckill")
    16. public String seckill(@RequestBody SeckillRequest request) {
    17. // 验证用户身份,假设用户身份验证通过
    18. if (!isUserAuthenticated(request.getUserId())) {
    19. return "用户身份验证失败";
    20. }
    21. // 检查秒杀活动是否开始或结束
    22. if (!isSeckillStarted()) {
    23. return "秒杀活动未开始";
    24. }
    25. if (isSeckillEnded()) {
    26. return "秒杀活动已结束";
    27. }
    28. // 处理秒杀请求
    29. return "秒杀请求处理中";
    30. }
    31. private boolean isUserAuthenticated(Long userId) {
    32. // 省略实际的用户身份验证逻辑
    33. return true;
    34. }
    35. private boolean isSeckillStarted() {
    36. // 省略实际的秒杀活动开始检查逻辑
    37. return seckillStarted;
    38. }
    39. private boolean isSeckillEnded() {
    40. // 省略实际的秒杀活动结束检查逻辑
    41. return seckillEnded;
    42. }
    43. }
    44. public static class SeckillRequest {
    45. private Long userId;
    46. private Long productId;
    47. // 省略getter和setter方法
    48. }
    49. }

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

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

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

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

    1. import org.springframework.boot.SpringApplication;
    2. import org.springframework.boot.autoconfigure.SpringBootApplication;
    3. import org.springframework.web.bind.annotation.PostMapping;
    4. import org.springframework.web.bind.annotation.RequestBody;
    5. import org.springframework.web.bind.annotation.RestController;
    6. import org.springframework.web.client.RestTemplate;
    7. @SpringBootApplication
    8. public class FrontendApplication {
    9. private static final String CACHE_SERVER_URL = "http://cache-server:8080"; // 缓存服务器地址
    10. public static void main(String[] args) {
    11. SpringApplication.run(FrontendApplication.class, args);
    12. }
    13. @RestController
    14. public class SeckillController {
    15. private final RestTemplate restTemplate = new RestTemplate();
    16. @PostMapping("/seckill")
    17. public String seckill(@RequestBody SeckillRequest request) {
    18. // 请求缓存服务器获取商品信息和库存数量
    19. String url = CACHE_SERVER_URL + "/product/" + request.getProductId();
    20. ProductInfo productInfo = restTemplate.getForObject(url, ProductInfo.class);
    21. if (productInfo == null) {
    22. return "获取商品信息失败";
    23. }
    24. // 处理秒杀请求
    25. return "请求处理中";
    26. }
    27. }
    28. public static class SeckillRequest {
    29. private Long userId;
    30. private Long productId;
    31. // 省略getter和setter方法
    32. }
    33. public static class ProductInfo {
    34. private Long productId;
    35. private String productName;
    36. private int stock;
    37. // 省略getter和setter方法
    38. }
    39. }

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

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

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

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

    Redis Cluster配置示例:

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

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

    Memcached Cluster配置示例:

    1. # 安装memcached
    2. sudo apt-get update
    3. sudo apt-get install memcached
    4. # 启动memcached节点1
    5. memcached -p 11211 -d
    6. # 启动memcached节点2
    7. memcached -p 11212 -d
    8. # 配置memcached集群
    9. echo "add 127.0.0.1 11212" | nc 127.0.0.1 11211

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

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

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

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

    1. import org.springframework.boot.SpringApplication;
    2. import org.springframework.boot.autoconfigure.SpringBootApplication;
    3. import org.springframework.web.bind.annotation.GetMapping;
    4. import org.springframework.web.bind.annotation.PathVariable;
    5. import org.springframework.web.bind.annotation.RestController;
    6. import java.util.HashMap;
    7. import java.util.Map;
    8. @SpringBootApplication
    9. public class CacheServerApplication {
    10. private static final Map<Long, ProductInfo> productCache = new HashMap<>();
    11. public static void main(String[] args) {
    12. SpringApplication.run(CacheServerApplication.class, args);
    13. }
    14. @RestController
    15. public class CacheController {
    16. @GetMapping("/product/{productId}")
    17. public ProductInfo getProductInfo(@PathVariable Long productId) {
    18. // 从缓存中获取商品信息
    19. return productCache.getOrDefault(productId, new ProductInfo(productId, "Unknown", 0));
    20. }
    21. }
    22. public static class ProductInfo {
    23. private Long productId;
    24. private String productName;
    25. private int stock;
    26. public ProductInfo(Long productId, String productName, int stock) {
    27. this.productId = productId;
    28. this.productName = productName;
    29. this.stock = stock;
    30. }
    31. // 省略getter和setter方法
    32. }
    33. }

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

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

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

    1. import org.springframework.boot.SpringApplication;
    2. import org.springframework.boot.autoconfigure.SpringBootApplication;
    3. import org.springframework.web.bind.annotation.GetMapping;
    4. import org.springframework.web.bind.annotation.PathVariable;
    5. import org.springframework.web.bind.annotation.RestController;
    6. import java.util.HashMap;
    7. import java.util.Map;
    8. @SpringBootApplication
    9. public class CacheServerApplication {
    10. private static final Map<Long, ProductInfo> productCache = new HashMap<>();
    11. public static void main(String[] args) {
    12. SpringApplication.run(CacheServerApplication.class, args);
    13. }
    14. @RestController
    15. public class CacheController {
    16. @GetMapping("/product/{productId}")
    17. public ProductInfo getProductInfo(@PathVariable Long productId) {
    18. // 从缓存中获取商品信息
    19. return productCache.getOrDefault(productId, new ProductInfo(productId, "Unknown", 0));
    20. }
    21. }
    22. public static class ProductInfo {
    23. private Long productId;
    24. private String productName;
    25. private int stock;
    26. public ProductInfo(Long productId, String productName, int stock) {
    27. this.productId = productId;
    28. this.productName = productName;
    29. this.stock = stock;
    30. }
    31. // 省略getter和setter方法
    32. }
    33. }

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

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

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

    1. import org.springframework.boot.SpringApplication;
    2. import org.springframework.boot.autoconfigure.SpringBootApplication;
    3. import org.springframework.web.bind.annotation.PostMapping;
    4. import org.springframework.web.bind.annotation.RequestBody;
    5. import org.springframework.web.bind.annotation.RestController;
    6. import java.util.HashMap;
    7. import java.util.Map;
    8. @SpringBootApplication
    9. public class FrontendApplication {
    10. private static final Map<Long, Integer> stockMap = new HashMap<>(); // 模拟商品库存
    11. private static final Map<Long, Boolean> seckillMap = new HashMap<>(); // 模拟秒杀活动是否开始
    12. private static final Map<Long, Boolean> seckillEndMap = new HashMap<>(); // 模拟秒杀活动是否结束
    13. private static final Map<Long, Long> userOrders = new HashMap<>(); // 模拟用户订单
    14. public static void main(String[] args) {
    15. // 初始化商品库存
    16. stockMap.put(1L, 10);
    17. // 初始化秒杀活动状态
    18. seckillMap.put(1L, true);
    19. // 初始化秒杀活动结束状态
    20. seckillEndMap.put(1L, false);
    21. SpringApplication.run(FrontendApplication.class, args);
    22. }
    23. @RestController
    24. public class SeckillController {
    25. @PostMapping("/seckill")
    26. public String seckill(@RequestBody SeckillRequest request) {
    27. Long productId = request.getProductId();
    28. Long userId = request.getUserId();
    29. // 判断秒杀活动是否开始或结束
    30. if (!seckillMap.getOrDefault(productId, false)) {
    31. return "秒杀活动未开始";
    32. }
    33. if (seckillEndMap.getOrDefault(productId, true)) {
    34. return "秒杀活动已结束";
    35. }
    36. // 判断库存是否足够
    37. Integer stock = stockMap.getOrDefault(productId, 0);
    38. if (stock <= 0) {
    39. return "商品已售罄";
    40. }
    41. // 生成订单
    42. userOrders.put(userId, productId);
    43. // 更新库存
    44. stockMap.put(productId, stock - 1);
    45. return "秒杀成功";
    46. }
    47. }
    48. public static class SeckillRequest {
    49. private Long userId;
    50. private Long productId;
    51. // 省略getter和setter方法
    52. }
    53. }

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

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

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

    1. import org.springframework.boot.SpringApplication;
    2. import org.springframework.boot.autoconfigure.SpringBootApplication;
    3. import org.springframework.web.bind.annotation.PostMapping;
    4. import org.springframework.web.bind.annotation.RequestBody;
    5. import org.springframework.web.bind.annotation.RestController;
    6. import org.springframework.web.client.RestTemplate;
    7. @SpringBootApplication
    8. public class FrontendApplication {
    9. private static final String LOAD_BALANCER_URL = "http://backend-load-balancer";
    10. public static void main(String[] args) {
    11. SpringApplication.run(FrontendApplication.class, args);
    12. }
    13. @RestController
    14. public class OrderController {
    15. private final RestTemplate restTemplate = new RestTemplate();
    16. @PostMapping("/order")
    17. public String placeOrder(@RequestBody OrderRequest request) {
    18. // 向后端负载均衡器发送订单信息
    19. String url = LOAD_BALANCER_URL + "/order";
    20. return restTemplate.postForObject(url, request, String.class);
    21. }
    22. }
    23. public static class OrderRequest {
    24. private Long orderId;
    25. private Long productId;
    26. private Long userId;
    27. // 省略getter和setter方法
    28. }
    29. }

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

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

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

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

    1. import java.util.ArrayList;
    2. import java.util.List;
    3. import java.util.Random;
    4. public class LoadBalancer {
    5. private List<String> backendServers;
    6. public LoadBalancer() {
    7. backendServers = new ArrayList<>();
    8. backendServers.add("http://backend-server1");
    9. backendServers.add("http://backend-server2");
    10. // 添加更多后端服务器...
    11. }
    12. public String selectBackendServer() {
    13. // 模拟负载均衡算法,这里简单使用随机选择
    14. Random random = new Random();
    15. int index = random.nextInt(backendServers.size());
    16. return backendServers.get(index);
    17. }
    18. public static void main(String[] args) {
    19. LoadBalancer loadBalancer = new LoadBalancer();
    20. // 模拟请求分发给后端服务器
    21. for (int i = 0; i < 10; i++) {
    22. String backendServer = loadBalancer.selectBackendServer();
    23. System.out.println("Request sent to: " + backendServer);
    24. }
    25. }
    26. }

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

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

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

    1. import org.springframework.boot.SpringApplication;
    2. import org.springframework.boot.autoconfigure.SpringBootApplication;
    3. import org.springframework.web.bind.annotation.PostMapping;
    4. import org.springframework.web.bind.annotation.RequestBody;
    5. import org.springframework.web.bind.annotation.RestController;
    6. import java.util.HashMap;
    7. import java.util.Map;
    8. @SpringBootApplication
    9. public class BackendApplication {
    10. private static final Map<Long, Integer> stockMap = new HashMap<>();
    11. public static void main(String[] args) {
    12. // 初始化库存数量
    13. stockMap.put(1L, 10);
    14. SpringApplication.run(BackendApplication.class, args);
    15. }
    16. @RestController
    17. public class OrderController {
    18. @PostMapping("/order")
    19. public String createOrder(@RequestBody OrderRequest request) {
    20. Long orderId = request.getOrderId();
    21. Long productId = request.getProductId();
    22. Long userId = request.getUserId();
    23. // 判断库存是否足够
    24. Integer stock = stockMap.getOrDefault(productId, 0);
    25. if (stock <= 0) {
    26. return "商品已售罄";
    27. }
    28. // 生成订单
    29. String orderInfo = "订单信息:订单号-" + orderId + ",商品ID-" + productId + ",用户ID-" + userId;
    30. // 更新库存数量
    31. stockMap.put(productId, stock - 1);
    32. return "生成订单成功:" + orderInfo;
    33. }
    34. }
    35. public static class OrderRequest {
    36. private Long orderId;
    37. private Long productId;
    38. private Long userId;
    39. // 省略getter和setter方法
    40. }
    41. }

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

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

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

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

    1. import org.springframework.boot.SpringApplication;
    2. import org.springframework.boot.autoconfigure.SpringBootApplication;
    3. import org.springframework.web.bind.annotation.PostMapping;
    4. import org.springframework.web.bind.annotation.RequestBody;
    5. import org.springframework.web.bind.annotation.RestController;
    6. import java.sql.Connection;
    7. import java.sql.DriverManager;
    8. import java.sql.PreparedStatement;
    9. import java.sql.SQLException;
    10. @SpringBootApplication
    11. public class BackendApplication {
    12. public static void main(String[] args) {
    13. SpringApplication.run(BackendApplication.class, args);
    14. }
    15. @RestController
    16. public class OrderController {
    17. @PostMapping("/order")
    18. public String createOrder(@RequestBody OrderRequest request) {
    19. Long orderId = request.getOrderId();
    20. Long productId = request.getProductId();
    21. Long userId = request.getUserId();
    22. // 生成订单
    23. String orderInfo = "订单信息:订单号-" + orderId + ",商品ID-" + productId + ",用户ID-" + userId;
    24. // 将订单信息写入数据库集群
    25. boolean success = writeToDatabase(orderInfo);
    26. if (success) {
    27. return "生成订单成功:" + orderInfo;
    28. } else {
    29. return "生成订单失败:" + orderInfo;
    30. }
    31. }
    32. private boolean writeToDatabase(String orderInfo) {
    33. // 连接数据库集群 这里为了方便演示没有用yaml进行配置
    34. String url = "jdbc:mysql://database-cluster:3306/database";
    35. String user = "user";
    36. String password = "password";
    37. try (Connection connection = DriverManager.getConnection(url, user, password)) {
    38. // 写入订单信息
    39. String sql = "INSERT INTO orders (order_info) VALUES (?)";
    40. try (PreparedStatement statement = connection.prepareStatement(sql)) {
    41. statement.setString(1, orderInfo);
    42. int rowsAffected = statement.executeUpdate();
    43. return rowsAffected > 0;
    44. }
    45. } catch (SQLException e) {
    46. e.printStackTrace();
    47. return false;
    48. }
    49. }
    50. }
    51. public static class OrderRequest {
    52. private Long orderId;
    53. private Long productId;
    54. private Long userId;
    55. // 省略getter和setter方法
    56. }
    57. }

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

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

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

    1. import org.springframework.boot.SpringApplication;
    2. import org.springframework.boot.autoconfigure.SpringBootApplication;
    3. import org.springframework.web.bind.annotation.PostMapping;
    4. import org.springframework.web.bind.annotation.RequestBody;
    5. import org.springframework.web.bind.annotation.RestController;
    6. import redis.clients.jedis.Jedis;
    7. @SpringBootApplication
    8. public class BackendApplication {
    9. private static final String REDIS_HOST = "localhost";
    10. private static final int REDIS_PORT = 6379;
    11. private static final String LOCK_KEY = "inventory_lock";
    12. private static final String INVENTORY_KEY = "inventory";
    13. public static void main(String[] args) {
    14. SpringApplication.run(BackendApplication.class, args);
    15. }
    16. @RestController
    17. public class OrderController {
    18. @PostMapping("/order")
    19. public String createOrder(@RequestBody OrderRequest request) {
    20. Long orderId = request.getOrderId();
    21. Long productId = request.getProductId();
    22. Long userId = request.getUserId();
    23. // 获取分布式锁
    24. try (Jedis jedis = new Jedis(REDIS_HOST, REDIS_PORT)) {
    25. boolean locked = false;
    26. while (!locked) {
    27. locked = jedis.setnx(LOCK_KEY, "locked") == 1;
    28. if (!locked) {
    29. try {
    30. Thread.sleep(100);
    31. } catch (InterruptedException e) {
    32. Thread.currentThread().interrupt();
    33. }
    34. }
    35. }
    36. // 获取锁成功,处理订单
    37. int stock = Integer.parseInt(jedis.get(INVENTORY_KEY));
    38. if (stock > 0) {
    39. // 生成订单
    40. String orderInfo = "订单信息:订单号-" + orderId + ",商品ID-" + productId + ",用户ID-" + userId;
    41. System.out.println("生成订单成功:" + orderInfo);
    42. // 更新库存数量
    43. jedis.set(INVENTORY_KEY, String.valueOf(stock - 1));
    44. } else {
    45. System.out.println("商品已售罄");
    46. }
    47. // 释放锁
    48. jedis.del(LOCK_KEY);
    49. }
    50. return "订单处理完成";
    51. }
    52. }
    53. public static class OrderRequest {
    54. private Long orderId;
    55. private Long productId;
    56. private Long userId;
    57. // 省略getter和setter方法
    58. }
    59. }

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

     总结

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

  • 相关阅读:
    C++看谁排得快
    机器学习-特征选择:使用Lassco回归精确选择最佳特征
    Leetcode978. Longest Turbulent Subarray
    使去中心化媒体网络相关联的NFT元数据标准
    MAC: 使用技巧
    lambda stream流处理异常的方法/不终止stream流处理异常
    2023 安装 facebookresearch slowfast
    海量数据处理
    LeetCode讲解篇之77. 组合
    SpringBoot接口 - 如何生成接口文档之非侵入方式(通过注释生成)Smart-Doc?
  • 原文地址:https://blog.csdn.net/2201_75809246/article/details/136515839