ShardingSphere-ShardingSphere基本介绍及核心概念

一、分布式数据库中间件

1.1 定义

分布式数据库中间件(Distributed Database Middleware,简称DDM),是一款分布式关系型数据库,采用先进的存储计算分离架构,实现并发,计算,数据存储三个方面均可线性扩展,专注于解决数据库分布式扩展问题,突破了传统数据库的容量和性能瓶颈,实现海量数据高并发访问,其核心的优势是提供数据库水平扩展能力。

1.2 分布式数据库中间件分类

Proxy代理,顾名思义即Web应用将所有的增删改查sql发送到给一个代理服务,代理服务去数据库完成相应的操作后再返回给Web应用

在这里插入图片描述

Jdbc直连,增强JDBC包,对所有的数据库操作语句按照特定的要求分发到不同的库中

在这里插入图片描述

1.3 分库分表

互联网业务兴起之后,海量用户加上海量数据的特点,单个数据库服务器已经难以满足业务需要,必须考虑数据库集群的方式提升性能,高性能数据库集群的第一种方式是“读写分离”,第一种方式是“数据库分片”。

读写分离架构

读写分离原理:读写分离的基本原理是将数据库读写操作分散到不同的节点上,下面是其基本架构图

在这里插入图片描述
【云原生 • Docker】mysql主从复制

读写分离的问题:

读写分离分散了数据库读写操作的压力,但是没有分散存储压力,为了满足业务数据存储的需求,就需要将存储分散到多台数据库服务器上。

数据库分片架构

随着业务的演进,大规模业务数据越来越大,数据库的访问就会出现瓶颈,分库分表的实现远比字面意思要复杂的多,介绍分库分表的基本概念。

为了解决数据量过大导致数据库性能降低的问题,讲原来独立的数据库拆分成若干个数据库,将原来数据量大的表拆分成若干个数据表,使得单一数据库,单一数据表的数据量变得足够小,从而达到提升数据库性能的作用。

遇到的问题

  • 用户请求量太大
    单服务器TPS、内存、IO都是有上限的,需要将请求打散分布到多个服务器
  • 单库数据量太大
    单个数据库处理能力有限;单库所在服务器的磁盘空间有限;单库上的操作IO有瓶颈
  • 单表数据量太大
    查询、插入、更新操作都会变慢,在加字段、加索引、机器迁移都会产生高负载,影响服务

原理探究:为什么大公司后台数据库都要搞分库分表?

数据分片:
将存放在单一数据库的数据分散地存放至多个数据库和表中,以到达提升性能瓶颈和可用性的效果,数据分片的有效手段是对关系型数据库进行分库和分表,数据分片的拆分方式又分为垂直分片和水平分片

垂直分片

垂直分库

按照业务拆分的方式称为垂直分片,又叫纵向拆分。它的核心理念是专库专用。在拆分之前,一个数据库由多个数据表构成,每个表对应着不同的业务。而拆分之后,则是按照业务将表进行归类,分布到不同的数据库中,从而将压力分散到不同的数据库中。

在这里插入图片描述

垂直拆分可以缓解数据量和访问量带来的问题,但无法根治。如果垂直拆分之后,单表的数据量依然超过单节点所能承受的阈值,则需要水平分片进一步来处理。

垂直分表

垂直分表适合将表中某些不常用的列,或者是占用了大量空间的列拆分出去。

表中字段太多且包含大字段的时候,在查询时对数据库的IO、内存会受到影响,同时更新数据时,产生的binlog文件会很大,MySQL在主从同步时也会有延迟的风险
在这里插入图片描述

水平分片

水平分表

针对数据量巨大的单张表(比如订单表),按照规则把一张表的数据切分到多张表里面去。但是这些表还是在同一个库中,所以库级别的数据库操作还是有IO瓶颈。

在这里插入图片描述

水平分库

水平分库是把同一个表的数据按一定规则拆到不同的数据库中,每个库可以放在不同的服务器上,每个库只有这个表的部分数据,这些库可以分布在不同服务器。 水平分库分表能够有效的缓解单机和单库的性能瓶颈和压力,突破IO、连接数、硬件资源等的瓶颈

在这里插入图片描述

单表进行拆分后,是否将多个表分散在不同的数据库服务器中,可以根据实际的切分效果来决定。

  • 水平分表:单表切分为多表后,新的表即使在同一个数据库服务器中,也可能带来可观的性能提升,如果性能能够满足业务需求,可以不拆分到多台数据库服务器,毕竟业务分库也会引入很多复杂性。
  • 水平分库:如果单表拆分为多表后,单台服务器依然无法满足性能要求,那就需要将多个表分散在不同的数据库服务器中。

阿里巴巴Java开发手册:
【推荐】单表行数超过500万行或单表容量超过2GB,才推荐进行分库分表。
说明:如果预计三年后的数据量根本达不到这个级别,请不要在创建表时就分库分表。

