高并发下实现防止重复下单的设计 附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+ Wails开发轻量级桌面应用端

Wails 是一个使用 Go 语言开发的框架,允许开发者使用 Go 和前端技术(如 HTML、CSS 和 JavaScript)来构建跨平台的桌面应用程序。Wails 提供了一个简单的方式来将 ...

在 Go 项目中使用 LevelDB 进行数据存储

LevelDB 是一个由 Google 开发的高性能键值存储库,广泛应用于需要快速读写操作的场景。本文将介绍如何在 Go 项目中使用 LevelDB 作为数据存储,并通过示例代码展示如何初始化数...

详解Go语言依赖注入工具wire最佳实践介绍与使用

wire是一个强大的依赖注入工具,通过代码生成的方式实现了高效的依赖注入。本文详细介绍了wire的入门级和高级使用技巧,并通过示例代码展示了其强大的功能。无论是简单的依赖注入,还是复杂的依赖图生...

Go语言中copy命令讲解 切片之间复制元素

在Go语言中,copy函数是一个非常常用的内置函数,用于在切片(slice)之间复制元素。理解copy函数的用法和机制对于高效处理数据操作至关重要1. copy函数的基本用法copy函数的基本语...

Java中线程池遇到父子任务示例及避坑

在Java中使用线程池可以有效地管理和调度线程,提高系统的并发处理能力。然而,当涉及到父子任务时,可能会遇到一些常见的Bug,特别是在子线程中查询数据并行处理时。本文将通过示例代码展示这些常见问...

深入理解 Go 语言中的 goto:用法与最佳实践

在学习编程语言时,goto 一直是一个颇具争议的概念。它常常因为“跳跃式”的行为被认为会让代码混乱且难以维护,但在 Go 语言中,goto 被保留并提供了一些实际的应用场景。今天我们将深入探讨 ...

图片Base64编码

CSR生成

图片无损放大

图片占位符

Excel拆分文件