简述

  1. ShardingJDBC: 它是一个轻量级的Java框架,供给了数据分片、读写别离、散布式主键生成等数据拜访功用。ShardingJDBC 直接嵌入在应用程序中,不需求经过中间件署理的方式完成数据库拜访。
  2. 多数据源: 在 ShardingJDBC 中,多数据源指的是将数据储存到多个数据库中。数据依据某种分片战略(如按照ID规模、哈希值等)散布在不同的数据库中。
  3. 读写别离: 读写别离是经过装备主库(写操作)和从库(读操作)来完成的。应用程序写入操作首要针对主库,读取操作可以涣散到多个从库中,从而提高数据库的读取功用和体系的可扩展性。

原理

ShardingJDBC 的中心组件和功用,包括一些相关代码片段以更好地理解其工作原理。

1. SQL 解析

SQL 解析是 ShardingJDBC 处理流程的起点。ShardingJDBC 运用 ANTLR(另一个语言辨认东西)作为 SQL 解析东西。

  • SQL解析类SQLParseEngine 是解析的进口点。它接纳原始 SQL 句子并依据数据库方言(MySQL、PostgreSQL 等)进行解析。
  • 解析进程:解析进程包括词法剖析和语法剖析,用于构建笼统语法树(AST)。AST 供给了 SQL 结构的具体视图,包括表名、列名、条件等。

SQLParseEngine 源码剖析

public final class SQLParseEngine {
    private final DatabaseType databaseType;
    private final String sql;
    private final LexerEngine lexerEngine;
    private final SQLStatementParser statementParser;
    public SQLParseEngine(DatabaseType databaseType, String sql, Properties properties, List<SQLToken> sqlTokens) {
        this.databaseType = databaseType;
        this.sql = sql;
        Lexer lexer = LexerFactory.newInstance(databaseType, sql);
        this.lexerEngine = new LexerEngine(lexer);
        this.statementParser = SQLStatementParserFactory.newInstance(databaseType, lexerEngine);
    }
    public SQLStatement parse() {
        lexerEngine.nextToken();
        return statementParser.parse();
    }
}

在上面的代码中,SQLParseEngine 运用了数据库类型(DatabaseType)和 SQL 句子来初始化。它首要创立一个 Lexer(词法解析器),然后运用这个 Lexer 创立一个 LexerEngine,而且依据数据库类型创立相应的 SQL 句子解析器。parse 办法最终回来一个 SQL 句子方针(SQLStatement),这个方针代表了解析后的 SQL 句子。

2. 路由计算

路由是依据分片战略和解分出的 SQL 信息,确认 SQL 应该履行在哪些具体的数据库和表上。

  • 路由类ShardingRouter 担任履行路由逻辑。
  • 路由战略:经过完成 ShardingStrategy 接口的类(如 StandardShardingStrategyComplexShardingStrategy),依据分片键和分片算法确认方针数据源。

ShardingRouter 源码剖析

public final class ShardingRouter {
    private final ShardingRule shardingRule;
    public ShardingRouter(ShardingRule shardingRule) {
        this.shardingRule = shardingRule;
    }
    public RoutingResult route(final SQLStatement sqlStatement) {
        // 省掉具体路由逻辑
    }
}

在这儿,ShardingRouter 经过 ShardingRule(包括分片战略和规矩)来进行初始化。route 办法接受一个 SQL 句子方针,并依据分片规矩回来路由成果。

3. SQL 改写

依据路由成果,ShardingJDBC 会改写原始 SQL,使其适用于方针的物理数据库和表。

  • 改写类SQLRewriteEngine 担任 SQL 改写。
  • 改写进程:依据路由成果和 AST,SQLRewriteEngine 会修改表名、增加额外的条件等,生成最终要履行的 SQL。

SQLRewriteEngine 源码剖析

public final class SQLRewriteEngine {
    private final SQLStatement sqlStatement;
    private final List<SQLToken> sqlTokens;
    public SQLRewriteEngine(SQLStatement sqlStatement) {
        this.sqlStatement = sqlStatement;
        this.sqlTokens = new LinkedList<>();
    }
    public String rewrite() {
        // 省掉具体改写逻辑
    }
}

这儿的 SQLRewriteEngine 接纳一个 SQL 句子方针,并依据路由成果和 SQL 句子中的令牌(SQLToken)列表来改写 SQL。

4. 履行计划生成与履行

生成 SQL 的履行计划并在相应的数据库实例上履行。

  • 履行类ShardingExecuteEngine 担任办理 SQL 的履行。
  • 履行进程:它或许涉及到并行查询、兼并成果集等操作。对于写操作,一般直接路由到主库;对于读操作,则或许涉及到多个从库。
