Sharding-jdbc分库分表

引入

不断增长的业务数据会对MySQL数据库的存储和访问造成极大的性能影响,由于单台服务器的资源(CPU、磁盘、内存等)总是有限的,所以单台数据库所能承载的数据量、数据处理能力都将遭遇瓶颈。不论是获取连接还是插入或查询都会有极大的性能问题。

MySQL大数据量造成的性能瓶颈解决方案:

  1. 从SQL入手 加入索引 -> 缓存 -> 主从复制/读写分离
  2. 使用缓存,减少对数据库的同步访问
  3. 减少MySQL使用,可以使用HBase、polarDB、TiDB等分布式存储,ES查询
  4. 分库分表

拓展:MySQL数据库分区

分区原理:分区表是由多个相关的底层表实现,存储引擎管理分区的各个底层表和管理普通表一样,只是分区表在各个底层表上各自加上一个相同的索引(分区表要求所有的底层表都必须使用相同的存储引擎)。

分区优点:它对用户屏蔽了sharding的细节,即使查询条件没有sharding column,它也能正常工作(只是这时候性能一般)。

分区缺点:连接数、网络吞吐量等资源都受到单机的限制;并发能力远远达不到互联网高并发的要求。(主要因为虽然每个分区可以独立存储,但是分区表的总入口还是一个MySQL实例)。

适用场景:并发能力要求不高;数据不是海量(分区数有限,存储能力就有限)。

什么情况下分库分表

随着单库单表中的数据量越来越大、数据库的查询QPS越来越高,相应的,对数据库的读写所需要的时间也越来越多。数据库的读写性能可能会成为业务发展的瓶颈,在优化SQL、使用缓存后仍然存在性能问题或者通过读写分离后,主库的写数据库能力仍然有性能瓶颈。这时可以考虑分库分表:

分库还是分表

如果数据库的查询QPS过高,就需要考虑拆库,通过分库来分担单个数据库的连接压力。比如,如果查询QPS为3500,假设单库可以支撑1000个连接数的话,那么就可以考虑拆分成4个库,来分散查询连接压力。

如果单表数据量过大,当数据量超过一定量级后,无论是对于数据查询还是数据更新,在经过索引优化等纯数据库层面的传统优化手段之后,还是可能存在性能问题。这是量变产生了质变。既然数据量很大,那我们就来个分而治之,化整为零。这就产生了分表,把数据按照一定的规则拆分成多张表,来解决单表环境下无法解决的存取性能问题。

简而言之:分库分表就是将单个数据库拆分成多个数据库,将数据离散到多个数据库中,分表同理。分库分表后不仅可以提升性能还能增加MySQL服务的可用性。单库部署情况下,如果数据库宕机,那么故障影响就是100%,而且恢复可能耗时很长。如果我们拆分成2个库,分别部署在不同的机器上,此时其中1个库宕机,那么故障影响就是50%,还有50%的数据可以继续服务。如果我们拆分成4个库,分别部署在不同的机器上,此时其中1个库宕机,那么故障影响就是25%,还有75%的数据可以继续服务,恢复耗时也会很短。当然,我们也不能无限制的拆库,这也是牺牲存储资源来提升性能、可用性的方式,毕竟资源总是有限的。

  • 分库分表技术选择

    分库分表中间件全部可以归结为两大类型:

    CLIENT模式:客户端模式,以jar包形式提供服务,无需额外部署和依赖,相当于增强版的 JDBC 

    PROXY模式:代理模式,单独的一个服务去代理分库分表的功能,需要单独部署提供服务

    CLIENT模式代表有阿里的TDDL,开源社区的sharding-jdbc(sharding-jdbc的3.x版本即sharding-sphere已经支持了proxy模式),架构图如下: 

PROXY模式代表有阿里的cobar,民间组织的MyCat。

无论是CLIENT模式,还是PROXY模式。几个核心的步骤是一样的:SQL解析,重写,路由,执行,结果归并。

MyCat和ShardingSphere的区别

MyCat是proxy模式,sharding-jdbc是client模式(3.x后支持proxy模式)

MyCat

工作流程如如下:

MyCat支持读写分离、数据分片,多数据源管理,需要单独启一个服务,如果数据库特别多的情况下,对MyCat的压力会变大,性能降低。

