日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網(wǎng)為廣大站長提供免費收錄網(wǎng)站服務(wù),提交前請做好本站友鏈:【 網(wǎng)站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(wù)(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

redis 是一款高性能的內(nèi)存數(shù)據(jù)存儲系統(tǒng),它被廣泛應(yīng)用于各種實時數(shù)據(jù)處理場景,如緩存、消息隊列、實時統(tǒng)計等。為了最大化 Redis 的性能,我們應(yīng)該針對具體應(yīng)用場景,對其配置參數(shù)進行優(yōu)化。在本文中,我們將介紹一些優(yōu)化 Redis 配置的技巧,以提高其性能。

 

Redis慢在哪里?

  • 對 Redis 進行基準性能測試

例如,我的機器配置比較低,當延遲為 2ms 時,我就認為 Redis 變慢了,但是如果你的硬件配置比較高,那么在你的運行環(huán)境下,可能延遲是 0.5ms 時就可以認為 Redis 變慢了。所以,你只有了解了你的 Redis 在生產(chǎn)環(huán)境服務(wù)器上的基準性能,才能進一步評估,當其延遲達到什么程度時,才認為 Redis 確實變慢了。

為了避免業(yè)務(wù)服務(wù)器到 Redis 服務(wù)器之間的網(wǎng)絡(luò)延遲,你需要直接在 Redis 服務(wù)器上測試實例的響應(yīng)延遲情況。執(zhí)行以下命令,就可以測試出這個實例 60 秒內(nèi)的最大響應(yīng)延遲:

./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.

從輸出結(jié)果可以看到,這 60 秒內(nèi)的最大響應(yīng)延遲為 119 微秒(0.119毫秒)。你還可以使用以下命令,查看一段時間內(nèi) Redis 的最小、最大、平均訪問延遲。

$ redis-cli -h 127.0.0.1 -p 6379 --latency-history -i 1
min: 0, max: 1, avg: 0.13 (100 samples) -- 1.01 seconds range
min: 0, max: 1, avg: 0.12 (99 samples) -- 1.01 seconds range
min: 0, max: 1, avg: 0.13 (99 samples) -- 1.01 seconds range
min: 0, max: 1, avg: 0.10 (99 samples) -- 1.01 seconds range
min: 0, max: 1, avg: 0.13 (98 samples) -- 1.00 seconds range
min: 0, max: 1, avg: 0.08 (99 samples) -- 1.01 seconds range

如果你觀察到的 Redis 運行時延遲是其基線性能的 2 倍及以上,就可以認定 Redis 變慢了。?網(wǎng)絡(luò)對 Redis 性能的影響,一個簡單的方法是用 iPerf 這樣的工具測試網(wǎng)絡(luò)極限帶寬。

服務(wù)器端


# iperf -s -p 12345 -i 1 -M
iperf: option requires an argument -- M
------------------------------------------------------------
Server listening on TCP port 12345
TCP window size: 4.00 MByte (default)
------------------------------------------------------------
[  4] local 172.20.0.113 port 12345 connected with 172.20.0.114 port 56796
[ ID] Interval       Transfer     Bandwidth
[  4]  0.0- 1.0 sec   614 MBytes  5.15 Gbits/sec
[  4]  1.0- 2.0 sec   622 MBytes  5.21 Gbits/sec
[  4]  2.0- 3.0 sec   647 MBytes  5.42 Gbits/sec
[  4]  3.0- 4.0 sec   644 MBytes  5.40 Gbits/sec
[  4]  4.0- 5.0 sec   651 MBytes  5.46 Gbits/sec
[  4]  5.0- 6.0 sec   652 MBytes  5.47 Gbits/sec
[  4]  6.0- 7.0 sec   669 MBytes  5.61 Gbits/sec
[  4]  7.0- 8.0 sec   670 MBytes  5.62 Gbits/sec
[  4]  8.0- 9.0 sec   667 MBytes  5.59 Gbits/sec
[  4]  9.0-10.0 sec   667 MBytes  5.60 Gbits/sec
[  4]  0.0-10.0 sec  6.35 GBytes  5.45 Gbits/sec
客戶端


# iperf -c 服務(wù)器端IP -p 12345 -i 1 -t 10 -w 20K
------------------------------------------------------------
Client connecting to 172.20.0.113, TCP port 12345
TCP window size: 40.0 KByte (WARNING: requested 20.0 KByte)
------------------------------------------------------------
[  3] local 172.20.0.114 port 56796 connected with 172.20.0.113 port 12345
[ ID] Interval       Transfer     Bandwidth
[  3]  0.0- 1.0 sec   614 MBytes  5.15 Gbits/sec
[  3]  1.0- 2.0 sec   622 MBytes  5.21 Gbits/sec
[  3]  2.0- 3.0 sec   646 MBytes  5.42 Gbits/sec
[  3]  3.0- 4.0 sec   644 MBytes  5.40 Gbits/sec
[  3]  4.0- 5.0 sec   651 MBytes  5.46 Gbits/sec
[  3]  5.0- 6.0 sec   652 MBytes  5.47 Gbits/sec
[  3]  6.0- 7.0 sec   669 MBytes  5.61 Gbits/sec
[  3]  7.0- 8.0 sec   670 MBytes  5.62 Gbits/sec
[  3]  8.0- 9.0 sec   667 MBytes  5.59 Gbits/sec
[  3]  9.0-10.0 sec   668 MBytes  5.60 Gbits/sec
[  3]  0.0-10.0 sec  6.35 GBytes  5.45 Gbits/sec

 

2.使用復(fù)雜度過高的命令

首先,第一步,你需要去查看一下 Redis 的慢日志(slowlog)。

Redis 提供了慢日志命令的統(tǒng)計功能,它記錄了有哪些命令在執(zhí)行時耗時比較久。

查看 Redis 慢日志之前,你需要設(shè)置慢日志的閾值。例如,設(shè)置慢日志的閾值為 5 毫秒,并且保留最近 500 條慢日志記錄:

# 命令執(zhí)行耗時超過 5 毫秒,記錄慢日志
CONFIG SET slowlog-log-slower-than 5000
# 只保留最近 500 條慢日志
CONFIG SET slowlog-max-len 500

 

1)經(jīng)常使用 O(N) 以上復(fù)雜度的命令,例如 SORT、SUNION、ZUNIONSTORE 聚合類命令。

