シャーディング-JDBC -- シャーディング データベースおよびテーブル ミドルウェア

目次

1. ShardingSphere の概要

2. クイックスタート(サブテーブル)

1. データベースを作成する

2. 物理テーブルを作成する 

3. SpringBoot プロジェクトを作成し、依存関係を導入する 

4. application.properties に構成を追加します。

5. 対応するエンティティ クラスを作成し、MyBatis-Plus を使用して CRUD を迅速に構築します

6. 主な起動クラスの構成

7. テストを書く

3. サブデータベースとサブテーブルを試してみる

1. db_device_1 データベースを作成します。そして、データベースに 2 つの物理テーブルを作成します。

2. データソース構成を調整する

3. テストクラスを実行する

4. サブデータベースおよびサブテーブルに対してクエリを作成する

1. device_id に基づいたクエリ

2. device_id 範囲に基づいたクエリ

4. サブデータベースとサブテーブルのコア知識ポイント

1. 中心となる概念

2. シャーディングとシャーディング戦略

1) シャーディングキー

2) 断片化アルゴリズム

3) シャーディング戦略

3. 断片化戦略の実施

1) Standard 標準シャーディング戦略の正確なシャーディング

2) 標準標準フラグメンテーション戦略のスコープフラグメンテーション 

3) 複雑な断片化戦略

4) ヒント強制ルーティングポリシー

 4. バインディングテーブル

5. 放送リスト

5. 読み取りと書き込みの分離を実現 

1. マスタースレーブ同期データベースを構築する

2. sharding-jdbc を使用して読み取りと書き込みの分離を実現する 

6. 実装原理 - 接続モード

6.1.接続モード

6.1.1.メモリ制限モード 

6.1.2. 接続制限モード

6.2. 自動実行エンジン

1. ShardingSphere の概要

        Apache ShardingSphere は、オープンソースの分散データベース ソリューションのエコシステムであり、JDBC、Proxy、Sidecar (計画中) という 3 つの製品で構成されており、これらの製品は個別に導入でき、ハイブリッド導入と併用もサポートします。いずれも標準化されたデータ水平拡張、分散トランザクション、分散ガバナンスなどの機能を提供し、Java同型、異種言語、クラウドネイティブなど多様なアプリケーションシナリオに適用できます。

        Apache ShardingSphere は、分散シナリオでリレーショナル データベースのコンピューティング機能とストレージ機能を完全かつ合理的に利用することを目的としていますが、新しいリレーショナル データベースを実装するわけではありません。リレーショナル データベースは、現在でも大きな市場シェアを占めており、企業の基幹システムの基盤となっています。将来的にそのデータベースを揺るがすのは難しいでしょう。私たちは、破壊するのではなく、オリジナルのベースで増分を提供することに重点を置いています。

公式ウェブサイト: Apache ShardingSphere

2. クイックスタート(サブテーブル)

1. データベースを作成する

db_device_0 という名前のデータベースを作成します。

2. 物理テーブルを作成する 

論理的には、tb_device はデバイス情報を記述するテーブルを表しますが、サブテーブルの概念を反映させるために、tb_device テーブルを 2 つに分割します。したがって、 tb_device は論理テーブルであり、 tb_device_0 と tb_device_1 は論理テーブルの物理テーブルです。

CREATE TABLE `tb_device_0` (
 `device_id` bigint NOT NULL AUTO_INCREMENT,
 `device_type` int DEFAULT NULL,
 PRIMARY KEY (`device_id`)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb3;


CREATE TABLE `tb_device_1` (
 `device_id` bigint NOT NULL AUTO_INCREMENT,
 `device_type` int DEFAULT NULL,
 PRIMARY KEY (`device_id`)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8mb3

3. SpringBoot プロジェクトを作成し、依存関係を導入する 

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.16</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.22</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shardingsphere</groupId>
            <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
            <version>4.1.1</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.49</version>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.0.5</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

4. application.properties に構成を追加します。

# 配置真实数据源
spring.shardingsphere.datasource.names=ds1
# 配置第 1 个数据源
spring.shardingsphere.datasource.ds1.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.ds1.url=jdbc:mysql://localhost:3306/db_device_0?serverTimezone=UTC&characterEncoding=utf-8&useSSL=false
spring.shardingsphere.datasource.ds1.username=db_device_0
spring.shardingsphere.datasource.ds1.password=db_device_0
# 配置物理表
spring.shardingsphere.sharding.tables.tb_device.actual-data-nodes=ds1.tb_device_$->{0..1}

# 配置分表策略:根据device_id作为分⽚的依据(分⽚键、分片算法)

# 将device_id作为分片键
spring.shardingsphere.sharding.tables.tb_device.table-strategy.inline.sharding-column=device_id
# 用device_id % 2 来作为分片算法 奇数会存入 tb_device_1 偶数会存入 tb_device_0
spring.shardingsphere.sharding.tables.tb_device.table-strategy.inline.algorithm-expression=tb_device_$->{device_id%2}
# 开启SQL显示
spring.shardingsphere.props.sql.show = true

5. 対応するエンティティ クラスを作成し、MyBatis-Plus を使用して CRUD を迅速に構築します

package com.my.sharding.shperejdbc.demo.entity;

import lombok.Data;

@Data
public class TbDevice {

    private Long deviceId;
    private Integer deviceType;
}

6. 主な起動クラスの構成

package com.my.sharding.shperejdbc.demo;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

// 配置mybatis扫描的mapper!
@MapperScan("com.my.shardingshpere.jdbc.demo.mapper")
@SpringBootApplication
public class MyShardingShpereJdbcDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(MyShardingShpereJdbcDemoApplication.class, args);
    }

}

7. テストを書く

@SpringBootTest
class MyShardingShpereJdbcDemoApplicationTests {

    @Autowired
    DeviceMapper deviceMapper;

    @Test
    void testInitData(){
        for (int i = 0; i < 10; i++) {
            TbDevice tbDevice = new TbDevice();
            tbDevice.setDeviceId((long) i);
            tbDevice.setDeviceType(i);
            deviceMapper.insert(tbDevice);
        }
    }

}

データベースを実行して表示します。

フラグメンテーション戦略によれば、これら 10 個のデータのうち、ID が奇数のデータは tb_device_1 テーブルに挿入され、ID が奇数のデータは tb_device_0 テーブルに挿入されることがわかります。 

3. サブデータベースとサブテーブルを試してみる

1. db_device_1 データベースを作成します。そして、データベースに 2 つの物理テーブルを作成します。

CREATE TABLE `tb_device_0` (
 `device_id` bigint NOT NULL AUTO_INCREMENT,
 `device_type` int DEFAULT NULL,
 PRIMARY KEY (`device_id`)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb3;


CREATE TABLE `tb_device_1` (
 `device_id` bigint NOT NULL AUTO_INCREMENT,
 `device_type` int DEFAULT NULL,
 PRIMARY KEY (`device_id`)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8mb3

2. データソース構成を調整する

2 つのデータ ソースを提供し、以前に作成した 2 つの MySQL データベースをデータ ソースとして使用し、シャーディング戦略を作成します。

# 配置真实数据源
spring.shardingsphere.datasource.names=ds0,ds1
# 配置第 1 个数据源
spring.shardingsphere.datasource.ds0.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.ds0.url=jdbc:mysql://localhost:3306/db_device_0?serverTimezone=UTC&characterEncoding=utf-8&useSSL=false
spring.shardingsphere.datasource.ds0.username=db_device_0
spring.shardingsphere.datasource.ds0.password=db_device_0

# 配置第 1 个数据源
spring.shardingsphere.datasource.ds1.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.ds1.url=jdbc:mysql://localhost:3306/db_device_1?serverTimezone=UTC&characterEncoding=utf-8&useSSL=false
spring.shardingsphere.datasource.ds1.username=db_device_1
spring.shardingsphere.datasource.ds1.password=db_device_1

# 配置物理表
spring.shardingsphere.sharding.tables.tb_device.actual-data-nodes=ds$->{0..1}.tb_device_$->{0..1}

# 配置分库策略 
spring.shardingsphere.sharding.default-database-strategy.inline.sharding-column=device_id
# ⾏表达式分⽚策略 使⽤Groovy的表达式
spring.shardingsphere.sharding.default-database-strategy.inline.algorithm-expression=ds$->{device_id%2}



# 配置分表策略:根据device_id作为分⽚的依据(分⽚键、分片算法)
# 将device_id作为分片键
spring.shardingsphere.sharding.tables.tb_device.table-strategy.inline.sharding-column=device_id
spring.shardingsphere.sharding.default-database-strategy.inline.sharding-column=device_id
# ⾏表达式分⽚策略 使⽤Groovy的表达式
# 用device_id % 2 来作为分片算法 奇数会存入 tb_device_1 偶数会存入 tb_device_0
spring.shardingsphere.sharding.tables.tb_device.table-strategy.inline.algorithm-expression=tb_device_$->{device_id%2}
# 开启SQL显示
spring.shardingsphere.props.sql.show = true

以前の構成と比較して、今回は 2 つのデータベースの断片化戦略が追加されており、どのデータベースを保存するかは device_id のパリティ特性に基づいて決定されます。同時に、データベースとテーブル間の関係を決定するために Groovy スクリプトが使用されました。

ds$->{0..1}.tb_device_$->{0..1}

に相当:

ds0.tb_device_0
ds0.tb_device_1
ds1.tb_device_0
ds1.tb_device_1

3. テストクラスを実行する

結果: device_id の奇数番目のデータは ds1.tb_device_1 テーブルに格納され、偶数番目のデータは ds0.tb_device_0 テーブルに格納されることがわかります。

4. サブデータベースおよびサブテーブルに対してクエリを作成する

1. device_id に基づいたクエリ

    /**
     * 根据device_id查询
     */
    @Test
    void testQueryByDeviceId(){
        QueryWrapper<TbDevice> wrapper = new QueryWrapper<>();
        wrapper.eq("device_id",1);
        List<TbDevice> list = deviceMapper.selectList(wrapper);
        list.stream().forEach(e->{
            System.out.println(e);
        });

    }

結果: TbDevice(deviceId=1、deviceType=1) 

クエリされたライブラリは tb_device_1 です

2. device_id 範囲に基づいたクエリ

    /**
     * 根据 device_id 范围查询
     */
    @Test
    void testDeviceByRange(){
        QueryWrapper<TbDevice> wrapper = new QueryWrapper<>();
        wrapper.between("device_id",1,10);
        List<TbDevice> devices = deviceMapper.selectList(wrapper);
        devices.stream().forEach(e->{
            System.out.println(e);
        });
    }

結果:

Error querying database.  Cause: java.lang.IllegalStateException: Inline strategy cannot support this type sharding:RangeRouteValue(columnName=device_id, tableName=tb_device, valueRange=[1‥10])

理由:インライン断片化戦略は範囲クエリをサポートできません。

4. サブデータベースとサブテーブルのコア知識ポイント

1. 中心となる概念

        断片化戦略を理解する前に、まず論理テーブル、実テーブル、データ ノード、バインディング テーブル、ブロードキャスト テーブルという重要な概念を理解しましょう。

  • 論理テーブル

        水平分割されたデータベース(テーブル)の論理やデータ構造が同じテーブルの総称。例: 注文データは主キーの仮数に基づいて 10 個のテーブル (t_order_0 ~ t_order_9) に分割され、それらの論理テーブル名は t_order です。

  • 実テーブル

        シャードデータベースに実際に存在する物理テーブル。つまり、前の例では t_order_0 から t_order_9 です。

  • データノード

        データ断片化の最小単位。これは、データ ソース名とデータ テーブルで構成されます (例: ds_0.t_order_0)。

  • バインディングテーブル

        一貫したパーティショニング ルールを持つメイン テーブルとサブテーブルを指します。例: t_order テーブルと t_order_item テーブルは両方とも order_id に従って分割されているため、2 つのテーブルは相互にバインドされています。バインドされたテーブル間の複数テーブルの相関クエリにはデカルト積相関がなくなり、相関クエリの効率が大幅に向上します。たとえば、SQL が次の場合:

SELECT i.* FROM t_order o JOIN t_order_item i ON o.order_id=i.order_id
WHERE o.order_id in (10, 11);

        バインディング テーブル リレーションシップが構成されていない場合、分割キー order_id が値 10 を 0 番目のスライスにルーティングし、値 11 を 1 番目のスライスにルーティングすると仮定すると、ルーティングされる SQL は 4 になるはずで、これらはデカルト積として表示されます。

SELECT i.* FROM t_order_0 o JOIN t_order_item_0 i ON
o.order_id=i.order_id WHERE o.order_id in (10, 11);
SELECT i.* FROM t_order_0 o JOIN t_order_item_1 i ON
o.order_id=i.order_id WHERE o.order_id in (10, 11);
SELECT i.* FROM t_order_1 o JOIN t_order_item_0 i ON
o.order_id=i.order_id WHERE o.order_id in (10, 11);
SELECT i.* FROM t_order_1 o JOIN t_order_item_1 i ON
o.order_id=i.order_id WHERE o.order_id in (10, 11);

        バインディング テーブルの関係を構成した後、ルーティング SQL は 2 になるはずです。

SELECT i.* FROM t_order_0 o JOIN t_order_item_0 i ON
o.order_id=i.order_id WHERE o.order_id in (10, 11);
SELECT i.* FROM t_order_1 o JOIN t_order_item_1 i ON
o.order_id=i.order_id WHERE o.order_id in (10, 11);

         このうち t_order は FROM の一番左側にあり、ShardingSphere はこれをバインディング テーブル全体のメイン テーブルとして使用します。すべてのルーティング計算ではメイン テーブルのストラテジのみが使用され、その後、t_order_item テーブルのパーティション計算では t_order の条件が使用されます。したがって、バインドされたテーブル間のパーティション キーはまったく同じである必要があります。

2. シャーディングとシャーディング戦略

1) シャーディングキー

シャーディングに使用されるデータベース フィールドは、データベース (テーブル) を水平分割するためのキー フィールドです。例: 順序テーブルの順序主キーの仮数がモジュロで分割されている場合、順序主キーは分割フィールドになります。SQL に断片化フィールドがない場合、完全なルーティングが実行され、パフォーマンスが低下します。ShardingSphere は、単一のシャーディング フィールドのサポートに加えて、複数のフィールドに基づくシャーディングもサポートします。

2) 断片化アルゴリズム

        =、>=、.、<、BETWEEN、IN による分割をサポートする分割アルゴリズムを通じてデータを分割します。断片化アルゴリズムはアプリケーション開発者自身が実装する必要があり、実現可能な柔軟性は非常に高いです。現在、4 つのフラグメンテーション アルゴリズムが提供されています。フラグメンテーション アルゴリズムはビジネス実装と密接に関連しているため、組み込みのフラグメンテーション アルゴリズムは提供されません。代わりに、フラグメンテーション戦略を通じてさまざまなシナリオが抽出され、より高いレベルの抽象化が提供され、アプリケーション開発者が自身で実装するためのインターフェイスが提供されます。

  • 正確な断片化アルゴリズム

        PreciseShardingAlgorithm に対応します。これは、単一のキーを分割キーとして使用して = と IN を分割するシナリオを処理するために使用されます。StandardShardingStrategy と一緒に使用する必要があります。

  • 範囲分割アルゴリズム

        RangeShardingAlgorithm に対応し、分割を実行するために BETWEEN AND、>、=、<= の分割キーとして単一のキーを使用するシナリオを処理するために使用されます。StandardShardingStrategy と一緒に使用する必要があります。

  • 複合フラグメンテーションアルゴリズム

        ComplexKeysShardingAlgorithm に対応し、シャーディングのシャーディング キーとして複数のキーを使用するシナリオを処理するために使用されます。複数のシャーディング キーを含むロジックは比較的複雑であり、アプリケーション開発者自身がその複雑さに対処する必要があります。ComplexShardingStrategy と一緒に使用する必要があります。

  • ヒント断片化アルゴリズム

        HintShardingAlgorithm に対応し、ヒントがシャーディングに使用されるシナリオを処理するために使用されます。HintShardingStrategy と一緒に使用する必要があります。

