Redis-基礎
- 2021 年 8 月 9 日
- 筆記
- Middleware
1. 簡介
redis是一個key-value存儲系統。與memcached一樣,為了保證效率,數據都是快取在記憶體中。區別的是redis會周期性的把更新的數據寫入磁碟或者把修改操作寫入追加的記錄文件,並且在此基礎上實現了master-slave同步。
1.1 數據結構
Redis可以存儲鍵與5種不同數據結構類型之間的映射,這5種數據結構類型分別為String(字元串)、List(列表)、Set(集合)、Hash(散列)和 Zset(有序集合)。
這些數據類型都支援push/pop、add/remove及取交集並集和差集及更豐富的操作,而且這些操作都是原子性的。在此基礎上,redis支援各種不同方式的排序。
1.2 為什麼快
- 完全基於記憶體,絕大部分請求是純粹的記憶體操作,非常快速。數據存在記憶體中,類似於HashMap,HashMap的優勢就是查找和操作的時間複雜度都是O(1);
- 數據結構簡單,對數據操作也簡單,Redis中的數據結構是專門進行設計的;
- 採用單進程單執行緒(這也解釋為什麼redis可以處理高並發問題),避免了不必要的上下文切換和競爭條件,也不存在多進程或者多執行緒導致的切換而消耗 CPU,不用去考慮各種鎖的問題,不存在加鎖釋放鎖操作,沒有因為可能出現死鎖而導致的性能消耗;
- 使用多路I/O復用模型;
- 使用底層模型不同,它們之間底層實現方式以及與客戶端之間通訊的應用協議不一樣,Redis直接自己構建了VM 機制 ,因為一般的系統調用系統函數的話,會浪費一定的時間去移動和請求;
2. 安裝
redis 安裝很簡單,只要下載好,直接運行即可。官方下載地址
這裡主要講下docker-compose安裝
docker-compose.yaml
配置資訊如下
version: '3.7'
services:
redis:
# 容器名稱
container_name: redis
# 鏡像 默認redis:latest
image: redis
# 自啟動
restart: always
# 覆蓋dockerfile中的cmd名稱
# 啟動redis並開啟AOF模式
command: ["redis-server", "--appendonly", "yes"]
# 宿主機埠6379:容器默認埠6379
ports:
- "6379:6379"
# 文件映射
volumes:
- ./data:/data
我們可以使用redis桌面工具Redis Desktop Manager
連接安裝啟動好的redis server進行測試。
創建一個新的鏈接,因為安裝的時候並沒有開啟認證,所以僅需要輸入地址和埠即可。
3. 常用命令
由於redis命令太多了 這裡主要講一下常用的命令。
我們可以隨便進一個database進行練習
3.1 keys
序號 | 命令 | 描述 |
---|---|---|
1 | DEL key | 該命令用於在 key 存在時刪除 key。 |
2 | EXISTS key | 檢查給定 key 是否存在。 |
3 | EXPIRE key seconds | 為給定 key 設置過期時間,以秒計。 |
4 | EXPIREAT key timestamp | 為 key 設置過期時間。 不同在於 EXPIREAT 命令接受的時間參數是 UNIX 時間戳(unix timestamp)。 |
5 | PEXPIRE key milliseconds | 設置 key 的過期時間以毫秒計。 |
6 | PEXPIREAT key milliseconds-timestamp | 設置 key 過期時間的時間戳(unix timestamp) 以毫秒計7 |
7 | KEYS pattern | 查找所有符合給定模式( pattern)的 key 。 |
8 | MOVE key db | 將當前資料庫的 key 移動到給定的資料庫 db 當中。 |
9 | PERSIST key | 移除 key 的過期時間,key 將持久保持。 |
10 | PTTL key | 以毫秒為單位返回 key 的剩餘的過期時間。 |
11 | TTL key | 以秒為單位,返回給定 key 的剩餘生存時間(TTL, time to live)。 |
12 | RENAME key newkey | 修改 key 的名稱 |
13 | TYPE key | 返回 key 所儲存的值的類型。 |
3.2 string
序號 | 命令 | 描述 |
---|---|---|
1 | SET key value | 設置指定 key 的值 |
2 | GET key | 獲取指定 key 的值。 |
3 | GETRANGE key start end | 返回 key 中字元串值的子字元 |
4 | GETSET key value | 將給定 key 的值設為 value ,並返回 key 的舊值(old value)。 |
5 | MGET key1 key2.. | 獲取所有(一個或多個)給定 key 的值。 |
6 | SETEX key seconds value | 將值 value 關聯到 key ,並將 key 的過期時間設為 seconds (以秒為單位)。 |
7 | SETNX key value | 只有在 key 不存在時設置 key 的值。 |
8 | STRLEN key | 返回 key 所儲存的字元串值的長度。 |
9 | MSET key value [key value …] | 同時設置一個或多個 key-value 對。 |
10 | PSETEX key milliseconds value | 這個命令和 SETEX 命令相似,但它以毫秒為單位設置 key 的生存時間,而不是像 SETEX 命令那樣,以秒為單位。 |
11 | INCR key | 將 key 中儲存的數字值增一。 |
12 | INCRBY key increment | 將 key 所儲存的值加上給定的增量值(increment)。 |
13 | INCRBYFLOAT key increment | 將 key 所儲存的值加上給定的浮點增量值(increment) 。 |
14 | DECR key | 將 key 中儲存的數字值減一。 |
15 | DECRBY key decrement | key 所儲存的值減去給定的減量值(decrement) 。 |
3.3 hash
序號 | 命令 | 描述 |
---|---|---|
1 | HDEL key field1 [field2] | 刪除一個或多個哈希表欄位 |
2 | HEXISTS key field | 查看哈希表 key 中,指定的欄位是否存在。 |
3 | HGET key field | 獲取存儲在哈希表中指定欄位的值。 |
4 | HGETALL key | 獲取在哈希表中指定 key 的所有欄位和對應的值 |
5 | HINCRBY key field increment | 為哈希表 key 中的指定欄位的整數值加上增量 increment 。 |
6 | HINCRBYFLOAT key field increment | 為哈希表 key 中的指定欄位的浮點數值加上增量 increment 。 |
7 | HKEYS key | 獲取所有哈希表中的欄位 |
8 | HLEN key | 獲取哈希表中欄位的數量 |
9 | HMGET key field1 [field2] | 獲取所有給定欄位的值 |
10 | HMSET key field1 value1 [field2 value2 ] | 同時將多個 field-value (域-值)對設置到哈希表 key 中。 |
11 | HSET key field value | 將哈希表 key 中的欄位 field 的值設為 value 。 |
12 | HSETNX key field value | 只有在欄位 field 不存在時,設置哈希表欄位的值。 |
13 | HVALS key | 獲取哈希表中所有值。 |
3.4 list
序號 | 命令及描述 | 描述 |
---|---|---|
1 | BLPOP key1 [key2 ] timeout | 移出並獲取列表的第一個元素, 如果列表沒有元素會阻塞列表直到等待超時或發現可彈出元素為止。 |
2 | BRPOP key1 [key2 ] timeout | 移出並獲取列表的最後一個元素, 如果列表沒有元素會阻塞列表直到等待超時或發現可彈出元素為止。 |
3 | LINDEX key index] | 通過索引獲取列表中的元素 |
4 | LINSERT key BEFORE|AFTER pivot value | 在列表的元素前或者後插入元素 |
5 | LLEN key | 獲取列表長度 |
6 | LPOP key | 移出並獲取列表的第一個元素 |
7 | LPUSH key value1 [value2] | 將一個或多個值插入到列表頭部 |
8 | LPUSHX key value | 將一個值插入到已存在的列表頭部 |
9 | LRANGE key start stop | 獲取列表指定範圍內的元素 |
10 | LREM key count value | 移除列表元素 |
11 | LSET key index value | 通過索引設置列表元素的值 |
12 | LTRIM key start stop | 對一個列表進行修剪(trim),就是說,讓列表只保留指定區間內的元素,不在指定區間之內的元素都將被刪除。 |
13 | RPOP key | 移除列表的最後一個元素,返回值為移除的元素。 |
14 | RPUSH key value1 [value2] | 在列表中添加一個或多個值 |
15 | RPUSHX key value | 為已存在的列表添加值 |
3.5 set
序號 | 命令 | 描述 |
---|---|---|
1 | SADD key member1 [member2] | 向集合添加一個或多個成員 |
2 | SCARD key | 獲取集合的成員數 |
3 | SDIFF key1 [key2] | 返回第一個集合與其他集合之間的差異。 |
4 | SDIFFSTORE destination key1 [key2] | 返回給定所有集合的差集並存儲在 destination 中 |
5 | SINTER key1 [key2] | 返回給定所有集合的交集 |
6 | SINTERSTORE destination key1 [key2] | 返回給定所有集合的交集並存儲在 destination 中 |
7 | SISMEMBER key member | 判斷 member 元素是否是集合 key 的成員 |
8 | SMEMBERS key | 返回集合中的所有成員 |
9 | SPOP key | 移除並返回集合中的一個隨機元素 |
10 | SRANDMEMBER key [count] | 返回集合中一個或多個隨機數 |
11 | SREM key member1 [member2] | 移除集合中一個或多個成員 |
12 | SUNION key1 [key2] | 返回所有給定集合的並集 |
13 | SUNIONSTORE destination key1 [key2] | 所有給定集合的並集存儲在 destination 集合中 |
3.6 sorted set
序號 | 命令 | 描述 |
---|---|---|
1 | ZADD key score1 member1 [score2 member2] | 向有序集合添加一個或多個成員,或者更新已存在成員的分數 |
2 | ZCARD key | 獲取有序集合的成員數 |
3 | ZCOUNT key min max | 計算在有序集合中指定區間分數的成員數 |
4 | ZINCRBY key increment member | 有序集合中對指定成員的分數加上增量 increment |
5 | ZINTERSTORE destination numkeys key [key …] | 計算給定的一個或多個有序集的交集並將結果集存儲在新的有序集合 destination 中 |
6 | ZLEXCOUNT key min max | 在有序集合中計算指定字典區間內成員數量 |
7 | ZRANGE key start stop [WITHSCORES] | 通過索引區間返回有序集合指定區間內的成員 |
8 | ZRANGEBYLEX key min max [LIMIT offset count] | 通過字典區間返回有序集合的成員 |
9 | ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT] | 通過分數返回有序集合指定區間內的成員 |
10 | ZRANK key member | 返回有序集合中指定成員的索引 |
11 | ZREM key member [member …] | 移除有序集合中的一個或多個成員 |
12 | ZREMRANGEBYLEX key min max | 移除有序集合中給定的字典區間的所有成員 |
13 | ZREMRANGEBYRANK key start stop | 移除有序集合中給定的排名區間的所有成員 |
14 | ZREMRANGEBYSCORE key min max | 移除有序集合中給定的分數區間的所有成員 |
15 | ZREVRANGE key start stop [WITHSCORES] | 返回有序集中指定區間內的成員,通過索引,分數從高到低 |
16 | ZREVRANGEBYSCORE key max min [WITHSCORES] | 返回有序集中指定分數區間內的成員,分數從高到低排序 |
17 | ZREVRANK key member | 返回有序集合中指定成員的排名,有序集成員按分數值遞減(從大到小)排序 |
18 | ZSCORE key member | 返回有序集中,成員的分數值 |
19 | ZUNIONSTORE destination numkeys key [key …] | 計算給定的一個或多個有序集的並集,並存儲在新的 key 中 |
20 | ZSCAN key cursor [MATCH pattern] [COUNT count] | 迭代有序集合中的元素(包括元素成員和元素分值) |
4. key過期策略
key 以兩種方式過期:被動方式(惰性過期)和主動方式(定期過期)。
4.1 惰性過期
只有當訪問一個key時,才會判斷該key是否已過期,過期則清除。該策略可以最大化地節省CPU資源,卻對記憶體非常不友好。極端情況可能出現大量的過期key沒有再次被訪問,從而不會被清除,佔用大量記憶體。
4.2 定期過期
redis會每隔一定的時間,從expires字典中掃描一定數量的key,並清除其中已過期的key。通過調整定時掃描的時間間隔和每次掃描的限定耗時,可以在不同情況下使得CPU和記憶體資源達到最優的平衡效果。
Redis 會定期在具有過期設置的key中隨機測試一些鍵。所有已經過期的key都從key空間中刪除。
具體來說,Redis 每秒執行 10 次(100ms一次):
- 從具有關聯過期的key集中測試 20 個隨機key。
- 刪除所有發現過期的key。
- 如果超過 25% 的key已過期,將從步驟 1 重新開始。
這是一個平凡的概率演算法,基本上假設是我們的樣本代表整個密鑰空間,我們繼續過期直到可能過期的密鑰百分比低於25%
這意味著在任何給定時刻,使用記憶體的已過期鍵的最大數量最大等於每秒最大寫入操作數量除以 4。
5. 驅逐key
當 Redis 用作快取時,通常可以方便地讓它在您添加新數據時自動驅逐舊數據。
LRU (Least Recently Used)實際上只是支援的驅逐方法之一。 Redis 使用的 LRU 演算法,實際上是精確 LRU 的近似值。
從 Redis 4.0 版開始,引入了新的 LFU(Least Frequently Used)驅逐策略。
5.1 配置指令
我們可以通過redis命令查看當前的驅逐策略,可以看出當前默認的驅逐策略為noeviction
127.0.0.1:6379> config get maxmemory-policy
1) "maxmemory-policy"
2) "noeviction"
查看當前的maxmemory
設置,可以看出當前的設置為0
,相當於沒有限制
config get maxmemory
1) "maxmemory"
2) "0"
當然,如果要設置這些參數,可以使用命令或者直接修改配置文件redis.conf
例如,為了配置 100 兆位元組的記憶體限制,可以在redis.conf
文件中使用以下指令。
maxmemory 100mb
5.2 驅逐策略
-
noeviction
:當記憶體使用超過配置的時候會返回錯誤,不會驅逐任何key。 -
allkeys-lru
:通過首先嘗試刪除最近較少使用的 (LRU) key,以便為添加的新數據騰出空間。 -
volatile-lru
:通過首先嘗試刪除最近較少使用的 (LRU)key,但僅限於具有過期時間的key,以便為添加的新數據騰出空間。 -
allkeys-random
:隨機驅逐key以便為添加的新數據騰出空間。 -
volatile-random
:隨機驅逐key以便為添加的新數據騰出空間,但僅驅逐具有過期時間的key。 -
volatile-ttl
:驅逐具有過期時間的key,並嘗試首先驅逐具有較短生存時間(TTL)的key,以便為添加的新數據騰出空間。 -
allkeys-lfu
:通過首先嘗試刪除使用頻率最少的key,以便為添加的新數據騰出空間。 -
volatile-lfu
:通過首先嘗試刪除使用頻率最少的key,但僅限於具有過期時間的key,以便為添加的新數據騰出空間。
7和8從 Redis 4.0 版開始引入
如何抉擇:
如果沒有與先決條件匹配的驅逐鍵,則策略volatile-lru、volatile-random和volatile-ttl 的行為類似於noeviction。
根據應用程式的訪問模式選擇正確的驅逐策略很重要,但是您可以在應用程式運行時在運行時重新配置策略,並使用 Redis INFO輸出監控快取未命中和命中的數量以調整您的設置.
一般來說,作為一個經驗法則:
- 當期望請求的流行度呈冪律分布時,請使用allkeys-lru策略,也就是說,您期望元素子集的訪問頻率遠高於其他元素。如果您不確定,這是一個不錯的選擇。
- 如果您有一個循環訪問,其中所有的鍵都被連續掃描,或者當您希望分布均勻(所有元素可能以相同的概率訪問)時,請使用allkeys-random。
- 如果您希望能夠通過在創建快取對象時使用不同的 TTL 值來向 Redis 提供關於什麼是到期的良好候選者的提示,請使用volatile-ttl。
該volatile-lru和volatile-random,當你想用一個實例兩個快取,並擁有一套永久鍵的策略是有用的。然而,運行兩個 Redis 實例來解決這樣的問題通常是一個更好的主意。
還值得注意的是,為鍵設置過期會消耗記憶體,因此使用像allkeys-lru這樣的策略會提高記憶體效率,因為無需為鍵在記憶體壓力下被逐出設置過期。
5.3 驅逐程式如何運作
重要的是要了解驅逐過程的工作方式如下:
- 客戶端運行新命令,導致添加更多數據。
- Redis 檢查記憶體使用情況,如果大於
maxmemory
限制,則根據策略驅逐鍵。 - 執行新命令,等等。
所以我們不斷地越過記憶體限制的邊界,越過它,然後通過驅逐鍵返回到限制之下。
如果某個命令導致使用大量記憶體(例如存儲到新密鑰中的大集合交集)一段時間,則記憶體限制可能會明顯超出。
5.4 新生KEY策略
另外一個問題是,當創建新對象的時候,對象的使用計數如果為0,很容易就會被淘汰掉(LFU),還需要為新生key設置一個初始counter。counter會被初始化為LFU_INIT_VAL,默認5。
6. 持久性
Redis 提供了一系列不同的持久性選項:
- RDB(Redis Database):RDB 持久性以指定的時間間隔執行數據集的時間點快照。
- AOF(Append Only File):AOF 持久化記錄伺服器收到的每個寫操作,在伺服器啟動時會再次播放,重建原始數據集。命令使用與 Redis 協議本身相同的格式以僅附加的方式記錄。當日誌變得太大時,Redis 能夠在後台重寫日誌。
- 無持久性:如果希望數據在伺服器運行時一直存在,您可以完全禁用持久性。
- RDB + AOF:可以在同一個實例中組合 AOF 和 RDB。請注意,在這種情況下,當 Redis 重新啟動時,AOF 文件將用於重建原始數據集,因為它保證是最完整的。
要理解的最重要的事情是 RDB 和 AOF 持久性之間的不同權衡。讓我們從 RDB 開始:
6.1 RDB優勢
- RDB 是 Redis 數據的非常緊湊的單文件時間點表示。RDB 文件非常適合備份。例如,您可能希望在最近 24 小時內每小時存檔一次 RDB 文件,並在 30 天內每天保存一個 RDB 快照。這使您可以在發生災難時輕鬆恢復不同版本的數據集。
- RDB 非常適合災難恢復,它是一個可以傳輸到遠程數據中心或 Amazon S3(可能已加密)的緊湊文件。
- RDB 最大限度地提高了 Redis 的性能,因為 Redis 父進程為了持久化需要做的唯一工作是派生一個將完成所有其餘工作的子進程。父實例永遠不會執行磁碟 I/O 或類似操作。
- 與 AOF 相比,RDB 允許更快地重新啟動大數據集。
- 在副本上,RDB 支援重啟和故障轉移後的部分重新同步。
6.2 RDB 的缺點
- 如果您需要在 Redis 停止工作(例如斷電後)時將數據丟失的可能性降至最低,那麼 RDB 並不好。您可以在生成 RDB 的地方配置不同的保存點(例如,在對數據集進行至少 5 分鐘和 100 次寫入之後,但您可以有多個保存點)。但是,您通常會每五分鐘或更長時間創建一個 RDB 快照,因此,如果 Redis 因任何原因在沒有正確關閉的情況下停止工作,您應該準備好丟失最近幾分鐘的數據。
- RDB 經常需要 fork() 以便使用子進程在磁碟上持久化。如果數據集很大,Fork() 可能會很耗時,如果數據集很大且 CPU 性能不是很好,可能會導致 Redis 停止為客戶端服務幾毫秒甚至一秒鐘。AOF 也需要 fork() 但你可以調整你想要重寫日誌的頻率,而不會對持久性進行任何權衡。
6.3 AOF優勢
- 使用 AOF Redis 更持久:你可以有不同的 fsync 策略:根本沒有 fsync,每秒 fsync,每次查詢 fsync。使用 fsync 每秒寫入性能的默認策略仍然很棒(fsync 是使用後台執行緒執行的,當沒有 fsync 正在進行時,主執行緒將努力執行寫入。)但您只能丟失一秒鐘的寫入。
- AOF 日誌是僅附加日誌,因此在斷電時不會出現尋道或損壞問題。即使日誌由於某種原因(磁碟已滿或其他原因)以半寫命令結束,redis-check-aof 工具也能夠輕鬆修復它。
- 當 AOF 變得太大時,Redis 能夠在後台自動重寫。重寫是完全安全的,因為當 Redis 繼續追加到舊文件時,會使用創建當前數據集所需的最少操作集生成一個全新的文件,一旦第二個文件準備就緒,Redis 就會切換這兩個文件並開始追加到新的那一個。
- AOF 以易於理解和解析的格式包含所有操作的日誌。您甚至可以輕鬆導出 AOF 文件。例如,即使您不小心使用FLUSHALL命令刷新了所有內容,只要在此期間沒有重寫日誌,您仍然可以通過停止伺服器、刪除最新命令並重新啟動 Redis 來再次保存您的數據集。
6.4 AOF的缺點
- AOF 文件通常比相同數據集的等效 RDB 文件大。
- AOF 可能比 RDB 慢,具體取決於確切的 fsync 策略。一般來說,將 fsync 設置為每秒性能仍然非常高,並且禁用 fsync 後,即使在高負載下,它也應該與 RDB 一樣快。即使在寫入負載巨大的情況下,RDB 仍然能夠提供更多關於最大延遲的保證。
- 過去我們在特定命令中遇到過罕見的錯誤(例如有一個涉及阻塞命令的錯誤,如BRPOPLPUSH) 導致生成的 AOF 在重新載入時無法重現完全相同的數據集。這些錯誤很少見,我們在測試套件中進行了測試,自動創建隨機複雜數據集並重新載入它們以檢查一切正常。但是,使用 RDB 持久性幾乎不可能出現此類錯誤。為了更清楚地說明這一點:Redis AOF 通過增量更新現有狀態來工作,就像 MySQL 或 MongoDB 那樣,而 RDB 快照一次又一次地從頭開始創建所有內容,這在概念上更加健壯。但是 – 1)需要注意的是,每次Redis重寫AOF時,都是從數據集中包含的實際數據開始重新創建,與始終附加的 AOF 文件(或重寫讀取舊 AOF 而不是讀取記憶體中的數據)相比,對錯誤的抵抗力更強。2) 我們從未收到過用戶關於在現實世界中檢測到的 AOF 損壞的單一報告。
6.5 如何抉擇?
一般的跡象是,如果您想要與 PostgreSQL 可以提供的數據安全程度相當的數據安全性,則應該同時使用這兩種持久性方法。
如果您非常關心您的數據,但在發生災難時仍然可以忍受幾分鐘的數據丟失,您可以簡單地單獨使用 RDB。
有很多用戶單獨使用 AOF,但我們不鼓勵它,因為不時擁有 RDB 快照對於進行資料庫備份、更快地重新啟動以及在 AOF 引擎中出現錯誤時是一個好主意。
注意:由於所有這些原因,我們很可能在未來將 AOF 和 RDB 統一為一個持久化模型(長期計劃)。
以下部分將說明有關這兩種持久性模型的更多細節。
6.6 RDB如何運作的
默認情況下,Redis 將數據集的快照保存在磁碟上的一個名為dump.rdb
. 如果數據集中至少有 M 次更改,您可以將 Redis 配置為每 N 秒保存一次數據集,或者您可以手動調用SAVE或BGSAVE命令。
例如,如果至少有 1000 個鍵更改,此配置將使 Redis 每 60 秒自動將數據集轉儲到磁碟:
save 60 1000
這種策略被稱為快照。
具體操作步驟如下:
每當 Redis 需要將數據集轉儲到磁碟時,就會發生以下情況:
- Redisforks。我們現在有一個子進程和一個父進程。
- 子進程開始將數據集寫入臨時 RDB 文件。
- 當子進程寫完新的 RDB 文件時,它會替換舊的。
這種方法允許 Redis 從寫時複製語義中受益。
6.7 AOF 如何運作的
快照不是很持久。如果您運行Redis的電腦停止運行,您的電源線發生故障,或者您不小心kill -9
您的實例,Redis上寫入的最新數據將丟失。雖然這對於某些應用程式來說可能不是什麼大問題,但存在完全持久性的用例,在這些情況下,Redis 不是一個可行的選擇。
該只追加文件是Redis的選擇,完全耐用的策略。它在 1.1 版中可用。
你可以在你的配置文件中開啟AOF:
appendonly yes
從現在開始,每次 Redis 收到更改數據集的命令(例如SET)時,它都會將其附加到 AOF 中。當您重新啟動 Redis 時,它將重新播放 AOF 以重建狀態。
6.7.1 日誌重寫
您可以猜到,隨著寫入操作的執行,AOF 變得越來越大。例如,如果您將一個計數器遞增 100 次,您最終將在數據集中得到一個包含最終值的鍵,但在 AOF 中有 100 個條目。重建當前狀態不需要這些條目中的 99 個。
所以Redis支援了一個有趣的特性:它能夠在不中斷對客戶端的服務的情況下在後台重建AOF。每當您發出BGREWRITEAOF 時, Redis 都會寫入在記憶體中重建當前數據集所需的最短命令序列。如果您在 Redis 2.2 中使用 AOF,則需要不時運行BGREWRITEAOF。Redis 2.4 能夠自動觸發日誌重寫(更多資訊請參見 2.4 示例配置文件)。
6.7.2 僅追加文件的持久性如何?
您可以配置 Redis 將 fsync
數據在磁碟上的次數。共有三個選項:
appendfsync always
:fsync
每次將新命令附加到 AOF 時。非常非常緩慢,非常安全。請注意,在執行來自多個客戶端或管道的一批命令之後,這些命令會附加到 AOF,因此這意味著單個寫入和單個 fsync(在發送回復之前)。appendfsync everysec
:fsync
每一秒。足夠快(在 2.4 中可能和快照一樣快),如果發生災難,您可能會丟失 1 秒的數據。appendfsync no
:永遠不要fsync
,只需將您的數據交到作業系統的手中。更快、更不安全的方法。通常 Linux 會使用這種配置每 30 秒刷新一次數據,但這取決於內核的精確調整。
建議(和默認)策略是fsync
每秒。它既非常快又非常安全。該always
策略在實踐中很慢,但它支援組提交,因此如果有多個並行寫入,Redis 會嘗試執行單個fsync
操作。
6.7.3 如果我的 AOF 被截斷,我該怎麼辦?
有可能是伺服器在寫入AOF文件時崩潰,或者寫入時存儲AOF文件的卷已滿。發生這種情況時,AOF 仍包含表示數據集給定時間點版本的一致數據(使用默認 AOF fsync 策略,該版本可能長達一秒),但 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 可能無法恢復,可能需要執行以下步驟:
-
製作 AOF 文件的備份副本。
-
使用
redis-check-aof
Redis附帶的工具修復原始文件:$ redis-check-aof –fix
-
可選地用於
diff -u
檢查兩個文件之間的區別。 -
使用固定文件重新啟動伺服器。
6.7.4 如果我的 AOF 損壞了,我該怎麼辦?
如果 AOF 文件不僅被截斷,而且被中間的無效位元組序列損壞,事情就會變得更加複雜。Redis 會在啟動時抱怨並中止:
* Reading the remaining AOF tail...
# Bad file format reading the append only file: make a backup of your AOF file, then use ./redis-check-aof --fix <filename>
最好的辦法是運行該redis-check-aof
實用程式,最初沒有--fix
選項,然後了解問題,在文件中給定的偏移處跳轉,看看是否可以手動修復文件:AOF 使用相同的格式Redis 協議,手動修復非常簡單。否則有可能讓實用程式為我們修復文件,但在這種情況下,從無效部分到文件末尾的所有 AOF 部分可能會被丟棄,如果損壞發生,將導致大量數據丟失在文件的初始部分。
AOF具體操作如下:
日誌重寫使用已用於快照的相同的寫時複製技巧。這是它的工作原理:
- Redis fork,所以現在我們有一個子進程和一個父進程。
- 子進程開始在臨時文件中寫入新的 AOF。
- 父進程在記憶體緩衝區中累積所有新更改(但同時它將新更改寫入舊的僅附加文件中,因此如果重寫失敗,我們是安全的)。
- 當子進程完成重寫文件時,父進程收到一個訊號,並將記憶體緩衝區附加到子進程生成的文件的末尾。
- 現在 Redis 原子地將舊文件重命名為新文件,並開始將新數據附加到新文件中。