面试实录 —— 电商支付系统中,如何有效避免用户重复支付?

12 小时 20 分钟前
 YunFun

面试官:在电商支付系统中,如何有效避免用户重复支付?请详细阐述你的设计思路。

应试者:防止重复支付是电商支付系统的核心设计挑战之一。我的解决方案主要从以下几个维度考虑:

唯一性标识设计

在支付流程中,我们需要为每笔交易生成全局唯一且可追溯的幂等标识:

type PaymentIdentifier struct {
    OrderID       string    // 订单 ID
    UserID        int64     // 用户 ID
    TransactionID string    // 全局唯一事务 ID
    CreatedAt     time.Time // 创建时间
}

// 生成全局唯一事务 ID
func GenerateTransactionID() string {
    // 使用雪花算法生成分布式唯一 ID
    return snowflake.Generate().String()
}

幂等性控制机制

核心实现思路:

type PaymentService struct {
    // 分布式锁,防止并发冲突
    locker distributed.Locker
    
    // 已处理交易的缓存
    processedTransactions *sync.Map
    
    // 数据库连接
    db *gorm.DB
}

func (s *PaymentService) ProcessPayment(ctx context.Context, payment *Payment) error {
    // 1. 获取分布式锁
    lock, err := s.locker.Lock(payment.TransactionID)
    if err != nil {
        return errors.Wrap(err, "获取锁失败")
    }
    defer lock.Unlock()
    
    // 2. 检查交易是否已处理
    if _, processed := s.processedTransactions.Load(payment.TransactionID); processed {
        return errors.New("交易已处理")
    }
    
    // 3. 数据库层面的幂等性检查
    var existingPayment Payment
    if err := s.db.Where("transaction_id = ?", payment.TransactionID).First(&existingPayment).Error; err == nil {
        return errors.New("重复交易")
    }
    
    // 4. 执行支付逻辑
    if err := s.executePayment(payment); err != nil {
        return err
    }
    
    // 5. 记录已处理交易
    s.processedTransactions.Store(payment.TransactionID, true)
    
    return nil
}

多层幂等性保障

面试官:能详细解释一下你提到的多层幂等性保障吗?每一层具体是如何实现的?

应试者:多层幂等性保障是一种分层防重复提交的策略:

客户端层

type PaymentRequest struct {
    RequestID     string    // 客户端生成的唯一请求 ID
    OrderID       string
    Amount        decimal.Decimal
    PaymentMethod string
}

func GenerateClientRequestID() string {
    // 结合时间戳、随机数、设备 ID 等
    return fmt.Sprintf("%s-%d-%s", 
        time.Now().Format("20060102150405"),
        rand.Int63(),
        deviceID)
}

网关层限流与去重

type PaymentGateway struct {
    // 使用 Redis 实现请求去重
    requestCache *redis.Client
    
    // 限流器
    rateLimiter *rate.Limiter
}

func (pg *PaymentGateway) ValidateRequest(req *PaymentRequest) error {
    // 限流检查
    if !pg.rateLimiter.Allow() {
        return errors.New("请求过于频繁")
    }
    
    // 请求去重
    cacheKey := fmt.Sprintf("payment:request:%s", req.RequestID)
    
    // 使用分布式缓存防重
    if pg.requestCache.Exists(cacheKey).Val() > 0 {
        return errors.New("重复请求")
    }
    
    // 缓存请求,设置过期时间
    pg.requestCache.Set(cacheKey, "1", time.Minute*10)
    
    return nil
}

服务端事务管理

func (s *PaymentService) ProcessPayment(ctx context.Context, req *PaymentRequest) error {
    // 开启数据库事务
    tx := s.db.Begin()
    
    // 检查是否存在相同的事务
    var existingTxn PaymentTransaction
    if err := tx.Where("request_id = ?", req.RequestID).First(&existingTxn).Error; err == nil {
        tx.Rollback()
        return errors.New("事务已存在")
    }
    
    // 创建新的支付事务
    txn := PaymentTransaction{
        RequestID:  req.RequestID,
        Status:     "Processing",
        CreateTime: time.Now(),
    }
    
    if err := tx.Create(&txn).Error; err != nil {
        tx.Rollback()
        return err
    }
    
    // 执行实际支付
    if err := s.executePayment(tx, req); err != nil {
        tx.Rollback()
        return err
    }
    
    // 提交事务
    return tx.Commit().Error
}

数据库唯一约束

