簡介: 針對寫少讀多的業務可以考慮通過添加數據庫節點來使其達到提升性能的目的,但添加節點,往往涉及到數據的搬遷,擴容週期比較長,很難應對徒增的業務流量,這個時候可以考慮採用讀寫分離的方式,將讀寫流量做分流,減輕主實例的壓力,同時利用只讀庫橫向的擴展能力,快速提升讀性能。
在數據庫使用過程中經常會遇到一些場景:
- 業務寫流量一直相對比較穩定,但隨着時間,數據不斷增加,數據庫的壓力也會越來越大,寫操作會影響到讀請求的性能,做任何優化可能都達不到最終的效果;
- 在應用的用戶訪問量比較低的時候,一個數據庫的讀寫能力是完全能夠勝任的。但是在用戶訪問量增大的時候,數據庫很快會成爲瓶頸;
針對這種寫少讀多的業務可以考慮通過添加數據庫節點來使其達到提升性能的目的,但添加節點,往往涉及到數據的搬遷,擴容週期比較長,很難應對徒增的業務流量。這個時候可以考慮採用讀寫分離的方式,將讀寫流量做分流,減輕主實例的壓力,同時利用只讀庫橫向的擴展能力,快速提升讀性能。
其基本原理是讓主數據庫處理事務性查詢,而從數據庫處理select查詢。當業務量非常大時,一臺服務器的性能無法滿足需求,就可以讀寫分離方式分攤負載,避免因負載太高而造成無法及時響應請求。目前業界實現讀寫分離的方案主要有兩種:
基於程序代碼內部實現
在代碼中根據select,insert進行路由分類,這類方法也是目前生產環境應用最廣泛的,優點是性能好,因爲在程序代碼中已經將讀寫的數據源拆分至兩個,所以不需要額外的MySQL proxy解析SQL報文,在進行路由至不同數據庫節點。缺點是通常該架構較複雜,運維成本相對較高。
基於中間Proxy(odp/mycat等)實現
Proxy一般位於客戶端和服務器之間,代理服務器接到客戶端請求後通過解析SQL文本再將SQL路由至可用的數據庫節點中。優點是程序不需要改造可以實現無縫遷移,可移植性較好。缺點是性能相對前者略微遜色一些,並且並不是所有的讀操作都能夠被路由至從節點中。
上述方案都不能算是透明的,要不需要對業務代碼進行改造,要不需要業務系統依然第三方組件;除此之外,業界主流的讀寫方案都無法做到一致性讀,應用在使用弱一致性讀時,要充分考慮主備副本的數據同步延時,並根據具體業務場景考慮延時的業務影響(髒讀)是否能夠接受。讀寫分離業務都還需要做額外的改造,以應對只讀庫異常或者延遲過大的時候下,對業務做降級處理。
爲此,PolarDB-X內核側提出一種提供了透明的強一致的讀寫分離能力,支持多種讀寫分離策略滿足各類業務需求。
PolarDB-X Native 讀寫分離
PolarDB-X配置了多種讀寫策略,提供了透明的強一致的讀寫分離能力。簡單的說其特點有:
- 無論什麼狀況都不用擔心誤寫了“備副本或只讀副本”,因爲它不支持寫,寫操作會被路由到主副本;
- 無論什麼時候不用擔心“備副本或只讀副本”故障,因爲它會自動路由給其他正常的副本或者切回主副本;
- 無論什麼場景不用擔心 “備副本或只讀副本”讀不到最新的數據,因爲它提供的是強一致的讀寫能力;
- 大查詢不用擔心打爆“主副本”,因爲它支持將大查詢路由給”備副本或只讀副本“,避免對主副本造成壓力。
其整體的方案設計如下:
PolarDB-X存儲節點基於X-Paxos複製協議,整合了binary log,實現了統一的consensus log。consensus log不僅扮演了binary log的角色,同時還維護了LogIndex(全局一致性日誌位點)。LogIndex中記錄了主庫了最新修改consensus log位點信息,當只讀庫做一致性複製的時候,也會不斷更新自身的LogIndex信息。通過LogIndex我們可以確保在只讀庫上讀到最新的數據,結合TSO可以確保讀到已提交的一致性數據。路由到只讀節點的強一致性讀查詢過程如下:
- 客戶端把請求發送到CN;
- CN識別到請求會發送給只讀實例,首先會從主實例DN節點獲取當前最大LogIndex;
- CN把LogIndex +TSO 請求一起發送給只讀節點;
- 只讀節點根據接收到的LogIndex判斷是否等到只讀節點事務狀態回放到相應位點;根據TSO判斷數據可見性,給CN返回結果。
同時爲了降低每次一次只讀查詢都會分別與主庫交互獲取LogIndex,我們爲此做了異步Grouping Fetch Logindex優化,在高併發下儘可能將多次Fetch Logindex整合成一次請求,大大降低對主庫的壓力。
讀寫分離的操作管理
業務上不需要做任何改造,在需要的時候購買只讀實例,默認情況下主實例就具備了讀寫分離的能力。如果要開啓,需要在阿里控制檯點擊主實例上的集羣地址-》配置管理。
會進入下只讀分離的配置管理頁面
操作 |
解釋 |
資源配置 |
選擇購買的只讀實例加入到讀寫分離集羣,只有在讀寫分離集羣的實例纔可以分擔主實例的流量 |
智能讀寫分離 |
基於統計信息做代價估算,將偏AP的複雜查詢路由給只讀實例做MPP加速。非混合負載場景,可以選擇關閉 |
只讀流量佔比 |
將只讀流量設置按一定的比例路由給只讀實例 |
數據一致性 |
強一致性: 路由到只讀實例的請求可以讀到已提交最新鮮的數據,但如果只讀實例延遲大的話,路由到只讀實例 弱一致性: 路由到只讀實例的請求只是讀到實例上可見的最新數據。 |
只讀實例延遲閥值 |
只讀實例延遲超過閾值,那麼流量會打到其他延遲正常的只讀實例上或者切回主實例。 |
只讀可用性 |
當只讀實例延遲超過閾值或者只讀實例 HA時,則認爲當前只讀實例處於不可用的狀態,流量自動切回主實例或者其他正常的只讀實例上去。 |
用戶真正在使用的時候,業務上完全不需要改造任何代碼,一般只需要調整只讀流量佔比
,調整爲0意味着流量全部路由給主庫;調整成>0,只讀流量將按照設置的比例自動分流給只讀實例。在這個基礎上結合只讀實例延遲閥值
和只讀可用性
,可以做到在只讀實例異常狀態下,流量自動切回主實例或者其他正常的只讀實例上去。除了數據庫級別的配置,PolarDB-X也支持session級別和query級別的讀寫配置,可靈活控制某個session下只讀查詢或者某個具體query的路由分發。
讀寫分離的可用性測試
爲了衡量PolarDB-X 讀寫分離能力的可用性,我們利用sysbench 從正確性 性能角度設計了三種實驗場景來驗證。
測試環境
- 測試所用實例規格
主實例規格2*16C128G (CN)+2*4C32G(DN), 2個只讀實例規格 2*4C32G(CN) + 2*4C 32G(DN)
- 測試所用壓力機規格
ecs.g7ne.8xlarge(24 vCPU,48 GB內存)
測試參數配置
- 修改參數ENABLE_COROUTINE的值爲true,XPROTO_MAX_DN_CONCURRENT的值爲4000,詳細操作步驟請參見參數設置。
- 通過命令行連接到PolarDB-X實例,在同一會話內執行如下SQL語句,關閉日誌記錄與CPU採樣統計:
set ENABLE_SET_GLOBAL = true;
set global RECORD_SQL = false;
set global MPP_METRIC_LEVEL = 0;
set global ENABLE_CPU_PROFILE = false;
測試數據準備
nohup sysbench --config-file='sysb.conf' --create-table-options='partition by key(id)' --tables='16' --threads='16' --table-size='10000000' oltp_point_select prepare &
實驗一:一致性讀的正確性
從附件中下載這次測試的oltp_insert_read_verify.lua腳本,該腳本邏輯比較簡單,就是在主實例插入一條數據,測試在不同併發下在一致性讀開啓和關閉條件下,是否可以正確讀到。一次實驗的執行流程如下:
#準備表
sysbench --config-file='sysb.conf' --db-ps-mode='disable' --skip-trx='on' --mysql-ignore-errors='all' --tables='8' --threads={併發度} --time=60 oltp_insert_read_verify prepare
#運行
sysbench --config-file='sysb.conf' --db-ps-mode='disable' --skip-trx='on' --mysql-ignore-errors='all' --tables='8' --threads={併發度} --time=60 oltp_insert_read_verify run
#清理
sysbench --config-file='sysb.conf' --db-ps-mode='disable' --skip-trx='on' --mysql-ignore-errors='all' --tables='8' --threads={併發度} --time=60 oltp_insert_read_verify cleanup
測試結果
併發 |
不一致性次數(弱一致性讀) |
不一致性次數(強一致性讀) |
4 |
2 |
0 |
8 |
5 |
0 |
16 |
16 |
0 |
32 |
33 |
0 |
64 |
57 |
0 |
從測試結果看,強一致性讀下,可以保證路由到只讀實例的流量讀到最新的數據;而非強一致性讀下,無法保證。
實驗二:高併發下吞吐表現
我們分別利用oltp_point_select和oltp_read_only兩個腳本,測試了下在當前規格下高併發下的性能數據。
- 點查場景
sysbench --config-file='sysb.conf' --db-ps-mode='disable' --skip-trx='on' --mysql-ignore-errors='all' --tables='16' --table-size='10000000' --threads=512 oltp_point_select run
測試了在不同只讀實例個數,不同配置下的性能數據,其QPS表現如下
只讀實例查詢佔比 |
主實例+一個只讀實例(強一致性) |
主實例+一個只讀實例(弱一致性) |
主實例+兩個只讀實例(強一致性) |
主實例+兩個只讀實例(弱一致性) |
0% |
88201.17 |
88201.17 |
88201.17 |
88201.17 |
50% |
124269.63 |
171935.56 |
171783.84 |
208648.34 |
100% |
62891.86 |
89213.13 |
117133.83 |
169352.07 |
- oltp_read場景
sysbench --config-file='sysb.conf' --db-ps-mode='disable' --skip-trx='on' --mysql-ignore-errors='all' --tables='16' --table-size='10000000' --range-size=5 --threads=512 oltp_read_only run
測試了在不同只讀實例個數,不同配置下的性能數據,其QPS表現如下
只讀實例查詢佔比 |
主實例+一個只讀實例(強一致) |
主實例+一個只讀實例(弱一致) |
主實例+兩個只讀實例(強一致) |
主實例+兩個只讀實例(弱一致) |
0% |
29145.43 |
29145.43 |
29145.43 |
29145.43 |
50% |
44084.40 |
55399.80 |
61698.85 |
73161.11 |
100% |
23115.23 |
29235.73 |
42160.54 |
56393.54 |
從測試結果看:
1. 在強一致性讀下,在TP讀場景下流量從主實例切換到只讀實例上吞吐的性能衰減20~30%,但是通過添加只讀實例個數,性能可以做到一定的線性增加;
2.在弱一致性讀下,在TP讀場景下流量從主實例切換到只讀實例上吞吐的性能未衰減,且通過添加只讀實例的個數,性能可以做到線性增加;
實驗三:低併發下RT表現
同樣的我們分別利用oltp_point_select和oltp_read_only兩個腳本,測試了下在(一個主實例+只讀實例)下在強一致性讀條件下,不同併發的RT表現。
- 點查場景(ms)
併發 |
只讀查詢比例0% |
只讀查詢比例100% |
只讀查詢比例50% |
4 |
0.61 |
0.89 |
0.87 |
8 |
0.62 |
0.91 |
0.89 |
16 |
0.64 |
0.94 |
0.90 |
32 |
0.71 |
1.04 |
0.99 |
64 |
1.18 |
1.27 |
1.27 |
- oltp_read場景(ms)
併發 |
只讀查詢比例0% |
只讀查詢比例100% |
只讀查詢比例50% |
4 |
11.65 |
21.89 |
17.63 |
8 |
12.21 |
22.69 |
17.29 |
16 |
14.21 |
23.95 |
18.23 |
32 |
20 |
27.17 |
21.50 |
64 |
40.5 |
50.7 |
31.37 |
從測試結果看:
- 低併發場景下,只讀實例和主實例壓力都不大,流量從主實例切換到只讀實例上,RT衰減近30%~40%, 但都是在業務可以接受的RT範圍內抖動;
- 隨着併發增加,流量從主實例切換到只讀實例上RT衰減會變得不明顯,主實例的資源會成爲影響RT的主要因素。
本文的讀寫分離路由的是非事務下的只讀流量,這裏給大家提一個問題:事務下的讀查詢是否可以做讀寫分離?