首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >秒杀、分库分表、全链路追踪:一个电商微服务的架构全拆解

秒杀、分库分表、全链路追踪:一个电商微服务的架构全拆解

作者头像
王中阳AI编程
发布2026-05-19 11:38:14
发布2026-05-19 11:38:14
1360
举报
文章被收录于专栏:Go语言学习专栏Go语言学习专栏

一、用微服务重构电商系统

很多中小团队做电商,起步阶段都是单体架构——一个 Go 服务包揽用户、商品、订单、支付所有逻辑,数据库也是单库单表。业务初期这样没问题,但随着流量增长,问题开始集中爆发:

  • 多端数据割裂:H5 商城、管理后台、服务商端各自对接同一套接口,数据一致性和迭代效率越来越差。
  • 高并发扛不住:秒杀场景下,单体应用的数据库连接池和内存直接打满,服务雪崩。
  • 问题定位困难:一个请求跨了五六个服务调用,没有链路追踪,线上出了 Bug 只能靠猜。
  • 安全防护缺失:SQL 注入、XSS 攻击、接口重放,生产环境裸奔。

我们团队经历了这个完整的痛苦过程,最终决定用 GoFrame + gRPC + ETCD 做微服务重构,把单体拆成独立的服务域,配合双网关、全链路追踪、分布式事务等企业级方案,支撑百万级并发。

这篇文章,就是我们整个重构过程的架构复盘。

二、整体架构设计

2.1 技术选型

模块

技术选型

选型理由

业务框架

GoFrame v2.9+

Go 生态最全的工程化框架,ORM/缓存/配置/日志开箱即用

通信协议

gRPC + Protobuf

服务间高性能通信,强类型接口定义,自动生成客户端代码

服务注册/发现

ETCD

轻量级分布式 KV,支持集群部署,Go 原生集成

API 网关

Kong

成熟的开源网关,支持限流、鉴权、灰度发布等插件

配置中心

Nacos

支持多环境配置管理和热更新,与 GoFrame 生态集成方便

链路追踪

Jaeger

CNCF 毕业项目,支持 gRPC/HTTP 全链路透传

搜索引擎

Elasticsearch

商品搜索多字段加权排序、高亮显示

消息队列

RabbitMQ

死信队列处理订单超时,消息可靠投递

监控告警

Prometheus + Grafana

自定义业务指标采集,实时告警

日志

Loki

轻量级日志聚合,与 Grafana 无缝集成

数据库中间件

ShardingSphere-Proxy

分库分表透明代理,业务代码零侵入

容器编排

Kubernetes

HPA 自动扩缩容,灰度发布,云原生部署

2.2 双网关架构:为什么不是一个网关打天下

这是我们架构中最特别的设计之一。很多团队会只用一个网关统一承接所有流量,但在电商场景中,H5 商城和 Admin 管理台对网关的需求完全不同:

  • H5 网关:面向公网用户,核心诉求是高并发限流、防刷、JWT 鉴权、灰度发布。
  • Admin 网关:面向内部运营,核心诉求是细粒度 RBAC 权限控制、操作审计、IP 白名单。

两个网关共享底层的 Kong 实例,但各自配置独立的插件策略和路由规则。这样 H5 侧的限流策略不会影响管理台操作,管理台的权限变更也不会波及用户侧。

2.3 服务拆分策略

我们按照业务域拆分了以下核心服务:

代码语言:javascript
复制
├── user-service      用户服务(注册/登录/OAuth2.0)
├── product-service   商品服务(SPU/SKU/ES搜索)
├── order-service     订单服务(下单/支付/超时取消)
├── payment-service   支付服务(微信/支付宝双通道)
├── inventory-service 库存服务(防超卖/分布式锁)
├── seckill-service   秒杀服务(独立部署,流量隔离)
└── admin-service     管理服务(RBAC/审计/运营工具)

每个服务独立数据库、独立部署、独立扩缩容。服务间通过 gRPC 同步调用,跨服务数据一致性通过分布式事务保障。

三、核心技术实现

3.1 秒杀系统:如何扛住峰值流量