水平分库规则

  • 水平分库规则
    • 尽量不跨库、不跨表,保证同一类的数据都在同一个服务器上面。
    • 数据在切分之前,需要考虑如何高效的进行数据获取,如果每次查询都要跨越多个节点,就需要谨慎使用。
  • 水平分表规则
    • RANGE
      • 时间:按照年、月、日去切分。例如order_2020、order_202005、order_20200501
      • 地域:按照省或市去切分。例如order_beijing、order_shanghai、order_chengdu
      • 大小:从0到1000000一个表。例如1000001-2000000放一个表,每100万放一个表
    • HASH
      • 用户ID取模
        不同的业务使用的切分规则是不一样,就上面提到的切分规则,举例如下:

        • 站内信
          用户维度:用户只能看到发送给自己的消息,其他用户是不可见的,这种情况下是按照用户ID hash分库,在用户查看历史记录翻页查询时,所有的查询请求都在同一个库内

        • 用户表
          范围法:以用户ID为划分依据,将数据水平切分到两个数据库实例,如:1到1000W在一张表,1000W到2000W在一张表,这种情况会出现单表的负载较高

          按照用户ID HASH尽量保证用户数据均衡分到数据库中

          如果在登录场景下,用户输入手机号和验证码进行登录,这种情况下,登录时是不是需要扫描所有分库的信息?
          最终方案:用户信息采用ID做切分处理,同时存储用户ID和手机号的映射的关系表(新增一个关系表),关系表采用手机号进行切分。可以通过关系表根据手机号查询到对应的ID,再定位用户信息。

        • 流水表
          时间维度:可以根据每天新增的流水来判断,选择按照年份分库,还是按照月份分库,甚至也可以按照日期分库

        • 订单表
          在线招聘网站中,求职者(下面统称C端用户)投递企业(下面统称B端用户)的职位产生的记录称之为订单表。在线上的业务场景中,C端用户看自己的投递记录,每次的投递到了哪个状态,B端用户查看自己收到的简历,对于合适的简历会进行下一步沟通,同一个公司内的员工可以协作处理简历。
          如何能同时满足C端和B端对数据查询,不进行跨库处理?

          最终方案:为了同时满足两端用户的业务场景,采用空间换时间,将一次的投递记录存为两份,C端的投递记录以用户ID为分片键,B端收到的简历按照公司ID为分片键
          在这里插入图片描述
          数据库拆分原则:

  1. 优先考虑缓存降低对数据库的读操作。
  2. 再考虑读写分离,降低数据库写操作。
  3. 最后开始数据拆分,切分模式: 首先垂直(纵向)拆分、再次水平拆分。
  4. 首先考虑按照业务垂直拆分。
  5. 再考虑水平拆分:先分库(设置数据路由规则,把数据分配到不同的库中)
  6. 最后再考虑分表,单表拆分到数据1000万以内。

主键选择

  • UUID:本地生成,不依赖数据库,缺点就是作为主键性能太差
  • SNOWFLAKE:百度UidGenerator、美团Leaf、基于SNOWFLAKE算法实现

看下之前文章原理探究:分布式架构的几个常见的坑中分库分表的坑这个章节。

数据库的扩容

如何实现丝滑的数据库扩容

1.4 实现分库分表

  • 数据分片:根本需求
  • 读写分离:架构实现方案
  • 分布式事务:标准化事务处理接口

读写分离和数据分片具体实现的方式有两种:程序代码封装中间件封装

程序代码封装

程序代码封装指在代码中抽象一个数据访问层或中间层封装。实现读写操作分离和数据库服务器连接的管理。

其基本机构是:以读写分离为例

在这里插入图片描述

中间件封装

中间件封装指的是独立一套系统出来,实现读写操作分离和数据库服务器连接的管理。对于业务服务器来说,访问中间件和访问数据库没有区别,在业务服务器看来,中间件就是一个数据库服务器。
在这里插入图片描述

常用解决方案

  • 基于代理层方式:Mycat、Sharding-Proxy、MySQL Proxy
  • 基于应用层方式:Sharding-jdbc

分库分表面临的问题:

  • 多数据库进行高效的治理
  • 跨节点关联查询
  • 跨节点分页和排序
  • 全局唯一的主键
  • 事务一致性
  • 数据迁移

二、ShardingSphere与JDBC

官网地址

2.1 ShardingSphere的演进过程

在这里插入图片描述
从版本发布角度,我们也可以进一步梳理 ShardingSphere 发展历程中主线版本与核心功能之间的演进关系图:

在这里插入图片描述

2.2 ShardingSphere 的设计理念:不是颠覆,而是兼容

对于一款开源中间件来说,要得到长足的发展,一方面依赖于社区的贡献,另外在很大程度上还取决于自身的设计和发展理念。

