十、Sharding-JDBC
1、概述
ShardingSphere是一套起始于当当的开源的分布式数据库中间件解决方案组成的生态圈,它由Sharding-JDBC、Sharding-Proxy和Sharding-Sidecar(计划中)这3款相互独立的产品组成。 他们均提供标准化的数据分片、分布式事务和数据库治理功能,可适用于如Java同构、异构语言、容器、云原生等各种多样化的应用场景。
(1)、Sharding-JDBC
客户端直连数据库,以jar包形式提供服务,无需额外部署和依赖,可理解为增强版的JDBC驱动,完全兼容JDBC和各种ORM框架。
(2)、Sharding-Proxy
定位为透明化的数据库代理端,提供封装了数据库二进制协议的服务端版本,用于完成对异构语言的支持。 目前先提供MySQL/PostgreSQL版本,它可以使用任何兼容MySQL/PostgreSQL协议的访问客户端(如:MySQL Command Client, MySQL Workbench, Navicat等)操作数据,对DBA更加友好。
(3)、Sharding-Sidecar(规划中)
定位为Kubernetes的云原生数据库代理,以Sidecar的形式代理所有对数据库的访问。 通过无中心、零侵入的方案提供与数据库交互的的啮合层,即Database Mesh,又可称数据网格。
2、基本概念
例:t_order表水平拆分为:t_order_1、t_order_2
(1)、逻辑表
逻辑表是水平拆表的数据表的总称,即t_order。
(2)、真实表
在分片的数据库中真实存在的物理表。即t_order_1、t_order_2。
(3)、数据节点
数据分片的最小物理单元,由数据源名称和数据表组成。例:db0.t_order_1。
(4)、绑定表
指分片规则一致的主表和子表。例:t_order表垂直拆分为t_order表、t_order_item表,两张表均按照order_id分片,绑定表之间的分区键完全相同,则这两张表互为绑定表关系。绑定表之间的多表关联查询不会出现笛卡尔积关联,关联查询效率会显著提升。
例:t_order_1表、t_order_item_1表;t_order_2表、t_order_item_2表。
t_order表和t_order_item互为绑定表查询结果:t_order_1 join t_order_item_1;t_order_2 join t_order_item_2;
t_order表和t_order_item不是互为绑定表查询结果:t_order_1 join t_order_item_1;t_order_2 join t_order_item_2;t_order_1 join t_order_item_2;t_order_2 join t_order_item_1;
(5)、广播表
指所有分片数据源中都存在的表,表结构和表中的数据在每个数据库中均完全一致。适用于数据量不大且需要与海量数据表进行挂链查询的场景,例如:字典表。
(6)、分片键
用于分片的数据库字段,是将数据库/表水平拆分的关键字段。
(7)、分片算法
通过分片算法将数据分片,支持通过=、between和in分片。分片算法需要应用方开发者自行实现,灵活度分厂高。包括:精确分片算法、范围分片算法、符合分片算法等。
例:
精确分片算法:where order_id = ? ;where order_id in(?,?,?)
范围分片算法:where order_id between ? and ?
(8)、分片策略
分片键和分片算法组成分片策略,由于分片算法的独立性,将其独立抽离。内置分片策略分为:尾数取模、哈希、范围、标签、时间等。分片策略常用的表达式为Groovy表达式,如:t_order_$->{order_id%2}。
(9)、自增主键生成策略
通过客户端生成自增主键,以替换数据库原生自增主键的方式,做到分布式主键无重复。
3、配置
(1)、Maven依赖
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>4.0.1</version>
</dependency>
(2)、application.properties配置文件
#定义两个数据源ds0和ds1
spring.shardingsphere.datasource.names = ds0,ds1
#配置ds0数据源
spring.shardingsphere.datasource.ds0.type = com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.ds0.driver-class-name = com.mysql.jdbc.Driver
spring.shardingsphere.datasource.ds0.jdbc-url = jdbc:mysql://127.0.0.1:3306/sob1?serverTimezone=GMT%2B8&useSSL=false&useUnicode=true&characterEncoding=UTF-8
spring.shardingsphere.datasource.ds0.username = root
spring.shardingsphere.datasource.ds0.password = root
#配置ds1数据源
spring.shardingsphere.datasource.ds1.type = com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.ds1.driver-class-name = com.mysql.jdbc.Driver
spring.shardingsphere.datasource.ds1.jdbc-url = jdbc:mysql://127.0.0.2:3306/sob2?serverTimezone=GMT%2B8&useSSL=false&useUnicode=true&characterEncoding=UTF-8
spring.shardingsphere.datasource.ds1.username = root
spring.shardingsphere.datasource.ds1.password = root
#水平分库策略
#对用户ID取模,user_id为偶数该用户的业务数据路由到ds1数据源;user_id为奇数该用户的业务数据路由到ds2数据源。
spring.shardingsphere.sharding.default-database-strategy.inline.sharding-column = user_id
spring.shardingsphere.sharding.default-database-strategy.inline.algorithm-expression = ds$->{user_id % 2}
#绑定表:指分片规则一致的主表和子表。绑定表之间的分区键完全相同,则这两张表互为绑定表关系。
spring.shardingsphere.sharding.binding-tables = t_order, t_order_item
#水平分表策略:t_order表示为逻辑表,t_order_1、t_order_2为物理表。
#数据节点:user表只在ds0的数据源中存在,需要配置数据节点
spring.shardingsphere.sharding.tables.t_user.actual-data-nodes = ds$->{0}.t_user
#分片键:user表只在ds0的数据源中存在
spring.shardingsphere.sharding.tables.t_user.table-strategy.inline.sharding-column = user_id
#分表策略:user表只在ds0的数据源中存在
spring.shardingsphere.sharding.tables.t_user.table-strategy.inline.algorithm-expression = t_user
#数据节点:ds0数据源节点为ds0.t_order_0,ds0.t_order_1;ds1数据源节点为ds1.t_order_0,ds1.t_order_1。
spring.shardingsphere.sharding.tables.t_order.actual-data-nodes = ds$->{0..1}.t_order_$->{0..1}
#分片键:指定order表中的主键order_id(字段名称)为分片键。(order表中字段名为order_id与order_item表中字段order_id统一)
spring.shardingsphere.sharding.tables.t_order.table-strategy.inline.sharding-column = order_id
#分表策略:对order_id取模,order_id为偶数的订单数据路由到t_order_0;order_id为奇数的订单数据路由到t_order_1。
spring.shardingsphere.sharding.tables.t_order.table-strategy.inline.algorithm-expression = t_order_$->{order_id % 2}
#使用SNOWFLAKE算法生成全局主键
spring.shardingsphere.sharding.tables.t_order.key-generator.column = order_id
spring.shardingsphere.sharding.tables.t_order.key-generator.type = SNOWFLAKE
#数据节点:ds0数据源节点为ds0.order_item_0,ds0.order_item_1;ds0数据源节点为ds1.order_item_0,ds1.order_item_1。
spring.shardingsphere.sharding.tables.t_order_item.actual-data-nodes=ds$->{0..1}.t_order_item_$->{0..1}
#分片键,因为order, order_item为绑定关系,所以分片键相同。
spring.shardingsphere.sharding.tables.t_order_item.table-strategy.inline.sharding-column = order_id
#分表策略:对order_id取模,order_id为偶数的订单明细数据路由到t_order_item_0;order_id为奇数的订单明细数据路由到t_order_item_1。
spring.shardingsphere.sharding.tables.t_order_item.table-strategy.inline.algorithm-expression = t_order_item_$->{order_id % 2}
#使用SNOWFLAKE算法生成全局主键
spring.shardingsphere.sharding.tables.t_order_item.key-generator.column = order_item_id
spring.shardingsphere.sharding.tables.t_order_item.key-generator.type = SNOWFLAKE
4、执行过程
(1)、SQL解析
SQL解析过程分为词法解析和语法解析。词法解析器用于将SQL拆解为不可再分的原子符号。并根据不同的数据库方言所提供的字典,将其归类为关键字、表达式、字面量和操作符。再使用语法解析器将SQL转换为抽象语法树。
①、SQL语法:select id, name from user where status = ‘active’ and age > 18
②、解析后的SQL语法树
(2)、SQL路由
SQL路由就是把针对逻辑表的数据操作映射到对数据节点的操作过程。
根据解析上下文匹配数据库和表的分片策略,并生成路由路径。对于携带分片键的SQL,根据分片键操作符不同可以划分为单片路由(分片键的操作符是=)、多片路由(分片键的操作符是in)和范围路由(分片键的操作符是between),不携带分片键的SQL则采用广播路由。根据分片键进行路由的场景可分为:直接路由、标准路由、笛卡尔积路由等。
①、标准路由
标准路由是Sharding-jdbc最为推荐使用的分片方式,它的适用范围是不包括关联查询或仅包含绑定表之间关联查询的SQL。当分片运算符是=时,路由结果将落入单库(表),当分片运算符是between或in时,则路由结果不一定落入唯一的库(表),因此一条逻辑SQL最终可能被拆分为多条用于执行的真实SQL。例:根据order_id取模的奇数和偶数进行数据分片。
②、笛卡尔积路由
笛卡尔积路由是最复杂的情况,它无法根据绑定表的关系定位分片规则,因为非绑定表之间的关联查询需要拆解为笛卡尔积组合执行。笛卡尔积路由查询性能较低,需谨慎使用。
例:order_1表、order_item_1表;order_2表、order_item_2表。
order表和order_item互为绑定表查询结果:order_1 join order_item_1;order_2 join order_item_2;
order表和order_item不是绑定表查询结果:order_1 join order_item_1;order_2 join order_item_2;order_1 join order_item_2;order_2 join order_item_1;
③、全表路由
对于不携带分片键的SQL,则采取广播路由的方式。根据SQL类型划分为:全库表路由、全库路由、全实例路由、单播路由和阻断路由5种类型。其中全库表路由用于处理对数据库中与其逻辑表相关的所有真实表的操作,主要包括不带分片键的DQL(数据查询)和DML(数据操纵),以及DDL(数据定义)等。
(3)、SQL改写
面向逻辑表书写的SQL,并不能直接在真实的数据库中执行,SQL改写用于将逻辑SQL改写为在真实数据库中可以正确执行的SQL。
如果Sharding-jdbc需要在结果归并时获取相应的数据,像group by 和 order by操作,但该数据并未能通过查询的SQL返回。结果归并时,需要根据group by 和 order by的字段进行分组和排序,但如果原始SQL的选择项中并未包含分组项或排序项,则需要对原始SQL进行改写。
原始SQL:select order_id from t_order order by user_id;
改写SQL:select order_id from t_order_1 order by user_id;
(4)、SQL执行
Sharding-JDBC采用一套自动化的执行引擎,负责将路由和改写完成之后的真实SQL安全且高效发送到底层数据源执行。它更关注平衡数据源连接创建以及内存占用所产生的消耗,以及最大限度地合理利用并发等问题。执行引擎的目标是自动化的平衡资源控制与执行效率,他能在以下两种模式自适应切换。
①、内存限制模式
Sharding-JDBC对一次操作所耗费的数据库连接数量不做限制。如果实际执行的SQL需要对某数据库实例中的200张表做操作,则对每张表创建一个新的数据库连接,并通过多线程的方式并发处理,以达成执行效率最大化。面向分析
②、连接限制模式
Sharding-JDBC严格控制对一次操作所耗费的数据库连接数量。如果实际执行SQL需要对某数据库实例中的200张表做操作,那么只会创建唯一的数据库连接,并对200张表串行处理。如果一次操作中的分片散落再不同的数据库,仍然采用多线程处理对不同库的操作,但每个库的每次操作仍然只创建一个唯一的数据库连接。面向事务操作
③、内存限制模式适用于OLAP(联机分析处理)操作,面向查询分析的操作,可以通过放宽数据库连接的限制提升系统的吞吐量;
连接限制模式适用于OLTP(联机事务处理过程)操作,面向事务的操作,严格控制数据库连接,以保证系统数据库资源能够被更多应用所使用。
(5)、结果归并
5、读写分离
(1)、Sharding-JDBC读写分离
读写分离是对于同一时刻有大量并发读操作和较少写操作类型的应用系统来说,将数据库拆分为主库和从库。主库负责处理事务性的增删改操作,从库负责处理查询操作,能够有效的避免由数据更新导致的行锁,使得整个系统的查询性能得到极大的改善。
①、一主多从
一主多从的配置方式,可以将查询请求均匀分散到多个数据副本,提升系统的处理能力。
②、多主多从
多主多从不仅能提升系统的吞吐量,还能够提升系统的可用性。
注:读写分离的数据节点中的数据内容是一致的,而水平分片的每个节点的数据内容却不同。将水平分片和读写分离联合使用,能够更加有效提升系统性能。
Sharding-JDBC读写分离则是根据SQL语义的分析,将读操作和写操作分别路由到主库与从库。它提供透明化读写分离,让使用方尽量像使用同一个数据库一样使用主从数据库集群。
Sharding-JDBC提供一主多从的读写分离配置,可独立使用,也可配合分库分表使用,同一线程且同一数据库连接内,如有写入操作,以后的读操作均从主库读取,用于保证数据的一致性。
Sharding-JDBC不提供主从数据库的数据同步功能。
(2)、MySQL主从同步配置
①、安装好两台MySQL,并且启动
②、登录主库,创建一个用户用于主从复制
③、修改主库配置文件
④、修改从库配置文件
⑤、从库执行SQL将从库关联主库
(3)、application.properties配置文件
#定义两个数据源m0和s0。m0为master,s0为slave
spring.shardingsphere.datasource.names = m0,s0
#配置master数据源
spring.shardingsphere.datasource.m0.type = com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.m0.driver-class-name = com.mysql.jdbc.Driver
spring.shardingsphere.datasource.m0.jdbc-url = jdbc:mysql://127.0.0.1:3306/sob1?useUnicode=true
spring.shardingsphere.datasource.m0.username = root
spring.shardingsphere.datasource.m0.password = root
#配置slave数据源
spring.shardingsphere.datasource.s0.type = com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.s0.driver-class-name = com.mysql.jdbc.Driver
spring.shardingsphere.datasource.s0.jdbc-url = jdbc:mysql://127.0.0.2:3306/sob2?useUnicode=true
spring.shardingsphere.datasource.s0.username = root
spring.shardingsphere.datasource.s0.password = root
#主从数据库的逻辑数据源定义为ds0,并定义主库为m0,从库为s0
spring.shardingsphere.sharding.master-slave-rules.ds0.master-data-source-name = m0
spring.shardingsphere.sharding.master-slave-rules.ds0.slave-data-source-name = s0
#配置数据节点,同步的为主从库中的t_user表
spring.shardingsphere.sharding.tables.t_user.actual-data-nodes = ds0.t_user