【AI架构】拆开 Dify 看企业级 AI 平台:服务、存储、网络与安全的边界

很多人把 Dify 当成"给大模型套了一层可视化界面"的 Web 应用。我把它 main 分支的 Docker Compose 和环境变量模板翻了一遍,看到的是另一种东西:模块化单体 + 多进程角色 + 独立隔离执行服务 + 专用存储组件。本文从服务拓扑、四类存储、三种网络信任域、凭据与插件供应链几个角度拆解它的边界设计,最后给出从单机 Compose 走向生产部署的五步路线。

本文依据 langgenius/dify main 分支在 2026-10-25 公开的 Docker Compose 与环境配置。Dify 迭代很快,组件和配置随版本会变;文中的图表达的是职责和关键调用关系,不是逐函数验证过的调用图,部分组件需要显式启用,并非默认全部运行。

一、它早就不是一个"套壳 Web 应用"了

如果只看 Dify 最早的用法,它确实很像一个"给大模型加了一层可视化界面"的 Web 应用:浏览器里配 Prompt、选模型,后端调 LLM API,再把结果送回前端。最简单的聊天应用,四个方块就画完了:

  flowchart LR
    U[用户] --> W[Web 前端]
    W --> A[API 服务]
    A --> D[(关系数据库)]
    A --> L[外部大模型 API]

但 AI 应用一旦从"聊天窗口"走向承载业务流程,请求—响应这个模型就开始不够用了:

  • 知识库要上传、解析、切分、Embedding、建索引,同时还要支撑在线低延迟检索。
  • Workflow 要处理节点依赖、条件分支、循环、并行,并持续往外推流式事件。
  • 插件和工具要装第三方代码、调外部服务,甚至跑用户写的脚本。
  • Agent 可能拿到文件操作、命令执行和更长生命周期的自主任务能力。
  • 生产环境还要稳定性、弹性扩容、资源隔离、持久化、故障恢复和审计。

先说清楚一点:上面这条演进脉络是我从当前架构反推出来的,不是版本考古,不代表 Dify 真的依次发布过这些部署形态。

有意思的是 Dify 应对这些需求的方式。它没有把每个功能都拆成微服务,而是保留一个统一的 Python 业务核心,再把任务执行、插件执行、代码沙箱、Agent Runtime 和基础设施一块块剥出去。最后长成的形态,我会这样概括:

模块化单体 + 多进程角色 + 独立隔离执行服务 + 专用存储组件。

二、服务架构:一个业务核心,多种运行角色

服务拓扑

  flowchart TB
    Client[浏览器 / 应用客户端 / 外部系统] --> Gateway[Nginx 入口网关]

    subgraph Presentation[接入层]
      Web[Web / Next.js]
      Gateway --> Web
    end

    subgraph Business[核心业务层]
      API[API Server / Flask]
      Workflow[Workflow Engine]
      Knowledge[RAG / 知识库服务]
      App[应用、会话、权限等业务模块]
      API --> Workflow
      API --> Knowledge
      API --> App
    end
    Gateway --> API
    Web --> API

    subgraph Jobs[后台任务层]
      Redis[(Redis / Broker 与缓存)]
      Worker[Celery Worker]
      Beat[Celery Beat]
      Beat --> Redis
      Redis --> Worker
    end
    API --> Redis

    subgraph Runtime[独立执行服务]
      Plugin[Plugin Daemon]
      Sandbox[Code Sandbox]
      Agent[Agent Backend]
      AgentShell[Local Sandbox]
      SSRF[SSRF Proxy]
      AgentProxy[Agent SSRF Proxy]
      Agent --> AgentShell
      AgentShell --> AgentProxy
      Sandbox --> SSRF
    end
    Workflow --> Plugin
    Workflow --> Sandbox
    API --> Agent
    Worker --> Plugin

    subgraph Storage[数据与存储]
      SQL[(关系数据库)]
      Vector[(向量数据库)]
      Object[(文件或对象存储)]
    end
    API --> SQL
    Worker --> SQL
    Knowledge --> Vector
    Worker --> Vector
    API --> Object
    Worker --> Object

    Plugin --> External[模型 / 工具 / 外部 API]
    Agent --> Plugin

