SpringBoot 項目優雅實現讀寫分離

来源:https://www.cnblogs.com/jingdongkeji/archive/2023/11/13/17828805.html
-Advertisement-
Play Games

當使用Spring Boot開發資料庫應用時,讀寫分離是一種常見的優化策略。讀寫分離將讀操作和寫操作分別分配給不同的資料庫實例,以提高系統的吞吐量和性能。 ...


一、讀寫分離介紹

當使用Spring Boot開發資料庫應用時,讀寫分離是一種常見的優化策略。讀寫分離將讀操作和寫操作分別分配給不同的資料庫實例,以提高系統的吞吐量和性能。

讀寫分離實現主要是通過動態數據源功能實現的,動態數據源是一種通過在運行時動態切換資料庫連接的機制。它允許應用程式根據不同的條件或配置選擇不同的數據源,以實現更靈活和可擴展的資料庫訪問。

二、實現讀寫分離-基礎

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. 創建動態數據源配置類

將主資料庫和從資料庫的數據源添加到動態數據源中,並可以通過枚舉創建一個數據源 map,這樣就可以通過上面的路由返回的枚舉來切換數據源

@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層的方法上使用自定義註解標記查詢數據源

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

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

10. 排除掉數據源自動配置類

如果不排除自動配置類會導致初始化多個 dataSource 對象導致出現問題

SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})

三、實現讀寫分離-進階

1. 使用鏈接池,以Hikari為例

修改鏈接配置,加入鏈接池相關配置即可

# 主庫配置
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);
        }
    }
}

作者:京東健康 蘇曼