Apache ShardingSphere

Apache ShardingSphere是一套开源的分布式数据库解决方案组成的生态圈,它由DBC、Proxy和Sidecar(规划中)这3款既能够独立部署,又支持混合部署配合使用的产品组成。  

ShardingSphere-JDBC

定位为轻量级 Java 框架,在 Java 的 JDBC 层提供的额外服务。 它使用客户端直连数据库,以 jar 包形式提供服务,无需额外部署和依赖,可理解为增强版的 JDBC 驱动,完全兼容 JDBC 和各种 ORM 框架。

  • 适用于任何基于 JDBC 的 ORM 框架,如:JPA, Hibernate, Mybatis, Spring JDBC Template 或直接使用 JDBC;
  • 支持任何第三方的数据库连接池,如:DBCP, C3P0, BoneCP, HikariCP 等;
  • 支持任意实现 JDBC 规范的数据库,目前支持 MySQL,PostgreSQL,Oracle,SQLServer 以及任何可使用 JDBC 访问的数据库。

ShardingSphere-Proxy 

定位为透明化的数据库代理端,提供封装了数据库二进制协议的服务端版本,用于完成对异构语言的支持。 目前提供 MySQL 和 PostgreSQL(兼容 openGauss 等基于 PostgreSQL 的数据库)版本,它可以使用任何兼容 MySQL/PostgreSQL 协议的访问客户端(如:MySQL Command Client, MySQL Workbench, Navicat 等)操作数据,对 DBA 更加友好。

  • 向应用程序完全透明,可直接当做 MySQL/PostgreSQL 使用;
  • 适用于任何兼容 MySQL/PostgreSQL 协议的的客户端。

分库分表的方式

分库分表有两种方式:垂直切分和水平切分。

垂直拆分:垂直分表和垂直分库

例如:原来是很多模块公用一个数据库资源, 经过垂直分库之后, 我们的商品模块, 订单模式, 用户模块等使用了单独自己的数据资源,各个模块项目资源的竞争就不存在了

垂直拆分的好处:

  1. 减轻了数据库的压力
  2. 每个数据库分摊数据,提高了查询的效率
  3. 我们每个数据库访问相应的CPU 内存,网络压力变小
  4. 业务更加清晰
  5. 解耦,系统扩展容易

垂直拆分的不足:

  1. 系统的复杂性增加了
  2. 多个数据库数据表联查是不是增加了复杂性
  3. 事务处理也变得麻烦了
  4. 单表数据量很大(垂直拆分也解决不了)

水平拆分:水平分表和水平分库

水平拆分的不足

  1. 拆分过程复杂
  2. 事务处理复杂(MQ最终一致性)
  3. 多库多表联合查询难度复杂,join、count、orderby等
  4. 水平拆分后单表的数据会分散到不同的数据源中(多数据源管理问题)

分库分表的算法

当热点数据出现后,怎么避免热点数据集中存取到某个特定库/表,造成各分库分表读写压力不均的问题。所以分库分表的算法需要支持负载均衡,主要有以下算法

  1. Hash取模:hash(paritition key) % 分库/分表数量 ;缺点:后期扩容、数据迁移不方便,每次扩容按照2的倍数扩容,此时需要迁移50%数据。
  2. 一致性哈希:类似Redis槽(先分组)分配给不同的节点,分布根据均匀,扩容容易,增加分库/分表时最多迁移1/N数据,N为分库或分表的数量,不受2的倍数限制,缺点:算法实现相对复杂,通过虚拟节点等手段避免数据倾斜。

分库分表ID问题

分库分表后无法在使用表的主键自增(可能重复),需要使用第三方的唯一ID作为数据库主键,可以使用UUID、雪花算法或者数据库+本地缓存处理唯一ID问题。

分库分表后的事务

分库后,一个事务可能跨越多个数据库,无法利用数据库的事务管理器完成事务,可以通过MQ可靠消息来实现最终一致性,对于强一致的请求可以使用数据库自带的XA协议来保证,但是效率可能较低。

