溫馨提示×

溫馨提示×

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

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

提升Redis性能的小技巧有哪些

發(fā)布時間:2022-03-08 09:39:38 來源:億速云 閱讀:213 作者:小新 欄目:關(guān)系型數(shù)據(jù)庫

這篇文章主要介紹了提升Redis性能的小技巧有哪些,具有一定借鑒價值,感興趣的朋友可以參考下,希望大家閱讀完這篇文章之后大有收獲,下面讓小編帶著大家一起了解一下。

01 使用 pipeline

Redis 是基于請求-響應(yīng)模型的 TCP 服務(wù)器。意味著單次請求 RTT(往返時間),取決于當(dāng)前網(wǎng)絡(luò)狀況 。這會導(dǎo)致單個 Redis 請求可能非???,比如通過本地環(huán)路網(wǎng)卡。可能非常慢,比如處于網(wǎng)絡(luò)狀況不佳的環(huán)境。

另一方面,Redis 每次請求-響應(yīng),都涉及到 read 和 write 系統(tǒng)調(diào)用。甚至?xí)|發(fā)多次 epoll_wait 系統(tǒng)調(diào)用(Linux 平臺)。這導(dǎo)致 Redis 不斷在用戶態(tài)和內(nèi)核態(tài)進行切換。

static int connSocketRead(connection *conn, void *buf, size_t buf_len) {
    // read 系統(tǒng)調(diào)用
    int ret = read(conn->fd, buf, buf_len);}static int connSocketWrite(connection *conn, const void *data, size_t data_len) {
    // write 系統(tǒng)調(diào)用
    int ret = write(conn->fd, data, data_len);}int aeProcessEvents(aeEventLoop *eventLoop, int flags) {
    // 事件觸發(fā),Linux 下為 epoll_wait 系統(tǒng)調(diào)用
    numevents = aeApiPoll(eventLoop, tvp);}

那么,如何節(jié)省往返時間和系統(tǒng)調(diào)用次數(shù)呢?批處理是一個好的辦法。

為此,Redis 提供了 「pipeline」。pipeline 的原理很簡單,將多個命令打包成「一個命令」發(fā)送。Redis 收到后,解析成多個命令執(zhí)行。最終將多個結(jié)果打包返回。

「pipeline 可以有效的提升 Redis 性能」

但是,使用 pipeline 有幾點需要你留意

  1. 「pipeline 不能保證原子性」。在一次 pipeline 命令執(zhí)行期間,可能會執(zhí)行其它 client 發(fā)起的命令。請記住,pipeline 只是批量處理命令。想要保證原子性,使用 MULTI 或者 Lua 腳本。

  2. 「單次 pipeline 命令不宜過多」。當(dāng)使用 pipeline 時,Redis 會將 pipeline 命令的響應(yīng)結(jié)果,暫存在內(nèi)存 Reply buffer 中,等待所有命令執(zhí)行完畢后返回。如果 pipeline 命令過多,可能會導(dǎo)致占用較多內(nèi)存。可以將單個 pipeline 拆分成多個 pipeline。


02 開啟 IO 多線程

在「Redis 6」版本以前,Redis 是 「單線程」 讀取、解析、執(zhí)行命令的。Redis 6 開始,引入了 IO 多線程。

IO 線程負責(zé)讀取命令、解析命令、返回結(jié)果。開啟后可以有效提升 IO 性能。

我畫了一張示意圖供你參考

提升Redis性能的小技巧有哪些
如上圖所示,主線程和 IO 線程會共同參與命令的讀取、解析以及結(jié)果響應(yīng)。

但執(zhí)行命令的,為 「主線程」。

IO 線程默認關(guān)閉,你可以修改 redis.conf 以下配置開啟。

io-threads 4
io-threads-do-reads yes

「io-threads」 是 IO 線程數(shù)(包含主線程),我建議你根據(jù)機器,設(shè)置不同值進行壓測,取最優(yōu)值。


03 避免 big key

Redis 執(zhí)行命令是單線程的,這意味著 Redis 操作「big key」有阻塞的風(fēng)險。

big key 通常指的是 Redis 存儲的 value 過大。包括:

  • 單個 value 過大。如 200M 大小的 String。

  • 集合元素過多。如 List、Hash、Set、ZSet 中有幾百、上千萬數(shù)據(jù)。

舉個例子,假設(shè)我們有一個 200M 大小的 String key,名稱為「foo」。

執(zhí)行如下命令

127.0.0.1:6379> GET foo

當(dāng)返回結(jié)果時,Redis 會分配 200m 的內(nèi)存,并執(zhí)行 memcpy 拷貝。

void _addReplyProtoToList(client *c, const char *s, size_t len) {
    ...
    if (len) {
        /* Create a new node, make sure it is allocated to at
         * least PROTO_REPLY_CHUNK_BYTES */
        size_t size = len < PROTO_REPLY_CHUNK_BYTES? PROTO_REPLY_CHUNK_BYTES: len;
        // 分配內(nèi)存(例子中為 200m)
        tail = zmalloc(size + sizeof(clientReplyBlock));
        /* take over the allocation's internal fragmentation */
        tail->size = zmalloc_usable_size(tail) - sizeof(clientReplyBlock);
        tail->used = len;
        // 內(nèi)存拷貝
        memcpy(tail->buf, s, len);
        listAddNodeTail(c->reply, tail);
        c->reply_bytes += tail->size;

        closeClientOnOutputBufferLimitReached(c, 1);
    }}

而 Redis 輸出 buf 為 16k

// server.h#define PROTO_REPLY_CHUNK_BYTES (16*1024) /* 16k output buffer */typedef struct client {
    ...
    char buf[PROTO_REPLY_CHUNK_BYTES];} client;

這意味著 Redis 無法單次返回響應(yīng)數(shù)據(jù),需要注冊「可寫事件」,從而觸發(fā)多次 write 系統(tǒng)調(diào)用。

這里有兩個耗時點:

  • 分配大內(nèi)存(也可能釋放內(nèi)存,如 DEL 命令)

  • 觸發(fā)多次可寫事件(頻繁執(zhí)行系統(tǒng)調(diào)用,如 write、epoll_wait)

那么,如何找出 big key 呢?

如果 slow log 出現(xiàn)了簡單命令,如 GET、SET、DEL,大概率是出現(xiàn)了 big key。

127.0.0.1:6379> SLOWLOG GET
    3) (integer) 201323  // 單位微妙
    4) 1) "GET"
       2) "foo"

其次,可以通過 Redis 分析工具來查找 big key。

$ redis-cli --bigkeys -i 0.1
...
[00.00%] Biggest string found so far '"foo"' with 209715200 bytes

-------- summary -------

Sampled 1 keys in the keyspace!
Total key length in bytes is 3 (avg len 3.00)

Biggest string found '"foo"' has 209715200 bytes

1 strings with 209715200 bytes (100.00% of keys, avg size 209715200.00)
0 lists with 0 items (00.00% of keys, avg size 0.00)
0 hashs with 0 fields (00.00% of keys, avg size 0.00)
0 streams with 0 entries (00.00% of keys, avg size 0.00)
0 sets with 0 members (00.00% of keys, avg size 0.00)
0 zsets with 0 members (00.00% of keys, avg size 0.00)

對于 big key,有以下幾點建議:

1.業(yè)務(wù)中盡量避免 big key 出現(xiàn)。當(dāng)出現(xiàn) big key 時,你要判斷這樣設(shè)計是否合理,又或者是出現(xiàn)了 bug。

2.將 big key 拆分為多個小 key。

3.使用替代命令。

  • 如果 Redis 版本大于 4.0,可使用 UNLINK 命令替代 DEL。Redis 版本大于 6.0,可開啟 lazy-free 機制。將釋放內(nèi)存操作,放到后臺線程執(zhí)行。

  • LRANGE、HGETALL 等替換為 LSCAN、HSCAN 分次獲取。

但我還是建議在業(yè)務(wù)中避免 big key。


04 避免執(zhí)行時間復(fù)雜度高的命令

我們知道 Redis 是「單線程」執(zhí)行命令的。執(zhí)行時間復(fù)雜度高的命令,很可能會阻塞其它請求。

復(fù)雜度高的命令和元素數(shù)量有關(guān)。通常有以下兩種場景。

  1. 元素太多,消耗 IO 資源。如 HGETALL、LRANGE,時間復(fù)雜度為 O(N)。

  2. 計算過于復(fù)雜,消費 CPU 資源。如 ZUNIONSTORE,時間復(fù)雜度為 O(N)+O(M log(M))

Redis 官方手冊,標記了命令執(zhí)行的時間復(fù)雜度。建議你在使用不熟悉的命令前,先查看手冊,留意時間復(fù)雜度。

實際業(yè)務(wù)中,你應(yīng)該盡量避免時間復(fù)雜度高的命令。如果必須要用,有兩點建議

  1. 保證操作的元素數(shù)量,盡可能少。

  2. 讀寫分離。復(fù)雜命令通常是讀請求,可以放到「slave」結(jié)點執(zhí)行。


05 使用惰性刪除 Lazy free

key 過期或是使用 DEL 刪除命令時,Redis 除了從全局 hash 表移除對象外,還會將對象分配的內(nèi)存釋放。當(dāng)遇到 big key 時,釋放內(nèi)存會造成主線程阻塞。

為此,Redis 4.0 引入了 UNLINK 命令,將釋放對象內(nèi)存操作放入 bio 后臺線程執(zhí)行。從而有效減少主線程阻塞。

Redis 6.0 更進一步,引入了 Lazy-free 相關(guān)配置。當(dāng)開啟配置后,key 過期和 DEL 命令內(nèi)部,會將「釋放對象」操作「異步執(zhí)行」。

void delCommand(client *c) {
    delGenericCommand(c,server.lazyfree_lazy_user_del);}void delGenericCommand(client *c, int lazy) {
    int numdel = 0, j;

    for (j = 1; j < c->argc; j++) {
        expireIfNeeded(c->db,c->argv[j]);
        // 開啟 lazy free 則使用異步刪除
        int deleted = lazy ? dbAsyncDelete(c->db,c->argv[j]) :
                              dbSyncDelete(c->db,c->argv[j]);
        ...
    }}

建議至少升級到 Redis 6,并開啟 Lazy-free。


06 讀寫分離

Redis 通過副本,實現(xiàn)「主-從」運行模式,是故障切換的基石,用來提高系統(tǒng)運行可靠性。也支持讀寫分離,提高讀性能。

你可以部署一個主結(jié)點,多個從結(jié)點。將讀命令分散到從結(jié)點中,從而減輕主結(jié)點壓力,提升性能。

提升Redis性能的小技巧有哪些


07 綁定 CPU

Redis 6.0 開始支持綁定 CPU,可以有效減少線程上下文切換。

CPU 親和性(CPU Affinity)是一種調(diào)度屬性,它將一個進程或線程,「綁定」到一個或一組 CPU 上。也稱為 CPU 綁定。

設(shè)置 CPU 親和性可以一定程度避免 CPU 上下文切換,提高 CPU L1、L2 Cache 命中率。

早期「SMP」架構(gòu)下,每個 CPU 通過 BUS 總線共享資源。CPU 綁定意義不大。

提升Redis性能的小技巧有哪些
而在當(dāng)前主流的「NUMA」架構(gòu)下,每個 CPU 有自己的本地內(nèi)存。訪問本地內(nèi)存有更快的速度。而訪問其他 CPU 內(nèi)存會導(dǎo)致較大的延遲。這時,CPU 綁定對系統(tǒng)運行速度的提升有較大的意義。

提升Redis性能的小技巧有哪些
現(xiàn)實中的 NUMA 架構(gòu)比上圖更復(fù)雜,通常會將 CPU 分組,若干個 CPU 分配一組內(nèi)存,稱為 「node」。

你可以通過 「numactl -H 」 命令來查看 NUMA 硬件信息。

$ numactl -H
available: 2 nodes (0-1)node 0 cpus: 0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38
node 0 size: 32143 MB
node 0 free: 26681 MB
node 1 cpus: 1 3 5 7 9 11 13 15 17 19 21 23 25 27 29 31 33 35 37 39
node 1 size: 32309 MB
node 1 free: 24958 MB
node distances:
node 0 1
  0: 10 21
  1: 21 10

上圖中可以得知該機器有 40 個 CPU,分組為 2 個 node。

node distances 是一個二維矩陣,表示 node 之間 「訪問距離」,10 為基準值。上述命令中可以得知,node 自身訪問,距離是 10???node 訪問,如 node 0 訪問 node 1 距離為 21。說明該機器「跨 node 訪問速度」比「node 自身訪問速度」慢 2.1 倍。

其實,早在 2015 年,有人提出 Redis 需要支持設(shè)置 CPU 親和性,而當(dāng)時的 Redis 還沒有支持 IO 多線程,該提議擱置。

而 Redis 6.0 引入 IO 多線程。同時,也支持了設(shè)置 CPU 親和性。

我畫了一張 Redis 6.0 線程家族供你參考。

提升Redis性能的小技巧有哪些

上圖可分為 3 個模塊

  • 主線程和 IO 線程:負責(zé)命令讀取、解析、結(jié)果返回。命令執(zhí)行由主線程完成。

  • bio 線程:負責(zé)執(zhí)行耗時的異步任務(wù),如 close fd。

  • 后臺進程:fork 子進程來執(zhí)行耗時的命令。

Redis 支持分別配置上述模塊的 CPU 親和度。你可以在 redis.conf 找到以下配置(該配置需手動開啟)。

# IO 線程(包含主線程)綁定到 CPU 0、2、4、6
server_cpulist 0-7:2
# bio 線程綁定到 CPU 1、3
bio_cpulist 1,3
# aof rewrite 后臺進程綁定到 CPU 8、9、10、11
aof_rewrite_cpulist 8-11
# bgsave 后臺進程綁定到 CPU 1、10、11
bgsave_cpulist 1,10-11

我在上述機器,針對 IO 線程和主線程,進行如下測試:

首先,開啟 IO 線程配置。

io-threads 4 # 主線程 + 3 個 IO 線程io-threads-do-reads yes # IO 線程開啟讀和解析命令功能

測試如下三種場景:

  1. 不開啟 CPU 綁定配置。

  2. 綁定到不同 node。
    「server_cpulist 0,1,2,3」

  3. 綁定到相同 node。
    「server_cpulist 0,2,4,6」

通過 redis-benchmark 對 get 命令進行基準測試,每種場景執(zhí)行 3 次。

$ redis-benchmark -n 5000000 -c 50 -t get --threads 4

結(jié)果如下:

1.不開啟 CPU 綁定配置

throughput summary: 248818.11 requests per second
throughput summary: 248694.36 requests per second
throughput summary: 249004.00 requests per second

2.綁定不同 node

throughput summary: 248880.03 requests per second
throughput summary: 248447.20 requests per second
throughput summary: 248818.11 requests per second

3.綁定相同 node

throughput summary: 284414.09 requests per second
throughput summary: 284333.25 requests per second
throughput summary: 265252.00 requests per second

根據(jù)測試結(jié)果,綁定到同一個 node,qps 大約提升 15%

使用綁定 CPU,你需要注意以下幾點:

  1. Linux 下,你可以使用 「numactl --hardware」 查看硬件布局,確保支持并開啟 NUMA。

  2. 線程要盡可能分布在 「不同的 CPU,相同的 node」,設(shè)置 CPU 親和度才有效。否則會造成頻繁上下文切換和遠距離內(nèi)存訪問。

  3. 你要熟悉 CPU 架構(gòu),做好充分的測試。否則可能適得其反,導(dǎo)致 Redis 性能下降。


08 合理配置持久化策略

Redis 支持兩種持久化策略,RDB 和 AOF。

RDB 通過 fork 子進程,生成數(shù)據(jù)快照,二進制格式。

AOF 是增量日志,文本格式,通常較大。會通過 AOF rewrite 重寫日志,節(jié)省空間。

除了手動執(zhí)行「BGREWRITEAOF」命令外,以下 4 點也會觸發(fā) AOF 重寫

  1. 執(zhí)行「config set appendonly yes」命令

  2. AOF 文件大小比例超出閾值,「auto-aof-rewrite-percentage」

  3. AOF 文件大小絕對值超出閾值,「auto-aof-rewrite-min-size」

  4. 主從復(fù)制完成 RDB 加載

RDB 和 AOF,都是在主線程中觸發(fā)執(zhí)行。雖然具體執(zhí)行,會通過 fork 交給后臺子進程。但 fork 操作,會拷貝進程數(shù)據(jù)結(jié)構(gòu)、頁表等,當(dāng)實例內(nèi)存較大時,會影響性能。

AOF 支持以下三種策略。

  1. appendfsync no:由操作系統(tǒng)決定執(zhí)行 fsync 時機。 對 Linux 來說,通常每 30 秒執(zhí)行一次 fsync,將緩沖區(qū)中的數(shù)據(jù)刷到磁盤上。如果 Redis qps 過高或?qū)?big key,可能導(dǎo)致 buffer 寫滿,從而頻繁觸發(fā) fsync。

  2. appendfsync everysec: 每秒執(zhí)行一次 fsync。

  3. appendfsync always: 每次「寫」會調(diào)用一次 fsync,性能影響較大。

AOF 和 RDB 都會對磁盤 IO 造成較高的壓力。其中,AOF rewrite 會將 Redis hash 表所有數(shù)據(jù)進行遍歷并寫磁盤。對性能會產(chǎn)生一定的影響。

線上業(yè)務(wù) Redis 通常是高可用的。如果對緩存數(shù)據(jù)丟失不敏感??紤]關(guān)閉 RDB 和 AOF 以提升性能。

如果無法關(guān)閉,有以下幾點建議:

  1. RDB 選擇業(yè)務(wù)低峰期做,通常為凌晨。保持單個實例內(nèi)存不超過 32 G。太大的內(nèi)存會導(dǎo)致 fork 耗時增加。

  2. AOF 選擇 appendfsync no 或者 appendfsync everysec。

  3. AOF auto-aof-rewrite-min-size 配置大一些,如 2G。避免頻繁觸發(fā) rewrite。

  4. AOF 可以僅在從節(jié)點開啟,減輕主節(jié)點壓力。

根據(jù)本地測試,不開啟 AOF,寫性能大約能提升 20% 左右。


09 使用長連接

Redis 是基于 TCP 協(xié)議,請求-響應(yīng)式服務(wù)器。使用短連接會導(dǎo)致頻繁的創(chuàng)建連接。

短連接有以下幾個慢速操作:

  1. 創(chuàng)建連接時,TCP 會執(zhí)行三次握手、慢啟動等策略。

  2. Redis 會觸發(fā)新建/斷開連接事件,執(zhí)行分配/銷毀客戶端等耗時操作。

  3. 如果你使用的是 Redis Cluster,新建連接時,客戶端會拉取 slots 信息初始化。建立連接速度更慢。

所以,相對于性能快速的 Redis,創(chuàng)建連接是十分慢速的操作。

「建議使用連接池,并合理設(shè)置連接池大小」。

但使用長連接時,需要留意一點,要有「自動重連」策略。避免因網(wǎng)絡(luò)異常,導(dǎo)致連接失效,影響正常業(yè)務(wù)。


10 關(guān)閉 SWAP

SWAP 是內(nèi)存交換技術(shù)。將內(nèi)存按頁,復(fù)制到預(yù)先設(shè)定的磁盤空間上。

內(nèi)存是快速的,昂貴的。而磁盤是低速的,廉價的。

通常使用 SWAP 越多,系統(tǒng)性能越低。

Redis 是內(nèi)存數(shù)據(jù)庫,使用 SWAP 會導(dǎo)致性能快速下降

建議留有足夠內(nèi)存,并關(guān)閉 SWAP。


我繪制了思維導(dǎo)圖,方便大家記憶。

提升Redis性能的小技巧有哪些
可以看到,性能優(yōu)化并不容易,需要我們了解很多底層知識,并做出充分測試。在不同機器、不同系統(tǒng)、不同配置下,Redis 都會有不同的性能表現(xiàn)。

感謝你能夠認真閱讀完這篇文章,希望小編分享的“提升Redis性能的小技巧有哪些”這篇文章對大家有幫助,同時也希望大家多多支持億速云,關(guān)注億速云行業(yè)資訊頻道,更多相關(guān)知識等著你來學(xué)習(xí)!

向AI問一下細節(jié)

免責(zé)聲明:本站發(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