1.Redis複制的原理和優化
1.1 Redis單機的問題
1.1.1 機器故障
在一台伺服器上部署一個Redis節點,如果機器發生主機闆損壞,硬碟損壞等問題,不能在短時間修複完成,就不能處理Redis操作了,這就是單機可能存在的問題
同樣的,伺服器正常運作,但是Redis主程序發生當機事件,此時隻需要重新開機Redis就可以了。如果不考慮在Redis重新開機期間的性能損失,可以考慮Redis的單機部署
Redis單機部署出現故障時,把Redis遷移到另一台伺服器上,此時需要把發生故障的Redis中的資料同步到新部署的Redis節點,這也需要很高的成本
1.1.2 容量瓶頸
一台伺服器有16G記憶體,此時配置設定12G記憶體運作Redis
如果有新需求:Redis需要占用32G或者64G等更多的記憶體,此時這台伺服器就不能滿足需求了,此時可以考慮更換一台更大記憶體的伺服器,也可以用多台伺服器組成一個Redis叢集來滿足這個需求
1.1.3 QPS瓶頸
根據Redis官方的說法,單台Redis可以支援10萬的QPS,如果現在的業務需要100萬的QPS,此時可以考慮使用Redis分布式
2.什麼是主從複制
2.1 一主一從模型
一個Redis節點為master節點(主節點),負責對外提供服務。
另一個節點為slave節點(從節點),負責同步主節點的資料,以達到備份的效果。當主節點發生當機等故障時,從節點也可以對外提供服務
如下圖所示

