一、背景介绍
移动互联网时代,随着软件用户量的不断增长,由此产生的数据量也在飞速增长,比如,用户表、订单表、聊天消息表等。据统计,MySQL单表
可以存储10亿级
数据,只是这时性能比较差,业界公认MySQL
单表容量在1KW量级
是最佳状态,因为这时它的BTREE
索引树高在3~5之间
。
既然一张表无法搞定,那么就想办法将数据放到多个地方,目前比较普遍的方案有3个:
- 分区
- 要求数据不是海量(分区数有限,存储能力就有限)
- 业务并发能力要求不高
- 分库分表
- 互联网行业处理海量数据的通用方法
- 发展几十年的
RDBMS
(关系型数据库)具有生态完善、绝对稳定、事务特性的优点,只要有软件的地方,它都是核心存储的首选
- NoSQL/NewSQL
NoSQL/NewSQL
宣传的无论多厉害,就现在各大公司对它的定位,都是RDBMS
的补充,而不是取而代之
本文就分库分表
的一些核心流程展开介绍:
二、是单库分表
,还是分库分表
- 单库分表适用场景:
- 单表数据量太大,查询时需要扫描的行数太多,
SQL
执行效率低下 CPU
出现瓶颈
- 单表数据量太大,查询时需要扫描的行数太多,
- 分库分表适用场景:
- 磁盘读
IO
到了瓶颈:热点数据太多,数据库缓存放不下,每次查询时会产生大量的IO
,导致查询速度低下 - 请求的数据太多、网络带宽不够
- 数据库连接数超过了最大限定值
- 磁盘读
分库分表
的复杂度要高于单库分表
,如果数据量不是特别大,且QPS
也不是特别高,首选单库分表
,待某些指标有接近阈值的迹象时,再考虑分库分表。
分库分表
相对单库分表
来说,复杂的地方有:
- 需要约定多个分片数据源
- 需要定义多个分片数据源的事务管理器
- 数据一致性的处理方案(强一致 or 最终一致)
- 数据迁移、后续扩容
- …
三、分布式数据库中间件选型
本文主要针对时下比较流行的两款数据库中间件产品做下介绍:
主要指标 | Sharding-JDBC | MyCat |
---|---|---|
所属 | Apache | 基于阿里 Cobar 二次开发,社区维护 |
活跃度 | 高 | 高 |
ORM支持 | 任意 | 任意 |
基于客户端还是服务端 | 客户端 | 服务端 |
分库 | 支持 | 支持 |
分表 | 支持 | 不支持单库分表 |
事务 | 自带弱XA、最大努力送达型柔性事务 | 自带弱XA |
监控 | 无,可通过其它方式支持 | 自带 |
读写分离 | 支持 | 支持 |
限制 | 部分 JDBC 方法不支持、SQL语句限制 | 部分 JDBC 方法不支持、SQL语句限制 |
数据库连接池 | 任意 | 任意 |
MySQL交互协议 | JDBC Driver | 前后端均用 NIO |
开发 | 开发成本高,代码入侵大 | 开发成本小,代码入侵小 |
运维 | 维护成本低 | 维护成本高 |
配置难度 | 一般 | 复杂 |
Sharding-JDBC
架构图:
简单介绍:
Sharding-JDBC
是一款轻量级的框架,以工程依赖JAR
的形式提供功能,无需额外部署和依赖,可以理解为增强版的JDBC
驱动- 对于运维同事来说,只需要协助一些简单的配置及后续的扩容工作,无需关注底层代码与分片策略规则,相对
MyCat
,这是Sharding-JDBC
的优势,减少了部署成本以及运维同事的学习成本
MyCat
架构图
简单介绍:
MyCat
并不是业务系统代码里面的配置,而是独立运行的中间件,所有配置都会交给运维同事执行- 对于运维同事来说,它是在数据库
Server
前增加的一层代理,MyCat
本身不存数据,数据是在后端数据库上存储的,因此,数据可靠性以及事务等都是通过数据库保证的 MyCat
down 掉的时候,系统不能对数据库进行操作,会对所有用户产生影响MyCat
比较适合大数据工作
通过以上分析,可见
Sharding-JDBC
相对MyCat
来说,更轻量,首选肯定是Sharding-JDBC
,只要代码层面做好防腐层(依赖倒置)的设计,就算以后数据量级达到了百亿、千亿,也可以更加灵活方便的替换其它中间件产品,甚至NewSQL
。
四、分布式ID的生成方式
实现方式
-
完全依赖数据源的方式:
ID
的生成规则,读取控制完全由数据源控制,常见的如数据库自增长ID
、序列号、优雅的Flickr
方案、基于Redis
的原子操作incr/incrBy
产生顺序号、Mongodb
的ObjectId
、美团(Leaf
)的号段模式… -
半依赖数据源的方式:
ID
的生成规则,有部分生成因子需要由数据源(或配置信息)控制,如百度的uid-generator
、美团(Leaf
)的Snowflake
模式… -
不依赖数据源的方式:
ID
的生成规则完全由机器信息独立计算,不依赖任何配置信息和数据记录,如常见的UUID
及变种、GUID
…
实践方案
实践方案适用于以上提及的三种实现方式,可作为这三种实现方式的一种补充,旨在提升系统吞吐量,但原有实现方式的局限性依然存在。
-
实时获取方案:顾名思义,每次要获取
ID
时,实时生成。简单快捷,ID
都是连续不间断的,但吞吐量可能不是最高的。 -
预生成方案:预先生成一批
ID
放在数据池里,可简单自增长生成,也可以设置步长,分批生成,需要将这些预先生成的数据,放在存储容器里(JVM
内存,Redis
,数据库表均可以),可以较大幅度地提升吞吐量,但需要开辟临时存储空间,断电宕机后可能会丢失已有ID
,ID
也可能有间断。
选择分布式ID
的生成方式时,需要特别注意以下几个地方:
-
全局唯一:必须保证
ID
是全局唯一的 -
高可用:无限接近于
100%
的可用性 -
高性能:低延时,
ID
生成响应要快 -
接入方便:遵循拿来主义原则,在系统设计和实现上要尽可能的简单
-
长度适中:不要太长,最好
64bit
,使用long
比较好操作。如果是96bit
,需要各种移位,相当的不方便,还有可能有些组件不能支持这么大的ID
-
分片支持:可以控制
ShardingId
,比如某一个用户的文章要放在同一个分片内,这样查询效率高,修改也容易,实现稍复杂 -
信息安全:如果
ID
是连续的,恶意用户的扒取工作就非常容易做了,直接按照顺序下载指定URL
即可;如果是订单号就更危险了,竞争对手可以直接知道我们一天的订单量,所以要结合自己的业务场景来考虑
如果系统要求的吞吐量不是极高,个人推荐了解下优雅的
Flickr
方案,如果系统要求的吞吐量极高,个人推荐了解下美团的(Leaf
)项目,由于篇幅有限,这里不做过多展开。
五、分片/表键选择
分片键的定义
分片键即分库分表的拆分字段,是在水平拆分过程中用于生成拆分规则的数据表字段,根据分片键的值将数据表水平拆分到每个分库/分表