秒杀是电商系统最极端的并发场景。我们采用三层削峰策略:

第一层:Kong 网关限流

在网关层配置令牌桶限流策略,将超出承载能力的请求直接拦截,避免打到后端服务。

代码语言:javascript
复制
# Kong 限流配置示意
rate-limiting:
  plugin: rate-limiting
  config:
    minute: 1000
    policy: redis
    fault_tolerant: true

第二层:Redis Lua 原子扣库存

库存扣减是秒杀的核心,必须保证原子性。我们用 Redis + Lua 脚本实现,避免超卖:

代码语言:javascript
复制
-- 秒杀扣库存 Lua 脚本(简化示意)
local stock = redis.call('GET', KEYS[1])
if tonumber(stock) <= 0 then
    return -1
end
redis.call('DECR', KEYS[1])
return 1

第三层:Sentinel 熔断 + 令牌桶削峰

即使网关和 Redis 扛住了,下游服务也可能因为数据库压力过载而崩溃。我们引入 Sentinel 做服务级熔断,同时用令牌桶控制进入下单流程的请求速率,把瞬间流量摊平。

3.2 防超卖:我们试了 4 种方案

防超卖是电商的经典难题。我们实际对比了 4 种方案:

方案

优点

缺点

最终选择

数据库行锁(SELECT FOR UPDATE)

实现简单

并发性能差,锁等待严重

Redis 分布式锁(SETNX)

性能好

锁续期复杂,存在死锁风险

Redis Lua 原子操作

原子性好,性能高

需要保证 Redis 与 DB 最终一致

是(秒杀场景)

数据库乐观锁(版本号)

无锁竞争

高并发下 CAS 重试多,性能下降

是(常规下单场景)

最终方案:秒杀场景用 Redis Lua 原子扣减,异步同步到数据库;常规下单用数据库乐观锁。两种场景各取所长。

3.3 分库分表:ShardingSphere-Proxy 实战

单库单表在订单量达到千万级后,查询性能断崖式下降。我们用 ShardingSphere-Proxy 做透明分库分表:

  • 分片策略:按用户 ID 取模分库,按订单时间范围分表
  • 分布式主键:Snowflake 算法生成全局唯一 ID
  • 跨分片查询:通过 ShardingSphere 的合并引擎处理,业务代码零侵入

深度分页的坑

分库分表后,LIMIT offset, size 的性能会随 offset 增大急剧恶化。ShardingSphere 需要将所有分片的 offset + size 条数据都拉到 Proxy 层做合并,当 offset=100000 时,实际扫描的数据量是 分片数 × 100010

我们用两种方式解决:

  1. 游标翻页(Scroll API):用上一页最后一条记录的排序值作为游标,避免大 offset
  2. 禁止深翻页:业务层面限制最多翻 50 页,超过的引导用户缩小搜索范围

3.4 商品搜索:ES 多字段加权 + 高亮

商品搜索不能只靠数据库 LIKE,我们引入 Elasticsearch 做全文检索:

  • 多字段加权:商品名称权重 10,分类权重 5,描述权重 2,保证名称匹配的排在前面
  • 高亮显示:搜索关键词在结果中高亮标记
  • 搜索建议:基于用户搜索历史的热门搜索词推荐
代码语言:javascript
复制
// ES 多字段加权查询(简化示意)
{
  "query": {
    "multi_match": {
      "query": "手机壳",
      "fields": ["name^10", "category^5", "description^2"],
      "type": "best_fields"
    }
  },
  "highlight": {
    "fields": { "name": {} }
  }
}

3.5 分布式事务:订单超时取消

下单后 30 分钟未支付需要自动取消订单并释放库存。我们用 RabbitMQ 死信队列实现:

  1. 下单成功后,消息投递到延迟队列(TTL=30min)
  2. 消息过期后进入死信队列
  3. 死信消费者执行取消逻辑:关订单 + 释放库存 + 退款

这比定时任务轮询更精准,也不会产生数据库压力。

3.6 全链路追踪:Jaeger 透传

