架构总览
相关源文件
本页面内容基于以下源文件生成:
- api/app.py
- api/dify_app.py
- api/extensions/ext_database.py
- api/extensions/ext_celery.py
- docker/docker-compose.yaml
- web/app/layout.tsx
- api/extensions/ext_otel.py
- api/extensions/ext_mail.py
- api/extensions/init.py
- api/extensions/ext_redis.py
- web/next/index.ts
- web/utils/index.ts
- web/config/index.ts
- web/.storybook/main.ts
- web/i18n-config/index.ts
- api/controllers/web/app.py
- api/services/errors/app.py
- web/plugins/eslint/index.js
- sdks/nodejs-client/src/index.ts
- api/controllers/console/setup.py
Dify 采用前后端分离的微服务化架构设计,后端基于 Flask 框架构建,通过扩展机制集成数据库、缓存、消息队列等基础设施;前端基于 Next.js 实现服务端渲染与状态管理。整体架构强调模块化、可观测性与高可用性,支持从单机部署到分布式集群的平滑扩展。
系统架构总览
正在加载图表渲染器...
架构要点说明:
- 前后端分离:前端 Next.js 应用独立部署,通过 REST API 与后端 Flask 服务通信,支持服务端渲染(SSR)优化首屏性能与 SEO
- 扩展驱动设计:后端采用 Flask 扩展模式(
init_app),将数据库、缓存、任务队列等基础设施解耦为独立模块,便于测试与替换 - 异步任务处理:通过 Celery + Redis 实现长时间运行任务的异步化(如工作流执行、数据索引),避免阻塞 HTTP 请求线程
- 可观测性集成:OpenTelemetry 提供分布式追踪与指标采集,支持 OTLP 协议导出到 Jaeger/Prometheus 等后端
- 高可用支持:Redis 支持 Sentinel 模式实现故障转移,数据库连接池针对 Gevent 协程进行兼容性适配
关键证据点:
- 应用入口区分迁移模式与生产模式:api/app.py:6-37
- 前端根布局集成全局 Provider:web/app/layout.tsx:5-97
应用启动与初始化
Flask 应用入口与工厂模式
后端服务采用工厂模式创建 Flask 应用实例,根据运行环境选择不同的初始化路径。入口文件 api/app.py 通过命令行参数判断当前是否为数据库迁移命令(flask db),从而决定加载轻量级的迁移应用还是完整的生产应用。
python1# api/app.py 核心逻辑 2def is_db_command() -> bool: 3 if len(sys.argv) > 1 and sys.argv[0].endswith("flask") and sys.argv[1] == "db": 4 return True 5 return False 6 7if is_db_command(): 8 from app_factory import create_migrations_app 9 app = create_migrations_app() 10else: 11 from app_factory import create_app 12 app = create_app() 13 celery = cast("Celery", app.extensions["celery"])
职责边界:
- 做什么:解析命令行参数、选择应用工厂、挂载 Celery 实例到 Flask 扩展字典
- 不做什么:不直接配置数据库连接、不定义路由、不处理业务逻辑
关键数据结构:
sys.argv:命令行参数列表,用于判断运行模式app.extensions["celery"]:Flask 扩展字典,存储 Celery 应用实例引用
错误处理与边界条件:
- 迁移模式下仅加载数据库配置,避免初始化不必要的扩展(如 Redis、Celery)
- 生产模式下通过
cast类型断言确保 Celery 已正确初始化,否则抛出KeyError
证据点: api/app.py:6-37
数据库连接与 Gevent 兼容性处理
数据库扩展 ext_database.py 在初始化时针对 Gevent 协程环境进行了特殊适配。由于 Gevent 通过 monkey patching 替换标准库的阻塞调用,SQLAlchemy 连接池的 reset 事件可能在协程切换时引发竞态条件,因此需要自定义重置逻辑。
python1# api/extensions/ext_database.py 核心逻辑 2def _setup_gevent_compatibility(): 3 @event.listens_for(Pool, "reset") 4 def _safe_reset(dbapi_connection, connection_record, reset_state): 5 if reset_state.terminate_only: 6 return 7 try: 8 hub = gevent.get_hub() 9 if hasattr(hub, "in_callback") and getattr(hub.loop, "in_callback", False): 10 gevent.spawn_later(0, lambda: _safe_rollback(dbapi_connection)) 11 else: 12 _safe_rollback(dbapi_connection) 13 except (AttributeError, ImportError): 14 _safe_rollback(dbapi_connection)
关键调用链:
init_app(app)被应用工厂调用db.init_app(app)初始化 SQLAlchemy 扩展_setup_gevent_compatibility()注册连接池事件监听器- 连接归还池时触发
_safe_reset,在安全上下文中执行回滚
错误处理策略:
_safe_rollback捕获所有异常并记录日志,避免回滚失败导致连接泄漏- 检测 Gevent hub 状态,在回调上下文中延迟执行回滚,避免嵌套调用
证据点: api/extensions/ext_database.py:16-62
后端核心扩展架构
Celery 异步任务队列配置
Celery 扩展负责配置异步任务队列,支持 Redis 作为 Broker 和 Backend,并提供 SSL 加密、Sentinel 高可用等企业级特性。任务分为两类:即时任务(如工作流触发)和定时任务(如缓存清理)。
python1# api/extensions/ext_celery.py 核心配置 2celery_app = Celery( 3 app.name, 4 task_cls=FlaskTask, 5 broker=dify_config.CELERY_BROKER_URL, 6 backend=dify_config.CELERY_BACKEND, 7) 8 9# 定时任务调度示例 10if dify_config.ENABLE_CLEAN_EMBEDDING_CACHE_TASK: 11 beat_schedule["clean_embedding_cache_task"] = { 12 "task": "schedule.clean_embedding_cache_task.clean_embedding_cache_task", 13 "schedule": crontab(minute="0", hour="2", day_of_month=f"*/{day}"), 14 }
关键数据结构:
FlaskTask:自定义任务基类,在任务执行时自动推送 Flask 应用上下文beat_schedule:Celery Beat 调度字典,定义定时任务的执行计划broker_transport_options:Sentinel 配置,包含主节点名称、超时时间等
SSL 配置逻辑:
- 仅当
BROKER_USE_SSL=True且 Broker URL 以redis://或rediss://开头时启用 - 支持配置 CA 证书、客户端证书、密钥文件等 SSL 参数
定时任务分类:
| 任务类型 | 示例任务 | 调度频率 |
|---|---|---|
| 清理任务 | clean_embedding_cache_task | 每月 2:00 AM |
| 监控任务 | queue_monitor_task | 每 30 分钟 |
| 工作流任务 | workflow_schedule_task | 可配置间隔 |
证据点: api/extensions/ext_celery.py:13-209
Redis 缓存客户端封装
Redis 扩展通过 RedisClientWrapper 类封装客户端实例,支持延迟初始化和动态切换。这种设计特别适用于 Sentinel 模式,当主节点故障转移后,客户端需要重新获取新的主节点地址。
python1# api/extensions/ext_redis.py 核心封装 2class RedisClientWrapper: 3 _client: Union[redis.Redis, RedisCluster, None] 4 5 def __getattr__(self, item: str) -> Any: 6 if self._client is None: 7 raise RuntimeError("Redis client is not initialized. Call init_app first.") 8 return getattr(self._client, item)
职责边界:
- 做什么:提供统一的 Redis 操作接口、支持 Sentinel/Cluster 模式、SSL 连接配置
- 不做什么:不实现缓存策略(如 TTL 管理)、不封装业务逻辑
SSL 配置映射:
python1cert_reqs_map = { 2 "CERT_NONE": ssl.CERT_NONE, 3 "CERT_OPTIONAL": ssl.CERT_OPTIONAL, 4 "CERT_REQUIRED": ssl.CERT_REQUIRED, 5}
客户端缓存支持:
- 仅在 RESP3 协议下启用(
REDIS_SERIALIZATION_PROTOCOL >= 3) - 通过
CacheConfig配置客户端侧缓存,减少网络往返
证据点: api/extensions/ext_redis.py:29-158
OpenTelemetry 可观测性集成
OpenTelemetry 扩展提供分布式追踪和指标采集能力,支持 OTLP 协议导出到 Jaeger、Prometheus 等后端。资源配置遵循 Semantic Conventions 1.32.0 规范,包含服务名称、版本、进程 ID 等标准属性。
python1# api/extensions/ext_otel.py 资源配置 2resource = Resource( 3 attributes={ 4 SERVICE_NAME: dify_config.APPLICATION_NAME, 5 SERVICE_VERSION: f"dify-{dify_config.project.version}-{dify_config.COMMIT_SHA}", 6 PROCESS_PID: os.getpid(), 7 DEPLOYMENT_ENVIRONMENT_NAME: f"{dify_config.DEPLOY_ENV}-{dify_config.EDITION}", 8 } 9)
导出器选择逻辑:
| 协议类型 | Trace 导出器 | Metric 导出器 |
|---|---|---|
grpc | GRPCSpanExporter | GRPCMetricExporter |
http | HTTPSpanExporter | HTTPMetricExporter |
| 其他 | ConsoleSpanExporter | ConsoleMetricExporter |
采样策略:
- 使用
ParentBasedTraceIdRatio采样器,根据配置的OTEL_SAMPLING_RATE决定是否采样 - 采样率范围 0.0-1.0,0.1 表示采样 10% 的请求
批处理配置:
max_queue_size:队列最大容量(默认 2048)schedule_delay_millis:导出间隔(默认 5000ms)max_export_batch_size:单次导出批量大小(默认 512)
证据点: api/extensions/ext_otel.py:14-136
邮件服务扩展
邮件扩展支持多种邮件服务提供商,包括 Resend、SMTP、SendGrid。通过配置 MAIL_TYPE 环境变量选择提供商,并在初始化时验证必要参数。
python1# api/extensions/ext_mail.py 提供商选择 2match mail_type: 3 case "resend": 4 resend.api_key = api_key 5 self._client = resend.Emails 6 case "smtp": 7 self._client = SMTPClient(server=..., port=..., use_tls=...) 8 case "sendgrid": 9 self._client = SendGridClient(sendgrid_api_key=..., _from=...)
错误处理:
- 未配置
MAIL_TYPE时记录警告日志但不抛出异常 - 缺少必要参数(如
RESEND_API_KEY)时抛出ValueError - 发送邮件前验证
to、subject、html参数非空
证据点: api/extensions/ext_mail.py:11-104
前端应用架构
Next.js 根布局与依赖注入
前端应用基于 Next.js App Router 架构,根布局 layout.tsx 负责注入全局依赖,包括状态管理、主题、国际化(i18nn)、监控等 Provider。这种设计确保所有页面共享统一的上下文环境。
tsx1// web/app/layout.tsx Provider 嵌套结构 2<JotaiProvider> 3 <ThemeProvider attribute="data-theme" defaultTheme="system"> 4 <NuqsAdapter> 5 <BrowserInitializer> 6 <SentryInitializer> 7 <TanstackQueryInitializer> 8 <I18nServerProvider> 9 <ToastProvider> 10 <GlobalPublicStoreProvider> 11 <TooltipProvider>{children}</TooltipProvider> 12 </GlobalPublicStoreProvider> 13 </ToastProvider> 14 </I18nServerProvider> 15 </TanstackQueryInitializer> 16 </SentryInitializer> 17 </BrowserInitializer> 18 </NuqsAdapter> 19 </ThemeProvider> 20</JotaiProvider>
Provider 职责说明:
| Provider | 职责 |
|---|---|
JotaiProvider | 原子化状态管理,用于全局状态共享 |
ThemeProvider | 主题切换(亮色/暗色/系统) |
NuqsAdapter | URL 状态同步,将状态持久化到查询参数 |
TanstackQueryInitializer | 服务端状态管理,处理数据缓存与同步 |
I18nServerProvider | 国际化,服务端获取用户语言偏好 |
SentryInitializer | 错误监控,上报前端异常 |
关键配置:
viewport设置禁止用户缩放(userScalable: false),确保移动端一致性体验suppressHydrationWarning避免主题切换时的水合警告
前端全局配置常量
配置文件 web/config/index.ts 定义了前端应用的全局常量,包括 API 端点、正则规则、默认参数、功能开关等。这些配置通过环境变量注入,支持不同部署环境的差异化配置。
typescript1// web/config/index.ts API 端点配置 2export const API_PREFIX = getStringConfig( 3 env.NEXT_PUBLIC_API_PREFIX, 4 'http://localhost:5001/console/api', 5) 6export const PUBLIC_API_PREFIX = getStringConfig( 7 env.NEXT_PUBLIC_PUBLIC_API_PREFIX, 8 'http://localhost:5001/api', 9)
关键配置分类:
| 类别 | 配置项 | 默认值 |
|---|---|---|
| API 端点 | API_PREFIX | http://localhost:5001/console/api |
| 安全 | CSRF_COOKIE_NAME | csrf_token 或 __Host-csrf_token |
| 验证 | MAX_VAR_KEY_LENGTH | 30 |
| 功能开关 | MAX_TOOLS_NUM | 环境变量控制 |
| 正则规则 | VAR_REGEX | /\{\{(#[\w-]{1,50}...)#\}\}/gi |
辅助函数:
getStringConfig(envVar, defaultValue):优先使用环境变量,回退到默认值getMaxVarNameLength(value):根据字符类型(中文/英文)返回最大变量名长度getMaxToken(modelId):根据模型 ID 返回最大 Token 数
证据点: web/config/index.ts:1-380
前端工具函数
通用工具函数
工具文件 web/utils/index.ts 提供了基础的异步错误处理、文本测量、随机字符串生成等通用函数。其中 asyncRunSafe 函数采用 Go 语言风格的错误处理模式,将异常转换为元组返回。
typescript1// web/utils/index.ts 异步错误包装 2export async function asyncRunSafe<T = any>(fn: Promise<T>): Promise<[Error] | [null, T]> { 3 try { 4 return [null, await fn] 5 } 6 catch (e: any) { 7 return [e || new Error('unknown error')] 8 } 9}
关键函数说明:
| 函数 | 职责 | 返回值 |
|---|---|---|
asyncRunSafe | 包装 Promise,捕获异常 | [Error] 或 [null, T] |
getTextWidthWithCanvas | 使用 Canvas 测量文本宽度 | number(像素) |
randomString(length) | 生成指定长度的随机字符串 | string |
getPurifyHref(href) | 转义 URL 防止 XSS | string |
fetchWithRetry(fn, retries) | 带重试机制的请求 | Promise<[Error] | [null, T]> |
错误处理策略:
asyncRunSafe捕获所有异常,包括unknown error情况fetchWithRetry在失败时递归调用,直到重试次数耗尽
关键数据流与调用链
正在加载图表渲染器...
数据流要点说明:
- 请求入口:浏览器请求首先到达 Next.js 根布局,初始化全局 Provider(状态管理、主题、国际化)
- API 调用:前端通过
API_PREFIX配置的端点发送 HTTP 请求到 Flask 后端 - 扩展初始化:Flask 应用启动时通过
init_app方法初始化数据库、Redis、Celery 等扩展 - 异步任务:长时间运行的操作(如工作流执行)通过 Celery 异步化,避免阻塞 HTTP 线程
- 结果存储:Celery 任务结果存储在 Redis 中,前端通过轮询或 WebSocket 获取状态更新
关键证据点:
- 前端布局初始化流程:web/app/layout.tsx:5-97
- 后端扩展初始化:api/app.py:6-37
核心设计决策与取舍
1. 工厂模式 vs 单例模式
选择理由:采用工厂模式(create_app / create_migrations_app)而非全局单例,便于在测试环境中创建隔离的应用实例,支持多租户场景下的配置切换。
已知限制:需要显式传递应用实例到扩展初始化函数,增加了代码耦合度。
2. Gevent 协程 vs 线程
选择理由:使用 Gevent 协程处理高并发 I/O 密集型请求,相比线程模型减少内存占用和上下文切换开销。
已知限制:需要 monkey patching 标准库,可能与某些不兼容的第三方库冲突;数据库连接池需要特殊处理。
证据点: api/extensions/ext_database.py:16-62
3. Celery 任务基类设计
选择理由:自定义 FlaskTask 基类在任务执行时自动推送应用上下文,确保任务内部可以访问 Flask 扩展(如数据库、缓存)。
替代方案:在每个任务函数内部手动推送上下文,但会导致代码重复。
证据点: api/extensions/ext_celery.py:13-209
4. Redis 客户端封装
选择理由:通过 RedisClientWrapper 延迟初始化客户端,支持 Sentinel 模式下的动态主节点切换。
已知限制:封装层增加了调用链深度,可能影响性能敏感场景。
证据点: api/extensions/ext_redis.py:29-158
5. OpenTelemetry 采样策略
选择理由:使用 ParentBasedTraceIdRatio 采样器,确保跨服务的追踪链完整性(子 span 跟随父 span 采样决策)。
替代方案:固定采样率可能导致高流量场景下丢失关键追踪,或低流量场景下采样过多。
证据点: api/extensions/ext_otel.py:14-136
6. 前端 Provider 嵌套顺序
选择理由:JotaiProvider 作为最外层确保状态管理可用;ThemeProvider 在内部避免主题切换触发状态重置;SentryInitializer 包裹业务组件以捕获所有异常。
已知限制:深层嵌套增加组件树复杂度,调试时需要追踪多层上下文。
技术选型表格
| 技术 | 用途 | 选型理由 | 替代方案 |
|---|---|---|---|
| Flask | 后端 Web 框架 | 轻量级、扩展性强、生态成熟 | Django(过于重量级)、FastAPI(异步支持但生态较新) |
| Next.js | 前端框架 | 支持 SSR/SSG、App Router 架构、TypeScript 原生支持 | Create React App(无 SSR)、Remix(生态较小) |
| Celery | 异步任务队列 | 功能完善、支持定时任务、与 Flask 集成良好 | RQ(功能较少)、Dramatiq(社区较小) |
| Redis | 缓存/消息队列 | 高性能、支持多种数据结构、Sentinel 高可用 | Memcached(无持久化)、KeyDB(生态较小) |
| PostgreSQL | 关系型数据库 | 功能强大、支持 JSONB、扩展性好 | MySQL(JSON 支持较弱)、SQLite(不适合生产) |
| OpenTelemetry | 可观测性 | 标准化协议、支持多语言、厂商中立 | Jaeger(仅追踪)、Prometheus(仅指标) |
| Jotai | 状态管理 | 原子化设计、TypeScript 友好、性能优秀 | Redux(样板代码多)、Zustand(缺少原子化) |
| TanStack Query | 服务端状态 | 自动缓存、后台更新、请求去重 | SWR(功能较少)、RTK Query(绑定 Redux) |
模块依赖关系图
正在加载图表渲染器...
依赖关系说明:
- 后端核心依赖:
app.py作为入口依赖所有扩展模块,扩展模块各自依赖对应的第三方库 - 前端依赖链:
layout.tsx依赖配置和工具模块,但不直接依赖后端模块 - 跨层通信:前后端通过 HTTP API 通信,无直接代码依赖
关键配置与启动流程
环境变量配置清单
| 变量名 | 用途 | 默认值 | 必需 |
|---|---|---|---|
CELERY_BROKER_URL | Celery Broker 地址 | - | 是 |
CELERY_BACKEND | Celery 结果后端 | redis | 否 |
REDIS_USE_SSL | Redis SSL 开关 | False | 否 |
BROKER_USE_SSL | Celery SSL 开关 | False | 否 |
OTEL_EXPORTER_TYPE | OpenTelemetry 导出器类型 | console | 否 |
OTLP_BASE_ENDPOINT | OTLP 端点地址 | - | 否 |
MAIL_TYPE | 邮件服务类型 | - | 否 |
NEXT_PUBLIC_API_PREFIX | 前端 API 端点 | http://localhost:5001/console/api | 否 |
启动流程时序
-
后端启动:
- 解析命令行参数,判断运行模式(迁移/生产)
- 调用应用工厂创建 Flask 实例
- 初始化扩展(数据库 → Redis → Celery → OpenTelemetry)
- 注册路由蓝图
- 启动 HTTP 服务器(Gunicorn)
-
前端启动:
- 加载环境变量到
env对象 - 解析根布局,初始化 Provider 树
- 服务端渲染页面 HTML
- 客户端水合,绑定事件处理
- 加载环境变量到
-
Celery Worker 启动:
- 加载 Flask 应用上下文
- 连接 Broker(Redis)
- 注册任务模块
- 启动任务消费者
关键证据点:
- 后端启动入口:api/app.py:6-37
- 前端布局初始化:web/app/layout.tsx:5-97
- Celery 配置:api/extensions/ext_celery.py:13-209