读这张图要注意两点:Workflow Engine、RAG、应用管理都是 API 代码内部的逻辑模块,不是三个容器;箭头表示业务依赖,不代表每条线都是一次网络 RPC。api_websocket、KnowledgeFS 这类可选部署模式我没画进去。

每个服务管什么

服务主要职责为什么要单独存在
nginxHTTP 入口、反向代理与路由统一外部入口
web控制台、Workflow 可视化编辑、应用前端展示层与业务处理分离
apiHTTP API、应用与工作流编排、权限、核心业务领域逻辑只维护一份
worker文档处理、索引构建等后台任务把耗时工作移出请求链路
worker_beat定时任务调度定时触发维护和后台工作
redis缓存、Celery Broker、部分临时状态解耦请求与后台消费
plugin_daemon插件生命周期与运行插件代码不进 API 主进程
sandboxWorkflow 代码节点执行承载高风险的代码运行
ssrf_proxy代理并约束受控组件的出站请求管住出站访问风险
agent_backend独立的 Agent 执行后端Agent 运行边界独立化
local_sandboxAgent 的 Shell 与工作空间隔离命令和文件操作
agent_ssrf_proxyAgent 沙箱专用出站代理对 Agent 网络访问做更细的规则
关系库 / 向量库 / 文件存储各自承载不同类型的数据一致性、检索、大文件各司其职

这张表里最值得记住的一件事是:企业级架构不等于全部微服务。 Compose 里 api 和 worker 用的是同一个后端镜像,只是启动模式不同。业务模型集中维护,进程却能按 HTTP 压力和后台负载分别扩缩。这是一个非常务实的复杂度折中:拆运行时,不拆领域模型。

一次 Workflow 运行经过哪里

以一个带知识检索、LLM 和代码节点的 Workflow 为例:

  sequenceDiagram
    participant C as 客户端
    participant A as API / Workflow
    participant V as 向量数据库
    participant P as Plugin Daemon
    participant S as Code Sandbox
    participant DB as 关系数据库
    C->>A: 提交 Workflow 执行请求
    A->>DB: 加载应用与 Workflow 配置
    A->>V: 检索知识片段
    V-->>A: 候选文本
    A->>P: 调用模型或插件能力
    P-->>A: 模型或工具结果
    A->>S: 执行代码节点
    S-->>A: 执行结果
    A->>DB: 记录运行数据
    A-->>C: 返回结果或流式事件

这里有一个常见误解:以为每次 Workflow 运行都丢给 Celery Worker 执行。不是。Workflow 引擎负责图调度:加载节点和连线、维护上下文、判断依赖是否满足、处理分支循环、收集输出和事件,它是 API 后端的一部分。Celery 主要承担文档处理、定时任务这类异步工作,具体走哪个队列取决于任务类型和配置。

RAG 其实是两套负载

  flowchart LR
    subgraph Offline[离线文档入库]
      File[文档上传] --> Task[异步处理任务]
      Task --> Parse[解析 / 清洗 / 分段]
      Parse --> Embedding[Embedding]
      Embedding --> Index[索引构建]
    end
    subgraph Online[在线检索]
      Query[用户 Query] --> Retrieve[语义 / 关键词检索]
      Retrieve --> Rank[重排 / 筛选]
      Rank --> Context[上下文拼装]
      Context --> LLM[LLM 生成]
    end
    Index --> Vector[(向量索引)]
    Vector --> Retrieve
    File --> Object[(文件存储)]
    Parse --> Meta[(元数据)]

上传一份文档可能触发几分钟的解析和索引,而用户的一次检索要求的是毫秒到秒级的响应。吞吐型负载和延迟型负载被拆到不同的运行角色上,是 Dify 从"能跑"走向"可运营"的关键一步:两者终于不必共用同一套资源配置。

三、存储:按数据的性质分层,别把一切塞进一个库

存储是 Dify 架构里最容易被忽略、却最直接决定可靠性的部分。它面对的至少是四种性质完全不同的数据:事务记录、临时状态、向量索引、非结构化文件。

四类存储

