django全自动分库分表
期待大神指点方法的不足之处或者其他更好的方法
由于当前项目目前只需要实现写,所以这里也没有考虑读写分离。如果要考虑读写分离,则在路由处进行设置即可。
分库原理
django在初始化的时候,从自己的数据库管理服务器获取当前的所有数据库,以及数据库的入库规则,在获取数据的时候,根据数据字段值动态生成Model,并动态保存到指定的数据库中。
动态生成的Model不仅能够实现分库,还能实现动态横向分表,牛逼吧。
一句话:通过动态路由分库,通过动态model分表
1 从数据库管理服务器获取设置
settings.py
#尽量放在靠前
GROUP_ID_TO_DATABASE={}#记录对应的动态model需要保存到哪个数据库
# # Application definition
def get_db_setting():
"""
从数据库管理服务器获取数据库设置和数据标对应的数据库
db_setting_url:连接数据库管理服务器的地址
django_info:当前配置的服务器的信息,必须为一个不重复的固定值
:return:
"""
import requests
django_info={
'django_device_id':'',#该服务器的设备id,建议从配置文件读取,且各个django服务器之间不要重复
}
r=requests.post(url=db_setting_url,data=django_info)
return r.json()
DATABASES,GROUP_ID_TO_DATABASE=get_db_setting()
2 创建数据库表
该model在collect这个app里面
#对应model.py里面的model
#MyModel是我自定义的一个抽象基类model,你可以直接使用models.Model
class BaseGetFaceRecord(MyModel):
"""
入库记录
基础的入库记录,该model为abstract=True的model,在之后会进行继承并创建真正需要的数据库
"""
collected_face = models.ForeignKey(BaseCollectFace, on_delete=models.CASCADE, verbose_name="对应人脸信息")
group_id = models.CharField(max_length=128, verbose_name="人脸库")
camera_id = models.CharField(max_length=128, verbose_name="抓拍相机ID")
arrived_time = models.DateTimeField(max_length=10, verbose_name="抓拍时间戳")
person_id = models.CharField(max_length=128, verbose_name="人脸ID(faceToken)")
first_arrive_time = models.DateTimeField(max_length=10, verbose_name="首次到访时间戳")
class Meta:
abstract = True
verbose_name = "人脸照片库"
verbose_name_plural = verbose_name
class BaseGetFaceRecord(MyModel):
"""
入库记录
基础的入库记录,该model为abstract=True的model,在之后会进行继承并创建真正需要的数据库
"""
collected_face = models.ForeignKey(BaseCollectFace, on_delete=models.CASCADE, verbose_name="对应人脸信息")
group_id = models.CharField(max_length=128, verbose_name="人脸库")
camera_id = models.CharField(max_length=128, verbose_name="抓拍相机ID")
arrived_time = models.DateTimeField(max_length=10, verbose_name="抓拍时间戳")
person_id = models.CharField(max_length=128, verbose_name="人脸ID(faceToken)")
first_arrive_time = models.DateTimeField(max_length=10, verbose_name="首次到访时间戳")
class Meta:
abstract = True
verbose_name = "人脸照片库"
verbose_name_plural = verbose_name
class CreateGetFaceRecordModel(object):
"""
动态创建动态创建人脸库的类
主要用来提供静态方法
也可以不写成类
"""
@staticmethod
def __get_GetFaceRecord_model(group_id: str):
"""
创建FaceRecord模型
:param group_id:人脸库名称
:return: FaceRecord
"""
#要连接的数据表,
table_name = 'getfacerecord_%s' % str(group_id)
#这里会重新设置创建的model的名字,每个model名字都是动态的,不是GetFaceRecord
class Metaclass(models.base.ModelBase):
def __new__(cls, name, bases, attrs):
name += group_id # 这是Model的name.
return models.base.ModelBase.__new__(cls, name, bases, attrs)
# 注意继承的顺序
class GetFaceRecord(BaseAlgoFace, metaclass=Metaclass):
@staticmethod
def is_exists(table_name1):
"""
判断这个表是否已经在数据库
:param table_name1:
:return:
"""
table_name1 = 'getfacerecord_' + table_name1
return table_name1 in connection.introspection.table_names()
class Meta:
abstract = False
db_table = table_name
app_label="collect"
return GetFaceRecord
@staticmethod
def create_GetFaceRecord(group_id: str):
"""
注册GetFaceRecord模型
:param group_id: 人脸库名称
:return: GetFaceRecord
"""
try:
# cls = apps.get_model('__main__', 'ExcelData_%s' % project_name)
# 获取模型对应的model,如果有的话,第一个参数为collect这个app,第二个参数为model的名字
# 这里不用__main__是因为之后会考虑到分库,不同的app会到不同的库里
cls = apps.get_model('collect', 'GetFaceRecord_%s' % group_id)
except LookupError:
cls = CreateGetFaceRecordModel.__get_GetFaceRecord_model(group_id)
except Exception as e:
raise e
if not cls.is_exists(group_id):
# 将数据表创建
with connection.schema_editor() as schema_editor:
# 创建模型,调用的应该是migrate和makemigrations里面的方法
schema_editor.create_model(cls)
return cls
else:
return cls
def get_GetFaceRecord(group_id: str):
"""
获取注册人脸的人脸库对应model,但是请注意,如果没有会创建该表和该模型,如果有,返回该模型
:param group_id:人脸库信息
:return:
"""
return CreateGetFaceRecordModel.create_GetFaceRecord(group_id)
3 创建数据库路由器
在对应的app里面创建自己的router.py,注意这部分内容也应该从数据库管理服务器获取
"""
@version: 1.0
@author: chise
@time : 2019/07/24 10:51
"""
from MultiAlgorithm.settings import GROUP_ID_TO_DATABASE
class BaseRouter():
def db_for_read(self, model, **hints):
"""
返回对应的数据库名称
:param model:
:param hints:
:return:
"""
if model._meta.label == "base.UserGroup" or model._meta.label == "base.FaceAggregation":
return 'default'
else:
model_name = str(model.__class__.__name__)
return GROUP_ID_TO_DATABASE[model_name]
def db_for_write(self, model, **hints):
if model._meta.label == "base.UserGroup" or model._meta.label == "base.FaceAggregation":
return 'default'
else:
model_name = str(model.__class__.__name__)
return GROUP_ID_TO_DATABASE[model_name]
def allow_relation(self, obj1, obj2, **hints):
"""
是否允许通过外键访问??
如果应该允许obj1和obj2之间的关系,返回True;如果应该阻止这种关系,返回False;如果路由器没有意见,返回None。
这纯粹是一个验证操作,由外键和许多对许多操作使用,以确定是否应该允许两个对象之间存在关系。
:param obj1:
:param obj2:
:param hints:
:return:
"""
return True
def allow_migrate(self, db, app_label, model_name=None, **hints):
"""
是否允许migrate进行迁移的验证
这里不允许通过migrate的方式将数据迁移,数据库表只能在数据库管理器里面进行创建
:param db:
:param app_label:
:param model_name:
:param hints:
:return:
"""
if app_label == 'base':
return db == 'base.UserGroup' or db == 'base.UserGroup'
return None
至此,就完成了动态的数据库配置
总结
通过这个方法,再结合nginx,就可完成django配置数据库。
注意,这里只是一个demo,还有很多东西没有进行实现,比如数据库DATABASE设置的热更新,如果配置,即可实现动态添加数据库和增加服务器配置。
创建路由的介绍可以看这,挺合适:https://www.jb51.net/article/141182.htm