金百泽Skill在高并发场景下的架构优化实践

5次阅读
没有评论

共计 2736 个字符,预计需要花费 7 分钟才能阅读完成。

image.webp

性能瓶颈的具体表现

在每日上午 9 点的技能预约高峰期,金百泽 Skill 平台监测到:

金百泽 Skill 在高并发场景下的架构优化实践

  • QPS 从平时 2000 骤降至 800
  • 订单创建 API 的 99 线延迟从 150ms 飙升到 1200ms
  • MySQL CPU 利用率持续超过 90%

架构选型分析

  1. 单体架构痛点
  2. 所有模块共用一个数据库连接池
  3. 商品查询与订单强耦合导致锁竞争
  4. 垂直扩展成本呈指数增长

  5. 微服务改造优势

  6. 关键路径分离:将商品服务 / 库存服务 / 订单服务独立部署
  7. 按业务特点选择存储方案(如商品用 ES,库存用 Redis)
  8. 故障隔离:单个服务抖动不影响核心链路

核心优化方案

分布式缓存设计

@startuml
component "商品服务" as goods
database "Redis 集群" as cache
goods -> cache : 查询商品详情 (带本地缓存)
cache --> goods : 命中返回
goods -> cache : 未命中时回源 DB
@enduml

关键实现代码:

// 二级缓存策略示例
func GetProduct(ctx context.Context, id int64) (*Product, error) {
    // 先查本地缓存
    if v, ok := localCache.Get(id); ok {return v.(*Product), nil
    }

    // 再查 Redis
    cacheKey := fmt.Sprintf("product:%d", id)
    if val, err := redis.Get(ctx, cacheKey).Bytes(); err == nil {product := &Product{}
        if err := json.Unmarshal(val, product); err == nil {localCache.Set(id, product, 30*time.Second) // 本地缓存 30 秒
            return product, nil
        }
    }

    // 最后查数据库
    product, err := db.GetProductByID(ctx, id)
    if err != nil {return nil, err}

    // 异步更新缓存
    go func() {data, _ := json.Marshal(product)
        redis.SetEX(ctx, cacheKey, data, 5*time.Minute) // Redis 缓存 5 分钟
    }()

    return product, nil
}

异步消息队列实现

订单创建流程改造:

  1. 同步只做基础校验
  2. 核心逻辑通过 Kafka 异步处理

生产者代码:

func CreateOrder(ctx context.Context, req *OrderRequest) error {
    // 基础校验
    if err := validate(req); err != nil {return err}

    // 发送 Kafka 消息
    msg := &sarama.ProducerMessage{
        Topic: "order_create",
        Value: sarama.ByteEncoder(proto.Marshal(req)),
    }

    // 带超时控制
    select {case producer.Input() <- msg:
        metrics.Inc("order.submit")
        return nil
    case <-ctx.Done():
        return ctx.Err()}
}

消费者代码:

func StartConsumer() {consumer, _ := sarama.NewConsumerGroup(...)
    handler := &orderHandler{db: db}

    go func() {
        for {consumer.Consume(context.Background(), 
                []string{"order_create"}, handler)
        }
    }()}

type orderHandler struct{db *gorm.DB}

func (h *orderHandler) Handle(msg *sarama.ConsumerMessage) error {
    var req OrderRequest
    if err := proto.Unmarshal(msg.Value, &req); err != nil {return err}

    // 实际创建订单逻辑
    return h.db.Transaction(func(tx *gorm.DB) error {// 扣减库存、生成订单等})
}

智能限流实现

滑动窗口算法 Go 版本:

type SlidingWindow struct {
    windowSize time.Duration
    slotNum    int
    slots      []int64
    current    int
    mutex      sync.Mutex
}

func NewSlidingWindow(windowSize time.Duration, slotNum int) *SlidingWindow {
    return &SlidingWindow{
        windowSize: windowSize,
        slotNum:    slotNum,
        slots:      make([]int64, slotNum),
    }
}

func (sw *SlidingWindow) Allow() bool {sw.mutex.Lock()
    defer sw.mutex.Unlock()

    now := time.Now().UnixNano()
    slotDuration := sw.windowSize.Nanoseconds() / int64(sw.slotNum)

    // 清理过期槽位
    for i := 0; i < sw.slotNum; i++ {if now-sw.slots[i] > slotDuration {sw.slots[i] = 0
        }
    }

    // 统计当前窗口请求数
    var total int64
    for _, v := range sw.slots {total += v}

    if total >= maxQPS {return false}

    sw.current = (sw.current + 1) % sw.slotNum
    sw.slots[sw.current]++
    return true
}

压测结果对比

指标 优化前 优化后 提升幅度
最大 TPS 1200 6500 441%
99 线延迟 850ms 210ms 75%↓
错误率 6.8% 0.2% 97%↓

生产环境注意事项

  1. 缓存雪崩预防
  2. 对热门商品设置随机 TTL(3~8 分钟)
  3. 本地缓存 +Redis 多级降级
  4. 实现缓存预热定时任务

  5. 消息积压处理

  6. 监控 Consumer Lag 指标
  7. 动态扩容 Consumer 实例
  8. 紧急情况走 DB 直连模式

  9. 限流动态调整

  10. 基于 CPU 水位自动调节阈值
  11. 针对 VIP 用户设置白名单
  12. 熔断后渐进式恢复流量

开放性问题思考

在最终优化方案中,我们通过异步化牺牲了部分数据实时性。对于需要强一致性的场景(如库存扣减),可以考虑:

  • 分布式事务(Seata 等)
  • 乐观锁 + 重试机制
  • 定时对账补偿

实际业务中需要根据 CAP 原理做权衡,例如秒杀场景可以接受短暂超卖后补偿退款,而金融交易则必须保证实时一致性。

正文完
 0
评论(没有评论)