数据库事务需要满足 ACID(原子性、一致性、隔离性、持久性)四个特性。

  • 原子性(Atomicity)指事务作为整体来执行,要么全部执行,要么全不执行;
  • 一致性(Consistency)指事务应确保数据从一个一致的状态转变为另一个一致的状态;
  • 隔离性(Isolation)指多个事务并发执行时,一个事务的执行不应影响其他事务的执行;
  • 持久性(Durability)指已提交的事务修改数据会被持久保存。

在单一数据节点中,事务仅限于对单一数据库资源的访问控制,称之为本地事务。 几乎所有的成熟的关系型数据库都提供了对本地事务的原生支持。 但是在基于微服务的分布式应用环境下,越来越多的应用场景要求对多个服务的访问及其相对应的多个数据库资源能纳入到同一个事务当中,分布式事务应运而生。

关系型数据库虽然对本地事务提供了完美的 ACID 原生支持。 但在分布式的场景下,它却成为系统性能的桎梏。 如何让数据库在分布式场景下满足 ACID 的特性或找寻相应的替代方案,是分布式事务的重点工作。

本地事务

在不开启任何分布式事务管理器的前提下,让每个数据节点各自管理自己的事务。 它们之间没有协调以及通信的能力,也并不互相知晓其他数据节点事务的成功与否。 本地事务在性能方面无任何损耗,但在强一致性以及最终一致性方面则力不从心。

两阶段提交

XA协议最早的分布式事务模型是由 X/Open 国际联盟提出的 X/Open Distributed Transaction Processing (DTP) 模型,简称 XA 协议。

基于XA协议实现的分布式事务对业务侵入很小。 它最大的优势就是对使用方透明,用户可以像使用本地事务一样使用基于XA协议的分布式事务。 XA协议能够严格保障事务 ACID 特性。

严格保障事务 ACID 特性是一把双刃剑。 事务执行在过程中需要将所需资源全部锁定,它更加适用于执行时间确定的短事务。 对于长事务来说,整个事务进行期间对数据的独占,将导致对热点数据依赖的业务系统并发性能衰退明显。 因此,在高并发的性能至上场景中,基于 XA 协议的分布式事务并不是最佳选择。

柔性事务

如果将实现了 ACID 的事务要素的事务称为刚性事务的话,那么基于 BASE 事务要素的事务则称为柔性事务。 BASE 是基本可用、柔性状态和最终一致性这三个要素的缩写。

  • 基本可用(Basically Available)保证分布式事务参与方不一定同时在线;
  • 柔性状态(Soft state)则允许系统状态更新有一定的延时,这个延时对客户来说不一定能够察觉;
  • 最终一致性(Eventually consistent)通常是通过消息传递的方式保证系统的最终一致性。

在 ACID 事务中对隔离性的要求很高,在事务执行过程中,必须将所有的资源锁定。 柔性事务的理念则是通过业务逻辑将互斥锁操作从资源层面上移至业务层面。 通过放宽对强一致性要求,来换取系统吞吐量的提升。

基于 ACID 的强一致性事务和基于 BASE 的最终一致性事务都不是银弹,只有在最适合的场景中才能发挥它们的最大长处。 可通过下表详细对比它们之间的区别,以帮助开发者进行技术选型。

本地事务两(三)阶段事务柔性事务
业务改造实现相关接口
一致性不支持支持最终一致
隔离性不支持支持业务方保证
并发性能无影响严重衰退略微衰退
适合场景业务方处理不一致短事务 & 低并发长事务 & 高并发

XA 事务

两阶段事务提交采用的是 X/OPEN 组织所定义的 DTP 模型所抽象的 AP(应用程序), TM(事务管理器)和 RM(资源管理器) 概念来保证分布式事务的强一致性。 其中 TM 与 RM 间采用 XA 的协议进行双向通信。 与传统的本地事务相比,XA 事务增加了准备阶段,数据库除了被动接受提交指令外,还可以反向通知调用方事务是否可以被提交。 TM 可以收集所有分支事务的准备结果,并于最后进行原子提交,以保证事务的强一致性。

Java 通过定义 JTA 接口实现了 XA 模型,JTA 接口中的 ResourceManager 需要数据库厂商提供 XA 驱动实现, TransactionManager 则需要事务管理器的厂商实现,传统的事务管理器需要同应用服务器绑定,因此使用的成本很高。 而嵌入式的事务管器可以通过 jar 形式提供服务,同 Apache ShardingSphere 集成后,可保证分片后跨库事务强一致性。

