← 返回文章列表

Dify 企业级实验(04):性能优化实战——长流程从 60 秒到秒回有哪些手段?

1. 业务场景

先讲一个我们实际遇到的场景。

客服在电话这头等着答案,电话那头的用户已经不耐烦了。订单分析工作流 8 个节点、串行 3 个 LLM 加 1 次 HTTP 请求,单次跑 40 秒以上——用户等不起,客服更等不起。

我们第一次接这类需求时,第一反应也是「慢就换更强的模型、加机器」。真正动手才发现——慢往往不是某个节点的错,而是结构问题。不测就改,改的往往不是瓶颈。

「工作流跑得慢」是生产环境最高频的问题之一:不是功能不对,是慢到没法用。慢在哪、怎么改、改完有没有用,三个问题一个都答不上来,就只能继续等。

这不是个例。任何「长流程、多 LLM 串行、含外部 HTTP 调用」的应用都是这个模式:报告生成、多步分析、串联校验——环节越多,越慢。

2. 场景痛点

这个流程的痛点,在客服和运维身上体现得最直接:

本质上,慢往往不是某个节点的错,而是结构问题——串行、重复、不可观测,三样占全了

3. 方案:为什么是先测量再优化

Dify 的节点级耗时分析 + 并行分支 + 结果缓存,正好对应「排查-优化-验证」三个动作。选它的理由:

这篇文章我们就用它把一条 40 秒+ 的订单分析链,优化到 15 秒内、缓存命中秒回。

4. 整体架构

graph TD subgraph sub_before["优化前(dify104_04_01,串行)"] b_start["开始:query/product_id"] b_a["情感分析 A(LLM)"] b_b["意图识别 B(LLM)"] b_c["库存查询 C(HTTP)"] b_d["汇总分析 D(LLM)"] b_e["模板输出 E"] b_end["结束"] b_start --> b_a --> b_b --> b_c --> b_d --> b_e --> b_end end %% 串行总耗时 ≈ 8 + 8 + 5 + 12 + 0.1 ≈ 33s+,实测 40s+ subgraph sub_after["优化后(dify104_04_02)"] a_start["开始:query/product_id"] read_cache["读取结果缓存(http → KV)"] query_cache["查询结果缓存(code:md5 键)"] hit{"缓存是否命中"} end_hit["结束(cache_result,秒回)"] pa["情感分析 A(LLM)"] pb["意图识别 B(LLM)"] pc["库存查询 C(HTTP)"] merge["并行结果合并(variable-aggregator)"] da["汇总分析 D(LLM)"] build_write["组装缓存写入"] write_cache["写入结果缓存(http → KV)"] te["模板输出 E"] a_end["结束"] a_start --> read_cache --> query_cache --> hit hit -- "命中" --> end_hit hit -- "未命中" --> pa & pb & pc pa --> merge pb --> merge pc --> merge merge --> da --> build_write --> write_cache --> te --> a_end end

链路很清晰:先查缓存 → 未命中走并行分析 → 合并汇总 → 写缓存 → 输出。缓存前置 + 并行分支,是这条链提速的两个支点。

5. 模块设计

5.1 缓存查询(cd_cache)

缓存键 = 参数组合的 md5;命中即返回缓存值,未命中继续往下跑:

def main(query: str, product_id: str, cache_body: str) -> dict:

    import json, hashlib

    key = hashlib.md5((str(query or "") + "|" + str(product_id or ""))

                      .encode("utf-8")).hexdigest()

    hit, cached = "false", ""

    try:

        cache = json.loads(cache_body or "{}").get("data") or []

        for item in cache:

            if isinstance(item, dict) and item.get("key") == key:

                hit, cached = "true", item.get("value", "")

                break

    except Exception:

        pass

    return {"cache_key": key, "hit": hit, "cached": cached}

5.2 缓存命中分流(if5)

命中走 end_cache 直接返回;未命中从默认 false 口扇出三条并行边(1.16 实测合法,if-else 默认口可以多边扇出):

- id: if5

  data:

    type: if-else

    title: 缓存是否命中

    cases:

    - case_id: hit

      logical_operator: or

      conditions:

      - comparison_operator: is

        value: 'true'

        variable_selector: [cd_cache, hit]

# 边:if5(hit) → end_cache;if5(false) → lm_senti / lm_intent / http_stock

5.3 并行结果合并(va_merge)

A/B/C 三个无依赖节点并行执行后,用 variable-aggregator 合并各支结果(102-06 实测:并行分支变量不合并,下游拿不到):

- id: va_merge

  data:

    type: variable-aggregator

    title: 并行结果合并

    output_type: string

    variables:

    - variable_selector: [lm_senti, text]

    - variable_selector: [lm_intent, text]

    - variable_selector: [http_stock, body]

5.4 缓存写入(cd_cache_write + http_cache_set)

汇总完成后组装 KV 写入请求(op: upsert),下次同参数调用直接命中:

def main(cache_key: str, summary: str) -> dict:

    import json

    return {"payload": json.dumps(

        {"op": "upsert", "key": cache_key, "value": summary},

        ensure_ascii=False)}

5.5 模板输出(E)

输出层用 template-transform 而非 LLM(验证「模板 vs LLM」选型收益——固定格式输出模板零 Token 消耗):

# 客服处理报告(优化后)

- 用户问题:{{ query }}

- 并行分析结果:{{ merged }}

- 综合答复:{{ summary }}

*结果已缓存,同参数再次查询秒回*

6. 运行验证

输入 预期 结果
同参数首次调用(query + product_id) 走 A/B/C 并行(最慢分支约 8s,而非串行 21s)+ D 汇总,结果写入缓存 通过(文档运行验证)
同参数二次调用 命中缓存,end 输出 cache_hit=true,秒回 <1s 通过(实测)
整体耗时对比 优化前 40s+ → 优化后 <15s(P95) 通过(文档运行验证)
并行改造后 A/B/C 总耗时 = 最慢分支耗时(8s 而非 21s) 通过(设计验证)

7. 实战坑

现象 修复
不测量直接改 优化方向错,改了不影响耗时的地方 先做节点级耗时分析(elapsed_time)再动手(实测,102-18)
并行分支变量不合并 下游拿不到各支结果 variable-aggregator 合并后再进下游(实测,102-06/07)
缓存用文件/内存模拟 code 沙箱禁写文件,报 PermissionError: /tmp 缓存走 KV upsert(key→value)持久化(实测,本实验)
以为 if-else 默认口只能单出口 不敢并行扇出 实测 1.16 默认 false 口多边扇出合法(实测,本实验)
LLM prompt 变量用双花括号 不替换,字面传给模型 一律 {{#节点.字段#}} 三花括号(实测,102-12)
缓存无过期策略 结果永久陈旧 按业务加 TTL/版本号(实验文档设计约束)

8. 实验文档及源码获取

文章聚焦核心配置与采坑点;实验的完整分步操作(节点搭建/参数表/调试指引)见实验文档原文。

联系我

15088711270

手机端点击号码可直接拨打 · 桌面端可复制

微信二维码

扫码加微信 · 备注「门户」更快通过