溫馨提示×

溫馨提示×

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

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

從零單排學Redis【白銀】

發(fā)布時間:2020-08-09 15:33:41 來源:ITPUB博客 閱讀:163 作者:Java3y 欄目:編程語言

前言

只有光頭才能變強

今天繼續(xù)來學習Redis,上一篇從零單排學Redis【青銅】已經將Redis常用的數(shù)據(jù)結構過了一遍了。如果還沒看的同學可以先去看一遍再回來~

這篇主要講的內容有:

  • Redis服務器的數(shù)據(jù)庫

  • Redis對過期鍵的處理

  • Redis持久化策略(RDB和AOF)

本文力求簡單講清每個知識點,希望大家看完能有所收獲

一、Redis服務器中的數(shù)據(jù)庫

我們應該都用過MySQL,MySQL我們可以在里邊創(chuàng)建好幾個庫:

從零單排學Redis【白銀】

同樣地,Redis服務器中也有數(shù)據(jù)庫這么一個概念。如果不指定具體的數(shù)量,默認會有16個數(shù)據(jù)庫。

從零單排學Redis【白銀】

上面的命令我們也可以發(fā)現(xiàn):當切換到15號數(shù)據(jù)庫,存進15號庫的數(shù)據(jù),再切換到0號數(shù)據(jù)庫時,是獲取不到的!

  • 這說明,數(shù)據(jù)庫與數(shù)據(jù)庫之間的數(shù)據(jù)是隔離的。

1.1Redis數(shù)據(jù)庫的原理

Redis服務器用redisServer結構體來表示,其中redisDb是一個數(shù)組,用來保存所有的數(shù)據(jù)庫,dbnum代表數(shù)據(jù)庫的數(shù)量(這個可以配置,默認是16)

struct redisServer{  

    //redisDb數(shù)組,表示服務器中所有的數(shù)據(jù)庫
    redisDb *db;  

    //服務器中數(shù)據(jù)庫的數(shù)量
    int dbnum;  

}; 

我們知道Redis是C/S結構,Redis客戶端通過redisClient結構體來表示:

typedef struct redisClient{  

    //客戶端當前所選數(shù)據(jù)庫
    redisDb *db;  

}redisClient;

Redis客戶端連接Redis服務端時的示例圖:

從零單排學Redis【白銀】

Redis中對每個數(shù)據(jù)庫用redisDb結構體來表示:

typedef struct redisDb { 
    int id;         // 數(shù)據(jù)庫ID標識
    dict *dict;     // 鍵空間,存放著所有的鍵值對              
    dict *expires;  // 過期哈希表,保存著鍵的過期時間                          
    dict *watched_keys; // 被watch命令監(jiān)控的key和相應client    
    long long avg_ttl;  // 數(shù)據(jù)庫內所有鍵的平均TTL(生存時間)     
} redisDb;

從代碼上我們可以發(fā)現(xiàn)最重要的應該是dict *dict,它用來存放著所有的鍵值對。對于dict數(shù)據(jù)結構(哈希表)我們在上一篇也已經詳細說了。一般我們將存儲所有鍵值對的dict稱為鍵空間。

鍵空間示意圖:

從零單排學Redis【白銀】

Redis的數(shù)據(jù)庫就是使用字典(哈希表)來作為底層實現(xiàn)的,對數(shù)據(jù)庫的增刪改查都是構建在字典(哈希表)的操作之上的。

例如:

redis > GET message

"hello world"
從零單排學Redis【白銀】

1.2鍵的過期時間

Redis是基于內存,內存是比較昂貴的,容量肯定比不上硬盤的。就我們現(xiàn)在一臺普通的機子,可能就8G內存,但硬盤隨隨便便都1T了。

因為我們的內存是有限的。所以我們會干掉不常用的數(shù)據(jù),保留常用的數(shù)據(jù)。這就需要我們設置一下鍵的過期(生存)時間了。

  • 設置鍵的生存時間可以通過EXPIRE或者PEXPIRE命令。

  • 設置鍵的過期時間可以通過EXPIREAT或者PEXPIREAT命令。