2)使用 O(N) 復(fù)雜度的命令,但 N 的值非常大。

第一種情況導致變慢的原因在于,Redis 在操作內(nèi)存數(shù)據(jù)時,時間復(fù)雜度過高,要花費更多的 CPU 資源。

第二種情況導致變慢的原因在于,Redis 一次需要返回給客戶端的數(shù)據(jù)過多,更多時間花費在數(shù)據(jù)協(xié)議的組裝和網(wǎng)絡(luò)傳輸過程中。

另外,我們還可以從資源使用率層面來分析,如果你的應(yīng)用程序操作 Redis 的 OPS 不是很大,但 Redis 實例的 CPU 使用率卻很高,那么很有可能是使用了復(fù)雜度過高的命令導致的。

3.操作bigkey

如果你查詢慢日志發(fā)現(xiàn),并不是復(fù)雜度過高的命令導致的,而都是 SET / DEL 這種簡單命令出現(xiàn)在慢日志中,那么你就要懷疑你的實例否寫入了 bigkey。

 

redis-cli -h 127.0.0.1 -p 6379 --bigkeys -i 1
-------- summary -------
Sampled 829675 keys in the keyspace!
Total key length in bytes is 10059825 (avg len 12.13)
Biggest string found 'key:291880' has 10 bytes
Biggest   list found 'mylist:004' has 40 items
Biggest    set found 'myset:2386' has 38 members
Biggest   hash found 'myhash:3574' has 37 fields
Biggest   zset found 'myzset:2704' has 42 members
36313 strings with 363130 bytes (04.38% of keys, avg size 10.00)
787393 lists with 896540 items (94.90% of keys, avg size 1.14)
1994 sets with 40052 members (00.24% of keys, avg size 20.09)
1990 hashs with 39632 fields (00.24% of keys, avg size 19.92)
1985 zsets with 39750 members (00.24% of keys, avg size 20.03)

這里我需要提醒你的是,當執(zhí)行這個命令時,要注意 2 個問題:

1)對線上實例進行 bigkey 掃描時,Redis 的 OPS 會突增,為了降低掃描過程中對 Redis 的影響,最好控制一下掃描的頻率,指定 -i 參數(shù)即可,它表示掃描過程中每次掃描后休息的時間間隔,單位是秒。

2)掃描結(jié)果中,對于容器類型(List、Hash、Set、ZSet)的 key,只能掃描出元素最多的 key。但一個 key 的元素多,不一定表示占用內(nèi)存也多,你還需要根據(jù)業(yè)務(wù)情況,進一步評估內(nèi)存占用情況。

4.集中過期

如果你發(fā)現(xiàn),平時在操作 Redis 時,并沒有延遲很大的情況發(fā)生,但在某個時間點突然出現(xiàn)一波延時,其現(xiàn)象表現(xiàn)為:變慢的時間點很有規(guī)律,例如某個整點,或者每間隔多久就會發(fā)生一波延遲。

如果是出現(xiàn)這種情況,那么你需要排查一下,業(yè)務(wù)代碼中是否存在設(shè)置大量 key 集中過期的情況。

如果有大量的 key 在某個固定時間點集中過期,在這個時間點訪問 Redis 時,就有可能導致延時變大。

Redis 的過期數(shù)據(jù)采用被動過期 + 主動過期兩種策略:

1)被動過期:只有當訪問某個 key 時,才判斷這個 key 是否已過期,如果已過期,則從實例中刪除。

2)主動過期:Redis 內(nèi)部維護了一個定時任務(wù),默認每隔 100 毫秒(1秒10次)就會從全局的過期哈希表中隨機取出 20 個 key,然后刪除其中過期的 key,如果過期 key 的比例超過了 25%,則繼續(xù)重復(fù)此過程,直到過期 key 的比例下降到 25% 以下,或者這次任務(wù)的執(zhí)行耗時超過了 25 毫秒,才會退出循環(huán)。

注意,這個主動過期 key 的定時任務(wù),是在 Redis 主線程中執(zhí)行的。

也就是說如果在執(zhí)行主動過期的過程中,出現(xiàn)了需要大量刪除過期 key 的情況,那么此時應(yīng)用程序在訪問 Redis 時,必須要等待這個過期任務(wù)執(zhí)行結(jié)束,Redis 才可以服務(wù)這個客戶端請求。

如果此時需要過期刪除的是一個 bigkey,那么這個耗時會更久。而且,這個操作延遲的命令并不會記錄在慢日志中。

因為慢日志中只記錄一個命令真正操作內(nèi)存數(shù)據(jù)的耗時,而 Redis 主動刪除過期 key 的邏輯,是在命令真正執(zhí)行之前執(zhí)行的。

5.實例內(nèi)存達到上限

當我們把 Redis 當做純緩存使用時,通常會給這個實例設(shè)置一個內(nèi)存上限 maxmemory,然后設(shè)置一個數(shù)據(jù)淘汰策略。

當 Redis 內(nèi)存達到 maxmemory 后,每次寫入新的數(shù)據(jù)之前,Redis 必須先從實例中踢出一部分數(shù)據(jù),讓整個實例的內(nèi)存維持在 maxmemory 之下,然后才能把新數(shù)據(jù)寫進來。

