共计 2736 个字符,预计需要花费 7 分钟才能阅读完成。
性能瓶颈的具体表现
在每日上午 9 点的技能预约高峰期,金百泽 Skill 平台监测到:

- QPS 从平时 2000 骤降至 800
- 订单创建 API 的 99 线延迟从 150ms 飙升到 1200ms
- MySQL CPU 利用率持续超过 90%
架构选型分析
- 单体架构痛点
- 所有模块共用一个数据库连接池
- 商品查询与订单强耦合导致锁竞争
-
垂直扩展成本呈指数增长
-
微服务改造优势
- 关键路径分离:将商品服务 / 库存服务 / 订单服务独立部署
- 按业务特点选择存储方案(如商品用 ES,库存用 Redis)
- 故障隔离:单个服务抖动不影响核心链路
核心优化方案
分布式缓存设计
@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
}
异步消息队列实现
订单创建流程改造:
- 同步只做基础校验
- 核心逻辑通过 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%↓ |
生产环境注意事项
- 缓存雪崩预防
- 对热门商品设置随机 TTL(3~8 分钟)
- 本地缓存 +Redis 多级降级
-
实现缓存预热定时任务
-
消息积压处理
- 监控 Consumer Lag 指标
- 动态扩容 Consumer 实例
-
紧急情况走 DB 直连模式
-
限流动态调整
- 基于 CPU 水位自动调节阈值
- 针对 VIP 用户设置白名单
- 熔断后渐进式恢复流量
开放性问题思考
在最终优化方案中,我们通过异步化牺牲了部分数据实时性。对于需要强一致性的场景(如库存扣减),可以考虑:
- 分布式事务(Seata 等)
- 乐观锁 + 重试机制
- 定时对账补偿
实际业务中需要根据 CAP 原理做权衡,例如秒杀场景可以接受短暂超卖后补偿退款,而金融交易则必须保证实时一致性。
正文完