來源:京東雲開發者社區 轉發請註明來源


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 目錄 Welcome to YARP - 1.認識YARP並搭建反向代理服務 Welcome to YARP - 2.配置功能 2.1 - 配置文件(Configuration Files) 2.2 - 配置提供者(Configuration Providers) 2.3 - 配置過濾器(Confi ...
  • 思路 我發現 .NET Core WebAPi項目有一個與Springboot的不同之處,就是Springboot項目有自動裝配機制,他可以將在src下麵與啟動類在同一級包目錄下的類進行掃描註冊 而之前我瞭解到Springboot的自動裝配機制本質上也就是通過掃描對應包,然後進行通過它自身進行服務註 ...
  • Sql Server中Cross Apply關鍵字的使用 前言 在寫一個業務的時候,有1列數據如下: 車牌號 湘A00001/湘G00001 湘A00002/湘G00002 湘A00003/湘G00003/湘A8888888 湘A00004/湘G00004/湘A00001 我的查詢條件也是車牌號,我 ...
  • 壓縮和解壓指令 gzip/gunzip 指令 gzip:用於壓縮文件 gunzip:用於解壓的 基本語法: gzip 文件,壓縮文件,只能將文件壓縮為 .gz 文件。 gunzip 文件.gz,解壓縮文件命令。 zip/unzip 指令 zip:用於壓縮文件 unzip:用於解壓文件,這個在項目打包 ...
  • 包括套接字相關函數socket、bind、listen、accept、recv、send、connect;以及IO多路復用函數select和epoll的簡介 ...
  • 學習Linux,為了省錢不想買一臺雲伺服器,或者不想裝VMware虛擬機,win11可以通過這種方式安裝Linux(Ubuntu) 一、開啟Windows功能 1.在window11的搜索框內,搜索"Windows功能",出現了“啟用或關閉Windows功能”,點擊打開。 2.勾選"適用於Linux ...
  • 閱讀 h2 資料庫的源碼是一項複雜的任務,需要對資料庫原理、Java 語言和操作系統有深入的理解。可以從以下幾方面入手來完成。 ...
  • 雙寫 加密欄位和明文分別存到兩個欄位中 , 查詢只對明文進行操作 . (備註: 這種只是應對檢查或者設計的方式 , 對於程式沒有實際意義) 使用函數 利用mysql已有加解密的函數 , 在排序和模糊搜索之前解密數據 , 再進行排序或者模糊搜索 . (備註: 查詢速度受到很大影響 , 不能使用索引 ) ...
一周排行
    -Advertisement-
    Play Games
  • 前言 微服務架構已經成為搭建高效、可擴展系統的關鍵技術之一,然而,現有許多微服務框架往往過於複雜,使得我們普通開發者難以快速上手並體驗到微服務帶了的便利。為瞭解決這一問題,於是作者精心打造了一款最接地氣的 .NET 微服務框架,幫助我們輕鬆構建和管理微服務應用。 本框架不僅支持 Consul 服務註 ...
  • 先看一下效果吧: 如果不會寫動畫或者懶得寫動畫,就直接交給Blend來做吧; 其實Blend操作起來很簡單,有點類似於在操作PS,我們只需要設置關鍵幀,滑鼠點來點去就可以了,Blend會自動幫我們生成我們想要的動畫效果. 第一步:要創建一個空的WPF項目 第二步:右鍵我們的項目,在最下方有一個,在B ...
  • Prism:框架介紹與安裝 什麼是Prism? Prism是一個用於在 WPF、Xamarin Form、Uno 平臺和 WinUI 中構建鬆散耦合、可維護和可測試的 XAML 應用程式框架 Github https://github.com/PrismLibrary/Prism NuGet htt ...
  • 在WPF中,屏幕上的所有內容,都是通過畫筆(Brush)畫上去的。如按鈕的背景色,邊框,文本框的前景和形狀填充。藉助畫筆,可以繪製頁面上的所有UI對象。不同畫筆具有不同類型的輸出( 如:某些畫筆使用純色繪製區域,其他畫筆使用漸變、圖案、圖像或繪圖)。 ...
  • 前言 嗨,大家好!推薦一個基於 .NET 8 的高併發微服務電商系統,涵蓋了商品、訂單、會員、服務、財務等50多種實用功能。 項目不僅使用了 .NET 8 的最新特性,還集成了AutoFac、DotLiquid、HangFire、Nlog、Jwt、LayUIAdmin、SqlSugar、MySQL、 ...
  • 本文主要介紹攝像頭(相機)如何採集數據,用於類似攝像頭本地顯示軟體,以及流媒體數據傳輸場景如傳屏、視訊會議等。 攝像頭採集有多種方案,如AForge.NET、WPFMediaKit、OpenCvSharp、EmguCv、DirectShow.NET、MediaCaptre(UWP),網上一些文章以及 ...
  • 前言 Seal-Report 是一款.NET 開源報表工具,擁有 1.4K Star。它提供了一個完整的框架,使用 C# 編寫,最新的版本採用的是 .NET 8.0 。 它能夠高效地從各種資料庫或 NoSQL 數據源生成日常報表,並支持執行複雜的報表任務。 其簡單易用的安裝過程和直觀的設計界面,我們 ...
  • 背景需求: 系統需要對接到XXX官方的API,但因此官方對接以及管理都十分嚴格。而本人部門的系統中包含諸多子系統,系統間為了穩定,程式間多數固定Token+特殊驗證進行調用,且後期還要提供給其他兄弟部門系統共同調用。 原則上:每套系統都必須單獨接入到官方,但官方的接入複雜,還要官方指定機構認證的證書 ...
  • 本文介紹下電腦設備關機的情況下如何通過網路喚醒設備,之前電源S狀態 電腦Power電源狀態- 唐宋元明清2188 - 博客園 (cnblogs.com) 有介紹過遠程喚醒設備,後面這倆天瞭解多了點所以單獨加個隨筆 設備關機的情況下,使用網路喚醒的前提條件: 1. 被喚醒設備需要支持這WakeOnL ...
  • 前言 大家好,推薦一個.NET 8.0 為核心,結合前端 Vue 框架,實現了前後端完全分離的設計理念。它不僅提供了強大的基礎功能支持,如許可權管理、代碼生成器等,還通過採用主流技術和最佳實踐,顯著降低了開發難度,加快了項目交付速度。 如果你需要一個高效的開發解決方案,本框架能幫助大家輕鬆應對挑戰,實 ...