SpringBoot實(shí)現(xiàn)數(shù)據(jù)庫(kù)讀寫(xiě)分離的3種方法小結(jié)
一、數(shù)據(jù)庫(kù)讀寫(xiě)分離概述
在大型應(yīng)用系統(tǒng)中,隨著訪問(wèn)量的增加,數(shù)據(jù)庫(kù)常常成為系統(tǒng)的性能瓶頸。為了提高系統(tǒng)的讀寫(xiě)性能和可用性,讀寫(xiě)分離是一種經(jīng)典的數(shù)據(jù)庫(kù)架構(gòu)模式。它將數(shù)據(jù)庫(kù)讀操作和寫(xiě)操作分別路由到不同的數(shù)據(jù)庫(kù)實(shí)例,通常是將寫(xiě)操作指向主庫(kù)(Master),讀操作指向從庫(kù)(Slave)。
讀寫(xiě)分離的主要優(yōu)勢(shì):
- 分散數(shù)據(jù)庫(kù)訪問(wèn)壓力,提高系統(tǒng)的整體吞吐量
- 提升讀操作的性能和并發(fā)量
- 增強(qiáng)系統(tǒng)的可用性和容錯(cuò)能力
在SpringBoot應(yīng)用中,有多種方式可以實(shí)現(xiàn)數(shù)據(jù)庫(kù)讀寫(xiě)分離,本文將介紹三種主實(shí)現(xiàn)方案。
二、方案一:基于AbstractRoutingDataSource實(shí)現(xiàn)動(dòng)態(tài)數(shù)據(jù)源
這種方案是基于Spring提供的AbstractRoutingDataSource
抽象類,通過(guò)重寫(xiě)其中的determineCurrentLookupKey()
方法來(lái)實(shí)現(xiàn)數(shù)據(jù)源的動(dòng)態(tài)切換。
2.1 實(shí)現(xiàn)原理
AbstractRoutingDataSource
的核心原理是在執(zhí)行數(shù)據(jù)庫(kù)操作時(shí),根據(jù)一定的策略(通常基于當(dāng)前操作的上下文)動(dòng)態(tài)地選擇實(shí)際的數(shù)據(jù)源。通過(guò)在業(yè)務(wù)層或AOP攔截器中設(shè)置上下文標(biāo)識(shí),讓系統(tǒng)自動(dòng)判斷是讀操作還是寫(xiě)操作,從而選擇對(duì)應(yīng)的數(shù)據(jù)源。
2.2 具體實(shí)現(xiàn)步驟
第一步:定義數(shù)據(jù)源枚舉和上下文持有器
// 數(shù)據(jù)源類型枚舉 public enum DataSourceType { MASTER, // 主庫(kù),用于寫(xiě)操作 SLAVE // 從庫(kù),用于讀操作 } // 數(shù)據(jù)源上下文持有器 public class DataSourceContextHolder { private static final ThreadLocal<DataSourceType> contextHolder = new ThreadLocal<>(); public static void setDataSourceType(DataSourceType dataSourceType) { contextHolder.set(dataSourceType); } public static DataSourceType getDataSourceType() { return contextHolder.get() == null ? DataSourceType.MASTER : contextHolder.get(); } public static void clearDataSourceType() { contextHolder.remove(); } }
第二步:實(shí)現(xiàn)動(dòng)態(tài)數(shù)據(jù)源
public class DynamicDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return DataSourceContextHolder.getDataSourceType(); } }
第三步:配置數(shù)據(jù)源
@Configuration public class DataSourceConfig { @Bean @ConfigurationProperties(prefix = "spring.datasource.master") public DataSource masterDataSource() { return DataSourceBuilder.create().build(); } @Bean @ConfigurationProperties(prefix = "spring.datasource.slave") public DataSource slaveDataSource() { return DataSourceBuilder.create().build(); } @Bean public DataSource dynamicDataSource() { DynamicDataSource dynamicDataSource = new DynamicDataSource(); Map<Object, Object> dataSourceMap = new HashMap<>(2); dataSourceMap.put(DataSourceType.MASTER, masterDataSource()); dataSourceMap.put(DataSourceType.SLAVE, slaveDataSource()); // 設(shè)置默認(rèn)數(shù)據(jù)源為主庫(kù) dynamicDataSource.setDefaultTargetDataSource(masterDataSource()); dynamicDataSource.setTargetDataSources(dataSourceMap); return dynamicDataSource; } @Bean public SqlSessionFactory sqlSessionFactory() throws Exception { SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); sqlSessionFactoryBean.setDataSource(dynamicDataSource()); // 設(shè)置MyBatis配置 // ... return sqlSessionFactoryBean.getObject(); } }
第四步:實(shí)現(xiàn)AOP攔截器,根據(jù)方法匹配規(guī)則自動(dòng)切換數(shù)據(jù)源
@Aspect @Component public class DataSourceAspect { // 匹配所有以select、query、get、find開(kāi)頭的方法為讀操作 @Pointcut("execution(* com.example.service.impl.*.*(..))") public void servicePointcut() {} @Before("servicePointcut()") public void switchDataSource(JoinPoint point) { // 獲取方法名 String methodName = point.getSignature().getName(); // 根據(jù)方法名判斷是讀操作還是寫(xiě)操作 if (methodName.startsWith("select") || methodName.startsWith("query") || methodName.startsWith("get") || methodName.startsWith("find")) { // 讀操作使用從庫(kù) DataSourceContextHolder.setDataSourceType(DataSourceType.SLAVE); } else { // 寫(xiě)操作使用主庫(kù) DataSourceContextHolder.setDataSourceType(DataSourceType.MASTER); } } @After("servicePointcut()") public void restoreDataSource() { // 清除數(shù)據(jù)源配置 DataSourceContextHolder.clearDataSourceType(); } }
第五步:配置文件application.yml
spring: datasource: master: jdbc-url: jdbc:mysql://master-db:3306/test?useSSL=false username: root password: root driver-class-name: com.mysql.cj.jdbc.Driver slave: jdbc-url: jdbc:mysql://slave-db:3306/test?useSSL=false username: root password: root driver-class-name: com.mysql.cj.jdbc.Driver
第六步:使用注解方式靈活控制數(shù)據(jù)源(可選增強(qiáng))
// 定義自定義注解 @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface DataSource { DataSourceType value() default DataSourceType.MASTER; } // 修改AOP攔截器,優(yōu)先使用注解指定的數(shù)據(jù)源 @Aspect @Component public class DataSourceAspect { @Pointcut("@annotation(com.example.annotation.DataSource)") public void dataSourcePointcut() {} @Before("dataSourcePointcut()") public void switchDataSource(JoinPoint point) { MethodSignature signature = (MethodSignature) point.getSignature(); Method method = signature.getMethod(); DataSource dataSource = method.getAnnotation(DataSource.class); if (dataSource != null) { DataSourceContextHolder.setDataSourceType(dataSource.value()); } } @After("dataSourcePointcut()") public void restoreDataSource() { DataSourceContextHolder.clearDataSourceType(); } } // 在Service方法上使用 @Service public class UserServiceImpl implements UserService { @Override @DataSource(DataSourceType.SLAVE) public List<User> findAllUsers() { return userMapper.selectAll(); } @Override @DataSource(DataSourceType.MASTER) public void createUser(User user) { userMapper.insert(user); } }
2.3 優(yōu)缺點(diǎn)分析
優(yōu)點(diǎn):
- 實(shí)現(xiàn)簡(jiǎn)單,不依賴第三方組件
- 侵入性小,對(duì)業(yè)務(wù)代碼影響較小
- 靈活性高,可以根據(jù)業(yè)務(wù)需求靈活切換數(shù)據(jù)源
- 支持多數(shù)據(jù)源擴(kuò)展,不限于主從兩個(gè)庫(kù)
缺點(diǎn):
- 需要手動(dòng)指定或通過(guò)約定規(guī)則判斷讀寫(xiě)操作
適用場(chǎng)景:
- 中小型項(xiàng)目,讀寫(xiě)請(qǐng)求分離明確
- 對(duì)中間件依賴要求低的場(chǎng)景
- 臨時(shí)性能優(yōu)化,快速實(shí)現(xiàn)讀寫(xiě)分離
三、方案二:基于ShardingSphere-JDBC實(shí)現(xiàn)讀寫(xiě)分離
ShardingSphere-JDBC是Apache ShardingSphere項(xiàng)目下的一個(gè)子項(xiàng)目,它通過(guò)客戶端分片的方式,為應(yīng)用提供了透明化的讀寫(xiě)分離和分庫(kù)分表等功能。
3.1 實(shí)現(xiàn)原理
ShardingSphere-JDBC通過(guò)攔截JDBC驅(qū)動(dòng),重寫(xiě)SQL解析與執(zhí)行流程來(lái)實(shí)現(xiàn)讀寫(xiě)分離。它能夠根據(jù)SQL語(yǔ)義自動(dòng)判斷讀寫(xiě)操作,并將讀操作負(fù)載均衡地分發(fā)到多個(gè)從庫(kù)。
3.2 具體實(shí)現(xiàn)步驟
第一步:添加依賴
<dependency> <groupId>org.apache.shardingsphere</groupId> <artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId> <version>5.2.1</version> </dependency> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <scope>runtime</scope> </dependency>
第二步:配置文件application.yml
spring: shardingsphere: mode: type: Memory datasource: names: master,slave1,slave2 master: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver jdbc-url: jdbc:mysql://master-db:3306/test?useSSL=false username: root password: root slave1: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver jdbc-url: jdbc:mysql://slave1-db:3306/test?useSSL=false username: root password: root slave2: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver jdbc-url: jdbc:mysql://slave2-db:3306/test?useSSL=false username: root password: root rules: readwrite-splitting: data-sources: readwrite_ds: type: Static props: write-data-source-name: master read-data-source-names: slave1,slave2 load-balancer-name: round_robin load-balancers: round_robin: type: ROUND_ROBIN props: sql-show: true # 開(kāi)啟SQL顯示,方便調(diào)試
第三步:創(chuàng)建數(shù)據(jù)源配置類
@Configuration public class DataSourceConfig { // 無(wú)需額外配置,ShardingSphere-JDBC會(huì)自動(dòng)創(chuàng)建并注冊(cè)DataSource @Bean @ConfigurationProperties(prefix = "mybatis") public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) { SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); sqlSessionFactoryBean.setDataSource(dataSource); return sqlSessionFactoryBean; } }
第四步:強(qiáng)制主庫(kù)查詢的注解(可選)
在某些場(chǎng)景下,即使是查詢操作也需要從主庫(kù)讀取最新數(shù)據(jù),ShardingSphere提供了hint機(jī)制來(lái)實(shí)現(xiàn)這一需求。
// 定義主庫(kù)查詢注解 @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface MasterRoute { } // 創(chuàng)建AOP切面攔截器 @Aspect @Component public class MasterRouteAspect { @Around("@annotation(com.example.annotation.MasterRoute)") public Object aroundMasterRoute(ProceedingJoinPoint joinPoint) throws Throwable { try { HintManager.getInstance().setWriteRouteOnly(); return joinPoint.proceed(); } finally { HintManager.clear(); } } } // 在需要主庫(kù)查詢的方法上使用注解 @Service public class OrderServiceImpl implements OrderService { @Autowired private OrderMapper orderMapper; @Override @MasterRoute public Order getLatestOrder(Long userId) { // 這里的查詢會(huì)路由到主庫(kù) return orderMapper.findLatestByUserId(userId); } }
3.3 優(yōu)缺點(diǎn)分析
優(yōu)點(diǎn):
- 自動(dòng)識(shí)別SQL類型,無(wú)需手動(dòng)指定讀寫(xiě)數(shù)據(jù)源
- 支持多從庫(kù)負(fù)載均衡
- 提供豐富的負(fù)載均衡算法(輪詢、隨機(jī)、權(quán)重等)
- 完整的分庫(kù)分表能力,可無(wú)縫擴(kuò)展
- 對(duì)應(yīng)用透明,業(yè)務(wù)代碼無(wú)需修改
缺點(diǎn):
- 引入額外的依賴和學(xué)習(xí)成本
- 配置相對(duì)復(fù)雜
- 性能有輕微損耗(SQL解析和路由)
適用場(chǎng)景:
- 中大型項(xiàng)目,有明確的讀寫(xiě)分離需求
- 需要負(fù)載均衡到多從庫(kù)的場(chǎng)景
- 未來(lái)可能需要分庫(kù)分表的系統(tǒng)
四、方案三:基于MyBatis插件實(shí)現(xiàn)讀寫(xiě)分離
MyBatis提供了強(qiáng)大的插件機(jī)制,允許在SQL執(zhí)行的不同階段進(jìn)行攔截和處理。通過(guò)自定義插件,可以實(shí)現(xiàn)基于SQL解析的讀寫(xiě)分離功能。
4.1 實(shí)現(xiàn)原理
MyBatis允許攔截執(zhí)行器的query
和update
方法,通過(guò)攔截這些方法,可以在SQL執(zhí)行前動(dòng)態(tài)切換數(shù)據(jù)源。這種方式的核心是編寫(xiě)一個(gè)攔截器,分析即將執(zhí)行的SQL語(yǔ)句類型(SELECT/INSERT/UPDATE/DELETE),然后根據(jù)SQL類型切換到相應(yīng)的數(shù)據(jù)源。
4.2 具體實(shí)現(xiàn)步驟
第一步:定義數(shù)據(jù)源和上下文(與方案一類似)
public enum DataSourceType { MASTER, SLAVE } public class DataSourceContextHolder { private static final ThreadLocal<DataSourceType> contextHolder = new ThreadLocal<>(); public static void setDataSourceType(DataSourceType dataSourceType) { contextHolder.set(dataSourceType); } public static DataSourceType getDataSourceType() { return contextHolder.get() == null ? DataSourceType.MASTER : contextHolder.get(); } public static void clearDataSourceType() { contextHolder.remove(); } } public class DynamicDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return DataSourceContextHolder.getDataSourceType(); } }
第二步:實(shí)現(xiàn)MyBatis攔截器
@Intercepts({ @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}), @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}), @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}) }) @Component public class ReadWriteSplittingInterceptor implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { Object[] args = invocation.getArgs(); MappedStatement ms = (MappedStatement) args[0]; try { // 判斷是否為事務(wù) boolean isTransactional = TransactionSynchronizationManager.isActualTransactionActive(); // 如果是事務(wù),則使用主庫(kù) if (isTransactional) { DataSourceContextHolder.setDataSourceType(DataSourceType.MASTER); return invocation.proceed(); } // 根據(jù)SQL類型選擇數(shù)據(jù)源 if (ms.getSqlCommandType() == SqlCommandType.SELECT) { // 讀操作使用從庫(kù) DataSourceContextHolder.setDataSourceType(DataSourceType.SLAVE); } else { // 寫(xiě)操作使用主庫(kù) DataSourceContextHolder.setDataSourceType(DataSourceType.MASTER); } return invocation.proceed(); } finally { // 清除數(shù)據(jù)源配置 DataSourceContextHolder.clearDataSourceType(); } } @Override public Object plugin(Object target) { if (target instanceof Executor) { return Plugin.wrap(target, this); } return target; } @Override public void setProperties(Properties properties) { // 可以從配置文件加載屬性 } }
第三步:配置數(shù)據(jù)源和MyBatis插件
@Configuration public class DataSourceConfig { @Bean @ConfigurationProperties(prefix = "spring.datasource.master") public DataSource masterDataSource() { return DataSourceBuilder.create().build(); } @Bean @ConfigurationProperties(prefix = "spring.datasource.slave") public DataSource slaveDataSource() { return DataSourceBuilder.create().build(); } @Bean public DataSource dynamicDataSource() { DynamicDataSource dynamicDataSource = new DynamicDataSource(); Map<Object, Object> dataSourceMap = new HashMap<>(2); dataSourceMap.put(DataSourceType.MASTER, masterDataSource()); dataSourceMap.put(DataSourceType.SLAVE, slaveDataSource()); dynamicDataSource.setDefaultTargetDataSource(masterDataSource()); dynamicDataSource.setTargetDataSources(dataSourceMap); return dynamicDataSource; } @Bean public SqlSessionFactory sqlSessionFactory(@Autowired ReadWriteSplittingInterceptor interceptor) throws Exception { SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); sqlSessionFactoryBean.setDataSource(dynamicDataSource()); // 添加MyBatis插件 sqlSessionFactoryBean.setPlugins(new Interceptor[]{interceptor}); // 其他MyBatis配置 // ... return sqlSessionFactoryBean.getObject(); } }
第四步:強(qiáng)制主庫(kù)查詢注解(可選)
@Configuration public class DataSourceConfig { @Bean @ConfigurationProperties(prefix = "spring.datasource.master") public DataSource masterDataSource() { return DataSourceBuilder.create().build(); } @Bean @ConfigurationProperties(prefix = "spring.datasource.slave") public DataSource slaveDataSource() { return DataSourceBuilder.create().build(); } @Bean public DataSource dynamicDataSource() { DynamicDataSource dynamicDataSource = new DynamicDataSource(); Map<Object, Object> dataSourceMap = new HashMap<>(2); dataSourceMap.put(DataSourceType.MASTER, masterDataSource()); dataSourceMap.put(DataSourceType.SLAVE, slaveDataSource()); dynamicDataSource.setDefaultTargetDataSource(masterDataSource()); dynamicDataSource.setTargetDataSources(dataSourceMap); return dynamicDataSource; } @Bean public SqlSessionFactory sqlSessionFactory(@Autowired ReadWriteSplittingInterceptor interceptor) throws Exception { SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); sqlSessionFactoryBean.setDataSource(dynamicDataSource()); // 添加MyBatis插件 sqlSessionFactoryBean.setPlugins(new Interceptor[]{interceptor}); // 其他MyBatis配置 // ... return sqlSessionFactoryBean.getObject(); } }
4.3 優(yōu)缺點(diǎn)分析
優(yōu)點(diǎn):
- 自動(dòng)識(shí)別SQL類型,無(wú)需手動(dòng)指定數(shù)據(jù)源
- 可靈活擴(kuò)展,支持復(fù)雜的路由規(guī)則
- 基于MyBatis原生插件機(jī)制,無(wú)需引入額外的中間件
缺點(diǎn):
- 僅適用于使用MyBatis的項(xiàng)目
- 需要理解MyBatis插件機(jī)制
- 沒(méi)有內(nèi)置的負(fù)載均衡能力,需要額外開(kāi)發(fā)
- 可能與其他MyBatis插件產(chǎn)生沖突
- 事務(wù)管理較為復(fù)雜
適用場(chǎng)景:
- 純MyBatis項(xiàng)目
- 定制化需求較多的場(chǎng)景
- 對(duì)第三方中間件有限制的項(xiàng)目
- 需要對(duì)讀寫(xiě)分離有更精細(xì)控制的場(chǎng)景
五、三種方案對(duì)比與選型指南
5.1 功能對(duì)比
功能特性 | 方案一:AbstractRoutingDataSource | 方案二:ShardingSphere-JDBC | 方案三:MyBatis插件 |
---|---|---|---|
自動(dòng)識(shí)別SQL類型 | ? 需要手動(dòng)或通過(guò)規(guī)則指定 | ? 自動(dòng)識(shí)別 | ? 自動(dòng)識(shí)別 |
多從庫(kù)負(fù)載均衡 | ? 需要自行實(shí)現(xiàn) | ? 內(nèi)置多種算法 | ? 需要自行實(shí)現(xiàn) |
與分庫(kù)分表集成 | ? 不支持 | ? 原生支持 | ? 需要額外開(kāi)發(fā) |
開(kāi)發(fā)復(fù)雜度 | ?? 中等 | ? 較低 | ??? 較高 |
配置復(fù)雜度 | ? 較低 | ??? 較高 | ?? 中等 |
5.2 選型建議
選擇方案一(AbstractRoutingDataSource)的情況:
- 項(xiàng)目規(guī)模較小,讀寫(xiě)分離規(guī)則簡(jiǎn)單明確
- 對(duì)第三方依賴敏感,希望減少依賴
- 團(tuán)隊(duì)對(duì)Spring原生機(jī)制較為熟悉
- 系統(tǒng)處于早期階段,可能頻繁變動(dòng)
選擇方案二(ShardingSphere-JDBC)的情況:
- 中大型項(xiàng)目,有復(fù)雜的數(shù)據(jù)庫(kù)訪問(wèn)需求
- 需要多從庫(kù)負(fù)載均衡能力
- 未來(lái)可能需要分庫(kù)分表
- 希望盡量減少代碼侵入
- 對(duì)開(kāi)發(fā)效率要求較高
選擇方案三(MyBatis插件)的情況:
- 項(xiàng)目完全基于MyBatis架構(gòu)
- 團(tuán)隊(duì)對(duì)MyBatis插件機(jī)制較為熟悉
- 有特定的定制化需求
- 希望對(duì)SQL路由有更細(xì)粒度的控制
- 對(duì)框架依賴有嚴(yán)格限制
六、實(shí)施讀寫(xiě)分離的最佳實(shí)踐
6.1 數(shù)據(jù)一致性處理
從庫(kù)數(shù)據(jù)同步存在延遲,這可能導(dǎo)致讀取到過(guò)期數(shù)據(jù)的問(wèn)題。處理方法:
- 提供強(qiáng)制主庫(kù)查詢的選項(xiàng):對(duì)于需要最新數(shù)據(jù)的查詢,提供從主庫(kù)讀取的機(jī)制
- 會(huì)話一致性:同一會(huì)話內(nèi)的讀寫(xiě)操作使用相同的數(shù)據(jù)源
- 延遲檢測(cè):定期檢測(cè)主從同步延遲,當(dāng)延遲超過(guò)閾值時(shí)暫停從庫(kù)查詢
// 實(shí)現(xiàn)延遲檢測(cè)的示例 @Component @Slf4j public class ReplicationLagMonitor { @Autowired private JdbcTemplate masterJdbcTemplate; @Autowired private JdbcTemplate slaveJdbcTemplate; private AtomicBoolean slaveTooLagged = new AtomicBoolean(false); @Scheduled(fixedRate = 5000) // 每5秒檢查一次 public void checkReplicationLag() { try { // 在主庫(kù)寫(xiě)入標(biāo)記 String mark = UUID.randomUUID().toString(); masterJdbcTemplate.update("INSERT INTO replication_marker(marker, create_time) VALUES(?, NOW())", mark); // 等待一定時(shí)間,給從庫(kù)同步的機(jī)會(huì) Thread.sleep(1000); // 從從庫(kù)查詢?cè)摌?biāo)記 Integer count = slaveJdbcTemplate.queryForObject( "SELECT COUNT(*) FROM replication_marker WHERE marker = ?", Integer.class, mark); // 判斷同步延遲 boolean lagged = (count == null || count == 0); slaveTooLagged.set(lagged); if (lagged) { log.warn("Slave replication lag detected, routing read operations to master"); } else { log.info("Slave replication is in sync"); } } catch (Exception e) { log.error("Failed to check replication lag", e); slaveTooLagged.set(true); // 發(fā)生異常時(shí),保守地認(rèn)為從庫(kù)延遲過(guò)大 } finally{ // 刪除標(biāo)記數(shù)據(jù) masterJdbcTemplate.update("DELETE FROM replication_marker WHERE marker = ?", mark); } } public boolean isSlaveTooLagged() { return slaveTooLagged.get(); } }
6.2 事務(wù)管理
讀寫(xiě)分離環(huán)境下的事務(wù)處理需要特別注意:
- 事務(wù)內(nèi)操作都走主庫(kù):確保事務(wù)一致性
- 避免長(zhǎng)事務(wù):長(zhǎng)事務(wù)會(huì)長(zhǎng)時(shí)間鎖定主庫(kù)資源
- 區(qū)分只讀事務(wù):對(duì)于只讀事務(wù),可以考慮路由到從庫(kù)
6.4 監(jiān)控與性能優(yōu)化
- 監(jiān)控讀寫(xiě)比例:了解系統(tǒng)的讀寫(xiě)比例,優(yōu)化資源分配
- 慢查詢監(jiān)控:監(jiān)控各數(shù)據(jù)源的慢查詢
- 連接池優(yōu)化:根據(jù)實(shí)際負(fù)載調(diào)整連接池參數(shù)
# HikariCP連接池配置示例 spring: datasource: master: # 主庫(kù)偏向?qū)懖僮?,連接池可以適當(dāng)小一些 maximum-pool-size: 20 minimum-idle: 5 slave: # 從庫(kù)偏向讀操作,連接池可以適當(dāng)大一些 maximum-pool-size: 50 minimum-idle: 10
七、總結(jié)
在實(shí)施讀寫(xiě)分離時(shí),需要特別注意數(shù)據(jù)一致性、事務(wù)管理和故障處理等方面的問(wèn)題。
通過(guò)合理的架構(gòu)設(shè)計(jì)和細(xì)致的實(shí)現(xiàn),讀寫(xiě)分離可以有效提升系統(tǒng)的讀寫(xiě)性能和可擴(kuò)展性,為應(yīng)用系統(tǒng)的高可用和高性能提供有力支持。
無(wú)論選擇哪種方案,請(qǐng)記住讀寫(xiě)分離是一種架構(gòu)模式,而非解決所有性能問(wèn)題的萬(wàn)能藥。在實(shí)施前應(yīng)充分評(píng)估系統(tǒng)的實(shí)際需求和潛在風(fēng)險(xiǎn),確保收益大于成本。
到此這篇關(guān)于SpringBoot實(shí)現(xiàn)數(shù)據(jù)庫(kù)讀寫(xiě)分離的3種方法小結(jié)的文章就介紹到這了,更多相關(guān)SpringBoot數(shù)據(jù)庫(kù)讀寫(xiě)分離內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java中常見(jiàn)的日期操作(取值、轉(zhuǎn)換、加減、比較)
本文給大家介紹java中常見(jiàn)的日期操作,日期取值、日期轉(zhuǎn)換、日期加減、日期比較,對(duì)java日期操作相關(guān)知識(shí)感興趣的朋友一起學(xué)習(xí)吧2015-12-12IDEA的Terminal無(wú)法執(zhí)行g(shù)it命令問(wèn)題
這篇文章主要介紹了IDEA的Terminal無(wú)法執(zhí)行g(shù)it命令問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-09-09基于Java解決華為機(jī)試實(shí)現(xiàn)密碼截取?
這篇文章主要介紹了基于Java解決華為機(jī)試實(shí)現(xiàn)密碼截取,文章圍繞主題相關(guān)資料展開(kāi)詳細(xì)內(nèi)容,具有一的參考價(jià)值,需要的小伙伴可以參考一下,希望對(duì)你有所幫助2022-02-02Java 關(guān)于String字符串原理上的問(wèn)題
字符串廣泛應(yīng)用 在 Java 編程中,在 Java 中字符串屬于對(duì)象,Java 提供了 String 類來(lái)創(chuàng)建和操作字符串,讓我們一起來(lái)了解它2022-04-04Spring Boot security 默認(rèn)攔截靜態(tài)資源的解決方法
這篇文章主要介紹了Spring Boot security 默認(rèn)攔截靜態(tài)資源,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-03-03