高并发下实现防止重复下单的设计 附Springboot+Redis和Go+Gin+Redis示例
在高并发的情况下,处理下单请求会遇到多种挑战,特别是订单的重复提交、数据一致性、锁的竞争和超时、系统瓶颈等问题。为了更好地解决这些问题,我们需要综合考虑以下几个方面:
1. 业务流程介绍
- 用户提交订单时,前端会发起支付请求,后端接收并处理支付状态,并更新订单状态。如果订单重复提交或并发请求没有得到合理处理,可能会导致用户重复支付、订单重复创建等问题。
- 整个订单创建和支付流程涉及前端(用户提交请求、查询订单状态等)、后端(订单处理、与支付平台交互等)和存储层(订单数据存储)。在这个流程中,如何高效处理并发请求是核心。
2. 并发情况下易出现的问题
- 重复提交订单:用户在前端点击下单时,由于网络延迟或系统响应慢,可能重复点击,导致多个请求同时到达服务器,产生重复订单。
- 分布式锁的竞争:在高并发情况下,多个请求同时争抢锁资源,可能出现锁竞争过于激烈,导致系统性能下降。
- 锁的超时与释放问题:如果某些请求长时间未能成功完成,可能导致锁未及时释放,其他请求被阻塞,影响系统吞吐量。
- 幂等性问题:即使获取了锁,如果没有处理好幂等性,仍然可能出现同一用户对同一商品多次下单的情况。
3. 高并发下的解决机制
- 分布式锁机制:采用Redis分布式锁来避免多个请求同时处理同一个订单。使用Redis的
SETNX
命令可以确保同一时间只有一个请求获得锁,其他请求会等待锁的释放。Redis锁机制的关键点在于,设置合理的过期时间,防止死锁,同时确保在锁竞争过于激烈时系统不会被阻塞。 - 乐观锁或版本控制:除了分布式锁,我们还可以使用乐观锁来防止重复提交。例如在订单表中使用版本号或状态字段,更新时检查当前版本号是否匹配。如果不匹配则认为有并发更新,从而避免重复订单写入。
- 幂等性设计:在订单处理过程中,必须确保幂等性,即同一请求无论发送多少次,服务器的处理结果都应该是一致的。可以通过唯一请求ID来标识每个下单请求,避免重复创建订单。
- 消息队列的引入:在超高并发下,可以使用消息队列来削峰填谷。订单请求先进入队列,后端服务异步处理订单,保证系统不会被瞬时的高并发流量击垮。
高并发下实现防止重复下单的设计
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 等消息队列系统
}
}
机制详解:
- 分布式锁的超时机制:使用Redis的
setIfAbsent
实现分布式锁,并设置过期时间防止死锁。如果锁在规定时间内没有释放,其他请求就可以重新获得锁。 - 幂等性检查:订单在处理之前,系统会检查数据库是否已经存在该订单,确保重复请求不会生成重复订单。
- 消息队列的异步处理:通过将下单请求发送到消息队列中进行异步处理,可以削减瞬时流量,防止系统被过载。
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")
}
机制详解:
- Redis 分布式锁:同样通过
SetNX
实现分布式锁,并设置过期时间防止死锁。在并发场景下,每个用户只能获得唯一锁,避免重复提交。 - 异步处理订单:通过模拟异步处理的方式,保证下单请求不会立即阻塞,减轻高并发带来的系统压力。
在百万并发的场景下,防止重复下单是一个复杂的问题,特别是要保证系统的高可用性、数据一致性和处理效率。为了有效解决这些问题,我们综合使用了 Redis 分布式锁、幂等性检查、异步处理 和 消息队列 等技术手段:
- Redis 分布式锁:确保每个用户在同一时间只能发起一次下单请求,避免并发重复提交。
- 幂等性检查:通过对订单数据的检查,确保即便请求多次到达,最终只会生成一个订单。
- 异步处理和消息队列:削峰填谷,通过消息队列缓解瞬时高并发流量的压力,保证系统不会被过载。
在高并发场景下,这些手段可以有效保证订单系统的稳定性和数据一致性,减少重复订单、锁竞争和系统瓶颈问题。
版权声明:本文为原创文章,版权归 全栈开发技术博客 所有。
本文链接:https://www.lvtao.net/dev/high-concurrency-duplicate-order-prevention-solutions.html
转载时须注明出处及本声明