SQL實(shí)現(xiàn)讀寫分離的分配的幾種方式
讀寫分離的分配,即如何在應(yīng)用程序中將讀操作和寫操作路由到不同的數(shù)據(jù)庫實(shí)例,可以通過幾種不同的方法來實(shí)現(xiàn)。這些方法可以在應(yīng)用程序?qū)印?shù)據(jù)庫層或使用中間件來完成。以下是幾種常見的實(shí)現(xiàn)方法:
應(yīng)用程序?qū)訉?shí)現(xiàn)
在應(yīng)用程序?qū)訉?shí)現(xiàn)讀寫分離,通常通過配置多個(gè)數(shù)據(jù)源并在代碼中顯式地選擇適當(dāng)?shù)臄?shù)據(jù)源。使用 AOP(面向切面編程)來自動(dòng)選擇數(shù)據(jù)源是一種常見的方法。
具體實(shí)現(xiàn)步驟
- 配置多數(shù)據(jù)源:配置一個(gè)主數(shù)據(jù)源(用于寫操作)和多個(gè)從數(shù)據(jù)源(用于讀操作)。
- 實(shí)現(xiàn)路由邏輯:通過 AOP 或其他方式在代碼中選擇適當(dāng)?shù)臄?shù)據(jù)源。
- 使用自定義注解:標(biāo)記需要路由到不同數(shù)據(jù)源的方法。
以下是詳細(xì)的實(shí)現(xiàn)示例:
配置文件
在 application.yml 中配置主庫和從庫的信息。
# application.yml
spring:
datasource:
master:
url: jdbc:mysql://master-db:3306/mydb
username: root
password: root
slaves:
- url: jdbc:mysql://slave-db1:3306/mydb
username: root
password: root
- url: jdbc:mysql://slave-db2:3306/mydb
username: root
password: root
數(shù)據(jù)源配置
import com.zaxxer.hikari.HikariDataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class DataSourceConfig {
@Autowired
private MasterDataSourceProperties masterProperties;
@Autowired
private SlaveDataSourceProperties slaveProperties;
@Bean
@Primary
public DataSource dataSource() {
AbstractRoutingDataSource routingDataSource = new ReplicationRoutingDataSource();
HikariDataSource masterDataSource = new HikariDataSource();
masterDataSource.setJdbcUrl(masterProperties.getUrl());
masterDataSource.setUsername(masterProperties.getUsername());
masterDataSource.setPassword(masterProperties.getPassword());
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put("master", masterDataSource);
for (int i = 0; i < slaveProperties.getSlaves().size(); i++) {
SlaveProperties slave = slaveProperties.getSlaves().get(i);
HikariDataSource slaveDataSource = new HikariDataSource();
slaveDataSource.setJdbcUrl(slave.getUrl());
slaveDataSource.setUsername(slave.getUsername());
slaveDataSource.setPassword(slave.getPassword());
targetDataSources.put("slave" + i, slaveDataSource);
}
routingDataSource.setTargetDataSources(targetDataSources);
routingDataSource.setDefaultTargetDataSource(masterDataSource);
return routingDataSource;
}
}
路由數(shù)據(jù)源
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class ReplicationRoutingDataSource extends AbstractRoutingDataSource {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
public static void setDataSourceType(String dataSourceType) {
contextHolder.set(dataSourceType);
}
public static void clearDataSourceType() {
contextHolder.remove();
}
@Override
protected Object determineCurrentLookupKey() {
return contextHolder.get();
}
}
數(shù)據(jù)源選擇器
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class DataSourceAspect {
@Before("@annotation(com.example.annotation.Master)")
public void setWriteDataSourceType() {
ReplicationRoutingDataSource.setDataSourceType("master");
}
@Before("@annotation(com.example.annotation.Slave) || execution(* com.example.service..*.find*(..))")
public void setReadDataSourceType() {
ReplicationRoutingDataSource.setDataSourceType("slave0"); // 可實(shí)現(xiàn)負(fù)載均衡策略
}
}
自定義注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Master {
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Slave {
}
示例服務(wù)
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Master
@Transactional
public void saveUser(User user) {
userRepository.save(user);
}
@Slave
public User findUserById(Long id) {
return userRepository.findById(id).orElse(null);
}
}
使用中間件
使用中間件來實(shí)現(xiàn)讀寫分離也是一種常見的方法。中間件通常位于應(yīng)用程序和數(shù)據(jù)庫之間,負(fù)責(zé)根據(jù)操作類型將請(qǐng)求路由到適當(dāng)?shù)臄?shù)據(jù)庫實(shí)例。常見的中間件包括 MySQL 的 ProxySQL 和 MariaDB 的 MaxScale。
ProxySQL 示例配置
- 安裝 ProxySQL:可以通過包管理器安裝 ProxySQL。
- 配置 ProxySQL:在
proxysql.cnf文件中配置主從數(shù)據(jù)庫。
datadir="/var/lib/proxysql"
admin_variables=
{
admin_credentials="admin:admin"
mysql_ifaces="0.0.0.0:6032"
}
mysql_variables=
{
threads=4
max_connections=1024
}
mysql_servers =
(
{ address="master-db", port=3306, hostgroup=0, max_connections=1000, weight=1 },
{ address="slave-db1", port=3306, hostgroup=1, max_connections=1000, weight=1 },
{ address="slave-db2", port=3306, hostgroup=1, max_connections=1000, weight=1 }
)
mysql_users =
(
{ username="proxyuser", password="proxypassword", default_hostgroup=0, transaction_persistent=1 }
)
mysql_query_rules =
(
{ rule_id=1, match_pattern="^SELECT", destination_hostgroup=1, apply=1 }
)
- 啟動(dòng) ProxySQL:使用
systemctl或其他方式啟動(dòng) ProxySQL。
systemctl start proxysql
數(shù)據(jù)庫層實(shí)現(xiàn)
有些數(shù)據(jù)庫本身提供了讀寫分離的功能。例如,MySQL 的復(fù)制機(jī)制允許配置一個(gè)主數(shù)據(jù)庫和多個(gè)從數(shù)據(jù)庫,然后通過連接池或驅(qū)動(dòng)程序?qū)崿F(xiàn)讀寫分離。
總結(jié)
讀寫分離的實(shí)現(xiàn)方法有多種,可以根據(jù)具體需求和技術(shù)棧選擇適合的方法。在應(yīng)用程序?qū)訉?shí)現(xiàn)讀寫分離較為靈活,可以精細(xì)控制讀寫操作的路由邏輯;使用中間件實(shí)現(xiàn)讀寫分離則可以簡(jiǎn)化應(yīng)用程序的邏輯,但需要額外維護(hù)中間件的配置和管理;在數(shù)據(jù)庫層實(shí)現(xiàn)讀寫分離可以利用數(shù)據(jù)庫本身的功能,減少對(duì)應(yīng)用程序的改動(dòng)。
到此這篇關(guān)于SQL實(shí)現(xiàn)讀寫分離的分配的幾種方式的文章就介紹到這了,更多相關(guān)SQL 讀寫分離分配內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SQLServer:探討EXEC與sp_executesql的區(qū)別詳解
本篇文章是對(duì)EXEC與sp_executesql的區(qū)別進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-06-06
SQL Server誤區(qū)30日談 第12天 TempDB的文件數(shù)和需要和CPU數(shù)目保持一致
TempDB的文件沒有必要分布在多個(gè)存儲(chǔ)器之間。如果你看到PAGELATCH類型的等待,即使你進(jìn)行了分布也不會(huì)改善性能,而如果PAGEIOLATCH型的等待,或許你需要多個(gè)存儲(chǔ)器,但這也不是必然-有可能你需要講整個(gè)TempDB遷移到另一個(gè)存儲(chǔ)系統(tǒng),而不是僅僅為TempDB增加一個(gè)文件2013-01-01
如何恢復(fù)數(shù)據(jù)庫的賬號(hào) 登錄名/用戶名等
當(dāng)重裝數(shù)系統(tǒng)/數(shù)據(jù)庫之后,如何恢復(fù)數(shù)據(jù)庫的賬號(hào) 登錄名/用戶名 孤立用戶 縮小ldf日志文件 修改sqlserver2000端口2013-08-08
LINQ to SQL:處理char(1)字段的方式會(huì)引起全表掃描問題
1.相關(guān)內(nèi)容: 在SQL Server 2000中,如果數(shù)據(jù)庫的排序規(guī)則為Chinese_PRC_CI_AS,那么查詢時(shí)是不分大小寫的,例如下列這二條SQL語句,查詢的結(jié)果是一樣的。2008-03-03
SQL Server實(shí)現(xiàn)自動(dòng)循環(huán)歸檔分區(qū)數(shù)據(jù)腳本詳解
最近在工作中遇到了關(guān)于sql server的一個(gè)問題,通過查找相關(guān)的資料終于解決了,所以下面這篇文章主要給大家介紹了關(guān)于SQL Server如何實(shí)現(xiàn)自動(dòng)循環(huán)歸檔分區(qū)數(shù)據(jù)腳本的相關(guān)資料,需要的朋友可以參考借鑒,下面來一起看看吧。2017-09-09
SQL?Server數(shù)據(jù)庫創(chuàng)建遠(yuǎn)程服務(wù)器備份計(jì)劃(SQL Server2016)
最近項(xiàng)目系統(tǒng)做安全加固,以前是本地備份,現(xiàn)在需要做遠(yuǎn)程內(nèi)網(wǎng)服務(wù)器數(shù)據(jù)庫備份,后期也有可能做異地備份,下面以SQL Server2016 內(nèi)網(wǎng)服務(wù)器數(shù)據(jù)庫備份為例給大家詳細(xì)講解SQL?Server數(shù)據(jù)庫創(chuàng)建遠(yuǎn)程服務(wù)器備份計(jì)劃,感興趣的朋友一起看看吧2023-10-10
EXEC(EXECUTE)函數(shù)訪問INSERTED或DELETED的內(nèi)部臨時(shí)觸發(fā)表
近段時(shí)間,MS SQL方面,一直需要開發(fā)動(dòng)態(tài)方面的存儲(chǔ)過程或是觸發(fā)器以及表函數(shù)。因?yàn)槌绦蛟O(shè)計(jì)一開始就是讓用戶動(dòng)態(tài)添或是刪除一個(gè)表的字段,然而這個(gè)表的相關(guān)存儲(chǔ)過程或是觸發(fā)器以及為報(bào)表準(zhǔn)備的表函數(shù)也會(huì)隨之這個(gè)表的字段變化而變化2012-01-01
SqlServer將查詢結(jié)果轉(zhuǎn)換為XML和JSON
這篇文章主要介紹了SqlServer將查詢結(jié)果轉(zhuǎn)換為XML和JSON的相關(guān)資料,需要的朋友可以參考下2017-07-07