通常,只有使用了事务管理器厂商所提供的 XA 事务连接池,才能支持 XA 的事务。 Apache ShardingSphere 在整合 XA 事务时,采用分离 XA 事务管理和连接池管理的方式,做到对应用程序的零侵入。

支持项

  • 支持数据分片后的跨库事务;
  • 两阶段提交保证操作的原子性和数据的强一致性;
  • 服务宕机重启后,提交/回滚中的事务可自动恢复;
  • 支持同时使用 XA 和非 XA 的连接池。

不支持项

  • 服务宕机后,在其它机器上恢复提交/回滚中的数据。

Sharding-jdbc分库分表实战

官网参考:分片 :: ShardingSphere

在知道什么情况下分库分表后,我们需要知道如何去分库分表,一般一个项目中,已经按照不同模块进行拆分了,模块粒度上分为不同的数据库,比如:订单数据库,用户数据库,这类的拆分可以看出垂直分库,在订单模块中,根据业务的数据量可能会对订单库在继续水平水库分表,比如每天产生订单数据500万条,一个月单表的数据量就会达到30*500w = 1.5亿,这种是无法接受的,可以对数据库水平拆分成4个库,每个库每天1个表。

sharding-jdbc的使用比较简单,主要就是根据业务来配置分库分表的数据源(可以实现读写分离)、分库分表策略算法,具体如下:

引入maven依赖

<dependency>
    <groupId>org.apache.shardingsphere</groupId>
    <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
    <version>4.0.0-RC1</version>
  </dependency>

application.properties配置数据源和分库、分表策略(分片策略)

#配置数据源别名, 4主4从
spring.shardingsphere.datasource.names=m0,s0,m1,s1,m2,s2,m3,s3

spring.shardingsphere.sharding.master-slave-rules.ds0.master-data-source-name=m0
spring.shardingsphere.sharding.master-slave-rules.ds0.slave-data-source-names=s0

spring.shardingsphere.sharding.master-slave-rules.ds1.master-data-source-name=m1
spring.shardingsphere.sharding.master-slave-rules.ds1.slave-data-source-names=s1

spring.shardingsphere.sharding.master-slave-rules.ds2.master-data-source-name=m2
spring.shardingsphere.sharding.master-slave-rules.ds2.slave-data-source-names=s2

spring.shardingsphere.sharding.master-slave-rules.ds3.master-data-source-name=m3
spring.shardingsphere.sharding.master-slave-rules.ds3.slave-data-source-names=s3

# 一个实体类对应两张表,覆盖
spring.main.allow-bean-definition-overriding=true

# 0号数据源Master
spring.shardingsphere.datasource.m0.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.m0.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.m0.jdbc-url=jdbc:mysql://129.23.23.111:3306/cust_order_00?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&allowMultiQueries=true&useSSL=false
spring.shardingsphere.datasource.m0.username=cust_order
spring.shardingsphere.datasource.m0.password=q02fxLKe4EOt22VG
spring.shardingsphere.datasource.m0.maxActive =10
spring.shardingsphere.datasource.m0.initialSize=10
spring.shardingsphere.datasource.m0.maxWait=30000
spring.shardingsphere.datasource.m0.minIdle=10

# 0号数据源Slave
spring.shardingsphere.datasource.s0.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.s0.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.s0.jdbc-url=jdbc:mysql://129.8.24.121:3306/cust_order_00?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&allowMultiQueries=true&useSSL=false
spring.shardingsphere.datasource.s0.username=cust_order
spring.shardingsphere.datasource.s0.password=q02fxLKe4EOt22VG
spring.shardingsphere.datasource.s0.maxActive =10
spring.shardingsphere.datasource.s0.initialSize=10
spring.shardingsphere.datasource.s0.maxWait=30000
spring.shardingsphere.datasource.s0.minIdle=10

