Django 实现分库
网站后端的数据库随着业务的不断扩大,用户的累积,数据库的压力会逐渐增大。一种办法是优化使用方法,也就是的优化 SQL 语句啦,添加缓存以达到减少存取的目的;另外一种办法是修改使用架构,在数据库层面上「分库分表」。
以前做手游服务器的时候,数据库用的是 NxM 的结构,即 N 个数据库,M 个表。通过用户 ID 哈希把不同的用户分布到不同的表中,以达到「均衡」的目的。分库分表是很常见的解决数据库压力的方法,适用于很多业务场景,比如社交类app,用户表、用户评论这种只会不断累加但不会删除。
我遇到一个刚需的例子是:日志统计平台,当然少不了日志存储。日志的特性是相互之间没有任何关联(业务简单),一直会增量上报(量大),单表存储,很快就会有查询性能问题。这是可能最合适的分库分表的业务场景了。
ORM 几乎是数据库切分的「天敌」(本质上他们有这不同的设计策略)。而我用的是 Django,Django 的 ORM 基本上就可以「分表」说再见了,一个模型对应一个表,如果要 10 个表,就要写 10 个模型,使用上麻烦,而且不容易扩展和维护。Django 提供了同时使用多数据库的方法,通过配置路由规则来选择使用的数据库,看起来是的「垂直分库」变的可行,这篇文章将介绍在日志统计平台中如何实现日志存储的分库。
BTW,在此之前我的日志系统是我自己脱离 Django 直接封装了一层 MySQL 的使用接口,实现 10*10 的日志存储库表结构,用了一段时间也没出现问题。缺陷就是增加新功能的时候太过繁琐,为不同业务的查询封装了多个接口,最蛋疼的时候没有 django
migrate
这样的工具,增加、删除字段会变的很复杂。
日志统计平台有自身的业务,以及其它要存储的数据用 default
来存储,日志自身的存储将被分成 10 个库,然后按照服务器 ID 哈希到这 10 个库中。库的别名为 sharding0
, sharding1
, ..., sharding9
。
新建项目
首先创建一个 logstat
的项目,然后一个创建 report
的 app。
>>> django-admin startproject logstat
>>> cd logstat
>>> django-startapp report
数据库配置
配置 setting
中配置 default
DATABASES:
DATABASES = {'default': {'ENGINE': 'django.db.backends.mysql','NAME': 'logstat','USER': 'logstat_user','PASSWORD': 'logstat_password','HOST': 'localhost','PORT': '3306','CHARSET': 'utf8',},
}
接下来配置分库日志的数据库,为了 demo 写起来方便,改成 2 个库:
DATABASES = {'default': {'ENGINE': 'django.db.backends.mysql','NAME': 'logstat','USER': 'logstat_user','PASSWORD': 'logstat_password','HOST': 'localhost','PORT': '3306','CHARSET': 'utf8',},'logsharding0': {'ENGINE': 'django.db.backends.mysql','NAME': 'logsharding0','USER': 'logstat_user','PASSWORD': 'logstat_password','HOST': 'localhost','PORT': '3306','CHARSET': 'utf8',},'logsharding1': {'ENGINE': 'django.db.backends.mysql','NAME': 'logsharding1','USER': 'logstat_user','PASSWORD': 'logstat_password','HOST': 'localhost','PORT': '3306','CHARSET': 'utf8',},
}
创建数据库
create database logstat charset='utf8';
grant all on logstat.* to 'logstat_user'@localhost identified by 'logstat_password';
create database logsharding0 charset='utf8';
grant all on logsharding0.* to 'logstat_user'@localhost identified by 'logstat_password';
create database logsharding1 charset='utf8';
grant all on logsharding1.* to 'logstat_user'@localhost identified by 'logstat_password';
添加模型类
在 logstat/report/models.py
中添加我们要存储的日志格式:
class Log(models.Model):serverid = models.IntegerField('服务器ID')logid = models.IntegerField('日志类型')desc = models.TextField('日志内容', blank=True)report_dt = models.DateTimeField('上报时间')
然后将 app 添加到 INSTALLED_APPS
中。./manage.py makemigrations
产生 migrations 文件。
同步数据库:
./manage.py migrate
./manage.py migrate --database=logsharding0
./manage.py migrate --database=logsharding1
这时候我们发现,所有的 migrations 都在 default
, logsharding0
, logsharding1
分别创建了表。这显然不是我们想要的。我们想要的效果是 report
app 中的模型不在 default
中创建,只在 logshardingx
中创建,而 default
中的模型,也不希望在 logshardingx
中创建。
此时我们需要添加数据库路由器。
数据库路由器
在 logstat/report
中创建 log_router.py
文件,添加路由规则:
class LogRouter(object):def allow_migrate(self, db, app_label, model_name=None, **hints):if app_label == 'report':return db == 'logsharding0' or db == 'logsharding1'return None
意思是,在 migrate
时,如果是 report
App,并且 database
是 logshardingx
是可以创建的,否则不创建。
在 setting.py
中添加数据库路由器,使之生效:
DATABASE_ROUTERS = ['report.log_router.LogRouter',]
同步数据库:
./manage.py migrate
./manage.py migrate --database=logsharding0
./manage.py migrate --database=logsharding1
这时,我们发现 report_log
表已经不再 log_stat
库中,而只出现在 logshardingx
中。但是在 logshardingx
还是会有 auth_group
,auth_group_permissions
... 这些 Django 组件的表。到现在,我们已经实现了分库的效果,这些额外的表我们不用关心,但是总觉得不优雅,还是去比较好。
这些额外的 App 分别是 admin
, auth
,contenttypes
, sessions
, messages
, staticfiles
,同样我们需要为他们设置路由规则,在 logstat
下创建 default_router.py
添加路由规则,使得其余的 App 自动只选择 default
:
class DefaultRouter(object):def allow_migrate(self, db, app_label, model_name=None, **hints):if app_label == 'admin' \or app_label == 'auth' \or app_label == 'staticfiles' \or app_label == 'sessions' \or app_label == 'messages' \or app_label == 'contenttypes':return db == 'default'return None
同样添加到 DATABASE_ROUTERS
中,
DATABASE_ROUTERS = ['logstat.default_router.DefaultRouter','report.log_router.LogRouter',
]
再执行 migrate --database=xxx
时,只创建了两个表,django_migrations
和 report_log
,这就是我们想要的效果。
备注: django_migrations
这个表是必须存在的,它是数据库 migrate
记录,以保证再次 migrate
时,migrations
文件不被重复执行。
分库使用
Django 在多数据库文档中提供了指定数据库的用法,但是我个人倾向于一个简单的规则:「同时只操作一个库」。从那个库中查询的数据,无论是修改、保存还是删除,都只操作同一个库。像:
>>> user_obj.save(using='new_users')
>>> user_obj.delete(using='legacy_users')
这种用户从一个表迁移到另外一个表,应该写的更明确一些,应在业务上迁移而不是利用 using
关键字和 Django 的设计取巧。尽管这非常方便,但是对于维护代码的人简直就是灾难!所谓业务上的迁移是,首先创建一个新的用户(User.objects.using('new_users').create()
),待新用户创建以后,再删除旧用户(User.objects.using('legacy_users').filter().delete()
),逻辑清晰。
自动选择一个库
日志存储的需求是:基于 serverid
的 hash 值选择一个存储库中的模型,封装一个函数即可:
def db_slice(serverid):slice_list = ('logsharding0','logsharding0')return slice_list[serverid % 2]
使用实例:
# 创建对象
Log.objects.using(db_slice(1)).create(serverid=1,logid=1001,desc='lalala',report_dt=datetime.now()
)# 查询对象
Log.objects.using(db_slice(1)).all()
结尾
自此,一个分库的例子就讲完了,也比较简单。聊几句个人想法,选择框架和选择技术,因为业务场景不同,很难有一个完美的解决方案,总是要做一些取舍。
比如说,自己写一个直接独立于 Django 的分库分表策略并不难,但是脱离了 Django 这一套东西,常用的 API 用不了(create
, filter
, delete
etc),为每一个操作封装一个 SQL 操作,测试起来比较麻烦,灵活性太强,扩展性差,重要的是还要防止 SQL 注入。
如果用 Django 的多 DB 实现策略,也有问题。首先是路由,新的 app 如果忘了设置路由规则,很容易把表生成到不想生成的地方,而且 Django 官方文档也说了,并不会检查非 default
迁移的一致性(1.10之后版本可能支持)。其次是外键,使用了分库之后外键约束自然就没有了,这也不算一个问题。还有在使用上,每次访问数据库都必须要用 using
显式的选择一个数据库。忘了如果路由规则设置没问题会直接报错这倒还好,如果选择错了,就蛋疼了,因为他们每个库的结构都是一样的,很难查出问题。这将为写代码增加复杂性。
我的建议是,既然没有一个完美的方案,就应该尽量的保证逻辑简单、清晰,不要过分的依赖框架,少使用 hack 技巧。像上面那种
>>> user_obj.save(using='new_users')
>>> user_obj.delete(using='legacy_users')
尽管可行,但个人认为这是非常不可取的。
转载于:https://www.cnblogs.com/floodwater/p/9842737.html
Django 实现分库相关推荐
- Django 应用分库,数据迁移成功,数据库没有生成表
Django 应用分库,数据迁移成功,数据库没有生成表 背景:不同应用对应不同数据库,在迁移数据成功后,数据库没有生成表 Django 官网:https://docs.djangoproject.co ...
- django全自动分库分表(横向)
django全自动分库分表 期待大神指点方法的不足之处或者其他更好的方法 由于当前项目目前只需要实现写,所以这里也没有考虑读写分离.如果要考虑读写分离,则在路由处进行设置即可. 分库原理 django ...
- django写mysql轮询_django 多数据库及分库实现方式
定义及路由机制 定义 在settings里面的DATABASES是一个字典,用于定义需要的数据库,如下,一共定义了两个数据库. DATABASES = { 'default': { 'NAME': ' ...
- web框架总结(django、flask)
1.MVC解读 M:Model,模型,和数据库进行交互 V:View,视图,负责产生Html页面 C:Controller,控制器,接收请求,进行处理,与M和V进行交互,返回应答. 1.用户点击注 2 ...
- django model filter_Django分表的两个方案
由来 知乎上的一个问题:Django 分表 怎么实现? 这个问题戳到了Django ORM的痛点,对于多数据库/分库的问题,Django提供了很好的支持,通过using和db router可以很好的完 ...
- django render_2020年最新Django经典面试问题与答案汇总(下)大江狗整理
终于我们来到Django经典面试问题与答案系列的终章了,在本章我们将分享最后10个常用有用的知识点,也是面试时常问的经典问题.如果你还没有读过本系列前两篇文章的,可以点击如下链接阅读. 2020年最新 ...
- Django配置数据库读写分离
对网站的数据库作读写分离(Read/Write Splitting)可以提高性能,在Django中对此提供了支持,下面我们来简单看一下.注意,还需要运维人员作数据库的读写分离和数据同步. 配置数据库 ...
- Django主从数据库分离配置
数据库主从配置,django 发表时间:2020-08-25 对网站的数据库作读写分离(Read/Write Splitting)可以提高性能,在Django中对此提供了支持,下面我们来简单看一下.注 ...
- Django如何使用多个数据库
1.定义数据库 (1) DATABASES内部选项: (2)自定义数据库 2.同步数据库 3.自动数据库路由 (1)定义数据库路由方法类 (2)使用路由数据库 (3)生成数据表并同步数据 4.手动选择 ...
最新文章
- Java基础篇:网络编程
- CentOS 6.0 VNC远程桌面配置
- 【机器学习】谷歌的速成课程(二)
- 信息系统项目管理师-项目评估的基本方法
- 微信小程序,图片居中显示,适配不同机型
- kernel 中标准的 ir 模块的 时间的定义_Linux开机流程详解:BIOSgt;MBRgt;GRUBgt;Kernel...
- 爬取豆瓣top250的代码
- 群里又会python的吗_自从会了Python在群里斗图就没输过,Python批量下载表情包!...
- win7双系统安装openSUSE13.2解决【引导加载器安装期间出错】问题
- TensorFlow windows安装
- VS找不到MFC90d.dll错误
- ideagit更新一个文件_idea使用ssh下载的git代码怎么没法更新
- 开源数字媒体资产管理系统:Razuna
- Android自定义控件封装之自定义属性的实现
- .NET应用程序调试—原理、工具、方法
- 【读书笔记】重新定义团队
- SoftICE初使用
- Java实现 LeetCode 492 构造矩形
- 实用的电脑快捷键技巧,赶紧收藏!
- 什么是视觉特效师,我如何才能成为一名艺术家?