ShardingSphere 的定位非常明确,就是一种关系型数据库中间件,而并非一个全新的关系型数据库。ShardingSphere 认为,在当下,关系型数据库依然占有巨大市场,但凡涉及数据的持久化,关系型数据库仍然是系统的标准配置,也是各个公司核心业务的基石,在可预见的未来中,这点很难撼动。所以,ShardingSphere 在当前阶段更加关注在原有基础上进行兼容和扩展,而非颠覆。那么 ShardingSphere 是如何做到这一点呢?

ShardingSphere 构建了一个生态圈,这个生态圈由一套开源的分布式数据库中间件解决方案所构成。按照目前的规划,ShardingSphere 由 Sharding-JDBC、Sharding-Proxy 和 Sharding-Sidecar 这三款相互独立的产品组成,其中前两款已经正式发布,而 Sharding-Sidecar 正在规划中。我们可以从这三款产品出发,分析 ShardingSphere 的设计理念。

ShardingSphere定位为关系型数据库中间件,旨在充分合理地在分布式的场景下利用关系型数据库的计算和存储能力,而并非实现一个全新的关系型数据库。

在这里插入图片描述

  • Sharding-JDBC:被定位为轻量级Java框架,在Java的JDBC层提供的额外服务,以jar包形式使用。
  • Sharding-Proxy:被定位为透明化的数据库代理端,提供封装了数据库二进制协议的服务端版本,用于完成对异构语言的支持。
  • Sharding-Sidecar:被定位为Kubernetes或Mesos的云原生数据库代理,以DaemonSet的形式代理所有对数据库的访问。
    在这里插入图片描述
    Sharding-JDBC、Sharding-Proxy和Sharding-Sidecar三者区别如下:
ShardingSphere-JDBCShardingSphere-ProxyShardingSphere-Sidecar
数据库任意MySQL/PostgreSQL MySQL/PostgreSQL
连接消耗数
异构语言仅 Java任意任意
性能损耗低损耗略高损耗低
无中心化
静态入口

ShardingSphere-JDBC 采用无中心化架构,适用于 Java 开发的高性能的轻量级 OLTP 应用;ShardingSphere-Proxy 提供静态入口以及异构语言的支持,适用于 OLAP 应用以及对分片数据库进行管理和运维的场景。

Apache ShardingSphere 是多接入端共同组成的生态圈。 通过混合使用 ShardingSphere-JDBC 和 ShardingSphere-Proxy,并采用同一注册中心统一配置分片策略,能够灵活的搭建适用于各种场景的应用系统,使得架构师更加自由地调整适合与当前业务的最佳系统架构。

ShardingSphere安装包下载:https://shardingsphere.apache.org/document/current/cn/downloads/
在这里插入图片描述
在这里插入图片描述
使用Git下载工程: https://github.com/apache/incubator-shardingsphere.git
在这里插入图片描述

2.3 Sharding-JDBC

ShardingSphere 的前身是 Sharding-JDBC,所以这是整个框架中最为成熟的组件。Sharding-JDBC 的定位是一个轻量级 Java 框架,在 JDBC 层提供了扩展性服务。我们知道 JDBC 是一种开发规范,指定了 DataSource、Connection、Statement、PreparedStatement、ResultSet 等一系列接口。而各大数据库供应商通过实现这些接口提供了自身对 JDBC 规范的支持,使得 JDBC 规范成为 Java 领域中被广泛采用的数据库访问标准。

基于这一点,Sharding-JDBC 一开始的设计就完全兼容 JDBC 规范,Sharding-JDBC 对外暴露的一套分片操作接口与 JDBC 规范中所提供的接口完全一致。开发人员只需要了解 JDBC,就可以使用 Sharding-JDBC 来实现分库分表,Sharding-JDBC 内部屏蔽了所有的分片规则和处理逻辑的复杂性。显然,这种方案天生就是一种具有高度兼容性的方案,能够为开发人员提供最简单、最直接的开发支持。关于 Sharding-JDBC 与 JDBC 规范的兼容性话题,我们将会在下一课时中详细讨论。
在这里插入图片描述
在实际开发过程中,Sharding-JDBC 以 JAR 包的形式提供服务。开发人员可以使用这个 JAR 包直连数据库,无需额外的部署和依赖管理。在应用 Sharding-JDBC 时,需要注意到 Sharding-JDBC 背后依赖的是一套完整而强大的分片引擎:

在这里插入图片描述
由于 Sharding-JDBC 提供了一套与 JDBC 规范完全一致的 API,所以它可以很方便地与遵循 JDBC 规范的各种组件和框架进行无缝集成。例如,用于提供数据库连接的 DBCP、C3P0 等数据库连接池组件,以及用于提供对象-关系映射的 Hibernate、MyBatis 等 ORM 框架。当然,作为一款支持多数据库的开源框架,Sharding-JDBC 支持 MySQL、Oracle、SQLServer 等主流关系型数据库。

