多租户架构
多租户架构
相关源文件
本章引用的主要源码文件:
CONTRIBUTING.mdREADME.mdREADME.zh-CN.mdbackend/alembic/README.mdbackend/alembic/versions/6a804aeb4830_duplicated_no_harm_user_file_migration.pybackend/alembic/versions/9aadf32dfeb4_add_user_files.pybackend/alembic_tenants/versions/3b45e0018bf1_add_new_available_tenant_table.pybackend/alembic_tenants/versions/ac842f85f932_new_column_user_tenant_mapping.pybackend/ee/onyx/background/celery/tasks/tenant_provisioning/tasks.pybackend/ee/onyx/db/license.pybackend/ee/onyx/server/middleware/license_enforcement.pybackend/ee/onyx/server/scim/auth.pybackend/ee/onyx/server/settings/api.pybackend/ee/onyx/server/tenants/admin_api.pybackend/ee/onyx/server/tenants/schema_management.pybackend/ee/onyx/server/tenants/user_mapping.pybackend/onyx/connectors/README.mdbackend/onyx/db/tag.pybackend/onyx/server/features/build/db/sandbox.pybackend/tests/external_dependency_unit/db/test_tag_race_condition.pybackend/tests/integration/common_utils/managers/scim_client.pybackend/tests/integration/common_utils/managers/scim_token.pybackend/tests/integration/multitenant_tests/tenants/test_tenant_provisioning_rollback.pybackend/tests/integration/tests/indexing/file_connector/test_files/.onyx_metadata.jsonbackend/tests/integration/tests/scim/test_scim_tokens.pybackend/tests/integration/tests/scim/test_scim_users.pybackend/tests/unit/ee/onyx/db/test_license.pybackend/tests/unit/ee/onyx/server/middleware/test_license_enforcement.pybackend/tests/unit/ee/onyx/server/settings/test_license_enforcement_settings.pybackend/tests/unit/ee/onyx/server/tenants/test_schema_management.pybackend/tests/unit/onyx/background/celery/tasks/tenant_provisioning/__init__.pybackend/tests/unit/onyx/background/celery/tasks/tenant_provisioning/test_check_available_tenants.pybackend/tests/unit/onyx/server/scim/test_auth.pydeployment/README.mddeployment/docker_compose/README.mdweb/src/components/errorPages/AccessRestrictedPage.tsxweb/src/components/errorPages/CloudErrorPage.tsxweb/src/components/errorPages/ErrorPage.tsxweb/src/components/errorPages/ErrorPageLayout.tsx
本文档描述了 Onyx 的多租户架构,该架构支持单个部署服务于多个隔离的组织(租户)。每个租户拥有独立的数据、用户和配置,同时共享相同的应用基础设施。
关于跨租户的认证方法,请参见 9.1 认证方法。关于租户内的用户角色和权限,请参见 9.3 用户角色和权限。
概述
多租户架构由 MULTI_TENANT 环境标志控制 shared_configs/configs.py:61。启用后,Onyx 会为每个组织提供隔离的运行环境,具体方式如下:
- PostgreSQL 模式级隔离:每个租户获得一个专用的数据库模式
ee/onyx/server/tenants/schema_management.py:17-19。 - Redis 键命名空间:通过
TenantRedis类,所有 Redis 键都带有租户 ID 前缀onyx/redis/redis_pool.py:42-146。 - 上下文变量:在请求生命周期内,使用
CURRENT_TENANT_ID_CONTEXTVAR实现线程安全的租户跟踪shared_configs/contextvars.py:64。 - 动态任务调度:自定义的 Celery Beat 调度器
DynamicTenantScheduler通过将模板分发到所有活跃的租户 ID,生成每个租户的后台任务onyx/background/celery/apps/beat.py:27-145。
来源:shared_configs/configs.py:61, onyx/redis/redis_pool.py:42-146, shared_configs/contextvars.py:64, onyx/background/celery/apps/beat.py:27-145
租户隔离架构
租户隔离在 API、后台工作线程和数据存储层中实现。
代码到系统的映射:隔离组件
下图将逻辑隔离概念映射到代码库中使用的具体类和变量。
来源:shared_configs/contextvars.py:64, onyx/background/celery/apps/app_base.py:82-101, onyx/db/engine/sql_engine.py:43, onyx/redis/redis_pool.py:42-146, onyx/redis/redis_pool.py:169-170
PostgreSQL 模式级隔离
每个租户获得一个包含所有应用表的完整 PostgreSQL 模式。模式命名格式为 tenant_{uuid},其中 UUID 唯一标识该组织 shared_configs/configs.py:63。
- 共享模式(
public):包含与租户无关的表,例如UserTenantMapping和AvailableTenantee/onyx/server/tenants/provisioning.py:39-42。 - 租户模式(
tenant_*):每个模式包含所有应用表的完整副本(用户、连接器、文档等)。
模式管理函数:
| 函数 | 用途 | 位置 |
|---|---|---|
create_schema_if_not_exists() | 在 Postgres 中创建新的租户模式 | ee/onyx/server/tenants/schema_management.py:17 |
run_alembic_migrations() | 将 schema_private 迁移应用到特定模式 | ee/onyx/server/tenants/schema_management.py:19 |
get_session_with_tenant() | 返回限定到特定租户 ID 的数据库会话 | onyx/db/engine/sql_engine.py:33 |
get_session_with_current_tenant() | 使用 CURRENT_TENANT_ID_CONTEXTVAR 限定会话范围 | onyx/db/engine/sql_engine.py:43 |
来源:ee/onyx/server/tenants/schema_management.py:17-19, shared_configs/configs.py:63, onyx/db/engine/sql_engine.py:33-43
Redis 命名空间隔离
TenantRedis 类确保共享同一 Redis 实例的租户之间不会发生键冲突。它会拦截标准的 Redis 命令,并在键前添加租户 ID 前缀 onyx/redis/redis_pool.py:42-45。
实现细节:
- 键前缀:
_prefixed方法将键转换为tenant_id:original_key格式onyx/redis/redis_pool.py:47-68。 - 方法包装:常见的 Redis 方法(例如
get、set、lock、delete)会自动被包装以应用此前缀onyx/redis/redis_pool.py:105-146。 - 扫描过滤:
scan_iter和sscan_iter被重写,以按租户前缀过滤,并从返回的键中去除前缀,确保租户只能看到自己的数据onyx/redis/redis_pool.py:81-103。
来源:onyx/redis/redis_pool.py:42-146
租户预配逻辑
预配处理创建新组织环境的整个生命周期,从模式创建到初始数据填充。
预配流程:代码实体
下图将预配逻辑映射到 provisioning.py 中的具体函数和后台任务。
来源:ee/onyx/server/tenants/provisioning.py:71-133, ee/onyx/server/tenants/provisioning.py:145-164, ee/onyx/background/celery/tasks/tenant_provisioning/tasks.py:48-115
get_or_provision_tenant 逻辑
get_or_provision_tenant 函数 ee/onyx/server/tenants/provisioning.py:71-133 遵循以下优先级:
- 查找:检查用户邮箱是否已映射到
UserTenantMapping中的某个租户ee/onyx/server/tenants/provisioning.py:91。 - 预预配池:尝试从
AvailableTenant表中获取一个租户ee/onyx/server/tenants/provisioning.py:99。 - 按需创建:如果池为空,则调用
create_tenant()从头构建一个新的环境ee/onyx/server/tenants/provisioning.py:127。
AvailableTenant 预预配
为了减少用户注册时的延迟,Onyx 维护了一个"可用"租户池。这些模式已经创建并迁移完成,但尚未分配给任何用户 ee/onyx/server/tenants/provisioning.py:39。
- 后台维护:
check_available_tenantsCelery 任务会定期运行,以确保可用租户数量达到TARGET_AVAILABLE_TENANTS的要求ee/onyx/background/celery/tasks/tenant_provisioning/tasks.py:79-82。 - 陈旧迁移:
_migrate_stale_pool_tenants函数确保池中的租户始终处于最新的 Alembic "head" 状态,防止用户最终注册时出现模式不匹配ee/onyx/background/celery/tasks/tenant_provisioning/tasks.py:127-160。 - 获取:当获取一个预预配的租户时,会立即执行
run_alembic_migrations,以确保在分配之前模式与当前应用版本保持一致ee/onyx/server/tenants/provisioning.py:102-110。
来源:ee/onyx/server/tenants/provisioning.py:98-110, ee/onyx/server/tenants/provisioning.py:39, ee/onyx/background/celery/tasks/tenant_provisioning/tasks.py:39-160
上下文管理
CURRENT_TENANT_ID_CONTEXTVAR
系统使用 contextvars 在异步任务和应用的不同层之间维护租户状态,而无需在每个函数签名中传递 tenant_id shared_configs/contextvars.py:64。
- API 中间件:从请求中提取租户 ID 并设置上下文变量
ee/onyx/server/middleware/tenant_tracking.py:24-26。 - Celery 工作线程:
TenantAwareTask拦截任务执行,从任务参数中提取tenant_id,并在任务逻辑运行之前设置上下文变量onyx/background/celery/apps/app_base.py:82-101。 - 数据库会话:
get_session_with_current_tenant从上下文变量中检索 ID,以配置 SQLAlchemy 会话onyx/db/engine/sql_engine.py:43。
来源:shared_configs/contextvars.py:64, onyx/background/celery/apps/app_base.py:82-101, onyx/db/engine/sql_engine.py:43
租户模式迁移
在多租户环境中,迁移采用两层方法:
- 公共迁移:针对
public模式的标准 Alembic 迁移。 - 私有迁移:使用
schema_private名称应用于每个租户模式的迁移ee/onyx/server/tenants/schema_management.py:19。
函数 run_alembic_migrations(tenant_id) ee/onyx/server/tenants/schema_management.py:19 以编程方式调用 Alembic,将特定租户模式升级到最新的 "head" 版本。
CLI 迁移管理
管理员可以通过 CLI 同时运行多个租户的迁移:
- 全部升级:
alembic -x upgrade_all_tenants=true upgrade headbackend/alembic/README.md:34。 - 范围过滤:使用
tenant_range_start和tenant_range_end按字母顺序批量迁移backend/alembic/README.md:49。
来源:ee/onyx/server/tenants/schema_management.py:19, ee/onyx/server/tenants/provisioning.py:102-109, backend/alembic/README.md:30-68