数据的水平扩展:在业务持续增长的同时,数据库也会变得越来越大,此时,我们就需要对其进行分割,使得单库的大小不

作者:禅与计算机程序设计艺术

1.简介

随着互联网公司的日益壮大,数据量的快速增长已成为企业面临的一个主要问题。而作为关系型数据库的支撑系统,MySQL的数据库也显现出了巨大的压力。 业务扩张、产品迭代、用户活跃等一系列原因导致海量的数据量,使得单台服务器的硬件资源无法满足需求。因此,对于数据存储系统的规划和部署,就成为一个非常重要的环节。 为了能够应付海量数据,就要对数据库进行水平拆分,将一个数据库分成多个小的数据库来降低单个数据库负载。相比于传统的垂直拆分方式,这种方法可以更好的满足海量数据的处理能力。另外,对于分库分表,还可以提高查询效率和并发能力。 本文将从如下几个方面进行介绍:

一、背景介绍

数据水平拆分(Horizontal Partitioning)是指将同一个物理数据库表根据某种规则拆分成多个逻辑上的独立数据库表,这样每一个逻辑数据库表都对应着一个或者多个物理磁盘文件,从而达到数据水平的伸缩性。数据水平拆分可以有效减少单个数据库的压力,并且提升数据库的吞吐量,并允许多个应用服务器共享相同的数据,从而实现数据库的共享和冗余。在分布式环境中,数据水平拆分也是一种常用的解决方案。通过分库分表,既可以降低应用程序访问数据库时的延迟,又可以提高数据库的读写并发能力,同时也能保护数据库的安全。数据水平拆分是一种典型的数据库优化策略,可通过如下步骤加速数据库水平扩展:

  1. 数据准备工作:清洗、规范化数据,并导入到MySQL主库;
  2. 制定分库分表方案:分析业务数据特征,确定分库分表规则;
  3. 分库分表工具或脚本生成分库分表SQL语句;
  4. 执行分库分表SQL语句,生成分库分表后的新数据库结构和数据;
  5. 配置路由规则,使请求按照预先设定的规则转向新的分库分表;
  6. 测试和调试:检查分库分表后的数据库是否正确运行;
  7. 上线发布:完成分库分表过程后,切换到新数据库,完成业务的上线发布。

二、基本概念术语说明

1.物理拆分与逻辑拆分

物理拆分就是真实将一个物理数据库拆分成多个物理数据库的过程,物理拆分后每个数据库对应了一个或者多个物理磁盘文件,物理拆分可以一定程度上提升数据库的读写性能。但它也带来了以下缺点:

  1. 维护复杂度增加:由于物理分片的存在,数据的插入、删除、更新操作等操作均需要考虑分片后数据的同步。
  2. 查询复杂度增加:如果查询涉及多个分片,则需要多次查询才能获取完整的数据结果。
  3. 索引维护难度增加:索引也需要维护在多个分片中的数据。
  4. 分片策略设计困难:如何决定分片策略,比如分片规则、分片数量、分片类型等。
  5. 事务一致性难以保证:因为各个分片之间的数据可能存在不一致性。

逻辑拆分则是在物理拆分的基础上,进一步将单个表或多个表的数据按照业务规则进行逻辑拆分,每个逻辑拆分之后得到一个逻辑库,逻辑库由多个逻辑表组成,每个逻辑表仅包含物理库的一部分数据。逻辑拆分的优势有:

  1. 查询性能提升:因为只需要访问部分数据,所以查询速度就会大幅提升。
  2. 大数据集内存可用降低:逻辑拆分可以让数据集在内存中加载的更快,有利于大数据集的查询。
  3. 提供并行计算能力:多个逻辑库可以并行计算,提升查询效率。
  4. 更好的维护策略:逻辑拆分可以更好地适用于不同的业务场景,如订单数据、日志数据等。
  5. 优化查询计划:优化器会更加注重每个逻辑库的查询效率,采用合适的索引策略,避免全库扫描。

2.分库分表原则

分库分表的原则一般包括:

  1. 数据切分粒度合理:选择恰当的切分粒度,保证每个分片包含的数据量符合预期。
  2. 根据主键散列分片:一般情况下,选择主键作为切分依据,将同一个业务实体的所有数据分配给同一个分片。
  3. 每个分片尽量保持在2GB以内:选择合理的分片大小,每个分片所占磁盘空间不能太大,否则会造成性能瓶颈。
  4. 使用冷热数据分片:不同级别的分片放在不同的数据库实例或主机,可以充分利用硬件资源,提升系统的并发处理能力。
  5. 通过负载均衡分担读写压力:通过负载均衡组件或代理分担读写压力,确保数据库集群的负载均衡。
  6. 数据迁移自动化:使用工具或脚本对分片进行灵活迁移,确保数据实时性。
  7. 备份和恢复简单化:只需对某个分片进行备份和恢复即可,无需对整个数据库进行备份。
  8. 监控和故障排查简单化:只需要关注某个分片的状态,就可以快速定位故障。

3.分库分表算法

1. Hash取模法

Hash取模法是最简单的分库分表算法,它是通过将所有记录按某个字段(一般是主键ID)进行哈希运算,然后取模运算来确定目标库。Hash取模法能够非常容易的实现分库功能,但其分片策略往往无法保证数据均匀分布。比如,同样的数据被哈希到两个库中,这就出现了数据倾斜的问题,导致某些库中数据过多,另一些库中数据过少。而且即便采用了一致性hash算法,仍然无法避免数据倾斜。

2. 桶取模法

桶取模法是一种基于范围的分库分表算法,它首先根据某些条件将大量的数据划分到多个桶中,然后再在这些桶中进行 Hash 或其他类似算法进行分库分表。这种分法依赖于数据库本身的性能及数据分布情况。当某个分区的数据量比较大的时候,如果选择的分库分表策略不当,将会出现数据倾斜问题。

