高并发下实现防止重复下单的设计 附Springboot+Redis和Go+Gin+Redis示例

在高并发的情况下,处理下单请求会遇到多种挑战,特别是订单的重复提交、数据一致性、锁的竞争和超时、系统瓶颈等问题。为了更好地解决这些问题,我们需要综合考虑以下几个方面:

1. 业务流程介绍

up.png

  • 用户提交订单时,前端会发起支付请求,后端接收并处理支付状态,并更新订单状态。如果订单重复提交或并发请求没有得到合理处理,可能会导致用户重复支付、订单重复创建等问题。
  • 整个订单创建和支付流程涉及前端(用户提交请求、查询订单状态等)、后端(订单处理、与支付平台交互等)和存储层(订单数据存储)。在这个流程中,如何高效处理并发请求是核心。

2. 并发情况下易出现的问题

  • 重复提交订单:用户在前端点击下单时,由于网络延迟或系统响应慢,可能重复点击,导致多个请求同时到达服务器,产生重复订单。
  • 分布式锁的竞争:在高并发情况下,多个请求同时争抢锁资源,可能出现锁竞争过于激烈,导致系统性能下降。
  • 锁的超时与释放问题:如果某些请求长时间未能成功完成,可能导致锁未及时释放,其他请求被阻塞,影响系统吞吐量。
  • 幂等性问题:即使获取了锁,如果没有处理好幂等性,仍然可能出现同一用户对同一商品多次下单的情况。

3. 高并发下的解决机制

  1. 分布式锁机制:采用Redis分布式锁来避免多个请求同时处理同一个订单。使用Redis的 SETNX 命令可以确保同一时间只有一个请求获得锁,其他请求会等待锁的释放。Redis锁机制的关键点在于,设置合理的过期时间,防止死锁,同时确保在锁竞争过于激烈时系统不会被阻塞。
  2. 乐观锁或版本控制:除了分布式锁,我们还可以使用乐观锁来防止重复提交。例如在订单表中使用版本号或状态字段,更新时检查当前版本号是否匹配。如果不匹配则认为有并发更新,从而避免重复订单写入。
  3. 幂等性设计:在订单处理过程中,必须确保幂等性,即同一请求无论发送多少次,服务器的处理结果都应该是一致的。可以通过唯一请求ID来标识每个下单请求,避免重复创建订单。
  4. 消息队列的引入:在超高并发下,可以使用消息队列来削峰填谷。订单请求先进入队列,后端服务异步处理订单,保证系统不会被瞬时的高并发流量击垮。

高并发下实现防止重复下单的设计

1. Java + Spring Boot 实现防止重复下单

在此方案中,我们将通过 Redis分布式锁、幂等性检查、数据库乐观锁 和 消息队列 结合使用,保证在高并发下防止重复下单,并确保系统的高可用性。

服务流程
  • 获取分布式锁:每次用户提交下单请求时,系统会尝试获取分布式锁(基于Redis),以保证每个用户在同一时刻只能发起一个下单操作。分布式锁的设置需要有一个过期时间,避免在异常情况下死锁。
  • 幂等性检查:即便获取了分布式锁,还需要检查该用户是否已经下单成功,避免多次重复创建订单。可以通过订单号、用户ID及商品ID的组合唯一标识来进行检查。
  • 订单创建:当且仅当用户没有重复提交订单时,系统才会真正创建订单,并更新到数据库中。
  • 锁的释放:订单创建成功后,需要及时释放分布式锁,让其他请求可以继续执行。
Java 代码(包含完整流程和并发处理)
@RestController
@RequestMapping("/order")
public class OrderController {

    @Autowired
    private OrderService orderService;

    // 下单接口
    @PostMapping("/create")
    public ResponseEntity<String> createOrder(@RequestParam String userId, @RequestParam String productId) {
        String requestId = UUID.randomUUID().toString();  // 为每次请求生成唯一ID

        // 1. 获取分布式锁,避免并发下单
        boolean lockAcquired = orderService.acquireLock(userId, requestId);
        if (!lockAcquired) {
            return ResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS).body("请勿重复提交订单");
        }