存储典型内容核心要求运维关注点
关系数据库(通常 PostgreSQL)应用定义、用户与租户、Workflow 配置、会话、运行记录、文档元数据事务、一致性、可查询备份、迁移、索引、连接池
Redis缓存、Celery Broker、部分协调与临时状态低延迟、高频读写内存、过期与持久化策略、Broker 可靠性
向量数据库(Weaviate / Qdrant / Milvus / pgvector 等)Embedding 与相似度检索索引ANN 检索性能维度、索引重建、内存与磁盘
文件存储(本地 / OpenDAL 适配的后端)上传文件、解析产物、业务生成文件大对象存取、生命周期共享访问、权限、备份与清理

当前 docker/.env.example 里的默认值是:关系库 PostgreSQL(DB_TYPE=postgresql),向量库 Weaviate(VECTOR_STORE=weaviate),文件存储走 OpenDAL 的文件系统后端(STORAGE_TYPE=opendal、OPENDAL_SCHEME=fs)。这些是模板默认值,不是唯一选项。

关系数据和向量索引为什么要分开

两者不是替代关系。关系库里存的是"这个文件属于哪个数据集、切成了哪些分段、索引任务处于什么状态";向量库里存的是能快速比较语义相似度的向量。

麻烦在于它们之间没有一个统一事务。文件上传后,系统先存元数据,再异步解析、切分、生成向量。向量构建中途失败,文件记录不一定要回滚,但必须有明确的处理状态、重试和清理策略;反过来删除一份文档,元数据、向量和原始对象又要联动清理。

所以 Dify 的知识库路径本质上是一条跨多种存储介质的异步数据流水线,而不是单库事务。运维上要把索引重建、幂等处理、失败恢复和悬挂对象清理当成日常机制,而不是出事后的补救。

文件存储必须和计算节点解耦

默认的本地文件系统适合单机体验。一旦横向扩展,问题马上出现:多个 API、Worker 实例怎么访问同一份上传文件?容器重建后文件还在吗?异步任务被调度到另一台机器上,原始文件从哪读?

生产环境应该用 Dify 支持的外部对象存储或共享存储,让应用实例尽量无状态。文件内容和元数据要在权限、引用关系、生命周期、备份上一起设计,而不是把本地目录复制到每台机器上。

插件和 Agent 的存储常被漏掉

除了业务数据,plugin_daemon 有插件包、工作目录和运行缓存;Agent 沙箱有工作空间和 Home 目录。它们的安全属性和生命周期都跟普通上传文件不一样。

当前 Compose 里 local_sandbox 的工作空间挂的是独立 Volume。这便于管理执行空间,但要清醒:Volume 不等于隔离、加密或跨租户权限控制。 只要是多用户、多租户的自主执行场景,任务级目录隔离、清理策略、用户身份、配额和宿主机文件权限都得逐项验证。

存储落地清单

  1. 关系库、Redis、向量库、对象存储分别规划容量、可用性和恢复目标。一个数据库快照不是完整备份。
  2. 业务库优先保证可靠备份和恢复演练;向量索引要先确认能否由原文和 Embedding 重建,以及重建要多久、花多少钱。
  3. 给文件、分段、向量、插件包、Agent 工作空间都定好生命周期和删除策略,多租户场景尤其注意资源清理。
  4. 评估 Redis 同时做缓存和 Broker 的风险,别让缓存驱逐或内存压力影响任务投递。
  5. 高并发下盯住连接池、慢查询、向量检索 P95/P99、Worker 积压和文件存储错误率。

四、网络:重点不是"能连通",而是"谁不该连通"

我认为 Dify 网络设计里最有代表性的变化,是把不可信代码和 Agent 执行环境从普通内网里剥出来,再通过受限代理只开放必要的访问。

三种信任等级

  • 入口域:Nginx 和必要的 Web/API 路由,对客户端暴露服务端点。
  • 业务域:API、Worker、数据库、Redis、向量库、插件服务,承担主要业务协作。
  • 受限执行域:Code Sandbox、Agent Local Sandbox,执行相对不可信的脚本和命令。

这三个域不该拥有同样的网络权限。沙箱如果能随意访问数据库、Redis、Docker 守护进程或云元数据接口,一段恶意代码就能绕过应用层的全部权限控制。

SSRF Proxy 在防什么

SSRF(服务端请求伪造)在 AI 工作流里比在传统 Web 里更值得警惕:用户可以配 HTTP 节点、填 URL、调外部工具,模型自己也可能生成要访问的地址。这些请求如果由有内网权限的服务直接发出,就可能打到本不该暴露的内部端点。

