zoukankan      html  css  js  c++  java
  • 20210203 6. 分库分表实战及中间件

    分库分表实战及中间件

    实战背景介绍

    背景描述

    • 刚开始我们的系统只用了单机数据库
    • 随着用户的不断增多,考虑到系统的高可用和越来越多的用户请求,我们开始使用数据库主从架构
    • 当用户量级和业务进一步提升后,写请求越来越多,这时我们开始使用了分库分表

    遇到的问题

    • 用户请求量太大

      单服务器 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 为分片键

      img

    • 主键选择

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

      • 强一致性:XA 协议
      • 最终一致性:TCC、saga、Seata
    • 数据库扩容

      • 成倍增加数据节点,实现平滑扩容
      • 成倍扩容以后,表中的部分数据请求已被路由到其他节点上面,可以清理掉
    • 业务层改造

      • 基于代理层方式:Mycat、Sharding-Proxy、MySQL Proxy
      • 基于应用层方式:Sharding-jdbc
    • 分库后面临的问题

      • 事务问题:一次投递需要插入两条记录,且分布在不同的服务器上,数据需要保障一致性。
      • 跨库跨表的 join 问题
        • 全局表(字典表):基础数据/配置数据,所有库都拷贝一份
        • 字段冗余:可以使用字段冗余就不用 join 查询了
        • 系统层组装:可以在业务层分别查询出来,然后组装起来,逻辑较复杂
      • 额外的数据管理负担和数据运算压力:数据库扩容、维护成本变高

    ShardingSphere 实战

    ShardingSphere

    Apache ShardingSphere 是一款开源的分布式数据库中间件组成的生态圈。它由 Sharding-JDBC 、 Sharding-Proxy 和 Sharding-Sidecar (规划中)这 3 款相互独立的产品组成。 他们均提供标准化的数据分片、分布式事务和数据库治理功能,可适用于如 Java 同构、异构语言、容器、云原生等各种多样化的应用场景。

    ShardingSphere 已于2020年4月16日成为 Apache 软件基金会的顶级项目。

    ShardingSphere 项目状态如下:

    img

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

    img

    • Sharding-JDBC :被定位为轻量级 Java 框架,在 Java 的 JDBC 层提供的额外服务,以 jar 包形式使用。
    • Sharding-Proxy :被定位为透明化的数据库代理端,提供封装了数据库二进制协议的服务端版本,用于完成对异构语言的支持。
    • Sharding-Sidecar :被定位为 Kubernetes 或 Mesos 的云原生数据库代理,以 DaemonSet 的形式代理所有对数据库的访问。

    img

    Sharding-JDBC、Sharding-Proxy 和 Sharding-Sidecar 三者区别如下:

    Sharding-JDBC Sharding-Proxy Sharding-Sidecar
    数据库 任意 MySQL MySQL
    连接消耗数
    异构语言 仅 Java 任意 任意
    性能 损耗低 损耗略高 损耗低
    无中心化
    静态入口

    ShardingSphere 版本:

    img

    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,SQL Server 和 PostgreSQL。

    img

    Sharding-JDBC主要功能:

    • 数据分片
      • 分库、分表
      • 读写分离
      • 分片策略
      • 分布式主键
    • 分布式事务
      • 标准化的事务接口
      • XA 强一致性事务
      • 柔性事务
    • 数据库治理
      • 配置动态化
      • 编排和治理
      • 数据脱敏
      • 可视化链路追踪

    Sharding-JDBC 内部结构:

    img

    • 图中黄色部分表示的是 Sharding-JDBC 的入口 API,采用工厂方法的形式提供。 目前有 ShardingDataSourceFactoryMasterSlaveDataSourceFactory 两个工厂类。
      • ShardingDataSourceFactory 支持分库分表、读写分离操作
      • MasterSlaveDataSourceFactory 支持读写分离操作
    • 图中蓝色部分表示的是 Sharding-JDBC 的配置对象,提供灵活多变的配置方式。ShardingRuleConfiguration 是分库分表配置的核心和入口,它可以包含多个 TableRuleConfigurationMasterSlaveRuleConfiguration
      • TableRuleConfiguration 封装的是表的分片配置信息,有5种配置形式对应不同的 Configuration 类型。
      • MasterSlaveRuleConfiguration 封装的是读写分离配置信息。
    • 图中红色部分表示的是内部对象,由 Sharding-JDBC 内部使用,应用开发者无需关注。Sharding-JDBC 通过ShardingRuleConfigurationMasterSlaveRuleConfiguration 生成真正供 ShardingDataSourceMasterSlaveDataSource 使用的规则对象。ShardingDataSourceMasterSlaveDataSource 实现了 DataSource 接口,是 JDBC 的完整实现方案。

    Sharding-JDBC 初始化流程:

    • 根据配置的信息生成 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} 更改为实际的版本号

      Spring Boot 启动器:

      <dependency>
          <groupId>org.apache.shardingsphere</groupId>
          <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
          <version>${latest.release.version}</version>
      </dependency>
      
    • 规则配置

      Sharding-JDBC 可以通过 Java,YAML,Spring 命名空间和 Spring Boot Starter 四种方式配置,开发者可根据场景选择适合的配置方式。

    • 创建 DataSource

      通过 ShardingDataSourceFactory 工厂和规则配置对象获取 ShardingDataSource,然后即可通过 DataSource 选择使用原生 JDBC 开发,或者使用 JPA, MyBatis 等 ORM 工具。

      DataSource dataSource = ShardingDataSourceFactory.createDataSource(dataSourceMap, shardingRuleConfig, props);
      

    数据分片剖析实战

    核心概念

    • 表概念

      • 真实表
        数据库中真实存在的物理表。例如 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:

        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:

        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 语句中的 = , > , < , >=, <= , INBETWEEN AND 的分片操作支持。提供PreciseShardingAlgorithmRangeShardingAlgorithm 两个分片算法。

        PreciseShardingAlgorithm 是必选的,RangeShardingAlgorithm 是可选的。但是 SQL 中使用了范围操作,如果不配置 RangeShardingAlgorithm 会采用全库路由扫描,效率低。

      • 复合分片策略 ComplexShardingStrategy

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

      • 行表达式分片策略 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

        不分片的策略。

    • 分片策略配置

      对于分片策略存有数据源分片策略和表分片策略两种维度,两种策略的 API 完全相同。

      • 数据源分片策略

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

      • 表分片策略

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

    流程剖析

    ShardingSphere 3 个产品的数据分片功能主要流程是完全一致的,如下图所示:

    img

    • SQL 解析

      SQL 解析分为词法解析和语法解析。 先通过词法解析器将 SQL 拆分为一个个不可再分的单词。再使用语法解析器对 SQL 进行理解,并最终提炼出解析上下文。

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

      • MySQL 解析器
      • Oracle 解析器
      • SQL Server 解析器
      • PostgreSQL 解析器
      • 默认 SQL 解析器
    • 查询优化

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

    • SQL 路由

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

    • SQL 改写

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

    • SQL执行

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

    • 结果归并

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

    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 WHENHAVINGUNION (ALL)
      • 支持分页子查询,但其他子查询有限支持,无论嵌套多少层,只能解析至第一个包含数据表的子查询,一旦在下层嵌套中再次找到包含数据表的子查询将直接抛出解析异常。

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

        SELECT COUNT(*) FROM (SELECT * FROM b_order o)
        

        以下子查询不支持:

        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') = '2020-05-05';
        

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

    不支持的 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_name2  -- UNION
    SELECT * FROM tbl_name1 UNION ALL SELECT * FROM tbl_name2  -- UNION 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’) = ? -- 会导致全路由
    
    • 分页查询

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

      • 性能瓶颈:

        查询偏移量过大的分页会导致数据库获取数据性能低下,以 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
        

    其他功能

    • 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
        

    读写分离剖析实战

    读写分离是通过主从的配置方式,将查询请求均匀的分散到多个数据副本,进一步的提升系统的处理能力。

    主从架构:读写分离,目的是高可用、读写扩展。主从库内容相同,根据 SQL 语义进行路由。

    分库分表架构:数据分片,目的读写扩展、存储扩容。库和表内容不同,根据分片配置进行路由。

    将水平分片和读写分离联合使用,能够更加有效的提升系统性能, 下图展现了将分库分表与读写分离一同使用时,应用程序与数据库集群之间的复杂拓扑关系:

    img

    读写分离虽然可以提升系统的吞吐量和可用性,但同时也带来了数据不一致的问题,包括多个主库之间的数据一致性,以及主库与从库之间的数据一致性的问题。 并且,读写分离也带来了与数据分片同样的问题,它同样会使得应用开发和运维人员对数据库的操作和运维变得更加复杂。

    读写分离应用方案

    在数据量不是很多的情况下,我们可以将数据库进行读写分离,以应对高并发的需求,通过水平扩展从库,来缓解查询的压力。如下:

    img

    分表+读写分离

    在数据量达到 500 万的时候,这时数据量预估千万级别,我们可以将数据进行分表存储。

    img

    分库分表+读写分离

    在数据量继续扩大,这时可以考虑分库分表,将数据存储在不同数据库的不同表中,如下:

    img

    透明化读写分离所带来的影响,让使用方尽量像使用一个数据库一样使用主从数据库集群,是 ShardingSphere 读写分离模块的主要设计目标。

    主库、从库、主从同步、负载均衡:

    • 核心功能
      • 提供一主多从的读写分离配置。仅支持单主库,可以支持独立使用,也可以配合分库分表使用
      • 独立使用读写分离,支持 SQL 透传。不需要 SQL 改写流程
      • 同一线程且同一数据库连接内,能保证数据一致性。如果有写入操作,后续的读操作均从主库读取。
      • 基于 Hint 的强制主库路由。可以强制路由走主库查询实时数据,避免主从同步数据延迟。
    • 不支持项
      • 主库和从库的数据同步
      • 主库和从库的数据同步延迟
      • 主库双写或多写
      • 跨主库和从库之间的事务的数据不一致。建议在主从架构中,事务中的读写均用主库操作。

    强制路由剖析实战

    在一些应用场景中,分片条件并不存在于 SQL,而存在于外部业务逻辑。因此需要提供一种通过在外部业务代码中指定路由配置的一种方式,在 ShardingSphere 中叫做 Hint。如果使用 Hint 指定了强制分片路由,那么 SQL 将会无视原有的分片逻辑,直接路由至指定的数据节点操作。

    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.lagou.hint.MyHintShardingAlgorithm
      spring.shardingsphere.sharding.tables.b_order.table-strategy.hint.algorithmclass-name=com.lagou.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 设置主库路由即可。

    数据脱敏剖析实战

    数据脱敏是指对某些敏感信息通过脱敏规则进行数据的变形,实现敏感隐私数据的可靠保护。涉及客户安全数据或者一些商业性敏感数据,如身份证号、手机号、卡号、客户号等个人信息按照规定,都需要进行数据脱敏。

    数据脱敏模块属于 ShardingSphere 分布式治理这一核心功能下的子功能模块:

    • 在更新操作时,它通过对用户输入的 SQL 进行解析,并依据用户提供的脱敏配置对SQL进行改写,从而实现对原文数据进行加密,并将密文数据存储到底层数据库。
    • 在查询数据时,它又从数据库中取出密文数据,并对其解密,最终将解密后的原始数据返回给用户。

    Apache ShardingSphere 自动化&透明化了数据脱敏过程,让用户无需关注数据脱敏的实现细节,像使用普通数据那样使用脱敏数据。

    整体架构

    ShardingSphere 提供的 Encrypt-JDBC 和业务代码部署在一起。业务方需面向 Encrypt-JDBC 进行 JDBC 编程。

    img

    Encrypt-JDBC 将用户发起的 SQL 进行拦截,并通过 SQL 语法解析器进行解析、理解 SQL 行为,再依据用户传入的脱敏规则,找出需要脱敏的字段和所使用的加解密器对目标字段进行加解密处理后,再与底层数据库进行交互。

    脱敏规则

    脱敏配置主要分为四部分:数据源配置,加密器配置,脱敏表配置以及查询属性配置,其详情如下图所示:

    img

    • 数据源配置:指 DataSource 的配置信息
    • 加密器配置:指使用什么加密策略进行加解密。目前 ShardingSphere 内置了两种加解密策略:AES/MD5
    • 脱敏表配置:指定哪个列用于存储密文数据(cipherColumn)、哪个列用于存储明文数据(plainColumn)以及用户想使用哪个列进行 SQL 编写(logicColumn
    • 查询属性的配置:当底层数据库表里同时存储了明文数据、密文数据后,该属性开关用于决定是直接查询数据库表里的明文数据进行返回,还是查询密文数据通过 Encrypt-JDBC 解密后返回。

    脱敏处理流程

    下图可以看出 ShardingSphere 将逻辑列与明文列和密文列进行了列名映射:

    img

    下方图片展示了使用 Encrypt-JDBC 进行增删改查时,其中的处理流程和转换逻辑,如下图所示:

    img

    加密策略解析

    ShardingSphere 提供了两种加密策略用于数据脱敏,该两种策略分别对应 ShardingSphere 的两种加解密的接口,即 EncryptorQueryAssistedEncryptor

    • Encryptor

      该解决方案通过提供 encrypt(), decrypt() 两种方法对需要脱敏的数据进行加解密。在用户进行 INSERT, DELETE, UPDATE时,ShardingSphere 会按照用户配置,对 SQL 进行解析、改写、路由,并会调用 encrypt()将数据加密后存储到数据库, 而在 SELECT 时,则调用 decrypt() 方法将从数据库中取出的脱敏数据进行逆向解密,最终将原始数据返回给用户。

      当前,ShardingSphere 针对这种类型的脱敏解决方案提供了两种具体实现类,分别是 MD5(不可逆),AES(可逆),用户只需配置即可使用这两种内置的方案。

    • QueryAssistedEncryptor

      相比较于第一种脱敏方案,该方案更为安全和复杂。它的理念是:即使是相同的数据,如两个用户的密码相同,它们在数据库里存储的脱敏数据也应当是不一样的。这种理念更有利于保护用户信息,防止撞库成功。

      它提供三种函数进行实现,分别是 encrypt(), decrypt(), queryAssistedEncrypt()。在 encrypt() 阶段,用户通过设置某个变动种子,例如时间戳。针对原始数据+变动种子组合的内容进行加密,就能保证即使原始数据相同,也因为有变动种子的存在,致使加密后的脱敏数据是不一样的。在 decrypt() 可依据之前规定的加密算法,利用种子数据进行解密。queryAssistedEncrypt() 用于生成辅助查询列,用于原始数据的查询过程。

      当前,ShardingSphere 针对这种类型的脱敏解决方案并没有提供具体实现类,却将该理念抽象成接口,提供给用户自行实现。ShardingSphere 将调用用户提供的该方案的具体实现类进行数据脱敏。

    分布式事务剖析实战

    分布式事务理论

    • CAP(强一致性)

      CAP 定理,又被叫作布鲁尔定理。对于共享数据系统,最多只能同时拥有 CAP 其中的两个,任意两个都有其适应的场景。

    • BASE(最终一致性)

      BASE 是指基本可用(Basically Available)、软状态( Soft State)、最终一致性( Eventual Consistency)。它的核心思想是即使无法做到强一致性(CAP 就是强一致性),但应用可以采用适合的方式达到最终一致性。

      • BA 指的是基本业务可用性,支持分区失败;
      • S 表示柔性状态,也就是允许短时间内不同步;
      • E 表示最终一致性,数据最终是一致的,但是实时是不一致的。

      原子性和持久性必须从根本上保障,为了可用性、性能和服务降级的需要,只有降低一致性和隔离性的要求。BASE 解决了 CAP 理论中没有考虑到的网络延迟问题,在 BASE 中用软状态和最终一致,保证了延迟后的一致性。

    分布式事务模式

    了解了分布式事务中的强一致性和最终一致性理论,下面介绍几种常见的分布式事务的解决方案:

    • 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 接口。

      img

      DTP: Distributed Transaction Processing(分布式事务处理)

      XA 之所以需要引入事务管理器,是因为在分布式系统中,从理论上讲两台机器无法达到一致的状态,需要引入一个单点进行协调。由全局事务管理器管理和协调的事务,可以跨越多个资源(数据库)和进程。

      事务管理器用来保证所有的事务参与者都完成了准备工作(第一阶段)。如果事务管理器收到所有参与者都准备好的消息,就会通知所有的事务都可以提交了(第二阶段)。 MySQL 在这个 XA 事务中扮演的是参与者的角色,而不是事务管理器。

    • TCC模式(最终一致性)

      TCC(Try-Confirm-Cancel)的概念,最早是由 Pat Helland 于 2007 年发表的一篇名为《Life beyond Distributed Transactions : an Apostate’s Opinion》的论文提出。TCC 是服务化的两阶段编程模型,其 TryConfirmCancel 3 个方法均由业务编码实现:

      • Try 操作作为一阶段,负责资源的检查和预留;
      • Confirm 操作作为二阶段提交操作,执行真正的业务;
      • Cancel 是预留资源的取消;

      TCC 事务模式相对于 XA 等传统模型如下图所示:

      img

      TCC 模式相比于 XA,解决了如下几个缺点:

      • 解决了协调者单点,由主业务方发起并完成这个业务活动。业务活动管理器可以变成多点,引入集群。
      • 同步阻塞:引入超时机制,超时后进行补偿,并且不会锁定整个资源,将资源转换为业务逻辑形式,粒度变小。
      • 数据一致性,有了补偿机制之后,由业务活动管理器控制一致性。
    • 消息队列模式(最终一致性)

      消息队列的方案最初是由 eBay 提出,基于 TCC 模式,消息中间件可以基于 Kafka 、 RocketMQ 等消息队列。此方案的核心是将分布式事务拆分成本地事务进行处理,将需要分布式处理的任务通过消息日志的方式来异步执行。消息日志可以存储到本地文本、数据库或 MQ 中间件,再通过业务规则人工发起重试。

      下面描述下事务的处理流程:

      img

      • 步骤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

      img

      在 Seata 中, AT 是分为两个阶段的,第一阶段,就是各个阶段本地提交操作;第二阶段会根据第一阶段的情况决定是进行全局提交还是全局回滚操作。具体的执行流程如下:

      • TM 开启分布式事务,负责全局事务的 begin 和 commit/rollback ( TM 向 TC 注册全局事务记录);
      • RM 作为参与者,负责分支事务的执行结果上报,并且通过 TC 的协调进行 commit/rollback(RM 向 TC 汇报资源准备状态 );
      • RM 分支事务结束,事务一阶段结束;
      • 根据 TC 汇总事务信息,由 TM 发起事务提交或回滚操作;
      • TC 通知所有 RM 提交/回滚资源,事务二阶段结束;

    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 时,可以做到对业务的零侵入。

    img

    • Begin(开启 XA 全局事务)

      XAShardingTransactionManager 会调用具体的 XA 事务管理器开启 XA 的全局事务。

    • 执行物理 SQL

      ShardingSphere 进行解析/优化/路由后会生成 SQL 操作,执行引擎为每个物理 SQL 创建连接的同时,物理连接所对应的 XAResource 也会被注册到当前 XA 事务中。事务管理器会在此阶段发送 XAResource.start 命令给数据库,数据库在收到 XAResource.end 命令之前的所有 SQL 操作,会被标记为 XA 事务。

      例如:

      XAResource1.start 			## Enlist阶段执行
      statement.execute("sql1"); 	## 模拟执行一个分片SQL1
      statement.execute("sql2"); 	## 模拟执行一个分片SQL2
      XAResource1.end 			## 提交阶段执行
      

      这里 sql1 和 sql2 将会被标记为 XA 事务。

    • Commit/Rollback(提交 XA 事务)

      XAShardingTransactionManager 收到接入端的提交命令后,会委托实际的 XA 事务管理进行提交动作,这时事务管理器会收集当前线程里所有注册的 XAResource ,首先发送 XAResource.end 指令,用以标记此 XA 事务的边界。 接着会依次发送 prepare 指令,收集所有参与 XAResource 投票,如果所有 XAResource 的反馈结果都是 OK ,则会再次调用 commit 指令进行最终提交,如果有一个 XAResource 的反馈结果为 No,则会调用 rollback 指令进行回滚。 在事务管理器发出提交指令后,任何 XAResource 产生的异常都会通过 recovery 日志进行重试,来保证提交阶段的操作原子性,和数据强一致性。

      例如:

      XAResource1.prepare 	## ack: yes
      XAResource2.prepare 	## ack: yes
      XAResource1.commit
      XAResource2.commit
      
      XAResource1.prepare 	## ack: yes
      XAResource2.prepare 	## ack: no
      XAResource1.rollback
      XAResource2.rollback
      

    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 引擎处理。

    img

    • 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 补偿)。

    Sharding-JDBC 整合 Seata 原理

    分布式事务的实现目前主要分为两阶段的 XA 强事务和 BASE 柔性事务。

    img

    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 的分片中。

    img

    • 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 。

    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>
      
    • Java 编码方式设置事务类型

      TransactionTypeHolder.set(TransactionType.XA);
      TransactionTypeHolder.set(TransactionType.BASE);
      
    • 参数配置

      ShardingSphere 默认的 XA 事务管理器为 Atomikos ,通过在项目的 classpath 中添加 jta.properties 来定制化 Atomikos 配置项。具体的配置规则如下:

      #	指定是否启动磁盘日志,默认为true。在生产环境下一定要保证为true,否则数据的完整性无法保证 
      com.atomikos.icatch.enable_logging=true
      #	JTA/XA资源是否应该自动注册
      com.atomikos.icatch.automatic_resource_registration=true
      #	JTA事务的默认超时时间,默认为10000ms
      com.atomikos.icatch.default_jta_timeout=10000
      #	事务的最大超时时间,默认为300000ms。这表示事务超时时间由UserTransaction.setTransactionTimeout()较大者决定。4.x版本之后,指定为0的话则表示不设置超时时间
      com.atomikos.icatch.max_timeout=300000
      #	指定在两阶段提交时,是否使用不同的线程(意味着并行)。3.7版本之后默认为false,更早的版本默认为true。如果为false,则提交将按照事务中访问资源的顺序进行。
      com.atomikos.icatch.threaded_2pc=false
      #	指定最多可以同时运行的事务数量,默认值为50,负数表示没有数量限制。在调用UserTransaction.begin()方法时,可能会抛出一个”Max number of active transactions reached”异常信息,表示超出最大事务数限制
      com.atomikos.icatch.max_actives=50
      #	是否支持subtransaction,默认为true
      com.atomikos.icatch.allow_subtransactions=true
      #	指定在可能的情况下,否应该join 子事务(subtransactions),默认值为true。如果设置为false,对于有关联的不同subtransactions,不会调用XAResource.start(TM_JOIN)
      com.atomikos.icatch.serial_jta_transactions=true
      #	指定JVM关闭时是否强制(force)关闭事务管理器,默认为false
      com.atomikos.icatch.force_shutdown_on_vm_exit=false
      #	在正常关闭(no-force)的情况下,应该等待事务执行完成的时间,默认为Long.MAX_VALUE
      com.atomikos.icatch.default_max_wait_time_on_shutdown=9223372036854775807
      
      #	========= 日志记录配置=======
      #	事务日志目录,默认为./。
      com.atomikos.icatch.log_base_dir=./
      #	事务日志文件前缀,默认为tmlog。事务日志存储在文件中,文件名包含一个数字后缀,日志文件以.log为扩展名,如tmlog1.log。遇到checkpoint时,新的事务日志文件会被创建,数字增加。
      com.atomikos.icatch.log_base_name=tmlog
      #	指定两次checkpoint的时间间隔,默认为500
      com.atomikos.icatch.checkpoint_interval=500
      
      #	=========日志恢复配置=============
      #	指定在多长时间后可以清空无法恢复的事务日志(orphaned),默认86400000ms
      com.atomikos.icatch.forget_orphaned_log_entries_delay=86400000
      #	指定两次恢复扫描之间的延迟时间。默认值为与com.atomikos.icatch.default_jta_timeout相同
      com.atomikos.icatch.recovery_delay=${com.atomikos.icatch.default_jta_timeout} 
      #	提交失败时,再抛出一个异常之前,最多可以重试几次,默认值为5
      com.atomikos.icatch.oltp_max_retries=5
      #	提交失败时,每次重试的时间间隔,默认10000ms
      com.atomikos.icatch.oltp_retry_interval=10000
      

      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 事务日志连接池超时时间,单位毫秒

    SPI 加载剖析

    在 Apache ShardingSphere 中,很多功能实现类的加载方式是通过 SPI 注入的方式完成的。 Service Provider Interface ( SPI )是 Java 提供的一套被第三方实现或扩展的 API ,它可以用于实现框架扩展或组件替换。

    本节汇总了 Apache ShardingSphere 所有通过 SPI 方式载入的功能模块。

    • SQL 解析

      SQL 解析的接口用于规定用于解析 SQL 的 ANTLR 语法文件。

      主要接口是 SQLParserEntry ,其内置实现类有 MySQLParserEntryPostgreSQLParserEntrySQLServerParserEntryOracleParserEntry

    • 数据库协议

      数据库协议的接口用于 Sharding-Proxy 解析与适配访问数据库的协议。

      主要接口是 DatabaseProtocolFrontendEngine,其内置实现类有 MySQLProtocolFrontendEnginePostgreSQLProtocolFrontendEngine

    • 数据脱敏

      数据脱敏的接口用于规定加解密器的加密、解密、类型获取、属性设置等方式。

      主要接口有两个: EncryptorQueryAssistedEncryptor ,其中 Encryptor 的内置实现类有 AESEncryptorMD5Encryptor

    • 分布式主键

      分布式主键的接口主要用于规定如何生成全局性的自增、类型获取、属性设置等。

      主要接口为 ShardingKeyGenerator,其内置实现类有 UUIDShardingKeyGeneratorSnowflakeShardingKeyGenerator

    • 分布式事务

      分布式事务的接口主要用于规定如何将分布式事务适配为本地事务接口。

      主要接口为 ShardingTransactionManager,其内置实现类有 XAShardingTransactionManagerSeataATShardingTransactionManager

    • XA事务管理器

      XA 事务管理器的接口主要用于规定如何将 XA 事务的实现者适配为统一的 XA 事务接口。

      主要接口为 XATransactionManager ,其内置实现类有 AtomikosTransactionManagerNarayanaXATransactionManagerBitronixXATransactionManager

    • 注册中心

      注册中心的接口主要用于规定注册中心初始化、存取数据、更新数据、监控等行为。

      主要接口为 RegistryCenter,其内置实现类有 Zookeeper

    编排治理剖析

    编排治理模块提供配置中心/注册中心(以及规划中的元数据中心)、配置动态化、数据库熔断禁用、调用链路等治理能力。

    • 配置中心

      配置集中化:越来越多的运行时实例,使得散落的配置难于管理,配置不同步导致的问题十分严重。将配置集中于配置中心,可以更加有效进行管理。

      配置动态化:配置修改后的分发,是配置中心可以提供的另一个重要能力。它可支持数据源、表与分片及读写分离策略的动态切换。

      • 配置中心数据结构

        配置中心在定义的命名空间的 config 下,以 YAML 格式存储,包括数据源,数据分片,读写分离、 Properties 配置,可通过修改节点来实现对于配置的动态管理。

        config
        ├──authentication 				# Sharding-Proxy权限配置
        ├──props 						# 属性配置
        ├──schema 						# Schema配置
        ├──├──sharding_db 					# SchemaName配置
        ├──├──├──datasource 					# 数据源配置
        ├──├──├──rule 							# 数据分片规则配置
        ├──├──masterslave_db 				# SchemaName配置
        ├──├──├──datasource 					# 数据源配置
        ├──├──├──rule 							# 读写分离规则
        
      • config/authentication

        password: root
        username: root
        
      • config/sharding/props

        sql.show: true
        
      • config/schema/schemeName/datasource

        多个数据库连接池的集合,不同数据库连接池属性自适配(例如:DBCP,C3P0,Druid, HikariCP)。

        ds_0:
            dataSourceClassName: com.zaxxer.hikari.HikariDataSource
                properties:
                    url: jdbc:mysql://127.0.0.1:3306/lagou1?            serverTimezone=UTC&useSSL=false
                    password: root
                    username: root
                    maxPoolSize: 50
                    minPoolSize: 1
        ds_1:
            dataSourceClassName: com.zaxxer.hikari.HikariDataSource
                properties:
                    url: jdbc:mysql://127.0.0.1:3306/lagou2?            serverTimezone=UTC&useSSL=false
                    password: root
                    username: root
                    maxPoolSize: 50
                    minPoolSize: 1
        
      • config/schema/sharding_db/rule

        数据分片配置,包括数据分片配置。

        tables:
            b_order:
                actualDataNodes: ds_$->{0..1}.b_order_$->{0..1}
                databaseStrategy:
                    inline:
                        shardingColumn: user_id
                        algorithmExpression: ds_$->{user_id % 2}
                keyGenerator:
                	column: order_id
                logicTable: b_order
                    tableStrategy:
                    inline:
                        shardingColumn: order_id
                        algorithmExpression: b_order_$->{order_id % 2}
            b_order_item:
                actualDataNodes: ds_$->{0..1}.b_order_item_$->{0..1}
                databaseStrategy:
                    inline:
                        shardingColumn: user_id
                        algorithmExpression: ds_$->{user_id % 2}
                keyGenerator:
                    column: order_item_id
                logicTable: b_order_item
                    tableStrategy:
                    inline:
                        shardingColumn: order_id
                        algorithmExpression: b_order_item_$->{order_id % 2}
        
      • config/schema/masterslave/rule

        读写分离独立使用时使用该配置

        name: ds_ms
        masterDataSourceName: master
        slaveDataSourceNames:
            - ds_slave0
            - ds_slave1
        loadBalanceAlgorithmType: ROUND_ROBIN
        
      • 动态生效

        在注册中心上修改、删除、新增相关配置,会动态推送到生产环境并立即生效。

    • 注册中心

      相对于配置中心管理配置数据,注册中心存放运行时的动态 / 临时状态数据,比如可用的 proxy 的实例,需要禁用或熔断的 datasource 实例。通过注册中心,可以提供熔断数据库访问程序对数据库的访问和禁用从库的访问的编排治理能力。治理仍然有大量未完成的功能(比如流控等)

      • 注册中心数据结构

        注册中心在定义的命名空间的 state 下,创建数据库访问对象运行节点,用于区分不同数据库访问实例。包括 instances 和 datasources 节点。

        instances
            ├──your_instance_ip_a@-@your_instance_pid_x
            ├──your_instance_ip_b@-@your_instance_pid_y
            ├──....
        datasources
            ├──ds0
            ├──ds1
            ├──....
        
      • state/instances

        数据库访问对象运行实例信息,子节点是当前运行实例的标识。 运行实例标识由运行服务器的 IP 地址和 PID 构成。运行实例标识均为临时节点,当实例上线时注册,下线时自动清理。注册中心监控这些节点的变化来治理运行中实例对数据库的访问等。

      • state/datasources

        可以控制读写分离,可动态添加删除以及禁用。

      • 熔断实例

        可在IP地址@-@PID节点写入 DISABLED(忽略大小写)表示禁用该实例,删除DISABLED表
        示启用。

        ZooKeeper 命令如下:

        [zk: localhost:2181(CONNECTED) 0] set /your_zk_namespace/your_app_name/state/instances/your_instance_ip_a@-@your_instance_pid_x DISABLED
        
      • 禁用从库

        在读写分离场景下,可在数据源名称子节点中写入DISABLED表示禁用从库数据源,删除 DISABLED 或节点表示启用。

        ZooKeeper 命令如下:

        [zk: localhost:2181(CONNECTED) 0] set /your_zk_namespace/your_app_name/state/datasources/your_slave_datasource_name 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 中。

    Sharding-Proxy 实战

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

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

    img

    Sharding-Proxy 的优势在于对异构语言的支持,以及为 DBA 提供可操作入口。

    Sharding-Proxy 使用过程:

    • 下载 Sharding-Proxy 的最新发行版;

    • 解压缩后修改 conf/server.yaml 和以 config- 前缀开头的文件,进行分片规则、读写分离规则配置

      编辑 %SHARDING_PROXY_HOME%confconfig-xxx.yaml

      编辑 %SHARDING_PROXY_HOME%confserver.yaml

    • 引入依赖 jar

      如果后端连接 MySQL 数据库,需要下载 MySQL 驱动, 解压缩后将 mysql-connector-java-5.1.48.jar 拷贝到 ${sharding-proxy}lib 目录。

      如果后端连接 PostgreSQL 数据库,不需要引入额外依赖。

    • Linux操作系统请运行 bin/start.sh,Windows操作系统请运行 bin/start.bat 启动 Sharding-Proxy。

      使用默认配置启动:${sharding-proxy}instart.sh
      配置端口启动:${sharding-proxy}instart.sh ${port}

    • 使用客户端工具连接。如: 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 配置文件,即为一个逻辑数据源。

    参考资料

  • 相关阅读:
    第二篇 Entity Framework Plus 之 Query Future
    【转载】保哥 釐清 CLR、.NET、C#、Visual Studio、ASP.NET 各版本之間的關係
    第一篇 Entity Framework Plus 之 Audit
    搭建公司内部的NuGet Server
    第三篇:Entity Framework CodeFirst & Model 映射 续篇 EntityFramework Power Tools 工具使用
    封装的方法
    选项卡切换
    获取鼠标坐标并且根据鼠标位置不同弹出不同内容
    点击其它位置,div下拉菜单消失
    用js写的简单的下拉菜单
  • 原文地址:https://www.cnblogs.com/huangwenjie/p/14367753.html
Copyright © 2011-2022 走看看