# 1号数据源Master
spring.shardingsphere.datasource.m1.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.m1.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.m1.jdbc-url=jdbc:mysql://129.34.23.112:3306/cust_order_01?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&allowMultiQueries=true&useSSL=false
spring.shardingsphere.datasource.m1.username=cust_order spring.shardingsphere.datasource.m1.password=q02fxLKe4EOt22VG
spring.shardingsphere.datasource.m1.maxActive =10
spring.shardingsphere.datasource.m1.initialSize=10
spring.shardingsphere.datasource.m1.maxWait=30000
spring.shardingsphere.datasource.m1.minIdle=10


# 1号数据源Slave
spring.shardingsphere.datasource.s1.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.s1.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.s1.jdbc-url=jdbc:mysql://129.8.24.121:3306/cust_order_01?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&allowMultiQueries=true&useSSL=false
spring.shardingsphere.datasource.s1.username=cust_order
spring.shardingsphere.datasource.s1.password=q02fxLKe4EOt22VG
spring.shardingsphere.datasource.s1.maxActive =10
spring.shardingsphere.datasource.s1.initialSize=10
spring.shardingsphere.datasource.s1.maxWait=30000
spring.shardingsphere.datasource.s1.minIdle=10

# 2号数据源Master
spring.shardingsphere.datasource.m2.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.m2.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.m2.jdbc-url=jdbc:mysql://129.13.22.221:3306/cust_order_02?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&allowMultiQueries=true&useSSL=false
spring.shardingsphere.datasource.m2.username=cust_order spring.shardingsphere.datasource.m2.password=q02fxLKe4EOt22VG
spring.shardingsphere.datasource.m2.maxActive =10
spring.shardingsphere.datasource.m2.initialSize=10
spring.shardingsphere.datasource.m2.maxWait=30000
spring.shardingsphere.datasource.m2.minIdle=10



# 2号数据源Slave
spring.shardingsphere.datasource.s2.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.s2.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.s2.jdbc-url=jdbc:mysql://129.8.24.121:3306/cust_order_02?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&allowMultiQueries=true&useSSL=false
spring.shardingsphere.datasource.s2.username=cust_order
spring.shardingsphere.datasource.s2.password=q02fxLKe4EOt22VG
spring.shardingsphere.datasource.s2.maxActive =10
spring.shardingsphere.datasource.s2.initialSize=10
spring.shardingsphere.datasource.s2.maxWait=30000
spring.shardingsphere.datasource.s2.minIdle=10


# 3号数据源Master
spring.shardingsphere.datasource.m3.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.m3.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.m3.jdbc-url=jdbc:mysql://129.11.21.112:3306/cust_order_03?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&allowMultiQueries=true&useSSL=false
spring.shardingsphere.datasource.m3.username=cust_order
spring.shardingsphere.datasource.m3.password=q02fxLKe4EOt22VG
spring.shardingsphere.datasource.m3.maxActive =10
spring.shardingsphere.datasource.m3.initialSize=10
spring.shardingsphere.datasource.m3.maxWait=30000
spring.shardingsphere.datasource.m3.minIdle=10


# 3号数据源Slave
spring.shardingsphere.datasource.s3.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.s3.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.s3.jdbc-url=jdbc:mysql://129.8.24.121:3306/fin_cust_account_03?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&allowMultiQueries=true&useSSL=false
spring.shardingsphere.datasource.s3.username=cust_order
spring.shardingsphere.datasource.s3.password=q02fxLKe4EOt22VG
spring.shardingsphere.datasource.s3.maxActive =10
spring.shardingsphere.datasource.s3.initialSize=10
spring.shardingsphere.datasource.s3.maxWait=30000
spring.shardingsphere.datasource.s3.minIdle=10

#分库策略:不同表不同分库策略
## t_keep_acct_detail 表 根据request_no分库 ##
spring.shardingsphere.sharding.tables.t_keep_acct_detail.database-strategy.standard.sharding-column=request_no
spring.shardingsphere.sharding.tables.t_keep_acct_detail.database-strategy.standard.precise-algorithm-class-name=com.payermax.fintech.cust.account.application.shardingjdbc.RequestNoShardingAlgorithm

## t_keep_acct_log 表 根据request_no分库 ##
spring.shardingsphere.sharding.tables.t_keep_acct_log.database-strategy.standard.sharding-column=request_no
spring.shardingsphere.sharding.tables.t_keep_acct_log.database-strategy.standard.precise-algorithm-class-name=com.payermax.fintech.cust.account.application.shardingjdbc.RequestNoShardingAlgorithm

