Redis主從複製

小松聊PHP进阶發表於2024-06-16

主從複製

  • 官方文件:https://redis.io/docs/latest/operate/oss_and_stack/management/replication/
  • 極簡概括:將一個主Redis伺服器的資料複製到其它從Redis伺服器的過程。
  • 角色:
    • 主節點(Master):負責處理客戶端的寫(或者讀)請求,並將寫操作同步到從節點。
    • 從節點(Slave):負責處理客戶端的讀請求,並將主節點傳送過來的資料更新資料複製到本地。
  • 解決問題:
    • 容災備份:主節點發生故障,從節點有對應的資料備份。
    • 負載均衡:當單個Redis節點扛不住時,利用讀寫分離的特性,多個Redis從節點分攤主節點讀的壓力。
  • 適用場景:讀多寫少大流量的服務端應用。
  • 優點:
    • 資料備份。
    • 負載均衡。
    • 有一定的容災性:當主節點和從節點之間的由於網路問題斷開,從伺服器會重新連線並嘗試繼續進行部分重新同步。當部分重新同步不可行時,複製副本將要求完全重新同步。
      +非阻塞: Redis主從複製是在主節點上非阻塞的。這意味著當一個或多個從節點執行初始同步或部分重新同步時,主伺服器將繼續響應客戶端的請求。
    • Redis主從複製在從節點上,基本也是非阻塞的,大key情況除外。
    • 支援級聯架構:Redis從節點也可成為其它例項的主節點,形成級聯架構。
    • 低耦合容災:當Redis主節點掛掉時,從節點還能維持基本的讀操作。
    • 進度同步:不同從節點可能存在著不同的進度,Redis有自動糾正偏移量的機制,使其每個節點保持同一進度。
  • 缺點:
    • 多臺機器不可避免的增加運維成本,
    • 受網路隔離和Redis本身的特性,無法保證主從強一致性,極端情況下,主節點活從節點掛掉,就會導致不一致。
    • 高併發或有大key的前提下,不可避免的出現主從複製延遲問題。
    • 若主節點掛掉,Redis主從功能本身缺少從節點自動升級為主節點的策略,因此出現了哨兵模式。

相關命令或配置

  • 命令(Redis服務停止前一直生效,適用於不停機熱配置)
    • info replication:檢視主從節點的狀況。
    • slaveof 主庫IP 主節點埠:在從節點配置主節點。
    • slaveof no one:讓Redis不做任何節點的從節點。
  • 配置(持久化配置):
    • replicaof 主庫IP 主庫埠:在從節點配置主節點。
    • masterauth 主節點密碼:在從節點配置主節點密碼。

一主二從實操

  • 誤區:Redis主從不比MySQL,Redis只需要在從庫上配置主庫就可以了。
  • 環境:CentOS7.6,配置了3臺可正常執行Redis的服務,且保證每個Redis例項可遠端連線(同一區域網、防火牆、埠放行、遠端連線許可權配置到位)。192.168.0.180(主)、192.168.0.181(從1)、192.168.0.182(從2)。
  • 記憶體問題:3個系統佔記憶體,在虛擬機器上將每個系統記憶體配置到256MB,Linux輕鬆啟動。
在從節點1和從節點2上分別進行配置
vim /usr/local/redis/etc/redis.conf
配置IP和埠
replicaof 192.168.0.180 6379
配置密碼
masterauth 123456

之後重啟redis


測試:
主節點上執行set a a
兩臺從節點上get a發現都能獲取到值。

若在從節點上設定值,會有如下錯誤:
(error) READONLY You can't write against a read only replica.

此時主節點執行info replication,會得到如下結果:
# Replication
role:master
connected_slaves:2
slave0:ip=192.168.0.182,port=6379,state=online,offset=2736,lag=1
slave1:ip=192.168.0.181,port=6379,state=online,offset=2736,lag=1
master_replid:9aaf7cbbcfb924c8a793e1b69f0597a57768844b
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:2736
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:2736

註釋:
Replication是註釋。
connected_slaves:表示從節點數量
slave0和slave1:描述兩個從節點的基本資訊。
master_replid: 主伺服器的複製 ID
master_replid2: 用於支援部分同步複製的第二複製 ID
master_repl_offset: 主伺服器的複製偏移量,複製的進度。
repl_backlog_active: 啟用複製 backlog 功能
repl_backlog_size: 複製 backlog 的大小為1048576位元組
repl_backlog_first_byte_offset: 複製 backlog 中第一個位元組的偏移量
repl_backlog_histlen: 複製 backlog 的歷史長度為2736位元組

backlog
複製積壓緩衝區,當主節點有連線的slave時建立,主節點響應寫請求時,會先寫到自己的backlog buffer中。
可以簡單的理解為,避免資料改動一個位元組就頻繁同步從節點,而是積累到一定的批次做緩衝。

從節點執行得到如下結果
# Replication
role:slave
master_host:192.168.0.180
master_port:6379
master_link_status:up
master_last_io_seconds_ago:3
master_sync_in_progress:0
slave_repl_offset:518
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:f1f768608ae81b330bb152f69a4a624bf3a9657e
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:518
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:518


