Django 性能之分库分表

2023年 1月 4日 48.8k 0

1. 碰到的问题

前端请求量大,并发高,访问速度慢,瓶颈主要表现在:

  • 单表大
  • 单库大
  • 网络 IO 慢
  • 磁盘 IO 慢

网络、磁盘 IO 优化,主要依靠硬件升级。理论上,数据库对单库、单表的大小没有限制,但是过大的单库、单表会导致更多的请求落到单机上,给 IO 造成压力。理想情况是,通过增加机器,能不断地增加系统并发能力。当 MySQL 单表数据量达到百万级别时,我们就应该开始存储相关的知识,以应对可能的问题。

2. 数据库架构设计的三种模式

为了解决数据库的性能问题,除了使用性能更好的硬件之外,另外一个思路就是从架构方面考虑。将一个数据库切分成多个部分放到不同的数据库上,从而缓解单一数据库的性能问题。数据库构架设计中主要有 Shared Everthting、Shared Nothing、和 Shared Disk。通常说的 Sharding,实际上指的就是 Shared Nothing,通过增加处理单元来扩展处理能力。

2.1 Shared Everthting

通常是单个主机,完全共享 CPU、Memory、IO,并行处理能力差。例如,SQL Server。

2.2 Shared Disk

各个处理单元使用私有的 CPU、Memory,共享 IO。可以通过增加节点来提高并行处理能力,直到存储接口达到瓶颈为止。例如 Oracle Rac。

2.3 Shared Nothing

各个处理单元都有自己私有的 CPU、Memory、IO。各个处理单元之间,通过协议通信,例如:Hadopp。

3. 拆分策略

3.1 垂直拆分

将关系紧密的数据聚合在一起,拆分到不同的 Server。

  • 分表:基于字段。
  • 分库:基于业务。

3.2 水平拆分

将同类数据,拆分到不同的 Server。

  • 分表:基于某种规则(hash 等)。
  • 分库:基于表结构相同,但数据集不同。

拆分后的问题

  • 主键生成(唯一 ID)
  • 数据的路由(分布、节点伸缩)
  • 事务支持。由数据库本身,转向了应用层。
  • 跨库 Join。由应用层组装。
  • count、group by、order by 等聚合

在生产环境中,通常会混合垂直、水平拆分实施。将原有数据库切分为矩阵一样,可以根据需要,无限拆分。

4. Django 中的 Sharding

4.1 分表方案

Django 分表方案,主要是自定义 Model 的 db_table 属性指定 ORM 操作的表名。下面的例子中,使用一个 Proxy 类,通过取余算法,将不同用户的数据分配到不同表中。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
from django.db import models

# 表分片的数量
SHARD_TABLE_NUMBER = 2


class UserProxy(models.Model):
    @classmethod
    def get_sharding_model(cls, uid=None):
        piece = uid % SHARD_TABLE_NUMBER

        class Meta:
            db_table = 'user_%s' % piece
        attrs = {
            '__module__': cls.__module__,
            '__doc__': 'using user_%s table' % piece,
            'Meta': Meta
        }
        return type(str('User%s' % piece), (cls, ), attrs)
    username = models.CharField(max_length=255)

    class Meta:
        abstract = True

User2 = UserProxy.get_sharding_model(uid=2)

需要考虑的新问题:

  • 分片数量改变后,如何保证一致性
  • 新建数据如何选择表
  • 如何同步表结构

4.2 分库方案

Django 原生支持分库,只需要在 settings.py 文件中,新增数据库配置:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'db_name1',
        'USER': 'db_user1',
        'PASSWORD': 'db_password1',
        'HOST': '127.0.0.0',
        'PORT': 3306,
    },
    'mydb2': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'db_name2',
        'USER': 'db_user2',
        'PASSWORD': 'db_password2',
        'HOST': '127.0.0.0',
        'PORT': 3306,
    }
}

有两种使用方法:

  • 使用 using,代码入侵比较强。
  • 1
    
    Author.objects.using('mydb').all()
    
  • 使用 Database Router
  • 第一步,编写 Database Router,指定匹配的 app_label 使用某个 DB。需要实现 db_for_read、db_for_write、allow_relationy 以及 allow_migrate 方法。

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    
    class MyRouter(object):
        def db_for_read(self, model, **hints):
            if model._meta.app_label == 'myapp_label':
                return 'mydb2'
            return None
    
        def db_for_write(self, model, **hints):
            pass
    
        def allow_relation(self, obj1, obj2, **hints):
            pass
    
        def allow_migrate(self, db, model):
            pass
    

    第二步,在 settings.py 文件中配置 Database Router。

    1
    
    DATABASE_ROUTERS = ['db_router.MyRouter']
    

    第三步,配置 Model 的 app_label

    1
    2
    3
    4
    5
    
    class MyModel(models.Model):
        pass
    
        class Meta:
            app_label = 'myapp_label'
    

    相关文章

    KubeSphere 部署向量数据库 Milvus 实战指南
    探索 Kubernetes 持久化存储之 Longhorn 初窥门径
    征服 Docker 镜像访问限制!KubeSphere v3.4.1 成功部署全攻略
    那些年在 Terraform 上吃到的糖和踩过的坑
    无需 Kubernetes 测试 Kubernetes 网络实现
    Kubernetes v1.31 中的移除和主要变更

    发布评论