3) シャーディング戦略

シャーディング キーとシャーディング アルゴリズムが含まれます。シャーディング アルゴリズムの独立性により、個別に抽出されます。シャーディング操作に実際に使用できるのは、シャーディング キー + シャーディング アルゴリズム、つまりシャーディング戦略です。現在、5 つの断片化戦略が提供されています。

  • 標準のシャーディング戦略

        StandardShardingStrategy に対応します。SQL ステートメントの =、>、=、、=、<= のシャーディングを提供します。RangeShardingAlgorithm が構成されていない場合、SQL の BETWEEN AND は完全なデータベース ルーティングに従って処理されます。

  • 複合シャーディング戦略

        ComplexShardingStrategy に対応します。複合断片化戦略。SQL ステートメント内の =、>、=、<=、IN、および BETWEEN AND の分割操作のサポートを提供します。ComplexShardingStrategy は複数のシャード キーをサポートしています。複数のシャード キー間の複雑な関係のため、過度のカプセル化は実行されません。代わりに、シャード キー値の組み合わせとシャード演算子を直接透過的にシャーディング アルゴリズムに送信します。アプリケーションによって完全に実装されます。開発者に最大限の柔軟性を提供します。

  • 行式シャーディング戦略

        InlineShardingStrategy に対応します。Groovy 式を使用すると、SQL ステートメント内の = と IN の分割操作がサポートされ、単一の分割キーのみがサポートされます。単純なフラグメンテーション アルゴリズムの場合、単純な構成で使用して、次のような面倒な Java コード開発を回避できます。 t_user_$->{u_id % 8} は、t_user テーブルが u_id モジュロ 8 に従って 8 つのテーブルに分割されることを意味します。名前は t_user_0 ~ t_user_7 です。

  • ヒントシャーディング戦略

        HintShardingStrategy に対応します。SQLからシャーディング値を抽出するのではなく、ヒント経由でシャーディング値を指定してシャーディングを実行する戦略。

  • 断片化戦略はありません

        NoneShardingStrategy に対応します。断片化されていない戦略。