role:slave: 表示這是一個從節點。
master_host:192.168.0.180: 指定了主節點的IP地址。
master_port:6379: 指定了主節點的埠號。
master_link_status:up: 表示從節點與主節點的連線狀態為正常(已連線)。
master_last_io_seconds_ago:3: 上次與主節點進行IO操作的時間,這裡是3秒前。
master_sync_in_progress:0: 沒有正在進行的同步操作。
slave_repl_offset:518: 從節點當前複製偏移量為518。
slave_priority:100: 從節點的複製優先順序為100。
slave_read_only:1: 從節點設定為只讀模式(read-only)。
connected_slaves:0: 連線的從節點數為0,即沒有其他從節點連線到這個從節點。
master_replid:f1f768608ae81b330bb152f69a4a624bf3a9657e: 主節點的複製ID。
master_replid2:0000000000000000000000000000000000000000: 第二複製ID。
master_repl_offset:518: 主節點當前複製偏移量為518。
second_repl_offset:-1: 第二複製偏移量為-1。
repl_backlog_active:1: 複製後備日誌(repl backlog)處於啟用狀態。
repl_backlog_size:1048576: 複製後備日誌的大小為1048576位元組(1MB)。
repl_backlog_first_byte_offset:1: 複製後備日誌的第一個位元組偏移量為1。
repl_backlog_histlen:518: 複製後備日誌歷史長度為518。

一主一從再一從實操

可以理解為爹子孫三代。
這個用法與一主二從沒有什麼區別,可以接著一主二從的基礎上,在從節點2上臨時設定slave of 192.168.0.181 6379(從1為從2主節點)即可。
注意密碼問題,由於命令列不支援masterauth命令,所以可能需要在從節點配置檔案中先把主節點密碼設定到位。

程式語言呼叫的繫結問題

  • 以PHP為例,讀寫呼叫主或者從伺服器,是由PHP控制的,不是由Redis主節點控制的(Redis主節點就沒有從節點的配置)。
  • PHP Redis擴充套件雖然提供了豐富的Redis Client API,但是沒有專門應對主從的解決方案。因此原生開發,讀寫操作呼叫那臺伺服器,是由開發者自定義控制,而不是PHP的內部實現。
    例如:
$redis_master = new Redis();
$redis_master->connect('192.168.0.180', 6379);
$redis_master->auth('123456');

$redis_slave = new Redis();
$redis_slave->connect('192.168.0.181', 6379);

$redis_master->set('k', 'v');
print_r($redis_master->get('k'));

如果是從庫寫,也會報如上一樣的錯誤:
Fatal error: Uncaught RedisException: READONLY You can't write against a read only replica. in E:\Host\test\t1.php:10 Stack trace: #0 E:\Host\test\t1.php(10): Redis->set('k', 'v') #1 {main} thrown in E:\Host\test\t1.php on line 10

對於Laravel框架:

config/database.php中的redis段,新增
'slave' => [
    'client' => 'predis',
    'host' => 'your_slave_redis_host',
    'password' => 'your_slave_redis_password',
    'port' => 6379,
    'database' => 0,
],

使用主節點
Redis::connection('default')->set('key', 'value');
使用從節點
Redis::connection('slave')->get('key');

從節點資料覆蓋問題

經過實測,若一個redis例項被做為從節點,那麼原先的老資料會被清空,這可能會造成資料丟失。

主從節點進度不一致問題

Redis的特性,主從複製效能不會差,除非遇到大key。所以這裡討論的是,從節點1和主節點進度相同,從節點2和主節點進度不同的問題。
可以模擬從節點2掛掉,直接關停從節點2,主節點多寫幾個key,從節點1正常同步。此時啟動從節點2,經過實測,發現進度得到了同步。
這意味Redis會自動處理進度不一致的問題,達到的是最終一致性。

主節點掛掉後從節點的狀態

經過實測,從節點不會自動變成主節點,從節點上的資料可以正常讀取,仍舊不能寫入。
比較好的一點是,雖然資料層,從節點強依賴主節點,但是主節點掛掉後,從節點沒有掛掉,勉強使用。
此時從節點不進行任何操作,若主節點恢復,則整個主從架構正常執行。

單執行緒下卻可以非同步複製的思考

官網有說:Redis主從複製的內部過程,是非同步進行的(非同步非阻塞,高效能)。由於Redis是單執行緒,通俗講就是同時只能執行一個任務,卻進行了非同步實現,這個問題值得理解。

透過查閱資料,得知單執行緒非同步是透過事件迴圈機制來處理。
單執行緒環境下:當一個非同步操作被啟動後,程式會繼續執行其它任務,但非同步操作的結果或者狀態變化會被放入事件佇列中,等待事件迴圈處理。當事件迴圈處理到該事件時,會呼叫相應的回撥函式來處理操作的結果或者狀態變化。在這種情況下,不需要額外建立新的執行緒。

按照我的理解,通俗講,整個過程就是一個迴圈機制,不停的迴圈,獲取事件佇列上的資料,一是可以讓待執行的非同步任務再下一輪迴圈中去執行,二是可以再本次迴圈中執行同步的任務,直到任務為空從而停止,不需要額外現成的依賴。

從節點備份的不可靠因素

純快取模式下:主節點崩潰,但是伺服器設定了一些運維策略,可以自動重啟程序。但Redis主節點啟動時,將以空資料集重新啟動。其它從節點快取的資料,也將會被銷燬。

所以:
複製與不帶永續性配置的master一起使用時,應該禁用例項的自動重啟。
或者,做好主節點的AOF與RDB配置。

RDB:配置save ‘’,並註釋掉其它save項。
AOF:配置appendonly no
可修改為純快取模式,親測確實會丟失資料。

相關文章