        try {
            // 2. 检查是否已有订单,避免重复下单
            boolean hasOrder = orderService.hasExistingOrder(userId, productId);
            if (hasOrder) {
                return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("订单已存在");
            }

            // 3. 发送消息队列异步处理订单,防止系统拥塞
            orderService.asyncCreateOrder(userId, productId);

            return ResponseEntity.ok("订单已接收,处理中");

        } finally {
            // 4. 释放分布式锁,防止死锁
            orderService.releaseLock(userId, requestId);
        }
    }
}

@Service
public class OrderService {

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Autowired
    private MessageQueueService messageQueueService;

    private static final String ORDER_LOCK_PREFIX = "order_lock:";

    // 获取分布式锁
    public boolean acquireLock(String userId, String requestId) {
        String lockKey = ORDER_LOCK_PREFIX + userId;
        // 使用 Redis 的 setnx 命令设置锁,设置过期时间为 5 秒,避免死锁
        Boolean success = redisTemplate.opsForValue().setIfAbsent(lockKey, requestId, Duration.ofSeconds(5));
        return success != null && success;
    }

    // 释放分布式锁
    public void releaseLock(String userId, String requestId) {
        String lockKey = ORDER_LOCK_PREFIX + userId;
        String value = redisTemplate.opsForValue().get(lockKey);
        if (requestId.equals(value)) {
            redisTemplate.delete(lockKey);
        }
    }

    // 检查用户是否已有该产品的订单
    public boolean hasExistingOrder(String userId, String productId) {
        // 查询数据库或缓存,判断是否已经有订单
        // 此处简单返回 false,实际业务中需要查询数据库
        return false;
    }

    // 异步处理订单创建,使用消息队列
    public void asyncCreateOrder(String userId, String productId) {
        messageQueueService.sendMessage("order_queue", userId + "," + productId);
    }
}

@Service
public class MessageQueueService {

    // 简单模拟消息队列发送
    public void sendMessage(String queueName, String message) {
        System.out.println("消息已发送到队列: " + queueName + " 内容: " + message);
        // 在实际场景中,这里可以对接 Kafka、RabbitMQ 等消息队列系统
    }
}
机制详解:
  1. 分布式锁的超时机制:使用Redis的 setIfAbsent 实现分布式锁,并设置过期时间防止死锁。如果锁在规定时间内没有释放,其他请求就可以重新获得锁。
  2. 幂等性检查:订单在处理之前,系统会检查数据库是否已经存在该订单,确保重复请求不会生成重复订单。
  3. 消息队列的异步处理:通过将下单请求发送到消息队列中进行异步处理,可以削减瞬时流量,防止系统被过载。

2. Go + Gin 实现防止重复下单

类似的逻辑也可以用 Go 和 Gin 实现。我们也将采用 Redis 分布式锁 和 消息队列 的结合方式来确保系统的高可用性和防止重复下单。

package main

import (
    "context"
    "fmt"
    "net/http"
    "sync"
    "time"

    "github.com/gin-gonic/gin"
    "github.com/go-redis/redis/v8"
)

var ctx = context.Background()

type OrderService struct {
    redisClient *redis.Client
    mutex       sync.Mutex // 本地锁
}

func NewOrderService() *OrderService {
    return &OrderService{
        redisClient: redis.NewClient(&redis.Options{
            Addr: "localhost:6379",
        }),
    }
}

func (s *OrderService) CreateOrder(c *gin.Context) {
    userId := c.Query("userId")
    productId := c.Query("productId")
    requestId := fmt.Sprintf("%d", time.Now().UnixNano()) // 生成唯一请求ID

    // 1. 获取分布式锁
    lockAcquired, err := s.acquireLock(userId, requestId)
    if err != nil || !lockAcquired {
        c.JSON(http.StatusTooManyRequests, gin.H{"message": "请勿重复提交订单"})
        return
    }

    // 2. 检查是否已有订单,防止重复下单
    if s.hasExistingOrder(userId, productId) {
        c.JSON(http.StatusBadRequest, gin.H{"message": "订单已存在"})
        s.releaseLock(userId, requestId)
        return
    }

    // 3. 异步处理订单创建
    s.asyncCreateOrder(userId, productId)
    c.JSON(http.StatusOK, gin.H{"message": "订单已接收,处理中"})

    // 4. 释放锁
    s.releaseLock(userId, requestId)
}

