知识卡片

串行变并发+预取依赖数据回传:读服务性能优化的通用范式

结构图卡

内容

配送至这类需要聚合多个下游依赖的读服务,如果每个依赖数据都串行获取,响应时间是所有依赖耗时的简单相加——案例中假设需要数据A(10ms)、B(15ms)、C(10ms)、D(20ms)、E(10ms)共5份数据,串行获取需要60ms。第一步优化是识别出哪些数据之间没有依赖关系可以并发获取:数据C依赖A和B、E依赖C、D不依赖任何数据,理清这层依赖关系后把无依赖的请求并发发出,把总耗时从简单相加压缩到关键路径的长度,案例中通过并发化把60ms压缩到30ms,几乎提升一倍。第二步优化更进一步:如果发现下游服务E本身还依赖一份数据F(5ms),而这份数据F恰好在当前服务里已经能够顺带获取到,就可以在获取A/B/D的同时预先一并把F也取回来,直接回传给下游服务E使用——如果预取没有传,下游服务E自己再去查一次也不影响正确性,但一旦当前服务能顺手拿到,就可以省掉下游服务这次单独查询,整体链路耗时能进一步压缩到25ms。这个案例给出的不只是一个具体的性能数字,而是一套可以复用的两步优化范式:第一步永远是先把调用链路里的依赖关系图画清楚,把能并发的部分从串行改成并发;第二步是审视调用链路里有没有”上游已经顺手拿到、下游又要重新去查”的重复劳动,把这部分数据预取并回传给下游,减少下游的重复调用——这两步分别针对”链路本身的执行方式”和”链路上的重复劳动”两个不同维度做优化,通常需要依次施展才能拿到最大收益。

结构图

flowchart LR
    subgraph 串行["优化前:串行获取 60ms"]
        S1["A 10ms"] --> S2["B 15ms"] --> S3["C 10ms"] --> S4["D 20ms"] --> S5["E 10ms"]
    end
    subgraph 并发["第一步:识别依赖关系后并发 30ms"]
        P1["A、B、D 并发获取"]
        P2["C 依赖A、B,随后获取"]
        P3["E 依赖C,最后获取"]
        P1 --> P2 --> P3
    end
    subgraph 预取["第二步:预取E所需的F并回传 25ms"]
        Q1["获取A/B/D时\n顺带预取F(5ms)"]
        Q2["C依赖A、B"]
        Q3["E需要F,已被回传\n无需下游重新查询"]
        Q1 --> Q2 --> Q3
    end
    串行 -.->|"识别可并发依赖"| 并发
    并发 -.->|"识别下游重复查询\n提前预取回传"| 预取

参考来源

- 位置:《高可用架构(第1卷)》第3章《电商架构热点专题》"3.1 亿级商品详情页架构演进技术解密"节,"3.1.3 遇到的一些问题和解决方案"(源文件:_epub-src/OEBPS/Text/Chapter3_1_4.xhtml) - 结论依据:原文详细举例说明数据A~E的串行(60ms)与并发化(30ms)获取耗时对比,以及"假设数据E还依赖数据F(5ms)……在此服务中在取数据A/B/D时预取数据F,那么整体性能就变为了25ms",直接支撑本卡片结论与结构图。 - 原始内容:如果串行获取那么需要60ms……如果并发化获取需要30ms,能提升一倍的性能。假设数据E还依赖数据F(5ms),而数据F是在数据E服务中获取的,此时就可以考虑在此服务中在取数据A/B/D时预取数据F,那么整体性能就变为了25ms。