Skip to content

每次进入订单列表页面都会触发全量同步? ​

  • 标签:后端、线程问题排查

🏛 业务架构师 ​

"每次进入订单列表页都触发全量同步"——这句话里有两个问题:性能浪费和架构错位。我先讲清为什么错,再给分档方案。

为什么全量同步是错的

  • 列表页是高频入口,每次进入都全量拉取(用户全部订单?同步到缓存?同步到搜索?),量大时页面秒开变 3 秒,直接伤害用户留存
  • 全量同步的计算资源随订单量线性增长,而订单量只增不减,这是个自我恶化的设计,撑不过两个大促
  • 全量数据里 99% 是用户根本不看的冷订单

正确设计:增量 + 缓存

第一档(改造最轻):

  • 列表页只查第一页(20 条),SQL 走 (user_id, id) 索引 + 游标分页,毫秒级返回
  • 如果"同步"是为了把订单同步到缓存/ES 供搜索:改为binlog 驱动的增量同步(Canal/Flink CDC),订单变更一条同步一条,列表页永远读现成数据

第二档(体验优化):

  • 首屏第一页缓存(Redis,短 TTL 或变更失效)
  • 下拉加载更多走游标翻页

架构上的收尾观点: 这个 bad case 背后是一个通用原则——读写路径分离、变更驱动同步:用户的视图(列表、搜索)应该由数据变更事件异步维护,而不是用户访问时现场计算。访问触发的全量计算,等于让最不懂计算成本的时机(用户正在等)承担最重的计算。改造它不是性能优化,是把架构从"拉模型"纠正为"推模型"。

🏗 系统架构师 ​

这个问题的本质是缓存粒度与同步策略设计错误:把"进页面"当成了全量同步触发器。我分三步治。

第一步:先问清同步的是什么。订单列表页要的通常只是"用户自己的、最近的、分页的"订单,而现在却每次全量拉取/全量刷新,既打 MySQL 又打下游同步链路,QPS 一高就是事故。

第二步:架构上去掉"页面驱动同步":

  • 加缓存层:用户维度订单列表进 Redis(SortedSet 按时间排 + Hash 存详情,或直接缓存分页结果短 TTL),进入页面先读缓存,miss 才回源
  • 变更驱动代替查询驱动:订单状态变更(支付回调、发货)由订单服务在事务提交后发 MQ,由同步/缓存服务增量更新,页面只做读,永远不触发同步
  • 增量同步:列表页只同步近 N 页或近 3 个月数据,全量走离线任务凌晨跑。用户翻到老订单再按需拉取

第三步:兜底与一致性:缓存用 Cache Aside,订单写库后删缓存 + MQ 异步兜底双保险;给同步任务加分布式锁防止并发重复刷;同步耗时与页面 RT 解耦——同步慢只影响数据新鲜度,不影响页面打开速度。

如果面试官追问量级:日均百万订单、列表 QPS 5000 的场景,这套"MQ 变更驱动 + Redis 列表缓存 + 离线全量补底"能把页面 RT 从秒级压到 50ms 内,MySQL 读 QPS 降两个数量级。

💻 全栈开发专家 ​

这类问题我先抓现场:全量同步是谁触发的、同步了什么范围。

排查过的真实案例

订单列表页每次打开都触发一次几十万条的全量同步任务:代码里在列表接口的 Filter/Interceptor 里顺手调了 syncService.syncAll(),而 syncAll 没有任何增量条件——典型的"顺手写了个后台任务在请求线程里跑"。

排查动作

  1. jstack 抓线程栈,发现大量 HTTP 线程卡在 syncService 的数据库读取上。
  2. 看 SkyWalking 调用链,列表接口下面挂了一个耗时段长的同步 span,元凶一目了然。
  3. 代码走查确认:无增量水位(last_sync_time)、无分页、还在请求线程同步执行。

修复方案

  • 改增量:同步表记录水位,WHERE update_time > ? AND update_time <= ?,只搬变化。
  • 改异步解耦:数据变更发 MQ(或 binlog CDC),同步服务消费增量事件,页面请求只读结果缓存,彻底不碰同步。
  • 改定时兜底:XXL-JOB 每 5 分钟跑一次增量补偿,处理 MQ 丢失的尾巴,任务里加分布式锁防多实例并发重跑。
  • 接口层面加缓存(列表页其实不需要实时全量),RT 从 2s 回到 50ms。

经验 "进页面触发全量"这类问题,根因多是把管理后台的一次性脚本复制进了在线接口。Code Review 时看到请求路径里出现 syncAll/loadAll/rebuild 这类方法名,我都会多问一句。