目录

    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 类,通过取余算法,将不同用户的数据分配到不同表中。

    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 文件中,新增数据库配置:

    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,
        }
    }
    

    有两种使用方法:

    1. 使用 using,代码入侵比较强。
    Author.objects.using('mydb').all()
    
    1. 使用 Database Router

    第一步,编写 Database Router,指定匹配的 app_label 使用某个 DB。

    需要实现 db_for_read、db_for_write、allow_relationy 以及 allow_migrate 方法。

    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。

    DATABASE_ROUTERS = ['db_router.MyRouter']
    

    第三步,配置 Model 的 app_label

    class MyModel(models.Model):
        pass
    
        class Meta:
            app_label = 'myapp_label'