這個踢出舊數(shù)據(jù)的邏輯也是需要消耗時間的,而具體耗時的長短,要取決于你配置的淘汰策略:

  • allkeys-lru:不管 key 是否設(shè)置了過期,淘汰最近最少訪問的 key
  • volatile-lru:只淘汰最近最少訪問、并設(shè)置了過期時間的 key
  • allkeys-random:不管 key 是否設(shè)置了過期,隨機淘汰 key
  • volatile-random:只隨機淘汰設(shè)置了過期時間的 key
  • allkeys-ttl:不管 key 是否設(shè)置了過期,淘汰即將過期的 key
  • noeviction:不淘汰任何 key,實例內(nèi)存達到 maxmeory 后,再寫入新數(shù)據(jù)直接返回錯誤
  • allkeys-lfu:不管 key 是否設(shè)置了過期,淘汰訪問頻率最低的 key(4.0+版本支持)
  • volatile-lfu:只淘汰訪問頻率最低、并設(shè)置了過期時間 key(4.0+版本支持)

 

一般最常使用的是 allkeys-lru / volatile-lru 淘汰策略,它們的處理邏輯是,每次從實例中隨機取出一批 key(這個數(shù)量可配置),然后淘汰一個最少訪問的 key,之后把剩下的 key 暫存到一個池子中,繼續(xù)隨機取一批 key,并與之前池子中的 key 比較,再淘汰一個最少訪問的 key。以此往復(fù),直到實例內(nèi)存降到 maxmemory 之下。

需要注意的是,Redis 的淘汰數(shù)據(jù)的邏輯與刪除過期 key 的一樣,也是在命令真正執(zhí)行之前執(zhí)行的,也就是說它也會增加我們操作 Redis 的延遲,而且,寫 OPS 越高,延遲也會越明顯。

如何進行Redis性能優(yōu)化?這一篇就夠了

 

如果此時你的 Redis 實例中還存儲了 bigkey,那么在淘汰刪除 bigkey 釋放內(nèi)存時,也會耗時比較久。

6.fork耗時嚴重

當 Redis 開啟了后臺 RDB 和 AOF rewrite 后,在執(zhí)行時,它們都需要主進程創(chuàng)建出一個子進程進行數(shù)據(jù)的持久化。主進程創(chuàng)建子進程,會調(diào)用操作系統(tǒng)提供的 fork 函數(shù)。

而 fork 在執(zhí)行過程中,主進程需要拷貝自己的內(nèi)存頁表給子進程,如果這個實例很大,那么這個拷貝的過程也會比較耗時。

而且這個 fork 過程會消耗大量的 CPU 資源,在完成 fork 之前,整個 Redis 實例會被阻塞住,無法處理任何客戶端請求。

如果此時你的 CPU 資源本來就很緊張,那么 fork 的耗時會更長,甚至達到秒級,這會嚴重影響 Redis 的性能。

那如何確認確實是因為 fork 耗時導致的 Redis 延遲變大呢?

你可以在 Redis 上執(zhí)行 INFO 命令,查看 latest_fork_usec 項,單位微秒。

# 上一次 fork 耗時,單位微秒
latest_fork_usec:59477

這個時間就是主進程在 fork 子進程期間,整個實例阻塞無法處理客戶端請求的時間。

如果你發(fā)現(xiàn)這個耗時很久,就要警惕起來了,這意味在這期間,你的整個 Redis 實例都處于不可用的狀態(tài)。

除了數(shù)據(jù)持久化會生成 RDB 之外,當主從節(jié)點第一次建立數(shù)據(jù)同步時,主節(jié)點也創(chuàng)建子進程生成 RDB,然后發(fā)給從節(jié)點進行一次全量同步,所以,這個過程也會對 Redis 產(chǎn)生性能影響。

如何進行Redis性能優(yōu)化?這一篇就夠了

 

7.開啟內(nèi)存大頁

除了上面講到的子進程 RDB 和 AOF rewrite 期間,fork 耗時導致的延時變大之外,這里還有一個方面也會導致性能問題,這就是操作系統(tǒng)是否開啟了內(nèi)存大頁機制

什么是內(nèi)存大頁?

我們都知道,應(yīng)用程序向操作系統(tǒng)申請內(nèi)存時,是按內(nèi)存頁進行申請的,而常規(guī)的內(nèi)存頁大小是 4KB。

linux 內(nèi)核從 2.6.38 開始,支持了內(nèi)存大頁機制,該機制允許應(yīng)用程序以 2MB 大小為單位,向操作系統(tǒng)申請內(nèi)存。

應(yīng)用程序每次向操作系統(tǒng)申請的內(nèi)存單位變大了,但這也意味著申請內(nèi)存的耗時變長。

這對 Redis 會有什么影響呢?

當 Redis 在執(zhí)行后臺 RDB,采用 fork 子進程的方式來處理。但主進程 fork 子進程后,此時的主進程依舊是可以接收寫請求的,而進來的寫請求,會采用 Copy On Write(寫時復(fù)制)的方式操作內(nèi)存數(shù)據(jù)。

 

也就是說,主進程一旦有數(shù)據(jù)需要修改,Redis 并不會直接修改現(xiàn)有內(nèi)存中的數(shù)據(jù),而是先將這塊內(nèi)存數(shù)據(jù)拷貝出來,再修改這塊新內(nèi)存的數(shù)據(jù),這就是所謂的「寫時復(fù)制」。

寫時復(fù)制你也可以理解成,誰需要發(fā)生寫操作,誰就需要先拷貝,再修改。

這樣做的好處是,父進程有任何寫操作,并不會影響子進程的數(shù)據(jù)持久化(子進程只持久化 fork 這一瞬間整個實例中的所有數(shù)據(jù)即可,不關(guān)心新的數(shù)據(jù)變更,因為子進程只需要一份內(nèi)存快照,然后持久化到磁盤上)。

但是請注意,主進程在拷貝內(nèi)存數(shù)據(jù)時,這個階段就涉及到新內(nèi)存的申請,如果此時操作系統(tǒng)開啟了內(nèi)存大頁,那么在此期間,客戶端即便只修改 10B 的數(shù)據(jù),Redis 在申請內(nèi)存時也會以 2MB 為單位向操作系統(tǒng)申請,申請內(nèi)存的耗時變長,進而導致每個寫請求的延遲增加,影響到 Redis 性能。

同樣地,如果這個寫請求操作的是一個 bigkey,那主進程在拷貝這個 bigkey 內(nèi)存塊時,一次申請的內(nèi)存會更大,時間也會更久。可見,bigkey 在這里又一次影響到了性能。

8.開啟AOF

前面我們分析了 RDB 和 AOF rewrite 對 Redis 性能的影響,主要關(guān)注點在 fork 上。

其實,關(guān)于數(shù)據(jù)持久化方面,還有影響 Redis 性能的因素,這次我們重點來看 AOF 數(shù)據(jù)持久化。

如果你的 AOF 配置不合理,還是有可能會導致性能問題。

當 Redis 開啟 AOF 后,其工作原理如下:

1)Redis 執(zhí)行寫命令后,把這個命令寫入到 AOF 文件內(nèi)存中(write 系統(tǒng)調(diào)用)

 

2)Redis 根據(jù)配置的 AOF 刷盤策略,把 AOF 內(nèi)存數(shù)據(jù)刷到磁盤上(fsync 系統(tǒng)調(diào)用)

為了保證 AOF 文件數(shù)據(jù)的安全性,Redis 提供了 3 種刷盤機制:

1)Appendfsync always:主線程每次執(zhí)行寫操作后立即刷盤,此方案會占用比較大的磁盤 IO 資源,但數(shù)據(jù)安全性最高。

2)appendfsync no:主線程每次寫操作只寫內(nèi)存就返回,內(nèi)存數(shù)據(jù)什么時候刷到磁盤,交由操作系統(tǒng)決定,此方案對性能影響最小,但數(shù)據(jù)安全性也最低,Redis 宕機時丟失的數(shù)據(jù)取決于操作系統(tǒng)刷盤時機。

3)appendfsync everysec:主線程每次寫操作只寫內(nèi)存就返回,然后由后臺線程每隔 1 秒執(zhí)行一次刷盤操作(觸發(fā)fsync系統(tǒng)調(diào)用),此方案對性能影響相對較小,但當 Redis 宕機時會丟失 1 秒的數(shù)據(jù)。

看到這里,我猜你肯定和大多數(shù)人的想法一樣,選比較折中的方案 appendfsync everysec 就沒問題了吧?

 

這個方案優(yōu)勢在于,Redis 主線程寫完內(nèi)存后就返回,具體的刷盤操作是放到后臺線程中執(zhí)行的,后臺線程每隔 1 秒把內(nèi)存中的數(shù)據(jù)刷到磁盤中。

這種方案既兼顧了性能,又盡可能地保證了數(shù)據(jù)安全,是不是覺得很完美?

但是,這里我要給你潑一盆冷水了,采用這種方案你也要警惕一下,因為這種方案還是存在導致 Redis 延遲變大的情況發(fā)生,甚至會阻塞整個 Redis。

你試想這樣一種情況:當 Redis 后臺線程在執(zhí)行 AOF 文件刷盤時,如果此時磁盤的 IO 負載很高,那這個后臺線程在執(zhí)行刷盤操作(fsync系統(tǒng)調(diào)用)時就會被阻塞住。

 

此時的主線程依舊會接收寫請求,緊接著,主線程又需要把數(shù)據(jù)寫到文件內(nèi)存中(write 系統(tǒng)調(diào)用),當主線程使用后臺子線程執(zhí)行了一次 fsync,需要再次把新接收的操作記錄寫回磁盤時,如果主線程發(fā)現(xiàn)上一次的 fsync 還沒有執(zhí)行完,那么它就會阻塞。

 

所以,如果后臺子線程執(zhí)行的 fsync 頻繁阻塞的話(比如 AOF 重寫占用了大量的磁盤 IO 帶寬),主線程也會阻塞,導致 Redis 性能變慢。

如何進行Redis性能優(yōu)化?這一篇就夠了

 

看到了么?在這個過程中,主線程依舊有阻塞的風險。

所以,盡管你的 AOF 配置為 appendfsync everysec,也不能掉以輕心,要警惕磁盤壓力過大導致的 Redis 有性能問題。

那什么情況下會導致磁盤 IO 負載過大?以及如何解決這個問題呢?

我總結(jié)了以下幾種情況,你可以參考進行問題排查:

1)進程正在執(zhí)行 AOF rewrite,這個過程會占用大量的磁盤 IO 資源

2)有其他應(yīng)用程序在執(zhí)行大量的寫文件操作,也會占用磁盤 IO 資源

 

對于情況1,說白了就是,Redis 的 AOF 后臺子線程刷盤操作,撞上了子進程 AOF rewrite!

9.綁定CPU

很多時候,我們在部署服務(wù)時,為了提高服務(wù)性能,降低應(yīng)用程序在多個 CPU 核心之間的上下文切換帶來的性能損耗,通常采用的方案是進程綁定 CPU 的方式提高性能。

我們都知道,一般現(xiàn)代的服務(wù)器會有多個 CPU,而每個 CPU 又包含多個物理核心,每個物理核心又分為多個邏輯核心,每個物理核下的邏輯核共用 L1/L2 Cache。

而 Redis Server 除了主線程服務(wù)客戶端請求之外,還會創(chuàng)建子進程、子線程。

其中子進程用于數(shù)據(jù)持久化,而子線程用于執(zhí)行一些比較耗時操作,例如異步釋放 fd、異步 AOF 刷盤、異步 lazy-free 等等。

如果你把 Redis 進程只綁定了一個 CPU 邏輯核心上,那么當 Redis 在進行數(shù)據(jù)持久化時,fork 出的子進程會繼承父進程的 CPU 使用偏好。

而此時的子進程會消耗大量的 CPU 資源進行數(shù)據(jù)持久化(把實例數(shù)據(jù)全部掃描出來需要耗費CPU),這就會導致子進程會與主進程發(fā)生 CPU 爭搶,進而影響到主進程服務(wù)客戶端請求,訪問延遲變大。

這就是 Redis 綁定 CPU 帶來的性能問題。

10.使用Swap

如果你發(fā)現(xiàn) Redis 突然變得非常慢,每次的操作耗時都達到了幾百毫秒甚至秒級,那此時你就需要檢查 Redis 是否使用到了 Swap,在這種情況下 Redis 基本上已經(jīng)無法提供高性能的服務(wù)了。

什么是 Swap?為什么使用 Swap 會導致 Redis 的性能下降?

如果你對操作系統(tǒng)有些了解,就會知道操作系統(tǒng)為了緩解內(nèi)存不足對應(yīng)用程序的影響,允許把一部分內(nèi)存中的數(shù)據(jù)換到磁盤上,以達到應(yīng)用程序?qū)?nèi)存使用的緩沖,這些內(nèi)存數(shù)據(jù)被換到磁盤上的區(qū)域,就是 Swap。

問題就在于,當內(nèi)存中的數(shù)據(jù)被換到磁盤上后,Redis 再訪問這些數(shù)據(jù)時,就需要從磁盤上讀取,訪問磁盤的速度要比訪問內(nèi)存慢幾百倍!

尤其是針對 Redis 這種對性能要求極高、性能極其敏感的數(shù)據(jù)庫來說,這個操作延時是無法接受的。

此時,你需要檢查 Redis 機器的內(nèi)存使用情況,確認是否存在使用了 Swap。

你可以通過以下方式來查看 Redis 進程是否使用到了 Swap:

# 先找到 Redis 的進程 ID
$ ps -aux | grep redis-server
# 查看 Redis Swap 使用情況
$ cat /proc/$pid/smaps | egrep '^(Swap|Size)'

輸出結(jié)果如下:

Size:               1256 kB
Swap:                  0 kB
Size:                  4 kB
Swap:                  0 kB
Size:                132 kB
Swap:                  0 kB
Size:              63488 kB
Swap:                  0 kB
Size:                132 kB
Swap:                  0 kB
Size:              65404 kB
Swap:                  0 kB
Size:            1921024 kB
Swap:                  0 kB

 

每一行 Size 表示 Redis 所用的一塊內(nèi)存大小,Size 下面的 Swap 就表示這塊 Size 大小的內(nèi)存,有多少數(shù)據(jù)已經(jīng)被換到磁盤上了,如果這兩個值相等,說明這塊內(nèi)存的數(shù)據(jù)都已經(jīng)完全被換到磁盤上了。

 

如果只是少量數(shù)據(jù)被換到磁盤上,例如每一塊 Swap 占對應(yīng) Size 的比例很小,那影響并不是很大。如果是幾百兆甚至上 GB 的內(nèi)存被換到了磁盤上,那么你就需要警惕了,這種情況 Redis 的性能肯定會急劇下降。

11.碎片整理

Redis 的數(shù)據(jù)都存儲在內(nèi)存中,當我們的應(yīng)用程序頻繁修改 Redis 中的數(shù)據(jù)時,就有可能會導致 Redis 產(chǎn)生內(nèi)存碎片。

內(nèi)存碎片會降低 Redis 的內(nèi)存使用率,我們可以通過執(zhí)行 INFO 命令,得到這個實例的內(nèi)存碎片率:

# Memory
used_memory:5709194824
used_memory_human:5.32G
used_memory_rss:8264855552
used_memory_rss_human:7.70G
...
mem_fragmentation_ratio:1.45

 

這個內(nèi)存碎片率是怎么計算的?

很簡單,mem_fragmentation_ratio = used_memory_rss / used_memory。

其中 used_memory 表示 Redis 存儲數(shù)據(jù)的內(nèi)存大小,而 used_memory_rss 表示操作系統(tǒng)實際分配給 Redis 進程的大小。

 

如果 mem_fragmentation_ratio > 1.5,說明內(nèi)存碎片率已經(jīng)超過了 50%,這時我們就需要采取一些措施來降低內(nèi)存碎片了。

 

解決的方案一般如下:

1)如果你使用的是 Redis 4.0 以下版本,只能通過重啟實例來解決

2)如果你使用的是 Redis 4.0 版本,它正好提供了自動碎片整理的功能,可以通過配置開啟碎片自動整理。

 

但是,開啟內(nèi)存碎片整理,它也有可能會導致 Redis 性能下降。

原因在于,Redis 的碎片整理工作是也在主線程中執(zhí)行的,當其進行碎片整理時,必然會消耗 CPU 資源,產(chǎn)生更多的耗時,從而影響到客戶端的請求。

所以,當你需要開啟這個功能時,最好提前測試評估它對 Redis 的影響。

Redis 碎片整理的參數(shù)配置如下:

# 開啟自動內(nèi)存碎片整理(總開關(guān))
activedefrag yes


# 內(nèi)存使用 100MB 以下,不進行碎片整理
active-defrag-ignore-bytes 100mb


# 內(nèi)存碎片率超過 10%,開始碎片整理
active-defrag-threshold-lower 10
# 內(nèi)存碎片率超過 100%,盡最大努力碎片整理
active-defrag-threshold-upper 100


# 內(nèi)存碎片整理占用 CPU 資源最小百分比
active-defrag-cycle-min 1
# 內(nèi)存碎片整理占用 CPU 資源最大百分比
active-defrag-cycle-max 25


# 碎片整理期間,對于 List/Set/Hash/ZSet 類型元素一次 Scan 的數(shù)量
active-defrag-max-scan-fields 1000

 

Redis如何優(yōu)化

1.慢查詢優(yōu)化

1)盡量不使用 O(N) 以上復(fù)雜度過高的命令,對于數(shù)據(jù)的聚合操作,放在客戶端做。

2)執(zhí)行 O(N) 命令,保證 N 盡量的小(推薦 N <= 300),每次獲取盡量少的數(shù)據(jù),讓 Redis 可以及時處理返回。

 

2.集中過期優(yōu)化

一般有兩種方案來規(guī)避這個問題:

1.集中過期 key 增加一個隨機過期時間,把集中過期的時間打散,降低 Redis 清理過期 key 的壓力

2.如果你使用的 Redis 是 4.0 以上版本,可以開啟 lazy-free 機制,當刪除過期 key 時,把釋放內(nèi)存的操作放到后臺線程中執(zhí)行,避免阻塞主線程。

第一種方案,在設(shè)置 key 的過期時間時,增加一個隨機時間,偽代碼可以這么寫:

# 在過期時間點之后的 5 分鐘內(nèi)隨機過期掉
redis.expireat(key, expire_time + random(300))

 

第二種方案,Redis 4.0 以上版本,開啟 lazy-free 機制:

# 釋放過期 key 的內(nèi)存,放到后臺線程執(zhí)行
lazyfree-lazy-expire yes

運維層面,你需要把 Redis 的各項運行狀態(tài)數(shù)據(jù)監(jiān)控起來,在 Redis 上執(zhí)行 INFO 命令就可以拿到這個實例所有的運行狀態(tài)數(shù)據(jù)。

在這里我們需要重點關(guān)注 expired_keys 這一項,它代表整個實例到目前為止,累計刪除過期 key 的數(shù)量。

你需要把這個指標監(jiān)控起來,當這個指標在很短時間內(nèi)出現(xiàn)了突增,需要及時報警出來,然后與業(yè)務(wù)應(yīng)用報慢的時間點進行對比分析,確認時間是否一致,如果一致,則可以確認確實是因為集中過期 key 導致的延遲變大。

3.實例內(nèi)存達到上限優(yōu)化

1)避免存儲 bigkey,降低釋放內(nèi)存的耗時

2)淘汰策略改為隨機淘汰,隨機淘汰比 LRU 要快很多(視業(yè)務(wù)情況調(diào)整)

3)拆分實例,把淘汰 key 的壓力分攤到多個實例上

4)如果使用的是 Redis 4.0 以上版本,開啟 layz-free 機制,把淘汰 key 釋放內(nèi)存的操作放到后臺線程中執(zhí)行(配置 lazyfree-lazy-eviction = yes)

 

4.fork耗時嚴重優(yōu)化

1)控制 Redis 實例的內(nèi)存:盡量在 10G 以下,執(zhí)行 fork 的耗時與實例大小有關(guān),實例越大,耗時越久。

2)合理配置數(shù)據(jù)持久化策略:在 slave 節(jié)點執(zhí)行 RDB 備份,推薦在低峰期執(zhí)行,而對于丟失數(shù)據(jù)不敏感的業(yè)務(wù)(例如把 Redis 當做純緩存使用),可以關(guān)閉 AOF 和 AOF rewrite。

3)Redis 實例不要部署在虛擬機上:fork 的耗時也與系統(tǒng)也有關(guān),虛擬機比物理機耗時更久。

4)降低主從庫全量同步的概率:適當調(diào)大 repl-backlog-size 參數(shù),避免主從全量同步。

從建立同步時,優(yōu)先檢測是否可以嘗試只同步部分數(shù)據(jù),這種情況就是針對于之前已經(jīng)建立好了復(fù)制鏈路,只是因為故障導致臨時斷開,故障恢復(fù)后重新建立同步時,為了避免全量同步的資源消耗,Redis會優(yōu)先嘗試部分數(shù)據(jù)同步,如果條件不符合,才會觸發(fā)全量同步。

這個判斷依據(jù)就是在master上維護的復(fù)制緩沖區(qū)大小,如果這個緩沖區(qū)配置的過小,很有可能在主從斷開復(fù)制的這段時間內(nèi),master產(chǎn)生的寫入導致復(fù)制緩沖區(qū)的數(shù)據(jù)被覆蓋,重新建立同步時的slave需要同步的offset位置在master的緩沖區(qū)中找不到,那么此時就會觸發(fā)全量同步。

如何避免這種情況?解決方案就是適當調(diào)大復(fù)制緩沖區(qū)repl-backlog-size的大小,這個緩沖區(qū)的大小默認為1MB,如果實例寫入量比較大,可以針對性調(diào)大此配置。

5.多核CPU優(yōu)化

那如何解決這個問題呢?

如果你確實想要綁定 CPU,可以優(yōu)化的方案是,不要讓 Redis 進程只綁定在一個 CPU 邏輯核上,而是綁定在多個邏輯核心上,而且,綁定的多個邏輯核心最好是同一個物理核心,這樣它們還可以共用 L1/L2 Cache。

當然,即便我們把 Redis 綁定在多個邏輯核心上,也只能在一定程度上緩解主線程、子進程、后臺線程在 CPU 資源上的競爭。

因為這些子進程、子線程還是會在這多個邏輯核心上進行切換,存在性能損耗。?

如何再進一步優(yōu)化?

可能你已經(jīng)想到了,我們是否可以讓主線程、子進程、后臺線程,分別綁定在固定的 CPU 核心上,不讓它們來回切換,這樣一來,他們各自使用的 CPU 資源互不影響。

其實,這個方案 Redis 官方已經(jīng)想到了。

Redis 在 6.0 版本已經(jīng)推出了這個功能,我們可以通過以下配置,對主線程、后臺線程、后臺 RDB 進程、AOF rewrite 進程,綁定固定的 CPU 邏輯核心:

 

  • Redis6.0 前綁定CPU核
taskset -c 0 ./redis-server
  • Redis6.0 后綁定CPU核
# Redis Server 和 IO 線程綁定到 CPU核心 0,2,4,6
server_cpulist 0-7:2
# 后臺子線程綁定到 CPU核心 1,3
bio_cpulist 1,3
# 后臺 AOF rewrite 進程綁定到 CPU 核心 8,9,10,11
aof_rewrite_cpulist 8-11
# 后臺 RDB 進程綁定到 CPU 核心 1,10,11
# bgsave_cpulist 1,10-1

 

如果你使用的正好是 Redis 6.0 版本,就可以通過以上配置,來進一步提高 Redis 性能。

這里我需要提醒你的是,一般來說,Redis 的性能已經(jīng)足夠優(yōu)秀,除非你對 Redis 的性能有更加嚴苛的要求,否則不建議你綁定 CPU。

6.查看Redis內(nèi)存是否發(fā)生Swap

$ redis-cli info | grep process_id
process_id: 5332

然后,進入 Redis 所在機器的 /proc 目錄下的該進程目錄中:

$ cd /proc/5332

最后,運行下面的命令,查看該 Redis 進程的使用情況。在這兒,我只截取了部分結(jié)果:

$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

一旦發(fā)生內(nèi)存 swap,最直接的解決方法就是增加機器內(nèi)存。如果該實例在一個 Redis 切片集群中,可以增加 Redis 集群的實例個數(shù),來分攤每個實例服務(wù)的數(shù)據(jù)量,進而減少每個實例所需的內(nèi)存量。

7.內(nèi)存大頁

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

首先,我們要先排查下內(nèi)存大頁。方法是:在 Redis 實例運行的機器上執(zhí)行如下命令:

$ cat /sys/kernel/mm/transparent_hugepage/enabled
[always] madvise never

如果執(zhí)行結(jié)果是 always,就表明內(nèi)存大頁機制被啟動了;如果是 never,就表示,內(nèi)存大頁機制被禁止。

在實際生產(chǎn)環(huán)境中部署時,我建議你不要使用內(nèi)存大頁機制,操作也很簡單,只需要執(zhí)行下面的命令就可以了:

echo never /sys/kernel/mm/transparent_hugepage/enabled

其實,操作系統(tǒng)提供的內(nèi)存大頁機制,其優(yōu)勢是,可以在一定程序上降低應(yīng)用程序申請內(nèi)存的次數(shù)。

但是對于 Redis 這種對性能和延遲極其敏感的數(shù)據(jù)庫來說,我們希望 Redis 在每次申請內(nèi)存時,耗時盡量短,所以我不建議你在 Redis 機器上開啟這個機制。

8.刪除使用Lazy Free

支持版本:Redis 4.0+

1)主動刪除鍵使用lazy free

  • UNLINK命令

 

127.0.0.1:7000> LLEN mylist
(integer) 2000000
127.0.0.1:7000> UNLINK mylist
(integer) 1
127.0.0.1:7000> SLOWLOG get
1) 1) (integer) 1
   2) (integer) 1505465188
   3) (integer) 30
   4) 1) "UNLINK"
      2) "mylist"
   5) "127.0.0.1:17015"
   6) ""

注意:DEL命令,還是并發(fā)阻塞的刪除操作

  • FLUSHALL/FLUSHDB ASYNC
127.0.0.1:7000> DBSIZE
(integer) 1812295
127.0.0.1:7000> flushall  //同步清理實例數(shù)據(jù),180萬個key耗時1020毫秒
OK
(1.02s)
127.0.0.1:7000> DBSIZE
(integer) 1812637
127.0.0.1:7000> flushall async  //異步清理實例數(shù)據(jù),180萬個key耗時約9毫秒
OK
127.0.0.1:7000> SLOWLOG get
 1) 1) (integer) 2996109
    2) (integer) 1505465989
    3) (integer) 9274       //指令運行耗時9.2毫秒
    4) 1) "flushall"
       2) "async"
    5) "127.0.0.1:20110"
    6) ""

 

2)被動刪除鍵使用lazy free

 

lazy free應(yīng)用于被動刪除中,目前有4種場景,每種場景對應(yīng)一個配置參數(shù);默認都是關(guān)閉。

lazyfree-lazy-eviction no
lazyfree-lazy-expire no
lazyfree-lazy-server-del no
slave-lazy-flush no

 

  • lazyfree-lazy-eviction

針對redis內(nèi)存使用達到maxmeory,并設(shè)置有淘汰策略時;在被動淘汰鍵時,是否采用lazy free機制;因為此場景開啟lazy free, 可能使用淘汰鍵的內(nèi)存釋放不及時,導致redis內(nèi)存超用,超過maxmemory的限制。此場景使用時,請結(jié)合業(yè)務(wù)測試。(生產(chǎn)環(huán)境不建議設(shè)置yes)

  • lazyfree-lazy-expire

針對設(shè)置有TTL的鍵,達到過期后,被redis清理刪除時是否采用lazy free機制;此場景建議開啟,因TTL本身是自適應(yīng)調(diào)整的速度。

 

  • lazyfree-lazy-server-del

針對有些指令在處理已存在的鍵時,會帶有一個隱式的DEL鍵的操作。如rename命令,當目標鍵已存在,redis會先刪除目標鍵,如果這些目標鍵是一個big key,那就會引入阻塞刪除的性能問題。此參數(shù)設(shè)置就是解決這類問題,建議可開啟。

 

  • slave-lazy-flush

針對slave進行全量數(shù)據(jù)同步,slave在加載master的RDB文件前,會運行flushall來清理自己的數(shù)據(jù)場景, 參數(shù)設(shè)置決定是否采用異常flush機制。如果內(nèi)存變動不大,建議可開啟。可減少全量同步耗時,從而減少主庫因輸出緩沖區(qū)爆漲引起的內(nèi)存使用增長。

3)lazy free的監(jiān)控

lazy free能監(jiān)控的數(shù)據(jù)指標,只有一個值:lazyfree_pending_objects,表示redis執(zhí)行l(wèi)azy free操作,在等待被實際回收內(nèi)容的鍵個數(shù)。并不能體現(xiàn)單個大鍵的元素個數(shù)或等待lazy free回收的內(nèi)存大小。所以此值有一定參考值,可監(jiān)測redis lazy free的效率或堆積鍵數(shù)量;比如在flushall async場景下會有少量的堆積。

# info memory


# Memory
lazyfree_pending_objects:0

 

注意事項:unlink命令入口函數(shù)unlinkCommand()和del調(diào)用相同函數(shù)delGenericCommand()進行刪除KEY操作,使用lazy標識是否為lazyfree調(diào)用。如果是lazyfree,則調(diào)用dbAsyncDelete()函數(shù)。

但并非每次unlink命令就一定啟用lazy free,redis會先判斷釋放KEY的代價(cost),當cost大于LAZYFREE_THRESHOLD(64)才進行l(wèi)azy free.

釋放key代價計算函數(shù)lazyfreeGetFreeEffort(),集合類型鍵,且滿足對應(yīng)編碼,cost就是集合鍵的元數(shù)個數(shù),否則cost就是1。

 

舉例:

  • 一個包含100元素的list key, 它的free cost就是100
  • 一個512MB的string key, 它的free cost是1 所以可以看出,redis的lazy free的cost計算主要時間復(fù)雜度相關(guān)。

 

9.AOF優(yōu)化

Redis 提供了一個配置項,當子進程在 AOF rewrite 期間,可以讓后臺子線程不執(zhí)行刷盤(不觸發(fā) fsync 系統(tǒng)調(diào)用)操作。

這相當于在 AOF rewrite 期間,臨時把 appendfsync 設(shè)置為了 none,配置如下:

# AOF rewrite 期間,AOF 后臺子線程不進行刷盤操作
# 相當于在這期間,臨時把 appendfsync 設(shè)置為了 none
no-appendfsync-on-rewrite yes

 

當然,開啟這個配置項,在 AOF rewrite 期間,如果實例發(fā)生宕機,那么此時會丟失更多的數(shù)據(jù),性能和數(shù)據(jù)安全性,你需要權(quán)衡后進行選擇。

如果占用磁盤資源的是其他應(yīng)用程序,那就比較簡單了,你需要定位到是哪個應(yīng)用程序在大量寫磁盤,然后把這個應(yīng)用程序遷移到其他機器上執(zhí)行就好了,避免對 Redis 產(chǎn)生影響。

當然,如果你對 Redis 的性能和數(shù)據(jù)安全都有很高的要求,那么建議從硬件層面來優(yōu)化,更換為 SSD 磁盤,提高磁盤的 IO 能力,保證 AOF 期間有充足的磁盤資源可以使用。同時盡可能讓Redis運行在獨立的機器上。

10.Swap優(yōu)化

1)增加機器的內(nèi)存,讓 Redis 有足夠的內(nèi)存可以使用

2)整理內(nèi)存空間,釋放出足夠的內(nèi)存供 Redis 使用,然后釋放 Redis 的 Swap,讓 Redis 重新使用內(nèi)存

釋放 Redis 的 Swap 過程通常要重啟實例,為了避免重啟實例對業(yè)務(wù)的影響,一般會先進行主從切換,然后釋放舊主節(jié)點的 Swap,重啟舊主節(jié)點實例,待從庫數(shù)據(jù)同步完成后,再進行主從切換即可。

預(yù)防的辦法就是,你需要對 Redis 機器的內(nèi)存和 Swap 使用情況進行監(jiān)控,在內(nèi)存不足或使用到 Swap 時報警出來,及時處理。

 

Redis變慢了排查步驟

1.獲取 Redis 實例在當前環(huán)境下的基線性能。

2.是否用了慢查詢命令?如果是的話,就使用其他命令替代慢查詢命令,或者把聚合計算命令放在客戶端做。

3.是否對過期 key 設(shè)置了相同的過期時間?對于批量刪除的 key,可以在每個 key 的過期時間上加一個隨機數(shù),避免同時刪除。

4.是否存在 bigkey?對于 bigkey 的刪除操作,如果你的 Redis 是 4.0 及以上的版本,可以直接利用異步線程機制減少主線程阻塞;如果是 Redis 4.0 以前的版本,可以使用 SCAN 命令迭代刪除;對于 bigkey 的集合查詢和聚合操作,可以使用 SCAN 命令在客戶端完成。

5.Redis AOF 配置級別是什么?業(yè)務(wù)層面是否的確需要這一可靠性級別?如果我們需要高性能,同時也允許數(shù)據(jù)丟失,可以將配置項 no-appendfsync-on-rewrite 設(shè)置為 yes,避免 AOF 重寫和 fsync 競爭磁盤 IO 資源,導致 Redis 延遲增加。當然, 如果既需要高性能又需要高可靠性,最好使用高速固態(tài)盤作為 AOF 日志的寫入盤。

6.Redis 實例的內(nèi)存使用是否過大?發(fā)生 swap 了嗎?如果是的話,就增加機器內(nèi)存,或者是使用 Redis 集群,分攤單機 Redis 的鍵值對數(shù)量和內(nèi)存壓力。同時,要避免出現(xiàn) Redis 和其他內(nèi)存需求大的應(yīng)用共享機器的情況。

7.在 Redis 實例的運行環(huán)境中,是否啟用了透明大頁機制?如果是的話,直接關(guān)閉內(nèi)存大頁機制就行了。

8.是否運行了 Redis 主從集群?如果是的話,把主庫實例的數(shù)據(jù)量大小控制在 2~4GB,以免主從復(fù)制時,從庫因加載大的 RDB 文件而阻塞。

9.是否使用了多核 CPU 或 NUMA 架構(gòu)的機器運行 Redis 實例?使用多核 CPU 時,可以給 Redis 實例綁定物理核;使用 NUMA 架構(gòu)時,注意把 Redis 實例和網(wǎng)絡(luò)中斷處理程序運行在同一個 CPU Socket 上。

分享到:
標簽:Redis
用戶無頭像

網(wǎng)友整理

注冊時間:

網(wǎng)站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨大挑戰(zhàn)2018-06-03

數(shù)獨一種數(shù)學游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數(shù)有氧達人2018-06-03

記錄運動步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定