溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Redis數(shù)據(jù)庫結(jié)構(gòu)和持久化分別是什么

發(fā)布時間:2021-06-23 14:52:59 來源:億速云 閱讀:147 作者:chen 欄目:大數(shù)據(jù)

本篇內(nèi)容介紹了“Redis數(shù)據(jù)庫結(jié)構(gòu)和持久化分別是什么”的有關(guān)知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠?qū)W有所成!

Redis數(shù)據(jù)庫,持久化

數(shù)據(jù)庫

Redis服務(wù)器將所有的數(shù)據(jù)庫都保存在服務(wù)器狀態(tài)redis.h/redisServer結(jié)構(gòu)的db數(shù)組中,每個項目都是一個redis.h/redisDb結(jié)構(gòu),每個redisDb結(jié)構(gòu)代表一個數(shù)據(jù)庫。

struct redisServer {
    //...
    // 一個數(shù)組,保存這服務(wù)器中的所有數(shù)據(jù)庫
    redisDb *db;
    // 服務(wù)器數(shù)據(jù)庫數(shù)量
    int dbnum;
    // ...
}

服務(wù)器初始化的時候,程序會根據(jù)服務(wù)器狀態(tài)的dbnum屬性來決定創(chuàng)建多少個數(shù)據(jù)庫,這個屬性值由redis.conf中配置項database選項決定,值默認是16。

切換數(shù)據(jù)庫使用select +數(shù)據(jù)庫號來切換,默認是0號數(shù)據(jù)庫。例如:select 9。

在服務(wù)器內(nèi)部,客戶端狀態(tài)redisClient結(jié)構(gòu)的db屬性記錄了客戶端當前的目標數(shù)據(jù)庫,這是個指向redisDb結(jié)構(gòu)的指針。

typedef struct redisClient {
    // ...
    // 記錄客戶端當前正在使用的數(shù)據(jù)庫
    redisDb *db;
    // ...
} redisClient;

鍵空間

Redis是一個key-value數(shù)據(jù)庫服務(wù)器,而每個數(shù)據(jù)庫都有redis.h/redisDb結(jié)構(gòu)表示,其中dict字典(hash表)保存了數(shù)據(jù)庫中所有的鍵值對,我們將這個字典成為鍵空間(key space)。

typedef struct redisDb {
	// ...
    // 數(shù)據(jù)庫鍵空間,保存數(shù)據(jù)庫中的所有鍵值對
    dict *dict;
    // ...
} redisDb;

鍵空間和用戶所見的數(shù)據(jù)庫是直接對應(yīng)的:

鍵空間的鍵也就是數(shù)據(jù)庫的鍵,每一個鍵都是一個字符串對象。

鍵空間的值也就是數(shù)據(jù)庫的值,每個值都可以是字符串對象,列表對象,hash表對象,集合對象和有序?qū)ο笾械钠渲幸环N。

圖解:

redis> SET message "hello world"
OK
redis> RPUSH alphabet a b c
(integer) 3
redis> HMSET book name "Redis Book" author "xxx" publisher "yyy"
(integer) 3

Redis數(shù)據(jù)庫結(jié)構(gòu)和持久化分別是什么

EXPIRE

  • SETEX: 這個命令只能用于字符串對象,在設(shè)置值的時候一并設(shè)置過期時間。

  • EXPIRE key ttl: 用于設(shè)置key存活時間ttl秒

  • PEXPIRE key ttl: 用于設(shè)置key存活時間ttl毫秒

  • EXPIREAT key timestamp: 設(shè)置key在timestamp所指定的秒數(shù)時間戳過期

  • PEXPIREAT key timestamp: 設(shè)置key在timestamp所指定的毫秒數(shù)時間戳過期

命令有多種,但是最終的執(zhí)行在底層都會全部轉(zhuǎn)換成PEXPIRE命令,即設(shè)置key在多少時間戳的時候過期。

過期時間并沒有直接維護在鍵空間dict字典中,想想,鍵過期之后,我們肯定需要清理內(nèi)存,這個時候如果一個一個全部遍歷所有的鍵,效率就太低了,因此redis采用了新的字典來保存過期時間。redisDb結(jié)構(gòu)的expires字典保存了數(shù)據(jù)庫中所有鍵的過期時間。同樣也是dict(hash表)結(jié)構(gòu)

typedef struct redisDb {
    // ...
    // 過期字典,保存著鍵的過期時間
    dict *expires;
    // ...
} redisDb;

圖解

Redis數(shù)據(jù)庫結(jié)構(gòu)和持久化分別是什么

過期鍵的刪除策略

既然鍵會過期,那肯定需要將其移除,避免其一直占用到內(nèi)存。那么對于怎么刪除過期的鍵,這個問題可能存在以下幾種不同的策略,我們先來看一看。

  • 定時刪除: 在設(shè)置鍵的過期時間的同時,創(chuàng)建一個定時器timer,讓定時器在鍵的過期時間來臨時,立即執(zhí)行對鍵的刪除操作。

  • 惰性刪除: 放任過期的鍵不管,但是每次從鍵空間中獲取鍵時,都檢查取得的鍵是否過期,如果過期的話就刪除,如果沒有過期就返回。

  • 定期刪除: 每隔一段時間,程序就對數(shù)據(jù)庫進行一次檢查,刪除里面的過期鍵。至于刪除多少過期鍵,以及要檢查多少個數(shù)據(jù)庫,由算法來決定。

定時刪除

定時刪除通過使用定時器,該策略可以保證過期鍵會盡可能快的被刪除,并釋放過期鍵所占用的內(nèi)存。

但是缺點是它對CPU時間是最不友好的,在過期鍵比較多的情況下,刪除鍵這一行為可能會占用相當一部分CPU時間,在內(nèi)存不緊張但是CPU時間很緊張的情況下,將CPU時間用在刪除和當前任務(wù)無關(guān)的過期鍵上,無疑是種浪費。

除此之外,創(chuàng)建一個定時器需要用到Redis服務(wù)器中的時間事件,而當前時間事件的實現(xiàn)方式是無需鏈表,因此并不能高效的處理大量的時間事件,而且還需要創(chuàng)建大量的定時器。

當過期鍵過多的時候,這種方式有點不太現(xiàn)實。

惰性刪除

惰性刪除策略對CPU時間來說是最又好的,程序只會在取出鍵時才對鍵進行過期檢查,這可以保證刪除過期鍵的操作只會在非做不可的情況下進行,并且刪除的目標僅限于當前鍵,該策略不會在刪除其他無關(guān)的過期鍵上話費任何CPU時間。

但是這種方式也有缺點,就是對內(nèi)存是最不友好的。想想如果一個鍵已經(jīng)過期,而這個鍵又仍然保存在數(shù)據(jù)庫中,那么只要我們不訪問這個鍵,那么這個鍵就永遠不會被刪除,它所占用的內(nèi)存就不會釋放(預(yù)防杠精,修正一下,down機等事故不算在內(nèi))。

使用此種策略時,如果數(shù)據(jù)庫中存在非常多的過期鍵,而這些件又恰好沒有被訪問到,這時redis就呵呵了吧~~

定期刪除

從上面定時刪除和惰性刪除來看,這兩種方式在單一使用時都有明顯的缺陷。

定時刪除占用太多CPU時間,影響服務(wù)器的響應(yīng)時間和吞吐量。

多想刪除浪費太多內(nèi)存,有內(nèi)存泄漏的危險。

定期刪除策略當然就是為了折中這倆東西,這種策略采用每隔一段時間執(zhí)行一次刪除過期鍵的操作,并限制刪除操作執(zhí)行的時長和頻率來減少刪除對CPU時間的影響。通過定期刪除,有效的見啥過期鍵帶來的內(nèi)存浪費。

那么問題來了,每次執(zhí)行時長多少合適,頻率多少合適呢?如果執(zhí)行的太頻繁或者時間太長,是不是又退化成了定時刪除。如果執(zhí)行的太少,或者時間太短,那么也會出現(xiàn)內(nèi)存浪費的情況。

因此這種情況,就需要很有經(jīng)驗的大佬來根據(jù)情況指定。接下來說說Redis的刪除策略。

Redis的過期鍵刪除策略

Redis服務(wù)器在實際使用的是惰性刪除和定期刪除兩種策略,通過配合使用,服務(wù)器可以很好的在合理使用CPU時間和避免浪費內(nèi)存空間之間取得平衡。

其實就是在定期刪除實現(xiàn)的同時,在取值的時候也加上過期驗證而已,很好理解吧。

過期鍵對RDB、AOF、復(fù)制的影響

因為采用了定期刪除的策略,因此肯定存在過期了但是還來不及刪除的情況,這種情況對于Redis的持久化和主從復(fù)制有什么影響呢?呵呵,TM的當然沒有影響,有影響就是bug了。

RDB

如果開啟了RDB功能

  • 如果是master,載入RDB文件時程序會對文件保存的鍵進行檢查,只載入未過期的鍵。

  • 如果是slave: 載入所有,但是不影響,因為主從服務(wù)器在進行同步的時候,從服務(wù)器的數(shù)據(jù)庫就會被清空。

AOF

AOF模式持久化運行時,如果是數(shù)據(jù)庫某個鍵已經(jīng)過期,對AOF不會產(chǎn)生影響,AOF一樣會將此鍵記錄,當鍵被刪除的時候,程序會向AOF文件追加一條DEL命令,來顯示的記錄該鍵已被刪除。

AOF重寫過程中,和生成RDB文件類似,過期的鍵不會保存到重寫后的AOF文件中。

復(fù)制

當運行在復(fù)制模式下時:

  • 主服務(wù)器在刪除一個過期鍵之后,會顯式的向所有從服務(wù)器發(fā)送一個DEL命令,告知從服務(wù)器刪除這個過期鍵。

  • 從服務(wù)器再執(zhí)行客戶端發(fā)送的讀命令時,即使碰到過期鍵也不會將其刪除,當然如果過期了,由于會判斷,因此也不會向客戶端返回。

  • 從服務(wù)器只有在接到主服務(wù)發(fā)來的DEL命令之后,才會刪除過期鍵。

通過由主服務(wù)器來控制從服務(wù)器統(tǒng)一地刪除過期鍵,可以保證主從服務(wù)器的數(shù)據(jù)一致性。

RDB持久化

生成RDB文件有兩個命令。

SAVE: 阻塞服務(wù)器進程,阻塞期間不能處理任何命令請求,直到RDB文件創(chuàng)建完畢。

BGSAVE: 派生子進程,由子進程負責創(chuàng)建RDB文件,主進程繼續(xù)處理命令請求。

RDB文件的載入是在服務(wù)器啟動的時候自動執(zhí)行的,所有Redis并沒有專門用于載入RDB文件的命令,只要服務(wù)器再啟動的時候檢測到RDB文件存在,就會自動載入RDB文件(AOF未開啟的情況下)。

看日志:

32188:M 25 Jun 2019 18:34:01.999 # Server initialized
32188:M 25 Jun 2019 18:34:01.999 * DB loaded from disk: 0.000 seconds
32188:M 25 Jun 2019 18:34:01.999 * Ready to accept connections

自動間隔保存

Redis允許用戶通過配置服務(wù)器的save選項,讓服務(wù)器每隔一點時間自動執(zhí)行一次BGSAVE命令。

用戶可以設(shè)置多個保存條件,只要其中任一滿足,就會執(zhí)行BGSAVE命令。

舉例:

# 在900秒之內(nèi),對數(shù)據(jù)庫至少進行了1次修改
save 900 1
# 在300秒之內(nèi),對數(shù)據(jù)庫至少進行了10次修改
save 300 10
# 在60秒之內(nèi),對數(shù)據(jù)庫至少進行了10000次修改
save 60 10000

服務(wù)器redisServer維護了dirty和lastsave屬性,用來保存修改計數(shù)和上一次執(zhí)行保存的時間。

struct redisServer {
    // ...
    // 修改計數(shù)器
    long long dirty;
    // 上一次執(zhí)行保存的時間
    time_t lastsave;
    // ...
}

每次服務(wù)器成功執(zhí)行一個修改命令之后,程序就會讀dirty計數(shù)器進行更新。

為了檢查保存條件是否滿足,Redis的服務(wù)器周期性操作函數(shù)serverCron默認每隔100毫秒就會執(zhí)行一次,該函數(shù)用于對正在運行的服務(wù)器進行維護,他的其中一項工作就是檢查所設(shè)置的保存條件是否滿足,如果滿足,就執(zhí)行BGSAVE命令。

RDB文件結(jié)構(gòu)

Redis數(shù)據(jù)庫結(jié)構(gòu)和持久化分別是什么

  • REDIS: 這部分長5個字節(jié),保存著“REDIS” 5個字符,通過這5個字符,程序可以在載入文件時,快速檢查所載入文件是否是RDB文件。

  • db_version: 長4個字節(jié),它的值是一個字符串表示的整數(shù),記錄了RDB的版本號。

  • database: 這部分包含著0個或任意多個數(shù)據(jù)庫以及各個數(shù)據(jù)庫中的鍵值對數(shù)據(jù),如果數(shù)據(jù)庫為空,那么這個部分也為空,長度為0字節(jié)。

  • EOF: 一個常量,長度為1字節(jié),這個常量標志著RDB文件正文內(nèi)容結(jié)束。

  • check_sum: 一個8字節(jié)無符號整數(shù),保存著一個校驗和,通過對之前4個部分的內(nèi)容進行計算得出的,用啦檢查RDB文件是否有出錯或者損壞的情況出現(xiàn)。

databases部分

一個RDB文件的databases部分可以保存任意多個非空數(shù)據(jù)庫。

比如0號和3號數(shù)據(jù)庫非空,那么將創(chuàng)建如下一RDB文件,database 0代表0好數(shù)據(jù)庫中的所有鍵值對數(shù)據(jù),database 3代表3號數(shù)據(jù)庫中的所有鍵值對數(shù)據(jù)。

Redis數(shù)據(jù)庫結(jié)構(gòu)和持久化分別是什么

每個非空數(shù)據(jù)庫database num在RDB文件都可以保存為SELECTDB、db_number、key_value_pair三個部分。

Redis數(shù)據(jù)庫結(jié)構(gòu)和持久化分別是什么

  • SELECTDB: 常量,1字節(jié),當程序遇到這個值的時候,就知道即將讀入的是一個數(shù)據(jù)庫號碼。

  • db_num: 保存著一個數(shù)據(jù)庫號碼,根據(jù)號碼的大小不同,這個部分長度科可以是1個2個或5個字節(jié)。

  • key_value_pair: 保存著所有的鍵值對。

當讀入數(shù)據(jù)庫號碼之后,服務(wù)器就會調(diào)用SELECT命令切換數(shù)據(jù)庫,使得之后讀入的鍵值對可以載入到正確的數(shù)據(jù)庫中。

日常貼個圖:

Redis數(shù)據(jù)庫結(jié)構(gòu)和持久化分別是什么

key_value_pair

這個部分保存著所有的鍵值對,如果鍵值對帶有過期時間,那么過期時間也會被保存在內(nèi)。

不帶過期時間的鍵值對在RDB文件中由TYPE、key、value三部分組成。

帶有過期時間的鍵值對在RDB文件中由EXPIRETIME_MS、ms、TYPE、key、value五部分組成。

日常貼個圖。

Redis數(shù)據(jù)庫結(jié)構(gòu)和持久化分別是什么")

TYPE表明了當前key的類型(比如REDIS_RDB_TYPE_SET),決定了接下來redis如何讀入和解釋value數(shù)據(jù)。

key總是一個字符串對象。

value會根據(jù)TYPE類型的不同以及保存內(nèi)容長度的不同而有所不同,這部分就不一一解釋了,喜歡的自己去百度吧。

AOF持久化

AOF持久化功能的實現(xiàn)可以分為追加(append)、文件寫入、文件同步(sync)三個步驟。

append

當AOF持久化功能打開時,服務(wù)器再執(zhí)行完一個寫命令之后,會以協(xié)議的格式將其追加到服務(wù)器狀態(tài)的aof_buf緩沖區(qū)末尾。

struct redisServer {
    // ...
    // AOF緩沖區(qū)
    sds aof_buf;
    // ...
}

比如:

redis> set mKey mValue
OK

那么服務(wù)器知性溫婉之后會將其轉(zhuǎn)成協(xié)議的格式,追加到aof_buf緩沖區(qū)末尾

協(xié)議格式
*3\r\n$3\r\nSET\r\n$4\r\nmKey\r\n$6\r\nmValue\r\n

文件寫入和sync

Rdis的服務(wù)器進程就是一個事件循環(huán),這個循環(huán)中的文件事件負責接收客戶端的命令請求,以及向客戶端發(fā)送命令回復(fù),而時間事件則負責執(zhí)行想serverCron函數(shù)這樣需要定時運行的函數(shù)。

因為服務(wù)器在處理文件事件時可能會執(zhí)行寫命令,使得這些命令內(nèi)容被追加到aof_buf緩沖區(qū),所有在服務(wù)器每次結(jié)束一個事件循環(huán)之前,他都會調(diào)用相應(yīng)函數(shù)(flushAppendOnlyFile),考慮是否需要將aof_buf緩沖區(qū)的內(nèi)容寫入和保存到AOF文件中。

flushAppendOnlyFile函數(shù)的行為有服務(wù)器配置appendfsync選項的值來決定。

appendfsync值函數(shù)行為
always將aof_buf緩沖區(qū)的所有內(nèi)容寫入并同步到AOF文件
everysec【系統(tǒng)默認值】將aof_buf緩沖區(qū)中的內(nèi)容寫入到AOF文件,如果上次同步AOF文件的時間已經(jīng)超過一秒鐘,那么再次對AOF文件進行同步,并且這個同步操作是由一個線程專門負責執(zhí)行的
no將aof_buf緩沖區(qū)中的所有內(nèi)容寫入到AOF文件,單并不對AOF文件進行同步,合適同步由操作系統(tǒng)來決定

載入

AOF的優(yōu)先級比RDB高哦!

因為AOF文件里面包含了重建數(shù)據(jù)庫狀態(tài)所需的所有寫命令,所以服務(wù)器只要讀入并重新執(zhí)行一遍AOF文件里面保存的寫命令,就可以還原服務(wù)器狀態(tài)了。因為redis的命令只能在客戶端上下文中執(zhí)行,所以這里需要使用一個偽客戶端(fake client)。

流程圖:

Redis數(shù)據(jù)庫結(jié)構(gòu)和持久化分別是什么

AOF重寫

因為AOF持久化是通過保存被執(zhí)行的寫命令來記錄數(shù)據(jù)庫狀態(tài)的,隨著服務(wù)器運行時間的增加,AOF文件中的內(nèi)容會越來越多,文件的體積也會越來越大,還原所需的時間也就越多。

比如說一些過期的鍵,在最開始會被寫入,然后后續(xù)又會被DEL,又或者我們使用了很多條RPUSH命令來操作一個key的列表數(shù)據(jù),等等之類的情況。

這個時候就需要重寫AOF文件,使用新的文件替換掉舊的文件。文件的重寫不需要對現(xiàn)有的AOF文件進行任何讀取分析等操作,而是根據(jù)當前數(shù)據(jù)庫狀態(tài)來實現(xiàn)的。

當然,如果列表,hash表,集合。,有序集合這四總可能會帶有多個元素的鍵時,會先檢查數(shù)量,如果過多的話,是會分成多條命令來記錄的,而不是單單使用一條命令,REDIS_AOF_REWRITE_ITEMS_PRE_CMD常量的值(64)決定了這個數(shù)量,也就是說每條指令最多將寫入64個元素,剩下的將繼續(xù)判斷并決定用幾條指令來寫入

后臺重寫

AOF的重寫也是有子程序執(zhí)行,這樣主程序可以繼續(xù)執(zhí)行命令請求。這個時候服務(wù)器接收的新的命令也有可能對數(shù)據(jù)庫進行了修改,從而導(dǎo)致當前數(shù)據(jù)庫狀態(tài)和重寫后的AOF文件所保存的數(shù)據(jù)庫狀態(tài)不一致。

因此Redis服務(wù)器設(shè)置了一個AOF重寫緩沖區(qū),這個緩沖區(qū)在服務(wù)器創(chuàng)建子進程之后開始使用,當執(zhí)行完一個寫命令之后,它會同時將這個寫命令發(fā)送給AOF緩沖區(qū)和AOF重寫緩沖區(qū)。

這樣一來AOF緩沖區(qū)的內(nèi)容會定期被寫入和同步到AOF文件,對現(xiàn)有的AOF文件的處理工作照常進行。

當完成AOF重寫工作后,子進程會向父進程發(fā)送一個信號,父進程接收到信號之后,調(diào)用一個信號函數(shù)處理器,并執(zhí)行以下工作:

  1. 將AOF重寫緩沖區(qū)的所有內(nèi)容寫入到新AOF文件中,這是新AOF文件數(shù)據(jù)庫狀態(tài)和當前一致。

  2. 對新的AOF文件進行重命名,原子的(atomic)覆蓋現(xiàn)有的AOF文件,完成新舊AOF文件的替換。

日常流程:

時間服務(wù)器進程(父進程)子進程
T1SET k1 v1
T2SET k1 v2
T3SET k1 v3
T4創(chuàng)建子進程,執(zhí)行AOF文件重寫開始AOF文件重寫
T5SET k2 v2執(zhí)行重寫操作
T6SET k3 v3執(zhí)行重寫操作
T7SET k4 v4完成AOF重寫,向父進程發(fā)送信號
T8接收到子進程發(fā)來的信號,將命令SET k2 v2,SET k3 v3,SET k4 v4追加到新的AOF文件末尾
T9用新AOF文件覆蓋舊AOF文件

“Redis數(shù)據(jù)庫結(jié)構(gòu)和持久化分別是什么”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實用文章!

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI