日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

redis核心技术与实战(三) 性能篇

發(fā)布時間:2025/3/8 编程问答 22 豆豆
生活随笔 收集整理的這篇文章主要介紹了 redis核心技术与实战(三) 性能篇 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

影響redis性能主要有以下部分:
Redis 內(nèi)部的阻塞式操作;
CPU核和NUMA架構
Redis關鍵系統(tǒng)配置
Redis內(nèi)存碎片
Redis緩沖區(qū)


下面一個個來介紹這些地方

1.《redis 有哪些阻塞點?》
redis實例主要交互的對象有以下幾點,我們依據(jù)下面這些點看看redis有哪些阻塞操作:

客戶端交互:網(wǎng)絡IO,增刪改查,數(shù)據(jù)庫操作
磁盤交互: AOF 同步磁盤,AOF重寫,RDB模式持久化
從庫交互: 數(shù)據(jù)同步,RDB文件生成,RDB文件傳輸,清空數(shù)據(jù)庫, 從庫加載RDB文件
切片集群交互:向其他實例傳輸哈希槽信息,數(shù)據(jù)遷移。
1. reids阻塞點分析
1.客戶端交互
a. redis實例與客戶端網(wǎng)絡IO交互會阻塞redis嗎?

多個客戶端 可以同時與 redis 交互,競爭redis 資源, 由于redis 使用了IO多路復用的線程模式,使redis 不會等待阻塞在某一個客戶端,不響應與其他客戶端 的交互,所以 redis與客戶端的 IO 操作 不是 阻塞點;

b. 讀寫操作

集合元素全量查詢操作 HGETALL、SMEMBERS,以及集合的聚合統(tǒng)計操作,例如求交、并和差集。

這些操作往往時間復雜度是O(n),所以這是

另外,大量數(shù)據(jù)刪除操作,也是redis 的另一個阻塞點;

因為 redis刪除操作涉及 內(nèi)存空間的釋放和管理,釋放內(nèi)存只是redis的第一步,為了更好的管理規(guī)劃內(nèi)存空間,在釋放內(nèi)存時,操作系統(tǒng)會維護一個內(nèi)存塊鏈表,把釋放的內(nèi)存空間插入空閑的內(nèi)存塊鏈表,以便后續(xù)管理使用;

當有大量內(nèi)存被釋放時,空閑的內(nèi)存塊鏈表操作時間就會增加,可能會造成redis主線程的阻塞;

所以,

2.磁盤交互
RDB快照和AOF重寫都 是 redis fork出來的子線程執(zhí)行的,只會在fork子線程的時候阻塞主線程, RDB和AOF重寫都不會造成阻塞;

但是,redis AOF模式下 有三種同步落盤操作:NO,every seconds , 同步寫日志;

NO,every seconds 都是異步執(zhí)行的 ,同步寫日志比較特殊,不靠異步子線程完成

一個同步寫磁盤的操作的耗時大約是 1~2ms,當有大量寫操作記錄在AOF日志時,并要求同步寫回的話,就會阻塞主線程;

所以,

3.主從交互
生成RDB文件,RDB文件傳輸都是 redis子線程來完成的,不阻塞;

對于從庫來說, 需要主線程 加載RDB文件后,才能執(zhí)行以后的 數(shù)據(jù)同步操作,

所以,

4.切片集群
**數(shù)據(jù)遷移:**當我們部署 Redis 切片集群時,每個 Redis 實例上分配的哈希槽信息需要在不同實例間進行傳遞,同時,當需要進行負載均衡或者有實例增刪時,數(shù)據(jù)會在不同的實例間進行遷移。不過,哈希槽的信息量不大,而數(shù)據(jù)遷移是漸進式執(zhí)行的,所以,一般來說,這兩類操作對 Redis 主線程的阻塞風險不大。

但是,如果你使用了 Redis Cluster 方案,而且同時正好遷移的是 bigkey 的話,就會造成主線程的阻塞,因為 Redis Cluster 使用了同步遷移,所以, Redis Cluster 中 大量bigkey數(shù)據(jù)遷移,可能導致 主線程;

2.關鍵路徑與非關鍵路徑
總結一下,有哪些阻塞點:

集合全量查詢和聚合操作;

bigkey 刪除和清空數(shù)據(jù)庫;

AOF 日志同步寫;

從庫加載 RDB 文件。

redis解決方案:

redis提供異步線程機制,那么以上所有阻塞點都可以用異步執(zhí)行嗎?我們來分析下;

在這之前我們先來看看什么是 關鍵路徑 ,什么是非關鍵路徑;

關鍵路徑: 客戶端需要等待redis返回具體結果,并根據(jù)結果做出操作 的是關鍵路徑,如:讀操作;

非關鍵路徑:反之,客戶端不關心 返回結果,不用給客戶端返回具體數(shù)據(jù)的 操作就是非關鍵路徑,如:刪除操作;

集合全量查詢和聚合操作: 需要給客戶端返回具體結果, 關鍵路徑;

bigkey 刪除和清空數(shù)據(jù)庫: 非關鍵路徑

AOF 日志同步寫:為了保證數(shù)據(jù)可靠性,Redis 實例需要保證 AOF 日志中的操作記錄已經(jīng)落盤,這個操作雖然需要實例等待,但它并不會返回具體的數(shù)據(jù)結果給實例。 所以,它是 非關鍵路徑。

從庫加載 RDB 文件: 庫要想對客戶端提供數(shù)據(jù)存取服務,就必須把 RDB 文件加載完成。 所以,它是關鍵路徑;

3.redis異步子線程機制
Redis 主線程啟動后,會使用操作系統(tǒng)提供的 pthread_create 函數(shù)創(chuàng)建 3 個子線程,分別由它們負責 AOF 日志寫操作、鍵值對刪除以及文件關閉的異步執(zhí)行。

主線程通過一個鏈表形式的任務隊列和子線程進行交互。當收到鍵值對刪除和清空數(shù)據(jù)庫的操作時,主線程會把這個操作封裝成一個任務,放入到任務隊列中,然后給客戶端返回一個完成信息,表明刪除已經(jīng)完成。

但實際上,這個時候刪除還沒有執(zhí)行,等到后臺子線程從任務隊列中讀取任務后,才開始實際刪除鍵值對,并釋放相應的內(nèi)存空間。因此,我們把這種異步刪除也稱為惰性刪除(lazy free)。此時,刪除或清空操作不會阻塞主線程,這就避免了對主線程的性能影響。

異步子線程機制:

注意點:異步的鍵值對刪除和數(shù)據(jù)庫清空操作是 Redis 4.0 后提供的功能,Redis 也提供了新的命令來執(zhí)行這兩個操作:

鍵值對刪除:當你的集合類型中有大量元素(例如有百萬級別或千萬級別元素)需要刪除時,我建議你使用 UNLINK 命令。

清空數(shù)據(jù)庫:可以在 FLUSHDB 和 FLUSHALL 命令后加上 ASYNC 選項,這樣就可以讓后臺子線程異步地清空數(shù)據(jù)庫,如下所示:

FLUSHDB ASYNC
FLUSHALL AYSNC
1
2
4.建議與擴展
1.建議
集合全量查詢和聚合操作:可以使用 SCAN 命令,分批讀取數(shù)據(jù),再在客戶端進行聚合計算;
從庫加載 RDB 文件:把主庫的數(shù)據(jù)量大小控制在 2~4GB 左右,以保證 RDB 文件能以較快的速度加載。
2.擴展
我們今天學習了關鍵路徑上的操作,你覺得,Redis 的寫操作(例如 SET、HSET、SADD 等)是在關鍵路徑上嗎?

需要客戶端根據(jù)業(yè)務需要來區(qū)分:

1、如果客戶端依賴操作返回值的不同,進而需要處理不同的業(yè)務邏輯,那么HSET和SADD操作算關鍵路徑,而SET操作不算關鍵路徑。因為HSET和SADD操作,如果field或member不存在時,Redis結果會返回1,否則返回0。而SET操作返回的結果都是OK,客戶端不需要關心結果有什么不同。

2、如果客戶端不關心返回值,只關心數(shù)據(jù)是否寫入成功,那么SET/HSET/SADD不算關鍵路徑,多次執(zhí)行這些命令都是冪等的,這種情況下可以放到異步線程中執(zhí)行。

3、但是有種例外情況,如果Redis設置了maxmemory,但是卻沒有設置淘汰策略,這三個操作也都算關鍵路徑。因為如果Redis內(nèi)存超過了maxmemory,再寫入數(shù)據(jù)時,Redis返回的結果是OOM error,這種情況下,客戶端需要感知有錯誤發(fā)生才行。

注意:客戶端經(jīng)常會阻塞等待發(fā)送的命令返回結果,在上一個命令還沒有返回結果前,客戶端會一直等待,直到返回結果后,才會發(fā)送下一個命令。此時,即使我們不關心返回結果,客戶端也要等到寫操作執(zhí)行完成才行。所以,在不關心寫操作返回結果的場景下,可以對 Redis 客戶端做異步改造。具體點說,就是使用異步線程發(fā)送這些不關心返回結果的命令,而不是在 Redis 客戶端中等待這些命令的結果。

2.《為什么CPU結構也會影響Redis的性能?》
1.CPU多核架構
L1包括一級指令緩存和一級數(shù)據(jù)緩存;

**物理核的私有緩存,**它其實是指緩存空間只能被當前的這個物理核使用,其他的物理核無法對這個核的緩存空間進行數(shù)據(jù)存取。

一個CPU有多個物理核(運行核心),每個物理核有自己私有的的L1,12緩存 ,這些緩存一般只有幾kb,但是訪問效率確是 納秒級別,一般不超過 10納秒;

如果 L1、L2 緩存中沒有所需的數(shù)據(jù),應用程序就需要訪問內(nèi)存來獲取數(shù)據(jù)。

而應用程序的訪存延遲一般在 百納秒級別,是訪問 L1、L2 緩存的延遲的近 10 倍,不可避免地會對性能造成影響。

所以,不同的物理核還會共享一個共同的三級緩存(Level 3 cache,簡稱為 L3 cache)

為了平衡cpu和內(nèi)存之間的差異,引入了 L3緩存(也就是CPU告訴緩存區(qū)),三級緩存不但平衡cpu L1,L2緩存與內(nèi)內(nèi)存訪問速度的差異,而且提升了 訪問容量,三級緩存容量可達到 幾MB,甚至 幾十MB ;

而且,CPU內(nèi) 多個物理核 之間共享 L3三級緩存;

另外:現(xiàn)在主流的 CPU 處理器中, 每個物理核 內(nèi)部有兩個邏輯核(超級線程),他們共享 物理核私有的L1,L2緩存;

下面看下 主流CPU架構圖:

2.CPU多核架構對Redis 性能的影響
在一個 CPU 核上運行時,應用程序需要記錄自身使用的軟硬件資源信息(例如棧指針、CPU 核的寄存器值等),我們把這些信息稱為運行時信息。

同時,應用程序訪問最頻繁的指令和數(shù)據(jù)還會被緩存到 L1、L2 緩存上,以便提升執(zhí)行速度。

但是,在多核 CPU 的場景下,一旦應用程序需要在一個新的 CPU 核上運行,那么,運行時信息就需要重新加載到新的 CPU 核上。而且,新的 CPU 核的 L1、L2 緩存也需要重新加載數(shù)據(jù)和指令,這會導致程序的運行時間增加。

當 context switch 發(fā)生后,Redis 主線程的運行時信息需要被重新加載到另一個 CPU 核上,而且,此時,另一個 CPU 核上的 L1、L2 緩存中,并沒有 Redis 實例之前運行時頻繁訪問的指令和數(shù)據(jù),所以,這些指令和數(shù)據(jù)都需要重新從 L3 緩存,甚至是內(nèi)存中加載。這個重新加載的過程是需要花費一定時間的。而且,Redis 實例需要等待這個重新加載的過程完成后,才能開始處理請求,所以,這也會導致一些請求的處理時間增加。

在 CPU 多核場景下,Redis 實例被頻繁調度到不同 CPU 核上運行的話,那么,對 Redis 實例的請求處理時間影響就更大了。每調度一次,一些請求就會受到運行時信息、指令和數(shù)據(jù)重新加載過程的影響,這就會導致某些請求的延遲明顯高于其他請求。

所以,我們要避免 Redis 總是在不同 CPU 核上來回調度執(zhí)行。于是,我們嘗試著**把 Redis 實例和 CPU 核綁定了,讓一個 Redis 實例固定運行在一個 CPU 核上。**我們可以使用 taskset 命令把一個程序綁定在一個核上運行。比如說,我們執(zhí)行下面的命令,就把 Redis 實例綁在了 0 號核上,其中,“-c”選項用于設置要綁定的核編號。

taskset -c 0 ./redis-server
1
3.多CPU架構:NUMA
在主流的服務器上,一個 CPU 處理器會有 10 到 20 多個物理核。同時,為了提升服務器的處理能力,服務器上通常還會有多個 CPU 處理器(也稱為多 CPU Socket),每個處理器有自己的物理核(包括 L1、L2 緩存),L3 緩存,以及連接的內(nèi)存,同時,不同處理器間通過總線連接。

**在多 CPU 架構上,應用程序可以在不同的處理器上運行。**在剛才的圖中,Redis 可以先在 Socket 1 上運行一段時間,然后再被調度到 Socket 2 上運行。

如果應用程序先在一個 Socket 上運行,并且把數(shù)據(jù)保存到了內(nèi)存,然后被調度到另一個 Socket 上運行,此時,應用程序再進行內(nèi)存訪問時,就需要訪問之前 Socket 上連接的內(nèi)存,這種訪問屬于遠端內(nèi)存訪問。和訪問 Socket 直接連接的內(nèi)存相比,遠端內(nèi)存訪問會增加應用程序的延遲。

4.NUMA架構對redis性能影響
在實際應用 Redis 時,我經(jīng)常看到一種做法,為了提升 Redis 的網(wǎng)絡性能,把操作系統(tǒng)的網(wǎng)絡中斷處理程序和 CPU 核綁定。

這個做法可以避免網(wǎng)絡中斷處理程序在不同核上來回調度執(zhí)行,的確能有效提升 Redis 的網(wǎng)絡處理性能。

但是,網(wǎng)絡中斷程序是要和 Redis 實例進行網(wǎng)絡數(shù)據(jù)交互的,一旦把網(wǎng)絡中斷程序綁核后,我們就需要注意 Redis 實例是綁在哪個核上了,這會關系到 Redis 訪問網(wǎng)絡數(shù)據(jù)的效率高低。

:網(wǎng)絡中斷處理程序從網(wǎng)卡硬件中讀取數(shù)據(jù),并把數(shù)據(jù)寫入到操作系統(tǒng)內(nèi)核維護的一塊內(nèi)存緩沖區(qū)。內(nèi)核會通過 epoll 機制觸發(fā)事件,通知 Redis 實例,Redis 實例再把數(shù)據(jù)從內(nèi)核的內(nèi)存緩沖區(qū)拷貝到自己的內(nèi)存空間,如下圖所示:

**潛在的風險:**如果網(wǎng)絡中斷處理程序和 Redis 實例各自所綁的 CPU 核不在同一個 CPU Socket 上,那么,Redis 實例讀取網(wǎng)絡數(shù)據(jù)時,就需要跨 CPU Socket 訪問內(nèi)存,這個過程會花費較多時間。

所以,為了避免 Redis 跨 CPU Socket 訪問網(wǎng)絡數(shù)據(jù),我們最好把網(wǎng)絡中斷程序和 Redis 實例綁在同一個 CPU Socket 上

并不是先把一個 CPU Socket 中的所有邏輯核編完,再對下一個 CPU Socket 中的邏輯核編碼,而是先給每個 CPU Socket 中每個物理核的第一個邏輯核依次編號,再給每個 CPU Socket 中的物理核的第二個邏輯核依次編號。

假設有 2 個 CPU Socket,每個 Socket 上有 6 個物理核,每個物理核又有 2 個邏輯核,總共 24 個邏輯核。我們可以執(zhí)行 *lscpu 命令,*查看到這些核的編號:

lscpu

Architecture: x86_64
...
NUMA node0 CPU(s): 0-5,12-17
NUMA node1 CPU(s): 6-11,18-23
...
1
2
3
4
5
6
7
可以看到,NUMA node0 的 CPU 核編號是 0 到 5、12 到 17。其中,0 到 5 是 node0 上的 6 個物理核中的第一個邏輯核的編號,12 到 17 是相應物理核中的第二個邏輯核編號。NUMA node1 的 CPU 核編號規(guī)則和 node0 一樣。

5.綁核的好處和壞處,以及解決方案
1.好處和壞處
好處:

在 CPU 多核的場景下,用 taskset 命令把 Redis 實例和一個核綁定,可以減少 Redis 實例在不同核上被來回調度執(zhí)行的開銷,避免較高的尾延遲;

在多 CPU 的 NUMA 架構下,如果你對網(wǎng)絡中斷程序做了綁核操作,建議你同時把 Redis 實例和網(wǎng)絡中斷程序綁在同一個 CPU Socket 的不同核上,這樣可以避免 Redis 跨 Socket 訪問內(nèi)存中的網(wǎng)絡數(shù)據(jù)的時間開銷。

壞處:

把 Redis 實例綁到一個 CPU 邏輯核上時,就會導致子進程、后臺線程和 Redis 主線程競爭 CPU 資源,一旦子進程或后臺線程占用 CPU 時,主線程就會被阻塞,導致 Redis 請求延遲增加。

2.解決方案
a.一個 Redis 實例對應綁一個物理核

按照上面的邏輯核編號,0,12 應該在同一個物理核內(nèi),我們可以把一個redis實例綁定一個物理核:

taskset ? -c 0,12 ./redis-server
1
把 Redis 實例和物理核綁定,可以讓主線程、子進程、后臺線程共享使用 2 個邏輯核,可以在一定程度上緩解 CPU 資源競爭。但是,因為只用了 2 個邏輯核,它們相互之間的 CPU 競爭仍然還會存在。如果你還想進一步減少 CPU 競爭,我再給你介紹一種方案。

b.修改redis源碼

通過編程實現(xiàn)綁核時,要用到操作系統(tǒng)提供的 1 個數(shù)據(jù)結構 cpu_set_t 和 3 個函數(shù) CPU_ZERO、CPU_SET 和 sched_setaffinity,我先來解釋下它們。

cpu_set_t 數(shù)據(jù)結構:是一個位圖,每一位用來表示服務器上的一個 CPU 邏輯核。
CPU_ZERO 函數(shù):以 cpu_set_t 結構的位圖為輸入?yún)?shù),把位圖中所有的位設置為 0。
CPU_SET 函數(shù):以 CPU 邏輯核編號和 cpu_set_t 位圖為參數(shù),把位圖中和輸入的邏輯核編號對應的位設置為 1。
sched_setaffinity 函數(shù):以進程 / 線程 ID 號和 cpu_set_t 為參數(shù),檢查 cpu_set_t 中哪一位為 1,就把輸入的 ID 號所代表的進程 / 線程綁在對應的邏輯核上。
那么,怎么在編程時把這三個函數(shù)結合起來實現(xiàn)綁核呢?

很簡單,我們分四步走就行。

第一步:創(chuàng)建一個 cpu_set_t 結構的位圖變量;

第二步:使用 CPU_ZERO 函數(shù),把 cpu_set_t 結構的位圖所有的位都設置為 0;

第三步:根據(jù)要綁定的邏輯核編號,使用 CPU_SET 函數(shù),把 cpu_set_t 結構的位圖相應位設置為 1;

第四步:使用 sched_setaffinity 函數(shù),把程序綁定在 cpu_set_t 結構位圖中為 1 的邏輯核上。

對于 Redis 來說,它是在 bio.c 文件中的 bioProcessBackgroundJobs 函數(shù)中創(chuàng)建了后臺線程。bioProcessBackgroundJobs 函數(shù)類似于剛剛的例子中的 worker 函數(shù),在這個函數(shù)中實現(xiàn)綁核四步操作,就可以把后臺線程綁到和主線程不同的核上了。和給線程綁核類似,當我們使用 fork 創(chuàng)建子進程時,也可以把剛剛說的四步操作實現(xiàn)在 fork 后的子進程代碼中,示例代碼如下:

int main(){
? ?//用fork創(chuàng)建一個子進程
? ?pid_t p = fork();
? ?if(p < 0){
? ? ? printf(" fork error\n");
? ?}
? ?//子進程代碼部分
? ?else if(!p){
? ? ? cpu_set_t cpuset; ?//創(chuàng)建位圖變量
? ? ? CPU_ZERO(&cpu_set); //位圖變量所有位設置0
? ? ? CPU_SET(3, &cpuset); //把位圖的第3位設置為1
? ? ? sched_setaffinity(0, sizeof(cpuset), &cpuset); ?//把程序綁定在3號邏輯核
? ? ? //實際子進程工作
? ? ? exit(0);
? ?}
? ?...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
對于 Redis 來說,生成 RDB 和 AOF 日志重寫的子進程分別是下面兩個文件的函數(shù)中實現(xiàn)的。

rdb.c 文件:rdbSaveBackground 函數(shù);
aof.c 文件:rewriteAppendOnlyFileBackground 函數(shù)。
這兩個函數(shù)中都調用了 fork 創(chuàng)建子進程,所以,我們可以在子進程代碼部分加上綁核的四步操作。

3.《響應波動延時:如何應對redis變慢?》
1.redis真的變慢了嗎?
redis是否變慢需要根據(jù) Redis 的響應延遲 與 redis實例的 基線性能比較 來判斷;

大部分時候,Redis 延遲很低,但是在某些時刻,有些 Redis 實例會出現(xiàn)很高的響應延遲,甚至能達到幾秒到十幾秒,不過持續(xù)時間不長,這也叫延遲“毛刺”。當你發(fā)現(xiàn) Redis 命令的執(zhí)行時間突然就增長到了幾秒,基本就可以認定 Redis 變慢了。

1.怎么測試redis的基線性能呢?
所謂的基線性能呢,也就是一個系統(tǒng)在低壓力、無干擾下的基本性能,這個性能只由當前的軟硬件配置決定。

從 2.8.7 版本開始,<!–redis-cli 命令提供了–intrinsic-latency 選項,–>**可以用來監(jiān)測和統(tǒng)計測試期間內(nèi)的最大延遲,這個延遲可以作為 Redis 的基線性能。**一般情況下,運行 120 秒就足夠監(jiān)測到最大延遲了,所以,我們可以把參數(shù)設置為 120。

./redis-cli --intrinsic-latency 120
Max latency so far: 17 microseconds.
Max latency so far: 44 microseconds.
Max latency so far: 94 microseconds.
Max latency so far: 110 microseconds.
Max latency so far: 119 microseconds.

36481658 total runs (avg latency: 3.2893 microseconds / 3289.32 nanoseconds per run).
Worst run took 36x longer than the average latency.
1
2
3
4
5
6
7
8
9
可以看出當前redis實例的最大延時是119 毫秒。

2. redis網(wǎng)絡延時 測試
如果你想了解網(wǎng)絡對 Redis 性能的影響,一個簡單的方法是用 iPerf 這樣的工具,測量從 Redis 客戶端到服務器端的網(wǎng)絡延遲。如果這個延遲有幾十毫秒甚至是幾百毫秒,就說明,Redis 運行的網(wǎng)絡環(huán)境中很可能有大流量的其他應用程序在運行,導致網(wǎng)絡擁塞了。這個時候,你就需要協(xié)調網(wǎng)絡運維,調整網(wǎng)絡的流量分配了。

2.如何應對redis變慢?
redis變慢排查我們可以從三個方面入手:

redis 自身特性
redis 文件系統(tǒng)
操作系統(tǒng)


1.redis 自身特性導致 變慢
1.慢查詢
Value 類型為 String 時,GET/SET 操作主要就是操作 Redis 的哈希表索引。這個操作復雜度基本是固定的,即 O(1)。但是,當 Value 類型為 Set 時,SORT、SUNION/SMEMBERS 操作復雜度分別為 O(N+M*log(M)) 和 O(N)。其中,N 為 Set 中的元素個數(shù),M 為 SORT 操作返回的元素個數(shù)。這個復雜度就增加了很多。

可以通過 Redis 日志,或者是 latency monitor 工具,查詢變慢的請求;

解決慢查詢:

用其他高效命令代替。比如說,如果你需要返回一個 SET 中的所有成員時,不要使用 SMEMBERS 命令,而是要使用 SSCAN 多次迭代返回,避免一次返回大量數(shù)據(jù),造成線程阻塞。
當你需要執(zhí)行排序、交集、并集操作時,可以在客戶端完成,而不要用 SORT、SUNION、SINTER 這些命令,以免拖慢 Redis 實例。
KEYS 命令需要遍歷存儲的鍵值對,所以操作延時高,KEYS 命令一般不被建議用于生產(chǎn)環(huán)境中
2. 大量key 同時過期
過期 key 的自動刪除機制,默認情況下,Redis 每 100 毫秒會刪除一些過期 key:

采樣 ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP 個數(shù)的 key,并將其中過期的 key 全部刪除;
如果超過 25% 的 key 過期了,則重復刪除的過程,直到過期 key 的比例降至 25% 以下。
ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP 是 Redis 的一個參數(shù),默認是 20;

刪除操作是阻塞的(Redis 4.0 后可以用異步線程機制來減少阻塞影響),頻繁使用帶有相同時間參數(shù)的 EXPIREAT 命令設置過期 key,這就會導致,在同一秒內(nèi)有大量的 key 同時過期。那么就會導致一直執(zhí)行 第二步,阻塞redis

盡量設置不同的過期時間,可以在設置時添加一個隨機數(shù)。

2.redis文件系統(tǒng)
Redis 會持久化保存數(shù)據(jù)到磁盤,這個過程要依賴文件系統(tǒng)來完成,所以,文件系統(tǒng)將數(shù)據(jù)寫回磁盤的機制,會直接影響到 Redis 持久化的效率。而且,在持久化的過程中,Redis 也還在接收其他請求,持久化的效率高低又會影響到 Redis 處理請求的性能。

1.AOF日志模式
AOF 日志提供了三種日志寫回策略:no、everysec、always。這三種寫回策略依賴文件系統(tǒng)的兩個系統(tǒng)調用完成,也就是 write 和 fsync。

write 只要把日志記錄寫到內(nèi)核緩沖區(qū),就可以返回了,并不需要等待日志實際寫回到磁盤;

而 fsync 需要把日志記錄寫回到磁盤后才能返回,時間較長。

當寫回策略配置為 everysec 時,Redis 會使用后臺的子線程異步完成 fsync 的操作。

而對于 always 策略來說,Redis 需要確保每個操作記錄日志都寫回磁盤,如果用后臺子線程異步完成,主線程就無法及時地知道每個操作是否已經(jīng)完成了,這就不符合 always 策略的要求了。所以,always 策略并不使用后臺子線程來執(zhí)行。

2.AOF重寫壓力過大導致fsync 阻塞
AOF重寫由子進程完成, 會有大量的IO操作;而fsync 雖然 是由后臺子線程完成寫入磁盤操作,但是,主線程在進行 寫操作時會監(jiān)視 fsync的執(zhí)行情況,如果子線程還未完成寫盤操作,主進程就會阻塞,不會返回給客戶端結果;

正是由于 主線程監(jiān)視 上一次fsync操作執(zhí)行情況,在寫磁盤壓力大時,可能導致 主線程阻塞;

例子:當主線程使用后臺子線程執(zhí)行了一次 fsync,需要再次把新接收的操作記錄寫回磁盤時,如果主線程發(fā)現(xiàn)上一次的 fsync 還沒有執(zhí)行完,那么它就會阻塞。所以,如果后臺子線程執(zhí)行的 fsync 頻繁阻塞的話(比如 AOF 重寫占用了大量的磁盤 IO 帶寬),主線程也會阻塞,導致 Redis 性能變慢。

由于 fsync 后臺子線程和 AOF 重寫子進程的存在,主 IO 線程一般不會被阻塞。但是,如果在重寫日志時,AOF 重寫子進程的寫入量比較大,fsync 線程也會被阻塞,進而阻塞主線程,導致延遲增加。

如果業(yè)務應用對延遲非常敏感,但同時允許一定量的數(shù)據(jù)丟失,那么,可以把配置項 no-appendfsync-on-rewrite 設置為 yes,如下所示:

no-appendfsync-on-rewrite yes
1
no-appendfsync-on-rewrite 表示 AOF重寫時選擇不 進行 fsync刷盤操作,yes表示可以不執(zhí)行fsync,no表示執(zhí)行fsync

針對延遲非常敏感,但同時允許一定量的數(shù)據(jù)丟失的應用我們可以 設置no-appendfsync-on-rewrite yes

另外,我們可以采用高速的固態(tài)硬盤作為 AOF 日志的寫入設備。

3.操作系統(tǒng):swap和內(nèi)存大頁機制THP
1.Swap
內(nèi)存 swap 是操作系統(tǒng)里將內(nèi)存數(shù)據(jù)在內(nèi)存和磁盤間來回換入和換出的機制,涉及到磁盤的讀寫,所以,一旦觸發(fā) swap,無論是被換入數(shù)據(jù)的進程,還是被換出數(shù)據(jù)的進程,其性能都會受到慢速磁盤讀寫的影響。

一旦 swap 被觸發(fā)了,Redis 的請求操作需要等到磁盤數(shù)據(jù)讀寫完成才行,swap 觸發(fā)后影響的是 Redis 主 IO 線程,這會極大地增加 Redis 的響應時間。

Redis 實例自身使用了大量的內(nèi)存,導致物理機器的可用內(nèi)存不足;
和 Redis 實例在同一臺機器上運行的其他進程,在進行大量的文件讀寫操作。文件讀寫本身會占用系統(tǒng)內(nèi)存,這會導致分配給 Redis 實例的內(nèi)存量變少,進而觸發(fā) Redis 發(fā)生 swap。
解決思路:

切片集群
加大內(nèi)存
1.首先查看redis進程號,這里是5332

$ redis-cli info | grep process_id
process_id: 5332
1
2
2.Redis 所在機器的 /proc 目錄下的該進程目錄中:

Redis 所在機器的 /proc 目錄下的該進程目錄中:
1
3.查看該 Redis 進程的使用情況,這里截取部分結果

$cat smaps | egrep '^(Swap|Size)'
Size: 584 kB
Swap: 0 kB
Size: 4 kB
Swap: 4 kB
Size: 4 kB
Swap: 0 kB
Size: 462044 kB
Swap: 462008 kB
Size: 21392 kB
Swap: 0 kB
1
2
3
4
5
6
7
8
9
10
11
Size 代表當前redis所占內(nèi)存,Swap表示這塊 Size 大小的內(nèi)存區(qū)域有多少已經(jīng)被換出到磁盤上了。如果這兩個值相等,就表示這塊內(nèi)存區(qū)域已經(jīng)完全被換出到磁盤了。

2. 內(nèi)存大頁THP
該機制支持 2MB 大小的內(nèi)存頁分配,而常規(guī)的內(nèi)存頁分配是按 4KB 的粒度來執(zhí)行的。

Redis 為了提供數(shù)據(jù)可靠性保證,需要將數(shù)據(jù)做持久化保存。這個寫入過程由額外的線程執(zhí)行,所以,此時,Redis 主線程仍然可以接收客戶端寫請求。客戶端的寫請求可能會修改正在進行持久化的數(shù)據(jù)。在這一過程中,Redis 就會采用寫時復制機制,也就是說,一旦有數(shù)據(jù)要被修改,Redis 并不會直接修改內(nèi)存中的數(shù)據(jù),而是將這些數(shù)據(jù)拷貝一份,然后再進行修改。

如果采用了內(nèi)存大頁,那么,即使客戶端請求只修改 100B 的數(shù)據(jù),Redis 也需要拷貝 2MB 的大頁。相反,如果是常規(guī)內(nèi)存頁機制,只用拷貝 4KB。兩者相比,你可以看到,當客戶端請求修改或新寫入數(shù)據(jù)較多時,內(nèi)存大頁機制將導致大量的拷貝,這就會影響 Redis 正常的訪存操作,最終導致性能變慢。

查看內(nèi)存大頁:

cat /sys/kernel/mm/transparent_hugepage/enabled
1
always代表使用了THP,never代表未使用

禁止使用THP:

echo never /sys/kernel/mm/transparent_hugepage/enabled
1
4.總結
梳理了一個包含 9 個檢查點的 Checklist,希望你在遇到 Redis 性能變慢時,按照這些步驟逐一檢查,高效地解決問題:

獲取 Redis 實例在當前環(huán)境下的基線性能。
是否用了慢查詢命令?如果是的話,就使用其他命令替代慢查詢命令,或者把聚合計算命令放在客戶端做。
是否對過期 key 設置了相同的過期時間?對于批量刪除的 key,可以在每個 key 的過期時間上加一個隨機數(shù),避免同時刪除。
是否存在 bigkey? 對于 bigkey 的刪除操作,如果你的 Redis 是 4.0 及以上的版本,可以直接利用異步線程機制減少主線程阻塞;如果是 Redis 4.0 以前的版本,可以使用 SCAN 命令迭代刪除;對于 bigkey 的集合查詢和聚合操作,可以使用 SCAN 命令在客戶端完成。
Redis AOF 配置級別是什么?業(yè)務層面是否的確需要這一可靠性級別?如果我們需要高性能,同時也允許數(shù)據(jù)丟失,可以將配置項 no-appendfsync-on-rewrite 設置為 yes,避免 AOF 重寫和 fsync 競爭磁盤 IO 資源,導致 Redis 延遲增加。當然, 如果既需要高性能又需要高可靠性,最好使用高速固態(tài)盤作為 AOF 日志的寫入盤。
Redis 實例的內(nèi)存使用是否過大?發(fā)生 swap 了嗎?如果是的話,就增加機器內(nèi)存,或者是使用 Redis 集群,分攤單機 Redis 的鍵值對數(shù)量和內(nèi)存壓力。同時,要避免出現(xiàn) Redis 和其他內(nèi)存需求大的應用共享機器的情況。
在 Redis 實例的運行環(huán)境中,是否啟用了透明大頁機制?如果是的話,直接關閉內(nèi)存大頁機制就行了。
是否運行了 Redis 主從集群?如果是的話,把主庫實例的數(shù)據(jù)量大小控制在 2~4GB,以免主從復制時,從庫因加載大的 RDB 文件而阻塞。
是否使用了多核 CPU 或 NUMA 架構的機器運行 Redis 實例?使用多核 CPU 時,可以給 Redis 實例綁定物理核;使用 NUMA 架構時,注意把 Redis 實例和網(wǎng)絡中斷處理程序運行在同一個 CPU Socket 上。
4.redis 中 的內(nèi)存碎片
1. 內(nèi)存碎片帶來的影響?
question 1: 為什么redis已經(jīng)刪除了數(shù)據(jù),使用top命令還會顯示redis 內(nèi)存占用較大呢?

那是因為redis雖然刪除了這些數(shù)據(jù),回收了內(nèi)存空間,但是 redis內(nèi)存分配器不會立刻把 內(nèi)存空間 返還給操作系統(tǒng);

所以,出現(xiàn)redis已經(jīng)刪除了數(shù)據(jù),但是 任務管理器還會顯示redis占用大內(nèi)存的情況;

question 2: redis 由內(nèi)存分配器回收,分配內(nèi)存,如果沒有內(nèi)存自動整理功能(整理內(nèi)存碎片),會有什么風險?

即使有大量的內(nèi)存 ,但是空間碎片較多,內(nèi)存利用率低, 當寫 bigkey 要求分配大量且連續(xù)的內(nèi)存空間時,沒有較大的連續(xù)內(nèi)存空間導致無法處理這個操作;

雖然有空閑空間,Redis 卻無法用來保存數(shù)據(jù),不僅會減少 Redis 能夠實際保存的數(shù)據(jù)量,還會降低 Redis 運行機器的成本回報率。

2.內(nèi)存碎片如何形成的?
主要有兩種方式:

內(nèi)因:操作系統(tǒng)的內(nèi)存分配機制
外因:Redis 的負載特征(鍵值大小不一致, 鍵值對修改,刪除等)
1. 內(nèi)因:內(nèi)存分配器的分配策略
Redis 可以使用 libc、jemalloc、tcmalloc 多種內(nèi)存分配器來分配內(nèi)存,默認使用 jemalloc。

jemalloc 的分配策略之一,是按照一系列固定的大小劃分內(nèi)存空間。例如 8 字節(jié)、16 字節(jié)、32 字節(jié)、48 字節(jié),…, 2KB、4KB、8KB 等。當程序申請的內(nèi)存最接近某個固定值時,jemalloc 會給它分配相應大小的空間。

這樣的分配方式本身是為了減少分配次數(shù)。例如,Redis 申請一個 20 字節(jié)的空間保存數(shù)據(jù),jemalloc 就會分配 32 字節(jié),此時,如果應用還要寫入 10 字節(jié)的數(shù)據(jù),Redis 就不用再向操作系統(tǒng)申請空間了,因為剛才分配的 32 字節(jié)已經(jīng)夠用了,這就避免了一次分配操作。

2.外因:鍵值對大小不一樣和刪改操作
不同大小的鍵值對,Redis 申請內(nèi)存空間分配時,本身就會有大小不一的空間需求。
鍵值對修改刪除帶來的內(nèi)存空間變化,刪除和修改都會帶來空間碎片;
3.redis內(nèi)存碎片處理
1.判斷是否有內(nèi)存碎片
INFO memory
# Memory
used_memory:1073741736
used_memory_human:1024.00M
used_memory_rss:1997159792
used_memory_rss_human:1.86G

mem_fragmentation_ratio:1.86
1
2
3
4
5
6
7
8
mem_fragmentation_ratio: 表示Redis 當前的內(nèi)存碎片率。它就是上面的命令中的兩個指標 used_memory_rss 和 used_memory 相除的結果。

used_memory_rss:操作系統(tǒng)實際分配給 Redis 的物理內(nèi)存空間,里面就包含了碎片

used_memory :redis 申請的內(nèi)存空間

問題:那么,該如何設置這個mem_fragmentation_ratio的值呢?這里有一些經(jīng)驗設置閾值:

1 <= mem_fragmentation_ratio <= 1.5:這種情況是合理的。
mem_fragmentation_ratio > 1.5 :這表明內(nèi)存碎片率已經(jīng)超過了 50%。一般情況下,這個時候,我們就需要采取一些措施來降低內(nèi)存碎片率了。
2.內(nèi)存碎片的清理
a.內(nèi)存清理
從 4.0-RC3 版本以后,Redis 自身提供了一種內(nèi)存碎片自動清理的方法,我們先來看這個方法的基本機制。

內(nèi)存碎片清理,簡單來說,就是**“搬家讓位,合并空間”**。

碎片清理是有代價的:

操作系統(tǒng)需要把多份數(shù)據(jù)拷貝到新位置,把原有空間釋放出來,這會帶來時間開銷。

因為 Redis 是單線程,在數(shù)據(jù)拷貝時,Redis 只能等著,這就導致 Redis 無法及時處理請求,性能就會降低。

而且,有的時候,數(shù)據(jù)拷貝還需要注意順序,就像剛剛說的清理內(nèi)存碎片的例子,操作系統(tǒng)需要先拷貝 D,并釋放 D 的空間后,才能拷貝 B。這種對順序性的要求,會進一步增加 Redis 的等待時間,導致性能降低。

b.如何 降低內(nèi)存清理時對redis性能的影響?
啟動內(nèi)存碎片自動清理:

config set activedefrag yes
1
主要從兩個方面三個參數(shù)控制內(nèi)存清理:

滿足兩個條件自動進行內(nèi)存清理:
active-defrag-ignore-bytes 100mb:內(nèi)存碎片字節(jié)數(shù)到達100MB
active-defrag-threshold-lower 10:表示內(nèi)存碎片空間占操作系統(tǒng)分配給 Redis 的總空間比例達到 10% 時,開始清理。
注意:清理過程中,不滿足以上條件時立刻停止自動清理,滿足條件后會繼續(xù)自動清理
控制內(nèi)存碎片CPU執(zhí)行時間:
active-defrag-cycle-min 25: 表示自動清理過程所用 CPU 時間的比例不低于 25%,保證清理能正常開展;
active-defrag-cycle-max 75:表示自動清理過程所用 CPU 時間的比例不高于 75%,一旦超過,就停止清理,從而避免在清理時,大量的內(nèi)存拷貝阻塞 Redis,導致響應延遲升高。
5.《redis 緩沖區(qū)》
1. 什么是緩沖區(qū)?
1.緩沖區(qū)解決什么問題?

redis緩沖區(qū)解決客戶端請求堆積或服務器處理數(shù)據(jù)速度過慢帶來的數(shù)據(jù)丟失以及性能問題。

2.緩沖區(qū)在redis 中有哪些應用場景?

cliet-server服務器模式高并發(fā)下暫存客戶端發(fā)送的命令數(shù)據(jù),或者是服務器端返回給客戶端的數(shù)據(jù)結果
主從節(jié)點間進行數(shù)據(jù)同步時,用來暫存主節(jié)點接收的寫命令和數(shù)據(jù)。
注意:redis之所以適合做緩存是因為它有高性能的內(nèi)存結構 ,以及完善的淘汰機制;

2.客戶端與服務器之間的緩沖區(qū)


cliet-server模式之間的緩沖區(qū),分為輸入緩沖區(qū)的輸出緩沖區(qū),服務器端給每個連接的客戶端都設置了一個輸入緩沖區(qū)和輸出緩沖區(qū)

client把請求命令和數(shù)據(jù) 放入 輸入緩沖區(qū),server 逐步讀取命令把執(zhí)行結果發(fā)送到輸出緩沖區(qū),client去輸出緩沖區(qū)拿響應結果。

3.如何處理輸入緩沖區(qū)溢出問題
1.查看輸入緩沖區(qū)使用情況
CLIENT LIST
id=5 addr=127.0.0.1:50487 fd=9 name= age=4 idle=0 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=26 qbuf-free=32742 obl=0 oll=0 omem=0 events=r cmd=client
1
2
client list 命令查看所有與server 相連的客戶端輸入緩沖區(qū)的信息

主要看兩類信息:

client 的信息,ip 地址,端口號

輸入緩沖區(qū)相關信息:

? cmd: 客戶端最新執(zhí)行的命令(當前為client)

? qbuf: 當前客戶端已使用 緩沖區(qū)大小

? qbuf-free: 剩余 緩沖區(qū)大小

2.什么情況下出現(xiàn)輸入緩沖區(qū)溢出問題?以及解決方案
有批量bigkey請求
server性能低, 頻繁阻塞或阻塞時間教久
怎么解決呢?

由于輸入緩沖區(qū) 并不能設置緩沖區(qū)的大小,默認最多1G,所有只能避免bigkey ,避免server性能低

注意:當多個客戶端連接占用的內(nèi)存總量,超過了 Redis 的 maxmemory 配置項時(例如 4GB),就會觸發(fā) Redis 進行數(shù)據(jù)淘汰。

4.如何處理輸出緩存沖溢出問題?
1.什么情況下出現(xiàn)輸出緩沖區(qū)溢出問題?
輸出bigkey結果
執(zhí)行MONITOR
輸出緩沖區(qū)設置過小
2.怎么設置輸出緩沖區(qū)的大小?
client-output-buffer-limit 配置項,來設置緩沖區(qū)的大小

例子:client-output-buffer-limit normal 0 0 0

四個參數(shù)代表什么?

one:類型,normal代表普通客戶端

two: 緩沖區(qū)最大限制

three : 持續(xù)輸出最大的數(shù)據(jù)量

four:持續(xù)輸出最大時間

3.不同的客戶端不同分配模式
普通客戶端

?client-output-buffer-limit normal 0 0 0
1
普通客戶端來說,它每發(fā)送完一個請求,會等到請求結果返回后,再發(fā)送下一個請求,這種發(fā)送方式稱為阻塞式發(fā)送。在這種情況下,如果不是讀取體量特別大的 bigkey,服務器端的輸出緩沖區(qū)一般不會被阻塞的。

所以,我們通常把普通客戶端的緩沖區(qū)大小限制,以及持續(xù)寫入量限制、持續(xù)寫入時間限制都設置為 0,也就是不做限制。

訂閱客戶端

一旦訂閱的 Redis 頻道有消息了,服務器端都會通過輸出緩沖區(qū)把消息發(fā)給客戶端。所以,訂閱客戶端和服務器間的消息發(fā)送方式,不屬于阻塞式發(fā)送。

因此,我們會給訂閱客戶端設置緩沖區(qū)大小限制、緩沖區(qū)持續(xù)寫入量限制,以及持續(xù)寫入時間限制,可以在 Redis 配置文件中這樣設置:

client-output-buffer-limit pubsub 8mb 2mb 60
1
5.主從集群中的緩沖區(qū)
1.復制緩沖區(qū)(replication_buffer)
replication_buffer的大小不算入 maxmemory

在全量復制過程中,主節(jié)點在向從節(jié)點傳輸 RDB 文件的同時,會繼續(xù)接收客戶端發(fā)送的寫命令請求。這些寫命令就會先保存在復制緩沖區(qū)中,主節(jié)點上會為每個從節(jié)點都維護一個復制緩沖區(qū),來保證主從節(jié)點間的數(shù)據(jù)同步。

按通常的使用經(jīng)驗,我們會**把主節(jié)點的數(shù)據(jù)量控制在 2~4GB,**這樣可以讓全量同步執(zhí)行得更快些,避免復制緩沖區(qū)累積過多命令。

config set client-output-buffer-limit slave 512mb 128mb 60
1
2.復制積壓緩沖區(qū)(repl_backlog_buffer)
repl_backlog_buffer算入maxmemeory

增量復制時使用的緩沖區(qū),這個緩沖區(qū)稱為復制積壓緩沖區(qū),為了應對復制積壓緩沖區(qū)的溢出問題,我們可以調整復制積壓緩沖區(qū)的大小,也就是設置 repl_backlog_size 這個參數(shù)的值.

6. 總結
針對命令數(shù)據(jù)發(fā)送過快過大的問題,對于普通客戶端來說可以避免 bigkey,而對于復制緩沖區(qū)來說,就是避免過大的 RDB 文件。
針對命令數(shù)據(jù)處理較慢的問題,解決方案就是減少 Redis 主線程上的阻塞操作,例如使用異步的刪除操作。
針對緩沖區(qū)空間過小的問題,解決方案就是使用 client-output-buffer-limit 配置項設置合理的輸出緩沖區(qū)、復制緩沖區(qū)和復制積壓緩沖區(qū)大小。當然,我們不要忘了,輸入緩沖區(qū)的大小默認是固定的,我們無法通過配置來修改它,除非直接去修改 Redis 源碼。
?

總結

以上是生活随笔為你收集整理的redis核心技术与实战(三) 性能篇的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。