2.4 Sharding-Proxy

ShardingSphere 中的 Sharding-Proxy 组件定位为一个透明化的数据库代理端,所以它是代理服务器分片方案的一种具体实现方式。在代理方案的设计和实现上,Sharding-Proxy 同样充分考虑了兼容性。

Sharding-Proxy 所提供的兼容性首先体现在对异构语言的支持上,为了完成对异构语言的支持,Sharding-Proxy 专门对数据库二进制协议进行了封装,并提供了一个代理服务端组件。其次,从客户端组件上讲,针对目前市面上流行的 Navicat、MySQL Command Client 等客户端工具,Sharding-Proxy 也能够兼容遵循 MySQL 和 PostgreSQL 协议的各类访问客户端。当然,和 Sharding-JDBC 一样,Sharding-Proxy 也支持 MySQL 和 PostgreSQL 等多种数据库。

接下来,我们看一下 Sharding-Proxy 的整体架构。对于应用程序而言,这种代理机制是完全透明的,可以直接把它当作 MySQL 或 PostgreSQL 进行使用:
在这里插入图片描述
总结一下,我们可以直接把 Sharding-Proxy 视为一个数据库,用来代理后面分库分表的多个数据库,它屏蔽了后端多个数据库的复杂性。同时,也看到 Sharding-Proxy 的运行同样需要依赖于完成分片操作的分片引擎以及用于管理数据库的治理组件。

虽然 Sharding-JDBC 和 Sharding-Proxy 具有不同的关注点,但事实上,我们完全可以将它们整合在一起进行使用,也就是说这两个组件之间也存在兼容性。

前面已经介绍过,我们使用 Sharding-JDBC 的方式是在应用程序中直接嵌入 JAR 包,这种方式适合于业务开发人员。而 Sharding-Proxy 提供静态入口以及异构语言的支持,适用于需要对分片数据库进行管理的中间件开发和运维人员。基于底层共通的分片引擎,以及数据库治理功能,可以混合使用 Sharding-JDBC 和 Sharding-Proxy,以便应对不同的应用场景和不同的开发人员:
在这里插入图片描述

2.5 Sharding-Sidecar

Sidecar 设计模式受到了越来越多的关注和采用,这个模式的目标是把系统中各种异构的服务组件串联起来,并进行高效的服务治理。

ShardingSphere 也基于该模式设计了 Sharding-Sidecar 组件。截止到目前,ShardingSphere 给出了 Sharding-Sidecar 的规划,但还没有提供具体的实现方案,这里不做具体展开。作为 Sidecar 模式的具体实现,我们可以想象**Sharding-Sidecar的作用就是以 Sidecar 的形式代理所有对数据库的访问。**这也是一种兼容性的设计思路,通过无中心、零侵入的方案将分布式的数据访问应用与数据库有机串联起来。

ShardingSphere 的核心功能:从数据分片到编排治理

介绍完 ShardingSphere 的设计理念之后,我们再来关注它的核心功能和实现机制。这里把 ShardingSphere 的整体功能拆分成四大部分,即基础设施、分片引擎、分布式事务和治理与集成,这四大部分也构成了本课程介绍 ShardingSphere 的整体行文结构,下面我们来分别进行介绍:

基础设施

作为一款开源框架,ShardingSphere 在架构上也提供了很多基础设施类的组件,这些组件更多与它的内部实现机制有关,我们将会在后续的源码解析部分详细展开讲解。但对开发人员而言,可以认为微内核架构和分布式主键是框架提供的基础设施类的核心功能。

  • 微内核架构

ShardingSphere 在设计上采用了微内核(MicroKernel)架构模式,来确保系统具有高度可扩展性。微内核架构包含两部分组件,即内核系统和插件。使用微内核架构对系统进行升级,要做的只是用新插件替换旧插件,而不需要改变整个系统架构:
在这里插入图片描述
在 ShardingSphere 中,抽象了一大批插件接口,包含用实现 SQL 解析的 SQLParserEntry、用于实现配置中心的 ConfigCenter、用于数据脱敏的 ShardingEncryptor,以及用于数据库治理的注册中心接口 RegistryCenter 等。开发人员完全可以根据自己的需要,基于这些插件定义来提供定制化实现,并动态加载到 ShardingSphere 运行时环境中。

  • 分布式主键

在本地数据库场景下,我们可以使用数据库自带的自增序列来完成主键的生成。但在分片场景下,我们将面对从本地数据库转换到分布式数据库的应用场景,这就需要考虑主键在各个数据库中的全局唯一性。为此,我们需要引入分布式主键机制。ShardingSphere 同样提供了分布式主键的实现机制,默认采用的是 SnowFlake(雪花)算法。

