第四節:Redis數據持久化機制(備份恢復)、緩存淘汰策略、主從同步原理、常見規范與優化詳解


一. 數據持久化

 1. 含義

 Redis 提供了 RDB 和 AOF 兩種持久化方式,默認開啟的是RDB,如果需要AOF,需要手動修改配置文件進行開啟。

 RDB:是一種對Redis存在內存中的數據周期性的持久化機制,將內存中的數據以快照的形式硬盤,實質上是fork了一個子進程在執行數據存儲,采用的是二進制壓縮的存儲模式。

如圖:

 AOF:是以文本日志的形式記錄Redis的每一個寫入、刪除請求(查詢請求不處理),它是以追加的方式(append-only)寫入,沒有磁盤尋址的開銷,所以寫入速度非常快(類似mysql的binlog)。

如圖:

2. 優缺點對比

(1). RDB優點

  生成多個數據文件,每個文件代表某一時刻redis的數據,而且RDB是fork一個子進程做持久化,所以RDB對Redis性能影響非常小,而且他在數據恢復的時候也比AOF快。

(2). RDB缺點

 A. RDB通常是每隔一段時間查看key變化數量從而決定是否持久化一次數據,比如5min分鍾1次吧,如果這期間宕機或斷電,丟失的就是這5min的數據了,而AOF最多丟失一秒。

 B. RDB如果生成的快照文件非常大,客戶端會卡頓 幾毫秒或者幾秒,恰逢當時是高並發期間,絕壁出問題!

(3). AOF優點

 A. AOF通常設置1s通過后台線程去追加一次,所以最多丟失1s數據,數據的完整性比RDB要高。

 B. AOF是appendonly追加的方式,少了磁盤尋址的開銷,所以寫入性能很高,文件也不易損壞。

 C. 可以對數據誤刪進行緊急恢復。

(4). AOF缺點

 A.  同樣的數據,AOF文件要比RDB文件大的多。

 B.  開啟AOF存儲后,整個Redis的QPS要下降,但依舊比關系型數據庫要高。

3. 適用場景及如何選擇

  RDB適合做冷備,AOF適合做熱備。

  在生產環境中,通常是冷備和熱備一起上,RDB中bgsave做全量持久化,AOF做增量持久化,在redis重啟的時候,使用rdb持久化的文件重新構建內存,再使用aof恢復最近操作數據,從而實現完整的服務到重啟之前的狀態。

單獨用RDB你會丟失很多數據,你單獨用AOF,你數據恢復沒RDB來的快,所以在生產環境中,第一時間用RDB恢復,然后AOF做數據補全,冷備熱備一起上,才是互聯網時代一個高健壯性系統的王道。

4. rdb持久化中save和bgsave的區別

 save和 bgsave 兩個命令都會調用 rdbSave 函數,但它們調用的方式各有不同:

(1). save 直接調用 rdbSave ,阻塞 Redis 主進程,直到保存完成為止,在主進程阻塞期間,服務器不能處理客戶端的任何請求。

(2). bgsave 則 fork 出一個子進程,子進程負責調用 rdbSave ,並在保存完成之后向主進程發送信號,通知保存已完成,Redis 服務器在 bgsave 執行期間仍然可以繼續處理客戶端的請求。

(bgsave的本質就是 fork 和 cow,fork是指redis通過創建子進程來進行bgsave操作,cow指的是copy on write,子進程創建后,父子進程共享數據段,父進程繼續提供讀寫服務,寫進的頁面數據會逐漸和子進程分離開來)

總結:save是阻塞方式的;bgsave是非阻塞方式的。

 

5. 相關代碼配置

#一. RDB存儲
#1. 下面配置為默認配置,默認就是開啟的,在一定的間隔時間中,檢測key的變化情況,然后持久化數據
save 900 1           #900s后至少1個key發生變化則進行存儲
save 300 10         #300s后至少10個key發生變化則進行存儲
save 60 10000 #60s后至少10000個key發生變化則進行存儲
#2. rdb文件的存儲路徑(默認當前目錄下,文件名為dump.rdb)
dbfilename dump.rdb
dir ./
#二. AOF存儲 #1.默認是關閉的,日志記錄的方式,可以記錄每一條命令的操作。可以每一次命令操作后,持久化數據,啟用的話通常使用每隔一秒持久化一次的策略 appendonly no(默認no)
--> appendonly yes (開啟aof) # appendfsync always #每一次操作都進行持久化 appendfsync everysec #每隔一秒進行一次持久化 # appendfsync no # 不進行持久化
#2. aof文件路徑 (默認為當前目錄下,文件名為 appendonly.aof)
appendfilename "appendonly.aof"
dir ./ (同上)
#3. 控制觸發自動重寫機制頻率
# auto-aof-rewrite-min-size 64mb //aof文件至少要達到64M才會自動重寫,文件太小恢復速度本來就很快,重寫的意義不大
# auto-aof-rewrite-percentage 100 //aof文件自上一次重寫后文件大小增長了100%則再次觸發重寫