## t_mer_acct_balance 表 根据member_Id分库 ##
spring.shardingsphere.sharding.tables.t_mer_acct_balance.database-strategy.standard.sharding-column=member_id
spring.shardingsphere.sharding.tables.t_mer_acct_balance.database-strategy.standard.precise-algorithm-class-name=com.payermax.fintech.cust.account.application.shardingjdbc.MemberIdShardingAlgorithm

## t_mer_acct_detail_log 表 根据member_Id分库 ##
spring.shardingsphere.sharding.tables.t_mer_acct_detail_log.database-strategy.standard.sharding-column=member_id
spring.shardingsphere.sharding.tables.t_mer_acct_detail_log.database-strategy.standard.precise-algorithm-class-name=com.payermax.fintech.cust.account.application.shardingjdbc.MemberIdShardingAlgorithm


## 默认0库 ##
spring.shardingsphere.sharding.tables.t_cutoff_date_info.actual-data-nodes=ds0.t_cutoff_date_info
spring.shardingsphere.sharding.tables.t_cutoff_task_log.actual-data-nodes=ds0.t_cutoff_task_log
spring.shardingsphere.sharding.tables.t_param_config.actual-data-nodes=ds0.t_param_config


#分表策略:针对记账请求和明细数据按照时间维度进行分表
spring.shardingsphere.sharding.tables.t_keep_acct_detail.actual-data-nodes=ds$->{0..3}.t_keep_acct_detail_${['0','1','2','3','4','5','6','7']}
spring.shardingsphere.sharding.tables.t_keep_acct_detail.table-strategy.standard.sharding-column=request_no
spring.shardingsphere.sharding.tables.t_keep_acct_detail.table-strategy.standard.precise-algorithm-class-name=com.payermax.fintech.cust.account.application.shardingjdbc.RequestNoShardingAlgorithm

spring.shardingsphere.sharding.tables.t_keep_acct_log.actual-data-nodes=ds$->{0..3}.t_keep_acct_log_${['0','1','2','3','4','5','6','7']}
spring.shardingsphere.sharding.tables.t_keep_acct_log.table-strategy.standard.sharding-column=request_no
spring.shardingsphere.sharding.tables.t_keep_acct_log.table-strategy.standard.precise-algorithm-class-name=com.payermax.fintech.cust.account.application.shardingjdbc.RequestNoShardingAlgorithm

spring.shardingsphere.sharding.tables.t_mer_acct_detail_log.actual-data-nodes=ds$->{0..3}.t_mer_acct_detail_log_${['01','02','03','04','05','06','07','08','09','10','11','12','13','14','15','16','17','18','19','20','21','22','23','24','25','26','27','28','29','30','31']}
spring.shardingsphere.sharding.tables.t_mer_acct_detail_log.table-strategy.standard.sharding-column=request_time
spring.shardingsphere.sharding.tables.t_mer_acct_detail_log.table-strategy.standard.precise-algorithm-class-name=com.payermax.fintech.cust.account.application.shardingjdbc.DayShardingAlgorithm
  • 分库分表的粒度是每个表,即针对每个表进行分库或者分表或者分库+分表,当然也可以配置某个表在某个固定的库中(不分库)和固定的表中(不分表)
  • 查询或者插入的时候必须指定分片键,否则将执行全路由,会全库查询或者插入,性能较差
  • 配置读写分离后,所有读操作都会操作读库,如果此时使用行锁select ..for update 也会读库,导致锁失效,超时等问题,解决方案如下:
    •  try(HintManager hintManager = HintManager.getInstance()){
                  hintManager.setMasterRouteOnly();
                  po = getBaseMapper().lockById(acctNo, memberId);
              }catch (Exception e){
                  log.error("getByIdLock error", e);
                  throw new BizException("get Lock fail to update accountBalance");
              }
  • 配置中使用 ${ expression } 或 $->{ expression } 标识行表达式即可。 目前支持数据节点和分片算法这两个部分的配置。 行表达式的内容使用的是 Groovy 的语法,Groovy 能够支持的所有操作,行表达式均能够支持。如下,行表达式中如果出现连续多个 ${ expression } 或 $->{ expression } 表达式,整个表达式最终的结果将会根据每个子表达式的结果进行笛卡尔组合。如上配置

    • ${begin..end} 表示范围区间
    • ${[unit1, unit2, unit_x]} 表示枚举值

