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

news2024/11/14 11:03:14



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

目录

引言

一. 系统架构设计

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

 总结

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

AI绘画工具Ideogram测评:和Midjourney不分伯仲的AI图像工具之一

Ideogram 是一款令人印象深刻的人工智能图像工具&#xff0c;但尽管它于去年 8 月推出并具有不可思议的文本渲染能力&#xff0c;但它并没有引起其他一些更引人注目的 GenAI 服务的关注。 随着该公司推出其生成式人工智能模型 1.0 版本&#xff0c;这种情况即将发生改变&#…

详解 Flink 的容错机制

一、检查点 Checkpoint 1. 介绍 有状态流应用中的检查点&#xff08;checkpoint&#xff09;&#xff0c;其实就是所有任务的状态在某个时间点的一个快照&#xff08;一份拷贝&#xff09;&#xff0c;这个时间点应该是所有任务都恰好处理完一个相同的输入数据的时刻。在一个流…

帕友的小贴士,锻炼

帕金森病作为一种慢性神经系统疾病&#xff0c;对患者的生活质量产生了深远的影响。虽然医学界对于帕金森病的治疗仍在不断探索&#xff0c;但合理的锻炼已经被证实是改善患者症状、提高生活质量的有效途径之一。本文旨在为帕金森病患者推荐一些适合的锻炼方法&#xff0c;帮助…

2024 年最佳 iPhone 数据恢复软件

最好的 iPhone 数据恢复软件是什么&#xff1f; 说到 iPhone 数据恢复&#xff0c;拥有合适的软件对于恢复丢失或删除的文件至关重要&#xff0c;无论是照片、视频、消息、联系人还是其他重要数据。那么&#xff0c;最好的 iPhone 数据恢复软件是什么&#xff1f;有几个因素有…

使用C++结合OpenCV进行图像处理与分类

⭐️我叫忆_恒心&#xff0c;一名喜欢书写博客的在读研究生&#x1f468;‍&#x1f393;。 如果觉得本文能帮到您&#xff0c;麻烦点个赞&#x1f44d;呗&#xff01; 近期会不断在专栏里进行更新讲解博客~~~ 有什么问题的小伙伴 欢迎留言提问欧&#xff0c;喜欢的小伙伴给个三…

力扣hot100: 48. 旋转图像

LeetCode&#xff1a;48. 旋转图像 受到力扣hot100&#xff1a;54. 螺旋矩阵的启发&#xff0c;我们可以对旋转图像按层旋转&#xff0c;我们只需要记录四个顶点&#xff0c;并且本题是一个方阵&#xff0c;四个顶点就能完成图像的旋转操作。 1、逐层旋转 注意到&#xff0…

设计随笔 ---- ADR4525 篇

ADR4525一颗超低噪声、高精度2.5V基准电压源&#xff1b; Fluke 17B准确度指标&#xff1a; ADR4525指标&#xff1a; Fluke 17B测试结果&#xff1a; 2.5V的基准&#xff0c;输出只有2.477V&#xff0c;其实这么高精度的电压基准用3位半的万用表来测试本身就是一个错误&#…

3-哈希表-51-四数相加 II-LeetCode454

3-哈希表-51-四数相加 II-LeetCode454 LeetCode: 题目序号454 更多内容欢迎关注我&#xff08;持续更新中&#xff0c;欢迎Star✨&#xff09; Github&#xff1a;CodeZeng1998/Java-Developer-Work-Note 技术公众号&#xff1a;CodeZeng1998&#xff08;纯纯技术文&#xff…

《QT实用小工具·七十》openssl+qt开发的P2P文件加密传输工具

1、概述 源码放在文章末尾 该项目实现了P2P的文件加密传输功能&#xff0c;具体包含如下功能&#xff1a; 1、 多文件多线程传输 2、rsaaes文件传输加密 3、秘钥随机生成 4、断点续传 5、跨域传输引导服务器 项目界面如下所示&#xff1a; 接收界面 发送界面 RSA秘钥生成…

(二)深度学习基础练习题(54道选择题)

本文整理了深度学习基础知识相关的练习题&#xff0c;共54道&#xff0c;适用于想巩固深度学习基础的同学。来源&#xff1a;如荷学数据科学题库&#xff08;技术专项-深度学习&#xff09;。 1&#xff09; 2&#xff09; 3&#xff09; 4&#xff09; 5&#xff09; 6&#…

