一种Django多租户解决方案

什么是多租户?

多租户技术或称多重租赁技术,简称SaaS,是一种软件架构技术,是实现如何在多用户环境下(此处的多用户一般是面向企业用户)共用相同的系统或程序组件,并且可确保各用户间数据的隔离性。

多租户数据隔离方案介绍

多租户数据隔离方案通常有三种:DataBase级别隔离、Schema级隔离和Table级隔离

DataBase级别隔离

即一个租户一个数据库,这种方案的用户数据隔离级别最高,安全性最好,但成本较高

Schema级隔离

多个或所有租户共享Database,但是每个租户一个Schema

Table级隔离

即租户共享同一个Database、同一个Schema,但在表中增加TenantID多租户的数据字段。这是共享程度最高、隔离级别最低的模式。

多租户数据隔离方案对比 实现方案 数据隔离程度 安全性
DataBase级别隔离      
Schema级隔离      
Table级隔离      
Django多租户方案的实现

django 是Python语言中非常流行的Web框架,但是Django本身没有提供一种多租户的实现方案,也没有一个比较成熟的Django扩展包来实现多租户,这里通过分析并改造Django源码来实现多租户。

这里以我自己的实现过程为例分享一下大概思路,对于实现思路不喜勿喷,欢迎issue

源码:

https://gitee.com/Ranger313/django-multi-tenant

https://github.com/AnsGoo/djangoMultiTenant

Django多租户实现方案的核心

通过djangoDATABASE_ROUTERS来实现不同租户访问不同的DataBase或者  Schame

通过Python动态语言的特性在运行时修改Django部分源码,让Django支持相应的逻辑

Django多租户模块划分

在多租户模型中我们将数据分为两部分:公共数据和租户数据

公共数据,指和租户无关的数据,通常这里指租户信息和全局用户信息

租户数据,指的是和属于某个租户的数据,数据与数据之间相关隔离

根据数据权限可知:

每个租户只能访问自己的数据

用户只有完成认证之后才能访问租户数据

用户最多只能属于某一个租户

超级管理员默认不能属于任何一个租户

这里我们将按照以上原则,将DjangoApp分为公共App和租户APP,

公共App相关models

# 租户表,租户相关信息和对应的数据库信息 class Tenant(models.Model): name: str = models.CharField(max_length=20, unique=True) label: str = models.CharField(max_length=200) code: str = models.CharField(max_length=10, unique=True) db_options: str = models.JSONField(null=True, blank=True) is_active: bool = models.BooleanField(default=True) # 全局用户表,全局用户 class GloabalUser(models.Model): username = models.CharField(max_length=50, unique=True) password = models.CharField(max_length=128) is_super = models.BooleanField(default=False) tenant = models.ForeignKey(Tenant,to_field=\'code\',on_delete=models.CASCADE, null=True, blank=True) 多租户架构中的租户识别流程

多租户架构租户识别流程

全局变量 ## 线程全局变量保存当前租户信息和其数据库连接名 from threading import local _thread_local = local() def get_current_db(): return getattr(_thread_local, \'db_name\', \'default\') def set_current_db(db_name): setattr(_thread_local, \'db_name\', db_name) 检查用户是否属于某个租户

采用替换django.contrib.auth.middleware.AuthenticationMiddleware认证中间件,让django在认证过程中判断当前用户是否属于全局用户,是否属于某个租户,并在请求的线程变量中缓存租户信息

class MultTenantAuthenticationMiddleware(AuthenticationMiddleware): def process_request(self, request:HttpRequest): super().process_request(request) if hasattr(request,\'user\'): user = request.user if not user.is_anonymous and user.tenant: code = user.tenant.code set_current_db(code) 根据数据库路由切换连接的数据库

通过配置公共app和租户app的方式,一旦用户访问是租户app里面的数据,则连接租户数据库

## 数据库映射,这里只需要定义共用的app,默认其他app为租户app DATABASE_APPS_MAPPING = { \'tenant\': \'default\', \'admin\': \'default\', \'sessions\': \'default\' } ... ## DATABASE_ROUTER class MultTenantDBRouter: def db_for_read(self, model:Model, **hints) -> str: if model._meta.app_label in settings.DATABASE_APPS_MAPPING: ## 如果访问的是公共app信息,返回默认数据连接信息 return settings.DATABASE_APPS_MAPPING[model._meta.app_label] ## 否则返回租户数据连接信息 return get_current_db() def db_for_write(self, model:Model, **hints): if model._meta.app_label in settings.DATABASE_APPS_MAPPING: return settings.DATABASE_APPS_MAPPING[model._meta.app_label] return get_current_db() def allow_migrate(self, db:str, app_label:str, **hints) -> bool: if app_label == \'contenttypes\': return True app_db = settings.DATABASE_APPS_MAPPING.get(app_label) if app_db == \'default\' and db == \'default\': return True elif app_db != \'default\' and db != \'default\': return True else: return False

至此就完成了一个最简单的django多租户解决方案。

Django多租户方案的优化

但是作为一个多租户方案上面的解决方案实在是太简单了,存在很多问题。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zgywjf.html