名词解释:

广播表:一个表可以存在所有的分片数据源中,结构及其数据在每个数据库中均完全一致。 适用于数据量不大且需要与海量数据的表进行关联查询的场景,例如:字典表,配置如下:

# 公共表配置
spring.shardingsphere.sharding.broadcast-tables=t_udict
# 配置主键的生成策略
spring.shardingsphere.sharding.tables.t_udict.key-generator.column=dictid
spring.shardingsphere.sharding.tables.t_udict.key-generator.type=SNOWFLAKE

单表:指所有的分片数据源中仅唯一存在的表。 适用于数据量不大且无需分片的表

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

分片算法

分片算法:用于将数据分片的算法,支持 =、>=、<=、>、<、BETWEEN 和 IN 进行分片。 分片算法可由开发者自行实现,常见的有取模、哈希、范围、时间等常用分片算法。

自定义分片算法

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

  • 精确分片算法

对应PreciseShardingAlgorithm,用于处理使用单一键作为分片键的=与IN进行分片的场景。需要配合StandardShardingStrategy使用。

  • 范围分片算法

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

  • 复合分片算法

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

  • Hint分片算法

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

分片策略(数据源分片策略和表分片策略)

包含分片键和分片算法,由于分片算法的独立性,将其独立抽离。真正可用于分片操作的是分片键 + 分片算法,也就是分片策略。目前提供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。不分片的策略

SQL Hint

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

自定义分片策略

实现ShardingAlgorithm相关接口或者继承相关类,重写doSharding方法即可

@Slf4j
public class DayShardingAlgorithm implements PreciseShardingAlgorithm<Date> {

      //第一个参数 为所有库或者表的集合
      //第二个参数 为当前为分片键的值


    @Override
    public String doSharding(Collection<String> tables, PreciseShardingValue<Date> shardingValue) {
        Date date = shardingValue.getValue();
        String day = new SimpleDateFormat("dd").format(date);
        log.info("select table,date:{}", date);
        Optional<String> option = tables.stream().filter(table -> table.contains(day)).findAny();
        if(option.isPresent()){
            log.info("select table {}, requestDate:{}", option.get(), date);
            return option.get();
        } else {
            log.error("can not find table, requestDate :{}", date);
            throw new UnsupportedOperationException();
        }
    }
}

Sharding-jdbc使用规范

分库分表策略支持以下几种形式:

路由至单数据节点

  • 100%全兼容(目前仅MySQL,其他数据库完善中)。

路由至多数据节点

全面支持DML、DDL、DCL、TCL和部分DAL。支持分页、去重、排序、分组、聚合、关联查询(不支持跨库关联)。比如: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}]


[DISTINCT] COLUMN_NAME [AS] [alias] | 
(MAX | MIN | SUM | AVG)(COLUMN_NAME | alias) [AS] [alias] | 
COUNT(* | COLUMN_NAME | alias) [AS] [alias]

tbl_name [AS] alias] [index_hint_list]
| table_reference ([INNER] | {LEFT|RIGHT} [OUTER]) JOIN table_factor [JOIN ON conditional_expr | USING (column_list)]

以下形式不支持

不支持路由至多数据节点,同理不支持包含schema的SQL。

不支持CASE WHEN、HAVING、UNION (ALL),有限支持子查询,

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

由于归并的限制,子查询中包含聚合函数目前无法支持。如下:

#支持
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 = ?))

不支持对分片键进行操作,如运算表达式和函数分片键会导致全路由

强制分片路由:

通过解析SQL语句提取分片键列与值并进行分片是ShardingSphere对SQL零侵入的实现方式。若SQL语句中没有分片条件,则无法进行分片,需要全路由。在一些应用场景中,分片条件并不存在于SQL,而存在于外部业务逻辑。因此需要提供一种通过外部指定分片结果的方式,在ShardingSphere中叫做Hint。Hint方式主要使用场景:

1.分片字段不存在SQL中、数据库表结构中,而存在于外部业务逻辑。

