- redis叢集資源配置建議
- Production environment
- basic replication
- 配置
- replication的特性
- replication中的網路連線
- replication過程
- replication ID
- 重啟和故障轉移下的部分同步
- Read-only replica
- replication的可靠性
- replication expire keys
- replica 和master的認證
- Redis的配置
- 靜態配置
- 動態配置
- 將redis作為一個快取
- Redis Sentinel
- 啟動
- 配置
- 主要配置
- 可選配置
- 使用IP地址和DNS名稱
- 認證
- ACL認證
- redis只配置密碼認證
- Sentinel ACL認證
- Sentinel只配置密碼認證
- 執行時重配置sentinel
- 增刪節點
- 增刪sentinels
- 移除replicas
- Sentinels和replicas的自動發現
- SDOWN 和 ODOWN故障狀態
- Replica的選擇和優先順序
- 配置的epochs和傳遞
- TILT模式
- Redis Cluster
- 配置
- 啟動
- 啟動埠
- 配置檔案引數
- 節點操作
- 新增節點
- 新增master節點
- 新增replica
- 移除節點
- 重置節點
- 升級節點
- 升級replica
- 升級master
- 新增節點
- 資料分片
- 概念
- Hash-tags
- 執行reshard
- master-replica模型
- 資料丟失場景
- 可用性
- Replica migration
- 從replica讀取資料
- 叢集拓撲
- 節點連線
- 節點握手
- 心跳檢測
- Cluster current epoch和configuration epoch
- currentEpoch
- configEpoch
- ConfigEpoch衝突解決演算法
- 故障處理
- 概念理解
- 故障處理中的時間點
- 幾個Epoch的用途
- 故障檢測
- 故障轉移
- replica選舉
- replica排名
- master的投票流程
- 概念理解
- UPDATE訊息
- 雜湊槽配置的傳遞
- 節點重新加入叢集
- Client連線
- client重定向
- MOVED重定向
- ASK重定向
- client重定向
- 配置
- 持久化
- RDB
- 優點
- 缺點
- 禁用RDB
- AOF
- 優點
- 缺點
- Log rewriting
- AOF和RDB持久化的互動
- 備份和恢復
- 備份RDB資料
- 備份AOF資料
- RDB
- Security
- ACL
- Command categories
- TLS
- 證書配置
- 埠配置
- 客戶端認證
- Replication
- Cluster
- Sentinel
- ACL
- Cli
- General
- 配置變更
- DB
- 切換db
- select <db_num>
- 交換兩個DB的資料
- 獲取一個key的剩餘ttl
- TTL <key_name>
- 使用scan命令
- 為hash設定過期時間
- 切換db
- replication
- persistent
- Sentinel
- ACL
- Cluster
- cluster中的幾個有用的命令
- 其他命令
- 診斷命令
- Slow log
- Latency monitor
- 事件和時間序列
- 啟用latency monitoring
- 監控
- Server
- Sentinel && Cluster
- Client
- Mem
- 碎片整理
- CPU
- Persistence
- rdb
- aof
- Stats
- replication
- replica
- Server
- 效能最佳化
- Benchmark
- 效能引數
- 診斷延遲問題
- checklist
- 測試系統的記憶體延遲
- Redis的單執行緒本質
- 慢命令造成的延遲
- fork造成的延遲
- 透明大頁造成的延遲
- swaping造成的延遲
- AOF和磁碟I/O導致的延遲
- Redis的記憶體
- Tips
- redis啟動失敗
- Redis升級
- 如何將一個replica提升為master
- AOF檔案截斷錯誤
- 使用RDB進行備份和恢復
- 如何在使用dump.rdb snapshot的同時啟用AOF
- sentinel中的網路隔離
- Redis遷移到k8s
- Python client
redis叢集資源配置建議
Production environment
分類 | 描述 | 最小要求 | 建議 |
---|---|---|---|
每個叢集的節點 | 至少需要3個節點來滿足異常處理、節點故障和網路隔離等情況下的可靠性和高可用。 | 3 節點 | >= 3 節點 (節點數必須是奇數) |
每個節點的cores | - | 4 cores | >=8 cores |
每個節點的記憶體 | RAM大小應考慮到規劃的redis儲存容量 | 15GB | >=30GB |
臨時儲存 | 用於儲存RDB和叢集日誌檔案 | RAM x 2 | >= RAM x 4 |
持久化儲存 | 用於儲存RDB和AOF檔案 | RAM x 3 | In-memory >= RAM x 6 (極端 '寫' 場景) 除外 |
網路 | 推薦使用多NICs節點,每個NIC >100Mbps | 1G | >=10G |
basic replication
本節描述了redis master-replica的基本複製流程和機制,這也是Sentinel和Cluster的基礎。由於這種模式不支援故障轉移,因此在master出現故障時,只能透過手動(或指令碼)執行FAILOVER進行故障轉移。
redis透過leader-follower(master-replica機制)實現高可用。replica例項作為master例項的複製。
配置
在replicas的配置檔案中增加如下配置即可啟用redis replication功能。此外還可以透過呼叫REPLICAOF
命令設定replica的replication配置(192.168.1.1 6379
為master的地址和埠):
replicaof 192.168.1.1 6379
replication的特性
redis 預設使用非同步replication,即replica會非同步向master確認其週期性接收到的資料,因此master不會在資料傳送之後等待replica的確認。這種方式下可能會丟失replica的確認訊息,但增加了吞吐量。
在使用replication功能時強烈建議在master和replicas中開啟持久化。如果master沒有啟用持久化,那麼在master重啟之後其dataset為空,replica會在replication過程中損壞其原先的dataset複製。
客戶端可以透過WAIT命令實現特定資料的同步replication。
redis的replication有如下特性:
- redis使用非同步複製方式,即replica會非同步確認master傳送的資料。
- 一個master可以有多個replicas
- replicas除了可以連線到master之外,還可以連線到其他replica,呈級聯模式(A -> B -> C)。從redis 4.0開始,所有的sub-replicas都會從master接收到相同的replication流。
- replication不會阻塞master,意味著master可以在處理一個或多個replica的初始化同步或部分重同步時的同時繼續處理請求。
- replication也不會阻塞replica,即replica在初始化(從master)同步資料的同時,還能使用老的dataset處理請求(redis.conf的
replica-serve-stale-data
為yes
,否則在replication流斷開時會返回錯誤)。但在初始化同步結束之後,必須刪除老的dataset並載入新的dataset,在這段時間內,replica會阻塞入站連線(對於較大的dataset,可能會持續數秒)。 - 可以使用replication實現可擴充套件性,如使用多個replicas處理只讀請求。
replication中的網路連線
master和replica的連線有如下情況:
- 如果master和replica之間的鏈路正常,master會將其dataset的變更(客戶端的寫入、key的過期和驅逐以及master dataset變更等)透過命令流傳送到replica
- 如果master和replica之間的鏈路出現故障,replica會透過重連嘗試執行部分同步,即獲取斷鏈期間丟失的命令。
- 如果無法執行部分同步,則replica會請求完整同步,該過程比較複雜,涉及master建立完整的資料快照並將其傳送給replica,之後繼續透過命令流傳送dataset變更。
replication過程
每個redis master都有一個replication ID,用於標記一個歷史dataset。replica和master都有一個offset(位元組偏移),master的offset表示其當前dataset的最新位元組偏移,replica的offset表示從master複製到的位元組偏移。
當replicas連線到master時,會使用 PSYNC
(Partial Resynchronization) 命令向master傳送其儲存的master replication ID和目前處理到的offset,透過這種方式。master可以只向replica傳送所需的增量部分(部分重同步)。如果master buffers中沒有足夠的backlog(repl-backlog-size
),或者replica提供了一個master不知道的replication ID,則會執行完整(重)同步。
The backlog is a buffer that accumulates replica data when replicas are disconnected for some time, so that when a replica wants to reconnect again, often a full resync is not needed, but a partial resync is enough, just passing the portion of data the replica missed while disconnected.
The bigger the replication backlog, the longer the replica can endure the disconnect and later be able to perform a partial resynchronization. The backlog is only allocated if there is at least one replica connected.
replication的一個例子如下:
- Redis-1:replid和offset為預設值,說明它從未與主節點進行過同步操作,所以是進行全量同步;
- Redis-2:replid主從節點一致,replica_offset>=backlog_off並且replica_offset<offset,說明該從節點丟失的資料可以透過backlog找回,所以可以進行部分同步;
- Redis-3:replid主從節點一致,replica_offset<backlog_off,說明該節點丟失的資料過多,透過複製backlog無法找回,所以是進行全量同步;
- Redis-4:replid主從節點一致,之前不是與當前節點進行主從複製,所以是進行全量同步;
master會啟動一個後臺程序來建立一個RDB檔案,同時快取從clients新接收到的所有寫命令。當後臺程序結束後,master會將該RDB檔案傳送給replica,replica會將其儲存到磁碟,再載入到記憶體中。之後master會透過命令流將所有快取命令傳送給replica。
通常一個完整(重)同步會要求master在磁碟上建立一個RDB檔案,然後從磁碟上載入該檔案並將其傳送給replicas。但如果磁碟較慢,可能會影響master的操作,從redis 2.8.18 開始,可以透過設定
repl-diskless-sync yes
來啟用diskless replication功能,此時master會透過網路直接將RDB傳送給replicas,而無需在磁碟上建立RDB檔案。
replication ID
從上面可以知道,如果兩個例項具有相同的replication ID和offset,則說明二者具有相同的資料。但實際上一個例項有兩個replication ID:main ID和secondary ID,對應當前master ID和上一個master的ID。
一個replication ID表示一個特定的dataset。當一個例項初始化為master或當一個replica被提升為master時會生成一個新的replication ID。連線到一個master的replicas會在握手之後繼承master的replication ID。因此相同ID的例項持有相同的資料(可能時間點不同)。
redis 例項有兩個replication ID的原因是,在發生故障轉移時,一個replica會被提升為master,此時它仍然持有之前master的replication ID(secondary ID)和offset,並將其設定為main ID,這樣在其他replicas和新的master進行同步時,仍然可以使用老master的replication ID來嘗試部分(重)同步。之後新的master才會生成一個新的main ID,並使用 main ID 和 secondary ID來同時處理replicas的連線,在所有replicas接收到所有老master的資料(offset)之後,會自動切換到新的master id。redis透過這種方式來降低在發生故障轉移時執行完整同步的機率。
新的master需要切換replication ID的原因是,在出現網路隔離的情況下,老的master可能仍然在工作,如果使用相同的replication ID,則會違背相同ID和相同offset表示相同dataset的事實。
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:1
slave0:ip=10.157.4.110,port=6379,state=online,offset=552352655438,lag=0
master_replid:f50c0dadc1005893c39bd8f3a482aa992df82786 # 本節點(master)的id,mainID
master_replid2:ddcc55eeeca1a1e2e26f858524413bea798b4190 # 上一次同步的master的ID,secondary ID
master_repl_offset:552352659332 # 本節點(master)的資料偏移量
second_repl_offset:329184800015 # 上一次同步的master的偏移量
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:552351610757
repl_backlog_histlen:1048576
上述master對應的replica的replication資訊如下:
# Replication
role:slave
master_host:10.157.4.40
master_port:6379
master_link_status:up
master_last_io_seconds_ago:2
master_sync_in_progress:0
slave_repl_offset:552357245144
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:f50c0dadc1005893c39bd8f3a482aa992df82786 #當前master的ID
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:552357245144 # 本節點複製到資料偏移量
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:552356196569
repl_backlog_histlen:1048576
重啟和故障轉移下的部分同步
- 如上面所述,在發生故障轉移時,新的master仍然能和現有的replicas進行部分(重)同步。
- 對於redis升級這樣的場景,可以透過
SHUTDOWN
+SAVE
命令來儲存replica的RDB檔案,方便進行部分(重)同步。需要注意的是如果replica透過AOF檔案進行重啟,則無法進行部分同步。因此在重啟前需要切換到RDB模式,重啟之後再切換回AOF。
Read-only replica
從 Redis 2.6開始,預設啟用read-only replica模式,可以透過replica-read-only
引數進行配置。該模式下,replica會拒絕所有寫命令。但該引數同時也意味著可以讓replicas支援寫命令,但這樣可能會導致replica和master資料不一致,因此不建議使用writable replicas。
另外注意,從redis 4.0開始,replica寫入的命令只是本地的,不會傳遞到子replicas中,子replicas會接收與master的直連的replica相同的replication流。例如下面場景,當B寫入命令後,C不會看到B寫入的命令,其dataset和master A相同。
A ---> B ---> C
replication的可靠性
redis透過如下兩個引數來提高replication的可靠性,即當連線的replicas不少於min-replicas-to-write
個,且lag小於min-replicas-max-lag
秒時,redis master才會接受寫請求。預設情況下min-replicas-to-write
為0(即禁用該選項),min-replicas-max-lag
為10。
min-replicas-to-write 3
min-replicas-max-lag 10
由於replication過程是非同步的,因此上述功能只能儘量減小發生資料丟失的時間視窗。
replication expire keys
redis使用如下方式來處理replication中過期的keys:
- replicas不會expire keys,它們等待master來expire keys,即當master expire一個key後,它會向所有的replicas同步一個
DEL
命令。 - 有時replicas的記憶體中會存在邏輯上已過期的keys,但由於master無法及時提供DEL命令,此時replica會使用其邏輯時鐘來向讀操作上報該key已經過期。
- lua指令碼執行過程中不會執行expire keys操作。
- 在一個replica提升為master之後,它會獨立expire keys,不會再依賴老的master。
replicas的Maxmemory
預設情況下replicas會忽略maxmemory
(replica-ignore-maxmemory yes
),即依賴master的DEL命令來expire keys,因此可能導致replica使用的記憶體大於其設定的maxmemory
。因此需要保證replica擁有足夠的記憶體,防止OOM。
replica 和master的認證
在redis.conf
中配置replica和master的認證:
masterauth <password>
Redis的配置
靜態配置
有兩種靜態配置reids的方式:
-
一種是透過
redis.conf
,生產中建議使用這種方式 -
另一種是透過命令列直接傳入的方式,建議在測試環境中使用。其引數的格式與
redis.conf
相同,只需要在引數前面加上--
字首./redis-server --port 6380 --replicaof 127.0.0.1 6379
動態配置
可以透過 CONFIG SET
和 CONFIG GET
動態修改redis的配置,還可以透過 CONFIG REWRITE
將動態配置持久化。
將redis作為一個快取
如果需要將Redis作為一個快取,可以使用如下方式
maxmemory 2mb
maxmemory-policy allkeys-lru
這種方式下,無需透過 EXPIRE
命令為keys設定TTL,當資料量達到2M時,會使用LRU演算法驅逐keys。
maxmemory-policy
有如下幾種策略:
- volatile-lru:只對設定了過期時間的keys執行LRU驅逐
- allkeys-lru:使用LRU驅逐所有keys
- volatile-lfu: 只對設定了過期時間的keys執行LFU驅逐
- allkeys-lfu: 使用LFU驅逐所有keys
- volatile-random:隨機驅逐一個設定了過期時間的key
- allkeys-random:隨機驅逐一個key
- volatile-ttl:隨機驅逐一個最接近過期時間的key(最小TTL)
- noeviction:不驅逐任何key,只在寫入時丟擲錯誤
Redis Sentinel
Sentinel會持續監控檢查master和replicas是否正常工作。如果一個master出現故障,Sentinel可以啟動故障轉移流程,將一個replica提升為master,然後將原master的replicas的master欄位修改為新的master,並在應用連線到Sentinel時,告知其新的master地址。
啟動
有如下兩種啟動方式,Sentinels的預設埠為26379,另外sentinel會將狀態儲存在配置檔案中,因此需要在啟動的時候指定sentinel.conf
:
redis-sentinel /path/to/sentinel.conf
redis-server /path/to/sentinel.conf --sentinel
配置
Sentine的狀態儲存在sentinel的配置檔案中(sentinel.conf
中的# Generated by CONFIG REWRITE
),因此可以安全地停止並重啟sentine程序。
主要配置
由於sentinel本身也可能出現問題,因此為了保證系統的魯棒性(robust),至少要部署3個sentinel例項。在啟動sentinel時需要指定一個名為sentinel.conf
的配置檔案,典型配置如下:
port 26379 #sentinel的埠,預設26379
sentinel monitor mymaster 127.0.0.1 6379 2
sentinel down-after-milliseconds mymaster 60000
sentinel failover-timeout mymaster 180000
sentinel parallel-syncs mymaster 1
sentinel monitor resque 192.168.1.3 6380 4
sentinel down-after-milliseconds resque 10000
sentinel failover-timeout resque 180000
sentinel parallel-syncs resque 5
在配置sentinel時只需指定監控的master即可,無需指定replicas和其他sentinels(自動發現)。上面配置的sentinel監控了兩個redis例項組,每一組包含一個master以及不定數目的replicas。一組名為mymaster
,另一組名為resque
。當故障轉移將一個replica提升為master或發現新的sentinel時會自動重寫該配置。
配置方式如下:
sentinel monitor <master-name> <ip> <port> <quorum>
quorum
指定了至少需要quorum
個Sentinels的確認才能認為一個master出現故障(將master狀態標記為ODOWN),如果要執行故障轉移,則需要將quorum
中的一個Sentinel選舉為leader,並需要大部分Sentinel來授權該leader Sentinel執行故障轉移。
例如有5個Sentinels,quorum
設定為2:
- 如果有2個Sentinels認為一個master不可達,則其中一個Sentinel會嘗試啟動故障轉移
- 如果有至少3個Sentinels的授權就可以執行故障轉移
可選配置
可選配置的格式為:
sentinel <option_name> <master_name> <option_value>
主要用於如下目的:
down-after-milliseconds
:定義一個master多長時間不可達(無法響應PINGs或返回錯誤)時,認為該master down。parallel-syncs
:定義了在故障轉移之後,同一時間可以重新配置為新master的replicas的數目,類似滾動升級。數值越小,故障轉移流程越長,但如果replicas配置了可以使用老資料響應client的讀請求,此時不應該將所有的replicas同時配置為新的master。
使用IP地址和DNS名稱
從redis 6.2版本開始,sentinel可以支援主機名,預設禁用。使用時需要注意,不要同時使用IP地址和主機名。
replica-announce-ip <hostname>
:配置redis例項的主機名sentinel announce-ip <hostname>
:配置sentinel例項的主機名
sentinel解析主機名,並在宣告一個例項以及更新配置時轉換為IP地址。
當client使用TLS連線例項時,可能會需要主機名(而非IP地址)來匹配證書的ASN。
該功能可能會與sentinel client不相容。
認證
ACL認證
從redis 6開始,需要透過ACL來管理使用者認證和許可權。在配置了ACL之後,Sentinel在連線redis例項時需要配置如下命令:
sentinel auth-user <master-name> <username> #預設使用者為 default
sentinel auth-pass <master-name> <password>
<username>
和 <password>
表示用於訪問一組例項所需的使用者名稱和密碼。需要在所有redis例項上為Sentinel申請最小許可權:
127.0.0.1:6379> ACL SETUSER sentinel-user ON >somepassword allchannels +multi +slaveof +ping +exec +subscribe +config|rewrite +role +publish +info +client|setname +client|kill +script|kill
redis只配置密碼認證
- 在master配置
requirepass
- replica配置master認證所需的密碼
masterauth
Sentinel配置連線redis例項的密碼即可:
sentinel auth-pass <master-name> <password>
Sentinel ACL認證
首先禁止未授權的訪問。禁用預設使用者(或建立一個強密碼)並建立一個新的,可以訪問Pub/Sub channel的使用者:
127.0.0.1:5000> ACL SETUSER admin ON >admin-password allchannels +@all
OK
127.0.0.1:5000> ACL SETUSER default off
OK
Sentinel會使用預設使用者連線其他例項,使如下方式建立一個新的超級使用者
sentinel sentinel-user <username>
sentinel sentinel-pass <password>
使用ACL限制client訪問:
127.0.0.1:5000> ACL SETUSER sentinel-user ON >user-password -@all +auth +client|getname +client|id +client|setname +command +hello +ping +role +sentinel|get-master-addr-by-name +sentinel|master +sentinel|myid +sentinel|replicas +sentinel|sentinels +sentinel|masters
Sentinel只配置密碼認證
在所有Sentinel的配置中新增如下引數即可啟用僅密碼認證:
requirepass "your_password_here"
執行時重配置sentinel
下面命令需要在sentinel例項上執行,需要注意的是,單個sentinel的配置變更並不會傳遞到其他sentines上,因此在使用如下命令進行配置變更時,需要在所有sentinel上執行一遍。
-
SENTINEL MONITOR
<name>
<ip>
<port>
<quorum>
:與sentinel.conf
配置檔案中的sentinel monitor
命令類似。 -
SENTINEL REMOVE
<name>
:移除指定的master。 -
SENTINEL SET
<name>
[<option>
<value>
...]:類似redis的CONFIG SET
命令,可以修改特定master的配置,如SENTINEL SET objects-cache-master down-after-milliseconds 1000
它還可以直接修改
quorum
配置:SENTINEL SET objects-cache-master quorum 5
增刪節點
增刪sentinels
新增sentinel比較簡單,由於sentinel的自動發現機制,只需要啟動新的sentinel並監控當前活動的master即可,10s之內,sentinel會獲取到其他sentinels和監控的master的replicas列表。
如果需要新增多個sentinel,建議一個一個新增,可以透過SENTINEL MASTER mastername
(num-other-sentinels
)或sentinel sentinels mastername
校驗新增的sentinels是否就緒。
移除一個sentinel相對比較複雜:sentinel不會忘記已經發現的sentinels。可以透過如下步驟移除一個sentinel:
- 停止需要移除的sentinel程序
- 向所有其他sentinel 例項傳送
SENTINEL RESET *
(如果只需要reset一個master,也可以將*
替換為特定的master)。等待30s來移除下一個 - 透過
SENTINEL MASTER mastername
來檢查當前活動的sentinels。
移除replicas
如果要移除一個replica,可以停止該replica,並向所有的sentinels傳送SENTINEL RESET mastername
命令,這樣sentinels就會在下一個10內重新整理replicas列表。
Sentinels和replicas的自動發現
sentinels會透過Redis例項的 Pub/Sub功能來發現監控相同master和replicas的其他sentinels,並相互檢查可用性以及互動訊息等。該channel名為 __sentinel__:hello
。
自動發現功能如下:
- 每個sentinel會定期(每2s)向Pub/Sub channel
__sentinel__
釋出監控到的每個master和replica訊息,包括ip、port和runid - 每個sentinel會訂閱Pub/Sub channel
__sentinel__
中的每個master和replica,查詢與之相關的未知的sentinels。當檢測到新的sentinels時,將其新增為該master的sentinels。 - Hello訊息包含完整的Master配置。如果sentinel接收到的配置版本號高於已有的配置版本,則立即更新配置。
- 在為一個master新增sentinel之前,需要檢查是否已經有相同runid或地址(IP和port對)的sentinel,如果存在,則移除匹配的sentinel,並新增新的sentinel。
SDOWN 和 ODOWN故障狀態
Redis Sentinel有兩個down概念:
-
一種是Subjectively Down condition (SDOWN),表示一個特定sentinel Instance報告的down狀態。當一個master在
down-after-milliseconds
內無法響應PING請求時就會產生該狀態。有效的響應如下:- PING響應 +PONG.
- PING響應 -LOADING 錯誤.
- PING響應 -MASTERDOWN 錯誤.
SDOWN並不足以觸發故障轉移,若要觸發,需要達到ODOWN狀態。
-
另一種是Objectively Down condition (ODOWN)。如果在給定時間內,足夠多的(不少於
quorum
)sentinels報告master不可用(能夠透過SENTINEL is-master-down-by-addr
接收到其他sentinels的反饋),就會將SDOWN提升為ODOWN。ODOWN狀態只適用於master,對於不需要sentinel參與的例項(如replica和其他sentinels),只有SDOWN狀態。
Replica的選擇和優先順序
在故障轉移過程中,會根據如下資訊選擇作為新master的replica:
- 和master的斷鏈時間
- replica優先順序
- 處理的replication offset
- Run ID
如果一個replica和master的斷鏈時間超過配置的master超時時間的10倍加sentinel認為master不可用的時間之和,則認為該replica不適合進行故障轉移。
(down-after-milliseconds * 10) + milliseconds_since_master_is_in_SDOWN_state
按照如下順序從剩下的replicas中進行選擇:
- 按照
redis.conf
中配置的replica-priority進行
排序,越小越好 - 如果優先順序相同,則檢查replica處理的replication offset,並選擇從master接收最多資料的replica
- 如果多個replica具有相同的優先順序和replication offset,則按照字典順序選擇一個小的run ID
大多數情況下,無需設定replica-priority。如果replica-priority為0,則表示該replica永遠不會被sentinel選擇為master。
配置的epochs和傳遞
當一個sentinel被授權執行故障轉移時,它會獲得一個與master有關的唯一的configuration epoch ,該值用於在故障轉移結束之後標記一個新版本的配置。
在故障轉移中有一個規則:如果一個Sentinel授權另一個sentinel執行一個master的故障轉移,則該Sentinel會等待一定時間來再次執行相同master的故障轉移,該時間為sentinel.conf
中定義的failover-timeout
的2倍(2 * failover-timeout
),這意味著sentinels不會同時對相同的maser進行故障轉移,如果第一個被授權的sentinel故障轉移失敗,則一段時間之後另一個Sentinel會再次嘗試故障轉移,以此類推。
一旦一個sentinel成功完成一個master的故障轉移,它會廣播新配置,這樣在其他sentinels接收到之後就可以更新與此master相關的配置。
故障轉移成功要求Sentine向選擇的replica傳送
REPLICAOF NO ONE
命令,並且在master的INFO
輸出中看到切換好的master。
每個sentinel都會使用redis的Pub/Sub( __sentinel__:hello
Pub/Sub channel)持續廣播其版本的master配置,同時所有sentinels都會等待檢視其他sentinels宣告的配置。由於不同的配置有不同的版本號(configuration epoch),優先選擇較高版本的配置,這樣所有的sentinel最終都會使用較高版本的配置。
TILT模式
TILT是一種防護模式。工作原理為:sentinel每秒會呼叫10次時鐘中斷,因此兩個時鐘中斷呼叫之間的時間差應該在100ns左右。sentinel會註冊上一個呼叫時鐘中斷的時間,並與當前呼叫作對比,如果時間差為負數或相對較大(如2s或更大)時,就會進入TILT模式。
在TILT模式下,sentinel仍然會繼續監控,但完全不起作用,且會對SENTINEL is-master-down-by-addr
響應負數。
如果恢復正常狀態30s,則會退出TILT模式。
當進入TILT模式後,可以透過sentinel_tilt_since_seconds
檢視進入TILT模式的時間,如果非TILT模式,則顯示-1。
$ redis-cli -p 26379
127.0.0.1:26379> info
(Other information from Sentinel server skipped.)
# Sentinel
sentinel_masters:1
sentinel_tilt:0
sentinel_tilt_since_seconds:-1
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
sentinel_simulate_failure_flags:0
master0:name=mymaster,status=ok,address=127.0.0.1:6379,slaves=0,sentinels=1
Redis Cluster
透過Redis Cluster可以跨多節點實現資料自動分片和水平擴容。透過Redis Cluster可以實現:
- 在多個節點上切分資料集
- 當一部分節點出現故障或無法通訊時仍能夠繼續操作剩餘的叢集
在使用Redis Cluster時要注意以下幾點:
-
Redis Cluster不像獨立的Redis一樣支援多DB,它只支援DB 0。
127.0.0.1:6379> info keyspace # Keyspace db0:keys=33880932,expires=24332094,avg_ttl=0
-
Cluster節點並不能將命令轉發到正確的節點,因此需要clients在接收到重定向錯誤(MODE和ASK)之後重定向到正確的節點,因此要求clients能夠連線到所有節點。
-
最大節點數推薦1000
-
當需要在一條命令中處理多個keys時,可以透過Hash-tags確保操作的所有keys都位於同一個雜湊槽中
配置
Redis Cluster要求最少需要3個master節點,推薦6個節點:3個masters和3個replicas。
啟動
使用如下方式啟動一個Cluster節點,後續可以新增節點
redis-server ./redis.conf
cluster-config-file
:該引數指定了叢集配置檔案的位置。每個節點在執行過程中,會維護一份叢集配置檔案;每當叢集資訊發生變化時(如增減節點),叢集內所有節點會將最新資訊更新到該配置檔案;當節點重啟後,會重新讀取該配置檔案,獲取叢集資訊,可以方便的重新加入到叢集中。也就是說,當Redis節點以叢集模式啟動時,會首先尋找是否有叢集配置檔案,如果有則使用檔案中的配置啟動,如果沒有,則初始化配置並將配置儲存到檔案中。叢集配置檔案由Redis節點維護,不需要人工修改。
編輯好
cluster-config-file
檔案後,使用redis-server
命令啟動該節點:redis-server <cluster-config-file>
啟動埠
每個Redis Cluster節點需要開啟兩個TCP連線:一個與clients連線的TCP埠(如6379);以及一個名為cluster bus port的埠,預設的cluster bus port為資料埠加10000(如16379),當然也可以透過redis.conf
中的cluster-port
覆蓋預設配置。
cluster bus是一個節點之間的通訊渠道,用於在節點之間交換資訊,如節點發現、故障探測、配置更新、故障轉移授權等等。為了讓一個Redis Cluster正常工作,需要:
- 向所有clients和其他需要使用client port進行key遷移的叢集開放client通訊埠(通常是6379)
- 向其他節點開放cluster bus port
配置檔案引數
Redis Cluster的配置檔名為redis.conf
,主要配置項如下:
- cluster-enabled
<yes/no>
:是否啟用Redis Cluster,如果否,則作為一個獨立的redis例項 - cluster-config-file
<filename>
:可選,Redis Cluster在叢集變更時自動持久化叢集配置(狀態),使用者不可修改。 - cluster-node-timeout
<milliseconds>
:如果一個master節點在這段時間內無法連線到大部分master,則說明該節點不可用,該節點將會停止接收請求。該引數用於讓master停止接收client請求。 - cluster-slave-validity-factor
<factor>
:如果為0,則replica會一直嘗試透過故障轉移為一個master,而不會考慮master和replica之間的斷鏈時間。如果為正數,且節點為replica,則當它和master節點的斷鏈時間超過cluster-node-timeout *factor時會嘗試故障轉移為master。注意如果一個master沒有replica,且該引數非0,則當一個master故障時會導致Redis Cluster不可用(直到master重新加入叢集)。該引數用於啟動故障轉移。 - cluster-migration-barrier
<count>
:定義一個master最少需要連線多少個replicas,這樣多餘的replica就可以遷移為一個沒有任何Working replica的master的replica。參見[replica-migration](#Replica migration)。 - cluster-require-full-coverage
<yes/no>
:預設yes
,如果為yes
,當叢集檢測到至少有1個雜湊槽uncovered(沒有該雜湊槽對應的節點)時,Redis Cluster將停止接收請求。如果為no
,則可以繼續為剩餘的keys接收請求。 - cluster-allow-reads-when-down
<yes/no>
:預設no
,如果為no
,即當一個節點無法連線到大部分masters或出現uncovered的雜湊槽時,叢集將停止接收任何流量。
節點操作
增加節點時需要將原有slots均衡到新的節點,可以透過redis-cli --cluster rebalance <ip:port> --cluster-use-empty-masters
實現
刪除節點時,需要將reshard需要刪除的節點的slots,然後redis-cli --cluster rebalance <ip:port>
再均衡slots
參見:Hash Slot Resharding and Rebalancing for Redis Cluster
新增節點
新增master節點
如果要新增一個master,首先新增一個空的節點,然後將資料轉移到該節點。
-
假設新增一個新節點127.0.0.1:7006,首先為其建立目錄
-
使用
add-node
新增節點,127.0.0.1:7000
為一個叢集已有的節點,redis-cli會透過CLUSTER MEET
命令將其加入叢集redis-cli --cluster add-node 127.0.0.1:7006 127.0.0.1:7000
-
使用reshard向該節點遷移資料
新增replica
新增replica的方式有如下三種:
-
當使用如下命令新增一個replica時,由於沒有指定master,Redis會隨機選擇一個具有最少replicas的master:
redis-cli --cluster add-node 127.0.0.1:7006 127.0.0.1:7000 --cluster-slave
-
使用如下方式可以為特定master新增一個replica:
redis-cli --cluster add-node 127.0.0.1:7006 127.0.0.1:7000 --cluster-slave --cluster-master-id 3c3a0c74aae0b56170ccb03a76b60cfe7dc1912e
-
還可以先啟動一個空的master節點,然後透過
CLUSTER REPLICATE
命令將該節點設定為某個master的replica,通常用於將已新增的節點轉移到不同的master下:CLUSTER REPLICATE node-id
移除節點
-
如果要移除一個replica節點,只需要使用
del-node
命令即可,127.0.0.1:7000
為叢集的一個已知節點,node-id
為需要刪除的reolica的id:redis-cli --cluster del-node 127.0.0.1:7000 `<node-id>`
-
如果要移除一個master節點,則必須保證master節點為空,因此首先需要將所有資料reshard到其他節點,再執行如上命令。
-
如果要移除一個故障節點,則不能使用
del-node
,因為del-node
會嘗試連線所有的節點,此時會返回connection refused
錯誤,可以呼叫CLUSTER FORGET
來移除該故障節點:redis-cli --cluster call 127.0.0.1:7000 cluster forget `<node-id>`
CLUSTER FORGET
命令會執行如下操作:- 從node table中移除指定ID的節點
- 在60s內禁止重新新增相同ID的節點。這是為了防止redis透過gossip再次發現需要移除的節點
重置節點
當需要將一個已有節點加入其他叢集時可以透過 CLUSTER RESET
命令重置節點,它有如下兩種形式:
CLUSTER RESET SOFT
CLUSTER RESET HARD
有如下規則:
- soft和hard:如果節點為replica,則將其變為master,並丟棄dataset。如果node是master,且包含keys,則中斷reset操作
- soft和hard:釋放所有的slots,重置手動故障轉移狀態
- soft和hard:從node table中移除其他節點,這樣該節點將無法被其他節點識別
- 僅hard:將
currentEpoch
,configEpoch
和lastVoteEpoch
設定為0 - 僅hard:將節點ID變為一個新的隨機ID
如果master包含非空資料集,則無法被重置。可以透過 FLUSHALL
命令清除資料。
升級節點
升級replica
升級replica節點比較簡單,只需要停止該節點,然後使用新版本重啟節點即可。如果此時有client透過replica執行讀操作,則當該節點不可用時會重新連線到另一個replica。
升級master
升級master比較複雜,建議流程如下:
- 使用
CLUSTER FAILOVER
手動觸發故障轉移,將需要升級的master變為replica(資料安全操作) - 等待master變為replica
- 然後升級該節點
- 在升級結束之後,可以再次手動觸發故障轉移將升級後的節點變為master
資料分片
概念
Redis Cluster沒有使用一致性雜湊,而是使用了一種稱為雜湊槽(hash slot)的分片方式。Redis Cluster 中有16384個雜湊槽,透過HASH_SLOT = CRC16(key) mod 16384來計算一個key所在的槽位。
Redis Cluster中的每個節點都負責一部分雜湊槽,假如有3個節點:
- 節點A的雜湊槽為0~5500
- 節點B的雜湊槽為5501~11000
- 節點C的雜湊槽為11001~16383
當新增一個節點D時,需要將部分雜湊槽從A、B、C轉移到D。類似地,如果要移除節點A,則需要將雜湊槽從A移到B和C,當A的雜湊槽為空時,就可以刪除A。注意:此處的A、B、C、D均表示master節點
將雜湊槽從一個節點轉移到其他節點時並不需要停止任何操作,因此,無論是新增刪除節點還是變更一個節點的雜湊槽百分比,都不需要停機。
Hash-tags
Redis Cluster中,不同的key可能會落到不同的雜湊槽中,如果在一條命令中同時操作不同雜湊槽的keys(Multi-keys operations),而這些keys對應的雜湊槽又位於不同節點時,會返回重定向錯誤。更多參見client重定向
[Redis] Multi-key command in cluster mode (feat. CROSS-SLOT)
為此,可以透過hash tags讓多個keys分配到相同的雜湊槽中。其方法是,如果key包含"{...}",則只會透過大括號內的字串的計算雜湊槽。如果一個key有多個大括號,則只會透過第一個出現的括號內的字串來計算雜湊槽。
- 如key {user1000}.following和key {user1000}.followers會使用user1000計算雜湊槽
- key foo{bar}{zap}使用bar進行雜湊
- key foo{{bar}}使用bar進行雜湊
- key foo{}{bar}由於第一個大括號內沒有任何字元,因此會對整個字串進行雜湊。
執行reshard
-
執行如下命令即可啟動互動式的reshard,命令中指定一個節點即可,redis-cli會自動發現其他節點:
redis-cli --cluster reshard 127.0.0.1:<cport>
-
也可透過如下方式執行非互動式的reshard,
--cluster-yes
表示非互動式:redis-cli --cluster reshard <host>:<port> --cluster-from <node-id> --cluster-to <node-id> --cluster-slots <number of slots> --cluster-yes
--cluster-from
可以指定all
,表示將其他節點作為源雜湊槽。
在reshard結束之後,可以透過cluster nodes
檢視雜湊槽分配情況。
master-replica模型
Redis Cluster中每個雜湊槽有1(master)到N個replicas(N-1個replica節點)。假如有A、B、C三個節點,如果B出現故障,此時將無法為5501~11000的雜湊槽提供服務。
但如果在建立叢集時為每個master新增了一個replica節點,這樣叢集中將包含節點A、B、C及其replica節點A1、B1、C1,如果B出現故障,此時B1將作為新的master。但如果B和B1同時出現故障,那麼Redis Cluster將無法繼續操作。
資料丟失場景
Redis Cluster不保證資料的強一致性(非同步複製),即在某些情況下,可能會丟失client寫入的資料。Redis有兩種資料丟失場景:
- 假設一個client寫入master B,master B向client響應OK,然後需要將寫入的資料傳送給它的replicas B1、B2和B3。由於B在回覆client前不會等待B1, B2, B3的應答,因此如果在B向client確認寫入資料之後,且還未向replicas傳送該資料之前發生了故障,此時會在節點超時之後將其中一個replica提升為master,導致資料丟失。
- 當客戶端寫入的master出現網路隔離時,且該master屬於被隔離出的小部分masters,這樣在網路恢復之後會丟失這段時間的寫資料
Redis Cluster有一個重要配置cluster-node-timeout
,如果一個master節點在這段時間內無法連線到大部分masters(Ping/Pong)就會進入錯誤狀態,它將停止接收寫操作,以此來限制資料丟失的視窗。
client可以透過WAIT 命令保證資料不丟失。
可用性
當出現網路隔離時,如果具有大部分masters的一側網路中包含不可達masters的(至少)1個replica,則會在NODE_TIMEOUT
加上replica切換為master的時間(1~2s)之後恢復叢集。
透過Replica migration自動將多餘的replica遷移到沒有replica的master上,以此來提升叢集的穩定性。
Replica migration
Redis Cluster中如果master及其replicas同時出現故障,將導致叢集不可用。為了提高叢集的可用性,最簡單的方式是為叢集中的每個master增加replica,但該方式也存在成本問題。
另一種方式是透過建立不對稱的master和replicas來讓叢集自動變更佈局。如果一個master沒有任何Working狀態的replica,Replica migration可以自動將一個replica遷移為該master的replica。
例如一個叢集有3個master A、B、C,其中A和B各有1個replica,C有2個replica:C1和C2
- Master A故障,A1提升為master
- 由於A1沒有任何replica,C2遷移為A1的replica
- 3小時之後A1也出現故障,C2提升為新的master,替換A1
- 叢集正常運作
演算法
由於migration algorithm操作的是replica,且不涉及configEpoch的變更,因此不需要叢集的授權。
在檢測到叢集中至少存在一個沒有Working replica的master時,每個replica都會執行遷移演算法,但通常只有一個replica會真正執行遷移:從具有最多replicas的master中選擇一個replica,且該replica處於非FAIL狀態,具有最小的節點ID。
在叢集不穩定時,可能會產生競爭,即同時有多個replica認為其具有最小的節點ID(實際中不大可能發生),導致多個replicas同時遷移到相同的master上。但這種情況並無害,如果競爭導致某個master沒有replicas,則在叢集穩定之後,會再次執行遷移演算法,將replica遷移回去。
cluster-migration-barrier
引數控制了一個master至少應該保留多少個replicas,剩餘的replicas才能被遷移。
從replica讀取資料
通常當client連線到replica節點時,replica節點會將client重定向其master,但可以透過 READONLY
命令讓client從replica讀取資料。
當client連線的replica的master不包含命令涉及的雜湊槽時,會向client傳送重定向資訊。
可以透過 READWRITE
命令取消readonly模式。
叢集拓撲
節點連線
Redis Cluster是一個節點互聯的網格,在一個N節點的叢集中,每個節點有N-1個出去的(cluster bus port)TCP連線,同時有N-1個進來的(cluster bus port)連線,這些TCP連線為長連線。節點之間透過gossip協議以及一個配置更新機制來避免交換太多資訊。
節點握手
叢集會透過如下兩種方式接納一個節點:
- 節點透過MEET訊息(
CLUSTER MEET
)訊息宣告本節點。MEET訊息型別PING訊息,但會強制讓接收的節點將其接納為節點的一部分。MEET訊息只能透過系統管理員傳送:CLUSTER MEET ip port
- 一個節點會接納已信任的節點所接納的節點。
心跳檢測
Redis Cluster的節點透過Ping和Pong報文來檢測心跳。通常一個node每秒會隨機Ping固定數目的nodes,且每個節點也會確保Ping那些沒有在NODE_TIMEOUT/2
時間內傳送Ping或接收到Pong的節點。
Ping和Pong報文中包含一個通用的首部:
- Node ID
- 傳送節點的currentEpoch和configEpoch
- node flag,即replica、master或其他節點資訊
- 傳送節點負責的的雜湊槽的bitmap
- 傳送節點的client port
- 傳送節點的Cluster port
- 傳送節點視角下的叢集狀態,down或ok
- 傳送節點的master node ID(如果傳送節點為replica)
Ping和Pong報文還包含一個 gossip section,該section僅包含傳送節點對其他節點的描述資訊,用於故障檢測和節點發現:
- Node ID.
- 節點的IP和port
- Node flags.
Cluster current epoch和configuration epoch
Cluster current epoch也被稱為currentEpoch
,configuration epoch也被稱為 configEpoch。
currentEpoch
新建立的節點(master/replica)的currentEpoch為0。
當從另一個節點接收到報文時,如果傳送的cluster bus訊息首部中的epoch高於本地節點的epoch,則將currentEpoch更新為傳送者的epoch。最終叢集中的所有節點都會更新為最大的currentEpoch。
該值目前僅用於將replica提升為master(將下一節)的場景。
configEpoch
每個master總是會透過Ping和Pong報文來宣告其configEpoch以及負責的雜湊槽bitmap,replica也會在Ping和Pong報文中宣告其configEpoch,但該值為最近一次和master互動時master的epoch值。新建立的master節點的configEpoch為0.
replica選舉時會生成新的configEpoch。replica會增加故障master的epoch,並嘗試獲取大部分masters的授權,一旦授權該replica,就會建立一個唯一的configEpoch,之後replica會使用新的configEpoch轉換為master。
configEpoch還可以用於解決多個節點宣告有分歧的配置導致的問題(網路隔離情況下)。
ConfigEpoch衝突解決演算法
在故障轉移中,當一個replica提升為master之後需要確保獲得一個唯一的configEpoch值。但在下面兩個場景中會以不安全方式建立configEpoch,即在建立時僅增加了本地節點的currentEpoch
,有可能導致configEpoch衝突。這兩種情況都是系統管理員觸發的:
- 使用
CLUSTER FAILOVER TAKEOVER
手動將一個replica節點提升為master,此時不需要大部分masters的授權同意,適用於配置多資料中心。 - 遷移slots也會在本地節點中生成一個新的configEpoch,出於效能原因,也無需其他節點的同意。
當手動將一個雜湊槽從節點A reshard到節點B時,會強制B將其configEpoch更新為叢集最大值,並加1(除非已經是最大的configEpoch),而無需其他節點的同意。實際中,一個reshard通常會涉及上百個雜湊槽,如果為每個雜湊槽都生成一個新的configEpoch會導致效能下降,為此,只需要在轉移第一個雜湊槽時生成一個新的configEpoch即可。
除上述兩種原因外,軟體bug或檔案系統問題也可能會導致多個節點具有相同的configEpoch。為了解決該問題,會透過沖突解決演算法來保證不同的節點具有不同的configEpoch:
- 如果一個master節點探測到和其他master節點具有相同的configEpoch
- 且相比其他具有相同configEpoch的節點,本節點ID更小
- 則將本節點的currentEpoch加1,作為configEpoch
如果存在多個相同configEpoch的節點,則除ID最大的節點外,其他節點都會透過執行上述步驟增加configEpoch,最終保證所有節點都具有唯一的configEpoch(replica的configEpoch與其master相同)。
在初始化一個叢集時,可以透過redis-cli的 CLUSTER SET-CONFIG-EPOCH
命令在加入叢集之前為每個節點配置不同的configEpoch。
故障處理
概念理解
Redis Cluster的故障轉移涉及兩個比較難以理解的點:一是故障轉移過程中replica和master存在多個時間區間,另一個是currentEpoch
/configEpoch
/lastVoteEpoch
這幾個概念。
故障處理中的時間點
在故障轉移中,一個Replica需要經歷如下幾個時間段,其中Election Delay為Election Delay = 500 milliseconds + random delay between 0 and 500 milliseconds + REPLICA_RANK * 1000 milliseconds
,Voter cycle也可能存在多個(replica投票請求失敗的情況下)。
對於master來說,只存在大小為NODE_TIMEOUT * 2
的投票週期,在一個週期內,一個master只會給相同master下的一個replica投票。
幾個Epoch的用途
lastVoteEpoch
:master側記錄的上一次投票週期資訊。- 用途:僅用於master校驗replica投票請求的有效性。
- 如果請求中的
currentEpoch
小於master記錄的lastVoteEpoch
,說明該請求屬於老的投票週期,並沒有啟動新的投票週期,忽略該請求。
- 如果請求中的
- 更新時機:在master給replica成功投票之後,會將
lastVoteEpoch
更新為請求中的currentEpoch
。
- 用途:僅用於master校驗replica投票請求的有效性。
currentEpoch
:叢集所有節點都應該具有相同的currentEpoch
- 用途:主要用於表示當前正在進行第幾個故障轉移投票週期。用於防止replica錯誤計算master的投票次數。
- replica會在投票請求中傳送
currentEpoch
,如果master發現請求的currentEpoch
小於該master的currentEpoch
,則master會忽略該請求。 - 如果replica接收到的master的投票中的
currentEpoch
小於該replica的currentEpoch
,也會忽略此投票結果
- replica會在投票請求中傳送
- 更新時機:
- Redis Cluster的節點會透過心跳訊息傳播
currentEpoch
,如果一個節點的currentEpoch
訊息接收到的心跳訊息中的currentEpoch
,則更新本地的currentEpoch
,最終所有節點都會更新為相同的currentEpoch
- Replica 執行投票請求時會將
currentEpoch
+ 1
- Redis Cluster的節點會透過心跳訊息傳播
- 用途:主要用於表示當前正在進行第幾個故障轉移投票週期。用於防止replica錯誤計算master的投票次數。
configEpoch
:不同的master應該具有不同的值。- 用途:
- 用於表示當前master的配置週期。當多個master宣稱對相同雜湊槽的所有權時,可以透過
configEpoch
解決分歧 - 投票過程中如果請求中的雜湊槽的
configEpoch
小於master本地記錄的對應雜湊槽的configEpoch
,則忽略該請求
- 用於表示當前master的配置週期。當多個master宣稱對相同雜湊槽的所有權時,可以透過
- 更新時機:
- 透過心跳訊息更新replica的
configEpoch
- 透過UPDATE訊息更新rejoin節點的
configEpoch
- replica在選舉成功後會生成新的
configEpoch
- ConfigEpoch衝突解決演算法自動解決多節點
configEpoch
衝突問題 - 執行
CLUSTER FAILOVER TAKEOVER
- 執行reshard雜湊槽
- 透過心跳訊息更新replica的
- 用途:
故障檢測
當Redis Cluster檢測到一個master或一個replica節點無法被大部分masters連線時,會嘗試將一個replica提升為master,如果故障轉移失敗,則叢集會丟擲錯誤並停止接收clients的請求。
Redis Cluster有兩個故障狀態:PFAIL和FAIL。PFAIL表示沒有在NODE_TIMEOUT
時間內未接收到對端的Pong訊息,認為對端可能出現故障,但還未經確認。FAIL表示在一定時間內(NODE_TIMEOUT
),大部分masters都確認了該節點故障。
當一個節點(無論master還是replica)無法在NODE_TIMEOUT
時間內連線時,就會被標記為PFAIL。但PFAIL只是每個節點對其他節點狀態的本地描述,如果要觸發故障轉移,還需要將其提升到FAIL狀態。
Redis Cluster中,每個節點都會透過心跳訊息(gossip訊息)向其他節點傳遞節點狀態資訊,這樣每個節點都會接收到其他每個節點的node flag集(多份)。以此來向其他節點通知其發現的故障節點。過程如下:
- 假設A節點將B節點標記為PFAIL
- A節點會透過gossip向叢集廣播上述訊息
- 在NODE_TIMEOUT * FAIL_REPORT_VALIDITY_MULT時間內(當前實現中 validity factor為2),大部分master將該節點標記為PFAIL或FAIL
之後,A節點會將B節點標記為FAIL,然後向所有可達的節點傳送心跳訊息(state為FAIL)。在其他節點接收到FAIL訊息之後,會強制將B節點設定為FAIL。
PFAIL->FAIL是單向的,但在如下場景(節點恢復)中可以清除FAIL標記:
- 節點可達,作為replica。此時replica不會執行故障轉移
- 節點可達,作為master,但不包含任何雜湊槽。由於空的master並不會真正參與到叢集中,需要配置才能加入叢集。
- 節點可達,作為master,但過去很長時間(N倍的
NODE_TIMEOUT
),且沒有檢測到任何被提升的replica。
需要注意的是,PFAIL->FAIL的轉變是基於約定的,但該約定比較弱:
- 節點需要一定時間來採集其他節點的報告,這些報告來自不同節點的不同時間點,因此可能存在老的故障報告。為此需要丟棄那些不在時間視窗內的報告
- 每個檢測到FAIL條件的節點都會強制其他節點應用FAIL訊息,但不能保證該訊息到達所有的節點。
在發生腦裂(網路隔離)的情況下可能會出現兩種情況:大部分節點認為一個節點處於FAIL狀態;少部分節點認為該節點處於非FAIL狀態:
- 場景1:如果大部分masters都將一個節點標記為FAIL,則最終其他節點都會將該節點設定為FAIL
- 場景2:如果只有小部分master將一個節點標記為FAIL,那麼將不會把replica提升為master,每個節點會按照上面的規則清除FAIL狀態。
PFAIL標記的目的只是為了觸發故障轉移。
故障轉移
可以透過CLUSTER FAILOVER
命令手動執行故障轉移,這種情況下由於replica會等待和master的資料一致時才進行切換,因此不會丟失資料。
replica選舉
為了將一個replica提升為master,首先要經過選舉,並贏得選舉。如果master為FAIL狀態,則該master下的所有replicas都會啟動選舉。但最後只有一個會贏得選舉並提升為master。
當滿足如下條件時會,replica會開始選舉:
- 該replica的master狀態為FAIL
- master包含非空的slots
- replica和master的replication斷開的連線沒有超過一定時間,以此確保被提升的replica的資料相對新。
為了被選中,replica首先會增加其currentEpoch計數器,然後透過廣播FAILOVER_AUTH_REQUEST
報文向所有masters發起請求投票。然後等待(NODE_TIMEOUT * 2
,至少2s)master的響應。
一旦一個master響應了一個replica 的FAILOVER_AUTH_ACK
請求,就不會在NODE_TIMEOUT * 2
時間內為相同master下的replica投票,也不會響應相同master的授權請求。
replica會丟棄小於currentEpoch的AUTH_ACK
應答,避免將上一次選舉的投票計算入內。
如果replica獲得了大部分masters的ACKs,則贏得選舉;否則中斷選舉,並在NODE_TIMEOUT * 4
之後重新嘗試選舉(至少4s)。
replica排名
一旦master變為FAIL狀態,replica會在選舉前等待一段時間,該延遲計算如下:
DELAY = 500 milliseconds + random delay between 0 and 500 milliseconds +
REPLICA_RANK * 1000 milliseconds.
replica延遲等待的原因是為了讓FAIL傳遞到叢集中,否則如果master沒有感知到該FAIL狀態可能會導致拒絕投票。另外由於不同排名的replica具有不同的REPLICA_RANK
,因此也可以防止所有replica同時啟動選舉。
REPLICA_RANK
會根據replica從master複製的資料量來對replica進行排名:具有最新replication offset的replica為0,第二新的為1,以此類推。這樣具有最新資料的replica會被優先選舉。如果一個排名較高的replica選舉失敗,則其他replica會在短時間內嘗試選舉。
一旦一個replica贏得選舉,它會得到唯一且遞增的configEpoch(大於其他已有master),並透過Ping和Pong宣告其master角色以及所擁有的雜湊槽。如果老master重新加入叢集,它會發現已經存在一個更高的configEpoch,此時會更新配置,並從新master複製訊息。
master的投票流程
在master接收到replicas的FAILOVER_AUTH_REQUEST
請求時,當滿足如下條件時,會給replica投票:
-
在一個epoch中,一個master只會進行一次投票,且不會處理老的投票週期(epoch)。每個master都有一個
lastVoteEpoch
欄位,如果請求中的currentEpoch
小於lastVoteEpoch
,則拒絕投票。當一個master為一個請求投票之後,會更新相應的lastVoteEpoch
。 -
只有當replica的master標記為FAIL,master才會給其master的replica投票
-
如果請求中的
currentEpoch
小於master的currentEpoch
,則忽略該請求。master只會給相同currentEpoch
的replica投票。如果該replica再次發起投票請求,則會增加currentEpoch。這是為了防止replica在新的投票中接收到因為延遲導致的老的投票資訊。如果沒有規則3,將會出現如下情況:
- master的
currentEpoch
為5,lastVoteEpoch
為1(表示只出現了少數故障選舉) - replica的
currentEpoch
為3 - replica嘗試使用epoch 4(3+1)進行選舉,master返回了ok(
currentEpoch
5),但該響應被延遲了 - replica沒有接收到請求,再次嘗試使用epoch 5(4+1)選舉,此時接收到了延遲的master響應(
currentEpoch
5),replica認為該響應有效。這樣就錯誤計算了投票的master的個數。
- master的
-
在給一個master的replica投票之後,masters不會在之後的
NODE_TIMEOUT * 2
時間內再次給相同master下的replica投票。由於一個epoch內不會同時選舉出2個replicas,因此該值在實際中主要是為了讓當選的replica有足夠的時間來通知其他replicas,避免其他replica再次當選,啟動不必要的故障轉移流程。 -
masters不會嘗試選擇最佳的replica。由於master不會在同一個epoch內對已經投過票的master下的replicas再次投票,因此最佳replica更有可能最先啟動選舉並贏得選舉。
-
如果master拒絕為一個replica投票,會直接忽略投票請求
-
如果一個replica傳送的
configEpoch
,小於master 雜湊槽對映表中記錄的任何一個與replica宣告的slots的configEpoch
,則master拒絕為其投票,說明replica的配置不是最新的,後續會透過UPDATE訊息更新配置。參見雜湊槽配置的傳遞
UPDATE訊息
雜湊槽配置的傳遞
Redis Cluster的一個關鍵功能是如何傳遞節點的雜湊槽資訊。這對於啟動一個新的叢集、故障轉移和節點重新加入叢集等場景至關重要。
有兩種傳遞slot配置的途徑:
- 心跳訊息:傳送者的Ping或Pong報文中會包含其擁有的雜湊槽資訊
- UPDATE訊息:由於心跳報文中包含了傳送者的configEpoch和雜湊槽,如果心跳訊息接收者發現傳送者的資訊不是最新的,則會向它傳送一個包含最新資訊的報文,強制更新其資訊。
在建立一個新的Redis Cluster節點時,其雜湊槽為空,值為NULL,表示雜湊槽沒有繫結到任何節點:
0 -> NULL
1 -> NULL
2 -> NULL
...
16383 -> NULL
雜湊槽的傳遞需要滿足如下規則:
規則1:如果一個雜湊槽為NULL,當一個已知節點宣告該雜湊槽所有權時,本節點會修改本地的雜湊槽表,將其關聯到它們所在的節點。下面表示節點A負責雜湊槽1和2,configEpoch為3:
0 -> NULL
1 -> A [3]
2 -> A [3]
...
16383 -> NULL
但這種對映不是固定的,在故障轉移和手動reshard時都會改變雜湊槽的對映關係。
規則2:如果一個雜湊槽已經被分配,且一個已知節點使用大於與該雜湊槽關聯的master 的configEpoch的configEpoch宣告該雜湊槽的所有權時,則會將該雜湊槽查詢繫結到新的節點。由於該規則的存在,叢集中的所有節點最終會同意具有最大configEpoch的節點對雜湊槽的所有權。
B為A的replica,在故障轉移之後,它會透過心跳訊息廣播其配置資訊,接收者在接收到節點B宣告的configEpoch 4和雜湊槽1和2的所有權的心跳資訊時,接收者會更新各自的本地雜湊槽對映表:
0 -> NULL
1 -> B [4]
2 -> B [4]
...
16383 -> NULL
節點重新加入叢集
當一個節點A重新加入叢集之後,它會透過心跳訊息,並使用老的epoch宣告其所擁有的雜湊槽,訊息接收者會發現與節點A相關的雜湊槽已經關聯了一個更高的configEpoch時,會向節點A傳送包含這些雜湊槽最新配置的UPDATE訊息,隨後A會根據上述規則2來更新其配置。
實際中可能會更加複雜,如果節點A在很長時間之後才重新加入叢集,則其原來擁有的雜湊槽可能已經被多個節點所有。在重新加入叢集后,節點A會按照如下規則來轉換角色:
master作為偷取它最後一個雜湊槽的節點的replica
大部分情況下,重新加入叢集的節點會作為故障轉移中新master的replica。
Client連線
client重定向
Redis的client可以連線到叢集的任一節點(包括replica),節點會分析請求中的keys對應的雜湊槽所在的節點,並處理屬於該節點的雜湊槽,否則會檢查其內部的雜湊槽到節點的對映表,給client返回MOVED錯誤:
GET x
-MOVED 3999 127.0.0.1:6381
上述錯誤表示key(3999)對應的雜湊槽所在的endpoint:port,之後client需要向該節點的endpoint:port重新傳送請求。
好的Redis client應該能夠記錄雜湊槽和節點的對映關係,可以透過 CLUSTER SHARDS
或CLUSTER SLOTS
獲取叢集佈局。
MOVED重定向
ASK重定向
TODO
持久化
Redis支援如下持久化模式:
- RDB(redis database): 用於週期性地持久化一個時間點的dataset快照
- AOF(append only file):AOF會持久化所有server接收到的寫操作。可以在server啟動的時候透過replay這些操作來重建原始的dataset。
- No persistence:禁用持久化。
- RDB+AOF:在相同的例項上同時啟用AOF和RDB
RDB
RDB是一個非常緊湊的,可以代表某個時間點的redis資料的二進位制快照檔案,檔名為dump.rdb
(dbfilename dump.rdb
),可以透過SAVE
或 BGSAVE
設定Snapshotting的生成條件。如下面表示每60s檢查一次,如果至少有1000個keys發生變更,則dump dataset:
save 60 1000
推薦使用
BGSAVE
非阻塞模式,SAVE
執行同步命令,會阻塞Redis。
在建立RDB檔案的過程中,redis首先會將dataset寫入一個臨時的RDB檔案中,當寫入結束後,移除掉舊的RDB檔案(rename操作)。
可以使用如下兩種方式獲取dump.rdb檔案的位置:
redis.conf
中定義的dir
引數- 透過
redis-cli -p 6379 CONFIG GET dir
命令獲取
優點
- 可以透過RDB實現週期性備份
- 非常適合容災。可以將RDB檔案傳輸到資料中心,如Amazon S3,
- RDB是在子程序中建立的,不會影響主程序的效能。
- 對於較大的dataset,RDB的重啟速度要快於AOF
- RDB支援replication中的部分同步
缺點
- RDB不能保證在redis停機的情況下最小化資料丟失。
- RDB需要fork()子程序來持久化到磁碟,如果dateset比較大,fork()可能會花費較長時間,導致redis在一定毫秒甚至1秒之內無法處理clients請求。而AOF雖然也需要執行fork(),但其fork()操作並沒有RDB那麼頻繁。
禁用RDB
config set save ""
AOF
透過如下配置啟用AOF,這樣在redis重啟之後就可以透過AOF恢復狀態:
appendonly yes
優點
- AOF有不同的fsync策略:不執行fsync(
appendfsync no
,依賴作業系統重新整理)、每秒執行一次fsync(appendfsync everysec
)、每個請求都執行fsync(appendfsync always
)。預設每秒執行一次fsync,即最多會丟失1秒的寫資料 - AOF是一個只追加的log檔案,即使發生斷電故障也不會造成損壞問題。如果由於某些原因導致log中最後一條命令不完整,也可以透過redis-check-aof工具進行修復
- AOF會在檔案變大時在後臺執行rewrite。
- AOF的格式易於理解和解析
缺點
- 對於相同的dataset,AOF檔案通常要大於RDB檔案
- 根據具體的fsync策略(如
appendfsync always
),AOF可能會比RDB慢。
Log rewriting
隨著寫操作的執行,AOF會變得越來越大,例如,如果對一個counter增加100次,會在dataset中得到一個最終的值,但在AOF檔案中則會有100條操作記錄,在恢復redis狀態時,其中的99條並沒有任何作用。rewrite的目的就是移除多餘的操作記錄,僅保留最終值。
rewrite的過程是安全的,在執行rewrite時,redis可以繼續在舊的檔案中追加資料。rewrite時會使用全新的檔案並寫入建立當前dataset所需的最小操作集,之後redis會切換到新檔案,並開始在新檔案中追加命令。
從Redis 7.0.0開始,當執行AOF rewrite時,主程序會開啟一個新的incremental AOF檔案繼續執行寫操作,子程序則會執行rewrite邏輯生成一個新的base AOF。redis會使用一個臨時的清單檔案來追蹤incremental AOF和base AOF。當這兩個檔案就緒後,就可以透過清單檔案來執行原子替換操作。在rewrite生效後,redis會刪除老的base檔案和無用的incremental檔案。
如果rewrite失敗,還可以使用老的base和increment檔案(如果存在),以及新建立的increment檔案來表示完整的更新後的dataset。
可以使用如下兩個redis.conf引數除錯rewrite的執行策略,
auto-aof-rewrite-percentage
:如果當前AOF檔案比上一次rewrite之後的AOF檔案大百分之auto-aof-rewrite-percentage
,且AOF檔案不小於auto-aof-rewrite-min-size
,則執行rewrite操作。auto-aof-rewrite-min-size
:只有當AOF檔案不小於該值時,才允許根據auto-aof-rewrite-percentage
執行rewrite。這是為了防止不必要的rewrite。
AOF和RDB持久化的互動
Redis >= 2.4中,為了避免高磁碟I/O,會避免同時進行AOF rewrite和RDB snapshot操作。如果正在進行snapshoting,且此時使用者透過BGREWRITEAOF
命令啟動了AOF rewrite,伺服器會透過ok狀態碼來告訴使用者該操作已經被排程,待snapshotting結束之後會開始rewrite。
如果同時啟用了AOF和RDB,則在redis重啟之後會優先使用AOF檔案來重建原始的dataset(AOF具有完整的資訊)。
備份和恢復
備份RDB資料
RDB檔案在生成之後就不會再變,因此可以直接將RDB檔案複製到安全的地方。
備份AOF資料
從Redis 7.0.0開始,AOF檔案被切分為appenddirname
(redis.conf)目錄中的多個檔案,可以使用copy/tar命令來建立目錄備份。但如果此時正在進行rewrite
,則可能會得到一個無效的備份,因此在備份時需要禁用AOF rewrite功能:
- 禁用AOF rewrite:
CONFIG SET
auto-aof-rewrite-percentage 0
- 確保沒有正在執行的rewrite:命令
INFO
persistence
的輸出中aof_rewrite_in_progress
為0。 - 複製
appenddirname
目錄 - 在備份結束後,重新啟用AOF rewrite:
CONFIG SET
auto-aof-rewrite-percentage <prev-value>
在小於7.0.0版本的redis中,可以直接複製AOF檔案。
Security
ACL
ACL有很多配置規則,參見acl-rules。建立ACL的方式有兩種:使用ACL SETUSER
命令;使用ACL LOAD
載入ACL檔案。
下面建立了建立了一個名為alice
的使用者,這裡沒有使用SETUSER
指定規則:
> ACL SETUSER alice
OK
此時alice的許可權如下:
- 在關閉ACL的情況下,無法使用AUTH配置
alice
- 該使用者沒有設定密碼
- 無法訪問任何命令。預設建立的使用者的command許可權為
-@all
- 無法訪問任何key
- 無法訪問任何Pub/Sub channels
下面為alice
配置了一個密碼,且只允許使用 GET
命令獲取以cached:
開頭的key。
> ACL SETUSER alice on >p1pp0 ~cached:* +get
OK
使用 ACL GETUSER
可以檢視使用者的許可權:
> ACL GETUSER alice
1) "flags"
2) 1) "on"
3) "passwords"
4) 1) "2d9c75..."
5) "commands"
6) "-@all +get"
7) "keys"
8) "~cached:*"
9) "channels"
10) ""
11) "selectors"
12) (empty array)
可以繼續使用SETUSER
為使用者新增許可權,SETUSER
可以為使用者追加新的許可權:
> ACL SETUSER alice ~objects:* ~items:* ~public:*
OK
> ACL LIST
1) "user alice on #2d9c75... ~cached:* ~objects:* ~items:* ~public:* resetchannels -@all +get"
2) "user default on nopass ~* &* +@all"
Command categories
為使用者一個個新增目錄比較繁瑣,可以透過categories直接給使用者新增多個命令的許可權。
使用下面命令可以檢視支援的categories,以及各個categories下面包含的命令。注意一個命令可能位於多個categories中:
ACL CAT -- Will just list all the categories available
ACL CAT <category-name> -- Will list all the commands inside the category
使用categories新增ACL的方式例如下:
> ACL SETUSER antirez on +@all -@dangerous >42a979... ~*
TLS
證書配置
tls-cert-file /path/to/redis.crt
tls-key-file /path/to/redis.key
tls-ca-cert-file /path/to/ca.crt
tls-dh-params-file /path/to/redis.dh
埠配置
port 0
表示禁用非TLS埠
port 0
tls-port 6379
客戶端認證
可以透過tls-auth-clients no
禁用客戶端認證
Replication
master上需要配置tls-port
和 tls-auth-clients
。
replica側需要設定tls-replication yes
來啟用和master連線的TLS。
Cluster
設定tls-cluster yes
來為cluster bus 啟用TLS。
Sentinel
Sentinel也會透過 tls-replication
來確定到master的連線是否啟用TLS,該引數還用於決定接受的來自其他Sentinel的連線是否啟用TLS。只有在啟用tls-replication
的情況下,才能啟用tls-port
。
Cli
General
- info memory:
used_memory_human
:表示快取的使用者資料大小。
- memory stats:以陣列格式展示服務的記憶體使用情況
- memory doctor:記憶體診斷工具
- FAILOVER:與
CLUSTER FAILOVER
類似,適用於非Cluster的redis
配置變更
- CONFIG GET:
CONFIG GET parameter [parameter ...]
。獲取redis配置(redis.conf)。可以透過CONFIG get *
檢視當前redis的所有配置 - CONFIG SET:在執行時設定redis的配置,無需重啟Redis
- CONFIG REWRITE:該命令會將redis正在使用的配置持久化到
redis.conf
檔案中,如果使用了CONFIG SET
命令,則生成的配置寄檔案可能會與原始檔案不一致。
DB
-
CONFIG GET databases:檢視redis的database數目,預設有16個DB
-
INFO keyspace:展示了各個db的主目錄,格式如下。其中
keys
表示該DB中keys總數,expires
表示過期的keys總數,avg_ttl
表示隨機取樣得到的平均ttl,單位毫秒,該值為0表示所有采樣的keys都沒有設定過期時間。dbXXX: keys=XXX,expires=XXX,avg_ttl=XXX,subexpiry=XXX
-
FLUSHALL [ASYNC | SYNC]:刪除所有DB的keys。
切換db
-
select <db_num>
交換兩個DB的資料
- SWAPDB index1 index2:交換兩個DB的資料,這樣連線到一個特定db的clients會立馬看到另一個db的資料。如
SWAPDB 0 1
會交換db0和db1的資料,所有連線到db0的client會立即看到交換過來的db1資料,同樣連線到db1的clients會看到原來屬於db0資料。
獲取一個key的剩餘ttl
-
TTL <key_name>
使用scan命令
- 獲取database中的一部分keys:
SCAN cursor [MATCH pattern] [COUNT count] [TYPE type]
,如SCAN 0 COUNT 100 MATCH NEW_MD*
表示獲取以NEW_MD
開頭的第0~100個資料。 - 獲取特定型別的keys:
SCAN 0 type <type>
,如scan 0 type hash
。可以使用type
命令檢視key的型別
在使用scan時也可以不指定count
,scan
會返回兩個值:第一個值(17
)為下次呼叫scan的起始位置,第二個為匹配的內容。如果scan返回的第一個值為0,則表示scan結束,即完全迭代。
redis 127.0.0.1:6379> scan 0
1) "17"
2) 1) "key:12"
2) "key:8"
3) "key:4"
4) "key:14"
5) "key:16"
6) "key:17"
7) "key:15"
8) "key:10"
9) "key:3"
10) "key:7"
11) "key:1"
redis 127.0.0.1:6379> scan 17
1) "0"
2) 1) "key:5"
2) "key:18"
3) "key:0"
4) "key:2"
5) "key:19"
6) "key:13"
7) "key:6"
8) "key:9"
9) "key:11"
為hash設定過期時間
HSET命令是沒有直接設定過期時間的引數的,有如下兩種方式設定過期時間:
-
一種是將過期時間放到hash數值中,透過程式控制的方式刪除,但這種方式依賴程式的執行,可能會導致redis記憶體洩露
-
另一種方式如下,即在配置一個hash之後,再呼叫EXPIRE命令設定其TTL,推薦這種方式:
redis> HSET myhash field1 "helloworld" (integer) 0 redis> EXPIRE myhash 60 (integer) 1
replication
-
Info replication:檢視replication資訊。在master上檢視其replicas的狀態,
connected_slaves
表示連線的replicas的數目。slave0:ip=10.150.208.73,port=6380,state=online,offset=455191,lag=0
中注意state
和lag
兩個欄位# Replication role:master connected_slaves:1 slave0:ip=10.150.208.73,port=6380,state=online,offset=455191,lag=0 master_replid:d550cc0b17bba2e43d5fe6caa57bb74ea23dc755 master_replid2:8c561bcd6cf98616cf802f646cbb5d87dba0a452 master_repl_offset:455191 second_repl_offset:2792 repl_backlog_active:1 repl_backlog_size:1048576 repl_backlog_first_byte_offset:660 repl_backlog_histlen:454532
-
role:顯示當前例項的角色:
master
、slave
還是sentinel
,以及replication offset。master上還可以看到其資料offset以及slave的資料offset資訊。127.0.0.1:6379> role 1) "master" 2) (integer) 11483751172473 3) 1) 1) "10.157.4.26" 2) "6379" 3) "11483751172187"
-
REPLICAOF:REPLICAOF <host port | NO ONE>
- 如果一個例項已經是某個master的replica,
REPLICAOF host port
會停止從老的master複製資料,並丟棄老的dataset,開始從新的master複製資料。 REPLICAOF NO ONE
會停止replication,並將該例項提升為master,但不會丟棄dataset。
- 如果一個例項已經是某個master的replica,
-
PSYNC:PSYNC replicationid
。在replica 節點上執行該命令,向 master 節點請求replication流。 replicationid
表示master的id;offset
表示replica最後接收到的偏移量。用於執行部分(重)同步- 可以透過
PSYNC ? -1
執行完整(重)同步。
-
WAIT:WAIT numreplicas
。客戶端使用。該命令會阻塞當前client,直到接收到至少 numreplicas
個replicas的確認訊息或timeout
(ms)。timeout
為0表示永遠阻塞。WAIT命令會在命令執行1s(使用了replicas和master的ping時間間隔)之後和超時之後返回已確認的replicas數目。
需要注意的是WAIT並不能讓redis成為一個強一致儲存,仍然存在資料丟失的可能性。例如在master給replicas傳送完一個寫命令之後就發生了故障,但replicas由於某種原因(如網路)沒能接收到這條命令,後續在某個relica提升為mater之後將會丟失掉這條訊息。但WAIT在一定程度上提升了資料的安全性。
-
SHUTDOWN:SHUTDOWN [NOSAVE | SAVE] [NOW] [FORCE] [ABORT]。關閉redis服務,過程如下:
- 如果replication中存在lag
- 透過
CLIENT PAUSE
暫停clients的寫操作 - 等待shutdown命令超時(預設10s),便於replicas追上replication offset
- 透過
- 停止所有clients
- 如果配置了save點,則執行一次阻塞SAVE
- 如果啟用AOF,則重新整理AOF檔案
- 退出服務
引數如下:
- SAVE:如果沒有配置save點,則強制執行一次DB save操作(RDB)
- NOSAVE:即使配置了save點,也不會執行DB save操作
- NOW:無需等待延遲的replicas,即跳過shutdown的第一步
- FORCE:忽略所有阻止服務退出的錯誤,如無法儲存RDB檔案。
- ABORT:取消正在進行的shutdown操作
如果需要儘快關閉redis例項,可以使用
SHUTDOWN NOW NOSAVE FORCE
命令。7.0之前,可以使用CONFIG appendonly no
和SHUTDOWN NOSAVE
來關閉AOF並退出Resis。Redis 7.0之後,在shutdown時,預設會等待10s來讓replicas儘可能追上master的offset,以此來減小在沒有配置save點和禁用AOF的情況下丟失的資料量。redis 7.0之前,在shutdown一個master節點之前可以透過
FAILOVER
(或CLUSTER FAILOVER
) 使用當前master降級,並將另一個replica提升為master。 - 如果replication中存在lag
persistent
-
info persistence: 檢視rdb和aof資訊
127.0.0.1:6379> info persistence # Persistence loading:0 rdb_changes_since_last_save:58287 rdb_bgsave_in_progress:1 #bgsave,後臺正在儲存dump.rdb檔案 rdb_last_save_time:1723595190 rdb_last_bgsave_status:ok #上一次bgsave的執行狀態 rdb_last_bgsave_time_sec:215 rdb_current_bgsave_time_sec:137 rdb_last_cow_size:56147968 aof_enabled:0 #標記是否啟用AOF aof_rewrite_in_progress:0 #標記是否正在進行AOF rewrite aof_rewrite_scheduled:0 #標記是否待執行AOF rewrite,排程之後需要考慮是否正在執行RDB dump等,並不會立即執行 aof_last_rewrite_time_sec:-1 aof_current_rewrite_time_sec:-1 aof_last_bgrewrite_status:ok aof_last_write_status:ok #上一次AOF rewrite的執行狀態 aof_last_cow_size:0 module_fork_in_progress:0 module_fork_last_cow_size:0
-
BGREWRITEAOF :後臺啟動AOF rewrite流程
-
BGSAVE:後臺生成rdb dump檔案。
Sentinel
-
info sentinel:顯示Redis Sentinel模式的資訊:
sentinel_masters
: 該Sentinel例項monitor的master數sentinel_tilt
: 值為1時表示該Sentinel進入TILT模式sentinel_tilt_since_seconds
: 當前TILT的持續時間sentinel_running_scripts
: 該sentinels當前執行的scripts數目sentinel_scripts_queue_length
: 等待執行的使用者指令碼佇列的長度sentinel_simulate_failure_flags
:SENTINEL
SIMULATE-FAILURE
命令的標記
-
SENTINEL MASTER
:展示特定master的狀態,主要注意點如下: num-other-sentinels
:表示還有幾個sentinel例項flags
:正常情況下只顯示master
,如果master down,可能會顯示s_down
或o_down
(參見[SDOWN 和 ODOWN故障狀態](#SDOWN 和 ODOWN故障狀態))num-slaves
:表示隸屬於該master的replica的數目
可以透過如下兩條命令檢視更多詳情:
- SENTINEL REPLICAS
<master name>
(>= 5.0
):展示特定master的replicas的資訊,透過flag可以檢視當前狀態 - SENTINEL SENTINELS
<master name>
:展示特定master的sentinels的資訊,透過flag可以檢視當前狀態
-
SENTINEL MASTERS:展示所有masters的狀態
-
SENTINEL FAILOVER
:如果master不可達,強制執行故障轉移,無需向自他sentinels的同意。 -
執行時重配置sentinel
-
SENTINEL RESET
<pattern>
:用於重置匹配名稱的masters。重置過程會清除一個master之前的所有狀態(包括正在進行的故障轉移),並移除與該master有關的replica和sentinel。用於移除sentinel和移除replica。
ACL
- ACL LIST
- ACL GETUSER
- ACL CAT : 檢視ACL category
Cluster
cluster中的幾個有用的命令
redis-cli --cluster check <ip:port> -a <password>
檢查Cluster狀態:
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
localhost:6379 (891a4f01...) -> 33773876 keys | 5462 slots | 1 slaves.
10.157.4.111:6379 (66fb9464...) -> 33783136 keys | 5461 slots | 1 slaves.
10.157.4.41:6379 (0107b5fb...) -> 33775733 keys | 5461 slots | 1 slaves.
[OK] 101332745 keys in 3 masters.
6184.86 keys per slot on average.
>>> Performing Cluster Check (using node localhost:6379)
M: 891a4f01875f855e0a3b13ce2121128270ef4e38 localhost:6379
slots:[5461-10922] (5462 slots) master
1 additional replica(s)
M: 66fb9464ff92d2373e72655da4fa54c96487f120 10.157.4.111:6379
slots:[10923-16383] (5461 slots) master
1 additional replica(s)
M: 0107b5fb8b5e1325aec42404753b7884061cb496 10.157.4.41:6379
slots:[0-5460] (5461 slots) master
1 additional replica(s)
S: 825faec7fc7c6bdc0ef6b7ede81477ea58d0e1c9 10.157.4.109:6379
slots: (0 slots) slave
replicates 0107b5fb8b5e1325aec42404753b7884061cb496
S: 463d0bda350bb5b1ceddb2318aed5e06e3162ff5 10.157.4.42:6379
slots: (0 slots) slave
replicates 66fb9464ff92d2373e72655da4fa54c96487f120
S: 6f0e1ad522a0bb9021b4ee6be2acb7e04e767b45 10.157.4.110:6379
slots: (0 slots) slave
replicates 891a4f01875f855e0a3b13ce2121128270ef4e38
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
-
redis-cli --cluster reshard <host>:<port> --cluster-from <node-id> --cluster-to <node-id> --cluster-slots <number of slots> --cluster-yes
:重分配slots -
redis-cli --cluster rebalance <ip:port>
:在非空的master節點上均衡slots,如果需要在空節點(如新加的節點)上均衡slots,需要使用引數--cluster-use-empty-masters
。 -
redis-cli --cluster fix <ip:port>"
:用於修復叢集,可以修復如下兩種情況:--cluster-search-multiple-owners
: 修復槽的重複分配問題。當叢集中的槽位在遷移過程中出現被重複分片的槽時(可以透過redis-cli --cluster check
檢查),可以透過該引數修復此問題--cluster-fix-with-unreachable-masters
: 修復不可達的主節點上的槽位。例如,叢集中某個主節點出現故障了,且故障轉移失敗。此時可以使用該引數將該主節點上的所有槽位恢復到存活的主節點上(之前的資料會丟失,僅僅是恢復了槽位)。
其他命令
-
info cluster:顯示Redis Cluster模式的資訊
-
CLUSTER SET-CONFIG-EPOCH:在初始化一個叢集時,可以透過該命令在加入叢集之前為每個節點配置不同的configEpoch。
-
CLUSTER NODES :管理員視角,展示Redis Cluster的叢集狀態,格式如下:
<id> <ip:port@cport[,hostname]> <flags> <master> <ping-sent> <pong-recv> <config-epoch> <link-state> <slot> <slot> ... <slot>
id
:節點IDip:port@cport
:節點IP:client_port@cluster_porthostname
:cluster-annouce-hostname
設定的主機名flags
:myself
: 表示本節點master
: master節點slave
: replica節點fail?
:PFAIL
狀態的節點,表示當前節點無法連線到的節點fail
:FAIL
狀態的節點。多個節點無法連線時,將PFAIL狀態提升到FAILhandshake
: 握手階段中不可信的節點noaddr
: 位置地址的節點nofailover
: 不會執行故障轉移的replicanoflags
: 無flag
master
:如果節點為replica,且知道其master,則使用master的節點ID,否則使用"-"ping-sent
:當前活動的ping的傳送Unix時間,0表示沒有pending的ping,單位毫秒pong-recv
:上一次接收到pong的Unix時間,單位毫秒config-epoch
:configuration epoch,表示該節點的currentEpoch
link-state
:節點到cluster bus的連線狀態,connected
或disconnected
slot
:雜湊槽號或範圍,表示一個節點負責的雜湊槽。
-
CLUSTER SLOTS :client視角,
CLUSTER NODES
的子集。Redis 7.0.0之後廢棄。每個元素的前兩行表示slot的開始和結尾:127.0.0.1:6379> cluster slots 1) 1) (integer) 10923 # slots 10923-16383 2) (integer) 16383 3) 1) "10.157.4.111" 2) (integer) 6379 3) "66fb9464ff92d2373e72655da4fa54c96487f120" 4) 1) "10.157.4.42" 2) (integer) 6379 3) "463d0bda350bb5b1ceddb2318aed5e06e3162ff5" 2) 1) (integer) 0 # slots 0-5460 2) (integer) 5460 3) 1) "10.157.4.41" 2) (integer) 6379 3) "0107b5fb8b5e1325aec42404753b7884061cb496" 4) 1) "10.157.4.109" 2) (integer) 6379 3) "825faec7fc7c6bdc0ef6b7ede81477ea58d0e1c9"
-
CLUSTER SHARDS:Redis 7.0.0引入。用於替代
CLUSTER SLOTS
命令。一個shard表示一組具有相同雜湊槽的master-replicas節點,每個shard只有一個master,以及0或多個replicas。該命令的會返回兩部分內容:"slots"和"nodes"。前者表示該shard中的slot,後者表示該shard中的節點。"nodes"中的
endpoint
欄位表示請求特定槽時應該連線的位置。 -
CLUSTER FAILOVER:
CLUSTER FAILOVER [FORCE | TAKEOVER]
。手動執行故障轉移,必須在需要執行故障轉移的master對應的某個replica上執行。流程如下:- replica通知master停止處理clients請求
- master使用當前的replication offset回應此replica
- replica等待本地replication offset和master相匹配,確保資料同步
- replica啟動故障轉移,從大部分masters中獲得一個新的configuration epoch,並廣播此configuration epoch
- 老master在接收到配置更新後會取消阻塞clients,並開始響應重定向訊息。
FORCE
選項:此時不會與master進行握手(master可能已經不可達),直接從第4步開始進行故障轉移。FORCE
仍然需要大部分masters的授權來進行故障轉移,併為該replica生成新的configuration epochTAKEOVER
選項:有時候需要將一個replica提升為master,而無需叢集的授權同意(如資料中心切換),它會單方面生成一個新的configuration epoch。如果其configuration epoch不是該master例項組中的最大值,則需要增加其configuration epoch,然後將master的所有雜湊槽分配給自己,並向其他節點傳遞新的配置。由於TAKEOVER
無法保證一個replica的配置是最新的,因此要謹慎使用。 -
CLUSTER REPLICATE:
CLUSTER REPLICATE node-id
。將一個節點設定為指定master的replica。如果該節點非replica,則需要滿足如下條件:- 節點沒有任何雜湊槽
- 節點為空,其keyspace中沒有任何keys
-
CLUSTER FORGET :用於移除一個故障節點,參見移除節點
-
READONLY:支援client從replicas讀取資料。可以用
READWRITE
取消該模式。
診斷命令
Slow log
透過Slow log可以檢視超過一定執行時間的命令。注意該執行時間不包括I/O操作,如與client互動的時間,只包含命令執行時間。
slowlog-log-slower-than
:當命令執行時間超過該值時,將其記錄到slow log中slowlog-max-len
:指定了slow log中記錄的最大數目。
檢視slow log的命令如下:
- SLOWLOG GET:
SLOWLOG GET [count]
。預設會展示最新的10條log,可以透過count
調整顯示的log數。其顯示的欄位如下:- 每條log的識別符號(每次+1)
- 記錄的執行命令的unix時間戳
- 執行命令花費的時間,單位微秒
- 命令的引數
- client的IP和地址
- 透過CLIENT SETNAME設定的client的名稱
Latency monitor
Latency monitoring包含如下概念:
- 採集不同時延敏感的程式碼路徑的延遲鉤子
- 記錄不同時間延遲峰值的時間序列
- 從時間序列拉取原始資料的報告引擎
- 可以根據測量來提供可讀性的報告和提示的分析引擎
事件和時間序列
時延峰值是指一個事件的執行時間超過了配置的時延閾值(latency-monitor-threshold)。
監控的不同的程式碼路徑有不同的名稱,成為事件(events)。如command
就是一個用於衡量可能的慢命令執行時延峰值的事件,fast-command
則是監控O(1) 和 O(log N)命令的事件名稱。其他命令則用於監控redis執行的特點操作,如fork
事件。
每個被監控到的時間會關聯一個獨立的時間序列。時間序列的工作方式如下:
- 每當產生時延峰值時,就會將其記錄到合適的時間序列中
- 每個時間序列包含160個元素
- 每個元素由包含一個Unix時間戳表示的時延峰值和一個使用毫秒錶示的事件執行時間。
- 同一秒內如果產生了多個時延峰值,則選擇最大時延。
- 記錄每個元素的最大延遲時間。
支援的event如下:
command
: regular commands.fast-command
: O(1) and O(log N) commands.fork
: the fork(2) system call.rdb-unlink-temp-file
: the unlink(2) system call.aof-fsync-always
: the fsync(2) system call when invoked by the appendfsync allways policy.aof-write
: writing to the AOF - a catchall event for write(2) system calls.aof-write-pending-fsync
: the write(2) system call when there is a pending fsync.aof-write-active-child
: the write(2) system call when there are active child processes.aof-write-alone
: the write(2) system call when no pending fsync and no active child process.aof-fstat
: the fstat(2) system call.aof-rename
: the rename(2) system call for renaming the temporary file after completing BGREWRITEAOF.aof-rewrite-diff-write
: writing the differences accumulated while performing BGREWRITEAOF.active-defrag-cycle
: the active defragmentation cycle.expire-cycle
: the expiration cycle.eviction-cycle
: the eviction cycle.eviction-del
: deletes during the eviction cycle.
啟用latency monitoring
使用如下命令啟用latency monitoring,表示延遲閾值為100ms。預設值為0,表示關閉latency monitoring。
CONFIG SET latency-monitor-threshold 100
檢視latency monitoring:
- LATENCY LATEST :從所有事件返回最新的延遲樣本。報告內容如下:
- 事件名稱
- 最新延遲事件發生的Unix時間戳
- 最新延遲事件的執行時間,單位毫秒
- redis例項啟動以來的最大最新延遲
- LATENCY HISTORY
:返回一個特定時間的時延時間序列 - LATENCY RESET:重置一個或多個事件的時延時間序列
- LATENCY GRAPH
:使用ASCII圖渲染一個事件的時延樣本 - LATENCY DOCTOR:返回一個人類可讀的時延分析報告
監控
redis exporter的監控資料來自redis的info命令,大部分metrics名稱就是在redis原始指標的基礎上加上redis_
字首。主要分為如下幾類:
server
: redis server的一般資訊clients
: Client連線資訊memory
: 記憶體佔用相關的資訊persistence
: RDB 和 AOF 資訊stats
: 一般狀態資訊replication
: Master/replica複製資訊cpu
: CPU佔用資訊commandstats
: Redis 命令狀態latencystats
: Redis 命令延遲百分比分佈統計sentinel
: Redis Sentinel資訊cluster
: Redis Cluster 資訊modules
: Modules 資訊keyspace
: Database相關統計資訊errorstats
: Redis錯誤統計資訊
下面給出各個部分重要的指標:
Server
- redis_uptime_in_seconds: Redis server 啟動後的秒數
- io_threads_active:表示是否啟用I/O多執行緒。透過配置的
io-threads
引數啟用該功能,預設禁用。
Sentinel && Cluster
- redis_sentinel_masters :該Sentinel例項監控的redis master的數目
- redis_cluster_enabled:是否啟用redis Cluster
Client
- redis_connected_clients:連線的客戶端數(不包括來自replicas的連線)
- redis_blocked_clients:pending在阻塞呼叫(
BLPOP
,BRPOP
,BRPOPLPUSH
,BLMOVE
,BZPOPMIN
,BZPOPMAX
)上的客戶端數目 - redis_clients_in_timeout_table:client timeout 表中的clients數目
Mem
-
redis_memory_max_bytes:對應container設定的memory limit,如果沒有設定,則為0。
-
redis_memory_used_bytes:redis使用的總記憶體
-
redis_memory_used_peak_bytes:redis的記憶體使用峰值
-
redis_memory_used_rss_bytes:redis申請的實體記憶體
-
redis_memory_used_startup_bytes
:redis啟動時消費的記憶體 -
redis_memory_used_overhead_bytes
:redis管理其內部資料結構佔用的記憶體 -
redis_memory_used_dataset_bytes
:dataset的大小。redis_memory_used_bytes
-redis_memory_used_overhead_bytes
-
used_memory_dataset_perc
:used_memory_dataset
佔淨記憶體使用的百分比。
碎片整理
理想情況下,used_memory_rss
應該稍微大於used_memory
,如果rss遠大於used,說明可能存在(external)記憶體碎片,可以透過allocator_frag_ratio
和allocator_frag_byte
檢視評估。如果used遠大於rss,說明redis進行了記憶體交換,可能會導致嚴重的延遲。當redis釋放記憶體時,會將記憶體返回到allocator,而allocator可能會或不會將記憶體返回給系統,因此作業系統報告的記憶體使用和redis實際使用的記憶體可能存在偏差,可以透過used_memory_peak
檢查這一點。
-
redis_active_defrag_running:是否正在進行記憶體碎片整理。
-
redis_mem_fragmentation_ratio:等於
redis_memory_used_rss_bytes
/redis_memory_used_bytes
,數值在1 ~ 1.5之間是比較健康的,超過該值,說明記憶體碎片比較多,需要進行記憶體碎片整理。包含allocator_*
指標的記憶體。 -
redis_mem_fragmentation_bytes:
redis_memory_used_rss_bytes
和redis_memory_used_bytes
之間的增量,如果redis_mem_fragmentation_bytes
較小(幾MB),則redis_mem_fragmentation_ratio
大於1.5也並不是問題。 -
redis_allocator_frag_ratio:等於
allocator_active
/allocator_allocated
。表示外部(external)記憶體碎片指標。 -
redis_allocator_frag_bytes:等於
allocator_active
-allocator_allocated
。 -
redis_defrag_hits
:defragmentation程序處理的reallocations(資源再分配)的數目 -
redis_defrag_key_hits
:成功執行碎片整理的keys數目 -
redis_defrag_misses
:defragmentation程序中斷的reallocations的數目 -
redis_defrag_key_misses
:defragmentation程序跳過的keys的數目 -
redis_defrag_hits: 碎片整理過程中重分配的值的數目
-
redis_defrag_misses: 碎片整理過程中未重分配的值的數目
-
redis_defrag_key_hits: 碎片整理中處理的keys的數目
-
redis_defrag_key_misses: 碎片整理中跳過的keys的數目
CPU
- redis_cpu_sys_seconds_total: Redis server佔用的System CPU,包含所有Server程序中的執行緒佔用的user CPU總數 (主執行緒和後臺執行緒)
- redis_cpu_user_seconds_total: Redis server佔用的User CPU,包含所有Server程序中的執行緒佔用的user CPU總數 (主執行緒和後臺執行緒)
redis_cpu_sys_children_seconds_total
: 後臺程序佔用的System CPUredis_cpu_user_children_seconds_total
: 後臺程序佔用的User CPUredis_cpu_sys_main_thread_seconds_total
: Redis server 主程序佔用的System CPUredis_cpu_user_main_thread_seconds_total
: Redis server 主程序程序佔用的User CPU
Persistence
- redis_loading_dump_file:表示是否在載入一個dump檔案
如果正在載入檔案,則還有如下指標(僅限Cli命令列):
loading_start_time
:檔案載入的開始時間loading_total_bytes
:載入的檔案大小
rdb
- redis_rdb_bgsave_in_progress:是否正在進行RDB save操作
- redis_rdb_last_bgsave_status:上一次bgsave的狀態
- redis_rdb_last_bgsave_duration_sec:上一次bgsave的持續時間
- redis_rdb_current_bgsave_duration_sec:正在進行的RDB save操作的持續時間
redis_rdb_last_cow_size_bytes
:上一次RDB save操作中COW使用的記憶體大小redis_rdb_changes_since_last_save
:自上一次dump之後的變動數目
aof
- redis_aof_enabled:是否啟用AOF
- redis_aof_rewrite_in_progress:是否正在進行AOF rewrite操作
- redis_aof_last_rewrite_duration_sec:上一次AOF rewrite操作的持續時間
- redis_aof_current_rewrite_duration_sec:目前正在進行的AOF rewrite操作的持續時間
- redis_aof_last_write_status:上一次AOF rewrite的操作狀態
redis_aof_last_cow_size_bytes
:上一次AOF rewrite操作中COW使用的記憶體大小
Stats
-
redis_connections_received_total:Server接收的連線總數
-
redis_commands_processed_total:Server處理的命令總數
-
redis_net_input_bytes_total:從網路讀取的位元組總數
-
redis_net_output_bytes_total:傳送到網路的位元組總數
-
redis_rejected_connections_total:達到
maxclients
限制而拒絕的連線總數 -
redis_total_reads_processed:處理的讀事件總數
-
redis_total_writes_processed:處理的寫事件總數
-
redis_replica_resyncs_full:full resync的replicas的數目
-
redis_replica_partial_resync_accepted:接收的部分重同步的請求數
-
redis_replica_partial_resync_denied:拒絕的部分重同步的請求數
-
redis_expired_keys_total:key expiration事件總數
-
redis_expired_stale_percentage:裡面過期的keys的百分比
-
redis_evicted_keys_total:達到maxmemory而被驅逐的keys的總數
-
evicted_clients
:Redis 7.0引入,由於達到maxmemory-clients
配置而被驅逐的clients -
redis_keyspace_hits_total:在主目錄中成功找到keys的數目
-
redis_keyspace_misses_total:無法在主目錄中成功找到keys的數目
-
redis_latest_fork_usec:上一次fork操作所用的時間,微秒
-
total_forks
:forks總數 -
acl_access_denied_auth
:認證失敗總數
replication
- redis_instance_info:展示redis例項狀態,如redis版本,模式,role等。
master_failover_state
:是否正在進行故障轉移
replica
redis_master_sync_in_progress
:表示master正在給replica同步資料master_link_status
:到master的鏈路狀態- redis_connected_slaves:連線的replica數
如果正在執行SYNC操作,則還會出現如下資訊(Cli):
master_sync_total_bytes
:master需要傳輸的資料大小,如果大小未知,可能為0master_sync_read_bytes
:已經傳輸的資料大小master_sync_left_bytes
:同步完成前未讀取的資料(如果master_sync_total_bytes
為0,可能為負數)
如果master和replica之前的鏈路斷開,還會出現如下欄位(cli):
master_link_down_since_seconds
:鏈路斷開的時間
效能最佳化
Benchmark
使用redis-benchmark校驗redis的效能
效能引數
io-threads
:redis大部分是單執行緒的,但有一個特定的操作,如UNLINK、慢I/O訪問等操作可以在其他執行緒上執行。預設禁用多執行緒,建議在4 cores以上的機器上啟用該功能,且建議只有真正遇到效能問題時才啟用該功能,該功能可以使redis的效能提升2倍。由於redis會佔用大分部CPU時間,因此建議,如果機器有4core,建議將該引數設定為2或3,如果機器有8 core,建議將該引數設定為6。io-threads-do-reads
:當啟用I/O threads時,只會用執行緒處理寫操作,可以使用該引數來讓執行緒支援讀操作。
注意:通常執行緒讀對效能的提升並不大。無法再執行時透過CONFIG SET設定該引數,且在啟用SSL時,該功能不生效。
診斷延遲問題
checklist
若遇到延遲問題,可以先嚐試如下方式:
- 使用Slog Log檢視慢命令
- 禁用透明大頁功能:
echo never > /sys/kernel/mm/transparent_hugepage/enabled
- 如果使用了虛擬機器,則其本身可能存在延遲因素,使用
./redis-cli --intrinsic-latency 100
測試 - 啟用Latency monitor 功能
測試系統的記憶體延遲
當redis執行在虛擬機器上時,可能因為虛擬機器帶來一定的延遲。下面命令中的"100"參數列示測試的時長。可以看到其內在延遲為0.115 milliseconds (或115 microseconds),比較好。
$ ./redis-cli --intrinsic-latency 100
Max latency so far: 1 microseconds.
Max latency so far: 16 microseconds.
Max latency so far: 50 microseconds.
Max latency so far: 53 microseconds.
Max latency so far: 83 microseconds.
Max latency so far: 115 microseconds.
Redis的單執行緒本質
Redis 使用的大部分是單執行緒設計,它使用單個執行緒來處理所有的client請求,即Redis會在一定時間內處理一個請求,然後依次處理其他請求。
上面說大部分的原因是,從Redis2.4開始可以使用多執行緒來在後臺處理一些慢I/O操作(主要與磁碟I/O相關),但這不會改變Redis使用單執行緒處理所有請求的事實。
慢命令造成的延遲
由於單執行緒的性質,如果Redis無法及時處理一個請求,其他clients就必須等待該請求處理結束。redis可以在很短時間內處理像GET
、SET
、LPUSH
這樣的命令,但像 SORT
, LREM
, SUNION
這樣的命令可能會操作很多元素,導致操作時間過長。
可以通[slow log](#Slow log)監控慢命令。
重要:一個常見的導致慢命令的原因是使用了KEYS
命令。
fork造成的延遲
為了在後臺生成RDB檔案,或在啟用AOF時重寫AOF檔案,redis需要fork後臺程序,而fork操作(主執行緒)會導致延遲。可以透過info命令的latest_fork_usec
檢視最近依次fork使用的時間。
透明大頁造成的延遲
當一個Linux核心啟用透明大頁時,redis會在fork
呼叫之後產生較大的延遲(為了持久化到磁碟)。大頁會導致如下問題:
- 呼叫fork時,會建立兩個共享大頁的程序
- 在一個繁忙的例項中,執行一些事件迴圈將導致命令關聯上千個頁,導致COW整個程序記憶體
- 這會導致較大的延遲和記憶體使用
echo never > /sys/kernel/mm/transparent_hugepage/enabled
swaping造成的延遲
檢視redis程序的swap資訊的方式如下:
- 獲取redis例項PID:
redis-cli info | grep process_id
- 進入程序的
/proc
檔案系統:$ cd /proc/<redis_pid>
- 檢視檔案中Swap欄位的值:
$ cat smaps | grep 'Swap:'
。如果全部為0kB或偶爾出現4k,則表示一切正常。 - 如果Swap欄位值大於4k,則需要對比實際大小和交換的大小:
$ cat smaps | egrep '^(Swap|Size)'
,如果實際值遠大於交換大值,則也沒問題。
AOF和磁碟I/O導致的延遲
AOF使用兩個系統呼叫來完成其工作:一個是write(2),應用將資料寫入AOF檔案,另一個是fdatasync(2),用於將核心檔案緩衝重新整理到磁碟。
如果系統正在進行sync或在輸出緩衝滿,且核心需要刷入磁碟來接收新的寫入時會導致write(2)阻塞。
fdatasync(2)帶來的延遲更加嚴重,很多核心和檔案系統在呼叫該方法時,可能會花費數毫秒到幾秒的時間(特別是當其他程序正在程序I/O操作時)。為此,從Redis 2.4開始,會在不同執行緒中呼叫 fdatasync(2) 。
AOF的appendfsync配置選項對效能的影響如下:
- 當設定為no時,redis不會執行fsync操作,只有write(2)會帶來延遲。此模式不常用。
- 如果設定為everysec,redis每秒會執行一次fsync。redis會使用不同的執行緒來執行fsync,如果正在執行fsync,則redis會透過緩衝來推遲執行write(2)(最大2s)。但如果fysnc執行時間過長,會導致在fysnc的同時執行write(2),造成延遲
- 當設定為always,則會在每次write操作之後(向client響應ok之前)執行一次fsync,該模式效能很低,通常建議使用高速磁碟。
可以使用如下方式檢視redis中fsync和wirte的延遲。
sudo strace -p $(pidof redis-server) -T -e trace=fdatasync,write -f
由於write(2)包含很多與磁碟I/O無關的資料(如向client sockets寫入的資料),可以使用如下命令僅展示慢的系統呼叫:
sudo strace -f -p $(pidof redis-server) -T -e trace=fdatasync,write 2>&1 | grep -v '0.0' | grep -v unfinished
Redis的記憶體
-
從Redis 2.2 開始,為了最佳化記憶體空間,redis將很多資料型別的最大數目設定為固定值。如果資料超過定義的上限,則redis會將其轉換為普通編碼。如:
#Redis <= 6.2 hash-max-ziplist-entries 512 hash-max-ziplist-value 64 zset-max-ziplist-entries 128 zset-max-ziplist-value 64 set-max-intset-entries 512
-
在移除掉一些keys時,Redis並不總是會將這部分記憶體釋放到OS。在為redis配置記憶體時,應該根據峰值記憶體,而不是平均記憶體。
-
如果不設定
maxmemory
,則redis會持續申請其看到的記憶體,直至記憶體全部分redis佔用。因此建議配置maxmemory
。
Tips
redis啟動失敗
如果redis啟動失敗,且沒有有用的日誌,可以直接執行redis啟動命令,檢視執行結果:
$ /usr/bin/redis-server /etc/redis/sentinel.conf --daemonize no --supervised systemd
*** FATAL CONFIG FILE ERROR (Redis 6.0.10) ***
Reading the configuration file, at line 8
>>> 'sentinel myid 922e4ec063ea8d860811f575f08e5ac696073f52'
sentinel directive while not in sentinel mode
Redis升級
為支援部分重同步,參見重啟和故障轉移下的部分同步
如何將一個replica提升為master
非sentinel和cluster
-
首先斷開replicas和master的連線:
redis-cli -h <replica_host> -p <replica_port> replicaof no one
-
校驗該replica是否被提升為master。使用
info replication
命令檢視:redis-cli -h <new_master_host> -p <new_master_port> info replication
-
在每個replica上執行
replicaof <new_master_host> <new_master_port>
命令,使其連線到新的master上:redis-cli -h <slave_host> -p <slave_port> replicaof <new_master_host> <new_master_port>
AOF檔案截斷錯誤
如果伺服器在寫AOF檔案時崩潰,有可能導致最後一條命令被截斷,這樣在redis載入AOF檔案時會出現如下錯誤:
* Reading RDB preamble from AOF file...
* Reading the remaining AOF tail...
# !!! Warning: short read while loading the AOF file !!!
# !!! Truncating the AOF at offset 439 !!!
# AOF loaded anyway because aof-load-truncated is enabled
可以使用如下命令修復並重啟redis:
$ redis-check-aof --fix <filename>
使用RDB進行備份和恢復
-
備份:呼叫
BGSAVE
建立RDB備份檔案,透過info persistence
中的rdb_bgsave_in_progress
和rdb_last_bgsave_status
檢視BGSAVE
的狀態 -
恢復:
-
恢復前需要確保redis server down,這樣就會在第一臺啟動的節點(master)上進行恢復
redis-cli shutdown
-
在配置檔案中禁用redis的AOF功能
appendonly no
-
透過
CONFIG get dir
獲取redis存放備份檔案的目錄。停止redis,將備份檔案放到該目錄下,修改備份檔案許可權:chmod 660 /home/redis/dump.rdb
-
重啟redis。如果啟用了AOF,則重新啟用AOF功能
-
如何在使用dump.rdb snapshot的同時啟用AOF
Redis >= 2.2
- 備份最新的
dump.rdb
檔案 - 啟用AOF:
redis-cli config set appendonly yes
- 如果要禁用rdb:
redis-cli config set save ""
- 確保寫命令追加到了AOF檔案
- 重要:更新
redis.conf
檔案,匹配上述操作,否則重啟redis會導致配置丟失,進而導致資料丟失。 - 透過
INFO persistence
命令等待AOF結束:aof_rewrite_in_progress
和aof_rewrite_scheduled
為0,且aof_last_bgrewrite_status
為ok
。若一切正常,重啟redis服務 - 重啟redis之後,校驗資料庫記憶體是否與之前的匹配
sentinel中的網路隔離
對於如下場景(M: master,S: sentinel,R: replica,C:client)
+----+
| M1 |
| S1 |
+----+
|
+----+ | +----+
| R2 |----+----| R3 |
| S2 | | S3 |
+----+ +----+
Configuration: quorum = 2
如果老的master出現網路隔離,此時可能會導致出現2個master節點,此時由於client(C1)仍然向M1寫資料,當網路恢復之後,老的master會變為新master的replica,導致資料丟失。
+----+
| M1 |
| S1 | <- C1 (writes will be lost)
+----+
|
/
/
+------+ | +----+
| [M2] |----+----| R3 |
| S2 | | S3 |
+------+ +----+
可以透過在master中配置如下引數緩解上述問題,即如果在10s之內,master無法向至少1個replica寫資料,就會變為unavailable狀態,待網路恢復之後,client就可以獲取到有效的配置。但這種方式意味著,如果兩個replicas down,將導致master無法處理寫請求
min-replicas-to-write 1
min-replicas-max-lag 10
Redis遷移到k8s
redis是單執行緒的,它傾向於使用具有較大快取的CPU,而不是具有更多cores的CPU。而k8s一般使用的是CPU share方式,從多個CPU上獲取的CPU執行時間,因此會引入程序上下文切換的問題。