其實EXPIRE、PEXPIREEXPIREAT這三個命令都是通過PEXPIREAT命令來實現(xiàn)的。

我們在redisDb結構體中還發(fā)現(xiàn)了dict *expires;屬性,存放所有鍵過期的時間。

舉個例子基本就可以理解了:

redis > PEXPIREAT message 1391234400000
(integer) 1

設置了message鍵的過期時間為1391234400000

從零單排學Redis【白銀】

既然有設置過期(生存)時間的命令,那肯定也有移除過期時間,查看剩余生存時間的命令了:

  • PERSIST(移除過期時間)

  • TTL(Time To Live)返回剩余生存時間,以秒為單位

  • PTTL以毫秒為單位返回鍵的剩余生存時間

1.2.1過期策略

上面我們已經能夠了解到:過期鍵是保存在哈希表中了。那這些過期鍵到了過期的時間,就會立馬被刪除掉嗎??

要回答上面的問題,需要我們了解一下刪除策略的知識,刪除策略可分為三種

  • 定時刪除(對內存友好,對CPU不友好)

    • 到時間點上就把所有過期的鍵刪除了。

  • 惰性刪除(對CPU極度友好,對內存極度不友好)

    • 每次從鍵空間取鍵的時候,判斷一下該鍵是否過期了,如果過期了就刪除。

  • 定期刪除(折中)

    • 每隔一段時間去刪除過期鍵,限制刪除的執(zhí)行時長和頻率。

Redis采用的是惰性刪除+定期刪除兩種策略,所以說,在Redis里邊如果過期鍵到了過期的時間了,未必被立馬刪除的!

1.2.2內存淘汰機制

如果定期刪除漏掉了很多過期key,也沒及時去查(沒走惰性刪除),大量過期key堆積在內存里,導致redis內存塊耗盡了,咋整?

我們可以設置內存最大使用量,當內存使用量超出時,會施行數(shù)據(jù)淘汰策略。

Redis的內存淘汰機制有以下幾種:

從零單排學Redis【白銀】

一般場景:

使用 Redis 緩存數(shù)據(jù)時,為了提高緩存命中率,需要保證緩存數(shù)據(jù)都是熱點數(shù)據(jù)??梢詫却孀畲笫褂昧吭O置為熱點數(shù)據(jù)占用的內存量,然后啟用allkeys-lru淘汰策略,將最近最少使用的數(shù)據(jù)淘汰

二、Redis持久化

Redis是基于內存的,如果不想辦法將數(shù)據(jù)保存在硬盤上,一旦Redis重啟(退出/故障),內存的數(shù)據(jù)將會全部丟失。

  • 我們肯定不想Redis里頭的數(shù)據(jù)由于某些故障全部丟失(導致所有請求都走MySQL),即便發(fā)生了故障也希望可以將Redis原有的數(shù)據(jù)恢復過來,這就是持久化的作用。

Redis提供了兩種不同的持久化方法來講數(shù)據(jù)存儲到硬盤里邊:

  • RDB(基于快照),將某一時刻的所有數(shù)據(jù)保存到一個RDB文件中。

  • AOF(append-only-file),當Redis服務器執(zhí)行寫命令的時候,將執(zhí)行的寫命令保存到AOF文件中。

2.1RDB(快照持久化)

RDB持久化可以手動執(zhí)行,也可以根據(jù)服務器配置定期執(zhí)行。RDB持久化所生成的RDB文件是一個經過壓縮的二進制文件,Redis可以通過這個文件還原數(shù)據(jù)庫的數(shù)據(jù)。

從零單排學Redis【白銀】

有兩個命令可以生成RDB文件:

  • SAVE阻塞Redis服務器進程,服務器不能接收任何請求,直到RDB文件創(chuàng)建完畢為止。

  • BGSAVE創(chuàng)建出一個子進程,由子進程來負責創(chuàng)建RDB文件,服務器進程可以繼續(xù)接收請求。

Redis服務器在啟動的時候,如果發(fā)現(xiàn)有RDB文件,就會自動載入RDB文件(不需要人工干預)

  • 服務器在載入RDB文件期間,會處于阻塞狀態(tài),直到載入工作完成。

除了手動調用SAVE或者BGSAVE命令生成RDB文件之外,我們可以使用配置的方式來定期執(zhí)行:

在默認的配置下,如果以下的條件被觸發(fā),就會執(zhí)行BGSAVE命令

    save 900 1              #在900秒(15分鐘)之后,至少有1個key發(fā)生變化,
    save 300 10            #在300秒(5分鐘)之后,至少有10個key發(fā)生變化
    save 60 10000        #在60秒(1分鐘)之后,至少有10000個key發(fā)生變化

原理大概就是這樣子的(結合上面的配置來看):

struct redisServer{
    // 修改計數(shù)器
    long long dirty;

    // 上一次執(zhí)行保存的時間
    time_t lastsave;

    // 參數(shù)的配置
    struct saveparam *saveparams;
};

遍歷參數(shù)數(shù)組,判斷修改次數(shù)和時間是否符合,如果符合則調用besave()來生成RDB文件

從零單排學Redis【白銀】

總結:通過手動調用SAVE或者BGSAVE命令或者配置條件觸發(fā),將數(shù)據(jù)庫某一時刻的數(shù)據(jù)快照,生成RDB文件實現(xiàn)持久化。

2.2AOF(文件追加)

上面已經介紹了RDB持久化是通過將某一時刻數(shù)據(jù)庫的數(shù)據(jù)“快照”來實現(xiàn)的,下面我們來看看AOF是怎么實現(xiàn)的。

  • AOF是通過保存Redis服務器所執(zhí)行的寫命令來記錄數(shù)據(jù)庫的數(shù)據(jù)的。

從零單排學Redis【白銀】

比如說我們對空白的數(shù)據(jù)庫執(zhí)行以下寫命令:

redis> SET meg "hello"
OK

redis> SADD fruits "apple" "banana" "cherry"
(integer) 3

redis> RPUSH numbers 128 256 512
(integer) 3 

Redis會產生以下內容的AOF文件:

從零單排學Redis【白銀】

這些都是以Redis的命令請求協(xié)議格式保存的。Redis協(xié)議規(guī)范(RESP)參考資料:

  • https://www.cnblogs.com/tommy-huang/p/6051577.html

AOF持久化功能的實現(xiàn)可以分為3個步驟:

  • 命令追加:命令寫入aof_buf緩沖區(qū)

  • 文件寫入:調用flushAppendOnlyFile函數(shù),考慮是否要將aof_buf緩沖區(qū)寫入AOF文件中

  • 文件同步:考慮是否將內存緩沖區(qū)的數(shù)據(jù)真正寫入到硬盤

從零單排學Redis【白銀】

flushAppendOnlyFile函數(shù)的行為由服務器配置的appendfsyn選項來決定的:

    appendfsync always     # 每次有數(shù)據(jù)修改發(fā)生時都會寫入AOF文件。
    appendfsync everysec   # 每秒鐘同步一次,該策略為AOF的默認策略。
    appendfsync no         # 從不同步。高效但是數(shù)據(jù)不會被持久化。

從字面上應該就更好理解了,這里我就不細說了…

下面來看一下AOF是如何載入與數(shù)據(jù)還原的:

  • 創(chuàng)建一個偽客戶端(本地)來執(zhí)行AOF的命令,直到AOF命令被全部執(zhí)行完畢。

從零單排學Redis【白銀】

2.2.1AOF重寫

從前面的示例看出,我們寫了三條命令,AOF文件就保存了三條命令。如果我們的命令是這樣子的:

redis > RPUSH list "Java" "3y"
(integer)2

redis > RPUSH list "Java3y"
integer(3)

redis > RPUSH list "yyy"
integer(4)

同樣地,AOF也會保存3條命令。我們會發(fā)現(xiàn)一個問題:上面的命令是可以合并起來成為1條命令的,并不需要3條。這樣就可以讓AOF文件的體積變得更小

AOF重寫由Redis自行觸發(fā)(參數(shù)配置),也可以用BGREWRITEAOF命令手動觸發(fā)重寫操作。

  • 要值得說明的是:AOF重寫不需要對現(xiàn)有的AOF文件進行任何的讀取、分析。AOF重寫是通過讀取服務器當前數(shù)據(jù)庫的數(shù)據(jù)來實現(xiàn)的!

比如說現(xiàn)在有一個Redis數(shù)據(jù)庫的數(shù)據(jù)如下:

從零單排學Redis【白銀】

新的AOF文件的命令如下,沒有一條是多余的!

從零單排學Redis【白銀】

2.2.2AOF后臺重寫

Redis將AOF重寫程序放到子進程里執(zhí)行(BGREWRITEAOF命令),像BGSAVE命令一樣fork出一個子進程來完成重寫AOF的操作,從而不會影響到主進程。

AOF后臺重寫是不會阻塞主進程接收請求的,新的寫命令請求可能會導致當前數(shù)據(jù)庫和重寫后的AOF文件的數(shù)據(jù)不一致!

為了解決數(shù)據(jù)不一致的問題,Redis服務器設置了一個AOF重寫緩沖區(qū),這個緩存區(qū)會在服務器創(chuàng)建出子進程之后使用

從零單排學Redis【白銀】

2.3RDB和AOF對過期鍵的策略

RDB持久化對過期鍵的策略:

  • 執(zhí)行SAVE或者BGSAVE命令創(chuàng)建出的RDB文件,程序會對數(shù)據(jù)庫中的過期鍵檢查,已過期的鍵不會保存在RDB文件中。

  • 載入RDB文件時,程序同樣會對RDB文件中的鍵進行檢查,過期的鍵會被忽略。

RDB持久化對過期鍵的策略:

  • 如果數(shù)據(jù)庫的鍵已過期,但還沒被惰性/定期刪除,AOF文件不會因為這個過期鍵產生任何影響(也就說會保留),當過期的鍵被刪除了以后,會追加一條DEL命令來顯示記錄該鍵被刪除了

  • 重寫AOF文件時,程序會對RDB文件中的鍵進行檢查,過期的鍵會被忽略。

復制模式:

  • 主服務器來控制從服務器統(tǒng)一刪除過期鍵(保證主從服務器數(shù)據(jù)的一致性)

2.4RDB和AOF用哪個?

RDB和AOF并不互斥,它倆可以同時使用。

  • RDB的優(yōu)點:載入時恢復數(shù)據(jù)快、文件體積小。

  • RDB的缺點:會一定程度上丟失數(shù)據(jù)(因為系統(tǒng)一旦在定時持久化之前出現(xiàn)宕機現(xiàn)象,此前沒有來得及寫入磁盤的數(shù)據(jù)都將丟失。)

  • AOF的優(yōu)點:丟失數(shù)據(jù)少(默認配置只丟失一秒的數(shù)據(jù))。

  • AOF的缺點:恢復數(shù)據(jù)相對較慢,文件體積大

如果Redis服務器同時開啟了RDB和AOF持久化,服務器會優(yōu)先使用AOF文件來還原數(shù)據(jù)(因為AOF更新頻率比RDB更新頻率要高,還原的數(shù)據(jù)更完善)

可能涉及到RDB和AOF的配置:

redis持久化,兩種方式
1、rdb快照方式
2、aof日志方式

----------rdb快照------------
save 900 1
save 300 10
save 60 10000

stop-writes-on-bgsave-error yes
rdbcompression yes
rdbchecksum yes
dbfilename dump.rdb
dir /var/rdb/

-----------Aof的配置-----------
appendonly no # 是否打開 aof日志功能

appendfsync always #每一個命令都立即同步到aof,安全速度慢
appendfsync everysec
appendfsync no 寫入工作交給操作系統(tǒng),由操作系統(tǒng)判斷緩沖區(qū)大小,統(tǒng)一寫入到aof  同步頻率低,速度快


no-appendfsync-on-rewrite yes 正在導出rdb快照的時候不要寫aof
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb 


./bin/redis-benchmark -n 20000

官網(wǎng)文檔:

  • https://redis.io/topics/persistence#rdb-advantages

向AI問一下細節(jié)

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

AI