2.强制在主库进行某些数据操作。

ShardingSphere使用ThreadLocal管理分片键值,可以通过编程的方式向 HintManager 中添加分片条件,该分片条件仅在当前线程内生效。除了通过编程的方式使用强制分片路由,Apache ShardingSphere 还可以通过 SQL 中的特殊注释的方式引用 Hint,使开发者可以采用更加透明的方式使用该功能。

指定了强制分片路由的 SQL 将会无视原有的分片逻辑,直接路由至指定的真实数据节点。

基于暗示(Hint)的数据分片

配置Hint分片算法

Hint分片算法需要用户实现org.apache.shardingsphere.api.sharding.hint.HintShardingAlgorithm接口。ShardingSphere在进行Routing时,如果发现LogicTable的TableRule采用了 Hint的分片算法,将会从HintManager中获取分片值进行路由操作。

参考配置如下:

shardingRule:
  tables:
   t_order:
        actualDataNodes: demo_ds_${0..1}.t_order_${0..1}
        databaseStrategy:
          hint:
            algorithmClassName: org.apache.shardingsphere.userAlgo.HintAlgorithm
        tableStrategy:
          hint:
            algorithmClassName: org.apache.shardingsphere.userAlgo.HintAlgorithm
  defaultTableStrategy:
    none:
  defaultKeyGenerator:
    type: SNOWFLAKE
    column: order_id
props:
    sql.show: true

获取HintManager

HintManager hintManager = HintManager.getInstance();

添加分片键值

  • 使用hintManager.addDatabaseShardingValue来添加数据源分片键值。
  • 使用hintManager.addTableShardingValue来添加表分片键值。

分库不分表情况下,强制路由至某一个分库时,可使用hintManager.setDatabaseShardingValue方式添加分片。通过此方式添加分片键值后,将跳过SQL解析和改写阶段,从而提高整体执行效率。

清除分片键值

分片键值保存在ThreadLocal中,所以需要在操作结束时调用hintManager.close()来清除ThreadLocal中的内容。

hintManager实现了AutoCloseable接口,可推荐使用try with resource自动关闭。

完整代码示例

// Sharding database and table with using hintManager.
        String sql = "SELECT * FROM t_order";
        try (HintManager hintManager = HintManager.getInstance();
             Connection conn = dataSource.getConnection();
             PreparedStatement preparedStatement = conn.prepareStatement(sql)) {
            hintManager.addDatabaseShardingValue("t_order", 1);
            hintManager.addTableShardingValue("t_order", 2);
            try (ResultSet rs = preparedStatement.executeQuery()) {
                while (rs.next()) {
                    // ...
                }
            }
        }

// Sharding database without sharding table and routing to only one database with using hintManger.
        String sql = "SELECT * FROM t_order";
        try (HintManager hintManager = HintManager.getInstance();
             Connection conn = dataSource.getConnection();
             PreparedStatement preparedStatement = conn.prepareStatement(sql)) {
            hintManager.setDatabaseShardingValue(3);
            try (ResultSet rs = preparedStatement.executeQuery()) {
                while (rs.next()) {
                    // ...
                }
            }
        }

基于暗示(Hint)的强制主库路由

获取HintManager

与基于暗示(Hint)的数据分片相同。

设置主库路由

  • 使用hintManager.setMasterRouteOnly设置主库路由。

清除分片键值

与基于暗示(Hint)的数据分片相同。

完整代码示例

String sql = "SELECT * FROM t_order";
try (
        HintManager hintManager = HintManager.getInstance();
        Connection conn = dataSource.getConnection();
        PreparedStatement preparedStatement = conn.prepareStatement(sql)) {
    hintManager.setMasterRouteOnly();
    try (ResultSet rs = preparedStatement.executeQuery()) {
        while (rs.next()) {
            // ...
        }
    }
}

读写分离

主库:添加、更新以及删除数据操作所使用的数据库,目前仅支持单主库。

从库:查询数据操作所使用的数据库,可支持多从库。

主从同步:将主库的数据异步的同步到从库的操作。由于主从同步的异步性,从库与主库的数据会短时间内不一致。

主从同步原理:通过binlog

负载均衡策略

通过负载均衡策略将查询请求疏导至不同从库。

  • 2
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值