#三. 混合持久化
#1. 開啟混合持久化配置 (5.0版本默認就是yes)
aof-use-rdb-preamble yes
#2. rdb和aof自身的配置也都需要開啟

6. aof持久化文件詳解

(1). aof文件長什么樣?

 通常我們配置aof持久化為everysec,即為每秒寫一次,這里我們先把混合持久化關掉 【aof-use-rdb-preamble no來進行演示。

演示步驟:先清空所有數據,然后把appendonly.aof文件刪掉(需要重啟一下),多次對name1和name2設置不同的值,如下:

#先清空所有數據,然后吧
flushdb

#對name1多次設置值
set name1 ypf1
set name1 ypf11

#對name2多次設置值
set name2 ypf2
set name2 ypf22
set name2 ypf222

aof文件中的內容如下:每條set指令都會aof文件中追加記錄。如下圖:

 剖析:

  每條指令都會追加記錄,但對於同一個key,實際上我們只需要最新值,比如name2,我們需要的是ypf222,前面的記錄用處不大,而且很占空間,所以redis內部有aof重寫機制(定期根據內存的最新數據生成aof文件),來處理這個問題。

(2). aof重寫機制

A. 自動重寫

 AOF文件里可能有太多沒用指令,所以AOF會定期根據內存的最新數據生成aof文件。

 通過下面的配置,可以控制aof自動重寫的頻率

#3. 控制觸發自動重寫機制頻率
# auto-aof-rewrite-min-size 64mb //aof文件至少要達到64M才會自動重寫,文件太小恢復速度本來就很快,重寫的意義不大
# auto-aof-rewrite-percentage 100 //aof文件自上一次重寫后文件大小增長了100%則再次觸發重寫 

B. 手動重寫

 運行指令【bgrewriteaof】,后台會fork一個子進程去重寫,對於redis正常進程操作影響很小,下面演示一下手動重寫后aof文件中的內容。

7. 混合持久化詳解

(1). 概念

 重啟 Redis 時,我們很少使用 RDB來恢復內存狀態,因為會丟失大量數據。我們通常使用 AOF 日志恢復,但是使用 AOF 日志性能相對 RDB來說要慢很多,這樣在 Redis 實例很大的情況下,啟動需要花費很長的時間。

 Redis 4.0 為了解決這個問題,帶來了一個新的持久化選項——混合持久化。

(2). 原理

 如果開啟了混合持久化,AOF在重寫時,不再是單純將內存數據轉換為RESP命令寫入AOF文件,而是將重寫這一刻之前的內存做RDB快照處理(重寫期間執行的指令和之后的指令仍然是轉換成resp指令吸入aof文件),並且將RDB快照內容和增量的AOF修改內存數據的命令存在一起,都寫入新的AOF文件,新的文件一開始不叫appendonly.aof,等到重寫完新的AOF文件才會進行改名,會覆蓋原有的AOF文件,完成新舊兩個AOF文件的替換。

 於是在 Redis 重啟的時候,可以先加載 RDB 的內容,然后再重放增量 AOF 日志就可以完全替代之前的AOF 全量文件重放,因此重啟效率大幅得到提升。

(3). 演示

 A. 開啟混合持久化機制 【aof-use-rdb-preamble yes】

 B. 運行指令【bgrewriteaof】手動進行aof重寫(前提內存中有內容),此時查看aof文件中的內容,類似亂碼的東西,就是rdb快照處理

 

 C. 再任意執行幾個set指令,然后查看aof文件中內容。

 

8. 數據備份恢復

(前言: 上面的代碼配置RDB和AOF都是自動進行持久化存儲的,下面我們進行介紹手動進行備份恢復)