Dify 的 Compose 里有一个基于 Squid 的 ssrf_proxy,配合 HTTP_PROXY、HTTPS_PROXY 使用。环境模板里的 SSRF_PROXY_ALLOW_PRIVATE_IPS 默认留空,即默认拒绝访问私有地址,确实要访问某个内网 API 时再显式加白名单。

  flowchart LR
    Tool[HTTP 节点 / Sandbox 出站请求] --> Proxy[SSRF Proxy]
    Proxy --> Check{地址与策略检查}
    Check -->|允许| Public[允许的目标服务]
    Check -->|拒绝| Deny[拦截请求]

但别由此推断"所有流量都被强制代理"。这是一个代理控制面,实际效果取决于:哪些进程真的走了代理、Docker 网络有没有旁路、HTTP 客户端是否遵守代理环境变量、宿主机和容器上有没有配防火墙。

Agent Local Sandbox:一条更硬的边界

Agent 的风险和代码节点不是一个量级。它会连续执行 Shell 命令、创建文件、下载资源,并在多轮之间保留工作状态。所以 Dify 给 Agent 单独配了三件套:agent_backend、local_sandbox、agent_ssrf_proxy。

最值得注意的是网络划分。官方 Compose 注释写得很明确:local_sandbox 不加入普通的 default 网络,只加入 agent_sandbox_network 和 local_sandbox_proxy_network。Agent Backend 通过受限执行接口和它通信,它要访问其他服务,必须走 Agent 专用的 Squid 代理,而这个代理的配置模板只放行特定的 Agent 接口和文件传输路径。

  flowchart TB
    API[API / Worker] --> Agent[Agent Backend]
    Agent -->|受控 Shell API| Local[Local Sandbox]
    Local -->|HTTP / HTTPS Proxy| Proxy[Agent SSRF Proxy]
    Proxy -->|允许的 Agent 接口| Agent
    Proxy -->|允许的文件 API| API
    Local -.-x DB[(业务数据库)]
    Local -.-x Redis[(Redis)]
    Local -.-x Internal[其他内部服务]

图里的虚线叉号是预期的隔离边界,不是对所有协议、所有路径都验证过的结论。代理环境变量本身拦不住所有出站,生产部署仍然要检查容器路由、网络策略和实际可达性。

这背后的思路一句话就能说完:不因为沙箱要执行代码,就把整个内网交给它;只开放完成任务所必需的那几个接口。

生产环境怎么加固

在 Kubernetes 或独立 VPC 里,可以在官方配置之上再往前走:

  • 入口只暴露必需的 HTTPS 端口,数据库、Redis、插件调试端口一律不对公网开放。
  • API、Worker、存储和不同沙箱分属不同命名空间或网络策略,默认拒绝、按需放行。
  • 出站统一走专用的 Egress Gateway 或代理,重点处理 DNS 重绑定、重定向、私网 CIDR、云厂商元数据地址和代理绕过。
  • 跨主机、跨集群的服务通信用 TLS 和服务身份认证,内网同样最小权限。
  • 定期做 SSRF 和网络隔离的回归测试,确认沙箱能访问哪些 IP、端口和 URL 路径。

这些是我推荐的企业安全措施,官方 Compose 并没有全部替你做好。

五、安全:把信任边界落到进程、网络和凭据上

代码执行不能和主 API 共享进程权限

Workflow 代码节点和 Agent Shell 命令是两个最高风险的执行入口。应用层做了输入校验,也不能把它们当普通函数调用来信任。Dify 把它们放进独立的 sandbox 和 local_sandbox,为资源限制、出站控制和独立扩容留出了边界。

但跑在容器里不等于天然安全。 沙箱真正的强度取决于 Linux 权限、Capabilities、Seccomp、只读文件系统、资源配额、宿主机挂载、镜像供应链,以及有没有用更强的虚拟化隔离。看到服务名叫 sandbox 就放心,是最危险的误判。

内部服务调用要有身份

API 与 Plugin Daemon、Agent Backend 之间用内部 API Key 或 Token 认证。环境模板明确写了 DIFY_AGENT_API_TOKEN 的示例值只能用于开发,生产必须更换,Agent Backend 那边也要配上匹配的 Token,Release Notes 里也记录了这一要求。

