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

查看 32|回复 3
作者: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
}
多层幂等性保障
  • 客户端:生成唯一请求 ID
  • 网关层:请求去重
  • 服务端:事务幂等
  • 数据库:唯一约束

    面试官:能详细解释一下你提到的多层幂等性保障吗?每一层具体是如何实现的?
    应试者:多层幂等性保障是一种分层防重复提交的策略:
    客户端层
    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
    );
    这种多层设计的优势:
  • 在请求的不同阶段提供重复提交保护
  • 降低系统被恶意请求的风险
  • 提供细粒度的请求管理机制

    面试官:如果在高并发场景下,这套机制可能会引入性能瓶颈,你有什么优化建议吗?
    应试者:高并发场景下的性能优化是一个非常关键的话题。我的优化建议包括:
    缓存优化
  • 使用本地进程缓存(如 freecache )
  • 结合分布式缓存( Redis )
  • 对频繁访问的数据进行多级缓存

    异步处理
    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 知识 / 面试 / 创业经历 / 其他编程知识 👇
  • 公众号:GopherYes
  • B 站:YunFuns
  • 知乎、掘金、头条号:YunFun
  • 小红书号:986261983

  • juzuojuzou   
    假如,用户多次唤起支付宝但未支付,然后在支付宝里面看到多笔待支付单,再从支付宝里面全部支付呢

    YunFun
    OP
      
    @juzuojuzou #1 不是,想尽办法支付是吧,这种用户给我来一打😂
    lry   
    用户如果退出再重新进入页面,怎么保证幂等 ID ?
    您需要登录后才可以回帖 登录 | 立即注册

    返回顶部