针对分库分表的理论及框架中间件介绍 sharding sphere、mycat
具体实战内容
{{Sharding JDBC 实战}}
{{Sharding Proxy 实战}}
{{Mycat 实战}}
GitHub 代码:lane-mysql-42
前言 背景介绍
背景描述
刚开始我们的系统只用了单机数据库
随着用户的不断增多,考虑到系统的高可用和越来越多的用户请求,我们开始使用数据库主从架构
当用户量级和业务进一步提升后,写请求越来越多,这时我们开始使用了分库分表
遇到的问题
用户请求量太大
单服务器 TPS、内存、IO 都是有上限的,需要将请求打散分布到多个服务器
单库数据量太大
单个数据库处理能力有限;单库所在服务器的磁盘空间有限;单库上的操作 IO 有瓶颈
单表数据量太大
查询、插入、更新操作都会变慢,在加字段、加索引、机器迁移都会产生高负载,影响服务
如何解决
垂直拆分
垂直分库
微服务架构时,业务切割得足够独立,数据也会按照业务切分,保证业务数据隔离,大大提 升了数据库的吞吐能力
垂直分表
表中字段太多且包含大字段的时候,在查询时对数据库的 IO、内存会受到影响,同时更新数据时,产生的 binlog 文件会很大,MySQL 在主从同步时也会有延迟的风险
水平拆分
水平分表
针对数据量巨大的单张表(比如订单表),按照规则把一张表的数据切分到多张表里面去。 但是这些表还是在同一个库中,所以库级别的数据库操作还是有 IO 瓶颈。
水平分库
将单张表的数据切分到多个服务器上去,每个服务器具有相应的库与表,只是表中数据集合不同。 水平分库分表能够有效的缓解单机和单库的性能瓶颈和压力,突破 IO、连接数、硬件 资源等的瓶颈
水平分库规则
不跨库、不跨表,保证同一类的数据都在同一个服务器上面。
数据在切分之前,需要考虑如何高效的进行数据获取,如果每次查询都要跨越多个节点,就需要谨 慎使用。
水平分表规则
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 为分片键
主键选择
UUID:本地生成,不依赖数据库,缺点就是作为主键性能太差
SNOWFLAKE:百度 UidGenerator、美团 Leaf、基于 SNOWFLAKE 算法实现
数据一致性
强一致性:XA 协议
最终一致性:TCC、Saga、Seata
数据库扩容
成倍增加数据节点,实现平滑扩容
成倍扩容以后,表中的部分数据请求已被路由到其他节点上面,可以清理掉
业务层改造
基于代理层方式:Mycat、Sharding-Proxy、MySQL Proxy
基于应用层方式:Sharding-jdbc
分库后面临的问题
事务问题:一次投递需要插入两条记录,且分布在不同的服务器上,数据需要保障一致性。
跨库跨表的 join 问题
- 全局表(字典表):基础数据/配置数据,所有库都拷贝一份
- 字段冗余:可以使用字段冗余就不用 join 查询了
- 系统层组装:可以在业务层分别查询出来,然后组装起来,逻辑较复杂
额外的数据管理负担和数据运算压力:数据库扩容、维护成本变高
第 1 节 ShardingSphere 实战
1.1 ShardingSphere
Apache ShardingSphere 是一款开源的分布式数据库中间件组成的生态圈。它由 Sharding-JDBC、 Sharding-Proxy 和 Sharding-Sidecar(规划中)这 3 款相互独立的产品组成。 他们均提供标准化的数据 分片、分布式事务和数据库治理功能,可适用于如 Java 同构、异构语言、容器、云原生等各种多样化的 应用场景。
ShardingSphere 项目状态如下:
ShardingSphere 定位为关系型数据库中间件,旨在充分合理地在分布式的场景下利用关系型数据库的 计算和存储能力,而并非实现一个全新的关系型数据库。
Sharding-JDBC:被定位为轻量级 Java 框架,在 Java 的 JDBC 层提供的额外服务,以 jar 包形式使用。
Sharding-Proxy:被定位为透明化的数据库代理端,提供封装了数据库二进制协议的服务端版 本,用于完成对异构语言的支持。
Sharding-Sidecar:被定位为 Kubernetes 或 Mesos 的云原生数据库代理,以 DaemonSet 的形式代 理所有对数据库的访问。
Sharding-JDBC、Sharding-Proxy 和 Sharding-Sidecar 三者区别如下:
ShardingSphere 安装包下载:https://shardingsphere.apache.org/document/current/cn/downloads/
使用 Git 下载工程:git clone https://github.com/apache/incubator-shardingsphere.git
1.2 Sharding-JDBC
Sharding-JDBC 定位为轻量级 Java 框架,在 Java 的 JDBC 层提供的额外服务。 它使用客户端直连数据库, 以 jar 包形式提供服务,无需额外部署和依赖,可理解为增强版的 JDBC 驱动,完全兼容 JDBC 和各种 ORM 框架的使用。
适用于任何基于 Java 的 ORM 框架,如:JPA, Hibernate, Mybatis, Spring JDBC Template 或直接使 用 JDBC。
基于任何第三方的数据库连接池,如:DBCP, C3P0, BoneCP, Druid, HikariCP 等。
支持任意实现 JDBC 规范的数据库。目前支持 MySQL,Oracle,SQLServer 和 PostgreSQL。
Sharding-JDBC 主要功能:
数据分片
- 分库、分表
- 读写分离
- 分片策略
- 分布式主键
分布式事务
- 标准化的事务接口
- XA 强一致性事务
- 柔性事务
数据库治理
- 配置动态化
- 编排和治理
- 数据脱敏
- 可视化链路追踪
Sharding-JDBC 内部结构
图中黄色部分表示的是 Sharding-JDBC 的入口 API,采用工厂方法的形式提供。 目前有 ShardingDataSourceFactory 和 MasterSlaveDataSourceFactory 两个工厂类。
ShardingDataSourceFactory 支持分库分表、读写分离操作
MasterSlaveDataSourceFactory 支持读写分离操作
图中蓝色部分表示的是 Sharding-JDBC 的配置对象,提供灵活多变的配置方式。ShardingRuleConfifiguration 是分库分表配置的核心和入口,它可以包含多个 TableRuleConfifiguration 和 MasterSlaveRuleConfifiguration。
TableRuleConfifiguration 封装的是表的分片配置信息,有 5 种配置形式对应不同的 Configuration 类型。
MasterSlaveRuleConfifiguration 封装的是读写分离配置信息。
图中红色部分表示的是内部对象,由 Sharding-JDBC 内部使用,应用开发者无需关注。
ShardingJDBC 通过 ShardingRuleConfiguration 和 MasterSlaveRuleConfiguration 生成真正供 ShardingDataSource 和 MasterSlaveDataSource 使用的规则对象。
ShardingDataSource 和 MasterSlaveDataSource 实现了 DataSource 接口,是 JDBC 的完整实现方案。
Sharding-JDBC 初始化流程
由配置信息生成 rule 然后结合 datasource 提供接口 factory
- 根据配置的信息生成 Configuration 对象
- 通过 Factory 会将 Configuration 对象转化为 Rule 对象
- 通过 Factory 会将 Rule 对象与 DataSource 对象封装
- Sharding-JDBC 使用 DataSource 进行分库分表和读写分离操作
Sharding-JDBC 使用过程
引入 maven 依赖
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-core</artifactId>
<version>${latest.release.version}</version>
</dependency>
注意: 请将 ${latest.release.version}更改为实际的版本号。
规则配置
Sharding-JDBC 可以通过 Java,YAML,Spring 命名空间和 Spring Boot Starter 四种方式配置,开 发者可根据场景选择适合的配置方式。
创建 DataSource
通过 ShardingDataSourceFactory 工厂和规则配置对象获取 ShardingDataSource,然后即可通过 DataSource 选择使用原生 JDBC 开发,或者使用 JPA, MyBatis 等 ORM 工具。
DataSource dataSource = ShardingDataSourceFactory
.createDataSource(dataSourceMap, shardingRuleConfig, props);
1.3 数据分片剖析实战
1.3.1 核心概念
表概念
真实表
数据库中真实存在的物理表。例如 b_order0、b_order1
逻辑表
在分片之后,同一类表结构的名称(总成)。例如 b_order。
数据节点
在分片之后,由数据源和数据表组成。例如 ds0.b_order1
绑定表
指的是分片规则一致的关系表(主表、子表),例如 b_order 和 b_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);
如果不配置绑定表关系,采用笛卡尔积关联,会生成 4 个 SQL 查询表 00、01、10、11
select * from b_order0 o join b_order_item0 i
on(o.order_id=i.order_id) where o.order_id in (10,11);
select * from b_order0 o join b_order_item1 i
on(o.order_id=i.order_id) where o.order_id in (10,11);
select * from b_order1 o join b_order_item0 i
on(o.order_id=i.order_id) where o.order_id in (10,11);
select * from b_order1 o join b_order_item1 i
on(o.order_id=i.order_id) where o.order_id in (10,11);
如果配置绑定表关系,生成 2 个 SQL 查询表 00、11
select * from b_order0 o join b_order_item0 i
on(o.order_id=i.order_id) where o.order_id in (10,11);
select * from b_order1 o join b_order_item1 i on(o.order_id=i.order_id)
where o.order_id in (10,11);
广播表
在使用中,有些表没必要做分片,例如字典表、省份信息等,因为他们数据量不大,而且这种表可能需要与海量数据的表进行关联查询。广播表会在不同的数据节点上进行重复存储,存储的表结构和数据完全相同。
分片算法(ShardingAlgorithm)
由于分片算法和业务实现紧密相关,因此并未提供内置分片算法,而是通过分片策略将各种场景提 炼出来,提供更高层级的抽象,并提供接口让应用开发者自行实现分片算法。目前提供 4 种分片算 法。
- 精确分片算法 PreciseShardingAlgorithm
用于处理使用单一键作为分片键的=与 IN 进行分片的场景。 - 范围分片算法 RangeShardingAlgorithm
用于处理使用单一键作为分片键的 BETWEEN AND、>、<、>=、<=进行分片的场景。 - 复合分片算法 ComplexKeysShardingAlgorithm
用于处理使用多键作为分片键进行分片的场景,多个分片键的逻辑较复杂,需要应用开发者自行处理其中的复杂度。 - Hint 分片算法 HintShardingAlgorithm
用于处理使用 Hint 行分片的场景。对于分片字段非 SQL 决定,而由其他外置条件决定的场景,可使用 SQL Hint 灵活的注入分片字段。例:内部系统,按照员工登录主键分库,而数据库中并无此字段。SQL Hint 支持通过 Java API 和 SQL 注释两种方式使用。
分片策略(ShardingStrategy)
分片策略包含分片键和分片算法,真正可用于分片操作的是分片键 + 分片算法,也就是分片策略。目前提供 5 种分片策略。
- 标准分片策略 StandardShardingStrategy
- 只支持单分片键,提供对 SQL 语句中的=, >, <, >=, <=, IN 和 BETWEEN AND 的分片操作支持。提供 PreciseShardingAlgorithm 和 RangeShardingAlgorithm 两个分片算法。
- PreciseShardingAlgorithm 是必选的,RangeShardingAlgorithm 是可选的。但是 SQL 中使用了范围操作,如果不配置 RangeShardingAlgorithm 会采用全库路由扫描,效率低。
- 复合分片策略 ComplexShardingStrategy
- 支持多分片键。提供对 SQL 语句中的=, >, <, >=, <=, IN 和 BETWEEN AND 的分片操作支持。由于多分片键之间的关系复杂,因此并未进行过多的封装,而是直接将分片键值组合以及分片操作符透传至分片算法,完全由应用开发者实现,提供最大的灵活度。
- 行表达式分片策略 InlineShardingStrategy
- 只支持单分片键。使用 Groovy 的表达式,提供对 SQL 语句中的=和 IN 的分片操作支持,对于简单的分片算法,可以通过简单的配置使用,从而避免繁琐的 Java 代码开发。如: tuser$->{u_id % 8} 表示 t_user 表根据 u_id 模 8,而分成 8 张表,表名称为 t_user_0 到 t_user_7。
- Hint 分片策略 HintShardingStrategy
- 通过 Hint 指定分片值而非从 SQL 中提取分片值的方式进行分片的策略。
- 不分片策略 NoneShardingStrategy
分片策略配置
对于分片策略存有数据源分片策略和表分片策略两种维度,两种策略的 API 完全相同。
- 数据源分片策略
用于配置数据被分配的目标数据源。 - 表分片策略
用于配置数据被分配的目标表,由于表存在于数据源内,所以表分片策略是依赖数据源分片策略结果的。
1.3.2 流程剖析
ShardingSphere 3 个产品的数据分片功能主要流程是完全一致的,如下图所示。
SQL 解析
SQL 解析分为词法解析和语法解析。 先通过词法解析器将 SQL 拆分为一个个不可再分的单词。再使 用语法解析器对 SQL 进行理解,并最终提炼出解析上下文。
Sharding-JDBC 采用不同的解析器对 SQL 进行解析,解析器类型如下:
- MySQL 解析器
- Oracle 解析器
- SQLServer 解析器
- PostgreSQL 解析器
- 默认 SQL 解析器
查询优化
负责合并和优化分片条件,如 OR 等。
SQL 路由
根据解析上下文匹配用户配置的分片策略,并生成路由路径。目前支持分片路由和广播路由。
SQL 改写
将 SQL 改写为在真实数据库中可以正确执行的语句。SQL 改写分为正确性改写和优化改写。
SQL 执行
通过多线程执行器异步执行 SQL。
结果归并
将多个执行结果集归并以便于通过统一的 JDBC 接口输出。结果归并包括流式归并、内存归并和使 用装饰者模式的追加归并这几种方式。
1.3.3 SQL 使用规范
SQL 使用规范
支持项
路由至单数据节点时,目前 MySQL 数据库 100% 全兼容,其他数据库完善中。
路由至多数据节点时,全面支持 DQL、DML、DDL、DCL、TCL。支持分页、去重、排 序、分组、聚合、关联查询(不支持跨库关联)。以下用最为复杂的查询为例:
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)
支持分页子查询,但其他子查询有限支持,无论嵌套多少层,只能解析至第一个包含数据表 的子查询,一旦在下层嵌套中再次找到包含数据表的子查询将直接抛出解析异常。
例如,以下子查询可以支持:
SELECT COUNT(*) FROM (SELECT * FROM b_order o)
以下子查询不支持 3 层嵌套:
SELECT COUNT(*) FROM (
SELECT * FROM b_order o WHERE o.id IN (
SELECT id FROM b_order WHERE status = ?))
简单来说,通过子查询进行非功能需求,在大部分情况下是可以支持的。比如分页、统计总 数等;而通过子查询实现业务查询当前并不能支持。
由于归并的限制,子查询中包含聚合函数目前无法支持。
不支持包含 schema 的 SQL。因为 ShardingSphere 的理念是像使用一个数据源一样使用多数 据源,因此对 SQL 的访问都是在同一个逻辑 schema 之上。
当分片键处于运算表达式或函数中的 SQL 时,将采用全路由的形式获取结果。
例如下面 SQL,create_time 为分片键:
SELECT * FROM b_order WHERE to_date(create_time, 'yyyy-mm-dd') = '202005-05';
由于 ShardingSphere 只能通过 SQL 字面提取用于分片的值,因此当分片键处于运算表达式 或函数中时,ShardingSphere 无法提前获取分片键位于数据库中的值,从而无法计算出真正 的分片值。
不支持的 SQL 示例
-- VALUES语句不支持运算 表达式
INSERT INTO tbl_name (col1, col2, …) VALUES(1+2, ?, …)
-- INSERT .. SELECT
INSERT INTO tbl_name (col1, col2, …) SELECT col1, col2, … FROM tbl_name WHERE col3 = ?
--having
SELECT COUNT(col1) as count_alias FROM tbl_name GROUP BY col1 HAVING count_alias > ?
--union
SELECT * FROM tbl_name1 UNION SELECT * FROM tbl_name2
--union all
SELECT * FROM tbl_name1 UNION ALL SELECT * FROM tbl_name2
--包含schema
SELECT * FROM ds.tbl_name1
--同时使用普通聚合函数 和DISTINCT
SELECT SUM(DISTINCT col1), SUM(col1) FROM tbl_name
--会导致 全路由
SELECT * FROM tbl_name WHERE to_date(create_time, ‘yyyy-mm-dd’) = ?
分页查询
完全支持 MySQL 和 Oracle 的分页查询,SQLServer 由于分页查询较为复杂,仅部分支持.
性能瓶颈:
查询偏移量过大的分页会导致数据库获取数据性能低下,以 MySQL 为例
SELECT * FROM b_order ORDER BY id LIMIT 1000000, 10
这句 SQL 会使得 MySQL 在无法利用索引的情况下跳过 1000000 条记录后,再获取 10 条记录, 其性能可想而知。 而在分库分表的情况下(假设分为 2 个库),为了保证数据的正确性,SQL 会改写为:
SELECT * FROM b_order ORDER BY id LIMIT 0, 1000010
即将偏移量前的记录全部取出,并仅获取排序后的最后 10 条记录。这会在数据库本身就执行 很慢的情况下,进一步加剧性能瓶颈。 因为原 SQL 仅需要传输 10 条记录至客户端,而改写之 后的 SQL 则会传输 1,000,010 * 2 的记录至客户端。
ShardingSphere 的优化:
ShardingSphere 进行了以下 2 个方面的优化。
首先,采用流式处理 + 归并排序的方式来避免内存的过量占用。
其次,ShardingSphere 对仅落至单节点的查询进行进一步优化。
分页方案优化:
由于 LIMIT 并不能通过索引查询数据,因此如果可以保证 ID 的连续性,通过 ID 进行分页是比较 好的解决方案:
SELECT * FROM b_order WHERE id > 1000000 AND id <= 1000010 ORDER BY id
或通过记录上次查询结果的最后一条记录的 ID 进行下一页的查询:
SELECT * FROM b_order WHERE id > 1000000 LIMIT 10
1.3.4 其他功能
Inline 行表达式
InlineShardingStrategy:采用 Inline 行表达式进行分片的配置。
Inline 是可以简化数据节点和分片算法配置信息。主要是解决配置简化、配置一体化。
语法格式:
行表达式的使用非常直观,只需要在配置中使用 ${expression}
或者 $->{ expression }
标识行表达式 即可。例如:
${begin..end} 表示范围区间
${[unit1, unit2, unit_x]} 表示枚举值
行表达式中如果出现多个 ${}
或 $->{}
表达式,整个表达式结果会将每个子表达式结果进行笛卡尔 (积)组合。例如,以下行表达式:
${['online', 'offline']}_table${1..3}
$->{['online', 'offline']}_table$->{1..3}
最终会解析为:
online_table1, online_table2, online_table3, offline_table1, offline_table2, offline_table3
数据节点配置
对于均匀分布的数据节点,如果数据结构如下:
db0
├── b_order2
└── b_order1
db1
├── b_order2
└── b_order1
用行表达式可以简化为:
db${0..1}.b_order${1..2} 或者 db$->{0..1}.b_order$->{1..2}
对于自定义的数据节点,如果数据结构如下:
db0
├── b_order0
└── b_order1
db1
├── b_order2
├── b_order3
└── b_order4
用行表达式可以简化为:
db0.b_order$->{0..1},db1.b_order->${2..4}
分片算法配置
行表达式内部的表达式本质上是一段 Groovy 代码,可以根据分片键进行计算的方式,返回相应的 真实数据源或真实表名称。
ds${id % 10} 或者 ds$->{id % 10}
结果为:ds0、ds1、ds2… ds9
分布式主键
ShardingSphere 不仅提供了内置的分布式主键生成器,例如 UUID、SNOWFLAKE,还抽离出分布式主键生成器的接口,方便用户自行实现自定义的自增主键生成器
内置主键生成器
UUID
采用 UUID.randomUUID()的方式产生分布式主键。
SNOWFLAKE
在分片规则配置模块可配置每个表的主键生成策略,默认使用雪花算法,生成 64bit 的长整型 数据。
自定义主键生成器
自定义主键类,实现 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=LAGOUKEY
1.4 读写分离剖析实战
读写分离是通过主从的配置方式,将查询请求均匀的分散到多个数据副本,进一步的提升系统的处理能 力。
主从架构:读写分离,目的是高可用、读写扩展。主从库内容相同,根据 SQL 语义进行路由。
分库分表架构:数据分片,目的读写扩展、存储扩容。库和表内容不同,根据分片配置进行路由。
将水平分片和读写分离联合使用,能够更加有效的提升系统性能, 下图展现了将分库分表与读写分离一 同使用时,应用程序与数据库集群之间的复杂拓扑关系。
读写分离虽然可以提升系统的吞吐量和可用性,但同时也带来了数据不一致的问题,包括多个主库之间 的数据一致性,以及主库与从库之间的数据一致性的问题。 并且,读写分离也带来了与数据分片同样的 问题,它同样会使得应用开发和运维人员对数据库的操作和运维变得更加复杂。
读写分离应用方案
在实际业务中,某天老板让你设计数据库,该如何设计?
你可以直接一步到位,读写分离 + 分库分表。就比如刚成立一家公司,你让老板买一栋楼用来办公,老板会同意吗?(如果是十年前,老板应该赚翻了)
实际依然应该根据数据循序渐进来搭建
500 万一下数据一主二从,读写分离就已经足够了
500 万到 1000 万,考虑主库分表 + 读写分离
超过 1000 万,考虑分库分表 + 读写分离
在数据量不是很多的情况下,我们可以将数据库进行读写分离,以应对高并发的需求,通过水平扩展从 库,来缓解查询的压力。如下:
分表 + 读写分离
在数据量达到 500 万的时候,这时数据量预估千万级别,我们可以将数据进行分表存储。
分库分表 + 读写分离
在数据量继续扩大,这时可以考虑分库分表,将数据存储在不同数据库的不同表中,如下:
透明化读写分离所带来的影响,让使用方尽量像使用一个数据库一样使用主从数据库集群,是 ShardingSphere 读写分离模块的主要设计目标。
主库、从库、主从同步、负载均衡
核心功能
提供一主多从的读写分离配置,仅支持单主库,可以支持独立使用,也可以配合分库分表使 用
独立使用读写分离,支持 SQL 透传。不需要 SQL 改写流程
同一线程且同一数据库连接内,能保证数据一致性。如果有写入操作,后续的读操作均从主 库读取。
基于 Hint 的强制主库路由。可以强制路由走主库查询实时数据,避免主从同步数据延迟。
不支持项
主库和从库的数据同步
主库和从库的数据同步延迟
主库双写或多写
跨主库和从库之间的事务的数据不一致。建议在主从架构中,事务中的读写均用主库操作。
1.5 强制路由剖析实战
在一些应用场景中,分片条件并不存在于 SQL,而存在于外部业务逻辑。因此需要提供一种通过在外部业务代码中指定路由配置的一种方式,在 ShardingSphere 中叫做 Hint。如果使用 Hint 指定了强制分片 路由,那么 SQL 将会无视原有的分片逻辑,直接路由至指定的数据节点操作。
简而言之 Hint 策略就是在代码中按照逻辑指定路由的库
HintManager 主要使用 ThreadLocal 管理分片键信息,进行 hint 强制路由。在代码中向 HintManager 添 加的配置信息只能在当前线程内有效。
Hint 使用场景:
数据分片操作,如果分片键没有在 SQL 或数据表中,而是在业务逻辑代码中
读写分离操作,如果强制在主库进行某些数据操作
Hint 使用过程:
编写分库或分表路由策略,实现 HintShardingAlgorithm 接口
public class MyHintShardingAlgorithm implements HintShardingAlgorithm<Integer> {
@Override
public Collection<String> doSharding(Collection<String> collection, HintShardingValue<Integer> hintShardingValue) {
//添加分库或分表路由逻辑
} }
在配置文件指定分库或分表策略
#强制路由库和表
spring.shardingsphere.sharding.tables.b_order.databasestrategy.hint.algorithm-class-name=com.galaxy.hint.MyHintShardingAlgorithm
spring.shardingsphere.sharding.tables.b_order.table-strategy.hint.algorithmclass-name=com.galaxy.hint.MyHintShardingAlgorithm
spring.shardingsphere.sharding.tables.b_order.actual-data-nodes=ds$-> {0..1}.b_order$->{0..1}
在代码执行查询前使用 HintManager 指定执行策略值
@Test
//路由库和表
public void test() {
HintManager hintManager = HintManager.getInstance();
hintManager.addDatabaseShardingValue("b_order", 1);
hintManager.addTableShardingValue("b_order", 1);
List<Order> list = orderRepository.findAll();
hintManager.close();
list.forEach(o -> {
System.out.println(o.getOrderId() + " " + o.getUserId() + " " + o.getOrderPrice());
});
}
在读写分离结构中,为了避免主从同步数据延迟及时获取刚添加或更新的数据,可以采用强制路由 走主库查询实时数据,使用 hintManager.setMasterRouteOnly 设置主库路由即可。
1.6 数据脱敏剖析实战
数据脱敏是指对某些敏感信息通过脱敏规则进行数据的变形,实现敏感隐私数据的可靠保护。涉及客户 安全数据或者一些商业性敏感数据,如身份证号、手机号、卡号、客户号等个人信息按照规定,都需要 进行数据脱敏。
数据脱敏模块属于 ShardingSphere 分布式治理这一核心功能下的子功能模块。
在更新操作时,它通过对用户输入的 SQL 进行解析,并依据用户提供的脱敏配置对 SQL 进行改写, 从而实现对原文数据进行加密,并将密文数据存储到底层数据库。
在查询数据时,它又从数据库中取出密文数据,并对其解密,最终将解密后的原始数据返回给用 户。
Apache ShardingSphere 自动化&透明化了数据脱敏过程,让用户无需关注数据脱敏的实现细节,像 使用普通数据那样使用脱敏数据。
整体架构
ShardingSphere 提供的 Encrypt-JDBC 和业务代码部署在一起。业务方需面向 Encrypt-JDBC 进行 JDBC 编 程。
Encrypt-JDBC 将用户发起的 SQL 进行拦截,并通过 SQL 语法解析器进行解析、理解 SQL 行为,再依据用 户传入的脱敏规则,找出需要脱敏的字段和所使用的加解密器对目标字段进行加解密处理后,再与底层 数据库进行交互。
脱敏规则
脱敏配置主要分为四部分:数据源配置,加密器配置,脱敏表配置以及查询属性配置,其详情如下图所 示:
数据源配置:指 DataSource 的配置信息
加密器配置:指使用什么加密策略进行加解密。目前 ShardingSphere 内置了两种加解密策略: AES/MD5
脱敏表配置:指定哪个列用于存储密文数据(cipherColumn)、哪个列用于存储明文数据 (plainColumn)以及用户想使用哪个列进行 SQL 编写(logicColumn)
查询属性的配置:当底层数据库表里同时存储了明文数据、密文数据后,该属性开关用于决定是直 接查询数据库表里的明文数据进行返回,还是查询密文数据通过 Encrypt-JDBC 解密后返回。
脱敏处理流程
下图可以看出 ShardingSphere 将逻辑列与明文列和密文列进行了列名映射。
下方图片展示了使用 Encrypt-JDBC 进行增删改查时,其中的处理流程和转换逻辑,如下图所示。
加密策略解析
ShardingSphere 提供了两种加密策略用于数据脱敏,该两种策略分别对应 ShardingSphere 的两种加解 密的接口,即 Encryptor 和 QueryAssistedEncryptor。
Encryptor
该解决方案通过提供 encrypt(), decrypt()两种方法对需要脱敏的数据进行加解密。在用户进行 INSERT, DELETE, UPDATE 时,ShardingSphere 会按照用户配置,对 SQL 进行解析、改写、路由, 并会调用 encrypt()将数据加密后存储到数据库, 而在 SELECT 时,则调用 decrypt()方法将从数据库 中取出的脱敏数据进行逆向解密,最终将原始数据返回给用户。
当前,ShardingSphere 针对这种类型的脱敏解决方案提供了两种具体实现类,分别是 MD5(不可 逆),AES(可逆),用户只需配置即可使用这两种内置的方案。
QueryAssistedEncryptor
相比较于第一种脱敏方案,该方案更为安全和复杂。
它的理念是:即使是相同的数据,如两个用户 的密码相同,它们在数据库里存储的脱敏数据也应当是不一样的。这种理念更有利于保护用户信 息,防止撞库成功。
它提供三种函数进行实现,分别是 encrypt(), decrypt(), queryAssistedEncrypt()。在 encrypt()阶 段,用户通过设置某个变动种子,例如时间戳。针对原始数据 + 变动种子组合的内容进行加密,就 能保证即使原始数据相同,也因为有变动种子的存在,致使加密后的脱敏数据是不一样的。在 decrypt()可依据之前规定的加密算法,利用种子数据进行解密。queryAssistedEncrypt()用于生成 辅助查询列,用于原始数据的查询过程。
当前,ShardingSphere 针对这种类型的脱敏解决方案并没有提供具体实现类,却将该理念抽象成 接口,提供给用户自行实现。ShardingSphere 将调用用户提供的该方案的具体实现类进行数据脱 敏。
1.7 分布式事务剖析实战
1.7.1 分布式事务理论
CAP(强一致性)
CAP 定理,又被叫作布鲁尔定理。对于共享数据系统,最多只能同时拥有 CAP 其中的两个,任意两 个都有其适应的场景。
BASE(最终一致性)
BASE 是指基本可用(Basically Available)、软状态( Soft State)、最终一致性( Eventual Consistency)。它的核心思想是即使无法做到强一致性(CAP 就是强一致性),但应用可以采用 适合的方式达到最终一致性。
BA 指的是基本业务可用性,支持分区失败;
S 表示柔性状态,也就是允许短时间内不同步;
E 表示最终一致性,数据最终是一致的,但是实时是不一致的。
原子性和持久性必须从根本上保障,为了可用性、性能和服务降级的需要,只有降低一致性和隔离 性的要求。BASE 解决了 CAP 理论中没有考虑到的网络延迟问题,在 BASE 中用软状态和最终一 致,保证了延迟后的一致性。
1.7.2 分布式事务模式
了解了分布式事务中的强一致性和最终一致性理论,下面介绍几种常见的分布式事务的解决方案。
2PC 模式(强一致性)
2PC 是 Two-Phase Commit 缩写,即两阶段提交,就是将事务的提交过程分为两个阶段来进行处 理。事务的发起者称协调者,事务的执行者称参与者。协调者统一协调参与者执行。
阶段 1:准备阶段
协调者向所有参与者发送事务内容,询问是否可以提交事务,并等待所有参与者答复。 各参与者执行事务操作,但不提交事务,将 undo 和 redo 信息记入事务日志中。 如参与者执行成功,给协调者反馈 yes;如执行失败,给协调者反馈 no。
阶段 2:提交阶段
如果协调者收到了参与者的失败消息或者超时,直接给每个参与者发送回滚(rollback)消息; 否则,发送提交(commit)消息。
2PC 方案实现起来简单,实际项目中使用比较少,主要因为以下问题:
性能问题:所有参与者在事务提交阶段处于同步阻塞状态,占用系统资源,容易导致性能瓶 颈。
可靠性问题:如果协调者存在单点故障问题,如果协调者出现故障,参与者将一直处于锁定 状态。
数据一致性问题:在阶段 2 中,如果发生局部网络问题,一部分事务参与者收到了提交消 息,另一部分事务参与者没收到提交消息,那么就导致了节点之间数据的不一致。
简而言之就是准备执行阶段和提交/回滚阶段
3PC 模式(强一致性)
3PC 三阶段提交,是两阶段提交的改进版本,与两阶段提交不同的是,引入超时机制。同时在协 调者和参与者中都引入超时机制。
三阶段提交将两阶段的准备阶段拆分为 2 个阶段,插入了一个 preCommit 阶段,解决了原先在两阶段提交中,参与者在准备之后,由于协调者或参与者发生崩溃或错误,而导致参与者无法知晓处于长时间等待的问题。如果在指定的时间内协调者没有收到参 与者的消息则默认失败。
阶段 1:canCommit
协调者向参与者发送 commit 请求,参与者如果可以提交就返回 yes 响应,否则返回 no 响 应。
阶段 2:preCommit
协调者根据阶段 1 canCommit 参与者的反应情况执行预提交事务或中断事务操作。
参与者均反馈 yes:协调者向所有参与者发出 preCommit 请求,参与者收到 preCommit 请求后,执行事务操作,但不提交;将 undo 和 redo 信息记入事务日志 中;
各参与者向协调者反馈 ack 响应或 no 响应,并等待最终指令。 任何一个参与者反馈 no 或等待超时:协调者向所有参与者发出 abort 请求,无论收到 协调者发出的 abort 请求,或者在等待协调者请求过程中出现超时,参与者均会中断事 务。
阶段 3:do Commit
该阶段进行真正的事务提交,根据阶段 2 preCommit 反馈的结果完成事务提交或中断操作。
相比 2PC 模式,3PC 模式降低了阻塞范围,在等待超时后协调者或参与者会中断事务。避免了协调 者单点问题,阶段 3 中协调者出现问题时(比如网络中断等),参与者会继续提交事务。
简而言之就是是否连接正常、准备执行、成功提交失败回滚
多了个准备阶段看是否连接正常避免超时阻塞,协调者挂掉之后则参与者不会阻塞,而是继续提交
XA(强一致性)
XA 是由 X/Open 组织提出的分布式事务的规范,是基于两阶段提交协议。 XA 规范主要定义了全局 事务管理器(TM)和局部资源管理器(RM)之间的接口。目前主流的关系型数据库产品都是实现 了 XA 接口。
XA 之所以需要引入事务管理器,是因为在分布式系统中,从理论上讲两台机器理论上无法达到一 致的状态,需要引入一个单点进行协调。由全局事务管理器管理和协调的事务,可以跨越多个资源 (数据库)和进程。
事务管理器用来保证所有的事务参与者都完成了准备工作(第一阶段)。如果事务管理器收到所有参 与者都准备好的消息,就会通知所有的事务都可以提交了(第二阶段)。MySQL 在这个 XA 事务中 扮演的是参与者的角色,而不是事务管理器。
TCC 模式(最终一致性)
TCC(Try-Confirm-Cancel)的概念,最早是由 Pat Helland 于 2007 年发表的一篇名为《Life beyond Distributed Transactions:an Apostate’s Opinion》的论文提出。TCC 是服务化的两阶段 编程模型,其 Try、Confirm、Cancel 3 个方法均由业务编码实现:
Try 操作作为一阶段,负责资源的检查和预留;
Confirm 操作作为二阶段提交操作,执行真正的业务;
Cancel 是预留资源的取消;
TCC 事务模式相对于 XA 等传统模型如下图所示:
TCC 模式相比于 XA,解决了如下几个缺点:
解决了协调者单点,由主业务方发起并完成这个业务活动。业务活动管理器可以变成多点, 引入集群。
同步阻塞:引入超时机制,超时后进行补偿,并且不会锁定整个资源,将资源转换为业务逻 辑形式,粒度变小。
数据一致性,有了补偿机制之后,由业务活动管理器控制一致性。
消息队列模式(最终一致性)
消息队列的方案最初是由 eBay 提出,基于 TCC 模式,消息中间件可以基于 Kafka、RocketMQ 等 消息队列。此方案的核心是将分布式事务拆分成本地事务进行处理,将需要分布式处理的任务通过 消息日志的方式来异步执行。消息日志可以存储到本地文本、数据库或 MQ 中间件,再通过业务规 则人工发起重试。
下面描述下事务的处理流程:
步骤 1:事务主动方处理本地事务。
事务主动方在本地事务中处理业务更新操作和 MQ 写消息操作。
步骤 2:事务主动方通过消息中间件,通知事务被动方处理事务消息。
事务主动方主动写消息到 MQ,事务消费方接收并处理 MQ 中的消息。
步骤 3:事务被动方通过 MQ 中间件,通知事务主动方事务已处理的消息
事务主动方根据反 馈结果提交或回滚事务。
为了数据的一致性,当流程中遇到错误需要重试,容错处理规则如下:
当步骤 1 处理出错,事务回滚,相当于什么都没发生。
当步骤 2 处理出错,由于未处理的事务消息还是保存在事务发送方,可以重试或撤销本地业 务操作。
如果事务被动方消费消息异常,需要不断重试,业务处理逻辑需要保证幂等。
如果是事务被动方业务上的处理失败,可以通过 MQ 通知事务主动方进行补偿或者事务回滚。
如果多个事务被动方已经消费消息,事务主动方需要回滚事务时需要通知事务被动方回滚。
Saga 模式(最终一致性)
Saga 这个概念源于 1987 年普林斯顿大学的 Hecto 和 Kenneth 发表的一篇数据库论文 Sagas ,一 个 Saga 事务是一个有多个短时事务组成的长时的事务。
在分布式事务场景下,我们把一个 Saga 分 布式事务看做是一个由多个本地事务组成的事务,每个本地事务都有一个与之对应的补偿事务。
在 Saga 事务的执行过程中,如果某一步执行出现异常,Saga 事务会被终止,同时会调用对应的补偿 事务完成相关的恢复操作,这样保证 Saga 相关的本地事务要么都是执行成功,要么通过补偿恢复 成为事务执行之前的状态。(自动反向补偿机制)。
Saga 事务基本协议如下:
每个 Saga 事务由一系列幂等的有序子事务(sub-transaction) Ti 组成。 每个 Ti 都有对应的幂等补偿动作 Ci,补偿动作用于撤销 Ti 造成的结果。
Saga 是一种补偿模式,它定义了两种补偿策略:
向前恢复(forward recovery):对应于上面第一种执行顺序,发生失败进行重试,适用于 必须要成功的场景。
向后恢复(backward recovery):对应于上面提到的第二种执行顺序,发生错误后撤销掉之前所 有成功的子事务,使得整个 Saga 的执行结果撤销。
Saga 的执行顺序有两种,如上图:
事务正常执行完成:T1, T2, T3, …, Tn,例如:减库存(T1),创建订单(T2),支付(T3),依次有序完 成整个事务。
事务回滚:T1, T2, …, Tj, Cj,…, C2, C1,其中 0 < j < n,例如:减库存(T1),创建订单(T2),支付 (T3),支付失败,支付回滚(C3),订单回滚(C2),恢复库存(C1)。
Seata 框架
Fescar 开源项目,最初愿景是能像本地事务一样控制分布式事务,解决分布式环境下的难题。
Seata(Simple Extensible Autonomous Transaction Architecture)是一套一站式分布式事务解 决方案,是阿里集团和蚂蚁金服联合打造的分布式事务框架。Seata 目前的事务模式有 AT、TCC、 Saga 和 XA,默认是 AT 模式,AT 本质上是 2PC 协议的一种实现。
Seata AT 事务模型包含 TM(事务管理器),RM(资源管理器),TC(事务协调器)。其中 TC 是一个独立 的服务需要单独部署,TM 和 RM 以 jar 包的方式同业务应用部署在一起,它们同 TC 建立长连接,在 整个事务生命周期内,保持 RPC 通信。
全局事务的发起方作为 TM,全局事务的参与者作为 RM
TM 负责全局事务的 begin 和 commit/rollback
RM 负责分支事务的执行结果上报,并且通过 TC 的协调进行 commit/rollback。
在 Seata 中,AT 时分为两个阶段的
第一阶段,就是各个阶段本地提交操作;
第二阶段会根据第 一阶段的情况决定是进行全局提交还是全局回滚操作。
具体的执行流程如下:
TM 开启分布式事务,负责全局事务的 begin 和 commit/rollback(TM 向 TC 注册全局事务记 录);
RM 作为参与者,负责分支事务的执行结果上报,并且通过 TC 的协调进行 commit/rollback(RM 向 TC 汇报资源准备状态 );
RM 分支事务结束,事务一阶段结束;
根据 TC 汇总事务信息,由 TM 发起事务提交或回滚操作;
TC 通知所有 RM 提交/回滚资源,事务二阶段结束;
1.7.3 Sharding-JDBC 整合 XA 原理
Java 通过定义 JTA 接口实现了 XA 的模型,JTA 接口里的 ResourceManager 需要数据库厂商提供 XA 的驱动 实现,而 TransactionManager 则需要事务管理器的厂商实现,传统的事务管理器需要同应用服务器绑 定,因此使用的成本很高。 而嵌入式的事务管器可以以 jar 包的形式提供服务,同 ShardingSphere 集成 后,可保证分片后跨库事务强一致性。
ShardingSphere 支持以下功能:
支持数据分片后的跨库 XA 事务
两阶段提交保证操作的原子性和数据的强一致性
服务宕机重启后,提交/回滚中的事务可自动恢复
SPI 机制整合主流的 XA 事务管理器,默认 Atomikos
同时支持 XA 和非 XA 的连接池
提供 spring-boot 和 namespace 的接入端
ShardingSphere 整合 XA 事务时,分离了 XA 事务管理和连接池管理,这样接入 XA 时,可以做到对业务的 零侵入。
Begin(开启 XA 全局事务)
XAShardingTransactionManager 会调用具体的 XA 事务管理器开启 XA 的全局事务。
执行物理 SQL
ShardingSphere 进行解析/优化/路由后会生成 SQL 操作,执行引擎为每个物理 SQL 创建连接的同 时,物理连接所对应的 XAResource 也会被注册到当前 XA 事务中。事务管理器会在此阶段发送 XAResource.start 命令给数据库,数据库在收到 XAResource.end 命令之前的所有 SQL 操作,会被 标记为 XA 事务。
例如
XAResource1.start statement.execute("sql1");
statement.execute("sql2"); XAResource1.end
## Enlist阶段执行 ## 模拟执行一个分片SQL1 ## 模拟执行一个分片SQL2 ## 提交阶段执行
这里 sql1 和 sql2 将会被标记为 XA 事务。
Commit/rollback(提交 XA 事务)
XAShardingTransactionManager 收到接入端的提交命令后,会委托实际的 XA 事务管理进行提交 动作,这时事务管理器会收集当前线程里所有注册的 XAResource,首先发送 XAResource.end 指 令,用以标记此 XA 事务的边界。 接着会依次发送 prepare 指令,收集所有参与 XAResource 投票, 如果所有 XAResource 的反馈结果都是 OK,则会再次调用 commit 指令进行最终提交,如果有一个 XAResource 的反馈结果为 No,则会调用 rollback 指令进行回滚。 在事务管理器发出提交指令后, 任何 XAResource 产生的异常都会通过 recovery 日志进行重试,来保证提交阶段的操作原子性,和 数据强一致性。
1.7.4 Sharding-JDBC 整合 Saga 原理
ShardingSphere 的柔性事务已通过第三方 servicecomb-saga 组件实现的,通过 SPI 机制注入使用。 ShardingSphere 是基于反向 SQL 技术实现的反向补偿操作,它将对数据库进行更新操作的 SQL 自动生成 反向 SQL,并交由 Saga-actuator 引擎执行。使用方则无需再关注如何实现补偿方法,将柔性事务管理器 的应用范畴成功的定位回了事务的本源——数据库层面。ShardingSphere 支持以下功能:
完全支持跨库事务
支持失败 SQL 重试及最大努力送达
支持反向 SQL、自动生成更新快照以及自动补偿
默认使用关系型数据库进行快照及事务日志的持久化,支持使用 SPI 的方式加载其他类型的持久化
Saga 柔性事务的实现类为 SagaShardingTransactionMananger, ShardingSphere 通过 Hook 的方式拦截逻辑 SQL 的解析和路由结果,这样,在分片物理 SQL 执行前,可以生成逆向 SQL,在事务提交阶段再 把 SQL 调用链交给 Saga 引擎处理。
Init(Saga 引擎初始化)
包含 Saga 柔性事务的应用启动时,saga-actuator 引擎会根据 saga.properties 的配置进行初始化的 流程。
Begin(开启 Saga 全局事务)
每次开启 Saga 全局事务时,将会生成本次全局事务的上下文(SagaTransactionContext),事务 上下文记录了所有子事务的正向 SQL 和逆向 SQL,作为生成事务调用链的元数据使用。
执行物理 SQL
在物理 SQL 执行前,ShardingSphere 根据 SQL 的类型生成逆向 SQL,这里是通过 Hook 的方式拦截 Parser 的解析结果进行实现。
Commit/rollback(提交 Saga 事务)
提交阶段会生成 Saga 执行引擎所需的调用链路图,commit 操作产生 ForwardRecovery(正向 SQL 补偿)任务,rollback 操作产生 BackwardRecovery 任务(逆向 SQL 补偿)。
1.7.5 Sharding-JDBC 整合 Seata 原理
分布式事务的实现目前主要分为两阶段的 XA 强事务和 BASE 柔性事务。
Seata AT 事务作为 BASE 柔性事务的一种实现,可以无缝接入到 ShardingSphere 生态中。在整合 Seata AT 事务时,需要把 TM,RM,TC 的模型融入到 ShardingSphere 分布式事务的 SPI 的生态中。
在数据库 资源上,Seata 通过对接 DataSource 接口,让 JDBC 操作可以同 TC 进行 RPC 通信。同样, ShardingSphere 也是面向 DataSource 接口对用户配置的物理 DataSource 进行了聚合,因此把物理 DataSource 二次包装为 Seata 的 DataSource 后,就可以把 Seata AT 事务融入到 ShardingSphere 的分片 中。
Init(Seata 引擎初始化)
包含 Seata 柔性事务的应用启动时,用户配置的数据源会按 seata.conf 的配置,适配成 Seata 事务所 需的 DataSourceProxy,并且注册到 RM 中。
Begin(开启 Seata 全局事务)
TM 控制全局事务的边界,TM 通过向 TC 发送 Begin 指令,获取全局事务 ID,所有分支事务通过此全 局事务 ID,参与到全局事务中;全局事务 ID 的上下文存放在当前线程变量中。
执行分片物理 SQL
处于 Seata 全局事务中的分片 SQL 通过 RM 生成 undo 快照,并且发送 participate 指令到 TC,加入到 全局事务中。ShardingSphere 的分片物理 SQL 是按多线程方式执行,因此整合 Seata AT 事务时, 需要在主线程和子线程间进行全局事务 ID 的上下文传递,这同服务间的上下文传递思路完全相 同。
Commit/rollback(提交 Seata 事务)
提交 Seata 事务时,TM 会向 TC 发送全局事务的 commit 和 rollback 指令,TC 根据全局事务 ID 协调所 有分支事务进行 commit 和 rollback。
1.7.6 Sharding-JDBC 分布式事务实战
ShardingSphere 整合了 XA、Saga 和 Seata 模式后,为分布式事务控制提供了极大的便利,我们可以在 应用程序编程时,采用以下统一模式进行使用。
引入 Maven 依赖
//XA模式
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-transaction-xa-core</artifactId>
<version>${shardingsphere.version}</version>
</dependency>
//Saga模式
<dependency>
<groupId>io.shardingsphere</groupId>
<artifactId>sharding-transaction-base-saga</artifactId>
<version>${shardingsphere-spi-impl.version}</version>
</dependency>
//Seata模式
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-transaction-base-seata-at</artifactId>
<version>${sharding-sphere.version}</version>
</dependency>
</dependencies>
在代码上设置事务类型
TransactionTypeHolder.set(TransactionType.XA);
TransactionTypeHolder.set(TransactionType.BASE);
参数配置
ShardingSphere 默认的 XA 事务管理器为 Atomikos,通过在项目的 classpath 中添加 jta.properties 来定制化 Atomikos 配置项。具体的配置规则如下:
Saga 可以通过在项目的 classpath 中添加的 saga.properties 来定制化 Saga 事务的配置项。配置项属性及说明如下:
属性名称 | 默认 值 | 说明 |
---|---|---|
saga.actuator.executor.size | 5 | 使用的线程池大小 |
saga.actuator.transaction.max.retries | 5 | 失败 SQL 的最大重试次 数 |
saga.actuator.compensation.max.retries | 5 | 失败 SQL 的最大尝试补 偿次数 |
saga.actuator.transaction.retry.delay.milliseconds | 5000 | 失败 SQL 的重试间隔, 单位毫秒 |
saga.actuator.compensation.retry.delay.milliseconds | 3000 | 失败 SQL 的补偿间隔, 单位毫秒 |
saga.persistence.enabled | false | 是否对日志进行持久 化 |
saga.persistence.ds.url | 无 | 事务日志数据库 JDBC 连接 |
saga.persistence.ds.username | 无 | 事务日志数据库用户 名 |
saga.persistence.ds.password | 无 | 事务日志数据库密码 |
saga.persistence.ds.max.pool.size | 50 | 事务日志连接池最大 连接数 |
saga.persistence.ds.min.pool.size | 1 | 事务日志连接池最小 连接数 |
saga.persistence.ds.max.life.time.milliseconds | 0(无 限制) | 事务日志连接池最大 存活时间,单位毫秒 |
saga.persistence.ds.idle.timeout.milliseconds | **60 1000 | 事务日志连接池空闲 回收时间,单位毫秒 |
saga.persistence.ds.connection.timeout.milliseconds | **30 1000 | 事务日志连接池超时 时间,单位毫秒 |
1.8 SPI 加载剖析
在 Apache ShardingSphere 中,很多功能实现类的加载方式是通过 SPI 注入的方式完成的。 Service Provider Interface (SPI)是 Java 提供的一套被第三方实现或扩展的 API,它可以用于实现框架扩展或 组件替换
本节汇总了 Apache ShardingSphere 所有通过 SPI 方式载入的功能模块
SQL 解析
SQL 解析的接口用于规定用于解析 SQL 的 ANTLR 语法文件。 主要接口是 SQLParserEntry,其内置实现类有 MySQLParserEntry, PostgreSQLParserEntry, SQLServerParserEntry 和 OracleParserEntry。
数据库协议
数据库协议的接口用于 Sharding-Proxy 解析与适配访问数据库的协议。 主要接口是 DatabaseProtocolFrontendEngine,其内置实现类有 MySQLProtocolFrontendEngine 和 PostgreSQLProtocolFrontendEngine。
数据脱敏
数据脱敏的接口用于规定加解密器的加密、解密、类型获取、属性设置等方式。 主要接口有两个:Encryptor 和 QueryAssistedEncryptor,其中 Encryptor 的内置实现类有 AESEncryptor 和 MD5Encryptor
分布式主键
分布式主键的接口主要用于规定如何生成全局性的自增、类型获取、属性设置等。 主要接口为 ShardingKeyGenerator,其内置实现类有 UUIDShardingKeyGenerator 和 SnowflakeShardingKeyGenerator。
分布式事务
分布式事务的接口主要用于规定如何将分布式事务适配为本地事务接口。 主要接口为 ShardingTransactionManager,其内置实现类有 XAShardingTransactionManager 和 SeataATShardingTransactionManager。
XA 事务管理器
XA 事务管理器的接口主要用于规定如何将 XA 事务的实现者适配为统一的 XA 事务接口。 主要接口为 XATransactionManager,其内置实现类有 AtomikosTransactionManager, NarayanaXATransactionManager 和 BitronixXATransactionManager。
注册中心
注册中心的接口主要用于规定注册中心初始化、存取数据、更新数据、监控等行为。 主要接口为 RegistryCenter,其内置实现类有 Zookeeper。
1.9 编排治理剖析
编排治理模块提供配置中心/注册中心(以及规划中的元数据中心)、配置动态化、数据库熔断禁用、 调用链路等治理能力。
配置中心
配置集中化:越来越多的运行时实例,使得散落的配置难于管理,配置不同步导致的问题十分严 重。将配置集中于配置中心,可以更加有效进行管理。
配置动态化:配置修改后的分发,是配置中心可以提供的另一个重要能力。它可支持数据源、表与 分片及读写分离策略的动态切换。
配置中心数据结构
配置中心在定义的命名空间的 config 下,以 YAML 格式存储,包括数据源,数据分片,读写分 离、Properties 配置,可通过修改节点来实现对于配置的动态管理。
config/schema/sharding_db/rule 数据分片配置,包括数据分片配置。
注册中心
相对于配置中心管理配置数据,注册中心存放运行时的动态/临时状态数据,比如可用的 proxy 的实 例,需要禁用或熔断的 datasource 实例。通过注册中心,可以提供熔断数据库访问程序对数据库 的访问和禁用从库的访问的编排治理能力。治理仍然有大量未完成的功能(比如流控等)
注册中心数据结构
注册中心在定义的命名空间的 state 下,创建数据库访问对象运行节点,用于区分不同数据库 访问实例。包括 instances 和 datasources 节点。
state/instances
数据库访问对象运行实例信息,子节点是当前运行实例的标识。 运行实例标识由运行服务器 的 IP 地址和 PID 构成。运行实例标识均为临时节点,当实例上线时注册,下线时自动清理。 注册中心监控这些节点的变化来治理运行中实例对数据库的访问等。
state/datasources
可以控制读写分离,可动态添加删除以及禁用。
熔断实例
可在 IP 地址 @-@PID 节点写入 DISABLED(忽略大小写)表示禁用该实例,删除 DISABLED 表 示启用。
支持的配置中心和注册中心
ShardingSphere 在数据库治理模块使用 SPI 方式载入数据到配置中心/注册中心,进行实例熔断和 数据库禁用。 目前,ShardingSphere 内部支持 Zookeeper 和 Etcd 这种常用的配置中心/注册中 心。 此外,您可以使用其他第三方配置中心/注册中心,例如 Apollo、Nacos 等,并通过 SPI 的方式 注入到 ShardingSphere,从而使用该配置中心/注册中心,实现数据库治理功能。
应用性能监控
APM 是应用性能监控的缩写。目前 APM 的主要功能着眼于分布式系统的性能诊断,其主要功能包 括调用链展示,应用拓扑分析等。
ShardingSphere 并不负责如何采集、存储以及展示应用性能监控的相关数据,而是将 SQL 解析与 SQL 执行这两块数据分片的最核心的相关信息发送至应用性能监控系统,并交由其处理。 换句话 说,ShardingSphere 仅负责产生具有价值的数据,并通过标准协议递交至相关系统。 ShardingSphere 可以通过两种方式对接应用性能监控系统。
使用 OpenTracing API 发送性能追踪数据。面向 OpenTracing 协议的 APM 产品都可以和 ShardingSphere 自动对接,比如 SkyWalking,Zipkin 和 Jaeger。 使用 SkyWalking 的自动探针。 ShardingSphere 团队与 SkyWalking 团队共同合作,在 SkyWalking 中实现了 ShardingSphere 自动探针,可以将相关的应用性能数据自动发送到 SkyWalking 中。
1.10 Sharding-Proxy 实战
Sharding-Proxy 是 ShardingSphere 的第二个产品,定位为透明化的数据库代理端,提供封装了数据库 二进制协议的服务端版本,用于完成对异构语言的支持。 目前先提供 MySQL 版本,它可以使用任何兼 容 MySQL 协议的访问客户端(如:MySQL Command Client, MySQL Workbench 等操作数据,对 DBA 更 加友好。
向应用程序完全透明,可直接当做 MySQL 使用 适用于任何兼容 MySQL 协议的客户端
Sharding-Proxy 的优势在于对异构语言的支持,以及为 DBA 提供可操作入口。
Sharding-Proxy 使用过程
1、下载 Sharding-Proxy 的最新发行版;
2、解压缩后修改 conf/server.yaml 和以 config-前缀开头的文件,进行分片规则、读写分离规则配置
编辑 %SHARDING_PROXY_HOME%\conf\config-xxx.yaml
编辑 %SHARDING_PROXY_HOME%\conf\server.yaml
3、引入依赖 jar
如果后端连接 MySQL 数据库,需要下载 MySQL 驱动, 解压缩后将 mysql-connector-java5.1.48.jar 拷贝到 ${sharding-proxy}\lib 目录。
如果后端连接 PostgreSQL 数据库,不需要引入额外依赖。
4、Linux 操作系统请运行 bin/start.sh,Windows 操作系统请运行 bin/start.bat 启动 Sharding-Proxy。
使用默认配置启动:${sharding-proxy}\bin\start.sh
配置端口启动:${sharding-proxy}\bin\start.sh ${port}
5、使用客户端工具连接。如: mysql -h 127.0.0.1 -P 3307 -u root -p root
若想使用 Sharding-Proxy 的数据库治理功能,则需要使用注册中心实现实例熔断和从库禁用功能。 Sharding-Proxy 默认提供了 Zookeeper 的注册中心解决方案。只需按照配置规则进行注册中心的配置, 即可使用。
注意事项
Sharding-Proxy 默认不支持 hint,如需支持,请在 conf/server.yaml 中,将 props 的属性 proxy.hint.enabled 设置为 true。在 Sharding-Proxy 中,HintShardingAlgorithm 的泛型只能是 String 类型。
Sharding-Proxy 默认使用 3307 端口,可以通过启动脚本追加参数作为启动端口号。如: bin/start.sh 3308
Sharding-Proxy 使用 conf/server.yaml 配置注册中心、认证信息以及公用属性。
Sharding-Proxy 支持多逻辑数据源,每个以”config-“做前缀命名 yaml 配置文件,即为一个逻辑数 据源。
第 2 节 Mycat 实战
2.1 Mycat 简介
Mycat 是一个实现了 MySQL 协议的 Server,前端用户可以把它看作是一个数据库代理,用 MySQL 客 户端工具和命令行访问,而其后端可以用 MySQL 原生协议或 JDBC 协议与多个 MySQL 服务器通信, 其核心功能是分库分表和读写分离,即将一个大表水平分割为 N 个小表,存储在后端 MySQL 服务器里 或者其他数据库里。
对于 DBA 来说,可以这么理解
Mycat Mycat 就是 MySQL Server,但是 Mycat 本身并不存储数据,数据是在后端的 MySQL 上存储的, 因此数据可靠性以及事务等都是 MySQL 保证的。简单的说,Mycat 就是 MySQL 最佳伴侣。
对于软件工程师来说,可以这么理解 Mycat
Mycat 就是一个近似等于 MySQL 的数据库服务器,你可以用连接 MySQL 的方式去连接 Mycat(除了端 口不同,默认的 Mycat 端口是 8066 而非 MySQL 的 3306,因此需要在连接字符 串上增加端口信息),大多数 情况下,可以用你熟悉的对象映射框架使用 Mycat,但建议对于分 片表,尽量使用基础的 SQL 语句,因为这样能 达到最佳性能,特别是几千万甚至几百亿条记录的 情况下。
对于架构师来说,可以这么理解 Mycat
Mycat 是一个强大的数据库中间件,不仅仅可以用作读写分离、以及分表分库、容灾备份,而且 可以用于多 用户应用开发、云平台基础设施、让你的架构具备很强的适应性和灵活性,借助于即 将发布的 Mycat 智能优化模块,系统的数据访问瓶颈和热点一目了然,根据这些统计分析数据, 你可以自动或手工调整后端存储,将不同的 表映射到不同存储引擎上,而整个应用的代码一行也 不用改变。
2.2 Mycat 核心概念
2.2.1 逻辑库
对数据进行分片处理之后,从原有的一个库,被切分为多个分片数据库,所有的分片数据库集群构成了 整个完整的数据库存储。Mycat 在操作时,使用逻辑库来代表这个完整的数据库集群,便于对整个集群 操作。
2.2.2 逻辑表
既然有逻辑库,那么就会有逻辑表,分布式数据库中,对应用来说,读写数据的表就是逻辑表。
2.2.3 分片表
分片表,是指那些原有的很大数据的表,需要切分到多个数据库的表,这样,每个分片都有一部分数 据,所 有分片构成了完整的数据。例如在 mycat 配置中的 t_node 就属于分片表,数据按照规则被分 到 dn1,dn2 两个分片节点上。
<table name="t_node" primaryKey="vid" autoIncrement="true" dataNode="dn1,dn2" rule="rule1" />
2.2.4 非分片表
一个数据库中并不是所有的表都很大,某些表是可以不用进行切分的,非分片是相对分片表来说的,就 是那些不需要进行数据切分的表。如下配置中 t_node,只存在于分片节点 dn1 上。
<table name="t_node" primaryKey="vid" autoIncrement="true" dataNode="dn1" />
2.2.5 ER 表
Mycat 提出了基于 E-R 关系的数据分片策略,子表的记录与所关联的父表记录存放在同一个数据分片上,即子表依赖于父表,通过表分组(Table Group)保证数据 join 不会跨库操作。表分组(Table Group) 是解决跨分片数据 join 的一种很好的思路,也是数据切分规划的重要一条规则。类似于 sharding jdbc 中的绑定表
2.2.6 全局表
一个真实的业务系统中,往往存在大量的类似字典表的表,这些表基本上很少变动,字典表具有以下几 个特 性:
变动不频繁;
数据量总体变化不大;
数据规模不大,很少有超过数十万条记录。
对于这类的表,在分片的情况下,当业务表因为规模而进行分片以后,业务表与这些附属的字典表之间 的关联,就成了比较棘手的问题,所以 Mycat 中通过数据冗余来解决这类表的 join,即所有的分片都有 一份数据的拷贝,所有将字典表或者符合字典表特性的一些表定义为全局表。数据冗余是解决跨分片数 据 join 的一种很好的思路,也是数据切分规划的另外一条重要规则。类似于 sharding jdbc 的广播表
2.2.7 分片节点
数据切分后,一个大表被分到不同的分片数据库上面,每个表分片所在的数据库就是分片节点 dataNode。
2.2.8 节点主机
数据切分后,每个分片节点不一定都会独占一台机器,同一机器上面可以有多个分片数据库, 这样一个 或多个分片节点所在的机器就是节点主机,为了规避单节点主机并发数限制, 尽量将读写压力高的分片 节点均衡的放在不同的节点主机 dataHost。
2.2.9 分片规则
前面讲了数据切分,一个大表被分成若干个分片表,就需要一定的规则 rule,这样按照某种业务规则把 数据分到 某个分片的规则就是分片规则,数据切分选择合适的分片规则非常重要,将极大的避免后续数 据处理的难度。
2.3 server.xml 配置
server.xml 几乎保存了所有 mycat 需要的系统配置信息。
2.3.1 user 标签
这个标签主要用于定义登录 mycat 的用户和权限。例如下面的例子中,我们定义了一个用户,用户名 为 user、密码也为 user,可访问的 schema 为 lg_edu_order。
<user name="user">
<property name="password">user</property>
<property name="schemas">lg_edu_order</property>
<property name="readOnly">true</property>
<property name="defaultSchema">lg_edu_order</property>
</user>
2.3.2 firewall 标签
访问的 ip 和权限
<firewall>
<!-- ip白名单 用户对应的可以访问的 ip 地址 -->
<whitehost>
<host host="127.0.0.*" user="root"/>
<host host="127.0.*" user="root"/>
<host host="127.*" user="root"/>
<host host="1*7.*" user="root"/>
</whitehost>
<!-- 黑名单允许的 权限 后面为默认 -->
<blacklist check="true">
<property name="selelctAllow">false</property>
<property name="selelctIntoAllow">false</property>
<property name="updateAllow">false</property>
<property name="insertAllow">false</property>
<property name="deletetAllow">false</property>
<property name="dropAllow">false</property>
</blacklist>
</firewall>
2.3.3 全局序列号
在实现分库分表的情况下,数据库自增主键已无法保证自增主键的全局唯一。为此,Mycat 提供了全局主键 sequence,并且提供了包含本地配置和数据库配置等多种实现方式。
<system>
<property name="sequnceHandlerType">0</property>
</system>
0 表示使用本地文件方式;
1 表示使用数据库方式生成;
2 表示使用本地时间戳方式;
3 表示基于 ZK 与本地配置的分布式 ID 生成器;
4 表示使用 zookeeper 递增方式生成
本地文件
此方式 Mycat 将 sequence 配置到文件中,当使用到 sequence 中的配置后,Mycat 会更下 classpath 中的 sequence_conf.properties 文件中 sequence 当前的值。
#default global sequence
GLOBAL.HISIDS=
GLOBAL.MINID=10001
GLOBAL.MAXID=20000
GLOBAL.CURID=10000
# self define sequence
COMPANY.HISIDS=
COMPANY.MINID=1001
COMPANY.MAXID=2000
COMPANY.CURID=1000
ORDER.HISIDS=
ORDER.MINID=1001
ORDER.MAXID=2000
ORDER.CURID=1000
数据库方式
在数据库中建立一张表,存放 sequence 名称(name),sequence 当前值(current_value),步长 (increment) 等信息
CREATE TABLE MYCAT_SEQUENCE (
name VARCHAR(64) NOT NULL,
current_value BIGINT(20) NOT NULL,
increment INT NOT NULL DEFAULT 1,
PRIMARY KEY (name) ) ENGINE = InnoDB;
本地时间戳方式
ID 为 64 位二进制 ,42(毫秒)+5(机器 ID)+5(业务编码)+12(重复累加)
换算成十进制为 18 位数的 long 类型,每毫秒可以并发 12 位二进制的累加。
在 Mycat 下配置 sequence_time_conf.properties 文件
WORKID=0-31 任意整数 DATAACENTERID=0-31 任意整数
每个 Mycat 配置的 WORKID、DATAACENTERID 不同,组成唯一标识,总共支持 32*32=1024 种组合。
分布式 ZK ID 生成器
Zk 的连接信息统一在 myid.properties 的 zkURL 属性中配置。基于 ZK 与本地配置的分布式 ID 生成 器,InstanceID 可以通过 ZK 自动获取,也可以通过配置文件配置。在 sequence_distributed_conf.properties,只要配置 INSTANCEID=ZK 就表示从 ZK 上获取 InstanceID。
ID 最大为 63 位二进制,可以承受单机房单机器单线程 1000*(2^6)=640000 的并发。结构如下
current time millis(微秒时间戳 38 位,可以使用 17 年)
clusterId(机房或者 ZKid,通过配置文件配置,5 位)
instanceId(实例 ID,可以通过 ZK 或者配置文件获取,5 位)
threadId(线程 ID,9 位)
increment(自增,6 位)
ZK 递增方式
Zk 的连接信息统一在 myid.properties 的 zkURL 属性中配置。需要配置 sequence_conf.properties 文 件
TABLE.MINID 某线程当前区间内最小值
TABLE.MAXID 某线程当前区间内最大值
TABLE.CURID 某线程当前区间内当前值
2.4 schema.xml 配置
schema.xml 作为 Mycat 中重要的配置文件之一,管理着 Mycat 的逻辑库、表、分片节点、主机等信 息。
2.4.1 schema 标签
schema 标签用于定义 Mycat 实例中的逻辑库,Mycat 可以有多个逻辑库,每个逻辑库都有自己的相关 配 置。可以使用 schema 标签来划分这些不同的逻辑库。
<!-- 逻辑库 -->
<schema name="lg_edu_order" checkSQLschema="true" sqlMaxLimit="100"
dataNode="dn1"></schema>
2.4.2 table 标签
table 标签定义了 Mycat 中的逻辑表,所有需要拆分的表都需要在这个标签中定义
<table name="b_order" dataNode="dn1,dn2" rule="b_order_rule" primaryKey="ID"
autoIncrement="true"/>
属性 | 值 | 数量限制 | 说明 |
---|---|---|---|
name | String | (1) | 逻辑表名 |
dataNode | String | (1..*) | 分片节点 |
rule | String | (0..1) | 分片规则 |
ruleRequired | Boolean | (0..1) | 是否强制绑定分片规则 |
primaryKey | String | (1) | 主键 |
type | String | (0..1) | 逻辑表类型,全局表、普通表 |
autoIncrement | Boolean | (0..1) | 自增长主键 |
subTables | String | (1) | 分表 |
needAddLimit | Boolean | (0..1) | 是否为查询 SQL 自动加 limit 限制 |
2.4.3 dataNode 标签
dataNode 标签定义了 MyCat 中的分片节点,也就是我们通常说所的数据分片。
<!-- 数据节点 -->
<dataNode name="dn1" dataHost="lg_edu_order_1" database="lg_edu_order_1" />
name: 定义数据节点的名字,这个名字需要是唯一的,我们需要在 table 标签上应用这个名字,来建 立表与分片对应的关系。
dataHost : 用于定义该分片属于哪个分片主机,属性值是引用 dataHost 标签上定义的 name 属性。
database: 用于定义该分片节点属于哪个具体的库。
2.4.4 dataHost 标签
dataHost 标签在 Mycat 逻辑库中也是作为最底层的标签存在,直接定义了具体的数据库实例、读写分 离配置和心跳语句
<dataHost name="lg_edu_order_1" maxCon="100" minCon="10"
balance="0" writeType="0" dbType="mysql" dbDriver="native"
switchType="1" slaveThreshold="100"> </dataHost>
2.4.5 heartbeat 标签
heartbeat 标签内指明用于和后端数据库进行心跳检查的语句。例如:MySQL 可以使用 select user()、 Oracle 可以 使用 select 1 from dual 等
<dataHost>
<heartbeat>select user()</heartbeat>
</dataHost>
2.4.6 writeHost 和 readHost 标签
writeHost 和 readHost 标签都指定后端数据库的相关配置给 mycat,用于实例化后端连接池。唯一不同 的是,writeHost 指定写实例、readHost 指定读实例。在一个 dataHost 内可以定义多个 writeHost 和 readHost。但是,如果 writeHost 指定的后端数据库宕机, 那么这个 writeHost 绑定的所有 readHost 都将不可用。另一方面,由于这个 writeHost 宕机系统会自动的检测 到,并切换到备用的 writeHost 上去。
<dataHost name="lg_edu_order_2" maxCon="100" minCon="10" balance="0"
writeType="0" dbType="mysql" dbDriver="native" switchType="1"
slaveThreshold="100">
<heartbeat>select user()</heartbeat>
<writeHost host="M1" url="192.168.95.133:3306" user="root" password="1234">
</writeHost> </dataHost>
属性 | 值 | 数量限制 | 说明 |
---|---|---|---|
host | String | (1) | 主机名 |
url | String | (1) | 连接字符串 |
password | String | (1) | 密码 |
user | String | (1) | 用户名 |
weight | String | (1) | 权重 |
usingDecrypt | String | (1) | 是否对密码加密,默认 0 |
2.5 rule.xml 配置
rule.xml 用于定义 Mycat 的分片规则。
2.5.1 tableRule 标签
<tableRule name="c_order_rule">
<rule> <columns>user_id</columns> <algorithm>partitionByOrderFunc</algorithm> </rule> </tableRule>
name:指定唯一的名字,用于标识不同的表规则。
columns:指定要拆分的列名字。
algorithm:使用 function 标签中的 name 属性,连接表规则和具体路由算法。
2.5.2 function 标签
<function name="partitionByOrderFunc"
class="io.mycat.route.function.PartitionByMod">
<property name="count">2</property>
<!--取模2-->
</function>
name:指定算法的名字。
class:制定路由算法具体的类名字。
property: 为具体算法需要用到的一些属性。
2.6 Mycat 实战
2.6.1 Mycat 安装
提示:需要先安装 jdk,同 sharding proxy
下载 Mycat-server 工具包
解压 Mycat 工具包
tar -zxvf Mycat-server-1.6.7.5-release-20200410174409-linux.tar.gz
进入 mycat/bin,启动 Mycat
启动命令:./mycat start
停止命令:./mycat stop
重启命令:./mycat restart
查看状态:./mycat status
http://www.mycat.org.cn/访问 Mycat
mysql -uroot -proot -h127.0.0.1 -P8066
2.6.2 分库分表
在 rule.xml 配置 Mycat 分库分表。
<tableRule name="mod-long">
<rule>
<columns>id</columns>
<algorithm>mod-long</algorithm>
</rule>
</tableRule>
<function name="mod-long" class="io.mycat.route.function.PartitionByMod">
<!-- how many data nodes -->
<property name="count">2</property>
</function>
Mycat 常用分片规则如下:
时间类:按天分片、自然月分片、单月小时分片
哈希类:Hash 固定分片、日期范围 Hash 分片、截取数字 Hash 求模范围分片、截取数字 Hash 分 片、一致性 Hash 分片
取模类:取模分片、取模范围分片、范围求模分片
其他类:枚举分片、范围约定分片、应用指定分片、冷热数据分片
Mycat 常用分片配置示例:
自动分片
<tableRule name="auto-sharding-long">
<rule>
<columns>id</columns>
<algorithm>rang-long</algorithm>
</rule>
</tableRule>
<function name="rang-long"
class="io.mycat.route.function.AutoPartitionByLong">
<property name="mapFile">autopartition-long.txt</property>
</function>
autopartition-long.txt
# range start-end ,data node index
# K=1000,M=10000.
0-500M=0
500M-1000M=1
#1000M-1500M=2
枚举分片 把数据分类存储。
<tableRule name="sharding-by-intfile"> <rule>
<columns>sharding_id</columns>
<algorithm>hash-int</algorithm>
</rule> </tableRule>
<function name="hash-int"
class="io.mycat.route.function.PartitionByFileMap">
<property name="mapFile">partition-hash-int.txt</property>
<!-- 找不到分片时设置容错规则,把数据插入到默认分片0里面 -->
<property name="defaultNode">0</property> </function>
partition-hash-int.txt 文件内容如下:
10000=0 10010=1
冷热数据分片 根据日期查询日志数据冷热数据分布 ,最近 n 个月的到实时交易库查询,超过 n 个月的按照 m 天 分片。
<tableRule name="sharding-by-date">
<rule> <columns>create_time</columns>
<algorithm>sharding-by-hotdate</algorithm>
</rule> </tableRule>
<function name="sharding-by-hotdate"
class="org.opencloudb.route.function.PartitionByHotDate">
<!-- 定义日期格式 -->
<property name="dateFormat">yyyy-MM-dd</property>
<!-- 热库存储多少天数据 -->
<property name="sLastDay">30</property>
<!-- 超过热库期限的数据按照多少天来分片 -->
<property name="sPartionDay">30</property>
</function>
一致性哈希分片
<tableRule name="sharding-by-murmur"> <rule>
<columns>id</columns>
<algorithm>murmur</algorithm>
</rule> </tableRule>
<function name="murmur" class="io.mycat.route.function.PartitionByMurmurHash">
<property name="seed">0</property><!-- 默认是0 -->
<property name="count">2</property>
<!-- 要分片的数据库节点数量,必须指定,否 则没法分片 -->
<property name="virtualBucketTimes">160</property>
<!-- 一个实际的数据库节点 被映射为这么多虚拟节点,默认是160倍,也就是虚拟节点数是物理节点数的160倍 -->
<!-- <property name="weightMapFile">weightMapFile</property> 节点的权重, 没有指定权重的节点默认是1。以properties文件的格式填写,以从0开始到count-1的整数值也就 是节点索引为key,以节点权重值为值。所有权重值必须是正整数,否则以1代替 -->
<!-- <property name="bucketMapPath">/etc/mycat/bucketMapPath</property>
用于测试时观察各物理节点与虚拟节点的分布情况,如果指定了这个属性,会把虚拟节点的 murmur hash值与物理节点的映射按行输出到这个文件,没有默认值,如果不指定,就不会输出任何 东西 --> </function>
2.6.3 读写分离
在 schema.xml 文件中配置 Mycat 读写分离。使用前需要搭建 MySQL 主从架构,并实现主从复制, Mycat 不负数据同步问题。
<dataHost name="localhost1" maxCon="1000" minCon="10" balance="1" writeType="0"
dbType="mysql" dbDriver="native">
<heartbeat>select user()</heartbeat>
<!-- can have multi write hosts -->
<writeHost host="M1" url="localhost:3306" user="root" password="123456">
<readHost host="S1" url="localhost:3307" user="root" password="123456" weight="1" />
</writeHost>
</dataHost>
balance 参数:
- 0 : 所有读操作都发送到当前可用的 writeHost
- 1 :所有读操作都随机发送到 readHost 和 stand by writeHost
- 2 :所有读操作都随机发送到 writeHost 和 readHost
- 3 :所有读操作都随机发送到 writeHost 对应的 readHost 上,但是 writeHost 不负担读压力
writeType 参数:
- 0 : 所有写操作都发送到可用的 writeHost
- 1 :所有写操作都随机发送到 readHost
- 2 :所有写操作都随机发送到 writeHost,readHost
<dataHost name="localhost1" maxCon="1000" minCon="10" balance="1"
writeType="0" dbType="mysql" dbDriver="native">
<heartbeat>select user()</heartbeat> <!-- can have multi write hosts -->
<writeHost host="M1" url="localhost:3306" user="root" password="123456">
</writeHost>
<writeHost host="S1" url="localhost:3307" user="root" password="123456"> </writeHost>
</dataHost>
以上两种取模第一种当写挂了读不可用,第二种可以继续使用,事务内部的一切操作都会走写节点,所 以读操作不要加事务,如果读延时较大,使用根据主从延时切换的读写分离,或者强制走写节点
2.6.4 强制路由
一个查询 SQL 语句以/ !mycat /注解来确定其是走读节点还是写节点。
/! /*
/# /
/* /
强制走从:
/*!mycat:db_type=slave*/ select * from City //有效
/*#mycat:db_type=slave*/ select * from travelrecord
强制走写:
/*!mycat:db_type=master*/ select * from travelrecord //有效
/*#mycat:db_type=slave*/ select * from travelrecord
1.6 以后 Mycat 除了支持 db_type 注解以外,还有其他注解,如下:
/*!mycat:sql=sql */ 指定真正执行的SQL
/*!mycat:schema=schema1 */ 指定走那个schema
/*!mycat:datanode=dn1 */ 指定sql要运行的节点
/*!mycat:catlet=io.mycat.catlets.ShareJoin */ 通过catlet支持跨分片复杂SQL实现以及存 储过程支持等
2.6.5 主从延时切换
switchType 参数:
-1: 表示不自动切换
1 :表示自动切换
2 :基于 MySQL 主从同步状态决定是否切换
3 :基于 MySQL cluster 集群切换机制
1.4 开始支持 MySQL 主从复制状态绑定的读写分离机制,让读更加安全可靠
配置如下:
MyCAT 心跳检查语句配置为 show slave status
dataHost 上定义两个新属性: switchType=”2” 与 slaveThreshold=”100”
此时意味着开启 MySQL 主从复制状态绑定的读写分离与切换机制,Mycat 心 跳机 制通过检测 show slave status 中的 “Seconds_Behind_Master”, “Slave_IO_Running”, “Slave_SQL_Running” 三个字段来确定当前主从同步的状态以及 Seconds_Behind_Master 主从复制时 延, 当 Seconds_Behind_Master > slaveThreshold 时,读写分离筛选器会过滤掉此 Slave 机器,防止 读到很久之 前的旧数据,而当主节点宕机后,切换逻辑会检查 Slave 上的 Seconds_Behind_Master 是 否为 0,为 0 时则 表示主从同步,可以安全切换,否则不会切换。
<dataHost name="localhost1" maxCon="1000" minCon="10" balance="0" writeType="0"
dbType="mysql" dbDriver="native" switchType="2" slaveThreshold="100">
<heartbeat>show slave status </heartbeat> <!-- can have multi write hosts -->
<writeHost host="M1" url="localhost:3306" user="root" password="123456">
</writeHost>
<writeHost host="S1" url="localhost:3316" user="root"
</dataHost>
1.4.1 开始支持 MySQL 集群模式,让读更加安全可靠
配置如下:
MyCAT 心跳检查语句配置为 show status like ‘wsrep%’
dataHost 上定义两个新属性: switchType=”3”
此时意味着开启 MySQL 集群复制状态状态绑定的读写分离与切换机制,Mycat 心跳机制通过检测集群 复制时延时,如果延时过大或者集群出现节点问题不会负载改节点。
<dataHost name="localhost1" maxCon="1000" minCon="10" balance="0" writeType="0"
dbType="mysql" dbDriver="native" switchType="3" >
<heartbeat> show status like ‘wsrep%’</heartbeat>
<writeHost host="M1" url="localhost:3306" user="root"password="123456">
</writeHost>
<writeHost host="S1"url="localhost:3316"user="root"password="123456" >
</writeHost> </dataHost>
2.7 Mycat 事务
2.7.1 Mycat 数据库事务
Mycat 目前没有出来跨分片的事务强一致性支持,单库内部可以保证事务的完整性,如果跨库事务, 在执行的时候任何分片出错,可以保证所有分片回滚,但是一旦应用发起 commit 指令,无法保证所有 分片都成功,考虑到某个分片挂的可能性不大所以称为弱 XA。
2.7.2 XA 事务使用
Mycat 从 1.6.5 版本开始支持标准 XA 分布式事务,考虑到 MySQL 5.7 之前版本 XA 有 bug,所以推荐最 佳搭配 XA 功能使用 MySQL 5.7 版本。
Mycat 实现 XA 标准分布式事务,Mycat 作为 XA 事务协调者角色,即使事务过程中 Mycat 宕机挂掉, 由于 Mycat 会记录事务日志,所以 Mycat 恢复后会进行事务的恢复善后处理工作。考虑到分布式事务 的性能开销比较大,所以只推荐在全局表的事务以及其他一些对一致性要 求比较高的场景。
使用示例: XA 操作说明
#XA 事务需要设置手动提交
set autocommit=0;
#使用该命令开启 XA 事务
set xa=on;
#执行相应的 SQL 语句部分
insert into city(id,name,province) values(200,'chengdu','sichuan');
update position set salary='300000' where id<5;
#提交或回滚事务
commit; rollback;
2.7.3 保证 Repeatable Read
mycat 有一个特性,就是开事务之后,如果不运行 update/delete/select for update 等更新类语句 SQL 的话,不会将当前连接与当前 session 绑定。如下图所示:
这样做的好处是可以保证连接可以最大限度的复用,提升性能。
但是,这就会导致两次 select 中如果有其它的在提交的话,会出现两次同样的 select 不一 致的现象,即不 能 Repeatable Read,这会让人直连 MySQL 的人很困惑,可能会在依赖 Repeatable Read 的场景出现 问题。
所以做了一个开关,当 server.xml 的 system 配置了 strictTxIsolation=true 的时候,会关掉这个 特性,以保证 repeatable read,加了开关 后如下图所示:
具体效果演示
默认 false 的时候不满足 RR 隔离级别
修改为 true 的时候满足了 RR 隔离级别
结语
讲师启源,总感觉讲的东西缺少了些什么,感觉需要真正的实战才能融会贯通,虽然我也在自己的 mysql 上实际操作了一遍,仍是感觉不够连贯!