3. 断片化戦略の実施

1) Standard 標準シャーディング戦略の正確なシャーディング

        Standard では、標準シャーディング戦略をシャーディング データベースとシャーディング テーブルでそれぞれ構成できます。構成する場合、分割キー、正確な分割、または範囲分割を指定する必要があります。

  • シャードの正確なシャーディングを構成する
# 配置分库策略 为 标准分片策略的精准分片
#standard
spring.shardingsphere.sharding.default-databaswe-strategy.standard.sharding-column=device_id
spring.shardingsphere.sharding.default-database-strategy.standard.precise-algorithm-class-name=com.my.sharding.shperejdbc.demo.sharding.algorithm.database.MyDataBasePreciseAlgorithm

正確なセグメンテーション アルゴリズムを実装する実装クラスを提供する必要があります。このアルゴリズムでは、正確なセグメンテーションのロジックがインラインの行式と同じ意味を持つことができます。

import org.apache.shardingsphere.api.sharding.standard.PreciseShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.standard.PreciseShardingValue;

import java.util.Collection;

public class MyDataBasePreciseAlgorithm implements PreciseShardingAlgorithm<Long> {
    /**
     *
     *  数据库标准分片策略
     * @param collection     数据源集合
     * @param preciseShardingValue 分片条件
     * @return
     */
    @Override
    public String doSharding(Collection<String> collection, PreciseShardingValue<Long> preciseShardingValue) {
        // 获取逻辑表明 tb_device
        String logicTableName = preciseShardingValue.getLogicTableName();
        // 获取分片键
        String columnName = preciseShardingValue.getColumnName();
        // 获取分片键的具体值
        Long value = preciseShardingValue.getValue();
        //根据分⽚策略:ds$->{device_id % 2} 做精确分⽚
        String shardingKey = "ds"+(value%2);
        if(!collection.contains(shardingKey)){
            throw new UnsupportedOperationException("数据源:"+shardingKey+"不存在!");
        }
        return shardingKey;
    }

}
  • シャーディングテーブルの正確なシャーディングを構成する
# 配置分表策略 为 标准分片策略的精准分片
spring.shardingsphere.sharding.tables.tb_device.table-strategy.standard.sharding-column=device_id
spring.shardingsphere.sharding.tables.tb_device.table-strategy.standard.precise-algorithm-class-name=com.my.sharding.shperejdbc.demo.sharding.algorithm.table.MyTablePreciseAlgorithm

同時に、テーブルシャーディングのための正確なシャーディングアルゴリズムの実装クラスを提供する必要があります。

import org.apache.shardingsphere.api.sharding.standard.PreciseShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.standard.PreciseShardingValue;

import java.util.Collection;

public class MyTablePreciseAlgorithm implements PreciseShardingAlgorithm<Long> {
    @Override
    public String doSharding(Collection<String> collection, PreciseShardingValue<Long> preciseShardingValue) {
        String logicTableName = preciseShardingValue.getLogicTableName(); // 获取逻辑表名
        Long value = preciseShardingValue.getValue(); // 获取具体分片键值
        String shardingKey = logicTableName+"_"+(value % 2);
        if(!collection.contains(shardingKey)){
            throw new UnsupportedOperationException("数据表:"+shardingKey+"不存在!");
        }
        return shardingKey;
    }
}

テスト前に ID に基づいて正確にクエリを実行するテスト ケースは、以前と同じ効果があります。ID に基づいて特定のデータベース内のテーブルにクエリを実行します。

2) 標準標準フラグメンテーション戦略のスコープフラグメンテーション 

  • シャーディング ライブラリの範囲シャーディングの構成
spring.shardingsphere.sharding.default-databaswe-strategy.standard.sharding-column=device_id
spring.shardingsphere.sharding.default-database-strategy.standard.precise-algorithm-class-name=com.my.sharding.shperejdbc.demo.sharding.algorithm.database.MyDataBasePreciseAlgorithm
spring.shardingsphere.sharding.default-database-strategy.standard.range-algorithm-class-name=com.my.sharding.shperejdbc.demo.sharding.algorithm.database.MyDataBaseRangeAlgorithm

範囲クエリアルゴリズムの実装クラスを提供します。

import org.apache.shardingsphere.api.sharding.standard.RangeShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.standard.RangeShardingValue;

import java.util.Collection;

public class MyDataBaseRangeAlgorithm implements RangeShardingAlgorithm<Long> {
    /**
     *  直接返回所有的数据源
     *  由于范围查询,需要在两个库的两张表中查。
     * @param collection 具体的数据源集合
     * @param rangeShardingValue
     * @return
     */
    @Override
    public Collection<String> doSharding(Collection<String> collection, RangeShardingValue<Long> rangeShardingValue) {
        return collection;
    }
}
  • シャーディングテーブルの範囲シャーディングを構成する