(1). RDB模式

 比如當沒有達到RDB持久化的條件,而此時我又想把內存中的數據持久化到硬盤上,這個時候可以運行bgsave指令進行后台持久化。(save命令也可以,但會阻塞主線程)

 (下面步驟在關閉aof存儲的模式下進行,即appendonly no,rdb使用默認配置即可,為了演示效果,可以臨時刪除dump.rdb,實際是不需要的,數據會覆蓋的)

A. 運行 bgsave】 指令 ,默認配置下會在redis的根目錄下生成一個名為 dump.rdb的數據文件。

B.  可以通過指令 【config get dir】獲取數據存放的路徑,【config get dbfilename】獲取生成的rdb文件名稱。

 C. 關閉服務./redis-cli -a 123456 shutdown,重新啟動redis服務【./redis-server redis.conf】,將自動初始化rdb中的內容到內存(因為默認數據存儲目錄和啟動目錄是一致的)。

 

(2). AOF模式 

(下面步驟在開啟aof存儲的模式下進行,即appendonly yes,關閉rdb存儲,即 3個save全部注釋掉,然后 sava "" )

 save ""

#save 900 1
#save 300 10
#save 60 10000
View Code

A. 運行 bgrewriteaof】 指令 ,默認配置下會在redis的根目錄下生成一個名為 appendonly.aof的數據文件。

B. 關閉服務./redis-cli -a 123456 shutdown,重新啟動redis服務【./redis-server redis.conf】,將自動初始化aof中的內容到內存(因為默認數據存儲目錄和啟動目錄是一致的)。

(3). RDB和AOF模式同時開啟

(默認redis里沒有任何數據)

A. 先寫入 name1 和 name2,然后運行【bgsave】,然后寫入name3,運行【bgrewriteaof】指令,然后寫入name4

B. 關閉服務【./redis-cli -a 123456 shutdown】,重新啟動redis服務【./redis-server redis.conf】發現內存中的數據為 name1-4都有,說明同時開啟的時候,初始化內存數據的時候,使用的是AOF模式

 

PS: 

1. 只開啟rdb, 重啟的時候加載rdb文件進行恢復數據。

2. 只開啟aof,重啟的時候加載aof文件進行恢復數據。

3. 同時開始rdb和aof,重啟的時候依舊是使用aof文件進行恢復數據。

 

二. 緩存淘汰策略

1. 背景

 Redis對於過期鍵有三種清除策略:

 (1). 被動刪除:設置一個key過期時間后,當該key過期了,不會馬上從內存中刪除,當對其 讀/寫一個已經過期的key時,會觸發惰性刪除策略,直接刪除掉這個過期key .

 (2). 主動刪除:由於惰性刪除策略無法保證冷數據被及時刪掉,所以Redis會定期主動淘汰一批已過期的key .

 (3). 緩存淘汰策略:當前已用內存超過maxmemory限定時,觸發主動清理策略當REDIS運行在主從模式時,只有主結點才會執行被動和主動這兩種過期刪除策略,然后把刪除操作”del key”同步到從結點。(大量的key定期沒有刪除,而且也沒惰性刪除(查詢的時候刪除),這個時候要適用緩存淘汰策略了。)

2. 淘汰策略種類 

 當前已用內存超過redis.cnf配置文件中  # maxmemory <bytes>   限定時,會觸發主動清理策略根據自身業務類型,選好redis.cnf配置文件中 maxmemory-policy (最大內存淘汰策略),設置好過期時間。如果不設置最大內存, 當 Redis 內存超出物理內存限制時,內存的數據會開始和磁盤產生頻繁的交換 (swap),會讓 Redis 的性能急劇下降。

redis5.0相關配置如下:

############################## MEMORY MANAGEMENT ################################

# Set a memory usage limit to the specified amount of bytes.
# When the memory limit is reached Redis will try to remove keys
# according to the eviction policy selected (see maxmemory-policy).
#
# If Redis can't remove keys according to the policy, or if the policy is
# set to 'noeviction', Redis will start to reply with errors to commands
# that would use more memory, like SET, LPUSH, and so on, and will continue
# to reply to read-only commands like GET.
#
# This option is usually useful when using Redis as an LRU or LFU cache, or to
# set a hard memory limit for an instance (using the 'noeviction' policy).
#
# WARNING: If you have replicas attached to an instance with maxmemory on,
# the size of the output buffers needed to feed the replicas are subtracted
# from the used memory count, so that network problems / resyncs will
# not trigger a loop where keys are evicted, and in turn the output
# buffer of replicas is full with DELs of keys evicted triggering the deletion
# of more keys, and so forth until the database is completely emptied.
#
# In short... if you have replicas attached it is suggested that you set a lower
# limit for maxmemory so that there is some free RAM on the system for replica
# output buffers (but this is not needed if the policy is 'noeviction').
#
# maxmemory <bytes>

