MySQL數(shù)據(jù)庫讀寫分離與負(fù)載均衡的實(shí)現(xiàn)邏輯
讀寫分離與負(fù)載均衡的核心概念與目的
讀寫分離與負(fù)載均衡是數(shù)據(jù)庫優(yōu)化的關(guān)鍵策略,二者目標(biāo)不同但緊密關(guān)聯(lián)
讀寫分離的核心是將數(shù)據(jù)庫的讀操作(SELECT)與寫操作(INSERT/UPDATE/DELETE)分離
- 寫操作僅在主庫執(zhí)行
- 讀操作則分配到從庫
負(fù)載均衡則解決讀操作在多個從庫間的分配問題,避免單點(diǎn)壓力
主從復(fù)制配置的核心目的是分擔(dān)主庫讀負(fù)載——大多數(shù)應(yīng)用中,讀負(fù)載遠(yuǎn)高于寫負(fù)載
寫操作必須在主庫執(zhí)行以保證數(shù)據(jù)一致性,而讀操作可在主庫或從庫執(zhí)行,讀寫分離通過減少主庫讀壓力,使其專注寫操作,從而優(yōu)化性能,完成讀寫分離后,讀操作需在多個從服務(wù)器間分配,這需要讀負(fù)載均衡策略
讀寫分離的必要性與實(shí)現(xiàn)邏輯
讀寫分離的必要性:
- 寫操作不可分擔(dān):寫操作必須在主庫執(zhí)行,以保證數(shù)據(jù)一致性
- 讀操作靈活性:讀操作可在主庫或從庫執(zhí)行
- 優(yōu)化主庫性能:減少主庫讀壓力,使其專注寫操作,提升整體吞吐量
實(shí)現(xiàn)邏輯:
- 操作需明確區(qū)分:寫操作路由至主庫,讀操作盡量路由至從庫
- 主從復(fù)制延遲(毫秒級)對實(shí)時性敏感查詢影響顯著,此類查詢需在主庫執(zhí)行
讀寫分離的實(shí)現(xiàn)方式及優(yōu)缺點(diǎn)
1 ) 方式一:程序?qū)訉?shí)現(xiàn)讀寫分離
優(yōu)點(diǎn):
- 靈活控制:開發(fā)人員可精準(zhǔn)判斷查詢路由(例如,實(shí)時性敏感查詢強(qiáng)制走主庫)
- 性能無損:直連數(shù)據(jù)庫,無額外性能損耗,架構(gòu)簡單易維護(hù),無新故障點(diǎn)
- 故障點(diǎn)少:不引入新中間層,降低系統(tǒng)復(fù)雜度
缺點(diǎn):
- 開發(fā)成本高:需維護(hù)多數(shù)據(jù)庫連接,增加代碼復(fù)雜度及工作量
- 人為錯誤風(fēng)險:開發(fā)人員可能誤用數(shù)據(jù)庫連接(如讀操作誤路由至主庫)
- 維護(hù)困難:修改連接配置需重啟應(yīng)用,影響業(yè)務(wù)連續(xù)性
2 ) 方式二:中間件實(shí)現(xiàn)讀寫分離
常用工具:
- MySQL Proxy(官方實(shí)驗(yàn)性工具,性能穩(wěn)定性差,不推薦生產(chǎn)使用)
- MaxScale(MariaDB 提供,免費(fèi)且兼容 MySQL,支持讀寫分離與讀負(fù)載均衡)
優(yōu)點(diǎn):
- 對程序透明:應(yīng)用僅連接中間件,無需修改代碼,降低遷移成本
- 自動化路由:通過 SQL 語法分析(如 SELECT 路由至從庫,非 SELECT 路由至主庫)
- 集成負(fù)載均衡:支持多從庫讀請求分配(如輪詢、權(quán)重策略)
缺點(diǎn):
- 性能損耗:中間件需解析 SQL 及權(quán)限認(rèn)證,QPS 可能下降 50%~70%(需基準(zhǔn)測試驗(yàn)證)
- 延遲敏感查詢處理不足:無法自動識別實(shí)時性敏感查詢(如存儲過程),需程序添加路由提示,需通過SQL提示關(guān)鍵字(如
/*MASTER*/)強(qiáng)制路由,但需修改程序 - 依賴中間件穩(wěn)定性:高并發(fā)下可能成為性能瓶頸
- 存儲過程限制:無法解析存儲過程內(nèi)的操作類型,所有存儲過程默認(rèn)路由到主庫執(zhí)行,增加主庫負(fù)載
原生SQL示例(存儲過程導(dǎo)致的讀寫分離局限)
-- 存儲過程無法通過語法區(qū)分讀寫,只能路由至主庫 DELIMITER // CREATE PROCEDURE GetOrderDetails(IN orderId INT) BEGIN SELECT * FROM orders WHERE id = orderId; -- 讀操作,但中間件無法識別 UPDATE orders SET status = 'processed' WHERE id = orderId; -- 寫操作 END // DELIMITER ;
選型建議:中間件上線前需嚴(yán)格基準(zhǔn)測試(如sysbench壓測),避免性能瓶頸。讀寫分離需與讀負(fù)載均衡協(xié)同設(shè)計:
- 讀負(fù)載均衡策略包括程序輪詢(需手動調(diào)整從庫數(shù)量)或?qū)S密浖ㄈ鏛VS、HAProxy)
- MaxScale可同時實(shí)現(xiàn)兩類功能,降低架構(gòu)復(fù)雜度
關(guān)鍵總結(jié):
- 程序?qū)臃桨高m合定制化需求高、開發(fā)資源充足的項(xiàng)目
- 中間件方案(如 MaxScale)適合快速遷移舊系統(tǒng),但需嚴(yán)格性能測試
讀負(fù)載均衡的實(shí)現(xiàn)策略
目標(biāo):將讀請求均勻分配到多個從庫
實(shí)現(xiàn)方式:
- 程序輪詢:
- 開發(fā)人員手動分配查詢到不同從庫
- 缺點(diǎn):增減從庫需修改程序配置,靈活性差
- 專用軟硬件:
- 軟件方案:LVS、HAProxy、MaxScale(支持自動負(fù)載均衡)
- 硬件方案:F5 等設(shè)備
- 核心要求:生產(chǎn)前必須進(jìn)行基準(zhǔn)測試,驗(yàn)證負(fù)載分配效率
MaxScale實(shí)踐指南
MaxScale作為高效中間件,同時提供讀寫分離和讀負(fù)載均衡功能:
- 核心機(jī)制:通過路由模塊(如
readwritesplit)解析SQL,寫操作路由至主庫,讀操作按配置策略(如加權(quán)輪詢)分發(fā)至從庫 - 部署建議:
- 配置多從庫時,需定義
[slave_servers]列表并設(shè)置健康檢查 - 為規(guī)避主從延遲,可在查詢中添加注釋強(qiáng)制主庫執(zhí)行(如
SELECT /* master */ ...)
- 配置多從庫時,需定義
核心功能:
- 自動讀寫分離(基于 SQL 語法分析)
- 從庫讀負(fù)載均衡(支持權(quán)重分配)
MaxScale配置示例
# MaxScale 配置文件 (maxscale.cnf) # 定義主庫 [server1] type=server address=192.168.1.101 port=3306 protocol=MySQLBackend # 定義從庫 [server2] type=server address=192.168.1.102 port=3306 protocol=MySQLBackend # 定義從庫 [server3] type=server address=192.168.1.103 port=3306 protocol=MySQLBackend # 讀寫分離路由 [ReadWriteSplit] type=service router=readwritesplit servers=server1,server2,server3 user=maxscale password=maxscale_pass # 負(fù)載均衡策略 [ReadOnlyService] type=service router=readconnroute router_options=slave servers=server2,server3 # 監(jiān)控主從狀態(tài) [ReplicationMonitor] type=monitor module=mariadbmon servers=server1, server2, server3 user=monitor_user password=monitor_password
常見的Protocol選項(xiàng)
| Protocol 名稱 | 描述 | 適用場景 |
|---|---|---|
MySQLBackend | 用于連接MySQL或MariaDB數(shù)據(jù)庫的標(biāo)準(zhǔn)協(xié)議 | 最常用的選項(xiàng),適用于絕大多數(shù)MySQL/MariaDB環(huán)境 |
MariaDBBackend | 類似于MySQLBackend,但在某些新版本中可能有不同的行為或優(yōu)化 | 新版本MaxScale推薦用于MariaDB |
GaleraBackend | 專為Galera集群設(shè)計 | 用于Galera集群復(fù)制 |
KafkaBackend | 用于連接Kafka消息隊(duì)列 | 特殊用途,如日志轉(zhuǎn)發(fā)等 |
注意:以上列表基于MaxScale官方文檔和常見實(shí)踐整理,具體可用選項(xiàng)取決于你安裝的MaxScale版本
關(guān)鍵問題與解決方案
1 )主從延遲敏感查詢:
- 程序控制方案:開發(fā)人員顯式指定主庫連接執(zhí)行實(shí)時查詢
- 中間件方案:在SQL中添加注釋提示(如
/*ROUTE_TO_MASTER*/ SELECT...),但需代碼適配
2 )從庫擴(kuò)展性:
- 程序輪詢方式增減從庫需修改配置;中間件(如MaxScale)支持動態(tài)注冊新節(jié)點(diǎn)
3 )存儲過程處理:
- 建議將存儲過程拆分為獨(dú)立讀寫單元,或強(qiáng)制主庫執(zhí)行
關(guān)鍵技術(shù)細(xì)節(jié)補(bǔ)充與代碼示例
1 ) 主從復(fù)制延遲的影響
- 問題:主從延遲導(dǎo)致從庫數(shù)據(jù)短暫不一致,實(shí)時性敏感查詢需強(qiáng)制走主庫
- 解決方案:在查詢中添加路由提示(如
/* FORCE_MASTER */)
2 ) 原生 SQL 配置示例
主從復(fù)制基礎(chǔ)配置:
-- 主庫配置 (my.cnf) [mysqld] server-id=1 log-bin=mysql-bin binlog-format=ROW binlog-do-db=your_database -- 從庫配置 (my.cnf) [mysqld] server-id=2 relay-log=mysql-relay-bin read-only=1
從庫同步命令:
CHANGE MASTER TO MASTER_HOST='master_ip', MASTER_USER='replica_user', MASTER_PASSWORD='password', MASTER_LOG_FILE='mysql-bin.000001', MASTER_LOG_POS=107; START SLAVE;
3 ) NestJS 實(shí)現(xiàn)程序?qū)幼x寫分離
使用 TypeORM 多數(shù)據(jù)源配置:
// database.providers.ts
import { DataSource } from 'typeorm';
export const databaseProviders = [
{
provide: 'MASTER_DATABASE',
useFactory: () => new DataSource({
type: 'mysql',
host: 'master_host',
port: 3306,
username: 'master_user',
password: 'master_password',
database: 'your_db',
entities: [__dirname + '/..//*.entity{.ts,.js}'],
synchronize: false,
}).initialize(),
},
{
provide: 'SLAVE_DATABASE',
useFactory: () => new DataSource({
type: 'mysql',
host: 'slave_host',
port: 3306,
username: 'slave_user',
password: 'slave_password',
database: 'your_db',
entities: [__dirname + '/..//*.entity{.ts,.js}'],
synchronize: false,
}).initialize(),
},
];
// user.service.ts
import { Injectable, Inject } from '@nestjs/common';
import { InjectDataSource } from '@nestjs/typeorm';
import { DataSource } from 'typeorm';
@Injectable()
export class UserService {
constructor(
@Inject('MASTER_DATABASE') private masterDataSource: DataSource,
@Inject('SLAVE_DATABASE') private slaveDataSource: DataSource,
) {}
// 寫操作強(qiáng)制使用主庫
async createUser(userData: any) {
return this.masterDataSource.query(
`INSERT INTO users (name, email) VALUES (?, ?)`,
[userData.name, userData.email],
);
}
// 讀操作使用從庫(非實(shí)時敏感查詢)
async getUsers() {
return this.slaveDataSource.query(`SELECT * FROM users`);
}
// 實(shí)時敏感查詢使用主庫
async getRealTimeData() {
return this.masterDataSource.query(
`/* FORCE_MASTER */ SELECT * FROM transactions WHERE id = ?`,
[transactionId],
);
}
} 或
// database.providers.ts - 定義主從庫連接池
import { createPool, Pool } from 'mysql2/promise';
const masterPool: Pool = createPool({
host: 'master_db_host',
user: 'write_user',
password: 'write_password',
database: 'app_db',
connectionLimit: 10,
});
const slavePool: Pool = createPool({
host: 'slave_db_host', // 可擴(kuò)展為數(shù)組實(shí)現(xiàn)負(fù)載均衡
user: 'read_user',
password: 'read_password',
database: 'app_db',
connectionLimit: 20,
});
// query.service.ts - 根據(jù)操作類型路由查詢
import { Injectable } from '@nestjs/common';
@Injectable()
export class QueryService {
async executeQuery(sql: string, isWrite: boolean = false) {
const pool = isWrite ? masterPool : slavePool;
const [rows] = await pool.query(sql);
return rows;
}
// 實(shí)時性敏感查詢顯式指定主庫
async criticalQuery(sql: string) {
return this.executeQuery(`/*MASTER*/ ${sql}`, true);
}
}
// user.controller.ts - 業(yè)務(wù)層調(diào)用示例
@Controller('users')
export class UserController {
constructor(private queryService: QueryService) {}
@Get(':id')
async getUser(@Param('id') id: string) {
// 讀操作路由到從庫
return this.queryService.executeQuery(
`SELECT * FROM users WHERE id = ${id}`
);
}
@Post()
async createUser(@Body() userData) {
// 寫操作路由到主庫
return this.queryService.executeQuery(
`INSERT INTO users (name) VALUES ('${userData.name}')`,
true
);
}
}或
// 配置主庫和從庫連接
const masterDbConfig = { host: 'master-db', user: 'admin', password: 'pass' };
const slaveDbConfigs = [
{ host: 'slave-db1', user: 'readonly', password: 'pass' },
{ host: 'slave-db2', user: 'readonly', password: 'pass' }
];
// 手動路由讀寫操作
import { DataSource } from 'typeorm';
const masterDataSource = new DataSource({ ...masterDbConfig, type: 'mysql' });
const slaveDataSources = slaveDbConfigs.map(config => new DataSource({ ...config, type: 'mysql' }));
// 寫操作路由至主庫
async function executeWrite(query: string) {
await masterDataSource.query(query);
}
// 讀操作輪詢路由至從庫(簡單負(fù)載均衡)
let currentSlaveIndex = 0;
async function executeRead(query: string) {
const dataSource = slaveDataSources[currentSlaveIndex];
currentSlaveIndex = (currentSlaveIndex + 1) % slaveDataSources.length;
return dataSource.query(query);
}讀寫分離架構(gòu)優(yōu)化建議
1 ) 監(jiān)控與告警:
- 部署Prometheus + Grafana監(jiān)控主從延遲(
SHOW SLAVE STATUS中的Seconds_Behind_Master)
2 ) 故障轉(zhuǎn)移:
- 使用Keepalived實(shí)現(xiàn)中間件高可用,避免單點(diǎn)故障
3 ) 分庫分表協(xié)同:
- 讀寫分離可與ShardingSphere等分庫分表中間件集成,應(yīng)對海量數(shù)據(jù)場景
4 ) 連接池優(yōu)化:
- 配置從庫連接池大小為主庫的2-3倍(讀密集型場景)
5 ) 總結(jié):
- 讀寫分離是減輕主庫讀壓力的核心手段,負(fù)載均衡是分散從庫查詢負(fù)載的必備策略
- 程序控制方案靈活但維護(hù)成本高;中間件方案(如MaxScale)便捷但需性能驗(yàn)證
- 生產(chǎn)環(huán)境需結(jié)合基準(zhǔn)測試、實(shí)時監(jiān)控與代碼規(guī)范,平衡性能與復(fù)雜度
關(guān)鍵缺陷與應(yīng)對方案
1 ) 虛擬IP管理缺失
- ? 原生不支持自動分配VIP
- ? 解決方案:
# VIP切換腳本示例 (Python) import subprocess def assign_vip(new_master): subprocess.call(f"ssh {new_master} 'ifconfig eth0:1 192.168.1.100/24 up'", shell=True)
或集成Keepalived(需犧牲自動選主功能,增加架構(gòu)復(fù)雜度)
2 ) 復(fù)制鏈路監(jiān)控盲區(qū)
- 僅啟動/切換時檢查主從狀態(tài),運(yùn)行期無法檢測復(fù)制中斷或延遲
- 對比MMM缺乏從庫故障自動剔除機(jī)制
3 ) 安全風(fēng)險集中
- 強(qiáng)制要求全節(jié)點(diǎn)SSH免密互信
- 單點(diǎn)憑證泄露將威脅整個集群,多集群監(jiān)控場景風(fēng)險指數(shù)級放大
4 ) 讀負(fù)載均衡缺位
需額外引入代理層(如ProxySQL/MaxScale)實(shí)現(xiàn)讀流量分發(fā):
-- ProxySQL 讀分組配置示例 INSERT INTO mysql_servers (hostgroup_id, hostname, port) VALUES (10, 'slave1', 3306), (10, 'slave2', 3306); UPDATE mysql_query_rules SET destination_hostgroup=10 WHERE match_pattern='^SELECT'; LOAD MYSQL SERVERS TO RUNTIME;
技術(shù)強(qiáng)化建議
1 ) GTID+半同步最佳實(shí)踐
-- 啟用GTID與半同步 SET GLOBAL gtid_mode=ON; SET GLOBAL enforce_gtid_consistency=ON; INSTALL PLUGIN rpl_semi_sync_master SONAME 'semisync_master.so';
原生SQL半同步復(fù)制配置
-- 主庫配置 INSTALL PLUGIN rpl_semi_sync_master SONAME 'semisync_master.so'; SET GLOBAL rpl_semi_sync_master_enabled = 1; -- 從庫配置 INSTALL PLUGIN rpl_semi_sync_slave SONAME 'semisync_slave.so'; SET GLOBAL rpl_semi_sync_slave_enabled = 1;
2 ) NestJS健康檢查集成
// MHA狀態(tài)監(jiān)控端點(diǎn) (NestJS)
@Get('/mha-status')
async checkMHA() {
const ssh = await exec('masterha_check_status --conf=/etc/mha.cnf');
return { status: ssh.includes('alive') ? 'OK' : 'DOWN' };
} 3 ) 安全加固方案
- 使用證書代理跳板機(jī)替代直接SSH連接
- 通過Ansible Vault加密管理憑據(jù)
4 ) 架構(gòu)決策關(guān)鍵點(diǎn):
- 數(shù)據(jù)強(qiáng)一致場景:優(yōu)先采用 GTID+半同步+MHA
- 高可用擴(kuò)展需求:VIP管理需定制開發(fā)或整合Keepalived+自定義選主邏輯
- 安全合規(guī)環(huán)境:必須部署SSH證書中繼和網(wǎng)絡(luò)隔離策略
MHA架構(gòu)的深度剖析與優(yōu)化實(shí)踐
核心優(yōu)勢
1 ) 開源靈活性與擴(kuò)展性
- MHA采用Perl腳本語言開發(fā),提供完整的源代碼訪問權(quán)限和腳本接口
- 支持開發(fā)者使用Python等語言二次開發(fā)擴(kuò)展功能(如虛擬IP配置、郵件通知),只需確保參數(shù)與返回值格式兼容即可調(diào)用
2 ) 全面支持GTID復(fù)制
- 與早期MMM工具不同,MHA完美適配MySQL的GTID全局事務(wù)標(biāo)識復(fù)制機(jī)制,通過唯一事務(wù)ID確保數(shù)據(jù)一致性,顯著降低主從切換時數(shù)據(jù)丟失風(fēng)險
3 ) 智能故障切換策略
- 自動篩選數(shù)據(jù)最完備的從節(jié)點(diǎn)提升為主庫(基于日志應(yīng)用進(jìn)度)
- 最大化保留原主庫的二進(jìn)制日志
- 結(jié)合半同步復(fù)制可保障已提交事務(wù)零丟失
4 ) 集群化監(jiān)控能力
- 單監(jiān)控節(jié)點(diǎn)可管理多組主從集群,支持差異化切換策略,顯著降低服務(wù)器資源消耗
總結(jié)與建議
- 讀寫分離必要性:減輕主庫壓力是核心目標(biāo),尤其在高讀負(fù)載場景
- 方案選擇:
- 優(yōu)先中間件(如 MaxScale)快速實(shí)現(xiàn),但嚴(yán)格測試性能損耗
- 程序?qū)臃桨父`活,但需投入開發(fā)資源
- 負(fù)載均衡關(guān)鍵:結(jié)合軟硬件(如 HAProxy + MaxScale)實(shí)現(xiàn)動態(tài)擴(kuò)展
- 性能保障:所有方案上線前必須進(jìn)行 基準(zhǔn)測試,模擬高并發(fā)場景驗(yàn)證 QPS 與延遲
到此這篇關(guān)于MySQL數(shù)據(jù)庫讀寫分離與負(fù)載均衡的實(shí)現(xiàn)方式及深度分析的文章就介紹到這了,更多相關(guān)mysql數(shù)據(jù)庫讀寫分離內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Mysql 存在多條數(shù)據(jù)時如何按時間取最新的那一組數(shù)據(jù)(思路詳解)
這篇文章主要介紹了Mysql 存在多條數(shù)據(jù)時如何按時間取最新的那一組數(shù)據(jù),本文給大家分享兩種思路結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2024-04-04
MySQL 8.0.19安裝詳細(xì)教程(windows 64位)
這篇文章主要介紹了MySQL 8.0.19安裝詳細(xì)教程(windows 64位),本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-10-10
window10下mysql 8.0.20 安裝配置方法圖文教程
這篇文章主要為大家詳細(xì)介紹了window10下mysql 8.0.20 安裝配置方法圖文教程,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2020-05-05
MySQL數(shù)據(jù)庫遷移到Oracle數(shù)據(jù)庫的完整步驟記錄
在研發(fā)過程中可能會用到將表數(shù)據(jù)庫中的表結(jié)構(gòu)及數(shù)據(jù)遷移到另外一種數(shù)據(jù)庫中,比如說從mysql中遷移到oracle中,這篇文章主要給大家介紹了關(guān)于MySQL數(shù)據(jù)庫遷移到Oracle數(shù)據(jù)庫的完整步驟,需要的朋友可以參考下2024-06-06
mysql獲取指定時間段中所有日期或月份的語句(不設(shè)存儲過程,不加表)
最近需要用mysql獲取一個時間段中的所有月份,網(wǎng)上查都是要設(shè)置存儲過程或者加一個日期表的,不滿足我的需求,翻墻找資料加上自己試驗(yàn),如下代碼分享給大家2021-06-06