spring.shardingsphere.sharding.tables.tb_device.table-strategy.standard.sharding-column=device_id
spring.shardingsphere.sharding.tables.tb_device.table-strategy.standard.precise-algorithm-class-name=com.my.sharding.shperejdbc.demo.sharding.algorithm.table.MyTablePreciseAlgorithm
spring.shardingsphere.sharding.tables.tb_device.table-strategy.standard.range-algorithm-class-name=com.my.sharding.shperejdbc.demo.sharding.algorithm.table.MyTableRangeAlgorithm

範囲クエリ アルゴリズムの実装クラスを提供します。

import org.apache.shardingsphere.api.sharding.standard.RangeShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.standard.RangeShardingValue;

import java.util.Collection;

public class MyTableRangeAlgorithm implements RangeShardingAlgorithm<Long> {
    @Override
    public Collection<String> doSharding(Collection<String> collection, RangeShardingValue<Long> rangeShardingValue) {
        return collection;
    }
}

この時点で、範囲クエリのテスト ケースを再度実行したところ、成功したことがわかりました。

3) 複雑な断片化戦略

 @Test
 void queryDeviceByRangeAndDeviceType(){
     QueryWrapper<TbDevice> queryWrapper = new QueryWrapper<>();
     queryWrapper.between("device_id",1,10);
     queryWrapper.eq("device_type", 5);
     List<TbDevice> deviceList =
     deviceMapper.selectList(queryWrapper);
     System.out.println(deviceList);
 }

上記のテストコードの問題:

device_id で範囲クエリを実行する際、device_type に基づいて正確な検索を行う必要があります。2 つのライブラリ内の 3 つのテーブルもチェックする必要があることがわかりましたが、奇数の device_type は奇数ライブラリの奇数テーブルにのみ存在します。現時点では冗長です。不要なクエリが複数あります。

冗長な複数の検索を解決するには、複雑な断片化戦略を使用できます。 

  • 複雑な断片化戦略

        複数のフィールドの断片化戦略をサポートします。

# 配置分库策略 complex 传入多个分片键
spring.shardingsphere.sharding.default-database-strategy.complex.sharding-columns=device_id,device_type
spring.shardingsphere.sharding.default-database-strategy.complex.algorithm-class-name=com.sharding.algorithm.database.MyDataBaseComplexAlgorithm


# 配置分表策略 complex 传入多个分片键
spring.shardingsphere.sharding.tables.tb_device.table-strategy.complex.sharding-columns=device_id,device_type
spring.shardingsphere.sharding.tables.tb_device.table-strategy.complex.algorithm-class-name=com.sharding.algorithm.table.MyTableComplexAlgorithm
  • ブランチライブラリのアルゴリズム実装クラスを設定する
import org.apache.shardingsphere.api.sharding.complex.ComplexKeysShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.complex.ComplexKeysShardingValue;
import java.util.ArrayList;
import java.util.Collection;

public class MyDataBaseComplexAlgorithm implements ComplexKeysShardingAlgorithm<Integer> {
    /**
     *
     * @param collection
     * @param complexKeysShardingValue
     * @return  这一次要查找的数据节点集合
     */
    @Override
    public Collection<String> doSharding(Collection<String> collection, ComplexKeysShardingValue<Integer> complexKeysShardingValue) {
        Collection<Integer> deviceTypeValues = complexKeysShardingValue.getColumnNameAndShardingValuesMap().get("device_type");
        Collection<String> databases = new ArrayList<>();
        for (Integer deviceTypeValue : deviceTypeValues) {
            String databaseName = "ds"+(deviceTypeValue % 2);
            databases.add(databaseName);
        }
        return databases;
    }
}
  • サブテーブルのアルゴリズム実装クラスを設定する
import org.apache.shardingsphere.api.sharding.complex.ComplexKeysShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.complex.ComplexKeysShardingValue;
import java.util.ArrayList;
import java.util.Collection;

public class MyTableComplexAlgorithm implements ComplexKeysShardingAlgorithm<Integer> {
    @Override
    public Collection<String> doSharding(Collection<String> collection, ComplexKeysShardingValue<Integer> complexKeysShardingValue) {
        String logicTableName = complexKeysShardingValue.getLogicTableName();
        Collection<Integer> deviceTypeValues = complexKeysShardingValue.getColumnNameAndShardingValuesMap().get("device_type");
        Collection<String> tables = new ArrayList<>();
        for (Integer deviceTypeValue : deviceTypeValues) {
            tables.add(logicTableName+"_"+(deviceTypeValue%2));
        }
        return tables;
    }
}

テスト:

 データベースに一度だけクエリを実行した

4) ヒント強制ルーティングポリシー

ヒントは、SQL ステートメントの特性に関係なく、特定のライブラリ内の特定のテーブルへのルーティングを強制できます。

# 配置分库策略 ## ⾏表达式分⽚策略 使⽤Groovy的表达式
# inline
spring.shardingsphere.sharding.default-database-strategy.inline.sharding-column=device_id
spring.shardingsphere.sharding.default-database-strategy.inline.algorithm-expression=ds$->{device_id%2}

ヒントアルゴリズムの実装クラスを設定する

import org.apache.shardingsphere.api.sharding.hint.HintShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.hint.HintShardingValue;

import java.util.Arrays;
import java.util.Collection;

public class MyTableHintAlgorithm implements HintShardingAlgorithm<Long> {
    @Override
    public Collection<String> doSharding(Collection<String> collection, HintShardingValue<Long> hintShardingValue) {
        String logicTableName = hintShardingValue.getLogicTableName();

        String tableName = logicTableName + "_" +hintShardingValue.getValues().toArray()[0];
        if(!collection.contains(tableName)){
            throw new UnsupportedOperationException("数据表:"+tableName + "不存在");
        }
        return Arrays.asList(tableName);

    }
}

