传统的将数据集中存储至单一数据节点的解决方案,在性能、可用性和运维成本这三方面已经难于满足互联网的海量数据场景。
- 从性能方面来说,由于关系型数据库大多采用B+树类型的索引,在数据量超过阈值的情况下,索引深度的增加也将使得磁盘访问的IO次数增加,进而导致查询性能的下降;同时,高并发访问请求也使得集中式数据库成为系统的最大瓶颈。
- 从可用性的方面来讲,服务化的无状态型,能够达到较小成本的随意扩容,这必然导致系统的最终压力都落在数据库之上。而单一的数据节点,或者简单的主从架构,已经越来越难以承担。数据库的可用性,已成为整个系统的关键。
- 从运维成本方面考虑,当一个数据库实例中的数据达到阈值以上,对于DBA的运维压力就会增大。数据备份和恢复的时间成本都将随着数据量的大小而愈发不可控。一般来讲,单一数据库实例的数据的阈值在1TB之内,是比较合理的范围。
一、数据分片(Sharding)
数据分片(Sharding)指按照某个维度将存放在单一数据库中的数据分散地存放至多个数据库或表中以达到提升性能瓶颈以及可用性的效果。
数据分片的有效手段是对关系型数据库进行分库和分表。分库和分表均可以有效的避免由数据量超过可承受阈值而产生的查询瓶颈。 除此之外,分库还能够用于有效的分散对数据库单点的访问量;分表虽然无法缓解数据库压力,但却能够提供尽量将分布式事务转化为本地事务的可能,一旦涉及到跨库的更新操作,分布式事务往往会使问题变得复杂。
通过分库和分表进行数据的拆分来使得各个表的数据量保持在阈值以下,以及对流量进行疏导应对高访问量,是应对高并发和海量数据系统的有效手段。
数据分片的拆分方式又分为垂直分片和水平分片。
垂直分片,一般按照业务拆分。在微服务架构中,比较常用这种方式。它的核心理念是专库专用。 在拆分之前,一个数据库由多个数据表构成,每个表对应着不同的业务。
水平分片又称为横向拆分。 相对于垂直分片,它不再将数据根据业务逻辑分类,而是通过某个字段(或某几个字段),根据某种规则将数据分散至多个库或表中,每个分片仅包含数据的一部分。 例如:根据主键分片,偶数主键的记录放入0库(或表),奇数主键的记录放入1库(或表),如下图所示。
水平分片从理论上突破了单机数据量处理的瓶颈,并且扩展相对自由,是分库分表的标准解决方案。
跨库事务也是分布式的数据库集群要面对的棘手事情。
合理采用分表,可以在降低单表数据量的情况下,尽量使用本地事务,善于使用同库不同表可有效避免分布式事务带来的麻烦。
在不能避免跨库事务的场景,有些业务仍然需要保持事务的一致性。 而基于XA的分布式事务由于在并发度高的场景中性能无法满足需要,并未被互联网巨头大规模使用,他们大多采用最终一致性的柔性事务代替强一致事务。
二、什么时候考虑切分?
什么时候需要考虑做数据切分?
1、能不切分尽量不要切分。
并不是所有表都需要进行切分,主要还是看数据的增长速度。切分后会在某种程度上提升业务的复杂度,数据库除了承载数据的存储和查询外,协助业务更好的实现需求也是其重要工作之一。
不到万不得已不用轻易使用分库分表这个大招,避免"过度设计"和"过早优化"。分库分表之前,不要为分而分,先尽力去做力所能及的事情,例如:升级硬件、升级网络、读写分离、索引优化等等。当数据量达到单表的瓶颈时候,再考虑分库分表。
2、数据量过大,正常运维影响业务访问。
这里说的运维,指:
1)对数据库备份,如果单表太大,备份时需要大量的磁盘IO和网络IO。例如1T的数据,网络传输占50MB时候,需要20000秒才能传输完毕,整个过程的风险都是比较高的。
2)对一个很大的表进行DDL修改时,MySQL会锁住全表,这个时间会很长,这段时间业务不能访问此表,影响很大。在此操作过程中,都算为风险时间。将数据表拆分,总量减少,有助于降低这个风险。
3)大表会经常访问与更新,就更有可能出现锁等待。将数据切分,用空间换时间,变相降低访问压力。
3、随着业务发展,需要对某些字段垂直拆分。
举个例子,假如项目一开始设计的用户表如下:
在项目初始阶段,这种设计是满足简单的业务需求的,也方便快速迭代开发。而当业务快速发展时,用户量从10w激增到10亿,用户非常的活跃,每次登录会更新 last_login_name 字段,使得 user 表被不断update,压力很大。而其他字段:id, name, personal_info 是不变的或很少更新的,此时在业务角度,就要将 last_login_time 拆分出去,新建一个 user_time 表。
personal_info 属性是更新和查询频率较低的,并且text字段占据了太多的空间。这时候,就要对此垂直拆分出 user_ext 表了。
4、数据量快速增长。
随着业务的快速发展,单表中的数据量会持续增长,当性能接近瓶颈时,就需要考虑水平切分,做分库分表了。此时一定要选择合适的切分规则,提前预估好数据容量。
5、安全性和可用性。
鸡蛋不要放在一个篮子里。在业务层面上垂直切分,将不相关的业务的数据库分隔,因为每个业务的数据量、访问量都不同,不能因为一个业务把数据库搞挂而牵连到其他业务。利用水平切分,当一个数据库出现问题时,不会影响到100%的用户,每个库只承担业务的一部分数据,这样整体的可用性就能提高。
三、Sharding-JDBC 分库分表中间件
Sharding-JDBC 是开源的数据库中间件,定位为轻量级数据库驱动,由客户端直连数据库,以jar 包形式提供服务,没有使用中间层,无须额外部署,无须其他依赖。Sharding-JDBC 可以实现旧式迁移零成本的目标。
Sharding-JDBC 直接封装 JDBC API,可以理解为增强版的JDBC 驱动。Sharding-JDBC 可适用于任何基于Java的ORM框架,如MyBatis、Spring JDBC Template或直接使用JDBC。
- 适用于任何基于Java的ORM框架,如:JPA, Hibernate, Mybatis, Spring JDBC Template或直接使用JDBC。
- 基于任何第三方的数据库连接池,如:DBCP, C3P0, BoneCP, Druid, HikariCP等。
- 支持任意实现JDBC规范的数据库。目前支持MySQL,Oracle,SQLServer和PostgreSQL。
下面是基于Sharding-JDBC 分库分表的演示项目,主要是其中的三个配置文件。
- DataSourceConfig - sharding-jdbc数据源配置;
- DemoDatabaseShardingAlgorithm - 数据库分片的计算逻辑;
- DemoTableShardingAlgorithm - 数据表的分片规则;
pom.xml 文件中添加Sharding-JDBC依赖包,具体版本可以参考Sharding-JDBC 官网。
分库分表示意图
分别创建了2个数据库shop_0和shop_1。其中每个数据库都创建了2个数据表,shop_info_0和shop_info_1,如图所示。
这里蓝色的代表shop_0中的表,橙色的代表shop_1中的表。红色shop_info表是虚拟表。
分库分表的逻辑
- 基于shop_id 进行分库分表,具体算法规则如下所示。
- 流程大致是这样,在应用程序中我们操作虚拟表goods,但是当真正操作数据库的时候,会根据我们的分库分表规则进行匹配然后操作。
分库分表比仅分库或仅分表更为复杂,现列举一种策略:
- 中间变量 = 分库分表字段%(库数量 × 每个库的表数量)
- 库 = 取整(中间变量/每个库的表数量)
- 表 = 中间变量%每个库的表数量
(1)数据分库分片的计算逻辑,选择具体的数据库名称。
(2)数据表的分片规则,返回具体的表名称。
测试代码,模拟保存商户
/**
* 模拟保存商户
*/
@Test
public void saveShop() {
//保存10个商户
for (int i = 0; i < 10; i++) {
ShopInfo shopInfo = new ShopInfo();
shopInfo.setShopId(shopId++);
shopInfo.setAccount("Account" + i);
shopInfo.setUserName("name" + i);
shopService.saveShop(shopInfo);
}
}
上面的测试代码,合计插入了10条记录。然后查看一下数据库,首先看shop_0,如图所示。
shop_1 的数据记录。
从上面几张图可以看出,分库分表已经按照我们前面定义的数据库和数据表策略来进行插入。
本文暂时没有评论,来添加一个吧(●'◡'●)