分片引擎

对于分片引擎,ShardingSphere 同时支持数据分片和读写分离机制。

  • 数据分片

数据分片是 ShardingSphere 的核心功能,常规的,基于垂直拆分和水平拆分的分库分表操作它都支持。同时,ShardingSphere 也预留了分片扩展点,开发人员也可以基于需要实现分片策略的定制化开发。

  • 读写分离

在分库分表的基础上,ShardingSphere 也实现了基于数据库主从架构的读写分离机制。而且,这种读写分离机制可以和数据分片完美地进行整合。

分布式事务

分布式事务是分布式环境下确保数据一致性的基本功能,作为分布式数据库的一种生态圈,ShardingSphere 也提供了对分布式事务的全面支持。

  • 标准化事务处理接口

ShardingSphere 支持本地事务、基于 XA 两阶段提交的强一致性事务以及基于 BASE 的柔性最终一致性事务。同时,ShardingSphere 抽象了一组标准化的事务处理接口,并通过分片事务管理器 ShardingTransactionManager 进行统一管理。我们也可以根据需要实现自己的 ShardingTransactionManager 从而对分布式事务进行扩展。

  • 强一致性事务与柔性事务

ShardingSphere 内置了一组分布式事务的实现方案,其中强一致性事务内置集成了 Atomikos、Narayana 和 Bitronix 等技术来实现 XA 事务管理器;另一方面,ShardingSphere 内部也整合了 Seata 来提供柔性事务功能。

治理与集成

对于分布式数据库而言,治理的范畴可以很广,ShardingSphere 也提供了注册中心、配置中心等一系列功能来支持数据库治理。另一方面,ShardingSphere 作为一款支持快速开发的开源框架,也完成了与其他主流框架的无缝集成。

  • 数据脱敏

数据脱敏是确保数据访问安全的常见需求,通常做法是对原始的 SQL 进行改写,从而实现对原文数据进行加密。当我们想要获取原始数据时,在实现上就需要通过对数据库中所存储的密文数据进行解密才能完成。我们可以根据需要实现一套类似的加解密机制,但 ShardingSphere 的强大之处在于,它将这套机制内嵌到了 SQL 的执行过程中,业务开发人员不需要关注具体的加解密实现细节,而只要通过简单的配置就能实现数据的自动脱敏。

  • 配置中心

关于配置信息的管理,我们可以基于 YAML 格式或 XML 格式的配置文件完成配置信息的维护,这在 ShardingSphere 中都得到了支持。更进一步,在 ShardingSphere 中,它还提供了配置信息动态化的管理机制,可以支持数据源、表与分片及读写分离策略的动态切换。

  • 注册中心

相较配置中心,注册中心在 ShardingSphere 中的应用更为广泛。ShardingSphere 中的注册中心提供了基于 Nacos 和 ZooKeeper 的两种实现方式。而在应用场景上,我们可以基于注册中心完成数据库实例管理、数据库熔断禁用等治理功能。

  • 链路跟踪

SQL 解析与 SQL 执行是数据分片的最核心步骤,ShardingSphere 在完成这两个步骤的同时,也会将运行时的数据通过标准协议提交到链路跟踪系统。ShardingSphere 使用 OpenTracing API 发送性能追踪数据。像 SkyWalking、Zipkin 和 Jaeger 等面向 OpenTracing 协议的具体产品都可以和 ShardingSphere 自动完成对接。

  • 系统集成

这里所谓的系统集成,指的是 ShardingSphere 和 Spring 系列框架的集成。到目前为止,ShardingSphere 实现了两种系统的集成机制,一种是命名空间机制,即通过扩展 Spring Schema 来实现与 Spring 框架的集成;而另一种则是通过编写自定义的 starter 组件来完成与 Spring Boot 的集成。这样,无论开发人员采用哪一种 Spring 框架,对于使用 ShardingSphere 而言都是零学习成本。

三、ShardingSphere核心概念

官网地址

3.1 表概念

  • 真实表:数据库中真实存在的物理表。例如b_order0、b_order1
  • 逻辑表:水平拆分的数据库(表)的相同逻辑和数据结构表的总称。例:订单数据根据主键尾数拆分为10张表,分别是t_order_0到t_order_9,他们的逻辑表名为t_order。
  • 数据节点:在分片之后,由数据源和数据表组成。例如ds0.b_order1
  • 绑定表:指的是分片规则一致的关系表(主表、子表),例如b_orderb_order_item,均按照order_id分片,则此两个表互为绑定表关系。绑定表之间的多表关联查询不会出现笛卡尔积关联,可以提升关联查询效率。
b_order:b_order0、b_order1
b_order_item:b_order_item0、b_order_item1

select * from b_order o join b_order_item i on(o.order_id=i.order_id)where o.order_id in (10,11);