# MAXMEMORY POLICY: how Redis will select what to remove when maxmemory
# is reached. You can select among five behaviors:
#
# volatile-lru -> Evict using approximated LRU among the keys with an expire set.
# allkeys-lru -> Evict any key using approximated LRU.
# volatile-lfu -> Evict using approximated LFU among the keys with an expire set.
# allkeys-lfu -> Evict any key using approximated LFU.
# volatile-random -> Remove a random key among the ones with an expire set.
# allkeys-random -> Remove a random key, any key.
# volatile-ttl -> Remove the key with the nearest expire time (minor TTL)
# noeviction -> Don't evict anything, just return an error on write operations.
#
# LRU means Least Recently Used
# LFU means Least Frequently Used
#
# Both LRU, LFU and volatile-ttl are implemented using approximated
# randomized algorithms.
#
# Note: with any of the above policies, Redis will return an error on write
#       operations, when there are no suitable keys for eviction.
#
#       At the date of writing these commands are: set setnx setex append
#       incr decr rpush lpush rpushx lpushx linsert lset rpoplpush sadd
#       sinter sinterstore sunion sunionstore sdiff sdiffstore zadd zincrby
#       zunionstore zinterstore hset hsetnx hmset hincrby incrby decrby
#       getset mset msetnx exec sort
#
# The default is:
#
# maxmemory-policy noeviction

# LRU, LFU and minimal TTL algorithms are not precise algorithms but approximated
# algorithms (in order to save memory), so you can tune it for speed or
# accuracy. For default Redis will check five keys and pick the one that was
# used less recently, you can change the sample size using the following
# configuration directive.
#
# The default of 5 produces good enough results. 10 Approximates very closely
# true LRU but costs more CPU. 3 is faster but not very accurate.
#
# maxmemory-samples 5

# Starting from Redis 5, by default a replica will ignore its maxmemory setting
# (unless it is promoted to master after a failover or manually). It means
# that the eviction of keys will be just handled by the master, sending the
# DEL commands to the replica as keys evict in the master side.
#
# This behavior ensures that masters and replicas stay consistent, and is usually
# what you want, however if your replica is writable, or you want the replica to have
# a different memory setting, and you are sure all the writes performed to the
# replica are idempotent, then you may change this default (but be sure to understand
# what you are doing).
#
# Note that since the replica by default does not evict, it may end using more
# memory than the one set via maxmemory (there are certain buffers that may
# be larger on the replica, or data structures may sometimes take more memory and so
# forth). So make sure you monitor your replicas and make sure they have enough
# memory to never hit a real out-of-memory condition before the master hits
# the configured maxmemory setting.
#
# replica-ignore-maxmemory yes
View Code

配置說明:

(1). LRU means Least Recently Used:  最近最少使用

(2). LFU means Least Frequently Used: 最不經常使用(表示按最近的訪問頻率進行淘汰,它比 LRU 更加精准地表示了一個 key 被訪問的熱度)

volatile-lru:超過最大內存后,在過期鍵中使用lru算法進行key的剔除,保證不過期數據不被刪除,但是可能會出現OOM問題。
allkeys-lru:超過最大內存后,在過期鍵中使用lru算法進行key的剔除,保證不過期數據不被刪除,但是可能會出現OOM問題。

volatile-lfu: 超過最大內存后,在過期鍵中使用lfu算法進行key的剔除,保證不過期數據不被刪除,但是可能會出現OOM問題。
allkeys-lfu : 超過最大內存后,在過期鍵中使用lfu算法進行key的剔除,保證不過期數據不被刪除,但是可能會出現OOM問題。

allkeys-random:隨機刪除所有鍵,直到騰出足夠空間為止。
volatile-random:  隨機刪除過期鍵,直到騰出足夠空間為止。

volatile-ttl:根據鍵值對象的ttl屬性,刪除最近將要過期數據,如果沒有,回退到noeviction策略。
noeviction:不會剔除任何數據,拒絕所有寫入操作並返回客戶端錯誤信息"(error)OOM command not allowed when used memory",此時Redis只響應讀操作。

PS: 位於上述配置文件的位置,默認配置的是noeviction

# maxmemory-policy noeviction

 

 

三. 主從同步原理

1. 主從的背景

 這里的主從架構是redis的第一代架構模式,即一個master對應多個slave,它的出現主要是為了實現讀寫分離,讓主節點之負責寫,從節點負責讀的請求,從而分攤壓力。

 后續的架構,無論是哨兵還是cluster,都是在主從架構的基礎上進行改進和升級的。

2. 主從同步原理

(1). 全量復制

 A. slave第一次啟動時,連接master,發送PSYNC命令,

 B. master收到psync命令后,會執行bgsave命令進行全量復制生成rdb快照文件,持久化期間,所有寫命令都被緩存在內存中

 C. master bgsave執行完畢,向slave發送rdb文件

 D. slave收到rdb文件,刪掉之前所有舊數據,開始載入rdb文件

 E. rdb文件同步結束之后,master將之前緩沖區中命令發送給slave。 

 F. 此后 master 每執行一個寫命令,就向slave發送相同的寫命令。

注:

 (1). 當master與slave之間的連接由於某些原因而斷開時,slave能夠自動重連Master,如果master收到了多個slave並發連接請求,它只會進行一次持久化,而不是一個連接一次,然后再把這一份持久化的數據發送給多個並發連接的slave。

 (2). 當master和slave斷開重連后,一般都會對整份數據進行復制。但從redis2.8版本開始,master和slave斷開重連后支持部分復制

流程圖如下:

(2). 增量復制(部分復制)

 如果出現網絡閃斷或者命令丟失等異常情況,從節點之前保存了自身已復制的偏移量和主節點的運行ID,主節點會根據偏移量把復制積壓緩沖區里的數據發送給從節點,保證主從復制進入正常狀態。

詳細說明:

從2.8版本開始,slave與master能夠在網絡連接斷開重連后只進行部分數據復制。

 master會在其內存中創建一個復制數據用的緩存隊列,緩存最近一段時間的數據,master和它所有的slave都維護了復制的數據下標offset(偏移量)和master的進程id,因此,當網絡連接斷開后,slave會請求master繼續進行未完成的復制,從所記錄的數據下標開始。如果master進程id變化了,或者從節點數據下標offset太舊,已經不在master的緩存隊列里了,那么將會進行一次全量數據的復制。

流程圖如下:

3. 同步過程中宕機怎么辦?

 (1).  會自動重連的,自動補充缺少的數據。

 (2).  如同上面主從同步原理所說的,master生成rdb文件期間,所有的寫命令都被緩存在內存中,這些命令並不會丟失。 

 

四. 常見規范和優化

1. key的命名設計

(1). 可讀性和可管理性

 以業務名(或數據庫名)為前綴(防止key沖突),用冒號分隔,比如業務名:表名:id

trade:order:1

(2). 簡潔性

 保證語義的前提下,控制key的長度,當key較多時,內存占用也不容忽視,例如:

user:{uid}:friends:messages:{mid} 簡化為 u:{uid}:fr:m:{mid}

(3). 不要包含特殊字符

 反例:包含空格、換行、單雙引號以及其他轉義字符

2. 命令的使用

(1).O(N)命令關注N的數量

 例如hgetall、lrange、smembers、zrange、sinter等並非不能使用,但是需要明確N的值。有遍歷的需求可以使用hscan、sscan、zscan代替。

(2).禁用命令

 禁止線上使用keys、flushall、flushdb等,通過redis的rename機制禁掉命令,或者使用scan的方式漸進式處理。

(3).合理使用select

 redis的多數據庫較弱,使用數字進行區分,很多客戶端支持較差,同時多業務用多數據庫實際還是單線程處理,會有干擾。

(4).使用批量操作提高效率

 原生命令:例如mget、mset。

 非原生命令:可以使用pipeline提高效率。

注:

 1. 原生是原子操作,pipeline是非原子操作。

 2. pipeline可以打包不同的命令,原生做不到

 3. pipeline需要客戶端和服務端同時支持。

(5). Redis事務功能較弱,不建議過多使用,可以用lua替代

 

3. 線程池最大連接數設計

 

 4. 線程池事先預熱

 

 

 

 

 

!

  • 作       者 : Yaopengfei(姚鵬飛)
  • 博客地址 : http://www.cnblogs.com/yaopengfei/
  • 聲     明1 : 如有錯誤,歡迎討論,請勿謾罵^_^。
  • 聲     明2 : 原創博客請在轉載時保留原文鏈接或在文章開頭加上本人博客地址,否則保留追究法律責任的權利。
 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM