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

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

目录

引言

[一. 系统架构设计](#一. 系统架构设计)

[1. 系统架构图](#1. 系统架构图)

[二、 系统流程](#二、 系统流程)

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

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

[前端部分(HTML + JavaScript):](#前端部分(HTML + JavaScript):)

[后端部分(Java + Spring Boot):](#后端部分(Java + Spring Boot):)

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

Nginx配置示例:

Apache配置示例:

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

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

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

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

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

[Redis Cluster配置示例:](#Redis Cluster配置示例:)

[Memcached Cluster配置示例:](#Memcached Cluster配置示例:)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

[后端应用服务器(Java + Spring Boot):](#后端应用服务器(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和商品ID
            var 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);
    }

    @RestController
    public 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:8001
    BalancerMember http://127.0.0.1:8002
</Proxy>

<VirtualHost *:80>
    ServerName example.com

    ProxyPass / 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);
    }

    @RestController
    public 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);
    }

    @RestController
    public 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);
    }

    @RestController
    public 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);
    }

    @RestController
    public 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);
    }

    @RestController
    public 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);
    }

    @RestController
    public 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);
    }

    @RestController
    public 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);
    }

    @RestController
    public 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);
    }

    @RestController
    public 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实现分布式锁来保护对库存数量的操作。当多个请求同时到达时,只有一个请求能够获得锁并处理订单,其他请求需要等待锁释放后才能继续处理。这样可以保证对库存数量的操作是原子性的,从而确保数据的一致性。

总结

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

相关推荐
晒太羊的猫10 分钟前
hot100子串
java·算法·leetcode
拖孩24 分钟前
一个人 + AI 做的小程序,今天,正式回本了
前端·后端·微信小程序
进击的丸子31 分钟前
代理网络环境下 Linux Pro 在线激活实操指南
javascript·后端·安全
开开心心就好34 分钟前
本地文件搜索工具,双击Ctrl就能呼出搜索
java·服务器·前端·支持向量机·ffmpeg·音视频·模拟退火算法
独隅38 分钟前
Claude 高效使用指南深度评测
java·开发语言·claude·claude code
ByteCoder.1 小时前
班前十分钟的清单,按体检引导单来写
java·服务器·前端·助播虾
海的透彻1 小时前
如何让docker容器启动时用自定义的用户
java·docker·dubbo
疯狂学习GIS1 小时前
将谷歌地球引擎账号升级为Contributor与Partner层级的方法
java·数据库·人工智能
GG.WinGel1 小时前
质数数目(蓝桥杯练习题)
java·数据结构·算法·青少年编程·蓝桥杯