テストケース:

    @Test
    void testHint(){
        HintManager hintManager = HintManager.getInstance();
        hintManager.addTableShardingValue("tb_device",0); // 强制指定只查询tb_device_0表
        List<TbDevice> devices = deviceMapper.selectList(null);
        devices.stream().forEach(System.out::println);
    }

結果:

 4. バインディングテーブル

まずデカルト積の創発をシミュレーションしてみましょう。

  • 2 つのライブラリ用に tb_device_info_0 、 tb_device_info_1 テーブルを作成します。
CREATE TABLE `tb_device_info_0` (
 `id` bigint NOT NULL,
 `device_id` bigint DEFAULT NULL,
 `device_intro` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL,
 PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
  • tb_device テーブルと tb_device_info テーブルの断片化戦略を構成します。
#tb_device表的分⽚策略
spring.shardingsphere.sharding.tables.tb_device.actual-data-nodes=ds$->
{0..1}.tb_device_$->{0..1}
spring.shardingsphere.sharding.tables.tb_device.tablestrategy.inline.sharding-column=device_id
spring.shardingsphere.sharding.tables.tb_device.tablestrategy.inline.algorithm-expression=tb_device_$->{device_id%2}

#tb_device_info表的分⽚策略
spring.shardingsphere.sharding.tables.tb_device_info.actual-datanodes=ds$->{0..1}.tb_device_info_$->{0..1}
spring.shardingsphere.sharding.tables.tb_device_info.tablestrategy.inline.sharding-column=device_id
spring.shardingsphere.sharding.tables.tb_device_info.tablestrategy.inline.algorithm-expression=tb_device_info_$->{device_id%2}

両方のテーブルのパーティション キーは device_id です。

  • テストケースを作成してデータを挿入する
    @Test
    void testInsertDeviceInfo(){
        for (int i = 0; i < 10; i++) {
            TbDevice tbDevice = new TbDevice();
            tbDevice.setDeviceId((long) i);
            tbDevice.setDeviceType(i);
            deviceMapper.insert(tbDevice);

            TbDeviceInfo tbDeviceInfo = new TbDeviceInfo();
            tbDeviceInfo.setDeviceId((long) i);
            tbDeviceInfo.setDeviceIntro(""+i);
            deviceInfoMapper.insert(tbDeviceInfo);
        }
    }
  • 結合クエリにデカルト積が表示される
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.lc.entity.TbDeviceInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;

import java.util.List;
@Mapper
public interface DeviceInfoMapper extends BaseMapper<TbDeviceInfo> {

    @Select("select a.id,a.device_id,b.device_type,a.device_intro from tb_device_info a left join tb_device b on a.device_id = b.device_id")
    public List<TbDeviceInfo> queryDeviceInfo();
}
    @Test
    void testQueryDeviceInfo(){
        List<TbDeviceInfo> tbDeviceInfos = deviceInfoMapper.queryDeviceInfo();
        tbDeviceInfos.stream().forEach(System.out::println);
    }

結果:

ご覧のとおり、デカルト積が生成され、20 個のデータが見つかりました。 

  • バインディングテーブルの設定 
# 配置 绑定表
spring.shardingsphere.sharding.binding-tables[0]=tb_device,tb_device_info

再度クエリを実行すると、デカルト積は表示されなくなります。

5. 放送リスト

 device_type 列に対応する tb_device_type テーブルのデータを複数のテーブルに分割しないシナリオがあり、両方のライブラリにテーブル内の全量のデータが必要です。

  • 両方のデータベースに tb_device_type テーブルを作成します
CREATE TABLE `tb_device_type` (
 `type_id` int NOT NULL AUTO_INCREMENT,
 `type_name` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL,
 PRIMARY KEY (`type_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
  •  ブロードキャストリストの設定
#⼴播表配置
spring.shardingsphere.sharding.broadcast-tables=tb_device_type
spring.shardingsphere.sharding.tables.tb_device_type.key-generator.type=SNOWFLAKE
spring.shardingsphere.sharding.tables.tb_device_type.key-generator.column=type_id
  • テストケースを書く
    @Test
    void testInsertDeviceType(){
        TbDeviceType tbDeviceType = new TbDeviceType();
        tbDeviceType.setTypeId(1l);
        tbDeviceType.setTypeName("消防器材");
        deviceTypeMapper.insert(tbDeviceType);

        TbDeviceType tbDeviceType1 = new TbDeviceType();
        tbDeviceType1.setTypeId(2l);
        tbDeviceType1.setTypeName("健身器材");
        deviceTypeMapper.insert(tbDeviceType1);
    }

結果:

2 つのライブラリの両方の tb_device_types に 2 つのデータが挿入されています。

5. 読み取りと書き込みの分離を実現 

1. マスタースレーブ同期データベースを構築する

  • マスタースレーブ同期原理

        マスターはデータを binlog に書き込みます。スレーブは、マスター ノードの Binlog データをローカルのリレーログ ログ ファイルに読み取ります。このとき、スレーブは継続的にマスターと同期し、データはデータベースに格納されるのではなく、リレーログに存在します。したがって、スレーブはリレーログのデータをデータベースに書き込むスレッドを開始します。

  •  マスターデータベースとスレーブデータベースを準備する

usr/local/docker/mysql に docker-compose.yml を作成し、次のように記述します。

version: '3.1'
services:
  mysql:
    restart: "always"
    image: mysql:5.7.25
    container_name: mysql-test-master
    ports:
     - 3308:3308
    environment:
      TZ: Asia/Shanghai
      MYSQL_ROOT_PASSWORD: 123456
    command:
      --character-set-server=utf8mb4
      --collation-server=utf8mb4_general_ci
      --explicit_defaults_for_timestamp=true
      --lower_case_table_names=1
      --max_allowed_packet=128M
      --server-id=47
      --log_bin=master-bin
      --log_bin-index=master-bin.index
      --skip-name-resolve
      --sql-mode="STRICT_TRANS_TABLES,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION,NO_ZERO_DATE,NO_ZERO_IN_DATE,ERROR_FOR_DIVISION_BY_ZERO"
    volumes:
     - mysql-data:/var/lib/mysql
volumes:
  mysql-data:

version: '3.1'
services:
  mysql:
    restart: "always"
    image: mysql:5.7.25
    container_name: mysql-test-slave
    ports:
     - 3309:3309
    environment:
      TZ: Asia/Shanghai
      MYSQL_ROOT_PASSWORD: 123456
    command:
      --character-set-server=utf8mb4
      --collation-server=utf8mb4_general_ci
      --explicit_defaults_for_timestamp=true
      --lower_case_table_names=1
      --max_allowed_packet=128M
      --server-id=48
      --relay-log=slave-relay-bin
      --relay-log-index=slave-relay-bin.index
      --log-bin=mysql-bin
      --log-slave-updates=1
      --sql-mode="STRICT_TRANS_TABLES,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION,NO_ZERO_DATE,NO_ZERO_IN_DATE,ERROR_FOR_DIVISION_BY_ZERO"
    volumes:
     - mysql-data:/var/lib/mysql1
volumes:
  mysql-data:

docker-compose up -dの使用を開始する 

構成に注意してください。

メインライブラリ:

サービスID: サーバーID=47

binlog を有効にする: log_bin=master-bin

binlogインデックス:log_bin-index=master-bin.index

図書館から:

サービスID: サーバーID=48

リレーログを有効にする:relay-log-index=slave-relay-bin.index

リレーログを有効にする:relay-log=slave-relay-bin 

bash コマンドを使用してマスター メイン ライブラリ コンテナに入り、show master status を使用してレコード ファイル名とオフセットを表示します。

 bash コマンドを使用してスレーブ コンテナーに入り、次のコマンドを順番に実行します。

#登录从服务
mysql -u root -p;
#设置同步主节点:
CHANGE MASTER TO
MASTER_HOST='主库地址',
MASTER_PORT=3306,
MASTER_USER='root',
MASTER_PASSWORD='123456',
MASTER_LOG_FILE='master-bin.000006',
MASTER_LOG_POS=154;
#开启slave
start slave;

 この時点でマスタ・スレーブ同期クラスタが完成します。

メイン ライブラリに db_device データベースを作成し、ライブラリにテーブルを作成します。

CREATE TABLE `tb_user` (
 `id` bigint(20) NOT NULL AUTO_INCREMENT,
 `name` varchar(255) DEFAULT NULL,
 PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4;

更新後にスレーブ データベースにもテーブルが同期的に作成されたことがわかりました。

2. sharding-jdbc を使用して読み取りと書き込みの分離を実現する 

  • 設定ファイルの書き込み
spring.shardingsphere.datasource.names=s0,m0
#配置主数据源
spring.shardingsphere.datasource.m0.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.m0.url=jdbc:mysql://localhost:3306/db_device?serverTimezone=UTC&characterEncoding=utf-8&useSSL=false
spring.shardingsphere.datasource.m0.username=root
spring.shardingsphere.datasource.m0.password=123456

# 配置从数据源
spring.shardingsphere.datasource.s1.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.s1.url=jdbc:mysql://localhost:3306/db_device?serverTimezone=UTC&characterEncoding=utf-8&useSSL=false
spring.shardingsphere.datasource.s1.username=root
spring.shardingsphere.datasource.s1.password=123456

# 分配读写规则
spring.shardingsphere.sharding.master-slave-rules.ds0.master-data-source-name=m0
spring.shardingsphere.sharding.master-slave-rules.ds0.slave-data-source-names[0]=s0

# 确定实际表
spring.shardingsphere.sharding.tables.tb_user.actual-data-nodes=ds0.tb_user
# 确定主键⽣成策略
spring.shardingsphere.sharding.tables.t_dict.key-generator.column=id
spring.shardingsphere.sharding.tables.t_dict.key-generator.type=SNOWFLAKE
# 开启显示sql语句
spring.shardingsphere.props.sql.show = true
  • テストライトデータ
 @Test
 void testInsertUser(){
     for (int i = 0; i < 10; i++) {
     TbUser user = new TbUser();
     user.setName(""+i);
     userMapper.insert(user);
 }

        現時点では、すべてのデータはマスター データベースにのみ書き込まれ、その後スレーブ データベースに同期されます。

  • テスト読み取りデータ
 Test
 void testQueryUser(){
     List<TbUser> tbUsers = userMapper.selectList(null);
     tbUsers.forEach( tbUser -> System.out.println(tbUser));
 }

        このとき、すべてのデータはスレーブ データベースから読み取られます。

6. 実装原理 - 接続モード

        ShardingSphere は、一連の自動実行エンジンを使用して、実行のために基礎となるデータ ソースにルーティングおよび書き換えた後、実際の SQL を安全かつ効率的に送信します。JDBC を介して SQL をデータ ソースに直接送信して実行するだけではなく、実行リクエストをスレッド プールに直接入れて同時実行することもありません。データ ソース接続の作成とメモリ使用量によって発生する消費量のバランスを取ること、および同時実行性やその他の問題の合理的な利用を最大化することに、より注意を払っています。実行エンジンの目標は、リソース制御と実行効率のバランスを自動的に取ることです。

6.1.接続モード

リソース制御の観点        から、ビジネス ユーザーがデータベースにアクセスするための接続数は制限される必要がありますこれにより、特定のビジネス操作がリソースを過剰に占有し、データベース接続リソースが枯渇し、他のビジネスの通常のアクセスに影響を与えることを効果的に防ぐことができます。特にデータベース インスタンスに多くのシャード テーブルがある場合、シャード キーを含まない論理 SQL は、同じデータベース内の異なるテーブルに該当する多数の実 SQL を生成します。独立した接続の場合、1 つのクエリが間違いなく多くのリソースを占有することになります。(メモリ制限モード)

実行効率の観点        から見ると、シャード クエリごとに独立したデータベース接続を維持することで、マルチスレッドをより効果的に利用して実行効率を向上させることができます。データベース接続ごとに独立したスレッドを開くことで、I/O による消費を並行して処理できます。各フラグメントに対して独立したデータベース接続を維持すると、クエリ結果データがメモリに早まってロードされることも回避できます。独立したデータベース接続は、クエリ結果セットのカーソル位置への参照を保持し、対応するデータを取得する必要があるときにカーソルを移動できます(接続制限モード)

        結果セット カーソルを下に移動して結果をマージする方法は、ストリーミング マージと呼ばれます。すべての結果データをメモリにロードする必要がないため、メモリ リソースが効果的に節約され、それによってガベージ コレクションの頻度が削減されます。各シャード クエリが独立したデータベース接続を保持していることを保証できない場合は、データベース接続を再利用して次のシャード テーブルのクエリ結果セットを取得する前に、現在のクエリ結果セットをメモリにロードする必要がありますしたがって、ストリーミング マージが使用できる場合でも、このシナリオではメモリ マージに変質します。

         一方で、データベース接続リソースを制御および保護し、他方で、より適切なマージ モードを採用してミドルウェアのメモリ リソースを節約し、この 2 つの関係をどのように処理するかが ShardingSphere 実行エンジンに必要なことです。解決するための質問です。具体的には、ShardingSphere によって分割された後、SQL が特定のデータベース インスタンスの下で 200 のテーブルを操作する必要がある場合です。それでは、200 個の接続を作成して並列実行することを選択するべきでしょうか、それとも 1 つの接続を作成してシリアルに実行することを選択する必要があるのでしょうか? 効率とリソース制御のどちらをどのように選択すればよいでしょうか?

         上記のシナリオに対して、ShardingSphere はソリューションを提供します。これは接続モードの概念を提案しており、メモリ制限モード (MEMORY_STRICTLY) と接続制限モード (CONNECTION_STRICTLY) の 2 つのタイプに分けられます。

6.1.1.メモリ制限モード 

        このモードを使用する前提条件は、ShardingSphere が1 回の操作で消費されるデータベース接続の数を制限しないことです。実際に実行される SQL でデータベース インスタンス内の 200 のテーブルに対する操作が必要な場合は、テーブルごとに新しいデータベース接続を作成し、マルチスレッドで同時に処理して実行効率を最大化します。SQL 条件が満たされる場合、メモリ オーバーフローや頻繁なガベージ コレクションを防ぐために、ストリーミング マージが優先されます。

6.1.2. 接続制限モード

        このモードを使用する前提条件は、ShardingSphere が操作で消費されるデータベース接続の数を厳密に制御することです。実際に実行される SQL でデータベース インスタンス内の 200 のテーブルに対する操作が必要な場合、一意のデータベース接続のみが作成され、200 のテーブルがシリアルに処理されます。1 つの操作のフラグメントが異なるデータベースに分散している場合でも、異なるライブラリでの操作を処理するためにマルチスレッドが使用されますが、各ライブラリの各操作は依然として一意のデータベース接続を作成するだけです。これにより、1 つのリクエストに対して多くのデータベース接続が使用されることによって発生する問題を防ぐことができます。このモードでは常にメモリ結合が選択されます。

6.2. 自動実行エンジン

        ShardingSphere は、最初はどのモードを使用するかの決定をユーザー構成に委ね、開発者が自社のビジネスの実際のシナリオ要件に基づいてメモリ制限モードまたは接続制限モードを使用することを選択できるようにします。

        ユーザーの使用コストを削減し、接続モードを動的にするために、ShardingSphere は自動実行エンジンのアイデアを洗練し、接続モードの概念を内部で消化しました。ユーザーは、いわゆるメモリ制限モードや接続制限モードが何であるかを知る必要はありませんが、現在のシナリオに基づいて実行エンジンに最適な実行プランを自動的に選択させます。

        自動実行エンジンは、各 SQL 操作に対する接続モードの選択の粒度を調整します。SQL リクエストごとに、自動実行エンジンはルーティング結果に基づいてリアルタイムの計算とトレードオフを実行し、適切な接続モードを使用して自律的に実行して、リソース制御と効率の最適なバランスを実現します。自動実行エンジンの場合、ユーザーはmaxConnectionSizePerQueryを構成するだけで済みます。このパラメーターは、クエリ中に各データベースに許可される最大接続数を示します。

maxConnectionSizePerQueryで許可される範囲        内で、接続で実行する必要があるリクエストの数が 1 より大きい場合、それは現在のデータベース接続が対応するデータ結果セットを保持できないことを意味し、メモリ マージを使用する必要があります。逆に、When接続によって実行する必要があるリクエストの数が 1 である場合、現在のデータベース接続は対応するデータ結果セットを保持でき、ストリーミング マージを使用できることを意味します。各接続モードの選択は、各物理データベースに固有です。つまり、同じクエリ内で複数のデータベースにルーティングされる場合、各データベースの接続モードは必ずしも同じではなく、混在した形で存在する可能性があります。(ユーザーが設定したmaxConnectionSizePerQuery /データベース上で実行する必要があるすべての SQL の数が0 または 1 の場合、メモリ制限モードが使用されます。 1 より大きい場合、接続制限モードが使用されます。中古

おすすめ

転載: blog.csdn.net/weixin_53922163/article/details/127701570