Django-Multitenant,分散式多租戶資料庫專案實戰(Python/Django+Postgres+Citus)

為少發表於2022-03-19

Python/Django 支援分散式多租戶資料庫,如 Postgres+Citus

通過將租戶上下文新增到您的查詢來實現輕鬆橫向擴充套件,使資料庫(例如 Citus)能夠有效地將查詢路由到正確的資料庫節點。

構建多租戶資料庫的架構包括:為每個租戶建立一個資料庫、為每個租戶建立一個 schema 和讓所有租戶共享同一個表。這個庫基於第三種設計,即讓所有租戶共享同一個表,它假設所有租戶相關的模型/表都有一個 tenant_id 列來表示租戶。

以下連結更多地討論了何時以及如何為您的多租戶資料庫選擇正確架構的權衡:

關於多租戶的其他有用連結:

  1. https://www.citusdata.com/blog/2017/03/09/multi-tenant-sharding-tutorial/
  2. https://www.citusdata.com/blog/2017/06/02/scaling-complex-sql-transactions/

專案原始碼

https://github.com/citusdata/django-multitenant

安裝

pip install --no-cache-dir django_multitenant

支援的 Django 版本/前提條件。

Python Django
3.X 2.2
3.X 3.2
3.X 4.0

用法

為了使用這個庫,您可以使用 Mixins 或讓您的模型從我們的自定義模型類繼承。

模型變化

  1. 在要使用庫的任何檔案中匯入它:
    from django_multitenant.fields import *
    from django_multitenant.models import *
    
  2. 所有模型都應繼承 TenantModel 類。Ex: class Product(TenantModel):
  3. 定義一個名為 tenant_id 的靜態變數,並使用該變數指定租戶列。Ex: tenant_id='store_id'
  4. TenantModel 子類的所有外來鍵都應使用 TenantForeignKey 代替 models.ForeignKey
  5. 實現上述 2 個步驟的示例模型:
      class Store(TenantModel):
        tenant_id = 'id'
        name =  models.CharField(max_length=50)
        address = models.CharField(max_length=255)
        email = models.CharField(max_length=50)
    
      class Product(TenantModel):
        store = models.ForeignKey(Store)
        tenant_id='store_id'
        name = models.CharField(max_length=255)
        description = models.TextField()
        class Meta(object):
          unique_together = ["id", "store"]
      class Purchase(TenantModel):
        store = models.ForeignKey(Store)
        tenant_id='store_id'
        product_purchased = TenantForeignKey(Product)
    

使用 mixins 更改模型

  1. 在您要使用庫的任何檔案中,只需:
    from django_multitenant.mixins import *
    
  2. 所有模型都應使用 TenantModelMixin 和 django models.Model 或您的客戶模型類 Ex: class Product(TenantModelMixin, models.Model):
  3. 定義一個名為 tenant_id 的靜態變數,並使用該變數指定租戶列。Ex: tenant_id='store_id'
  4. TenantModel 子類的所有外來鍵都應使用 TenantForeignKey 代替 models.ForeignKey
  5. 實現上述 2 個步驟的示例模型:
      class ProductManager(TenantManagerMixin, models.Manager):
        pass
    
      class Product(TenantModelMixin, models.Model):
        store = models.ForeignKey(Store)
        tenant_id='store_id'
        name = models.CharField(max_length=255)
        description = models.TextField()
    
        objects = ProductManager()
    
        class Meta(object):
          unique_together = ["id", "store"]
    
      class PurchaseManager(TenantManagerMixin, models.Manager):
        pass
    
      class Purchase(TenantModelMixin, models.Model):
        store = models.ForeignKey(Store)
        tenant_id='store_id'
        product_purchased = TenantForeignKey(Product)
    
        objects = PurchaseManager()
    

db 層自動化複合外來鍵:

  1. 使用 TenantForeignKey 在租戶相關模型之間建立外來鍵將自動將 tenant_id 新增到引用查詢(例如 product.purchases)和連線查詢(例如 product__name)。如果要確保在 db 層建立複合外來鍵(帶有 tenant_id),則應將 settings.py 中的資料庫 ENGINE 更改為 django_multitenant.backends.postgresql
      'default': {
          'ENGINE': 'django_multitenant.backends.postgresql',
          ......
          ......
          ......
        }
    

在哪裡設定租戶?

  1. 使用中介軟體編寫身份驗證邏輯,該中介軟體還為每個 session/request 設定/取消設定租戶。 這樣,開發人員不必擔心基於每個檢視設定租戶。只需在身份驗證時設定它,庫將確保其餘部分(將 tenant_id 過濾器新增到查詢中)。上面的示例實現如下:

        from django_multitenant.utils import set_current_tenant
    
        class MultitenantMiddleware:
            def __init__(self, get_response):
                self.get_response = get_response
    
            def __call__(self, request):
                if request.user and not request.user.is_anonymous:
                    set_current_tenant(request.user.employee.company)
                return self.get_response(request)
    

    在您的設定中,您需要更新 MIDDLEWARE 設定以包含您建立的設定。

       MIDDLEWARE = [
       # ...
       # existing items
       # ...
       'appname.middleware.MultitenantMiddleware'
    ]
    
  2. 在您希望基於租戶範圍的所有檢視中使用 set_current_tenant(t) api 設定租戶。 這將自動(不指定顯式過濾器)將所有 django API 呼叫範圍限定為單個租戶。如果未設定 current_tenant,則使用沒有租戶範圍的 預設/原生 API。

支援的 API

  1. Model.objects.* 下的大部分 API
  2. Model.save() 為租戶繼承的模型注入 tenant_id
 s=Store.objects.all()[0]
set_current_tenant(s)

#All the below API calls would add suitable tenant filters.
#Simple get_queryset()
Product.objects.get_queryset()

#Simple join
Purchase.objects.filter(id=1).filter(store__name='The Awesome Store').filter(product__description='All products are awesome')

#Update
Purchase.objects.filter(id=1).update(id=1)

#Save
p=Product(8,1,'Awesome Shoe','These shoes are awesome')
p.save()

#Simple aggregates
Product.objects.count()
Product.objects.filter(store__name='The Awesome Store').count()

#Subqueries
Product.objects.filter(name='Awesome Shoe');
Purchase.objects.filter(product__in=p);

更多

相關文章