3. Consistency Hash法

一致性 Hash 是一种基于虚拟节点的分布式哈希算法。它通过将所有的物理节点映射到一个共有的虚拟节点空间,这样每个节点都可以计算出自己的虚拟节点的位置,并确定自己应该存储哪些数据。一致性 Hash 的好处是可以尽可能的保证数据分布均匀,当节点增减或者重新分布时,只会影响到相关的虚拟节点。但是,一致性 Hash 算法仍然存在以下问题:

  1. 数据倾斜问题:节点数量不足时,仍然会出现数据倾斜。
  2. 负载不均衡:当负载发生变化时,也会导致数据分布不均匀。
  3. 调整节点难度大:当新增或删除节点时,需要对所有节点的虚拟节点分布重新计算,调整代价较大。

4.分库分表工具

目前市场上流行的分库分表工具有 MySQL Proxy,Mycat,ShardingSphere 和 TDDL 等。这里只介绍 MySQL Proxy 的用法。

1. MySQL Proxy

MySQL Proxy 是一款开源的 JDBC 连接池中间件,它可以帮助开发人员轻松实现数据库分库分表功能。MySQL Proxy 内部集成了分片算法,对开发者屏蔽了分库分表细节。开发人员只需要简单的配置就可以实现相应的分库分表功能。下面是 MySQL Proxy 的安装和配置步骤:

安装 MySQL Proxy

MySQL Proxy 可以通过源码编译安装或者下载发布包安装。如果是下载发布包安装的话,需要注意的是 MySQL Proxy 需要依赖 MySQL Server 。

wget https://dev.mysql.com/get/Downloads/Connector-J/mysql-connector-java-8.0.21.tar.gz
tar xzf mysql-connector-java-8.0.21.tar.gz
cd mysql-connector-java-8.0.21/
mvn clean package
cp target/mysql-connector-java-*-bin.jar $INSTALL_DIR/lib/ # 指定安装目录
配置 MySQL Proxy

MySQL Proxy 的配置文件位于 conf/ 下的 mysqlproxy.xml 文件,修改以下参数:

<server>
  <id>ndbcluster</id>
  <host>$PROXY_HOST</host>
  <port>3306</port>
  <username>root</username>
  <password>$PROXY_PASSWD</password>
  <schema>$SCHEMA_NAME</schema>
</server>
<users>
  <user>
    <username>root</username>
    <password>$ROOT_PASSWD</password>
  </user>
  <!-- additional users -->
</users>
<shardingRule>
  <tables>
    <table name="t" dataNodes="ds_${0..1}.t$->{0..9}">
      <databaseStrategy type="STANDARD"/>
      <tableStrategy type="HASH_MOD">
        <property name="modulo">$MODULUS</property>
        <property name="column">id</property>
      </tableStrategy>
      <keyGenerator class="io.shardingsphere.core.keygen.DefaultKeyGenerator"/>
    </table>
    <!-- other tables -->
  </tables>
</shardingRule>
<!-- sharding rule configuration -->
<dataSource>
  <defaultDSType>masterSlave</defaultDSType>
  <masterSlave>
    <name>ms_$DB_NAME</name>
    <masterDataSourceName>ms_ds_0</masterDataSourceName>
    <slaveDataSourceNames>
      <slaveDataSourceName>ms_ds_1</slaveDataSourceName>
    </slaveDataSourceNames>
    <loadBalanceAlgorithmType>ROUND_ROBIN</loadBalanceAlgorithmType>
  </masterSlave>
  <dataSources>
    <dataSource id="ds_0" url="$DS0_URL" username="$USER" password="$PASSWORD"/>
    <dataSource id="ds_1" url="$DS1_URL" username="$USER" password="$PASSWORD"/>
    <!-- more slave data sources -->
  </dataSources>
</dataSource>
<!-- master-slave data source configuration -->

这里假设有两组 MySQL 服务,分别为 ds_0 和 ds_1 ,它们的 url、用户名密码保持一致。在 tables 配置项下指定分片规则,按照 name 来指定逻辑表名,dataNodes 指定分片名称。其中 tableStrategy 配置项指定分片算法,按照 HASH_MOD 分片到两个库中,也就是 ds_0 和 ds_1 中。至于 keyGenerator 配置项,默认采用 DefaultKeyGenerator 类,也可以自定义 Key 生成器类。在 dataSource 配置项中,默认采用 master-slave 数据源模式,设置 loadBalanceAlgorithmType 属性为 ROUND_ROBIN 以实现负载均衡。

启动 MySQL Proxy 服务,执行以下命令:

./bin/start.sh
测试分库分表效果

测试分库分表效果,可以通过客户端或者 JDBC 操作来验证。下面是一个 Java 示例:

Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/$SCHEMA_NAME", "root", "$ROOT_PASSWD");
PreparedStatement ps = conn.prepareStatement("INSERT INTO t (c1) VALUES (?)");
for (int i = 0; i < 10; i++) {
    ps.setInt(1, i);
    ps.executeUpdate();
}
ps.close();
conn.commit();

// execute query in one of the shards to verify the distribution
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM t WHERE mod(id,$MODULUS)=1"); // where clause is added for testing purpose
while (rs.next()) {
    System.out.println(rs.getInt(1));
}
rs.close();
stmt.close();
conn.close();

这里假设逻辑表名为 t ,分片键为 id ,使用 HASH_MOD 分片算法,因此 mod 函数的参数值为 MODULUS (通常设置为10)。通过该示例,可以验证插入、查询操作是否正确地路由到指定的分片。

猜你喜欢

转载自blog.csdn.net/universsky2015/article/details/133566217
今日推荐