SpringBoot プロジェクトは読み取りと書き込みの分離をエレガントに実現 | JD Cloud 技術チーム

1. 読み書き分離の概要

Spring Boot を使用してデータベース アプリケーションを開発する場合、読み取りと書き込みを分離するのが一般的な最適化戦略です。読み取りと書き込みを分離すると、読み取り操作と書き込み操作が異なるデータベース インスタンスに割り当てられ、システムのスループットとパフォーマンスが向上します。

読み書き分離の実装は主に、実行時にデータベース接続を動的に切り替える仕組みであるダイナミックデータソース機能によって実現されます。これにより、アプリケーションはさまざまな条件や構成に基づいてさまざまなデータ ソースを選択し、より柔軟でスケーラブルなデータベース アクセスを実現できます。

2. 読み書き分離の実現 - 基本

1. マスターデータベースとスレーブデータベースの接続情報を設定する

# 主库配置
spring.datasource.master.jdbc-url=jdbc:mysql://ip:port/master?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false
spring.datasource.master.username=master
spring.datasource.master.password=123456
spring.datasource.master.driver-class-name=com.mysql.jdbc.Driver

# 从库配置
spring.datasource.slave.jdbc-url=jdbc:mysql://ip:port/slave?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false
spring.datasource.slave.username=slave
spring.datasource.slave.password=123456
spring.datasource.slave.driver-class-name=com.mysql.jdbc.Driver 

2. マスター データベースとスレーブ データベースのデータ ソース構成クラスを作成します。

マスター/スレーブだけでなく、複数の異なるデータベースでも、さまざまな条件制限と構成ファイルのプレフィックスを使用して、さまざまなデータ ソースの作成を完了できます。

メインデータベースのデータソース構成

@Configuration
@ConditionalOnProperty("spring.datasource.master.jdbc-url")
public class MasterDataSourceConfiguration {
    @Bean("masterDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.master")
    public DataSource masterDataSource() {
        return DataSourceBuilder.create().build();
    }
}

データベースデータソースからの設定

@Configuration
@ConditionalOnProperty("spring.datasource.slave.jdbc-url")
public class SlaveDataSourceConfiguration {
    @Bean("slaveDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.slave")
    public DataSource slaveDataSource() {
        return DataSourceBuilder.create().build();
    }
}

3. マスター/スレーブ データ ソース列挙の作成

public enum DataSourceTypeEnum {
    /**
     * 主库
     */
    MASTER,

    /**
     * 从库
     */
    SLAVE,
   ;
  
}

4. 動的ルーティング データ ソースを作成する

ここでスイッチが作成され、読み取り/書き込み分離の開始と終了を制御し、すべての操作をメイン ライブラリに切り替えることができます。次に、コンテキスト内のデータ ソース タイプに基づいて、異なるデータ ソース タイプの列挙を返します。

@Slf4j
public class DynamicRoutingDataSource extends AbstractRoutingDataSource {

    @Value("${DB_RW_SEPARATE_SWITCH:false}")
    private boolean dbRwSeparateSwitch;
    @Override
    protected Object determineCurrentLookupKey() {
        if(dbRwSeparateSwitch && DataSourceTypeEnum.SLAVE.equals(DataSourceContextHolder.getDataSourceType())) {
            log.info("DynamicRoutingDataSource 切换数据源到从库");
            return DataSourceTypeEnum.SLAVE;
        }
        log.info("DynamicRoutingDataSource 切换数据源到主库");
        // 根据需要指定当前使用的数据源,这里可以使用ThreadLocal或其他方式来决定使用主库还是从库
        return DataSourceTypeEnum.MASTER;
    }
}

5. 動的データソース構成クラスを作成する

動的データソースにマスターデータベースとスレーブデータベースのデータソースを追加し、列挙によりデータソースマップを作成し、上記ルートで返される列挙によりデータソースを切り替えられるようにします。

@Configuration
@ConditionalOnProperty("spring.datasource.master.jdbc-url")
public class DynamicDataSourceConfiguration {
    @Bean("dataSource")
    @Primary
    public DataSource dynamicDataSource(DataSource masterDataSource, DataSource slaveDataSource) {
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put(DataSourceTypeEnum.MASTER, masterDataSource);
        targetDataSources.put(DataSourceTypeEnum.SLAVE, slaveDataSource);

        DynamicRoutingDataSource dynamicDataSource = new DynamicRoutingDataSource();
        dynamicDataSource.setTargetDataSources(targetDataSources);
        dynamicDataSource.setDefaultTargetDataSource(masterDataSource);
        return dynamicDataSource;
    }
}

6. DatasourceContextHolder クラスを作成し、ThreadLocal を使用して現在のスレッドのデータ ソース タイプを保存します。

新しいスレッドを作成するときに、ThreadLocal のデータを正しく読み取ることができないという潜在的なリスクがあることに注意してください。新しいスレッドの開始が必要な場合は、TransmittableThreadLocal を使用して、親スレッドと子スレッドのデータを同期できます。Git アドレス: https://github.com/alibaba/transmittable-thread-local

public class DataSourceContextHolder {
    private static final ThreadLocal<DataSourceTypeEnum> contextHolder = new ThreadLocal<>();

    public static void setDataSourceType(DataSourceTypeEnum dataSourceType) {
        contextHolder.set(dataSourceType);
    }