public class ShardingExecuteEngine implements AutoCloseable {
    private final ExecutorService executorService;
    public ShardingExecuteEngine(int executorSize) {
        this.executorService = Executors.newFixedThreadPool(executorSize);
    }
    // 省掉履行办法
}

ShardingExecuteEngine 运用一个线程池来履行 SQL。这个类担任办理 SQL 的履行进程,包括或许的并行查询和成果集兼并。

5. 成果集兼并

对于查询操作,ShardingJDBC 需求兼并来自不同物理表或数据库的成果集。

  • 兼并类MergeEngine 担任成果集的兼并。
  • 兼并进程:依据不同的查询类型(聚合查询、排序查询等),MergeEngine 运用不同的兼并战略来保证回来给用户的是一个一致的成果集。
public final class MergeEngine {
    public MergedResult merge(List<QueryResult> queryResults, SQLStatement sqlStatement) {
        // 省掉兼并逻辑
    }
}

MergeEngine 担任将来自不同物理表或数据库的查询成果兼并成一个一致的成果集。它依据 SQL 句子的类型(如聚合查询、排序查询)来应用不同的兼并战略。

6. 散布式事务处理

处理散布式环境下的事务一致性。

  • 事务办理器ShardingTransactionManager 接口界说了事务办理的行为。
  • 具体完成:如 XAShardingTransactionManager 用于处理 XA 类型的散布式事务。

ShardingTransactionManager 接口和 XAShardingTransactionManager 完成

public interface ShardingTransactionManager {
    void begin();
    void commit();
    void rollback();
    // 省掉其他办法
}
public class XAShardingTransactionManager implements ShardingTransactionManager {
    // 完成散布式事务办理逻辑
}

ShardingTransactionManager 接口界说了事务办理的根本行为,如开始(begin)、提交(commit)和回滚(rollback)操作。XAShardingTransactionManager 是这个接口的一个完成,用于处理 XA 类型的散布式事务。

我们将假设有两个事务表:orderuser,而且这两个表需求依据不同的战略进行分片。一起,我们将设置四个数据源(两个主库和两个从库)来完成读写别离。

实践案例

场景设定

  1. 数据源ds0ds0_slaveds1ds1_slave。其间 ds0ds1 是主库,ds0_slaveds1_slave 是从库。
  2. 事务表orderuser
  3. 分片战略

    • order 表按照订单ID分片。
    • user 表按照用户ID分片。
  4. 读写别离:一切写操作都发生在主库,读操作可以分配到从库。

