MySQL主從復(fù)制數(shù)據(jù)同步的實(shí)現(xiàn)步驟
一、什么是 MySQL 的主從復(fù)制
MySQL 的主從復(fù)制(Master-Slave Replication)是一種將數(shù)據(jù)從一個(gè)主數(shù)據(jù)庫(kù)服務(wù)器(主庫(kù))復(fù)制到一個(gè)或多個(gè)從數(shù)據(jù)庫(kù)服務(wù)器(從庫(kù))的技術(shù)。主庫(kù)負(fù)責(zé)所有的數(shù)據(jù)寫(xiě)操作,從庫(kù)則通過(guò)讀取主庫(kù)的二進(jìn)制日志來(lái)同步主庫(kù)的數(shù)據(jù)變化。主從復(fù)制主要用于實(shí)現(xiàn)數(shù)據(jù)的備份、負(fù)載分擔(dān)和高可用性。
二、具體流程
1. 主庫(kù)記錄寫(xiě)操作
當(dāng)主庫(kù)執(zhí)行寫(xiě)操作(如 INSERT、UPDATE 或 DELETE)時(shí),這些操作會(huì)被記錄到二進(jìn)制日志(binlog)中。二進(jìn)制日志保存了所有數(shù)據(jù)更改的歷史記錄,這些記錄將成為從庫(kù)同步數(shù)據(jù)的來(lái)源。
步驟:主庫(kù)將所有寫(xiě)操作(數(shù)據(jù)變更)記錄到二進(jìn)制日志中。
2. 從庫(kù)連接主庫(kù)并讀取二進(jìn)制日志
從庫(kù)通過(guò)一個(gè)稱(chēng)為IO 線程的進(jìn)程與主庫(kù)建立連接,開(kāi)始讀取主庫(kù)的二進(jìn)制日志。主庫(kù)會(huì)將日志內(nèi)容發(fā)送給從庫(kù)的 IO 線程。
步驟:從庫(kù)的 IO 線程連接主庫(kù),持續(xù)接收主庫(kù)二進(jìn)制日志的最新內(nèi)容。
3. 從庫(kù)將二進(jìn)制日志寫(xiě)入中繼日志
從庫(kù)的 IO 線程將接收到的主庫(kù)二進(jìn)制日志復(fù)制到從庫(kù)的**中繼日志(relay log)**中。中繼日志在從庫(kù)本地保存了一份主庫(kù)數(shù)據(jù)變更的副本,供從庫(kù)執(zhí)行使用。
步驟:從庫(kù)的 IO 線程將接收的日志寫(xiě)入中繼日志,為數(shù)據(jù)變更做好準(zhǔn)備。
4. 從庫(kù)應(yīng)用中繼日志,執(zhí)行數(shù)據(jù)同步
從庫(kù)的另一個(gè)線程(稱(chēng)為SQL 線程)負(fù)責(zé)讀取中繼日志中的內(nèi)容,并逐條在從庫(kù)上執(zhí)行,以實(shí)現(xiàn)數(shù)據(jù)的同步。每當(dāng)主庫(kù)有新的數(shù)據(jù)變更,從庫(kù)都會(huì)從中繼日志中獲取并執(zhí)行這些變更,從而保持與主庫(kù)的數(shù)據(jù)一致。
步驟:從庫(kù)的 SQL 線程從中繼日志中讀取并執(zhí)行記錄的操作,逐步更新從庫(kù)數(shù)據(jù)。
5. 持續(xù)復(fù)制和同步
整個(gè)主從復(fù)制過(guò)程是一個(gè)持續(xù)的流程,只要主庫(kù)有新的數(shù)據(jù)變更,從庫(kù)就會(huì)自動(dòng)獲取并執(zhí)行對(duì)應(yīng)的更改,從而保持與主庫(kù)數(shù)據(jù)的一致性。主從復(fù)制會(huì)持續(xù)保持同步,以確保從庫(kù)能夠?qū)崟r(shí)或接近實(shí)時(shí)地反映主庫(kù)的最新數(shù)據(jù)。
步驟:主庫(kù)的所有寫(xiě)操作都會(huì)記錄到日志中,實(shí)時(shí)同步到從庫(kù);從庫(kù)持續(xù)讀取并執(zhí)行日志內(nèi)容,更新自身數(shù)據(jù)。
三、作用和好處
讀寫(xiě)分離,提高性能
- 減輕主庫(kù)壓力:將大量的讀操作分散到從庫(kù),主庫(kù)主要負(fù)責(zé)寫(xiě)操作,大大降低了主庫(kù)的負(fù)載壓力。
- 提升并發(fā)處理能力:增加從庫(kù)的數(shù)量可以提升系統(tǒng)的讀并發(fā)能力,處理更多用戶的并發(fā)請(qǐng)求,提升系統(tǒng)整體性能。
高可用性和容錯(cuò)性
- 數(shù)據(jù)冗余與容災(zāi):主從復(fù)制提供了數(shù)據(jù)的實(shí)時(shí)備份,從庫(kù)可以在主庫(kù)發(fā)生故障時(shí)快速接管服務(wù),提高系統(tǒng)的可用性。
- 故障切換:當(dāng)主庫(kù)出現(xiàn)故障時(shí),可以臨時(shí)將從庫(kù)升級(jí)為主庫(kù),保障服務(wù)持續(xù)運(yùn)行。
負(fù)載均衡
- 分擔(dān)查詢(xún)壓力:多個(gè)從庫(kù)可以共同承擔(dān)讀取請(qǐng)求,通過(guò)負(fù)載均衡算法(如輪詢(xún)、隨機(jī)等)分配請(qǐng)求,提升系統(tǒng)的穩(wěn)定性。
- 避免單點(diǎn)瓶頸:讀操作分散到不同的從庫(kù)上執(zhí)行,避免了單一數(shù)據(jù)庫(kù)因訪問(wèn)量過(guò)大而成為系統(tǒng)瓶頸。
擴(kuò)展性
- 水平擴(kuò)展:根據(jù)業(yè)務(wù)增長(zhǎng),靈活增加從庫(kù)數(shù)量,滿足性能需求,而無(wú)需對(duì)主庫(kù)進(jìn)行大幅度改造。
- 彈性擴(kuò)展:可以根據(jù)實(shí)際流量,增加或減少?gòu)膸?kù),做到靈活應(yīng)對(duì)負(fù)載變化。
數(shù)據(jù)備份和安全性
- 數(shù)據(jù)保護(hù):從庫(kù)可以用于數(shù)據(jù)備份和容災(zāi),防止主庫(kù)故障導(dǎo)致的數(shù)據(jù)丟失。
- 快速恢復(fù):在數(shù)據(jù)意外丟失或損壞時(shí),可以通過(guò)從庫(kù)恢復(fù)主庫(kù)的數(shù)據(jù)。
四、在實(shí)際應(yīng)用中的場(chǎng)景與示例
1. 電商平臺(tái)
在電商平臺(tái)中,用戶的瀏覽和查詢(xún)操作會(huì)產(chǎn)生大量的讀請(qǐng)求,而訂單創(chuàng)建、支付等操作會(huì)產(chǎn)生寫(xiě)請(qǐng)求。通過(guò)主從復(fù)制的讀寫(xiě)分離:
- 查詢(xún)庫(kù)存、商品詳情等高頻率的讀操作可以由從庫(kù)承擔(dān),減輕主庫(kù)壓力。
- 用戶下單、支付等寫(xiě)操作由主庫(kù)負(fù)責(zé),確保數(shù)據(jù)的完整性和一致性。
2. 社交媒體或內(nèi)容網(wǎng)站
在社交媒體應(yīng)用中,大量的內(nèi)容瀏覽和搜索會(huì)產(chǎn)生大量的讀操作,而發(fā)布、點(diǎn)贊等則是寫(xiě)操作。通過(guò)主從復(fù)制,平臺(tái)可以:
- 使用從庫(kù)來(lái)承擔(dān)瀏覽、查詢(xún)等操作,確保高并發(fā)下的快速響應(yīng)。
- 將寫(xiě)操作指向主庫(kù),確保用戶發(fā)布或點(diǎn)贊的實(shí)時(shí)更新。
五、在 Spring Boot 項(xiàng)目中集成 MySQL 主從復(fù)制
在 Spring Boot 項(xiàng)目中集成 MySQL 的主從復(fù)制數(shù)據(jù)同步,指的是將 Spring Boot 應(yīng)用程序連接到配置有主從復(fù)制的 MySQL 數(shù)據(jù)庫(kù)系統(tǒng)上,完成主從數(shù)據(jù)庫(kù)的配置管理,實(shí)現(xiàn)自動(dòng)的讀寫(xiě)分離。具體來(lái)說(shuō),就是通過(guò)多數(shù)據(jù)源配置,讓 Spring Boot 自動(dòng)識(shí)別是寫(xiě)請(qǐng)求還是讀請(qǐng)求,并將寫(xiě)請(qǐng)求發(fā)送到主庫(kù),讀請(qǐng)求發(fā)送到從庫(kù)。
集成的關(guān)鍵要素:
- 多數(shù)據(jù)源配置:在 Spring Boot 中配置主數(shù)據(jù)庫(kù)和從數(shù)據(jù)庫(kù)的連接。
- 動(dòng)態(tài)數(shù)據(jù)源路由:在應(yīng)用層面實(shí)現(xiàn)數(shù)據(jù)源的動(dòng)態(tài)選擇,讀操作使用從庫(kù),寫(xiě)操作使用主庫(kù)。
- 讀寫(xiě)分離注解或切面:利用自定義注解或 AOP 切面控制數(shù)據(jù)源的切換。
1.配置 MySQL 的主從復(fù)制環(huán)境
配置 MySQL 的主從復(fù)制環(huán)境是實(shí)現(xiàn) MySQL 主從復(fù)制數(shù)據(jù)同步的第一步。主要步驟包括設(shè)置主庫(kù)(Master)和從庫(kù)(Slave),并驗(yàn)證主從復(fù)制的成功。
1.1 設(shè)置主庫(kù)(Master)
配置主庫(kù)是主從復(fù)制的第一步。主庫(kù)負(fù)責(zé)記錄數(shù)據(jù)變更并將其傳遞給從庫(kù)。
1.1.1 修改主庫(kù)的配置文件
在主庫(kù)的 MySQL 配置文件中(通常位于 /etc/my.cnf 或 /etc/mysql/my.cnf),需要啟用二進(jìn)制日志并為主庫(kù)設(shè)置一個(gè)唯一的 server-id。在 [mysqld] 部分添加如下配置:
[mysqld] server-id=1 # 主庫(kù)的唯一 ID log-bin=mysql-bin # 啟用二進(jìn)制日志,主從復(fù)制依賴(lài)于此 binlog-do-db=your_database # 需要同步的數(shù)據(jù)庫(kù)名稱(chēng),多個(gè)數(shù)據(jù)庫(kù)可添加多行
- server-id:用于標(biāo)識(shí)每個(gè) MySQL 實(shí)例的唯一標(biāo)識(shí)符,主庫(kù)的
server-id一般設(shè)置為 1。 - log-bin:?jiǎn)⒂枚M(jìn)制日志,這是主從復(fù)制的基礎(chǔ)。
- binlog-do-db:指定需要同步的數(shù)據(jù)庫(kù)(多個(gè)數(shù)據(jù)庫(kù)可以多行設(shè)置)。
注意:如果需要同步多個(gè)數(shù)據(jù)庫(kù),可以多次添加 binlog-do-db 行,例如:
binlog-do-db=database1 binlog-do-db=database2
1.1.2 重啟 MySQL 服務(wù)
修改配置文件后,需要重啟 MySQL 以使配置生效:
# Linux 系統(tǒng) sudo systemctl restart mysqld # Windows 系統(tǒng) net stop mysql net start mysql
1.1.3 創(chuàng)建用于復(fù)制的用戶
在主庫(kù)中創(chuàng)建一個(gè)用于復(fù)制的用戶,并授予 REPLICATION SLAVE 權(quán)限。這個(gè)用戶用于從庫(kù)連接主庫(kù)并進(jìn)行數(shù)據(jù)同步。
在主庫(kù)的 MySQL 命令行中執(zhí)行以下命令:
CREATE USER 'replica_user'@'%' IDENTIFIED BY 'password'; GRANT REPLICATION SLAVE ON *.* TO 'replica_user'@'%'; FLUSH PRIVILEGES;
- replica_user:用于復(fù)制的用戶名,可以自定義。
- password:復(fù)制用戶的密碼,注意使用強(qiáng)密碼。
- %:允許所有遠(yuǎn)程 IP 訪問(wèn)。如果只允許特定從庫(kù)連接,可以用從庫(kù)的 IP 地址代替
%。
1.1.4 獲取主庫(kù)的二進(jìn)制日志信息
為了讓從庫(kù)知道從何處開(kāi)始復(fù)制數(shù)據(jù),主庫(kù)需要提供當(dāng)前的二進(jìn)制日志位置??梢酝ㄟ^(guò)以下命令查看:
SHOW MASTER STATUS;
命令執(zhí)行后,會(huì)輸出如下內(nèi)容:
+------------------+----------+--------------+------------------+ | File | Position | Binlog_Do_DB | Binlog_Ignore_DB | +------------------+----------+--------------+------------------+ | mysql-bin.000001 | 154 | your_database| | +------------------+----------+--------------+------------------+
- File:當(dāng)前的二進(jìn)制日志文件名。
- Position:二進(jìn)制日志的偏移量,從該位置開(kāi)始讀取數(shù)據(jù)。
建議:在生產(chǎn)環(huán)境中,執(zhí)行 SHOW MASTER STATUS; 之前,可以先執(zhí)行 FLUSH TABLES WITH READ LOCK; 來(lái)鎖定表,防止數(shù)據(jù)寫(xiě)入導(dǎo)致的數(shù)據(jù)不一致。獲取信息后再執(zhí)行 UNLOCK TABLES; 解鎖。
1.2 設(shè)置從庫(kù)(Slave)
配置從庫(kù)是主從復(fù)制的第二步。配置從庫(kù)連接到主庫(kù)并開(kāi)始同步數(shù)據(jù)。
1.2.1 修改從庫(kù)的配置文件
在從庫(kù)的 MySQL 配置文件中(通常是 /etc/my.cnf 或 /etc/mysql/my.cnf),設(shè)置一個(gè)唯一的 server-id 并配置中繼日志和只讀模式。 在 [mysqld] 部分添加以下配置:
[mysqld] server-id=2 # 從庫(kù)的唯一 ID,不能與主庫(kù)或其他從庫(kù)相同 relay-log=relay-log # 設(shè)置中繼日志文件名前綴 read-only=1 # 設(shè)置只讀模式,防止誤操作
- server-id:從庫(kù)的唯一標(biāo)識(shí)符,每個(gè)從庫(kù)的
server-id也需要是唯一的,通常從 2 開(kāi)始。 - relay-log:指定中繼日志的前綴,用于存儲(chǔ)從主庫(kù)同步的數(shù)據(jù)。
- read-only:開(kāi)啟只讀模式,防止意外寫(xiě)入。此模式下,擁有
SUPER權(quán)限的用戶仍可以寫(xiě)入數(shù)據(jù)。
1.2.2 重啟 MySQL 服務(wù)
修改配置文件后,重啟從庫(kù)的 MySQL 服務(wù)以應(yīng)用配置:
# Linux 系統(tǒng) sudo systemctl restart mysqld # Windows 系統(tǒng) net stop mysql net start mysql
1.2.3 配置從庫(kù)連接到主庫(kù)
在從庫(kù)的 MySQL 中,配置主庫(kù)信息以便開(kāi)始復(fù)制。需要用到在主庫(kù)上記錄的 File 和 Position 值。
CHANGE MASTER TO
MASTER_HOST='主庫(kù)的 IP 地址',
MASTER_USER='replica_user',
MASTER_PASSWORD='password',
MASTER_LOG_FILE='mysql-bin.000001', -- 主庫(kù)的 File 值
MASTER_LOG_POS=154; -- 主庫(kù)的 Position 值
- MASTER_HOST:主庫(kù)的 IP 地址。
- MASTER_USER:在主庫(kù)上創(chuàng)建的用于復(fù)制的用戶(如
replica_user)。 - MASTER_PASSWORD:復(fù)制用戶的密碼。
- MASTER_LOG_FILE:主庫(kù)的二進(jìn)制日志文件名。
- MASTER_LOG_POS:二進(jìn)制日志的位置偏移量。
1.2.4 啟動(dòng)復(fù)制進(jìn)程
配置完成后,啟動(dòng)從庫(kù)的復(fù)制進(jìn)程以開(kāi)始從主庫(kù)復(fù)制數(shù)據(jù):
START SLAVE;
1.2.5 查看從庫(kù)的復(fù)制狀態(tài)
運(yùn)行以下命令檢查從庫(kù)的狀態(tài),確認(rèn)從庫(kù)已成功連接到主庫(kù)并開(kāi)始復(fù)制數(shù)據(jù):
SHOW SLAVE STATUS\G
- Slave_IO_Running:應(yīng)為
Yes,表示從庫(kù)的 IO 線程正在讀取主庫(kù)的日志。 - Slave_SQL_Running:應(yīng)為
Yes,表示從庫(kù)的 SQL 線程正在執(zhí)行主庫(kù)傳遞的日志。 - Last_IO_Error 和 Last_SQL_Error:如果存在錯(cuò)誤信息,會(huì)在此處顯示。
如果 Slave_IO_Running 或 Slave_SQL_Running 為 No,請(qǐng)查看 Last_IO_Error 或 Last_SQL_Error 中的錯(cuò)誤消息,并根據(jù)錯(cuò)誤提示排查問(wèn)題。
1.3 驗(yàn)證主從復(fù)制是否成功
在配置完成后,驗(yàn)證主從復(fù)制的成功性。確保主庫(kù)的寫(xiě)操作能夠被從庫(kù)正確同步。
1.3.1 在主庫(kù)上創(chuàng)建測(cè)試數(shù)據(jù)
在主庫(kù)上選擇用于復(fù)制的數(shù)據(jù)庫(kù),并創(chuàng)建一個(gè)測(cè)試表插入數(shù)據(jù):
USE your_database;
CREATE TABLE test_table (
id INT PRIMARY KEY,
name VARCHAR(50)
);
INSERT INTO test_table (id, name) VALUES (1, 'Test Data');
1.3.2 在從庫(kù)上檢查數(shù)據(jù)同步情況
在從庫(kù)上選擇相同的數(shù)據(jù)庫(kù),查詢(xún) test_table 表,確認(rèn)是否同步了主庫(kù)的數(shù)據(jù):
USE your_database; SELECT * FROM test_table;
- 如果可以看到
Test Data,則表示主從復(fù)制配置成功并且工作正常。
1.3.3 驗(yàn)證實(shí)時(shí)同步效果
在主庫(kù)上繼續(xù)插入或更新數(shù)據(jù),再次在從庫(kù)上查詢(xún),驗(yàn)證數(shù)據(jù)是否能夠及時(shí)同步。主從復(fù)制一般情況下會(huì)立即同步,延遲較小。
1.4 故障排查
在配置主從復(fù)制的過(guò)程中,可能會(huì)遇到以下常見(jiàn)問(wèn)題:
1 從庫(kù)無(wú)法連接到主庫(kù)
- 檢查網(wǎng)絡(luò)連接:確保主庫(kù)的防火墻允許從庫(kù)的 IP 地址訪問(wèn) MySQL 的 3306 端口。
2 權(quán)限問(wèn)題
- 用戶權(quán)限:確保在主庫(kù)上創(chuàng)建的用戶擁有
REPLICATION SLAVE權(quán)限。
3 主從版本兼容性問(wèn)題
- 版本兼容性:確保主庫(kù)和從庫(kù)的 MySQL 版本相互兼容,推薦使用相同的版本。
4 日志位置不正確
- 文件和位置錯(cuò)誤:如果配置的
File和Position不正確,可以重新設(shè)置主從復(fù)制位置。
2.在 Spring Boot 項(xiàng)目中配置多數(shù)據(jù)源
配置多數(shù)據(jù)源是實(shí)現(xiàn)主從復(fù)制的重要步驟。通過(guò)多數(shù)據(jù)源配置,Spring Boot 應(yīng)用可以自動(dòng)區(qū)分并選擇主庫(kù)或從庫(kù),從而實(shí)現(xiàn)讀寫(xiě)分離。這一步的具體操作包括添加依賴(lài)、配置數(shù)據(jù)源信息,以及配置數(shù)據(jù)源路由以實(shí)現(xiàn)自動(dòng)選擇主庫(kù)或從庫(kù)。以下是詳細(xì)的分步講解。
2.1 添加必要的依賴(lài)
在使用 Spring Data JPA 和 MySQL 多數(shù)據(jù)源時(shí),需要添加以下依賴(lài)項(xiàng):
- MySQL 驅(qū)動(dòng):支持連接 MySQL 數(shù)據(jù)庫(kù)。
- Spring Data JPA:提供 JPA 支持,簡(jiǎn)化數(shù)據(jù)庫(kù)操作。
- HikariCP 連接池:Spring Boot 默認(rèn)的連接池,適合高并發(fā)環(huán)境且支持多數(shù)據(jù)源配置。
在項(xiàng)目的 pom.xml 中添加以下依賴(lài):
<dependencies>
<!-- MySQL 驅(qū)動(dòng) -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- Spring Data JPA -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- HikariCP 連接池(Spring Boot 默認(rèn)連接池) -->
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
</dependency>
</dependencies>
2.2 在配置文件中定義主庫(kù)和從庫(kù)的數(shù)據(jù)源信息
接下來(lái),需要在 application.properties 中定義主庫(kù)和從庫(kù)的連接信息。主庫(kù)通常用于寫(xiě)操作,從庫(kù)用于讀操作。
application.properties 文件內(nèi)容
# 主庫(kù)配置 spring.datasource.master.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.master.url=jdbc:mysql://主庫(kù)IP地址:3306/your_database?useSSL=false&characterEncoding=utf8 spring.datasource.master.username=主庫(kù)用戶名 spring.datasource.master.password=主庫(kù)密碼 # 從庫(kù)配置 spring.datasource.slave.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.slave.url=jdbc:mysql://從庫(kù)IP地址:3306/your_database?useSSL=false&characterEncoding=utf8 spring.datasource.slave.username=從庫(kù)用戶名 spring.datasource.slave.password=從庫(kù)密碼
說(shuō)明:
- spring.datasource.master:主庫(kù)連接信息,用于寫(xiě)操作。
- spring.datasource.slave:從庫(kù)連接信息,用于讀操作。
- driver-class-name:MySQL 驅(qū)動(dòng)類(lèi)名。
- url:數(shù)據(jù)庫(kù)連接 URL,其中包含數(shù)據(jù)庫(kù)的 IP 地址和數(shù)據(jù)庫(kù)名稱(chēng)。
- username 和 password:數(shù)據(jù)庫(kù)的用戶名和密碼。
2.3 創(chuàng)建數(shù)據(jù)源配置類(lèi),配置主從數(shù)據(jù)源
在項(xiàng)目中添加一個(gè)配置類(lèi),用于定義主庫(kù)和從庫(kù)數(shù)據(jù)源,并通過(guò)一個(gè)動(dòng)態(tài)數(shù)據(jù)源實(shí)現(xiàn)自動(dòng)選擇主庫(kù)或從庫(kù)。
2.3.1 配置主庫(kù)和從庫(kù)的數(shù)據(jù)源
首先,我們需要在配置類(lèi)中定義兩個(gè)數(shù)據(jù)源,即主庫(kù)和從庫(kù)。主庫(kù)主要用于寫(xiě)操作,從庫(kù)用于讀操作。
package com.example.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
import org.springframework.boot.jdbc.DataSourceBuilder;
@Configuration
public class DataSourceConfig {
// 定義主庫(kù)數(shù)據(jù)源
@Bean(name = "masterDataSource")
@ConfigurationProperties(prefix = "spring.datasource.master")
public DataSource masterDataSource() {
return DataSourceBuilder.create().build();
}
// 定義從庫(kù)數(shù)據(jù)源
@Bean(name = "slaveDataSource")
@ConfigurationProperties(prefix = "spring.datasource.slave")
public DataSource slaveDataSource() {
return DataSourceBuilder.create().build();
}
}
說(shuō)明:
@ConfigurationProperties:這個(gè)注解將
application.properties中spring.datasource.master和spring.datasource.slave配置的信息綁定到masterDataSource和slaveDataSource上。這樣,Spring Boot 會(huì)自動(dòng)讀取配置文件中的主從庫(kù)信息,并將它們分別注入到兩個(gè)數(shù)據(jù)源對(duì)象中。DataSourceBuilder:這是 Spring 提供的一個(gè)工具類(lèi),通過(guò)它可以根據(jù)配置文件構(gòu)建
DataSource對(duì)象。DataSource是與數(shù)據(jù)庫(kù)連接的核心組件,主要用于管理數(shù)據(jù)庫(kù)連接、連接池等信息。
2.3.2 動(dòng)態(tài)數(shù)據(jù)源路由
為了實(shí)現(xiàn)主從分離,我們需要根據(jù)請(qǐng)求自動(dòng)選擇主庫(kù)或從庫(kù)的數(shù)據(jù)源。AbstractRoutingDataSource 是 Spring 提供的一個(gè)抽象類(lèi),可以根據(jù)用戶定義的路由規(guī)則動(dòng)態(tài)選擇數(shù)據(jù)源。
創(chuàng)建 DynamicDataSource 類(lèi):該類(lèi)繼承自 AbstractRoutingDataSource,通過(guò)設(shè)置鍵值映射來(lái)實(shí)現(xiàn)數(shù)據(jù)源選擇。
package com.example.config;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class DynamicDataSource extends AbstractRoutingDataSource {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
// 設(shè)置當(dāng)前線程的數(shù)據(jù)源(主庫(kù)或從庫(kù))
public static void setDataSource(String dataSourceKey) {
contextHolder.set(dataSourceKey);
}
// 清除當(dāng)前線程的數(shù)據(jù)源
public static void clearDataSource() {
contextHolder.remove();
}
// 確定當(dāng)前數(shù)據(jù)源(由 AbstractRoutingDataSource 調(diào)用)
@Override
protected Object determineCurrentLookupKey() {
return contextHolder.get();
}
}
代碼詳解:
contextHolder:使用
ThreadLocal存儲(chǔ)當(dāng)前線程的數(shù)據(jù)源標(biāo)識(shí)(master或slave)。ThreadLocal確保每個(gè)線程擁有獨(dú)立的變量副本,不會(huì)干擾其他線程。setDataSource:用于設(shè)置當(dāng)前線程的數(shù)據(jù)源,通過(guò)傳入
master或slave來(lái)指定主庫(kù)或從庫(kù)。clearDataSource:用于清除當(dāng)前線程的數(shù)據(jù)源,避免數(shù)據(jù)源信息在線程之間混淆。
determineCurrentLookupKey:這是
AbstractRoutingDataSource提供的方法,它會(huì)在每次數(shù)據(jù)庫(kù)操作時(shí)調(diào)用。根據(jù)contextHolder中的數(shù)據(jù)源標(biāo)識(shí),選擇主庫(kù)或從庫(kù)。
2.3.3 將主從數(shù)據(jù)源注入到動(dòng)態(tài)數(shù)據(jù)源
我們需要將主庫(kù)和從庫(kù)的數(shù)據(jù)源注入到動(dòng)態(tài)數(shù)據(jù)源中,并根據(jù)不同的業(yè)務(wù)需求自動(dòng)選擇。
@Bean(name = "dynamicDataSource")
public DataSource dynamicDataSource(
@Qualifier("masterDataSource") DataSource masterDataSource,
@Qualifier("slaveDataSource") DataSource slaveDataSource) {
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put("master", masterDataSource);
targetDataSources.put("slave", slaveDataSource);
DynamicDataSource dynamicDataSource = new DynamicDataSource();
dynamicDataSource.setDefaultTargetDataSource(masterDataSource); // 設(shè)置默認(rèn)的數(shù)據(jù)源
dynamicDataSource.setTargetDataSources(targetDataSources); // 將主庫(kù)和從庫(kù)的數(shù)據(jù)源加入路由
return dynamicDataSource;
}
代碼詳解:
@Qualifier:指定
masterDataSource和slaveDataSource,通過(guò)依賴(lài)注入的方式將主庫(kù)和從庫(kù)的數(shù)據(jù)源傳入dynamicDataSource。targetDataSources:將
master和slave作為鍵,分別映射到masterDataSource和slaveDataSource。DynamicDataSource會(huì)通過(guò)determineCurrentLookupKey方法自動(dòng)選擇對(duì)應(yīng)的數(shù)據(jù)源。setDefaultTargetDataSource:設(shè)置默認(rèn)的數(shù)據(jù)源。在沒(méi)有指定數(shù)據(jù)源的情況下,系統(tǒng)會(huì)使用默認(rèn)數(shù)據(jù)源(一般為主庫(kù))。
2.3.4 配置 EntityManagerFactory 使用動(dòng)態(tài)數(shù)據(jù)源
對(duì)于使用 JPA 的項(xiàng)目,EntityManagerFactory 是 JPA 的核心組件之一。它負(fù)責(zé)管理實(shí)體管理器,處理 JPA 的持久化操作。這里需要將 EntityManagerFactory 配置為使用動(dòng)態(tài)數(shù)據(jù)源,以實(shí)現(xiàn)主從分離。
@Bean(name = "entityManagerFactory")
public LocalContainerEntityManagerFactoryBean entityManagerFactory(
@Qualifier("dynamicDataSource") DataSource dynamicDataSource) {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dynamicDataSource); // 使用動(dòng)態(tài)數(shù)據(jù)源
em.setPackagesToScan("com.example.entity"); // 實(shí)體類(lèi)包名
em.setJpaVendorAdapter(new HibernateJpaVendorAdapter()); // 設(shè)置 JPA 供應(yīng)商為 Hibernate
return em;
}
代碼詳解:
setDataSource:設(shè)置 JPA 的數(shù)據(jù)源為
dynamicDataSource,這樣 JPA 會(huì)根據(jù)動(dòng)態(tài)數(shù)據(jù)源的路由選擇合適的數(shù)據(jù)源。setPackagesToScan:指定 JPA 實(shí)體類(lèi)所在的包路徑,JPA 會(huì)掃描該包中的實(shí)體類(lèi),進(jìn)行數(shù)據(jù)庫(kù)操作的映射。
- HibernateJpaVendorAdapter:設(shè)置 JPA 的供應(yīng)商適配器。這里我們使用 Hibernate 作為 JPA 的實(shí)現(xiàn),它提供了 Hibernate 特有的優(yōu)化和配置支持。
通過(guò)配置 EntityManagerFactory 使用 dynamicDataSource,我們可以讓 JPA 在操作數(shù)據(jù)庫(kù)時(shí)自動(dòng)根據(jù)業(yè)務(wù)需要切換到主庫(kù)或從庫(kù),從而實(shí)現(xiàn)主從分離和讀寫(xiě)分離。
2.3.5 配置事務(wù)管理器
Spring Data JPA 默認(rèn)需要一個(gè)事務(wù)管理器來(lái)管理事務(wù)。我們需要為動(dòng)態(tài)數(shù)據(jù)源配置一個(gè) JpaTransactionManager,以確保事務(wù)操作可以正確地應(yīng)用于當(dāng)前選擇的數(shù)據(jù)源(主庫(kù)或從庫(kù))。
@Bean(name = "transactionManager")
public JpaTransactionManager transactionManager(
@Qualifier("entityManagerFactory") LocalContainerEntityManagerFactoryBean entityManagerFactory) {
return new JpaTransactionManager(entityManagerFactory.getObject());
}
代碼詳解:
JpaTransactionManager:Spring 提供的 JPA 事務(wù)管理器,它會(huì)自動(dòng)處理
EntityManager的創(chuàng)建和關(guān)閉,并確保在事務(wù)中對(duì)數(shù)據(jù)庫(kù)操作的 ACID 特性(原子性、一致性、隔離性、持久性)。entityManagerFactory.getObject():將
entityManagerFactory(配置了動(dòng)態(tài)數(shù)據(jù)源的實(shí)體管理器工廠)傳遞給JpaTransactionManager。這樣,事務(wù)管理器會(huì)根據(jù)動(dòng)態(tài)數(shù)據(jù)源路由來(lái)管理事務(wù),確保事務(wù)一致性。
3.實(shí)現(xiàn)數(shù)據(jù)源的動(dòng)態(tài)切換
為了實(shí)現(xiàn)數(shù)據(jù)庫(kù)的主從切換,使得 Spring Boot 項(xiàng)目可以根據(jù)操作類(lèi)型自動(dòng)選擇主庫(kù)或從庫(kù),我們需要實(shí)現(xiàn)數(shù)據(jù)源的動(dòng)態(tài)切換。實(shí)現(xiàn)動(dòng)態(tài)切換的關(guān)鍵步驟包括:定義自定義注解、創(chuàng)建 AOP 切面,在方法執(zhí)行時(shí)動(dòng)態(tài)地決定使用哪個(gè)數(shù)據(jù)源。
3.1 創(chuàng)建 @DataSource 注解(用于標(biāo)識(shí)使用哪個(gè)數(shù)據(jù)源)
首先,我們創(chuàng)建一個(gè)自定義注解 @DataSource,用于標(biāo)識(shí)在特定方法或類(lèi)上指定的數(shù)據(jù)源類(lèi)型(如主庫(kù) master 或從庫(kù) slave)。有了這個(gè)注解之后,我們可以在代碼中靈活地指定哪些操作使用主庫(kù),哪些操作使用從庫(kù)。
package com.example.annotation;
import java.lang.annotation.*;
@Target({ElementType.METHOD, ElementType.TYPE}) // 可以用在方法或類(lèi)上
@Retention(RetentionPolicy.RUNTIME) // 在運(yùn)行時(shí)保留,便于通過(guò)反射獲取
@Documented
public @interface DataSource {
String value() default "master"; // 默認(rèn)使用主庫(kù)
}
注解參數(shù)說(shuō)明:
@Target:定義注解的使用位置,
ElementType.METHOD表示可以作用于方法,ElementType.TYPE表示可以作用于類(lèi)。@Retention:指定注解的保留策略為
RUNTIME,即該注解會(huì)保留到運(yùn)行時(shí),并且可以通過(guò)反射獲取,這樣切面類(lèi)可以在運(yùn)行時(shí)識(shí)別注解。value:注解的屬性,用來(lái)指定數(shù)據(jù)源類(lèi)型,默認(rèn)為
"master",表示主庫(kù)。我們可以在使用注解時(shí),通過(guò)設(shè)置@DataSource("slave")來(lái)指定從庫(kù)。
3.2 創(chuàng)建 DataSourceAspect 切面類(lèi)(根據(jù)注解動(dòng)態(tài)切換數(shù)據(jù)源)
AOP(面向切面編程)可以在方法調(diào)用前后動(dòng)態(tài)地切入代碼邏輯。在這里,我們編寫(xiě)一個(gè) AOP 切面,用于在方法調(diào)用之前,根據(jù) @DataSource 注解的值設(shè)置數(shù)據(jù)源。在方法調(diào)用之后,清除數(shù)據(jù)源的標(biāo)識(shí),以確保不會(huì)影響后續(xù)操作。
package com.example.aspect;
import com.example.annotation.DataSource;
import com.example.config.DynamicDataSource;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class DataSourceAspect {
// 定義切點(diǎn):匹配帶有 @DataSource 注解的方法或類(lèi)
@Pointcut("@annotation(com.example.annotation.DataSource) || @within(com.example.annotation.DataSource)")
public void dataSourcePointCut() {
}
// 在方法執(zhí)行前,根據(jù)注解的值切換數(shù)據(jù)源
@Before("dataSourcePointCut()")
public void before(JoinPoint point) {
String dataSource = "master"; // 默認(rèn)使用主庫(kù)
Class<?> targetClass = point.getTarget().getClass();
MethodSignature signature = (MethodSignature) point.getSignature();
// 獲取方法上的 @DataSource 注解
DataSource ds = signature.getMethod().getAnnotation(DataSource.class);
if (ds != null) {
dataSource = ds.value();
} else if (targetClass.isAnnotationPresent(DataSource.class)) {
// 如果方法上沒(méi)有注解,則讀取類(lèi)上的 @DataSource 注解
ds = targetClass.getAnnotation(DataSource.class);
if (ds != null) {
dataSource = ds.value();
}
}
// 設(shè)置當(dāng)前線程的數(shù)據(jù)源標(biāo)識(shí)
DynamicDataSource.setDataSource(dataSource);
}
// 在方法執(zhí)行后,清除數(shù)據(jù)源標(biāo)識(shí)
@After("dataSourcePointCut()")
public void after(JoinPoint point) {
DynamicDataSource.clearDataSource();
}
}
切面類(lèi)代碼詳解:
@Pointcut:定義切點(diǎn)
dataSourcePointCut,用于匹配所有帶有@DataSource注解的方法或類(lèi)。這意味著我們可以在方法上、類(lèi)上使用@DataSource來(lái)控制數(shù)據(jù)源選擇。@Before:在目標(biāo)方法執(zhí)行之前觸發(fā)
before方法。MethodSignature:通過(guò)
MethodSignature可以獲取方法的注解。dataSource:默認(rèn)值為
"master"(主庫(kù))。首先檢查方法上的@DataSource注解,如果沒(méi)有找到,再檢查類(lèi)上的@DataSource注解。DynamicDataSource.setDataSource(dataSource):根據(jù)注解值設(shè)置當(dāng)前線程使用的數(shù)據(jù)源(主庫(kù)或從庫(kù))。這使得后續(xù)的數(shù)據(jù)庫(kù)操作將根據(jù)注解指定的數(shù)據(jù)源執(zhí)行。
@After:在目標(biāo)方法執(zhí)行后觸發(fā)
after方法,用于清除當(dāng)前線程的數(shù)據(jù)源標(biāo)識(shí),以避免線程復(fù)用時(shí)對(duì)其他請(qǐng)求產(chǎn)生干擾。DynamicDataSource.clearDataSource()方法會(huì)移除當(dāng)前線程的數(shù)據(jù)源標(biāo)識(shí)。
4.在業(yè)務(wù)代碼中使用
4.1 使用 @DataSource 注解
在業(yè)務(wù)代碼中,可以在需要使用主庫(kù)或從庫(kù)的業(yè)務(wù)方法上添加 @DataSource 注解。默認(rèn)情況下,@DataSource 注解的 value 屬性是 "master",表示主庫(kù)。我們可以在查詢(xún)類(lèi)方法上標(biāo)記 @DataSource("slave") 以使用從庫(kù),從而實(shí)現(xiàn)讀寫(xiě)分離。
4.1.1.在服務(wù)層使用 @DataSource 注解
假設(shè)我們有一個(gè) UserService 服務(wù)類(lèi),該類(lèi)提供了查詢(xún)用戶列表和保存用戶的功能。我們可以通過(guò) @DataSource 注解指定使用的數(shù)據(jù)庫(kù)。
import com.example.annotation.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
// 使用從庫(kù)(slave)進(jìn)行讀取操作
@DataSource("slave")
public List<User> getUsers() {
// 數(shù)據(jù)庫(kù)查詢(xún)操作,這里會(huì)通過(guò)切面選擇從庫(kù)
return userRepository.findAll();
}
// 使用主庫(kù)(master)進(jìn)行寫(xiě)入操作
@DataSource("master")
public void saveUser(User user) {
// 數(shù)據(jù)庫(kù)寫(xiě)入操作,這里會(huì)通過(guò)切面選擇主庫(kù)
userRepository.save(user);
}
}
示例說(shuō)明:
getUsers 方法:由于標(biāo)注了
@DataSource("slave")注解,因此在執(zhí)行getUsers方法時(shí)會(huì)選擇從庫(kù)作為當(dāng)前數(shù)據(jù)源,從而使查詢(xún)操作通過(guò)從庫(kù)完成,減輕主庫(kù)壓力。saveUser 方法:標(biāo)注了
@DataSource("master")注解,因此在執(zhí)行saveUser方法時(shí)會(huì)選擇主庫(kù)作為當(dāng)前數(shù)據(jù)源,從而保證數(shù)據(jù)寫(xiě)入操作在主庫(kù)上進(jìn)行,確保數(shù)據(jù)的一致性。
4.1.2.在 DAO 層(數(shù)據(jù)持久層)使用 @DataSource 注解
假設(shè)我們將數(shù)據(jù)源控制進(jìn)一步細(xì)化到 DAO 層。例如,在 UserRepository 中的查詢(xún)和保存方法上分別指定數(shù)據(jù)源。
import com.example.annotation.DataSource;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
// 使用從庫(kù)(slave)進(jìn)行讀取操作
@DataSource("slave")
List<User> findByName(String name);
// 使用主庫(kù)(master)進(jìn)行寫(xiě)入操作
@DataSource("master")
User save(User user);
}
示例說(shuō)明:
findByName 方法:該方法標(biāo)注了
@DataSource("slave"),因此在調(diào)用findByName時(shí),數(shù)據(jù)查詢(xún)操作將通過(guò)從庫(kù)完成。save 方法:該方法標(biāo)注了
@DataSource("master"),確保數(shù)據(jù)寫(xiě)入操作始終通過(guò)主庫(kù)進(jìn)行,保證了數(shù)據(jù)的完整性。
注意:將 @DataSource 注解放在服務(wù)層和 DAO 層都會(huì)生效。實(shí)際應(yīng)用中可以根據(jù)業(yè)務(wù)邏輯的復(fù)雜程度來(lái)決定在哪一層實(shí)現(xiàn)數(shù)據(jù)源的切換。
4.2 確保讀操作走從庫(kù),寫(xiě)操作走主庫(kù)
通過(guò)在業(yè)務(wù)方法中使用 @DataSource 注解,可以實(shí)現(xiàn)以下的邏輯:
1.讀操作走從庫(kù):在查詢(xún)數(shù)據(jù)的方法上添加 @DataSource("slave"),使這些方法通過(guò)從庫(kù)執(zhí)行。
- 從庫(kù)通常用于處理讀操作。這樣可以分擔(dān)主庫(kù)的負(fù)載,提升系統(tǒng)的讀取性能。
- 因?yàn)閺膸?kù)的數(shù)據(jù)來(lái)自主庫(kù)的復(fù)制,存在一定延遲,所以一般不用于強(qiáng)一致性要求的場(chǎng)景,而適合對(duì)實(shí)時(shí)性要求較低的查詢(xún)場(chǎng)景。
2.寫(xiě)操作走主庫(kù):在插入、更新、刪除數(shù)據(jù)的方法上添加 @DataSource("master"),確保這些操作通過(guò)主庫(kù)執(zhí)行。
- 主庫(kù)是數(shù)據(jù)的源頭,負(fù)責(zé)處理寫(xiě)入、更新等操作,以確保數(shù)據(jù)的準(zhǔn)確性和一致性。
- 這樣可以避免因?yàn)閺?fù)制延遲導(dǎo)致的數(shù)據(jù)不一致問(wèn)題。
示例:在 ProductService 中實(shí)現(xiàn)讀寫(xiě)分離
import com.example.annotation.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class ProductService {
@Autowired
private ProductRepository productRepository;
// 使用從庫(kù)讀取產(chǎn)品列表
@DataSource("slave")
public List<Product> getAllProducts() {
// 從庫(kù)執(zhí)行查詢(xún)
return productRepository.findAll();
}
// 使用主庫(kù)保存產(chǎn)品信息
@DataSource("master")
public void addProduct(Product product) {
// 主庫(kù)執(zhí)行插入操作
productRepository.save(product);
}
// 更新產(chǎn)品信息,使用主庫(kù)
@DataSource("master")
public void updateProduct(Product product) {
// 主庫(kù)執(zhí)行更新操作
productRepository.save(product);
}
// 刪除產(chǎn)品信息,使用主庫(kù)
@DataSource("master")
public void deleteProductById(Long productId) {
// 主庫(kù)執(zhí)行刪除操作
productRepository.deleteById(productId);
}
}
代碼解釋?zhuān)?/strong>
getAllProducts() 方法:
- 使用
@DataSource("slave")注解,從庫(kù)將執(zhí)行該方法中的查詢(xún)操作。 - 適用于讀取類(lèi)操作,減輕主庫(kù)壓力。
- 使用
addProduct()、updateProduct() 和 deleteProductById() 方法:
- 這些方法使用
@DataSource("master")注解,因此會(huì)走主庫(kù)。 - 適用于寫(xiě)操作(新增、修改、刪除),確保數(shù)據(jù)的強(qiáng)一致性和實(shí)時(shí)性。
- 這些方法使用
4.3 注意事項(xiàng)
在實(shí)現(xiàn)讀寫(xiě)分離時(shí),以下幾點(diǎn)需要注意:
主從數(shù)據(jù)一致性:從庫(kù)的數(shù)據(jù)源于主庫(kù)的復(fù)制,所以存在一定的延遲。對(duì)于不允許數(shù)據(jù)延遲的操作,建議強(qiáng)制使用主庫(kù)。比如訂單支付或庫(kù)存更新等操作,通常對(duì)實(shí)時(shí)性要求較高,應(yīng)直接走主庫(kù)。
事務(wù)管理:在事務(wù)中進(jìn)行讀操作,可能會(huì)導(dǎo)致數(shù)據(jù)源切換失效,因?yàn)樵谑聞?wù)中默認(rèn)會(huì)使用主庫(kù)。這種情況下,可以使用
@Transactional(readOnly = true)標(biāo)記方法為只讀事務(wù),讓 Spring 在事務(wù)中仍使用從庫(kù)。線程安全:因?yàn)閿?shù)據(jù)源切換是基于
ThreadLocal實(shí)現(xiàn)的,所以多線程環(huán)境下可以安全地設(shè)置和切換數(shù)據(jù)源標(biāo)識(shí)。然而,如果有異步操作,可能會(huì)導(dǎo)致數(shù)據(jù)源信息傳遞失效,需要特別注意。
5.測(cè)試和驗(yàn)證
完成 Spring Boot 項(xiàng)目中多數(shù)據(jù)源的配置之后,需要測(cè)試項(xiàng)目是否能夠正確地實(shí)現(xiàn)讀寫(xiě)分離。主要步驟包括:檢查 MySQL 主庫(kù)和從庫(kù)的服務(wù)狀態(tài)、啟動(dòng)項(xiàng)目、編寫(xiě)并運(yùn)行測(cè)試類(lèi),以及驗(yàn)證主從數(shù)據(jù)庫(kù)的數(shù)據(jù)同步情況。
5.1 檢查 MySQL 主庫(kù)和從庫(kù)的服務(wù)狀態(tài)
要確保 Spring Boot 項(xiàng)目能夠連接到主從數(shù)據(jù)庫(kù),首先需要確認(rèn) MySQL 主庫(kù)和從庫(kù)的服務(wù)狀態(tài)。如果主庫(kù)和從庫(kù)未啟動(dòng),Spring Boot 應(yīng)用將無(wú)法連接數(shù)據(jù)庫(kù)。
5.1.1 使用命令行檢查 MySQL 服務(wù)狀態(tài)
在主庫(kù)和從庫(kù)的服務(wù)器上,可以使用以下命令檢查 MySQL 服務(wù)狀態(tài):
# 檢查 MySQL 服務(wù)是否正在運(yùn)行 sudo systemctl status mysql
如果顯示 active (running),則表示 MySQL 服務(wù)正在運(yùn)行。
5.1.2 使用命令行啟動(dòng) MySQL 服務(wù)
如果 MySQL 服務(wù)未運(yùn)行,可以使用以下命令啟動(dòng):
# 啟動(dòng) MySQL 服務(wù) sudo systemctl start mysql
啟動(dòng)服務(wù)后,可以再次使用 status 命令檢查服務(wù)狀態(tài),確保 MySQL 服務(wù)已啟動(dòng)。
注意:如果主庫(kù)和從庫(kù)在不同的服務(wù)器上,您需要分別在主庫(kù)服務(wù)器和從庫(kù)服務(wù)器上執(zhí)行上述命令。
5.2 使用 IntelliJ IDEA 啟動(dòng) Spring Boot 項(xiàng)目
在確認(rèn) MySQL 主庫(kù)和從庫(kù)均已啟動(dòng)后,可以啟動(dòng) Spring Boot 項(xiàng)目。
5.2.1 在 IntelliJ IDEA 中啟動(dòng) Spring Boot 項(xiàng)目
- 打開(kāi)項(xiàng)目:在 IntelliJ IDEA 中打開(kāi)您的 Spring Boot 項(xiàng)目。
- 定位主類(lèi):在項(xiàng)目的
src/main/java目錄中,找到主類(lèi)(通常是帶有@SpringBootApplication注解的類(lèi),比如YourApplication類(lèi))。 - 運(yùn)行項(xiàng)目:右鍵點(diǎn)擊主類(lèi)文件,選擇“Run ‘YourApplication’”(運(yùn)行
YourApplication)選項(xiàng)。IDEA 將會(huì)在控制臺(tái)顯示啟動(dòng)日志。
5.2.2 檢查控制臺(tái)輸出
項(xiàng)目啟動(dòng)后,檢查 IDEA 控制臺(tái)的日志輸出,確保沒(méi)有報(bào)錯(cuò)信息。如果看到類(lèi)似以下內(nèi)容,則說(shuō)明項(xiàng)目啟動(dòng)成功:
INFO 12345 --- [main] com.example.YourApplication : Started YourApplication in 3.456 seconds (JVM running for 4.123)
5.3 編寫(xiě)測(cè)試類(lèi),測(cè)試讀寫(xiě)操作是否按照預(yù)期走對(duì)應(yīng)的數(shù)據(jù)源
在項(xiàng)目啟動(dòng)后,我們可以編寫(xiě)測(cè)試類(lèi),通過(guò)測(cè)試 Spring Boot 項(xiàng)目中是否實(shí)現(xiàn)了主從分離(讀操作走從庫(kù)、寫(xiě)操作走主庫(kù))。
5.3.1 創(chuàng)建測(cè)試類(lèi)
在項(xiàng)目的 src/test/java 目錄下,創(chuàng)建一個(gè)新的測(cè)試類(lèi),例如 UserServiceTest,用于測(cè)試服務(wù)類(lèi)中的讀寫(xiě)方法。以下是測(cè)試類(lèi)的示例代碼:
import com.example.service.UserService;
import com.example.model.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class UserServiceTest {
@Autowired
private UserService userService;
// 測(cè)試讀操作
@Test
public void testReadOperation() {
// 調(diào)用讀取用戶的方法
System.out.println("Executing read operation (should use slave database)...");
userService.getUsers();
}
// 測(cè)試寫(xiě)操作
@Test
public void testWriteOperation() {
// 創(chuàng)建一個(gè)新的用戶
User user = new User();
user.setName("Test User");
user.setEmail("testuser@example.com");
// 調(diào)用保存用戶的方法
System.out.println("Executing write operation (should use master database)...");
userService.saveUser(user);
}
}
代碼說(shuō)明
- @SpringBootTest:該注解用于在測(cè)試類(lèi)中啟動(dòng) Spring Boot 上下文。它會(huì)自動(dòng)加載配置文件和應(yīng)用上下文,確保測(cè)試類(lèi)中可以注入依賴(lài)。
- @Autowired:注入
UserService,以便在測(cè)試方法中調(diào)用getUsers和saveUser方法。 - @Test:每個(gè)測(cè)試方法都使用
@Test注解,表明這是一個(gè)測(cè)試用例,測(cè)試框架會(huì)自動(dòng)運(yùn)行帶有@Test的方法。
測(cè)試類(lèi)的測(cè)試方法
testReadOperation:測(cè)試從庫(kù)的讀取操作。
- 該方法會(huì)調(diào)用
userService.getUsers(),這應(yīng)該會(huì)使用從庫(kù)來(lái)執(zhí)行查詢(xún)操作。我們可以在日志中確認(rèn)是否成功走到了從庫(kù)。
- 該方法會(huì)調(diào)用
testWriteOperation:測(cè)試主庫(kù)的寫(xiě)入操作。
- 該方法會(huì)創(chuàng)建一個(gè)新用戶并調(diào)用
userService.saveUser(user),應(yīng)該會(huì)使用主庫(kù)來(lái)執(zhí)行插入操作??梢栽谌罩局胁榭词欠癯晒κ褂弥鲙?kù)。
- 該方法會(huì)創(chuàng)建一個(gè)新用戶并調(diào)用
5.3.3 在 IntelliJ IDEA 中運(yùn)行測(cè)試
在 IntelliJ IDEA 中可以通過(guò)以下步驟運(yùn)行測(cè)試類(lèi):
- 運(yùn)行單個(gè)測(cè)試方法:在
testReadOperation或testWriteOperation方法上右鍵點(diǎn)擊,選擇“Run ‘testReadOperation’”或“Run ‘testWriteOperation’”來(lái)運(yùn)行特定測(cè)試。 - 運(yùn)行整個(gè)測(cè)試類(lèi):在
UserServiceTest類(lèi)名上右鍵點(diǎn)擊,選擇“Run ‘UserServiceTest’”,以運(yùn)行所有測(cè)試方法。
5.3.4 檢查測(cè)試輸出
在控制臺(tái)中查看輸出信息。如果您在 DynamicDataSource 類(lèi)中添加了日志信息,可以看到類(lèi)似以下的日志:
Switching to data source: slave Executing read operation (should use slave database)... Switching to data source: master Executing write operation (should use master database)...
- 讀操作日志:如果
testReadOperation的日志顯示Switching to data source: slave,說(shuō)明讀操作成功走了從庫(kù)。 - 寫(xiě)操作日志:如果
testWriteOperation的日志顯示Switching to data source: master,說(shuō)明寫(xiě)操作成功走了主庫(kù)。
5.4 檢查主從數(shù)據(jù)庫(kù)的數(shù)據(jù)同步
在驗(yàn)證讀寫(xiě)操作走了正確的數(shù)據(jù)源后,還需要檢查主從數(shù)據(jù)庫(kù)的數(shù)據(jù)同步情況,以確認(rèn)主庫(kù)的數(shù)據(jù)更改是否成功同步到從庫(kù)。
5.4.1 執(zhí)行寫(xiě)入操作,檢查數(shù)據(jù)同步
運(yùn)行寫(xiě)入測(cè)試方法:通過(guò)
testWriteOperation或直接調(diào)用服務(wù)中的寫(xiě)入方法,向主庫(kù)插入新數(shù)據(jù)。在主庫(kù)中檢查數(shù)據(jù):連接到主庫(kù),執(zhí)行以下查詢(xún),確認(rèn)數(shù)據(jù)是否成功插入:
SELECT * FROM your_database.users WHERE name = 'Test User';
在從庫(kù)中檢查數(shù)據(jù)同步:稍等片刻后,連接到從庫(kù),執(zhí)行相同的查詢(xún),檢查數(shù)據(jù)是否已同步:
SELECT * FROM your_database.users WHERE name = 'Test User';
如果從庫(kù)中也能查詢(xún)到這條記錄,則表明主庫(kù)的數(shù)據(jù)成功同步到了從庫(kù)。
5.4.2 執(zhí)行更新操作,檢查數(shù)據(jù)同步
運(yùn)行更新操作:在主庫(kù)中更新記錄的某個(gè)字段,例如通過(guò)
userService.updateUser(user)方法更新email字段(假設(shè)該方法存在),或直接在主庫(kù)執(zhí)行更新 SQL 語(yǔ)句:UPDATE your_database.users SET email = 'updateduser@example.com' WHERE name = 'Test User';
在從庫(kù)中檢查數(shù)據(jù)同步:稍等片刻后,在從庫(kù)執(zhí)行相同的查詢(xún),確認(rèn)
email字段是否已更新為'updateduser@example.com'。
5.4.3 執(zhí)行刪除操作,檢查數(shù)據(jù)同步
運(yùn)行刪除操作:通過(guò)
userService.deleteUserById(userId)方法(假設(shè)存在)或直接在主庫(kù)執(zhí)行刪除語(yǔ)句:DELETE FROM your_database.users WHERE name = 'Test User';
在從庫(kù)中檢查數(shù)據(jù)同步:在從庫(kù)中執(zhí)行以下查詢(xún),確認(rèn)數(shù)據(jù)是否已同步刪除:
SELECT * FROM your_database.users WHERE name = 'Test User';
如果查詢(xún)結(jié)果為空,則表明刪除操作已成功同步到從庫(kù)。
到此這篇關(guān)于MySQL主從復(fù)制數(shù)據(jù)同步的實(shí)現(xiàn)步驟的文章就介紹到這了,更多相關(guān)MySQL 主從復(fù)制數(shù)據(jù)同步內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
mysql 5.7.18 zip版安裝配置方法圖文教程(win7)
這篇文章主要為大家詳細(xì)介紹了win7下mysql 5.7.8 zip版安裝配置方法圖文教程,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-08-08
保證MySQL與Redis數(shù)據(jù)一致性的6種實(shí)現(xiàn)方案
這篇文章將聚焦在一個(gè)非常重要且復(fù)雜的問(wèn)題上:MySQL與Redis數(shù)據(jù)的一致性,當(dāng)我們?cè)趹?yīng)用中同時(shí)使用MySQL和Redis時(shí),如何保證兩者的數(shù)據(jù)一致性呢?下面就來(lái)分享幾種實(shí)用的解決方案,需要的朋友可以參考下2024-03-03
MySQL?數(shù)據(jù)庫(kù)的對(duì)庫(kù)的操作及其數(shù)據(jù)類(lèi)型
這篇文章主要介紹了MySQL?數(shù)據(jù)庫(kù)的對(duì)庫(kù)的操作及其數(shù)據(jù)類(lèi)型,下面文字圍繞數(shù)據(jù)庫(kù)的對(duì)庫(kù)的操作及其數(shù)據(jù)類(lèi)型的相關(guān)資料展開(kāi)詳細(xì)介紹,需要的小伙伴可以參考一下,希望對(duì)你有所幫助2021-12-12
用命令創(chuàng)建MySQL數(shù)據(jù)庫(kù)(de1)的方法
下面小編就為大家?guī)?lái)一篇用命令創(chuàng)建MySQL數(shù)據(jù)庫(kù)(de1)的方法。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-03-03
pymysql.err.DataError:(1264, ")異常的有效解決方法(最新推薦)
遇到pymysql.err.DataError錯(cuò)誤時(shí),錯(cuò)誤代碼1264通常指的是MySQL數(shù)據(jù)庫(kù)中的Out of range value for column錯(cuò)誤,這意味著你嘗試插入或更新的數(shù)據(jù)超過(guò)了對(duì)應(yīng)數(shù)據(jù)庫(kù)列所允許的范圍,這篇文章主要介紹了pymysql.err.DataError:(1264, ")異常的有效問(wèn)題,需要的朋友可以參考下2024-05-05
解讀mysql主從配置及其原理分析(Master-Slave)
在windows下配置的,后面會(huì)在Linux下配置進(jìn)行測(cè)試,需要配置mysql數(shù)據(jù)庫(kù)同步的朋友可以參考下。2011-05-05
MySQL獲取當(dāng)前時(shí)間的多種方式總結(jié)
負(fù)責(zé)的項(xiàng)目中使用的是mysql數(shù)據(jù)庫(kù),頁(yè)面上要顯示當(dāng)天所注冊(cè)人數(shù)的數(shù)量,獲取當(dāng)前的年月日,下面這篇文章主要給大家總結(jié)介紹了關(guān)于MySQL獲取當(dāng)前時(shí)間的多種方式,需要的朋友可以參考下2023-02-02