    public static DataSourceTypeEnum getDataSourceType() {
        return contextHolder.get();
    }

    public static void clearDataSourceType() {
        contextHolder.remove();
    }
}

7. マスターおよびスレーブ データ ソースをマークするカスタム アノテーションを作成する

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MasterDataSource {
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface SlaveDataSource {
}

8. アスペクトクラスを作成し、データベース操作をインターセプトし、アノテーション設定に従ってデータソースパラメータを切り替えます。

@Aspect
@Component
public class DataSourceAspect {

    @Before("@annotation(xxx.MasterDataSource)")
    public void setMasterDataSource(JoinPoint joinPoint) {
        DataSourceContextHolder.setDataSourceType(DataSourceTypeEnum.MASTER);
    }

    @Before("@annotation(xxx.SlaveDataSource)")
    public void setSlaveDataSource(JoinPoint joinPoint) {
        DataSourceContextHolder.setDataSourceType(DataSourceTypeEnum.SLAVE);
    }

    @After("@annotation(xxx.MasterDataSource) || @annotation(xxx.SlaveDataSource)")
    public void clearDataSource(JoinPoint joinPoint) {
        DataSourceContextHolder.clearDataSourceType();
    }
}

9. サービス レイヤー メソッドでカスタム アノテーション タグを使用してデータ ソースをクエリします

@Service
public class TestService {
    @Autowired
    private TestDao testDao;

    @SlaveDataSource
    public Test test() {
        return testDao.queryByPrimaryKey(11L);
    }
}

10. データソース自動構成クラスを除外する

自動構成クラスを除外しないと、複数の dataSource オブジェクトの初期化で問題が発生します。

SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})

3. 高度な読み書き分離を実現

1. 接続プールを使用する (例: ヒカリ)

リンク構成を変更し、リンクプール関連の構成を追加します。

# 主库配置
spring.datasource.master.jdbc-url=jdbc:mysql://ip:port/master?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false
spring.datasource.master.username=master
spring.datasource.master.password=123456
spring.datasource.master.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.master.type=com.zaxxer.hikari.HikariDataSource
spring.datasource.master.hikari.name=master
spring.datasource.master.hikari.minimum-idle=5
spring.datasource.master.hikari.idle-timeout=30
spring.datasource.master.hikari.maximum-pool-size=10
spring.datasource.master.hikari.auto-commit=true
spring.datasource.master.hikari.pool-name=DatebookHikariCP
spring.datasource.master.hikari.max-lifetime=1800000
spring.datasource.master.hikari.connection-timeout=30000
spring.datasource.master.hikari.connection-test-query=SELECT 1

# 从库配置
spring.datasource.slave.jdbc-url=jdbc:mysql://ip:port/slave?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false
spring.datasource.slave.username=root
spring.datasource.slave.password=123456
spring.datasource.slave.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.slave.type=com.zaxxer.hikari.HikariDataSource
spring.datasource.slave.hikari.name=master
spring.datasource.slave.hikari.minimum-idle=5
spring.datasource.slave.hikari.idle-timeout=30
spring.datasource.slave.hikari.maximum-pool-size=10
spring.datasource.slave.hikari.auto-commit=true
spring.datasource.slave.hikari.pool-name=DatebookHikariCP
spring.datasource.slave.hikari.max-lifetime=1800000
spring.datasource.slave.hikari.connection-timeout=30000
spring.datasource.slave.hikari.connection-test-query=SELECT 1

2. mybatis を統合し、書き込み時にメイン ライブラリに強制的に切り替える

設定は不要で、通常通りmybatisを組み込むことで読み書き分離機能を利用できます。

mybatis のインターセプターを使用すると、書き込み操作中にメイン ライブラリに強制的に切り替えることができます。

@Intercepts({
        @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
})
@Component
public class WriteInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 获取 SQL 类型
        DataSourceTypeEnum dataSourceType = DataSourceContextHolder.getDataSourceType();
        if(DataSourceTypeEnum.SLAVE.equals(dataSourceType)) {
            DataSourceContextHolder.setDataSourceType(DataSourceTypeEnum.MASTER);
        }
        try {
            // 执行 SQL
            return invocation.proceed();
        } finally {
            // 恢复数据源  考虑到写入后可能会反查,后续都走主库
            // DataSourceContextHolder.setDataSourceType(dataSourceType);
        }
    }
}



著者: JD ヘルス スーマン

出典: JD Cloud Developer Community 転送する場合は出典を明記してください

Alibaba Cloudが深刻な障害に見舞われ、全製品が影響(復旧) Tumblr がロシアのオペレーティングシステムAurora OS 5.0 を冷却新しいUIが公開 Delphi 12とC++ Builder 12、RAD Studio 12多くのインターネット企業がHongmengプログラマーを緊急採用UNIX時間17 億時代に突入しようとしている (すでに突入している) Meituan が兵力を募集し、Hongmeng システム アプリの開発を計画Amazon が Linux 上の .NET 8 への Android の依存を取り除くために Linux ベースのオペレーティング システムを開発独立した規模はFFmpeg 6.1「Heaviside」がリリースされまし
{{名前}}
{{名前}}

おすすめ

転載: my.oschina.net/u/4090830/blog/10142653