【CW32F030CxTx StartKit开发板】开发资料

本来是参加21ic的评测活动&#xff0c;不知道为什么评测文章一直被提示有不良内容&#xff0c;所以只好先在此记录一下相关的资料。 此次测试的是CW32F030CxTxStartKit 评估板。该开发板为用户提供一种经济且灵活的方式使用 CW32F030CxTx 芯片构建系统原型&#xff0c;可进行性…

插卡式仪器模块:音频分析模块(插卡式)

• 24 位分辨率 • 192 KHz 采样率 • 支持多种模拟音频信号的输入/输出 应用场景 • 音频信号分析&#xff1a;幅值、频率、信噪比、THD、THDN 等指标 • 模拟音频测试&#xff1a;耳机、麦克风、扬声器测试&#xff0c;串扰测 音频分析仪 输入阻抗10 TΩ10 TΩ输入范围3…

第103天: 权限提升-Linux 系统辅助项目脏牛Dirty内核漏洞SUIDGUID

项目下载地址 综合类探针&#xff1a; https://github.com/liamg/traitor 自动化提权&#xff1a; https://github.com/AlessandroZ/BeRoot 信息收集&#xff1a; https://github.com/rebootuser/LinEnum https://github.com/sleventyeleven/linuxprivchecker 漏洞探针&#xf…

AI网络爬虫:批量爬取豆瓣图书搜索结果

工作任务&#xff1a;爬取豆瓣图书搜索结果页面的全部图书信息 在ChatGPT中输入提示词&#xff1a; 你是一个Python编程专家&#xff0c;要完成一个爬虫Python脚本编写的任务&#xff0c;具体步骤如下&#xff1a; 用 fake-useragent库设置随机的请求头&#xff1b; 设置chr…

【小程序】WXML模板语法

目录 数据绑定 数据绑定的基本原则 在data中定义页面的数据 Mustache语法的格式 Mustache语法的应用场景 事件绑定 什么是事件 小程序中常用的事件 事件对象的属性列表 target和currentTarget的区别 bindtap的语法格式 在事件处理函数中为data中的数据赋值 事件…

【linux】进程控制——进程创建,进程退出,进程等待

个人主页&#xff1a;东洛的克莱斯韦克-CSDN博客 祝福语&#xff1a;愿你拥抱自由的风 相关文章 【Linux】进程地址空间-CSDN博客 【linux】详解linux基本指令-CSDN博客 目录 进程控制概述 创建子进程 fork函数 父子进程执行流 原理刨析 常见用法 出错原因 进程退出 概…

【Linux】进程6——环境变量

1.什么是环境变量 环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数 比如&#xff1a;我们在编写C/C代码的时候&#xff0c;在链接的时候&#xff0c;从来不知道我们的所链接的动态静态库在哪里&#xff0c;但是照样可以链接成功&…

TalkingData 是一家专注于提供数据统计和分析解决方案的独立第三方数据智能服务平台

TalkingData 是一家专注于提供数据统计和分析解决方案的独立第三方数据智能服务平台。通过搜索结果&#xff0c;我们可以了解到 TalkingData 的一些关键特性和市场情况&#xff0c;并将其与同类型产品进行比较。 TalkingData 产品特性 数据统计与分析&#xff1a;提供专业的数…

Pulsar 社区周报 | No.2024-06-07 | Apache Pulsar 新分支 3.3 版本发布

“ 各位热爱 Pulsar 的小伙伴们&#xff0c;Pulsar 社区周报更新啦&#xff01;这里将记录 Pulsar 社区每周的重要更新&#xff0c;每周发布。 ” 本期主题&#xff1a;Apache Pulsar 新分支 3.3 版本发布 Apache Pulsar 新分支 3.3 版本发布&#xff1a;Apache Pulsar 3.3.0[1…

野花野草80种 ,依然是农村小时候的印象

【野花野草】 小时候&#xff0c;不论在山上、在田里、还是在路边&#xff0c;总能看到各种各样的小花小草&#xff0c;或外表相似&#xff0c;或独具特色&#xff0c;而它们的名字似乎总是一个谜。今天我们就盘点一下这些叫不出名字的植物吧&#xff0c;或许&#xff0c;还能…