Redis多集群主從部署
一、背景
這兩天在策劃一套Redis多機房多集群的部署方案,一主多從部署;目的是將一些重要、核心、且敏感的數據放在Redis中進行管理,通過異步消息隊列的方式,同步到db。
二、方案
1、機房與主庫的設定
- 假設有三個機房,分別是JF-A、JF-B、JF-C,其中涉及到Redis數據寫操作的只在JF-A,其余兩個機房只存有讀操作,所以Redis主庫部署在JF-A。
- 為了提前考慮到災備的情況,假設Redis主庫宕機,此時需要有快速的主庫恢復方案,所以在JF-A同步部署一個Redis master backup,通過運維手段,控制master與backup之間的自動切換。
- 進一步再考慮到JF-A斷網的情況(比如光釬被挖斷了:just a joke),需要在B機房也部署一套Redis master backup,通過運維手段控制所有Redis的主從對應關系 </ul>
- A機房主庫:JF-A-Master
- A級方備用主庫:JF-A-M-Backup(slaveof JF-A-Master)
- B級方備用主庫:JF-B-M-Backup(slaveof JF-A-Master)
- A機房從庫1:JF-A-S-1(slaveof JF-A-Master)
- A機房從庫2:JF-A-S-2(slaveof JF-A-S-1),S-1和S-2屬于同一臺機器的不同實例
- B機房從庫1:JF-B-S-1(slaveof JF-A-Master)
- B機房從庫2:JF-B-S-2(slaveof JF-B-S-1),S-1和S-2屬于同一臺機器的不同實例
- C機房從庫1:JF-C-S-1(slaveof JF-A-Master)
- C機房從庫2:JF-C-S-2(slaveof JF-C-S-1),S-1和S-2屬于同一臺機器的不同實例 </ul>
- 新增Redis從庫時,從庫采取Pull方式,發送SYNC信號從主庫拉數據(第一次)
- 主庫有數據更新時,會遍歷connected_slaves主動將數據Push到從庫
- 從庫維持一個與主庫的連接心跳,以此保證主庫能實時將數據同步從庫(repl-ping-slave-peroid配置,默認10s) </ul>
- Slave服務器連接到Master服務器.
- Slave服務器發送SYNC命令.
- Master服務器備份數據庫到.rdb文件.
- Master服務器把.rdb文件傳輸給Slave服務器.
- Slave服務器把.rdb文件數據導入到數據庫中. </ul>
2、各機房部署方案
考慮到主庫的同步壓力,以及跨機房大數據進行V*N同步耗資源的情況,采取樹狀主從同步方案:
三、通過Master配置檢測主從關系
如上圖,從slave0~4,分別表示各機房的備用主庫以及從庫,各從庫機器上,還有另外一個從庫實例,通過樹狀方式掛載到這些從庫上。
四、主從同步方案
Redis的主從同步基本是采取Pull&Push搭配的方式:
------------以下內容轉載自: Redis主從實現分析原理
一、主從實現原理
上面的這5步是同步的第一階段, 接下來在Master服務器上調用每一個命令都使用replicationFeedSlaves()來同步到Slave服務器.
二、主從同步實現細節
1、Slave服務器連接到Master服務器 / 發送SYNC命令
Slave服務器通過syncWithMaster()函數來連接Master服務器(如果Master服務器需要密碼登陸的話, 先登陸), 并且發送SYNC命令請求同步, 接著打開rdb文件(用于存儲由Master發送過來的數據), 創建讀rdb的IO事件(readSyncBulkPayload). 代碼如下:
int syncWithMaster(void) { ...... //登陸master服務器 if(server.masterauth) { syncWrite(fd, "AUTH xxx\r\n", strlen(server.masterauth)+7, 5); ...... } //發送SYNC命令 syncWrite(fd,"SYNC \r\n",7,5); ...... //打開rdb文件 dfd = open(tmpfile,O_CREAT|O_WRONLY|O_EXCL,0644); ...... //創建讀rdb的IO事件 aeCreateFileEvent(server.el, fd, AE_READABLE, readSyncBulkPayload, NULL); ...... return REDIS_OK; }
2、Master服務器備份數據庫到.rdb文件
當Slave服務器發送SYNC命令到Master服務器時, Master服務器便會調用syncCommand()函數來進行同步. 同步的第一步是把數據庫的數據存儲為rdb文件, 存儲完畢后調用updateSlavesWaitingBgsave()函數來發送rdb文件給所有的Slave服務器.代碼如下:
void syncCommand(redisClient *c) { //如果正在保存rdb文件 if (server.bgsavechildpid != -1) { ...... //主要判斷當前存儲rdb文件是不是由SYNC命令觸發的 //如果當前存儲rdb文件不是由SYNC命令觸發, 則要等到下一次 ...... } else {//否則調用rdbSaveBackground()存儲rdb文件 rdbSaveBackground(server.dbfilename); } }
當rdbSaveBackground()函數執行完畢, 就會調用updateSlavesWaitingBgsave()來發送rdb文件到所有的Slave服務器, 代碼如下:
void updateSlavesWaitingBgsave(int bgsaveerr) { listRewind(server.slaves,&li); while((ln = listNext(&li))) { slave->repldbfd = open(server.dbfilename,O_RDONLY); ....... aeCreateFileEvent(server.el,slave->fd,AE_WRITABLE, sendBulkToSlave,slave); } }
updateSlavesWaitingBgsave()要做的事情是, 打開rdb文件, 創建發送rdb文件IO事件(sendBulkToSlave). 而sendBulkToSlave()主要的工作就是把rdb文件發送給Slave服務器。而當Slave服務器接收rdb文件完畢之后 (readSyncBulkPayload()函數處理), 會清空原來數據庫的數據, 然后把rdb文件的數據導入到數據庫中。
3、增量同步
完成上面的步驟之后, 同步基本完成. 接下來的工作就是增量同步, 也就是當Master服務器有數據更新的時候, 會立刻同步到所有的Slave服務器. 由replicationFeedSlaves()函數完成。當我們在Master服務器增減數據的時候, 就會觸發replicationFeedSlaves(), 代碼如下:
void call(redisClient *c, struct redisCommand *cmd) { ...... if ((dirty || cmd->flags & REDIS_CMD_FORCE_REPLICATION) && listLength(server.slaves)) { replicationFeedSlaves(server.slaves,c->db->id,c->argv,c->argc); } ...... }
call()函數就是當用戶執行命令的時候觸發. 而dirty表示是否有數據更新, 如果有數據更新而且slave服務器不為空, 就執行replicationFeedSlaves()。而replicationFeedSlaves()主要做的工作就是把用戶執行的命令發送到所有 的Slave服務器, 讓Slave服務器執行,這樣就可以實施同步功能。