装备和代码完成

  1. 增加依靠:

    <dependency>
        <groupId>org.apache.shardingsphere</groupId>
        <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
        <version>YOUR_VERSION</version>
    </dependency>
    
  2. 装备文件 application.yml:

    spring:
      shardingsphere:
        datasource:
          names: ds0,ds0_slave,ds1,ds1_slave
          ds0:
            type: com.zaxxer.hikari.HikariDataSource
            driver-class-name: YOUR_DRIVER_CLASS
            jdbc-url: JDBC_URL_FOR_DS0
            username: YOUR_USERNAME
            password: YOUR_PASSWORD
          ds0_slave:
            type: com.zaxxer.hikari.HikariDataSource
            driver-class-name: YOUR_DRIVER_CLASS
            jdbc-url: JDBC_URL_FOR_DS0_SLAVE
            username: YOUR_USERNAME
            password: YOUR_PASSWORD
          ds1:
            type: com.zaxxer.hikari.HikariDataSource
            driver-class-name: YOUR_DRIVER_CLASS
            jdbc-url: JDBC_URL_FOR_DS1
            username: YOUR_USERNAME
            password: YOUR_PASSWORD
          ds1_slave:
            type: com.zaxxer.hikari.HikariDataSource
            driver-class-name: YOUR_DRIVER_CLASS
            jdbc-url: JDBC_URL_FOR_DS1_SLAVE
            username: YOUR_USERNAME
            password: YOUR_PASSWORD
        sharding:
          tables:
            order:
              actual-data-nodes: ds${0..1}.order_${0..1}
              table-strategy:
                inline:
                  sharding-column: id
                  algorithm-expression: order_${id % 2}
              database-strategy:
                inline:
                  sharding-column: user_id
                  algorithm-expression: ds${user_id % 2}
              key-generator:
                type: SNOWFLAKE
                column: id
            user:
              actual-data-nodes: ds${0..1}.user_${0..1}
              table-strategy:
                inline:
                  sharding-column: id
                  algorithm-expression: user_${id > 5000 ? 1 : 0}
              database-strategy:
                inline:
                  sharding-column: id
                  algorithm-expression: ds${id % 2}
              key-generator:
                type: SNOWFLAKE
                column: id
          master-slave-rules:
            ds0:
              master-data-source-name: ds0
              slave-data-source-names: ds0_slave
            ds1:
              master-data-source-name: ds1
              slave-data-source-names: ds1_slave
    

    这段装备是用于设置 Apache ShardingSphere(ShardingJDBC 的一个部分)的 YAML 格式的装备文件,专门用于 Spring Boot 项目。它界说了数据源(包括主从数据源),表的分片战略,以及主从复制规矩。让我们逐个部分进行具体解释:

    数据源装备

    datasource:
      names: ds0,ds0_slave,ds1,ds1_slave
      ds0:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: YOUR_DRIVER_CLASS
        jdbc-url: JDBC_URL_FOR_DS0
        username: YOUR_USERNAME
        password: YOUR_PASSWORD
      ...
    
    • names: 界说了一切数据源的称号,这儿有四个数据源:ds0, ds0_slave, ds1, ds1_slave
    • ds0, ds0_slave, ds1, ds1_slave: 分别界说了四个数据源的具体装备。
    • type: 数据源类型,这儿运用的是 HikariCP 连接池。
    • driver-class-name: 数据库驱动类。
    • jdbc-url: 数据库的 JDBC URL。
    • usernamepassword: 数据库的登录用户名和暗码。

    分片装备

    sharding:
      tables:
        order:
          actual-data-nodes: ds${0..1}.order_${0..1}
          table-strategy:
            inline:
              sharding-column: id
              algorithm-expression: order_${id % 2}
          database-strategy:
            inline:
              sharding-column: user_id
              algorithm-expression: ds${user_id % 2}
          key-generator:
            type: SNOWFLAKE
            column: id
        user:
          ...
    
    • sharding: 界说了分片的总体装备。
    • tables: 在这儿界说具体的表和它们的分片战略。
    • order: 这是一个表的称号。
    • actual-data-nodes: 界说实践的数据节点,ds${0..1}.order_${0..1} 表示 order 表在 ds0ds1 数据源上都有两个分片,即 order_0order_1
    • table-strategy: 界说表的分片战略。

      • sharding-column: 分片键,这儿运用 id
      • algorithm-expression: 分片算法表达式,这儿是简略的模 2 运算。
    • database-strategy: 界说数据库的分片战略,类似于表的分片战略。
    • key-generator: 界说主键生成战略,这儿运用的是雪花算法(SNOWFLAKE)。

    主从装备

    master-slave-rules:
      ds0:
        master-data-source-name: ds0
        slave-data-source-names: ds0_slave
      ds1:
        master-data-source-name: ds1
        slave-data-source-names: ds1_slave
    
    • master-slave-rules: 界说主从复制的规矩。
    • ds0ds1: 这儿界说了两组主从数据源。
    • master-data-source-name: 指定主数据源的称号。
    • slave-data-source-names: 指定从数据源的称号列表。
  3. 实体类和数据拜访层:

    界说 OrderUser 实体类,以及对应的 JPA 库房或 MyBatis 映射。

    Order 实体类

    @Entity
    @Table(name = "order")
    public class Order {
        @Id
        private Long id;
        @Column(name = "user_id")
     private Long userId;
        @Column(name = "order_amount")
        private BigDecimal orderAmount;
     // 标准的构造函数、getter 和 setter
        public Order() {
        }
        // ... 省掉其他构造函数、getter 和 setter 办法
        // ... 可以增加事务逻辑办法
    }
    

    User 实体类

    @Entity
    @Table(name = "user")
    public class User {
        @Id
        private Long id;
        @Column(name = "username")
        private String username;
        @Column(name = "email")
        private String email;
        // 标准的构造函数、getter 和 setter
        public User() {
        }
        // ... 省掉其他构造函数、getter 和 setter 办法
        // ... 可以增加事务逻辑办法
    }
    

    OrderRepository 接口

    @Repository
    public interface OrderRepository extends JpaRepository<Order, Long> {
        List<Order> findByUserId(Long userId);
        // ... 可以依据需求增加其他查询办法
    }
    

    UserRepository 接口

    @Repository
    public interface UserRepository extends JpaRepository<User, Long> {
        User findByUsername(String username);
        // ... 可以依据需求增加其他查询办法
    }
    

    补充一个mybatis的写法:

    @Mapper
    public interface UserMapper {
        @Select("SELECT * FROM user WHERE id = #{id}")
        User findById(Long id);
        @Insert("INSERT INTO user (username, email) VALUES (#{username}, #{email})")
        void insert(User user);
        // 更多的 MyBatis SQL 映射可以依据需求增加
    }
    

总结

ShardingJDBC 的源码完成体现了其作为一个数据库中间件框架的复杂性和灵活性。它将 SQL 解析、路由、改写、履行和成果集兼并等多个过程封装成一系列高度解耦的组件和接口。这种规划使得 ShardingJDBC 可以灵活地适应各种数据库和 SQL 方言,一起供给丰厚的分片战略和读写别离功用。