微服务架构下,一个用户请求可能跨 5-6 个服务。没有链路追踪,排查线上问题就是噩梦。

我们通过 Jaeger 实现 gRPC/HTTP 全链路透传:

  • 网关层生成 TraceID,通过 HTTP Header 传递
  • gRPC 调用通过 Metadata 透传 TraceID
  • 每个服务自动上报 Span 到 Jaeger Collector
  • Grafana 集成 Jaeger 数据源,可以直接从告警跳转到链路详情

3.7 认证与安全体系

JWT 令牌轮换:

Access Token 短有效期(15min),Refresh Token 长有效期(7天)。Token 过期后用 Refresh Token 静默续期,用户无感知。Refresh Token 存入 Redis,支持主动失效(踢人下线)。

OAuth2.0 三方登录:

支持微信、支付宝快捷登录,授权码模式,State 参数防 CSRF。

防注入:

所有数据库操作通过 GoFrame ORM 预编译,参数化查询,杜绝 SQL 注入。前端输入做 XSS 过滤。

3.8 监控与运维

Prometheus + Grafana:

自定义业务指标采集(QPS、订单成功率、支付成功率、库存告警),Grafana 看板实时展示。

Loki 日志告警:

轻量级日志聚合,按 TraceID 关联日志和链路,慢调用自动告警。

K8s HPA 自动扩缩容:

秒杀期间,seckill-service 根据 CPU 使用率自动扩容到 10 副本,流量回落后自动缩容。

四、踩坑与经验总结

1. 分库分表后 JOIN 查询几乎不可用

拆库之后,跨库 JOIN 直接报错。我们通过两种方式应对:冗余字段(把常用关联字段冗余到主表)和业务层组装(先查主表拿 ID 列表,再批量查从表)。前者牺牲存储换性能,后者牺牲性能换灵活,需要根据场景取舍。

2. 分布式锁的续期问题

Redis 分布式锁(SETNX)在高并发场景下,如果业务执行时间超过锁的 TTL,锁会自动释放,导致并发问题。我们引入了看门狗机制(Watchdog),后台协程定期续期,直到业务主动释放。

3. ES 与 MySQL 数据一致性

商品数据写入 MySQL 后需要同步到 ES,中间有延迟。我们用 Canal 监听 MySQL Binlog,异步写入 ES。但 Canal 也有延迟(通常 100ms-1s),对实时性要求极高的场景(如刚上架的商品必须立即可搜),需要额外做一次主动同步。

4. 秒杀服务必须独立部署

最初我们把秒杀逻辑放在 order-service 里,结果秒杀流量把整个订单服务打挂了,连带正常下单也受影响。后来把秒杀独立成 seckill-service,单独部署、单独限流、单独数据库,彻底隔离。

以上只是我们踩过的坑中比较有代表性的几个。如果你也在做微服务电商,不想自己踩一遍这些坑,可以扫码获取我们的完整源码和内部辅导,少走很多弯路。

五、写在最后

从单体到微服务,这个重构过程远比想象中复杂。不只是技术选型和代码实现,更难的是服务边界的划分、分布式一致性的权衡、以及运维体系的搭建。

Go 在微服务领域的生态已经足够成熟——GoFrame、gRPC、ETCD、Kong、Jaeger,每一个组件都经过大厂验证。关键是把这些组件串起来,形成一套可落地的完整架构。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2026-05-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 王中阳 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、用微服务重构电商系统
  • 二、整体架构设计
    • 2.1 技术选型
    • 2.2 双网关架构:为什么不是一个网关打天下
    • 2.3 服务拆分策略
  • 三、核心技术实现
    • 3.1 秒杀系统:如何扛住峰值流量
    • 3.2 防超卖:我们试了 4 种方案
    • 3.3 分库分表:ShardingSphere-Proxy 实战
    • 3.4 商品搜索:ES 多字段加权 + 高亮
    • 3.5 分布式事务:订单超时取消
    • 3.6 全链路追踪:Jaeger 透传
    • 3.7 认证与安全体系
    • 3.8 监控与运维
  • 四、踩坑与经验总结
  • 五、写在最后
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档