-- 创建支付事务表
CREATE TABLE payment_transactions (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    request_id VARCHAR(64) UNIQUE NOT NULL,  -- 唯一约束
    order_id VARCHAR(64) NOT NULL,
    status ENUM('Processing', 'Success', 'Failed') NOT NULL,
    amount DECIMAL(10,2) NOT NULL,
    create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

这种多层设计的优势:

面试官:如果在高并发场景下,这套机制可能会引入性能瓶颈,你有什么优化建议吗?

应试者:高并发场景下的性能优化是一个非常关键的话题。我的优化建议包括:

缓存优化

异步处理

func (s *PaymentService) AsyncPaymentProcess(req *PaymentRequest) {
    // 使用消息队列异步处理支付请求
    go func() {
        // 投递到消息队列
        err := s.messageQueue.Publish("payment_topic", req)
        if err != nil {
            // 记录投递失败日志
            log.Error("消息投递失败", err)
        }
    }()
}

// 消息消费者
func (s *PaymentService) PaymentConsumer() {
    for {
        msg := s.messageQueue.Consume("payment_topic")
        
        // 并发处理
        go s.ProcessPayment(context.Background(), msg)
    }
}

细粒度锁

type ConcurrentPaymentManager struct {
    // 使用分段锁减少锁竞争
    shardedLocks []*sync.RWMutex
}

func (m *ConcurrentPaymentManager) getLock(key string) *sync.RWMutex {
    // 对 key 进行哈希,选择锁
    return m.shardedLocks[hashCode(key) % len(m.shardedLocks)]
}

func (m *ConcurrentPaymentManager) ProcessPayment(req *PaymentRequest) {
    lock := m.getLock(req.RequestID)
    lock.Lock()
    defer lock.Unlock()
    
    // 处理支付逻辑
}

限流与熔断

type AdaptiveRateLimiter struct {
    // 动态调整的令牌桶
    limit *rate.Limiter
}

func (rl *AdaptiveRateLimiter) Adjust(currentLoad float64) {
    // 根据系统负载动态调整限流阈值
    if currentLoad > 0.8 {
        rl.limit = rate.NewLimiter(rate.Limit(50), 100)
    } else {
        rl.limit = rate.NewLimiter(rate.Limit(100), 200)
    }
}

监控与性能分析

面试官:最后,对于这样一个支付系统,你有什么架构 level 的思考?

应试者:支付系统不仅仅是技术实现,更是一个复杂的金融级系统。我的架构思考主要包括:

安全性

可用性

合规性

可观测性

核心是在高性能、高可用、安全性之间找到平衡,构建一个既健壮又灵活的支付系统架构。


更多 Go 高质量内容试读👇: https://portal.yunosphere.com

欢迎关注我,经常分享有用的 Go 知识 / 面试 / 创业经历 / 其他编程知识 👇

3675 次点击
所在节点    程序员
32 条回复
zx9481
7 小时 41 分钟前
不太明白 就算重复下单 生成的订单号也是两个呀
helone
7 小时 38 分钟前
重复支付问题无解的,考虑用程序解决还不如加上退款策略,好多大厂都这样的,重复支付没多久就退回来了
Charlie17Li
7 小时 37 分钟前
@wjfz 大,佬想问下,你提到的第一步里如果重复订单,怎么通过你说的 限流和取消订单 来解决的呢,可以展开讲讲吗
vishun
7 小时 35 分钟前
楼主到底是面试支付宝还是面试普通的商家啊,如果是支付宝的话需要处理这些,如果是普通商家对接第三方支付,第三方支付就已经做好了防止重复支付相关措施,例如唯一的商户订单号什么的,需要你自己处理的不多。
ffw5b7
7 小时 1 分钟前
@helone 如果的银行转账这种呢
helone
6 小时 59 分钟前
@ffw5b7 都支持原路退回的,这个策略本身依赖的是对账功能,能把每笔收入都一一对应到订单上,如果没有对应的订单就要发给人工判断是否原路退回
bugmakerxs
6 小时 57 分钟前
@juzuojuzou 支付回调发现已经支付,走回滚逻辑通知支付宝给用户退款
Yanlongli
6 小时 35 分钟前
1 、去支付渠道的订单号如果是唯一的,则支付渠道会保证不会重复支付。
2 、如果发送给支付渠道的订单号不是唯一的,则创建新订单时先通知支付渠道取消之前的订单。
lqu3j
6 小时 28 分钟前
多支付渠道,且支付渠道没有取消支付会话的情况下怎么处理? 这种场景下似乎无解的,只能乖乖标记订单,然后退款
mark2025
6 小时 7 分钟前
幂等标识 是什么东东?
pkoukk
5 小时 19 分钟前
我可以理解避免重复下单,但是啥情况需要这么高级别的避免重复支付?
做个分布式锁拦一下,然后做个定时机制或者事件机制,对一个订单存在多个支付单的情况,逐个退款就完事了
把支付搞这么重,感觉不合适
vipfts
5 小时 15 分钟前
把 app 和网站做得卡卡地,让用户无法在极短时间内两次支付。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1094298

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX