Redis上篇--分析
Redis上篇--解析
本文大部分知識整理自網上,在正文結束后都會附上參考地址。如果想要深入或者詳細學習可以通過文末鏈接跳轉學習。
1. 基本介紹
Redis 是一個開源的、高性能的 內存鍵值數據庫,Redis 的鍵值對中的 key 就是字符串對象,而 value 就是指Redis的數據類型,可以是String,也可以是List、Hash、Set、 Zset 的數據類型。Redis通常被用作數據庫、緩存、消息中間件和實時數據處理引擎。它以速度極快、支持豐富的數據結構而聞名,是現代應用架構中非常流行的組件。
它的核心特點我們需要了解一下:
- 內存存儲 (In-Memory):
- 數據主要存儲在內存 (RAM) 中,這是 Redis 速度驚人的根本原因(讀寫操作通常在微秒級別)。
- 它也提供可選的持久化機制,可以將數據異步或同步保存到磁盤,防止服務器重啟后數據丟失。
- 豐富的數據結構 (Data Structures):
- 不僅僅是簡單的 Key-Value 字符串!Redis 支持多種高級數據結構:
- Strings: 最基本類型,可以存儲文本、數字、二進制數據(如圖片片段)。
- Lists: 有序的元素集合,可在頭部或尾部插入/刪除,適合實現隊列、棧、時間線。
- Sets: 無序的唯一元素集合,支持交集、并集、差集等操作,適合標簽、共同好友。
- Sorted Sets (ZSets): 帶分數的有序唯一元素集合,元素按分數排序,完美適用于排行榜、優先級隊列。
- Hashes: 存儲字段-值對的集合,非常適合表示對象(如用戶信息:
field: name, value: "Alice"; field: age, value: 30)。 - Bitmaps / HyperLogLogs / Geospatial Indexes: 特殊用途的數據結構,用于位操作、基數統計(去重計數)、地理位置計算等。
- 不僅僅是簡單的 Key-Value 字符串!Redis 支持多種高級數據結構:
- 高性能與低延遲:
- 內存訪問 + 單線程架構 (核心命令執行是單線程,避免了鎖競爭) + 高效的網絡 I/O 模型 (epoll/kqueue) 使其擁有極高的吞吐量和極低的延遲。
- 持久化 (Persistence):
- RDB (Redis Database File): 在指定時間間隔生成整個數據集的內存快照。恢復快,文件緊湊。適合備份和災難恢復。
- AOF (Append-Only File): 記錄所有修改數據庫狀態的命令。更安全(最多丟失一秒數據),文件可讀性強,但文件通常更大,恢復可能比 RDB 慢??梢酝瑫r開啟或選擇其一。
- 原子操作與事務:
- 所有單條命令的執行都是原子的。
- 支持簡單的事務 (
MULTI/EXEC),可以將一組命令打包執行(但不支持回滾 - 命令語法錯誤會導致整個事務不執行,運行時錯誤不影響其他命令執行)。 - Lua 腳本: 可以執行復雜的、需要多個命令且保證原子性的操作。
其實還有像發布訂閱、集群架構本節就不贅述了。
典型應用場景
- 緩存 (Caching): 最常見的用途。將頻繁訪問的熱點數據(如數據庫查詢結果、頁面片段、會話信息)存儲在 Redis 中,顯著減輕后端數據庫壓力,提升應用響應速度。
- 會話存儲 (Session Store): 存儲用戶會話信息,易于在多服務器或微服務架構中實現會話共享。
- 排行榜/計數器 (Leaderboards / Counters): 利用 Sorted Sets 可以非常高效地實現實時排行榜。利用
INCR等命令實現高并發下的計數器(如點贊數、瀏覽量)。 - 實時系統 (Real-time Systems):
- 消息隊列 (Message Queue): 利用 Lists 或 Streams (一種更強大的持久化消息隊列數據結構) 實現簡單的消息隊列。
- 實時分析: 處理實時事件流(如用戶活動跟蹤、監控數據)。
- 地理空間應用 (Geospatial): 存儲地理位置坐標,執行附近位置查詢、距離計算等。
- 速率限制 (Rate Limiting): 限制用戶 API 調用頻率或操作次數。
- 分布式鎖 (Distributed Lock): 利用 Redis 的原子操作實現簡單的跨進程/跨機器的互斥鎖。
// 對應的數據類型作用場景:
String可以用來做緩存、計數器、限流、分布式鎖、分布式Session等。
Hash可以用來存儲復雜對象。List可以用來做消息隊列、排行榜、計數器、最近訪問記錄等。
Set可以用來做標簽系統、好友關系、共同好友、排名系統、訂閱關系等。
Zset可以用來做排行榜、最近訪問記錄、計數器、好友關系等。
Geo可以用來做位置服務、物流配送、電商推薦、游戲地圖等。
HyperLogLog可以用來做用戶去重、網站UV統計、廣告點擊統計、分布式計算等。
Bitmaps可以用來做在線用戶數統計、黑白名單統計、布隆過濾器等。
看了這么大幾點,就可以知道Redis相當牛逼了。都說Redis快,僅僅是因為內存操作嗎?接下來看一下它為什么這么快!
2. '快'的原因
Redis 可以達到極高的性能(官方測試讀速度約 11 萬次 / 秒,寫速度約 8 萬次 / 秒),快自然有快的道理
2.1 內存
基于內存操作
Redis將所有數據存儲在內存中,避免了傳統數據庫的磁盤I/O瓶頸,內存的讀寫速度遠高于磁盤,這使得Redis能夠實現超高的響應速度。
節選一下別人的對比:內存的讀寫速度,和磁盤讀寫速度的對比
- 最快情況下, 固態 硬盤 速度,大致是 內存速度的 百分之一,
- 最慢情況下, 機械 硬盤 速度,大致是 內存速度的 萬分之一,
內存讀寫速度可以達到每秒數百GB,在微秒級別,而磁盤(特別機械硬盤) 讀寫速度通常只有數十MB,在毫秒級別, 是數千倍的差距。
對比與傳統的關系型數據庫比如說MySQL,需要從磁盤加載數據到內存緩沖區才能操作,Redis不需要這個步驟,就避免了磁盤 I/O 的延遲。
+++
2.2 高性能數據結構
高效的數據結構
我們都知道Redis向我們用戶提供了value為string, list, hash, set, zset五種基本數據類型來使用,還有幾種高級的數據結構例如geo, bitmap, hyperloglog。本文就只看基本的數據類型了。
本節分析一下底層實現,這些數據類型底層實現有如下這么些:
sds( 簡單動態字符串)
ziplist(壓縮列表)
linkedlist(雙端鏈表)
hashtable(字典)
skiplist(跳表)
這些底層結構能夠在內存中高效地存儲和操作數據,為Redis的快速性能提供了堅實的基礎。這里附上別人的圖:【來自參考1】

2.2.1 sds
首先就是大家最為熟知的string類型了:它的底層實現是基于sds的。在這之前,我們需要知道這樣一件事,Redis中所有數據均通過redisObject結構體封裝
typedef struct redisObject {
unsigned type:4; // 數據類型(如String、List)
unsigned encoding:4; // 編碼方式(int/embstr/raw)
unsigned lru:24; // LRU緩存淘汰信息
int refcount; // 引用計數(用于共享和內存回收)
void *ptr; // 指向實際數據的指針
} robj;
/*
type:標識數據類型(String固定為OBJ_STRING)。
encoding:決定ptr指向的數據結構,String有三種可能編碼:
OBJ_ENCODING_INT:整數類型。
OBJ_ENCODING_EMBSTR:短字符串(≤44字節)。
OBJ_ENCODING_RAW:長字符串(>44字節)
*/
當前字符串的值可表示為64位有符號整數 long (-9223372036854775807至9223372036854775807 很大就是了),表示為int編碼,直接存儲整數。
如果不能表示為整數,字符串長度 ≤44字節的時候,為EMBSTR編碼:連續內存的短字符串,此時,redisObject和SDS分配在單塊連續內存中,這樣可以減少內存碎片,提升CPU緩存命中率。redisObject固定占16字節,SDS頭部(sdshdr8)占3字節(len、alloc、flags各1字節),總內存分配單元為64字節(Redis內存分配器jemalloc的最小單位),所以剩余空間:64 - 16 - 3 - 1(結束符\0) = 44字節。
如果超過了44字節,redisObject和SDS分兩次分配內存(不連續),ptr指向獨立的SDS結構。
那么這個SDS(Simple Dynamic String)到底是什么個情況呢?SDS是Redis自定義的動態字符串結構,解決了C原生字符串的缺陷(緩沖區溢出、長度計算O(n)、二進制不安全)
struct __attribute__((__packed__)) sdshdr8 {
uint8_t len; // 已用長度 1字節
uint8_t alloc; // 總分配容量(不包括頭部和\0) 1字節
unsigned char flags;// 標識SDS類型(sdshdr8/16/32/64) 1字節
char buf[]; // 實際數據(柔性數組)
};
基于這種結構,相比于c語言的字符串,我們可以O(1)復雜度獲取字符串長度:直接讀取len字段;C語言字符串是使用char數組存儲,以'\0'作為字符串結束,比如字符串”Redis“在C語言中存儲結構就是這個樣子的:
Redis'\0'
C語言字符串這種特殊規定,就導致無法存儲特殊字符。如果某個字符串中間包含'\0'字符,讀取字符串的時候就無法讀取到完整字符,遇到'\0'就結束了,此外呢,C字符串不記錄自身的長度,每次增長或縮短一個字符串,都要對底層的字符數組進行一次內存重分配操作。如果在 append 操作之前沒有通過內存重分配來擴展底層數據的空間大小,就會產生緩存區溢出;如果進行 trim 操作之后沒有通過內存重分配來釋放不再使用的空間,就會產生內存泄漏。
而Redis的這種結構就很好了,二進制安全,依賴len而非\0判斷結束,可存儲圖片等二進制數據;
另外還涉及到擴容問題,Redis的SDS中,如果新增的拼接字符串長度小于未使用空間,就不用擴容了;
此外,惰性空間釋放:縮短字符串時不立即回收內存,只是簡單的修改sdshdr 頭中的 Len 字段就可以了。
2.2.2 zipList
Redis的壓縮列表(ziplist)是一種極致內存優化的緊湊型數據結構,用于在小數據量場景下高效存儲列表、哈希和有序集合。它通過精巧的設計實現了O(1)頭部操作和高效內存利用,是Redis早期核心數據結構之一(Redis 7.0后逐漸被listpack替代,但理解其設計仍有重要意義)
// ziplist的數據結構
typedef struct {
uint32_t zlbytes; // 4字節 整個壓縮列表占用的內存字節數(uint32_t)
uint32_t zltail; // 最后一個entry的偏移量(實現O(1)尾部訪問)
uint16_t zllen; // entry數量(當≥65535時需遍歷計算)
entry* entries; // 實際存儲的節點列表
uint8_t zlend; // 結束標志(0xFF)
} ziplist;
// 其結點的數據結構
struct entry {
uint8_t prevlen; // 前驅節點長度(1或5字節)
uint8_t encoding; // 數據編碼方式(字節數組或整數)
optional uint8_t data[]; // 實際數據
};
壓縮列表以一種緊湊的方式存儲數據,將多個元素緊密地排列在一起,節省了存儲空間。在壓縮列表中,相鄰的元素可以共享同一個內存空間。這里附上【尼恩】的示意圖:

他的優點:緊湊存儲,無額外指針(相比鏈表節省 20%-30% 內存);通過 zltail 快速定位尾部,支持 O(1) 時間的尾插/尾刪;
但是,插入/刪除需移動后續節點,時間復雜度 O(N);會有連鎖更新風險。
Redis 的壓縮列表(Ziplist)與傳統雙向鏈表在數據結構設計、性能和應用場景上存在如下差異:
第一,從結構設計上來說
| 特性 | 壓縮列表(Ziplist) | 傳統雙向鏈表 |
|---|---|---|
| 存儲方式 | 連續內存塊,通過偏移量定位節點 | 離散內存塊,每個節點包含前驅/后繼指針 |
| 節點結構 | 無指針,包含 prevlen(前驅長度)+ encoding(編碼類型)+ 數據內容 |
包含前驅指針、后繼指針及數據內容 |
| 頭部元數據 | zlbytes(總字節數)、zltail(尾節點偏移)、zllen(節點數量)、zlend(結束符) |
通常僅記錄頭尾指針和節點數量 |
| 內存布局 | 緊湊存儲,無額外指針開銷 | 邏輯上是相鄰的 |
第二,內存效率對比
| 維度 | 壓縮列表 | 雙向鏈表 |
|---|---|---|
| 內存占用 | 極低(無指針,變長編碼優化) | 較高(每個節點需 2 個指針,占 16 字節) |
| 空間利用率 | 高(緊湊存儲減少碎片) | 低(易產生內存碎片) |
| 適用數據規模 | 小數據場景(元素數量 ≤ 512,單元素 ≤ 64 字節) | 大數據場景(無嚴格限制) |
第三,操作性能對比
| 操作類型 | 壓縮列表 | 雙向鏈表 |
|---|---|---|
| 隨機訪問 | O(n)(需遍歷節點) | O(n)(需遍歷指針鏈) |
| 頭尾插入/刪除 | O(1)(通過 zltail 快速定位) |
O(1)(直接操作頭尾指針) |
| 中間插入/刪除 | O(n)(需移動后續節點,可能觸發連鎖更新) | O(n)(需遍歷到節點位置,但指針操作快) |
2.2.3 雙向鏈表
Redis中的雙端鏈表是一種優化后的數據結構,專門用于存儲有序的元素集合。Redis雙端鏈表具備雙向鏈接的特性,即每個節點都包含指向前一個節點和后一個節點的指針。
這里附上別人的Redis雙向鏈表的示意圖:

可以看到每個節點包含 前驅指針、后繼指針 和 數據值,支持從頭部或尾部向中間遍歷;頭節點的 prev 和尾節點的 next 均指向 NULL,避免循環引用;List結構中還有長度字段:len 屬性直接記錄節點數,避免遍歷統計;還存有頭指針、尾指針,方便從頭或者尾部開始遍歷鏈表。
| 操作 | 時間復雜度 | 說明 |
|---|---|---|
| 頭部插入/刪除 | O(1) | 直接調整頭指針及相鄰節點指針 |
| 尾部插入/刪除 | O(1) | 同上 |
| 按索引訪問 | O(n) | 需從頭或尾遍歷(平均一半節點) |
| 按值查找 | O(n) | 順序遍歷,無哈希索引 |
| 遍歷全部節點 | O(n) | 需訪問每個節點 |
再在這里與2.2.3小節的壓縮列表對比一下:
| 場景 | 雙向鏈表 | 壓縮列表 |
|---|---|---|
| 大型列表 | ? 穩定O(1)操作 | ? 插入可能觸發O(n2)連鎖更新 |
| 小型列表 | ? 內存開銷大 | ? 內存節省80%+ |
| 頻繁頭尾操作 | ? 直接指針修改 | ? 尾部快,頭部需移動數據 |
| 中間位置修改 | ? O(1)指針操作 | ? O(n)數據移動 |
| 二進制安全 | ? 無特殊限制 | ? 支持 |
| 內存碎片 | ? 高(節點離散分配) | ? 極低(連續內存) |
2.2.4 漸進式擴容 雙哈希結構
通過Redis源碼,我們可以看到dict的定義如下:
typedef struct dict {
dictType *type; // 類型特定函數
void *privdata; // 私有數據
dictht ht[2]; // 雙哈希表(用于漸進式rehash)
long rehashidx; // rehash進度索引
} dict;
typedef struct dictht {
dictEntry **table; // 桶數組
unsigned long size; // 桶數量
unsigned long sizemask; // size-1(用于位運算)
unsigned long used; // 已用節點數
} dictht;
typedef struct dictEntry {
void *key; // 鍵(SDS)
union { // 值(聯合體)
void *val;
uint64_t u64;
int64_t s64;
} v;
struct dictEntry *next; // 鏈地址法解決沖突
} dictEntry;
從上述結構體我們可以推斷出其具體結構:

可以看到,redis處理哈希沖突的方式是鏈地址法,當多個鍵值對映射到同一個哈希表數組索引時,這些鍵值對會以鏈表的形式存儲,鏈表的每個節點包含鍵值對。(這種查找插入的速度都不慢,hash表的數據結構我這里就不解釋了,我們知道Redis的hash表如上圖所示就行了)
| 操作 | 平均復雜度 | 最壞復雜度 | 場景 |
|---|---|---|---|
| HSET/HGET | O(1) | O(n) | 哈希沖突嚴重 |
| HGETALL | O(n) | O(n) | 全量遍歷 |
| HDEL | O(1) | O(n) | 需鏈表刪除 |
隨著數據量的增長,Redis 的哈希表需要擴容存儲能力。但擴容操作本身可能會非常耗時,導致服務阻塞。所以就有了上面的ht[2]這個東西了??!
為了在擴容過程中避免阻塞服務,Redis 設計了雙哈希表結構 dict.ht,其中包含兩個哈希表:
- ht[0]:主哈希表,用于存儲當前所有數據,并處理客戶端的讀寫請求。
- ht[1]:備用哈希表,用于擴容操作。
擴容的流程大概如下:
當哈希表負載因子超過閾值(元素數/桶數)≥ 1 且未執行 BGSAVE/BGREWRITEAOF,或負載因子 ≥ 5 時,觸發擴容。Redis 會創建一個容量為原表兩倍的新哈希表(ht[1]),并通過分批次遷移數據實現無阻塞擴容。具體步驟為:
首先,初始化新表:分配 ht[1] 的內存空間,設置其 sizemask 和 used 字段,將舊表(ht[0])的 rehashidx 置為 0,標記擴容開始;
其次,漸進遷移:處理客戶端請求時,遷移 ht[0] 中 rehashidx 對應的一個哈希桶的所有鍵值對到 ht[1],并遞增 rehashidx,在這個過程中,查詢操作就同時搜索 ht[0] 和 ht[1],新增操作直接寫入 ht[1],刪除/更新操作如果所在桶若未遷移,在 ht[0] 執行;若已遷移則在 ht[1] 執行。
同時呢,還會有定時輔助遷移:即使無操作,Redis 也會在 serverCron 中觸發 1ms 的遷移任務,每次最多遷移 10 個桶,確??臻e時完成擴容;
最后完成收尾:當 ht[0] 的所有數據遷移完成后,釋放其內存,將 ht[1] 設為新的 ht[0],并重置 rehashidx 為 -1。
2.2.5 跳躍表
跳躍表(Skip List)是一種基于有序鏈表的隨機化數據結構,通過多層索引實現高效查找、插入和刪除操作,平均時間復雜度為 O(log n)。Redis 的有序集合(Sorted Set)就是采用跳躍表作為其核心數據結構。那么它長什么樣子呢

跳表的做法就是給鏈表做索引,而且是分層索引,可以有多層,層級越多占用 的空間肯定是越多的;
跳表通過維護多級索引,每個級別的索引都是原始鏈表的子集,用于快速定位元素。
每個節點在不同級別的索引中都有一個指針,通過這些指針,可以在不同級別上進行快速查找,從而提高了查找效率。
比如說在上面的圖中,查找元素8,就可以按照下面的順序去查找:

只用找1 5 7 8,就可以了。
可以很直觀地看到查找算法看起來挺簡單的,難點就是插入、刪除元素的時候,索引該如何去維護。跳躍表實現見后續文章吧,本文只做Redis數據結構的簡要概括。下面附上一張Redis中skipList的數據結構,來自參考3:

header:指向跳躍表的表頭節點
tail:指向跳躍表的表尾節點
level:記錄目前跳躍表內,層數最大的那個節點層數(表頭節點的層數不計算在內)
length:記錄跳躍表的長度,也就是跳躍表目前包含節點的數量(表頭節點不計算在內)
同時在跳躍表中,score值有序
| 特性 | 經典跳躍表 | Redis 跳躍表 |
|---|---|---|
| 排序依據 | 單鍵值 | 分值(score)+成員(ele) |
| 跨度設計 | 無 | 有(支持排名計算) |
| 遍歷方向 | 單向 | 雙向鏈表,方便以倒序方式獲取一個范圍內的元素。 |
| 頭節點 | 僅存指針 | 存儲最大層數 |
| 層數上限 | 無硬性限制 | 固定 32 層 |
Redis 為什么采用跳躍表而不用平衡樹?【copy自參考3】
- 從內存占用上來比較,跳表比平衡樹更靈活一些。平衡樹每個節點包含 2 個指針(分別指向左右子樹),而跳表每個節點包含的指針數目平均為 1/(1-p),具體取決于參數 p 的大小。如果像 Redis里的實現一樣,取 p=1/4,那么平均每個節點包含 1.33 個指針,比平衡樹更有優勢。
- 在做范圍查找的時候,跳表比平衡樹操作要簡單。在平衡樹上,我們找到指定范圍的小值之后,還需要以中序遍歷的順序繼續尋找其它不超過大值的節點。如果不對平衡樹進行一定的改造,這里的中序遍歷并不容易實現。而在跳表上進行范圍查找就非常簡單,只需要在找到小值之后,對第 1 層鏈表進行若干步的遍歷就可以實現。
- 從算法實現難度上來比較,跳表比平衡樹要簡單得多。平衡樹的插入和刪除操作可能引發子樹的調整,邏輯復雜,而跳表的插入和刪除只需要修改相鄰節點的指針,操作簡單又快速。
2.3 單線程、多路復用
Redis 的單線程設計是其高性能的核心支柱,但它并非字面意義上的“只有一個線程”。Redis 的工作線程(主線程)串行處理所有客戶端命令,但存在輔助線程處理異步任務

為什么堅持核心單線程呢?這可能是一種取舍,如果是多線程的話,需要考慮 共享數據結構需加鎖(如 Mutex)、線程上下文切換需要消耗 CPU 周期、線程安全編程難度高一些。
如果采用單線程的話,天然無鎖,可以保證操作的原子性;因為是單線程嘛,所以就沒有縣城上下文切換了;代碼就更簡單易懂了,也容易維護嘛。
單線程肯定會有不足的:比如說執行了慢命令,(如 KEYS *)會阻塞所有后續請求;單線程無法充分利用多核cpu。
所以可以得出:CPU 并不是Redis的瓶頸 → 避免鎖/切換開銷 → 單線程更高效
因為Redis使用內存存儲數據,所以數據訪問非常迅速,不會成為性能瓶頸。此外,Redis的數據操作大多數都是簡單的鍵值對操作,不包含復雜計算和邏輯,因而CPU開銷很小。相反,Redis的瓶頸在于內存的容量和網絡的帶寬,這些問題無法通過增加CPU核心來解決。
Redis 6.x開始引入了多線程, 但是多線程僅僅是在 處理網絡IO,Redis 核心命令執行依然是單線程,確保性能和一致性。
+++
那么,現在說一下Redis 的網絡 I/O 處理,采用 事件驅動的 Reactor 模式,結合 I/O 多路復用技術 和 漸進式多線程優化:
Redis 采用 Reactor 模式作為網絡模型的基礎架構,在這一模式下,Redis 通過一個主事件循環(Event Loop) 持續監聽并分發網絡事件。
首先,事件分發器:基于 I/O 多路復用技術(如 Linux 的 epoll)實現,負責監控所有客戶端連接的 Socket 文件描述符(FD);
然后,事件處理器:為不同事件(如連接、讀、寫)綁定對應的回調函數。例如:連接事件觸發 accept 處理器,創建新客戶端連接;讀事件觸發命令請求處理器,解析并執行 Redis 命令;寫事件觸發響應回復處理器,將結果返回客戶端。
通過 I/O 多路復用,單一線程可同時監聽數萬個 Socket,僅當 Socket 真正發生讀寫事件時才觸發回調,避免了線程空轉和阻塞,這種設計使得 Redis 在單線程下仍能高效處理高并發請求,尤其適合內存操作快速完成的場景
盡管單線程模型簡化了數據一致性管理,但網絡 I/O 瓶頸在高并發場景下逐漸顯現。為此,Redis 從 6.0 版本開始引入漸進式多線程優化:新增的 I/O 線程僅負責網絡數據的讀取與發送,而命令解析與數據操作仍由主線程單線程執行,這種設計確保了核心數據操作的原子性,避免多線程競爭。

主要流程:
1. 主線程接收新連接,將 Socket 分配至全局隊列;
2. I/O 線程池并行讀取請求數據并解析為命令(若啟用 io-threads-do-reads),或并行發送響應結果;
3. 主線程按順序執行所有命令,再將結果寫入緩沖區供 I/O 線程發送
【用戶可通過 io-threads 參數設置線程數(建議為 CPU 核數的 1~1.5 倍),并通過 io-threads-do-reads 控制是否啟用讀并行化,
在高并發網絡場景下,此優化可提升吞吐量 40%~50%,同時避免核心邏輯的鎖競爭】
2.4 異步持久化機制
單機的Redis速度已經獨步天下了,倘若遇到系統錯誤,導致Redis應用程序中斷了,由于數據是在內存里面,那不就全部丟失了嗎?閣下該如何應對!這就需要 說到Redis的持久化機制了。
Redis的持久化機制包含RDB和AOF兩種方式,其核心設計原則是最大化性能,因此持久化操作本質是異步的(主線程非阻塞);
- 首先說一下RDB(Redis Database):
它是將內存中的數據以二進制格式生成全量快照(Snapshot),寫入 dump.rdb 文件,通過 fork 子進程完成持久化,主進程繼續處理請求,僅 fork 操作短暫阻塞(約 1-100ms)。那么其觸發方式是什么樣子的呢?
可以自動觸發(默認異步),在配置文件里面配置 save m n:如 save 900 1 表示 900 秒內至少 1 次鍵修改時觸發;從節點全量復制時自動觸發。
也可以手動觸發,就需要命令的形式了,SAVE:同步阻塞主線程,生成快照(不推薦生產環境使用);BGSAVE:異步生成快照,通過子進程完成(默認方式)。
這種方式快速生成快照,對性能影響較小,此外呢,文件體積較小,適合備份和災難恢復。但是,如果是 Redis 服務器在兩次快照之間崩潰,可能會丟失部分數據

- 第二種方式AOF(Append-Only File)
它是將 Redis 執行的所有寫命令(如SET、INCR)追加到日志文件(默認名為appendonly.aof),同時在AOF 文件過大時,通過BGREWRITEAOF命令對日志進行瘦身(合并重復命令):創建一個新的AOF文件來替代現有的AOF文件,新舊兩個文件所保存的數據庫狀態是相同的,但 新 AOF文件 去掉 老的 冗余命令,通常體積會較舊AOF文件小很多,達到壓縮 AOF 文件體積的目的。
重寫瘦身觸發方式:【1】手動:BGREWRITEAOF 命令?!?】自動:滿足 auto-aof-rewrite-percentage(默認 100%)和 auto-aof-rewrite-min-size(默認 64MB)條件時觸發。
這個重寫操作是異步的嗎?Redis是利用子線程進行復制拷貝,總結來說就是 一個拷貝,兩處日志。?復制過程 不會卡主線程?,整個過程是讓子進程干活,主線程繼續服務用戶。兩處日志分別指:【1】主線程正常處理新操作,把命令記錄到? AOF 緩沖區 ,異步刷新到 原來的AOF日志?里(比如每秒刷一次磁盤)? 【2】同時,新操作還會被額外記錄到? AOF重做緩沖區?,等小弟整理完舊日志后,這些新操作會被追加到新的AOF文件里,保證數據不丟失?
那么AOF這種方式的異步點在哪里呢?
寫操作:主線程將命令追加到 aof_buf 內存緩沖區(非阻塞)
刷盤策略:根據配置異步刷盤
appendfsync always # 同步寫盤(強一致,性能差)
appendfsync everysec # 每秒異步刷盤(推薦-默認)
appendfsync no # 依賴操作系統刷盤

這種持久化方式數據安全性高(最多丟失 1 秒數據),支持命令級恢復,但是文件體積大,恢復速度慢,因為是很多命令嘛。
那么Redis是采用哪種方式呢?是同時開啟 RDB 和 AOF喔,利用 RDB 的快速恢復能力和 AOF 的數據安全性,重啟時,優先加載RDB恢復數據,再重放AOF增量操作。
end. 參考
- https://mp.weixin.qq.com/s/pFwnTOQSovy_nMrg0QQk-Q 【尼恩---Redis 為啥那么快?怎么實現 100W并發?說出 這 6大架構,面試官跪 了】
- http://www.rzrgm.cn/yidengjiagou/p/17239149.html 【為什么Redis不直接使用C語言的字符串?】
- https://mp.weixin.qq.com/s?__biz=MzkxNzIyMTM1NQ==&mid=2247501801&idx=1&sn=36ec0db469080065aac9ff6f15900f08&scene=21#wechat_redirect 【尼恩-跳躍表】

浙公網安備 33010602011771號