您好,登錄后才能下訂單哦!
這篇文章主要介紹了提升Redis性能的小技巧有哪些,具有一定借鑒價值,感興趣的朋友可以參考下,希望大家閱讀完這篇文章之后大有收獲,下面讓小編帶著大家一起了解一下。
Redis 是基于請求-響應模型的 TCP 服務器。意味著單次請求 RTT(往返時間),取決于當前網絡狀況 。這會導致單個 Redis 請求可能非常快,比如通過本地環路網卡。可能非常慢,比如處于網絡狀況不佳的環境。
另一方面,Redis 每次請求-響應,都涉及到 read 和 write 系統調用。甚至會觸發多次 epoll_wait 系統調用(Linux 平臺)。這導致 Redis 不斷在用戶態和內核態進行切換。
static int connSocketRead(connection *conn, void *buf, size_t buf_len) { // read 系統調用 int ret = read(conn->fd, buf, buf_len);}static int connSocketWrite(connection *conn, const void *data, size_t data_len) { // write 系統調用 int ret = write(conn->fd, data, data_len);}int aeProcessEvents(aeEventLoop *eventLoop, int flags) { // 事件觸發,Linux 下為 epoll_wait 系統調用 numevents = aeApiPoll(eventLoop, tvp);}
那么,如何節省往返時間和系統調用次數呢?批處理是一個好的辦法。
為此,Redis 提供了 「pipeline」。pipeline 的原理很簡單,將多個命令打包成「一個命令」發送。Redis 收到后,解析成多個命令執行。最終將多個結果打包返回。
「pipeline 可以有效的提升 Redis 性能」。
但是,使用 pipeline 有幾點需要你留意
「pipeline 不能保證原子性」。在一次 pipeline 命令執行期間,可能會執行其它 client 發起的命令。請記住,pipeline 只是批量處理命令。想要保證原子性,使用 MULTI 或者 Lua 腳本。
「單次 pipeline 命令不宜過多」。當使用 pipeline 時,Redis 會將 pipeline 命令的響應結果,暫存在內存 Reply buffer 中,等待所有命令執行完畢后返回。如果 pipeline 命令過多,可能會導致占用較多內存。可以將單個 pipeline 拆分成多個 pipeline。
在「Redis 6」版本以前,Redis 是 「單線程」 讀取、解析、執行命令的。Redis 6 開始,引入了 IO 多線程。
IO 線程負責讀取命令、解析命令、返回結果。開啟后可以有效提升 IO 性能。
我畫了一張示意圖供你參考
如上圖所示,主線程和 IO 線程會共同參與命令的讀取、解析以及結果響應。
但執行命令的,為 「主線程」。
IO 線程默認關閉,你可以修改 redis.conf 以下配置開啟。
io-threads 4 io-threads-do-reads yes
「io-threads」 是 IO 線程數(包含主線程),我建議你根據機器,設置不同值進行壓測,取最優值。
Redis 執行命令是單線程的,這意味著 Redis 操作「big key」有阻塞的風險。
big key 通常指的是 Redis 存儲的 value 過大。包括:
單個 value 過大。如 200M 大小的 String。
集合元素過多。如 List、Hash、Set、ZSet 中有幾百、上千萬數據。
舉個例子,假設我們有一個 200M 大小的 String key,名稱為「foo」。
執行如下命令
127.0.0.1:6379> GET foo
當返回結果時,Redis 會分配 200m 的內存,并執行 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; // 分配內存(例子中為 200m) tail = zmalloc(size + sizeof(clientReplyBlock)); /* take over the allocation's internal fragmentation */ tail->size = zmalloc_usable_size(tail) - sizeof(clientReplyBlock); tail->used = len; // 內存拷貝 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 無法單次返回響應數據,需要注冊「可寫事件」,從而觸發多次 write 系統調用。
這里有兩個耗時點:
分配大內存(也可能釋放內存,如 DEL 命令)
觸發多次可寫事件(頻繁執行系統調用,如 write、epoll_wait)
那么,如何找出 big key 呢?
如果 slow log 出現了簡單命令,如 GET、SET、DEL,大概率是出現了 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.業務中盡量避免 big key 出現。當出現 big key 時,你要判斷這樣設計是否合理,又或者是出現了 bug。
2.將 big key 拆分為多個小 key。
3.使用替代命令。
如果 Redis 版本大于 4.0,可使用 UNLINK 命令替代 DEL。Redis 版本大于 6.0,可開啟 lazy-free 機制。將釋放內存操作,放到后臺線程執行。
LRANGE、HGETALL 等替換為 LSCAN、HSCAN 分次獲取。
但我還是建議在業務中避免 big key。
我們知道 Redis 是「單線程」執行命令的。執行時間復雜度高的命令,很可能會阻塞其它請求。
復雜度高的命令和元素數量有關。通常有以下兩種場景。
元素太多,消耗 IO 資源。如 HGETALL、LRANGE,時間復雜度為 O(N)。
計算過于復雜,消費 CPU 資源。如 ZUNIONSTORE,時間復雜度為 O(N)+O(M log(M))
Redis 官方手冊,標記了命令執行的時間復雜度。建議你在使用不熟悉的命令前,先查看手冊,留意時間復雜度。
實際業務中,你應該盡量避免時間復雜度高的命令。如果必須要用,有兩點建議
保證操作的元素數量,盡可能少。
讀寫分離。復雜命令通常是讀請求,可以放到「slave」結點執行。
key 過期或是使用 DEL 刪除命令時,Redis 除了從全局 hash 表移除對象外,還會將對象分配的內存釋放。當遇到 big key 時,釋放內存會造成主線程阻塞。
為此,Redis 4.0 引入了 UNLINK 命令,將釋放對象內存操作放入 bio 后臺線程執行。從而有效減少主線程阻塞。
Redis 6.0 更進一步,引入了 Lazy-free 相關配置。當開啟配置后,key 過期和 DEL 命令內部,會將「釋放對象」操作「異步執行」。
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。
Redis 通過副本,實現「主-從」運行模式,是故障切換的基石,用來提高系統運行可靠性。也支持讀寫分離,提高讀性能。
你可以部署一個主結點,多個從結點。將讀命令分散到從結點中,從而減輕主結點壓力,提升性能。
Redis 6.0 開始支持綁定 CPU,可以有效減少線程上下文切換。
CPU 親和性(CPU Affinity)是一種調度屬性,它將一個進程或線程,「綁定」到一個或一組 CPU 上。也稱為 CPU 綁定。
設置 CPU 親和性可以一定程度避免 CPU 上下文切換,提高 CPU L1、L2 Cache 命中率。
早期「SMP」架構下,每個 CPU 通過 BUS 總線共享資源。CPU 綁定意義不大。
而在當前主流的「NUMA」架構下,每個 CPU 有自己的本地內存。訪問本地內存有更快的速度。而訪問其他 CPU 內存會導致較大的延遲。這時,CPU 綁定對系統運行速度的提升有較大的意義。
現實中的 NUMA 架構比上圖更復雜,通常會將 CPU 分組,若干個 CPU 分配一組內存,稱為 「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 需要支持設置 CPU 親和性,而當時的 Redis 還沒有支持 IO 多線程,該提議擱置。
而 Redis 6.0 引入 IO 多線程。同時,也支持了設置 CPU 親和性。
我畫了一張 Redis 6.0 線程家族供你參考。
上圖可分為 3 個模塊
主線程和 IO 線程:負責命令讀取、解析、結果返回。命令執行由主線程完成。
bio 線程:負責執行耗時的異步任務,如 close fd。
后臺進程:fork 子進程來執行耗時的命令。
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 線程開啟讀和解析命令功能
測試如下三種場景:
不開啟 CPU 綁定配置。
綁定到不同 node。
「server_cpulist 0,1,2,3」
綁定到相同 node。
「server_cpulist 0,2,4,6」
通過 redis-benchmark 對 get 命令進行基準測試,每種場景執行 3 次。
$ redis-benchmark -n 5000000 -c 50 -t get --threads 4
結果如下:
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
根據測試結果,綁定到同一個 node,qps 大約提升 15%
使用綁定 CPU,你需要注意以下幾點:
Linux 下,你可以使用 「numactl --hardware」 查看硬件布局,確保支持并開啟 NUMA。
線程要盡可能分布在 「不同的 CPU,相同的 node」,設置 CPU 親和度才有效。否則會造成頻繁上下文切換和遠距離內存訪問。
你要熟悉 CPU 架構,做好充分的測試。否則可能適得其反,導致 Redis 性能下降。
Redis 支持兩種持久化策略,RDB 和 AOF。
RDB 通過 fork 子進程,生成數據快照,二進制格式。
AOF 是增量日志,文本格式,通常較大。會通過 AOF rewrite 重寫日志,節省空間。
除了手動執行「BGREWRITEAOF」命令外,以下 4 點也會觸發 AOF 重寫
執行「config set appendonly yes」命令
AOF 文件大小比例超出閾值,「auto-aof-rewrite-percentage」
AOF 文件大小絕對值超出閾值,「auto-aof-rewrite-min-size」
主從復制完成 RDB 加載
RDB 和 AOF,都是在主線程中觸發執行。雖然具體執行,會通過 fork 交給后臺子進程。但 fork 操作,會拷貝進程數據結構、頁表等,當實例內存較大時,會影響性能。
AOF 支持以下三種策略。
appendfsync no:由操作系統決定執行 fsync 時機。 對 Linux 來說,通常每 30 秒執行一次 fsync,將緩沖區中的數據刷到磁盤上。如果 Redis qps 過高或寫 big key,可能導致 buffer 寫滿,從而頻繁觸發 fsync。
appendfsync everysec: 每秒執行一次 fsync。
appendfsync always: 每次「寫」會調用一次 fsync,性能影響較大。
AOF 和 RDB 都會對磁盤 IO 造成較高的壓力。其中,AOF rewrite 會將 Redis hash 表所有數據進行遍歷并寫磁盤。對性能會產生一定的影響。
線上業務 Redis 通常是高可用的。如果對緩存數據丟失不敏感。考慮關閉 RDB 和 AOF 以提升性能。
如果無法關閉,有以下幾點建議:
RDB 選擇業務低峰期做,通常為凌晨。保持單個實例內存不超過 32 G。太大的內存會導致 fork 耗時增加。
AOF 選擇 appendfsync no 或者 appendfsync everysec。
AOF auto-aof-rewrite-min-size 配置大一些,如 2G。避免頻繁觸發 rewrite。
AOF 可以僅在從節點開啟,減輕主節點壓力。
根據本地測試,不開啟 AOF,寫性能大約能提升 20% 左右。
Redis 是基于 TCP 協議,請求-響應式服務器。使用短連接會導致頻繁的創建連接。
短連接有以下幾個慢速操作:
創建連接時,TCP 會執行三次握手、慢啟動等策略。
Redis 會觸發新建/斷開連接事件,執行分配/銷毀客戶端等耗時操作。
如果你使用的是 Redis Cluster,新建連接時,客戶端會拉取 slots 信息初始化。建立連接速度更慢。
所以,相對于性能快速的 Redis,創建連接是十分慢速的操作。
「建議使用連接池,并合理設置連接池大小」。
但使用長連接時,需要留意一點,要有「自動重連」策略。避免因網絡異常,導致連接失效,影響正常業務。
SWAP 是內存交換技術。將內存按頁,復制到預先設定的磁盤空間上。
內存是快速的,昂貴的。而磁盤是低速的,廉價的。
通常使用 SWAP 越多,系統性能越低。
Redis 是內存數據庫,使用 SWAP 會導致性能快速下降。
建議留有足夠內存,并關閉 SWAP。
我繪制了思維導圖,方便大家記憶。
可以看到,性能優化并不容易,需要我們了解很多底層知識,并做出充分測試。在不同機器、不同系統、不同配置下,Redis 都會有不同的性能表現。
感謝你能夠認真閱讀完這篇文章,希望小編分享的“提升Redis性能的小技巧有哪些”這篇文章對大家有幫助,同時也希望大家多多支持億速云,關注億速云行業資訊頻道,更多相關知識等著你來學習!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。