Django 数据库分库+读写分离

Page content

为了解决单个数据库的性能问题,除了使用性能更好的硬件之外, 另外一个思路就是将一个数据库切分成多个部分放到不同的数据库上,从而缓解单一数据库的性能问题。

Django ORM提供了默认的分库功能,只需要在settings.py文件中添加相应的数据库配置。

例如我们在Django项目有users, book, blog三个app, 对应userbookblog三个DB,下面就探究一下如何在Django实现这三个库的分库?

如何配置分库

配置DB地址

settings.py DATABASES中配置需要连接的多个数据库地址。

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    },
    'db_book': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db_book.sqlite3'),
    },
    'db_blog': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db_blog.sqlite3'),
    },
}

DATABASE_APPS_MAPPING = {
    # example:
    # 'app_name':'database_name',
    'blog': 'db_blog',
    'book': 'db_book',
    'users': 'default',
}

这里我们定义了三个库和地址,default, db_blogdb_book;以及app name到db name的映射。

定义Model

对应的,在book app的models中,我们定义如下Book类。

# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import models

# Create your models here.


class Book(models.Model):
    book_id = models.IntegerField(primary_key=True)
    title = models.CharField(max_length=256)
    author = models.CharField(max_length=256)

    class Meta:
        app_label = 'book'
        db_table = 'book_tab'

在blog app的models中,我们定义如下Blog类。

# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import models

# Create your models here.


class Blog(models.Model):
    blog_id = models.IntegerField(primary_key=True)
    title = models.CharField(max_length=256)
    content = models.TextField()

    class Meta:
        app_label = 'blog'
        db_table = 'blog_tab'

类似的,在users app中定义User类。

执行migrate

migrate命令在同步变更时,每次操作一个数据库,默认操作default数据库。通过--database选项,可以指定操作的数据库。

python manage.py migrate
python manage.py migrate --database=db_book
python manage.py migrate --database=db_blog

通过上面的命令,把models里定义的信息,同步到数据库。

如何访问分库

通过前面几步,就完成了Django数据库分库的配置。那配置完了怎样使用呢? 有两种方法来使用分库:

  1. using()方法
  2. 指定Database Router

using()方法

在没有配置路由前,我们需要通过using方法指定需要访问的数据库,代码入侵比较强。示例:

  • 指定保存的数据库
b = Blog(content='test')
b.save(using='db_blog')
  • 指定读取的数据库
Book.objects.using('db_book').all()

配置DB路由

配置Database routers需要新建一个db_router.py文件,添加自定义的数据库路由类DbRouter

这个类里需要实现四个方法:db_for_readdb_for_writeallow_relation以及allow_migrate来定义路由规则, 我们可以根据app_label来指定访问的DB。

from django.conf import settings

DATABASE_MAPPING = settings.DATABASE_APPS_MAPPING


class DbRouter(object):
    def db_for_read(self, model, **hints):
        if model._meta.app_label in DATABASE_MAPPING:
            return DATABASE_MAPPING[model._meta.app_label]
        return None

    def db_for_write(self, model, **hints):
        if model._meta.app_label in DATABASE_MAPPING:
            return DATABASE_MAPPING[model._meta.app_label]
        return None

    def allow_relation(self, obj1, obj2, **hints):
        pass

    def allow_migrate(self, db, app_label, model=None, **hints):
        pass

然后,在settings.py文件添加数据库路由的配置DATABASE_ROUTERS,如下:

DATABASE_ROUTERS = ['partition.db_router.DbRouter']  # partition是项目名

配置在DATABASE_ROUTERS中的路由规则才会生效,能被django.db.router使用。

我们可以指定多个数据库路由规则,比如对于读操作,Django将会循环所有路由中的db_for_read()方法,直到其中一个有返回值,然后使用这个数据库进行当前操作。

按完成了上述的路由配置后,Django ORM会根据路由规则来选择访问的DB。 按照上面DbRouter的配置,user会访问default库,book访问db_book库,blog访问db_blog库。 例如:Book.objects.all()会自动访问db_book数据库,无需再通过using来指定数据库。

如何配置读写分离

数据库读写分离是只是数据库分库的一种特殊情况,在现有的基础上,我们只需要调整下配置就可以实现读写分离了。DbRouter示例:

主库写从库读

class DbRouter:
    def db_for_read(self, model, **hints):
        """
        读取时随机选择一个从库
        """
        import random
        return random.choice(['db_slave2', 'db_slave3', 'db_slave4'])

    def db_for_write(self, model, **hints):
        """
        写入时选择主库
        """
        return 'default'

多个app读写分离

class DbRouter:
    def db_for_read(self, model, **hints):
        if model._meta.app_label == 'book':
            return 'db_book_slave'
        if model._meta.app_label == 'blog':
            return 'db_blog_slave'

    def db_for_write(self, model, **hints):
       if model._meta.app_label == 'book':
            return 'db_book_master'
       if model._meta.app_label == 'blog':
            return 'db_blog_master'