2.2 一主多從模型
多個節點為slave節點(從節點)。每個slave都會對主節點中的資料進行備份,以達到更加高可用的效果。這種情況下就算master和一個slave同時發生當機故障,其餘的slave仍然可以對外讀提供服務,并保證資料不會丢失
當master有很多讀寫,達到Redis的極限閥值,可以使用多個slave節點對Redis的讀操作進行分流,有效實作流量的分流和負載均衡,是以一主多從也可以做讀寫分離
2.3 讀寫分離模型
master節點負責寫資料,同時用戶端可以從slave節點讀取資料
3.主從複制作用
對資料提供了多個備份,這些備份資料可以大大提高Redis的讀性能,是Redis高可用或者分布式的基礎
4.主從複制的配置
4.1 slaveof指令
取消複制
4.2 配置檔案配置
修改Redis配置檔案
/etc/redis.conf
slaveof <masterip> <masterport> # masterip為主節點IP位址,masterport為主節點端口
slave-read-only yes # 從節點隻做讀操作,不做寫操作,保證主從裝置資料相同
4.3 兩種主從配置方式比較
使用指令行配置無需重新開機Redis,可以實作統一配置
使用配置檔案方式配置不變于管理,而且需要重新開機Redis
4.4 例子
有兩台虛拟機,作業系統都是
CentOS 7.5
一台虛拟機的IP位址為192.168.81.100,做master
一台虛拟機的IP位址為192.168.81.101,做slave
第一步:在192.168.81.101虛拟機操作
[root@mysql ~]# vi /etc/redis.conf # 修改Redis配置檔案
bind 0.0.0.0 # 可以從外部連接配接Redis服務端
slaveof 192.168.81.100 6379 # 設定master的IP位址和端口
然後儲存修改,啟動Redis
[root@mysql ~]# systemctl stop firewalld # 關閉firewalld防火牆
[root@mysql ~]# systemctl start redis # 啟動slave上的Redis服務端
[root@mysql ~]# ps aux | grep redis-server # 檢視redis-server的程序
redis 2319 0.3 0.8 155204 18104 ? Ssl 09:55 0:00 /usr/bin/redis-server 0.0.0.0:6379
root 2335 0.0 0.0 112664 968 pts/2 R+ 09:56 0:00 grep --color=auto redis
[root@mysql ~]# redis-cli # 啟動Redis用戶端
127.0.0.1:6379> info replication # 檢視Redis的複制資訊 檢視192.168.81.101機器上的Redis的info
# Replication
role:slave # 角色為slave
master_host:192.168.81.100 # 主節點IP為192.168.81.100
master_port:6379 # 主節點端口為6379
master_link_status:up
master_last_io_seconds_ago:5
master_sync_in_progress:0
slave_repl_offset:155
slave_priority:100
slave_read_only:1
connected_slaves:0
master_repl_offset:0
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
第二步:在192.168.81.100虛拟機上操作
[root@localhost ~]# systemctl stop firewalld # 關閉firewalld防火牆
[root@localhost ~]# vi /etc/redis.conf # 修改Redis配置檔案
bind 0.0.0.0
然後儲存修改,啟動Redis
[root@localhost ~]# systemctl start redis # 啟動master上的Redis
[root@localhost ~]# ps aux | grep redis-server # 檢視redis-server程序
redis 2529 0.2 1.8 155192 18192 ? Ssl 17:55 0:00 /usr/bin/redis-server 0.0.0.0:6379
root 2536 0.0 0.0 112648 960 pts/2 R+ 17:56 0:00 grep --color=auto redis
[root@localhost ~]# redis-cli # 啟動master上的redis-cli用戶端
127.0.0.1:6379> info replication # 檢視192.168.81.100機器上Redis的資訊
# Replication
role:master # 角色為主節點
connected_slaves:1 # 連接配接一個從節點
slave0:ip=192.168.81.101,port=6379,state=online,offset=141,lag=2 # 從節點的資訊
master_repl_offset:141
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:2
repl_backlog_histlen:140
127.0.0.1:6379> set hello world # 向主節點寫入資料
OK
127.0.0.1:6379> info server
# Server
redis_version:3.2.10
redis_git_sha1:00000000
redis_git_dirty:0
redis_build_id:c8b45a0ec7dc67c6
redis_mode:standalone
os:Linux 3.10.0-514.el7.x86_64 x86_64
arch_bits:64
multiplexing_api:epoll
gcc_version:4.8.5
process_id:2529
run_id:7091f874c7c3eeadae873d3e6704e67637d8772b # 注意這個run_id
tcp_port:6379
uptime_in_seconds:488
uptime_in_days:0
hz:10
lru_clock:12784741
executable:/usr/bin/redis-server
config_file:/etc/redis.conf
第三步:回到192.168.81.101這台從節點上操作
127.0.0.1:6379> get hello # 擷取'hello'的值,可以擷取到
"world"
127.0.0.1:6379> set a b # 向192.168.81.101從節點寫入資料,失敗
(error) READONLY You can't write against a read only slave.
127.0.0.1:6379> slaveof no one # 取消從節點設定
OK
127.0.0.1:6379> info replication # 檢視192.168.81.101機器,已經不再是從節點,而變成主節點了
# Replication
role:master # 變成主節點了
connected_slaves:0
master_repl_offset:787
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
127.0.0.1:6379> dbsize # 檢視192.168.81.101上Redis所有資料大小
(integer) 2
第四步:回到192.168.81.100虛拟機
127.0.0.1:6379> mset a b c d e f # 向192.168.81.100上的Redis集合中寫入資料
OK
127.0.0.1:6379> dbsize # Redis中資料大小為5
(integer) 5
第五步:檢視192.168.81.100虛拟機上Redis的日志
[root@localhost ~]# tail /var/log/redis/redis.log # 檢視Redis最後10行日志
2529:M 14 Oct 17:55:09.448 * DB loaded from disk: 0.026 seconds
2529:M 14 Oct 17:55:09.448 * The server is now ready to accept connections on port 6379
2529:M 14 Oct 17:55:10.118 * Slave 192.168.81.101:6379 asks for synchronization
2529:M 14 Oct 17:55:10.118 * Partial resynchronization not accepted: Runid mismatch (Client asked for runid '9f93f85bce758b9c48e72d96a182a2966940cf52', my runid is '7091f874c7c3eeadae873d3e6704e67637d8772b') # 與192.168.81.100裝置上通過info指令檢視到的run_id相同
2529:M 14 Oct 17:55:10.118 * Starting BGSAVE for SYNC with target: disk # 執行BGSAVE指令成功
2529:M 14 Oct 17:55:10.119 * Background saving started by pid 2532
2532:C 14 Oct 17:55:10.158 * DB saved on disk
2532:C 14 Oct 17:55:10.159 * RDB: 12 MB of memory used by copy-on-write
2529:M 14 Oct 17:55:10.254 * Background saving terminated with success
2529:M 14 Oct 17:55:10.256 * Synchronization with slave 192.168.81.101:6379 succeeded # 向192.168.81.101同步資料成功
第六步:回到192.168.81.101虛拟機
127.0.0.1:6379> slaveof 192.168.81.100 6379 # 把192.168.81.101重新設定為192.168.81.100的從節點
OK
127.0.0.1:6379> dbsize
(integer) 5
127.0.0.1:6379> mget a
1) "b"
第七步:檢視192.168.81.101虛拟機上Redis的日志
[root@mysql ~]# tail /var/log/redis/redis.log # 檢視Redis最後10行日志
2319:S 14 Oct 09:55:17.625 * MASTER <-> SLAVE sync started
2319:S 14 Oct 09:55:17.625 * Non blocking connect for SYNC fired the event.
2319:S 14 Oct 09:55:17.626 * Master replied to PING, replication can continue...
2319:S 14 Oct 09:55:17.626 * Trying a partial resynchronization (request 9f93f85bce758b9c48e72d96a182a2966940cf52:16).
2319:S 14 Oct 09:55:17.628 * Full resync from master: 7091f874c7c3eeadae873d3e6704e67637d8772b:1 # 從master節點全量複制資料
2319:S 14 Oct 09:55:17.629 * Discarding previously cached master state.
2319:S 14 Oct 09:55:17.763 * MASTER <-> SLAVE sync: receiving 366035 bytes from master # 顯示從master同步的資料大小
2319:S 14 Oct 09:55:17.765 * MASTER <-> SLAVE sync: Flushing old data # slave清空原來的資料
2319:S 14 Oct 09:55:17.779 * MASTER <-> SLAVE sync: Loading DB in memory # 加載同步過來的RDB檔案
2319:S 14 Oct 09:55:17.804 * MASTER <-> SLAVE sync: Finished with success
5.全量複制和部分複制
5.1 全量複制
5.1.1 run_id的概念
Redis每次啟動時,都有一個随機ID來辨別Redis,這個随機ID就是上面通過info指令檢視得到的run_id
檢視192.168.81.101虛拟機上的run_id和偏移量
[root@localhost ~]# redis-cli info server |grep run_id
run_id:7e366f6029d3525177392e98604ceb5195980518
[root@localhost ~]# redis-cli info |grep master_repl_offset
master_repl_offset:0
檢視192.168.91.100虛拟機上的run_id和偏移量
[root@mysql ~]# redis-cli info server | grep run_id
run_id:7091f874c7c3eeadae873d3e6704e67637d8772b
[root@mysql ~]# redis-cli info | grep master_repl_offset
master_repl_offset:4483
run_id是一個非常重要的辨別。
在上面的例子裡,192.168.81.101做為slave去複制192.168.81.100這個master上的資料,會擷取192.168.81.100機器上對應的run_id在192.168.81.101上做一個辨別
當192.168.81.100機器上的Redis的run_id發生改變,意味着192.168.81.100機器上的Redis發生重新開機操作或者别的重大變化,192.168.81.101就會把192.168.81.100上的資料全部同步到192.168.81.101上,這就是全量複制的概念
5.1.2 offset的概念
偏移量(offset)就是資料寫入量的位元組數。
在192.168.81.100的Redis上寫入資料時,master就會記錄寫了多少資料,并記錄在偏移量中。
在192.168.81.100上的操作,會同步到192.168.81.101機器上,192.168.81.101上的Redis也會記錄偏移量。
當兩台機器上的偏移量相同時,代表資料同步完成
偏移量是部分複制很重要的依據
檢視192.168.81.100機器上Redis的偏移量
127.0.0.1:6379> info replication # 檢視複制資訊
# Replication
role:master
connected_slaves:1
slave0:ip=192.168.81.101,port=6379,state=online,offset=8602,lag=0
master_repl_offset:8602 # 此時192.168.81.100上的偏移量是8602
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:2
repl_backlog_histlen:8601
127.0.0.1:6379> set k1 v1 # 向192.168.81.100寫入資料
OK
127.0.0.1:6379> set k2 v2 # 向192.168.81.100寫入資料
OK
127.0.0.1:6379> set k3 v3 # 向192.168.81.100寫入資料
OK
127.0.0.1:6379> info replication # 檢視複制資訊
# Replication
role:master
connected_slaves:1
slave0:ip=192.168.81.101,port=6379,state=online,offset=8759,lag=1
master_repl_offset:8759 # 寫入資料後192.168.81.100上的偏移量是8759
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:2
repl_backlog_histlen:8758
檢視192.168.81.101機器上Redis的偏移量
127.0.0.1:6379> info replication # 檢視複制資訊
# Replication
role:slave
master_host:192.168.81.100
master_port:6379
master_link_status:up
master_last_io_seconds_ago:8
master_sync_in_progress:0
slave_repl_offset:8602
slave_priority:100
slave_read_only:1
connected_slaves:0
master_repl_offset:0 # 此時192.168.81.101上的偏移量是8602
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
127.0.0.1:6379> get k1
"v1"
127.0.0.1:6379> get k2
"v2"
127.0.0.1:6379> get k3
"v3"
127.0.0.1:6379> info replication # 檢視複制資訊
# Replication
role:slave
master_host:192.168.81.100
master_port:6379
master_link_status:up
master_last_io_seconds_ago:7
master_sync_in_progress:0
slave_repl_offset:8759 # 同步資料後192.168.81.101上的偏移量是8759
slave_priority:100
slave_read_only:1
connected_slaves:0
master_repl_offset:0
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
如果主從節點上的offset差距太大,說明從節點沒有從主節點同步資料,主從節點之間的連接配接出現問題:比如網絡,阻塞,緩沖區等
5.1.3 全量複制的概念
如果一個主節點上已經寫入很多資料,此時從節點不僅同步已經有的資料,同時同步slave在同步期間master上被寫入的資料(如果在同步期間master被寫入資料),以達到資料完全同步的目的,這就是Redis的全量複制的功能
Redis的master會把目前的RDB檔案同步給slave,在此期間master中寫入的資料會被寫入
複制緩沖區(repl_back_buffer)
中,當RDB檔案同步到slave完成,
master通過偏移量的對比
,把
複制緩沖區(repl_back_buffer)
中的資料同步給slave
Redis使用
psync
指令進行資料全量複制和部分複制
psync指令有兩個參數:run_id和偏移量
psync指令的步驟:
1.在slave第一次向master同步資料時,不知道master的run_id和offset,使用`psync ? -1`指令向master發起同步請求
2.master接受請求後,知道slave是做全量複制,master就會把run_id和offset響應給slave
3.slave儲存master發送過來的run_id和offset
4.master響應slave後,執行BGSAVE指令把目前所有資料生成RDB檔案,然後将RDB檔案同步給slave
5.Redis中的repl_back_buffer複制緩沖區可以記錄生成RDB檔案之後到同步完成這個時間段時寫入的資料,然後把這些資料也同步給slave
6.slave執行flushall指令清空slave中原有的資料,然後從RDB檔案讀取所有的資料,保證slave與master中資料的同步
如下圖所示:
5.1.4 全量複制的開銷
- 全量複制的開銷非常大
- master執行BGSAVE指令會消耗一定時間
- BGSAVE指令會fork子程序,對CPU,記憶體和硬碟都有一個消耗
- master将RDB檔案傳輸給slave的時間,傳輸過程中也會占用一定的網絡帶寬
- slave清除原有資料的時間,如果slave中原有資料比較多,清空原有資料也會消耗一定的時間
- slave加載RDB檔案會消耗一定時間
- 可能的AOF檔案重寫的時間:RDB檔案加載完成,如果slave節點的AOF功能開啟,則會執行AOF重寫操作,保證AOF檔案中儲存最新的資料
5.4 全量複制的問題
除了上面提到的開銷,如果master和slave之間的網絡出現問題,則在一段時間内slave上同步的資料就會丢失
解決這個問題的最好辦法就是再做一次全量複制,同步master中所有資料
Redis 2.8版本中添加了部分複制的功能,如果發生master和slave之間的網絡出現問題時,使用部分複制盡可能的減少丢失資料的可能,而不用全部複制
5.2 部分複制
當master與slave之間的連接配接斷開時,master在寫入資料同時也會把寫入的資料儲存到repl_back_buffer複制緩沖區中
當master與slave之間的網絡連通後,slave會執行
psync {offset} {run_id}
指令,offset是slave節點上的偏移量
master接收到slave傳輸的偏移量,會與repl_back_buffer複制緩沖區中的offset做對比,
如果接收到的offset小于repl_back_buffer中記錄的偏移量,master就會把兩個偏移量之間的資料發送給slave,slave同步完成,slave中的資料就與master中的資料一緻
6. 主從複制故障
6.1 slave當機
這種架構讀寫分離情況下,當機的slave無法從master中同步資料
6.2 master當機
Redis的master就無法提供服務了,隻有slave可以提供資料讀取服務
解決方法:把其中一個slave為成master,以提供寫入資料功能,另外一台slave重新做為新的master的從節點,提供讀取資料功能,這種解決方法依然需要手動完成
主從模式沒有實作故障的自動轉移,這就是Redis的sentinel的作用了
7.開發運維常見的問題
7.1 讀寫分離
讀寫分離:master負責寫入資料,把讀取資料的流量分攤到slave節點
讀寫分離一方面可以減輕master的壓力,另一方面又擴充了讀取資料的能力
讀寫分離可以遇到的問題:
7.1.1 複制資料延遲
大多數情況下,master采用異步方式将資料同步給slave,在這個過程中會有一個時間差
當slave遇到阻塞時,接收資料會有一定延遲,在這個時間段内從slave讀取資料可能會出現資料不一緻的情況
可以對master和slave的offset值進行監控,當offset值相差過多時,可以把讀流量轉換到master上,但是這種方式有一定的成本
7.1.2 讀到過期資料
Redis删除過期資料的方式
方式一:懶惰政策
當Redis操作這個資料時,才會去看這個資料是否過期,如果資料已經過期,會傳回一個-2給用戶端,表示查詢的資料已經過期
方式二:
每隔一個周期,Redis會采集一部分key,看這些key是否過期
如果過期key非常多或者采樣速度慢于key過期速度時,就會有很多過期key沒有被删除
此時slave會同步包括過期key在内的master上的所有資料
由于slave沒有删除資料的權限,此時基于讀寫分離的模式,用戶端會從slave中讀取一些過期的資料,也即髒資料
7.1.3 從節點故障
在圖9中,slave當機,從slave節點遷移為master節點的成本很高
在考慮使用讀寫分離之前,首先要考慮優化master節點的問題
Redis的性能很高,可以滿足大部分場景,可以優化一些記憶體的配置參數或者AOF的政策,也可以考慮使用Redis分布式
7.2 主從配置不一緻
第一種情況是:例如
maxmemory
不一緻:丢失資料
如master節點配置設定的記憶體為4G,而slave節點配置設定的記憶體隻有2G時,此時雖然可以進行正常的主從複制
但當slave從master同步的資料大于2G時,slave不會抛出異常,但會觸發slave節點的
maxmemory-policy
政策,對同步的資料進行一部分的淘汰,此時slave中的資料已經不完整了,造成丢失資料的情況
另一種主從配置不一緻的情況是:對master節點進行資料結構優化,但是沒有對slave做同樣的優化,會造成master和slave的記憶體不一緻
7.3 規避全量複制
7.3.1 全量複制的開銷是非常大的
第一次為一個master配置一個slave時,slave中沒有任何資料,進行全量複制不可避免
解決方法:主從節點的
maxmemory
不要設定過大,則傳輸和加載RDB檔案的速度會很快,開銷相對會小一些,也可以在使用者通路量比較低時進行全量複制
7.3.2 節點run_id不比對
當master重新開機時,master的run_id會發生變化。slave在同步資料時發現之前儲存的master的run_id與現在的run_id不比對,會認為目前master不安全
解決方法:
做一次全量複制,當master發生故障時,slave轉換為master提供資料寫入,或者使用Redis哨兵和叢集
Redis4.0版本中提供新的方法:當master的run_id發生改變時,做故障轉移可以避免做全量複制
7.3.3 複制緩沖區不足
複制緩沖區的作用是把新的指令寫入到緩沖區中
複制緩沖區實際是一個隊列,預設大小為1MB,即複制緩沖區隻能儲存1MB大小的資料
如果slave與master的網絡斷開,master就會把新寫入的資料儲存到複制緩沖區中
當寫入到複制緩沖區内的資料小于1MB時,就可以做部分複制,避免全量複制的問題
如果新寫入的資料大于1MB時,就隻能做全量複制了
在配置檔案中修改
rel_backlog_size
選項來
加大複制緩沖區的大小
,來減少全量複制的情況出現
7.4 規避複制風暴
主從架構中,master節點重新開機時,則master的run_id會發生變化,所有的slave節點都會進行主從複制
master生成RDB檔案,然後所有slave節點都會同步RDB檔案,在這個過程中對master節點的CPU,記憶體,硬碟有很大的開銷,這就是複制風暴
單主節點複制風暴解決方法
更換複制拓樸
單機多部署複制風暴
一台伺服器上的所有節點都是master,如果這台伺服器系統發生重新開機,則所有的slave節點都從這台伺服器進行全量複制,會對伺服器造成很大的壓力
主節點分散多機器
将master配置設定到不同的伺服器上
Redis的主從模式簡單總結
一個master可以有多個slave
一個slave還可以有slave
一個slave隻能有一個master
資料流向是單向的,隻能從master到slave