在不配置绑定表关系时,假设分片键order_id将数值10路由至第0片,将数值11路由至第1片,那么路由后的SQL应该为4条,它们呈现为笛卡尔积:

SELECT i.* FROM t_order_0 o JOIN t_order_item_0 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);

SELECT i.* FROM t_order_0 o JOIN t_order_item_1 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);

SELECT i.* FROM t_order_1 o JOIN t_order_item_0 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);

SELECT i.* FROM t_order_1 o JOIN t_order_item_1 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);

在配置绑定表关系后,路由的SQL应该为2条:

SELECT i.* FROM t_order_0 o JOIN t_order_item_0 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);

SELECT i.* FROM t_order_1 o JOIN t_order_item_1 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);
  • 广播表:在使用中,有些表没必要做分片,例如字典表、省份信息等,因为他们数据量不大,而且这种表可能需要与海量数据的表进行关联查询。广播表会在不同的数据节点上进行存储,存储的表结构和数据完全相同。

3.2 分片概念

分片键

用于分片的数据库字段,是将数据库(表)水平拆分的关键字段,例:将订单表中的订单主键的尾数取模分片,则订单主键为分片字段。 SQL中如果无分片字段,将执行全路由,性能较差。 除了对单分片字段的支持,ShardingSphere也支持根据多个字段进行分片。

分片算法(ShardingAlgorithm)

通过分片算法将数据分片,支持通过=、>=、<=、>、<、BETWEENIN分片。分片算法需要应用方开发者自行实现,可实现的灵活度非常高。

由于分片算法和业务实现紧密相关,因此并未提供内置分片算法,而是通过分片策略将各种场景提炼出来,提供更高层级的抽象,并提供接口让应用开发者自行实现分片算法。目前提供4种分片算法。

  • 精确分片算法PreciseShardingAlgorithm

用于处理使用单一键作为分片键的=和in进行分片的场景,需要配合StandardShardingStrategy使用。

  • 范围分片算法RangeShardingAlgorithm

用于处理使用单一键作为分片键的BETWEEN AND、>、<、>=、<=进行分片的场景。需要配合StandardShardingStrategy使用。

  • 复合分片算法ComplexKeysShardingAlgorithm

用于处理使用多键作为分片键进行分片的场景,包含多个分片键的逻辑较复杂,需要应用开发者自行处理其中的复杂度。需要配合ComplexShardingStrategy使用。

  • Hint分片算法HintShardingAlgorithm

用于处理使用Hint行分片的场景。需要配合HintShardingStrategy使用。

对于分片字段非SQL决定,而由其他外置条件决定的场景,可使用SQL Hint灵活的注入分片字段。例:内部系统,按照员工登录主键分库,而数据库中并无此字段。SQL Hint支持通过Java API和SQL注释两种方式使用。

分片策略

包含分片键和分片算法,由于分片算法的独立性,将其独立抽离。真正可用于分片操作的是分片键 + 分片算法,也就是分片策略。目前提供5种分片策略。

  • 标准分片策略

对应StandardShardingStrategy。提供对SQL语句中的=, >, <, >=, <=, IN和BETWEEN AND的分片操作支持。StandardShardingStrategy只支持单分片键,提供PreciseShardingAlgorithm和RangeShardingAlgorithm两个分片算法。PreciseShardingAlgorithm是必选的,用于处理=和IN的分片。RangeShardingAlgorithm是可选的,用于处理BETWEEN AND, >, <, >=, <=分片,如果不配置RangeShardingAlgorithm,SQL中的BETWEEN AND将按照全库路由处理。

  • 复合分片策略

对应ComplexShardingStrategy。复合分片策略。提供对SQL语句中的=, >, <, >=, <=, IN和BETWEEN AND的分片操作支持。ComplexShardingStrategy支持多分片键,由于多分片键之间的关系复杂,因此并未进行过多的封装,而是直接将分片键值组合以及分片操作符透传至分片算法,完全由应用开发者实现,提供最大的灵活度。

  • 行表达式分片策略

对应InlineShardingStrategy。使用Groovy的表达式,提供对SQL语句中的=和IN的分片操作支持,只支持单分片键。对于简单的分片算法,可以通过简单的配置使用,从而避免繁琐的Java代码开发,如: t_user_$->{u_id % 8} 表示t_user表根据u_id模8,而分成8张表,表名称为t_user_0t_user_7

  • Hint分片策略

对应HintShardingStrategy。通过Hint指定分片值而非从SQL中提取分片值的方式进行分片的策略。

  • 不分片策略

对应NoneShardingStrategy。不分片的策略。

分片策略配置

对于分片策略存有数据源分片策略和表分片策略两种维度。

  • 数据源分片策略

对应于DatabaseShardingStrategy。用于配置数据被分配的目标数据源。

  • 表分片策略

用于配置数据被分配的目标表,由于表存在与数据源内,所以表分片策略是依赖数据源分片
策略结果的。

3.3 流程剖析

ShardingSphere 3个产品的数据分片功能主要流程是完全一致的,如下图所示。
在这里插入图片描述

SQL解析

分为词法解析和语法解析。 先通过词法解析器将SQL拆分为一个个不可再分的单词。再使用语法解析器对SQL进行理解,并最终提炼出解析上下文。 解析上下文包括表、选择项、排序项、分组项、聚合函数、分页信息、查询条件以及可能需要修改的占位符的标记。

Sharding-JDBC采用不同的解析器对SQL进行解析,解析器类型如下:

  • MySQL解析器
  • Oracle解析器
  • SQLServer解析器
  • PostgreSQL解析器
  • 默认SQL解析器

查询优化

合并和优化分片条件,如OR等。

SQL路由

根据解析上下文匹配用户配置的分片策略,并生成路由路径。目前支持分片路由和广播路由。

SQL改写

将SQL改写为在真实数据库中可以正确执行的语句。SQL改写分为正确性改写和优化改写。

SQL执行

通过多线程执行器异步执行SQL。

结果归并

将多个执行结果集归并以便于通过统一的JDBC接口输出。结果归并包括流式归并、内存归并和使用装饰者模式的追加归并这几种方式。

3.4 SQL使用规范

支持项

  • 路由至单数据节点时,目前MySQL数据库100%全兼容,其他数据库完善中。

  • 路由至多数据节点时,全面支持DQL、DML、DDL、DCL、TCL。支持分页、去重、排序、分组、聚合、关联查询(不支持跨库关联)。以下用最为复杂的查询为例:

    • SELECT主语句
    SELECT select_expr [, select_expr ...] FROM table_reference [, table_reference ...]
    [WHERE predicates]
    [GROUP BY {col_name | position} [ASC | DESC], ...]
    [ORDER BY {col_name | position} [ASC | DESC], ...]
    [LIMIT {[offset,] row_count | row_count OFFSET offset}]
    

不支持项

  • 路由至多数据节点
  • 不支持CASE WHEN、HAVING、UNION (ALL),有限支持子查询。

除了分页子查询的支持之外(详情请参考分页),也支持同等模式的子查询。无论嵌套多少层,ShardingSphere都可以解析至第一个包含数据表的子查询,一旦在下层嵌套中再次找到包含数据表的子查询将直接抛出解析异常。

例如,以下子查询可以支持:

SELECT COUNT(*) FROM (SELECT * FROM t_order o)

以下子查询不支持:

SELECT COUNT(*) FROM (SELECT * FROM t_order o WHERE o.id IN (SELECT id FROM t_order WHERE status = ?))

简单来说,通过子查询进行非功能需求,在大部分情况下是可以支持的。比如分页、统计总数等;而通过子查询实现业务查询当前并不能支持。

  • 由于归并的限制,子查询中包含聚合函数目前无法支持。
  • 不支持包含schema的SQL。因为ShardingSphere的理念是像使用一个数据源一样使用多数据源,因此对SQL的访问都是在同一个逻辑schema之上。
  • 当分片键处于运算表达式或函数中的SQL时,将采用全路由的形式获取结果。
    例如下面SQL,create_time为分片键:
SELECT * FROM t_order WHERE to_date(create_time, 'yyyy-mm-dd') = '2019-01-01';

由于ShardingSphere只能通过SQL字面提取用于分片的值,因此当分片键处于运算表达式或函数中时,ShardingSphere无法提前获取分片键位于数据库中的值,从而无法计算出真正的分片值。

当出现此类分片键处于运算表达式或函数中的SQL时,ShardingSphere将采用全路由的形式获取结果。

不支持的SQL示例:

SQL不支持原因
INSERT INTO tbl_name (col1, col2, …) VALUES(1+2, ?, …)VALUES语句不支持运算表达式
INSERT INTO tbl_name (col1, col2, …) SELECT col1, col2, … FROM tbl_name WHERE col3 = ?INSERT … SELECT
SELECT COUNT(col1) as count_alias FROM tbl_name GROUP BY col1 HAVING count_alias > ?HAVING
SELECT * FROM tbl_name1 UNION SELECT * FROM tbl_name2UNION
SELECT * FROM tbl_name1 UNION ALL SELECT * FROM tbl_name2UNION ALL
SELECT * FROM ds.tbl_name1包含schema
SELECT SUM(DISTINCT col1), SUM(col1) FROM tbl_name详见DISTINCT支持情况详细说明
SELECT * FROM tbl_name WHERE to_date(create_time, ‘yyyy-mm-dd’) = ?会导致全路由

3.5 分页查询