func (s *OrderService) acquireLock(userId, requestId string) (bool, error) {
    lockKey := "order_lock:" + userId
    success, err := s.redisClient.SetNX(ctx, lockKey, requestId, 5*time.Second).Result()
    return success, err
}

func (s *OrderService) releaseLock(userId, requestId string) {
    lockKey := "order_lock:" + userId
    value, _ := s.redisClient.Get(ctx, lockKey).Result()
    if value == requestId {
        s.redisClient.Del(ctx, lockKey)
    }
}

func (s *OrderService) hasExistingOrder(userId, productId string) bool {
    // 假设这里查询数据库,返回是否存在订单
    return false
}

func (s *OrderService) asyncCreateOrder(userId, productId string) {
    // 模拟异步订单处理
    fmt.Println("异步订单创建: 用户", userId, " 产品:", productId)
}

func main() {
    r := gin.Default()
    orderService := NewOrderService()

    r.POST("/order/create", orderService.CreateOrder)
    r.Run(":8080")
}
机制详解:
  1. Redis 分布式锁:同样通过 SetNX 实现分布式锁,并设置过期时间防止死锁。在并发场景下,每个用户只能获得唯一锁,避免重复提交。
  2. 异步处理订单:通过模拟异步处理的方式,保证下单请求不会立即阻塞,减轻高并发带来的系统压力。

在百万并发的场景下,防止重复下单是一个复杂的问题,特别是要保证系统的高可用性、数据一致性和处理效率。为了有效解决这些问题,我们综合使用了 Redis 分布式锁、幂等性检查、异步处理 和 消息队列 等技术手段:

  1. Redis 分布式锁:确保每个用户在同一时间只能发起一次下单请求,避免并发重复提交。
  2. 幂等性检查:通过对订单数据的检查,确保即便请求多次到达,最终只会生成一个订单。
  3. 异步处理和消息队列:削峰填谷,通过消息队列缓解瞬时高并发流量的压力,保证系统不会被过载。

在高并发场景下,这些手段可以有效保证订单系统的稳定性和数据一致性,减少重复订单、锁竞争和系统瓶颈问题。

标签: Java, Go, Redis

相关文章

深入解析 Go 编译参数 -ldflags 及常见用法

在 Go 开发中,编译生成的二进制文件往往是程序性能和可移植性的关键。在某些场景下,我们需要对编译的输出进行优化,比如减少二进制文件大小、注入版本信息、控制调试信息等。而 -ldflags 是 ...

Redis高可用服务架构分析与搭建的几种方案

高可用Redis架构设计与实现Redis作为一款基于内存的高性能key-value数据库,已经在许多Web应用中得到了广泛使用,通常用于存储用户会话状态、加速数据查询以及实现消息队列和发布/订阅...

Go语言中定时器NewTicker详解

在 Golang 中,time 包提供了 NewTicker 函数,用于创建一个定时器,该定时器会定期向其 C 通道发送当前时间。NewTicker 非常适合用于需要定期执行某些任务的场景。1....

从入门到放弃:使用 spf13/viper 管理 Go 应用配置

在现代软件开发中,配置管理是一个至关重要的环节。随着应用的复杂性增加,配置管理的需求也变得更加多样化和复杂化。Go 语言社区中,spf13/viper 是一个非常流行的配置管理库,它提供了一种强...

使用 spf13/cobra 构建强大的 Go 命令行应用

spf13/cobra 是 Go 语言中非常流行的一个库,用于创建命令行应用(CLI)。它提供了一种强大且易于使用的框架来开发支持复杂命令结构的应用程序。Cobra 库主要用于创建像 kubec...

图片Base64编码

CSR生成

图片无损放大

图片占位符

Excel拆分文件