高并发下实现防止重复下单的设计 附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并发编程与调度器及并发模式详解

Go语言以其简洁的语法和强大的并发能力,成为现代网络编程和微服务架构的热门选择。本文将深入探讨Go的并发编程模型,调度器的工作机制,以及多种并发模式的实现和应用,帮助开发者更好地理解并发编程的设...

Go语言中sync.Pool详解

sync.Pool 是 Go 语言标准库中的一个数据结构,用于提供高效的对象池。它的主要作用是缓存临时对象,以减少内存分配和垃圾回收的开销。sync.Pool 特别适合用于存储短生命周期的对象,...

Go 中的并发 Map:使用sync.Map及其他实现方法

在 Go 语言中,并发编程是一个核心特性,能够高效地处理多个 goroutine 的并发执行。为了安全地在多个 goroutine 中共享数据,Go 提供了多种同步机制,其中之一就是线程安全的 ...

Go语言中的单例模式及其实现sync.Once

在软件开发中,单例模式是一种确保一个类只有一个实例的设计模式。在 Go 语言中,sync.Once 是实现单例模式的强大工具,它确保某个操作只被执行一次,适合在多线程环境中使用。本篇文章将详细介...

详解Go条件变量cond的使用

在 Go 语言中,条件变量(sync.Cond)是一种用于实现线程间同步的工具。它允许一个或多个 goroutine 等待某个条件的发生。条件变量通常与互斥锁(sync.Mutex)结合使用,以...

图片Base64编码

CSR生成

图片无损放大

图片占位符

Excel拆分文件