完全支持MySQL和Oracle的分页查询,SQLServer由于分页查询较为复杂,仅部分支持.

  • 性能瓶颈:
    查询偏移量过大的分页会导致数据库获取数据性能低下,以MySQL为例:
SELECT * FROM t_order ORDER BY id LIMIT 1000000, 10

这句SQL会使得MySQL在无法利用索引的情况下跳过1000000条记录后,再获取10条记录,其性能可想而知。 而在分库分表的情况下(假设分为2个库),为了保证数据的正确性,SQL会改写为:

SELECT * FROM t_order ORDER BY id LIMIT 0, 1000010

即将偏移量前的记录全部取出,并仅获取排序后的最后10条记录。这会在数据库本身就执行很慢的情况下,进一步加剧性能瓶颈。 因为原SQL仅需要传输10条记录至客户端,而改写之后的SQL则会传输1,000,010 * 2的记录至客户端。

  • ShardingSphere的优化
    ShardingSphere进行了2个方面的优化。
    • 首先,采用流式处理 + 归并排序的方式来避免内存的过量占用。由于SQL改写不可避免的占用了额外的带宽,但并不会导致内存暴涨。 与直觉不同,大多数人认为ShardingSphere会将1,000,010 * 2记录全部加载至内存,进而占用大量内存而导致内存溢出。 但由于每个结果集的记录是有序的,因此ShardingSphere每次比较仅获取各个分片的当前结果集记录,驻留在内存中的记录仅为当前路由到的分片的结果集的当前游标指向而已。 对于本身即有序的待排序对象,归并排序的时间复杂度仅为O(n),性能损耗很小。
    • 其次,ShardingSphere对仅落至单分片的查询进行进一步优化。 落至单分片查询的请求并不需要改写SQL也可以保证记录的正确性,因此在此种情况下,ShardingSphere并未进行SQL改写,从而达到节省带宽的目的。
  • 分页方案优化

由于LIMIT并不能通过索引查询数据,因此如果可以保证ID的连续性,通过ID进行分页是比较好的解决方案:

SELECT * FROM t_order WHERE id > 100000 AND id <= 100010 ORDER BY id

或通过记录上次查询结果的最后一条记录的ID进行下一页的查询:

SELECT * FROM t_order WHERE id > 100000 LIMIT 10

3.6 行表达式

Inline行表达式
InlineShardingStrategy:采用Inline行表达式进行分片的配置。

Inline是可以简化数据节点和分片算法配置信息。主要是解决配置简化、配置一体化。

语法格式:

${['online', 'offline']}_table${1..3}

最终会解析为:

online_table1, online_table2, online_table3, offline_table1, offline_table2, offline_table3

配置数据节点:
对于均匀分布的数据节点,如果数据结构如下:

db0
  ├── t_order0 
  └── t_order1 
db1
  ├── t_order0 
  └── t_order1

用行表达式可以简化为:

db${0..1}.t_order${0..1}

或者

db$->{0..1}.t_order$->{0..1}

对于自定义的数据节点,如果数据结构如下:

db0
  ├── t_order0 
  └── t_order1 
db1
  ├── t_order2
  ├── t_order3
  └── t_order4

用行表达式可以简化为:

db0.t_order${0..1},db1.t_order${2..4}

或者

db0.t_order$->{0..1},db1.t_order$->{2..4}

分片算法配置

对于只有一个分片键的使用=和IN进行分片的SQL,可以使用行表达式代替编码方式配置。

行表达式内部的表达式本质上是一段Groovy代码,可以根据分片键进行计算的方式,返回相应的真实数据源或真实表名称。

例如:分为10个库,尾数为0的路由到后缀为0的数据源, 尾数为1的路由到后缀为1的数据源,以此类推。用于表示分片算法的行表达为:

ds${id % 10}

或者

ds$->{id % 10}

结果为:ds0、ds1、ds2… ds9

3.7 分布式主键

ShardingSphere不仅提供了内置的分布式主键生成器,例如UUID、SNOWFLAKE,还抽离出分布式主键生成器的接口,方便用户自行实现自定义的自增主键生成器。

内置主键生成器:

自定义主键生成器:

  • 自定义主键类,实现ShardingKeyGenerator接口

  • 按SPI规范配置自定义主键类
    在Apache ShardingSphere中,很多功能实现类的加载方式是通过SPI注入的方式完成的。注意:在resources目录下新建META-INF文件夹,再新建services文件夹,然后新建文件的名字为org.apache.shardingsphere.spi.keygen.ShardingKeyGenerator,打开文件,复制自定义主键类全路径到文件中保存。

  • 自定义主键类应用配置

 #对应主键字段名
spring.shardingsphere.sharding.tables.t_book.key-generator.column=id
#对应主键类getType返回内容
spring.shardingsphere.sharding.tables.t_book.keygenerator.type=SELFKEY
  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值