Django 多租户架构:Schema 模式与 Shared 模式实现
各位同学,大家好!今天我们来深入探讨一下 Django 多租户架构的两种主要实现模式:Schema 模式和 Shared 模式。多租户架构允许单个应用程序实例为多个独立的用户(租户)提供服务,每个租户拥有自己的数据和配置,但共享底层的基础设施。选择哪种模式取决于应用程序的需求、性能考虑、数据隔离级别以及维护成本。
多租户架构概述
在深入讨论 Schema 模式和 Shared 模式之前,我们先简要回顾一下多租户架构的核心概念。
租户 (Tenant): 指应用程序的一个独立用户或客户。每个租户应该感觉自己在使用一个独立的应用程序实例。
数据隔离: 多租户架构的关键在于确保租户之间的数据隔离,防止未经授权的数据访问和篡改。
资源共享: 多个租户共享底层的计算、存储和网络资源,从而降低成本并提高资源利用率。
Schema 模式 (Database Per Tenant)
Schema 模式为每个租户创建一个独立的数据库 schema(或数据库),这是最彻底的数据隔离方法。每个租户的数据存储在自己的 schema 中,与其他租户的数据完全分离。
优点:
- 最高级别的数据隔离: 租户数据完全隔离,消除了数据泄露的风险。
- 数据安全: 即使应用程序存在漏洞,一个租户的数据也不会影响其他租户。
- 易于备份和恢复: 每个租户的数据可以独立备份和恢复。
- 易于迁移: 租户可以轻松地迁移到独立的服务器或数据库实例。
- 自定义 schema: 每个租户的 schema 可以根据其特定需求进行自定义。
缺点:
- 更高的资源消耗: 每个租户都需要独立的数据库 schema,增加了数据库服务器的资源消耗。
- 更复杂的管理: 需要管理多个数据库 schema,增加了管理复杂性。
- 更复杂的数据共享: 租户之间共享数据比较困难。
- 初始化开销大: 创建新租户的开销比较大,因为需要创建新的 schema。
实现:
在 Django 中实现 Schema 模式,通常使用 django-tenants
库。该库提供了一套中间件、模型和管理命令,简化了 Schema 模式的实现。
1. 安装 django-tenants:
pip install django-tenants
2. 配置 settings.py:
# settings.py
INSTALLED_APPS = [
'django_tenants', # 必须放在第一位
'django.contrib.contenttypes',
# ... 其他应用程序
'customers', # 存放租户模型的应用
'your_app', # 你的主应用
]
MIDDLEWARE = [
'django_tenants.middleware.main.TenantMainMiddleware',
# ... 其他中间件
]
SHARED_APPS = (
'django.contrib.contenttypes',
'django.contrib.auth',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'customers', # 租户应用
)
TENANT_APPS = (
'your_app',
# ... 其他租户特定的应用程序
)
DATABASE_ROUTERS = (
'django_tenants.routers.TenantSyncRouter',
)
TENANT_MODEL = "customers.Client" # 租户模型
PUBLIC_SCHEMA_NAME = 'public' # 公共schema的名字
DATABASE_URL = "postgres://user:password@host:port/database" # 设置数据库连接字符串
DATABASES = {
'default': dj_database_url.parse(DATABASE_URL),
}
3. 创建租户模型 (customers/models.py):
from django.db import models
from django_tenants.models import TenantMixin, DomainMixin
class Client(TenantMixin):
name = models.CharField(max_length=100)
created_on = models.DateField(auto_now_add=True)
auto_create_schema = True # 自动创建 schema
def __str__(self):
return self.name
class Domain(DomainMixin):
pass
4. 创建租户应用:
创建一个名为 customers
的 Django 应用,用于存放租户模型。
5. 创建主应用:
创建一个名为 your_app
的 Django 应用,用于存放租户特定的模型和视图。
6. 迁移数据库:
python manage.py migrate_schemas --shared
这个命令会创建公共 schema (public
) 和所有共享应用程序的表。
7. 创建租户:
from customers.models import Client, Domain
# 创建租户
tenant = Client(name='My Tenant')
tenant.save()
# 创建域名
domain = Domain(domain='mytenant.example.com', tenant=tenant)
domain.save()
示例代码:模型 (your_app/models.py):
from django.db import models
from django_tenants.utils import tenant_context
class Product(models.Model):
name = models.CharField(max_length=100)
price = models.DecimalField(max_digits=10, decimal_places=2)
def __str__(self):
return self.name
def save(self, *args, **kwargs):
current_tenant = Client.objects.get(schema_name=connection.schema_name)
with tenant_context(current_tenant):
super().save(*args, **kwargs) # 确保在正确的schema中保存
示例代码:视图 (your_app/views.py):
from django.shortcuts import render
from .models import Product
from django_tenants.utils import tenant_context
from customers.models import Client
from django.db import connection
def product_list(request):
current_tenant = Client.objects.get(schema_name=connection.schema_name)
with tenant_context(current_tenant):
products = Product.objects.all()
return render(request, 'product_list.html', {'products': products})
注意事项:
- 确保
django_tenants
应用在INSTALLED_APPS
中位于最前面。 - 使用
TenantSyncRouter
确保共享应用程序的表只在公共 schema 中创建。 - 使用
tenant_context
上下文管理器在租户特定的 schema 中执行数据库操作。
Shared 模式 (Shared Database, Shared Schema)
Shared 模式使用单个数据库和 schema 来存储所有租户的数据。通过在每个表中添加一个 tenant_id
列来区分不同租户的数据。
优点:
- 更低的资源消耗: 所有租户共享相同的数据库和 schema,降低了数据库服务器的资源消耗。
- 更简单的管理: 只需要管理一个数据库和 schema,降低了管理复杂性。
- 更方便的数据共享: 租户之间共享数据比较容易。
- 初始化开销小: 创建新租户的开销比较小,不需要创建新的 schema。
缺点:
- 较低级别的数据隔离: 租户数据存储在同一个表中,存在数据泄露的风险。
- 数据安全风险: 如果应用程序存在漏洞,一个租户的数据可能会影响其他租户。
- 备份和恢复复杂: 需要备份和恢复整个数据库,无法独立备份和恢复单个租户的数据。
- 迁移复杂: 租户迁移到独立的服务器或数据库实例比较困难。
- 难以自定义 schema: 所有租户必须使用相同的 schema。
- 查询性能影响: 由于所有租户的数据都存储在同一个表中,查询性能可能会受到影响。
实现:
在 Django 中实现 Shared 模式,需要手动添加 tenant_id
列到每个模型,并使用中间件或装饰器来过滤查询结果,确保每个租户只能访问自己的数据。
1. 添加 tenant_id
列到模型:
from django.db import models
from django.db.models import Q
class TenantAwareModel(models.Model):
tenant_id = models.IntegerField()
class Meta:
abstract = True
def save(self, *args, **kwargs):
# 在保存前设置 tenant_id
if not self.tenant_id:
self.tenant_id = get_current_tenant_id() # 需要实现 get_current_tenant_id()
super().save(*args, **kwargs)
@classmethod
def get_tenant_aware_queryset(cls, tenant_id):
return cls.objects.filter(tenant_id=tenant_id)
class Product(TenantAwareModel):
name = models.CharField(max_length=100)
price = models.DecimalField(max_digits=10, decimal_places=2)
def __str__(self):
return self.name
2. 创建中间件或装饰器来过滤查询结果:
from django.utils.deprecation import MiddlewareMixin
from threading import local
_thread_locals = local()
def get_current_tenant_id():
return getattr(_thread_locals, 'tenant_id', None)
class TenantMiddleware(MiddlewareMixin):
def process_request(self, request):
# 从请求中获取租户 ID (例如,从 URL、header 或 session 中)
tenant_id = self.get_tenant_id(request)
_thread_locals.tenant_id = tenant_id
def get_tenant_id(self, request):
# TODO: 实现从请求中获取租户 ID 的逻辑
# 例如:从 URL 中获取
# tenant_id = request.resolver_match.kwargs.get('tenant_id')
# 或者从 header 中获取
# tenant_id = request.META.get('HTTP_X_TENANT_ID')
# 这里只是一个示例,需要根据你的应用具体情况实现
return 1 # 默认返回1,需要替换为实际的逻辑
3. 配置 settings.py:
MIDDLEWARE = [
'your_app.middleware.TenantMiddleware',
# ... 其他中间件
]
4. 修改视图,使用 TenantAwareModel
:
from django.shortcuts import render
from .models import Product, get_current_tenant_id
def product_list(request):
tenant_id = get_current_tenant_id()
products = Product.get_tenant_aware_queryset(tenant_id)
return render(request, 'product_list.html', {'products': products})
注意事项:
get_current_tenant_id()
函数必须根据你的应用程序的实际情况实现,从请求中获取租户 ID。- 使用
TenantAwareModel
作为所有租户特定模型的基类,确保每个模型都包含tenant_id
列。 - 使用中间件或装饰器来过滤查询结果,确保每个租户只能访问自己的数据。
模式对比
为了更清晰地了解两种模式的差异,我们可以将它们总结在一个表格中:
特性 | Schema 模式 (Database Per Tenant) | Shared 模式 (Shared Database, Shared Schema) |
---|---|---|
数据隔离 | 最高 | 较低 |
数据安全 | 最高 | 较低 |
资源消耗 | 较高 | 较低 |
管理复杂性 | 较高 | 较低 |
数据共享 | 复杂 | 简单 |
备份和恢复 | 简单 | 复杂 |
迁移 | 简单 | 复杂 |
自定义 schema | 支持 | 不支持 |
查询性能 | 较好 | 可能较差 |
初始化开销 | 大 | 小 |
选择合适的模式
选择哪种模式取决于应用程序的需求。
- Schema 模式 适用于对数据隔离和安全性要求非常高的应用程序,例如金融、医疗等行业。
- Shared 模式 适用于对数据隔离要求不高,但对资源消耗和管理复杂性有较高要求的应用程序,例如 SaaS 应用、博客平台等。
在实际应用中,也可以将两种模式结合使用。例如,可以使用 Schema 模式来存储敏感数据,使用 Shared 模式来存储非敏感数据。
其它考虑事项
- 数据库选择: Schema 模式更适合支持 schema 的数据库,例如 PostgreSQL。Shared 模式则对数据库的类型没有特别的限制。
- 扩展性: Schema 模式的扩展性更好,可以轻松地将租户迁移到独立的服务器或数据库实例。
- 成本: Shared 模式的初始成本较低,但随着租户数量的增加,可能需要升级数据库服务器。Schema 模式的初始成本较高,但可以随着租户数量的增加,逐步增加数据库服务器的数量。
总结
今天我们深入探讨了 Django 多租户架构的两种主要实现模式:Schema 模式和 Shared 模式,它们在数据隔离、资源消耗和管理复杂性等方面各有优缺点。希望通过这次讲座,大家能够更好地理解这两种模式,并根据自己的应用程序的需求选择合适的模式。