一. 數據持久化
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
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
配置說明:
(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 : 原創博客請在轉載時保留原文鏈接或在文章開頭加上本人博客地址,否則保留追究法律責任的權利。