MySQL关于分库分表及其平滑扩容方案实例讲解
众所周知,很容易成为应用的瓶颈。单机数据库的资源和处理能力有限,在高并发的分布式系统中,可采用分库分表突破单机局限。本文总结了分库分表的相关概念、全局id的生成策略、分片策略、平滑扩容方案、以及流行的方案。
1 分库分表概述
在业务量不大时,单库单表即可支撑。
当数据量过大存储不下、或者并发量过大负荷不起时,就要考虑分库分表。
1.1 分库分表相关术语
读写分离: 不同的数据库,同步相同的数据,分别只负责数据的读和写; 分区: 指定分区列表达式,把记录拆分到不同的区域中(必须是同一服务器,可以是不同硬盘),应用看来还是同一张表,没有变化; 分库:一个系统的多张数据表,存储到多个数据库实例中; 分表: 对于一张多行(记录)多列(字段)的二维数据表,又分两种情形:
(1) 垂直分表: 竖向切分,不同分表存储不同的字段,可以把不常用或者大容量、或者不同业务的字段拆分出去;
(2) 水平分表(最复杂): 横向切分,按照特定分片算法,不同分表存储不同的记录。
1.2 真的要采用分库分表?
需要注意的是,分库分表会为数据库维护和业务逻辑带来一系列复杂性和性能损耗,除非预估的业务量大到万不得已,切莫过度设计、过早优化。
规划期内的数据量和性能问题,尝试能否用下列方式解决:
- 当前数据量:如果没有达到几百万,通常无需分库分表;
- 数据量问题:增加磁盘、增加分库(不同的业务功能表,整表拆分至不同的数据库);
- 性能问题:升级cpu/内存、读写分离、优化数据库系统配置、优化数据表/索引、优化 sql、分区、数据表的垂直切分;
- 如果仍未能奏效,才考虑最复杂的方案:数据表的水平切分。
2 全局id生成策略
2.1 自动增长列
优点:数据库自带功能,有序,性能佳。
缺点:单库单表无妨,分库分表时如果没有规划,id可能重复。解决方案:
2.1.1 设置自增偏移和步长
## 假设总共有 10 个分表
## 级别可选: session(会话级), global(全局)
set @@session.auto_increment_offset = 1; ## 起始值, 分别取值为 1~10
set @@session.auto_increment_increment = 10; ## 步长增量
如果采用该方案,在扩容时需要迁移已有数据至新的所属分片。
2.1.2 全局id映射表
在全局 redis 中为每张数据表创建一个 id 的键,记录该表当前最大 id;
每次申请 id 时,都自增 1 并返回给应用;
redis 要定期持久至全局数据库。
2.2 uuid(128位)
在一台机器上生成的数字,它保证对在同一时空中的所有机器都是唯一的。通常平台会提供生成uuid的api。
uuid 由4个连字号(-)将32个字节长的字符串分隔后生成的字符串,总共36个字节长。形如:550e8400-e29b-41d4-a716-446655440000。
uuid 的计算因子包括:以太网卡地址、纳秒级时间、芯片id码和许多可能的数字。
uuid 是个标准,其实现有几种,最常用的是微软的 guid(globals unique identifiers)。
优点:简单,全球唯一;
缺点:存储和传输空间大,无序,性能欠佳。
2.3 comb(组合)
组合 guid(10字节) 和时间(6字节),达到有序的效果,提高索引性能。
2.4 snowflake(雪花) 算法
snowflake 是 twitter 开源的分布式 id 生成算法,其结果为 long(64bit) 的数值。
其特性是各节点无需协调、按时间大致有序、且整个集群各节点单不重复。
该数值的默认组成如下(符号位之外的三部分允许个性化调整):
- 1bit: 符号位,总是 0(为了保证数值是正数)。喎? f/ware/vc/"="" target="_blank" class="keylink">vcd4ncjxwpi0gndfiaxq6ilrbw+vk/si/ydpdidy5imtqkao7pc9wpg0kpha+lsaxmgjpddogvdq140lekdviaxtk/b7d1tdqxcaridviaxs92rxjsusjrnans9ygmzigkiazmia9idewmjqgupa92rxjktwvcd4ncjxwpi0gmtjiaxq6imh3y666xsjdv7j2vdq148o/ushd68ta1qez1ia0mdk2ilj2ieleo6zp4lwx09ognda5zfk1xcbrufojrm/gzazksbzkxnri5ybjrcdt9ret16qjrntytci0/dbbz8lsu7rbw+sppc9wpg0kpggyiglkpq=="3-分片策略">3 分片策略
3.1 连续分片
根据特定字段(比如用户id、订单时间)的范围,值在该区间的,划分到特定节点。
优点:集群扩容后,指定新的范围落在新节点即可,无需进行数据迁移。
缺点:如果按时间划分,数据热点分布不均(历史数冷当前数据热),导致节点负荷不均。
3.3 id取模分片
缺点:扩容后需要迁移数据。
3.2 一致性hash算法
优点:扩容后无需迁移数据。
3.4 snowflake 分片
优点:扩容后无需迁移数据。
4 分库分表引入的问题
4.1 分布式事务
参见 分布式事务的解决方案
由于两阶段/三阶段提交对性能损耗大,可改用事务补偿机制。
4.2 跨节点 join
对于单库 join,mysql 原生就支持;
对于多库,出于性能考虑,不建议使用 mysql 自带的 join,可以用以下方案避免跨节点 join:
- 全局表: 一些稳定的共用数据表,在各个数据库中都保存一份;
- 字段冗余: 一些常用的共用字段,在各个数据表中都保存一份;
- 应用组装:应用获取数据后再组装。
另外,某个 id 的用户信息在哪个节点,他的关联数据(比如订单)也在哪个节点,可以避免分布式查询。
4.3 跨节点聚合
只能在应用程序端完成。
但对于分页查询,每次大量聚合后再分页,性能欠佳。
4.4 节点扩容
节点扩容后,新的分片规则导致数据所属分片有变,因而需要迁移数据。
5 节点扩容方案
相关资料: 数据库秒级平滑扩容架构方案
5.1 常规方案
如果增加的节点数和扩容操作没有规划,那么绝大部分数据所属的分片都有变化,需要在分片间迁移:
- 预估迁移耗时,发布停服公告;
- 停服(用户无法使用服务),使用事先准备的迁移脚本,进行数据迁移;
- 修改为新的分片规则;
- 启动服务器。
5.2 免迁移扩容
采用双倍扩容策略,避免数据迁移。扩容前每个节点的数据,有一半要迁移至一个新增节点中,对应关系比较简单。
具体操作如下(假设已有 2 个节点 a/b,要双倍扩容至 a/a2/b/b2 这 4 个节点):
- 无需停止应用服务器;
- 新增两个数据库 a2/b2 作为从库,设置主从同步关系为:a=>a2、b=>b2,直至主从数据同步完毕(早期数据可手工同步);
- 调整分片规则并使之生效:
原 id%2=0 => a 改为 id%4=0 => a, id%4=2 => a2;
原 id%2=1 => b 改为 id%4=1 => b, id%4=3 => b2。
- 解除数据库实例的主从同步关系,并使之生效;
- 此时,四个节点的数据都已完整,只是有冗余(多存了和自己配对的节点的那部分数据),择机清除即可(过后随时进行,不影响业务)。
6 分库分表方案
6.1 代理层方式
部署一台代理服务器伪装成 mysql 服务器,代理服务器负责与真实 mysql 节点的对接,应用程序只和代理服务器对接。对应用程序是透明的。
比如 mycat,官网,,参考文档:mycat+mysql 读写分离部署
mycat 后端可以支持 mysql, sql server, oracle, db2, postgresql等主流数据库,也支持mongodb这种新型nosql方式的存储,未来还会支持更多类型的存储。
mycat 不仅仅可以用作读写分离,以及分表分库、容灾管理,而且可以用于多租户应用开发、云平台基础设施,让你的架构具备很强的适应性和灵活性。
6.2 应用层方式
处于业务层和 jdbc 层中间,是以 jar 包方式提供给应用调用,对代码有侵入性。主要方案有:
(1)淘宝网的 tddl: 已于 2012 年关闭了维护通道,建议不要使用。
(2)当当网的 sharding-jdbc: 仍在活跃维护中:
是当当应用框架 ddframe 中,从关系型数据库模块 dd-rdb 中分离出来的数据库水平分片框架,实现透明化数据库分库分表访问,实现了 snowflake 分片算法;
sharding-jdbc定位为轻量java框架,使用客户端直连数据库,无需额外部署,无其他依赖,dba也无需改变原有的运维方式。
sharding-jdbc分片策略灵活,可支持等号、between、in等多维度分片,也可支持多分片键。
sql解析功能完善,支持聚合、分组、排序、limit、or等查询,并支持binding table以及笛卡尔积表查询。
sharding-jdbc直接封装jdbc api,可以理解为增强版的jdbc驱动,旧代码迁移成本几乎为零:
- 可适用于任何基于java的orm框架,如jpa、hibernate、mybatis、spring jdbc template或直接使用jdbc。
- 可基于任何第三方的数据库连接池,如dbcp、c3p0、 bonecp、druid等。
- 理论上可支持任意实现jdbc规范的数据库。虽然目前仅支持mysql,但已有支持oracle、sqlserver等数据库的计划。
喎?>上一篇: Android 请求网络接口实现方法
下一篇: C语言实现简单的面向对象代码实例