它防的是"进了容器网络就能随便调高权限内部服务"。但 Token 的价值完全取决于凭据管理:

  • 不要把示例密钥带上生产,也不要把真实密钥提交进 Git。
  • 用 Secret Manager、Kubernetes Secret 或类似机制注入,并定期轮换。
  • API、插件、Agent、沙箱各用各的凭据,别让所有服务共享一把高权限密钥。
  • 调试接口、内部管理接口和文件传输 API 单独做访问控制和审计日志。

插件就是供应链边界

插件带来了扩展性,也带来了第三方代码和依赖。独立的 Plugin Daemon 让插件和主业务进程分开,配置里也有插件大小、执行超时、签名校验(如 FORCE_VERIFYING_SIGNATURE)等参数。

但签名校验不等于代码审计,执行超时也替代不了沙箱化。 企业落地时要对插件来源、版本锁定、依赖漏洞、网络权限、文件权限和敏感凭据注入定统一标准,低信任来源的插件走单独的审核流程和运行环境。

多租户:有租户字段不等于隔离完成

Dify 面向多应用、多用户、多组织,租户隔离要端到端成立,至少得检查四件事:

  1. 关系数据查询始终携带租户 / 应用身份;
  2. 文件和向量检索按归属与授权范围过滤;
  3. 缓存键、任务上下文和日志不跨租户混用;
  4. 插件密钥、模型供应商凭据、Agent 工作目录互不泄露。

这是从架构风险推出来的审查项,不代表 Dify 的所有接口都已经通过了多租户渗透测试。

AI 特有的风险,网络代理管不了

网络和代码执行都隔离好了,AI 平台依然要面对 Prompt Injection、工具调用越权、知识库内容污染、敏感信息经由模型上下文泄露。代理只能限制请求能去哪,判断不了这次调用是否符合业务授权。 高风险工具还得叠加参数校验、业务权限检查、人工审批、最小权限凭据和审计记录。

六、从单机 Compose 到生产部署的五步

把官方 Compose 原样放上服务器,能撑住一部分低负载场景,但它不自带高可用,也不自带企业级安全。正式上生产,我会按这个顺序推进:

第一步,业务进程解耦。 API 和 Worker 分别水平扩容,给 CPU 密集的文档解析、Embedding 和普通后台任务设好队列与并发参数,Workflow 请求延迟单独监控。

第二步,状态外置。 数据库、Redis、向量库、对象存储交给有备份恢复能力的服务,确保 API/Worker 实例随时可替换,不会丢本地文件。

第三步,执行安全域隔离。 插件、代码执行、Agent Shell 各自设计网络边界、身份凭据和资源限额,限制出站,并实测是否存在直连内部数据库和云元数据接口的路径。

第四步,可观测与恢复。 为 API 延迟、Workflow 节点耗时、Celery 积压、向量检索、模型供应商错误率、插件异常、存储耗尽、被拒绝的出站请求建指标和日志链路,并演练数据库恢复、队列积压处理和索引重建。

第五步,变更治理。 固定镜像版本,跟踪官方迁移脚本和 Release Notes。Agent Runtime、插件 Daemon、向量库或数据库升级前先核对配置变化和兼容性,再灰度。

结语

Dify 值得研究,不是因为它堆了多少容器,而是它给不同性质的工作划了不同的边界:

  • 同步业务和后台任务分开,用户请求和批量索引各自调度;
  • 关系存储、向量检索、文件存储和缓存分开,一致性、检索效率和大对象存储各自优化;
  • 业务进程和不可信执行环境分开,脚本、插件、Agent 操作可以施加不同的权限和资源控制;
  • 普通内网和受限沙箱网络分开,有执行能力的组件不能直接摸到高价值内部服务。

所以我更愿意把 Dify 看成一套逐步强化的 AI 应用运行平台,而不是一个大模型聊天 Web UI。它真正的企业级价值在于可扩展和可隔离同时成立:新的 AI 能力能接进来,接进来之后还能被治理、观测和约束。

但也要说清楚:企业级不是 docker compose up 成功之后就自动拥有的属性。 高可用、租户隔离、凭据治理、审计、灾备、出站安全和供应链控制,都要结合自己的安全等级和部署环境,一项一项去实现、去验证。

参考