1 Redis常見面試問題
1.1 Redis是單執行緒還是多執行緒
Redis不同版本之間采用的執行緒模型是不一樣的,在Redis4.0版本之前使用的是單執行緒模型,在4.0版本之后增加了多執行緒的支持,
在4.0之前雖然說Redis是單執行緒,也只是說它的網路I/O執行緒以及Set 和 Get操作是由一個執行緒完成的,但是Redis的持久化、集群同步還是使用其他執行緒來完成,
4.0之后添加了多執行緒的支持,主要是體現在大資料的異步洗掉功能上,例如 unlink key、flushdb async、flushall async 等
1.2 使用單執行緒原因
那為什么Redis在4.0之前會選擇使用單執行緒?而且使用單執行緒還那么快?
選擇單執行緒主要是使用簡單,不存在鎖競爭,可以在無鎖的情況下完成所有操作,不存在死鎖和執行緒切換帶來的性能和時間上的開銷,但同時單執行緒也不能完全發揮出多核CPU性能
為什么單執行緒那么快主要有以下幾個原因:
Redis的大部分操作都在記憶體中完成,記憶體中的執行效率本身就很快,并且采用了高效的資料結構,比如哈希表和跳表,
使用單執行緒避免了多執行緒的競爭,省去了多執行緒切換帶來的時間和性能開銷,并且不會出現死鎖,
采用I/O 多路復用機制處理大量客戶端的Socket請求,因為這是基于非阻塞的 I/O 模型,這就讓Redis可以高效地進行網路通信,I/O的讀寫流程也不再阻塞,
1.3 Redis持久化
Redis資料是存盤在記憶體中的,為了保證Redis資料不丟失,那就要把資料從記憶體存盤到磁盤上,以便在服務器重啟后還能夠從磁盤中恢復原有資料,這就是Redis的資料持久化,
Redis資料持久化有三種方式:
AOF日志(Append Only File,檔案追加方式):記錄所有的操作命令,并以文本的形式追加到檔案中,RDB快照(Redis DataBase):將某一個時刻的記憶體資料,以二進制的方式寫入磁盤,- 混合持久化方式:Redis 4.0 新增了混合持久化的方式,集成了
RDB和AOF的優點,
1.3.1 AOF
AOF采用的是寫后日志的方式,Redis先執行命令把資料寫入記憶體,然后再記錄日志到檔案中,AOF日志記錄的是操作命令,不是實際的資料,如果采用AOF方法做故障恢復時需要將全量日志都執行一遍,

AOF采用的是寫后日志的方式,我們平時用的MySQL則采用的是 寫前日志,那 Redis為什么要先執行命令,再把資料寫入日志呢?
這個主要是由于Redis在寫入日志之前,不對命令進行語法檢查,所以只記錄執行成功的命令,避免出現記錄錯誤命令的情況,而且在命令執行后再寫日志不會阻塞當前的寫操作
后寫日志的風險:
- 資料可能會丟失:如果
Redis剛執行完命令,此時發生故障宕機,會導致這條命令存在丟失的風險, - 可能阻塞其他操作:
AOF日志其實也是在主執行緒中執行,所以當Redis把日志檔案寫入磁盤的時候,還是會阻塞后續的操作無法執行,
1.3.2 RDB
RDB采用的是記憶體快照的方式,它記錄的是某一時刻的資料,而不是操作,所以采用RDB方法做故障恢復時只需要直接把RDB檔案讀入記憶體即可,實作快速恢復,
Redis 提供了兩個命令來生成 RDB 快照檔案,分別是 save 和 bgsave,save 命令在主執行緒中執行,會導致阻塞,而 bgsave 命令則會創建一個子行程,用于寫入 RDB 檔案的操作,避免了對主執行緒的阻塞,這也是 Redis RDB 的默認配置,
save是同步的會阻塞客戶端命令,bgsave是在同步期間的時候是可以修改資料,
Redis是怎么解決在bgsave做快照的時候允許資料修改呢
這里主要是利用bgsave的子執行緒實作的,具體操作如下:
如果主執行緒執行讀操作,則主執行緒和 bgsave 子行程互相不影響;
如果主執行緒執行寫操作,則被修改的資料會復制一份副本,然后 bgsave子行程會把該副本資料寫入 RDB 檔案,在這個程序中,主執行緒仍然可以直接修改原來的資料,

需要注意,Redis 對 RDB 的執行頻率非常重要,因為這會影響快照資料的完整性以及 Redis 的穩定性,所以在 Redis 4.0 后,增加了 AOF 和 RDB 混合的資料持久化機制: 把資料以 RDB 的方式寫入檔案,再將后續的操作命令以 AOF 的格式存入檔案,既保證了 Redis 重啟速度,又降低資料丟失風險,
1.4 Redis高可用
Redis實作高可用主要有三種方式:主從復制、哨兵模式,以及 Redis 集群
1.4.1 主從復制
將從前的一臺 Redis 服務器,同步資料到多臺從 Redis 服務器上,即一主多從的模式,這個跟MySQL主從復制的原理一樣,

1.4.2 哨兵模式
使用 Redis 主從復制的時候,會有一個問題,就是當 Redis 的主從服務器出現故障宕機時,需要手動進行恢復,為了解決這個問題,Redis 增加了哨兵模式(因為哨兵模式做到了可以監控主從服務器,并且提供自動容災恢復的功能),

1.4.3 Redis Cluster(集群)
Redis Cluster 是一種分布式去中心化的運行模式,是在 Redis 3.0 版本中推出的 Redis 集群方案,它將資料分布在不同的服務器上,以此來降低系統對單主節點的依賴,從而提高 Redis 服務的讀寫性能,

使用哨兵模式在資料上有副本資料做保證,在可用性上又有哨兵監控,一旦master宕機會選舉salve節點為master節點,那為什么還需要使用集群模式呢?
哨兵模式歸根節點還是主從模式,在主從模式下我們可以通過增加salve節點來擴展讀并發能力,但是沒辦法擴展寫能力和存盤能力,存盤能力只能是master節點能夠承載的上限,所以為了擴展寫能力和存盤能力,我們就需要引入集群模式,
集群中那么多Master節點,redis cluster在存盤的時候如何確定選擇哪個節點呢?
Redis Cluster采用的是類一致性哈希演算法實作節點選擇的
Redis Cluster將自己分成了16384個Slot(槽位),哈希槽類似于資料磁區,每個鍵值對都會根據它的 key,被映射到一個哈希槽中,具體執行程序分為兩大步:
- 根據鍵值對的
key,按照CRC16演算法計算一個16 bit的值, - 再用
16bit值對16384取模,得到0~16383范圍內的模數,每個模數代表一個相應編號的哈希槽,
每個Redis節點負責處理一部分槽位,假入有三個master節點 ABC,每個節點負責的槽位如下:
| 節點 | 處理槽位 |
|---|---|
| A | 0-5000 |
| B | 5001 - 10000 |
| C | 10001 - 16383 |
Redis集群搭建
1.5 Redis記憶體(資料)淘汰策略
在redis中,我們是可以去設定最大使用記憶體大小server.maxmemory的,當redis記憶體資料集大小上升到一定程度的時候,就會施行資料淘汰機制,
Redis提供了幾種資料淘汰策略?該怎么選擇?
volatile-lru:從已經設定過期時間的資料集中,挑選最近最少使用的資料淘汰,volatile-ttl:從已經設定過期時間的資料集中,挑選即將要過期的資料淘汰,volatile-random:從已經設定過期時間的資料集中,隨機挑選資料淘汰,volatile-lfu:從已經設定過期時間的資料集中,會使用LFU演算法選擇設定了過期時間的鍵值對,allkeys-lru:從所有的資料集中,挑選最近最少使用的資料淘汰,allkeys-random:從所有的資料集中,隨機挑選資料淘汰,no-enviction:禁止淘汰資料,如果redis寫滿了將不提供寫請求,直接回傳錯誤
附錄:LRU和LFU是不同的:
LRU是最近最少使用頁面置換演算法(Least Recently Used),也就是首先淘汰最長時間未被使用的頁面!LFU是最近最不常用頁面置換演算法(Least Frequently Used),也就是淘汰一定時期內被訪問次數最少的頁
使用策略規則:
- 如果資料呈現
冪律分布,也就是一部分資料訪問頻率高,一部分資料訪問頻率低,則使用allkeys-lru - 如果資料呈現
平等分布,也就是所有的資料訪問頻率都相同,則使用allkeys-random
1.6 Redis過期鍵洗掉策略
Redis過期鍵洗掉策略:
定時洗掉:在設定鍵的過期時間的同時,創建一個timer,讓定時器在鍵的過期時間到達時,立即執行對鍵的洗掉操作,(主動洗掉)
對記憶體友好,但是對cpu時間不友好,有較多過期鍵的而情況下,洗掉過期鍵會占用相當一部分cpu時間,惰性洗掉:放任過期鍵不管,但是每次從鍵空間中獲取鍵時,都檢查取到的鍵是否過去,如果過期就洗掉,如果沒過期就回傳該鍵,(被動洗掉)
對cpu時間友好,程式只會在取出鍵的時候才會對鍵進行過期檢查,這不會在洗掉其他無關過期鍵上花費任何cpu時間,但是如果一個鍵已經過期,而這個鍵又保留在資料庫中,那么只要這個過期鍵不被洗掉,他所占用的記憶體就不會釋放,對記憶體不友好,定期洗掉:每隔一段時間就對資料庫進行一次檢查,洗掉里面的過期鍵,(主動洗掉)采用對記憶體和cpu時間折中的方法,每隔一段時間就對一些key進行采樣檢查,檢查是否過期,如果過期就進行洗掉
1、采樣一定個數的key,采樣的個數可以進行配置,并將其中過期的key全部洗掉;
2、如果過期key的占比超過可接受的過期key的百分比,則重復洗掉的程序,直到過期key的比例降至可接受的過期key的百分比以下
1.7 Redis的key和value可以存盤的最大值分別是多少
雖然Key的大小上限為512M,但是一般建議key的大小不要超過1KB,這樣既可以節約存盤空間,又有利于Redis進行檢索,
value的最大值也是512M,對于String型別的value值上限為512M,而集合、鏈表、哈希等key型別,單個元素的value上限也為512M
1.8 Redis實作資料的去重
Redis的set:它可以去除重復元素,也可以快速判斷某一個元素是否存在于集合中,如果元素很多(比如上億的計數),占用記憶體很大,Redis的bit:它可以用來實作比set記憶體高度壓縮的計數,它通過一個bit設定為1或者0,表示存盤某個元素是否存在資訊,例如網站唯一訪客計數,可以把user_id作為bit的偏移量offset,如設定為1表示有訪問,使用1 MB的空間就可以存放800多萬用戶的一天訪問計數情況,HyperLogLog:實作超大資料量精確的唯一計數都是比較困難的,HyperLogLog可以僅僅使用 12 k左右的記憶體,實作上億的唯一計數,而且誤差控制在百分之一左右,bloomfilter布隆過濾器:布隆過濾器是一種占用空間很小的資料結構,它由一個很長的二進制向量和一組Hash映射函陣列成,它用于檢索一個元素是否在一個集合中
1.9 Redis序列化
Redis什么時候需要序列化?
序列化:將Java物件轉換成位元組流的程序,反序列化:將位元組流轉換成Java物件的程序,

為什么需要序列化呢?
打個比喻:作為大城市漂泊的碼農,搬家是常態,當我們搬書桌時,桌子太大了就通不過比較小的門,因此我們需要把它拆開再搬過去,這個拆桌子的程序就是序列化,而我們把書桌復原回來(安裝)的程序就是反序列化啦,
比如想把記憶體中的物件狀態保存到一個檔案中或者資料庫中的時候(最常用,如保存到redis);再比喻想用套接字在網路上傳送物件的時候,都需要序列化,
RedisSerializer介面 是 Redis 序列化介面,用于 Redis KEY 和 VALUE 的序列化,有如下序列化方式:
JDK序列化方式 (默認)String序列化方式JSON序列化方式XML序列化方式
2 Redis監控
2.1 Redis監控指標
監控指標
- 性能指標:
Performance - 記憶體指標:
Memory - 基本活動指標:
Basic activity - 持久性指標:
Persistence - 錯誤指標:
Error
2.1.1 性能指標: Performance
| Name | Description |
|---|---|
| latency | Redis回應一個請求的時間 |
| instantaneous_ops_per_sec | 平均每秒處理請求總數 |
| hi rate(calculated)` | 快取命中率(計算出來的 |
2.1.2 記憶體指標: Memory
| Name | Description |
|---|---|
| used_memory | 已使用記憶體 |
| mem_fragmentation_ratio | 記憶體碎片率 |
| evicted_keys | 由于最大記憶體限制被移除的key的數量 |
| blocked_clients | 由于BLPOP,BRPOP,or BRPOPLPUSH而備阻塞的客戶端 |
2.1.3 基本活動指標:Basic activity
| Name | Description |
|---|---|
| connected_clients | 客戶端連接數 |
| conected_laves | slave數量 |
| master_last_io_seconds_ago | 最近一次主從互動之后的秒數 |
| keyspace | 資料庫中的key值總數 |
2.1.4 持久性指標: Persistence
| Name | Description |
|---|---|
| rdb_last_save_time | 最后一次持久化保存磁盤的時間戳 |
| rdb_changes_sice_last_save | 自最后一次持久化以來資料庫的更改數 |
2.1.5 錯誤指標:Error
| Name | Description |
|---|---|
| rejected_connections | 由于達到maxclient限制而被拒絕的連接數 |
| keyspace_misses key | 值查找失敗(沒有命中)次數 |
| master_link_down_since_seconds | 主從斷開的持續時間(以秒為單位) |
2.2 監控方式
Redis監控方式:
- redis-benchmark
- redis-stat
- redis-faina
- redislive
- redis-cli
- monitor
- showlog
get:獲取慢查詢日志
len:獲取慢查詢日志條目數
reset:重置慢查詢日志
相關配置:
slowlog-log-slower-than 1000# 設定慢查詢的時間下線,單位:微秒
slowlog-max-len 100# 設定慢查詢命令對應的日志顯示長度,單位:命令數
2.2.1 info
info(可以一次性獲取所有的資訊,也可以按塊獲取資訊)
server:服務器運行的環境引數clients:客戶端相關資訊memory:服務器運行記憶體統計資料persistence:持久化資訊stats:通用統計資料Replication:主從復制相關資訊CPU:CPU使用情況cluster:集群資訊Keypass:鍵值對統計數量資訊
終端info命令使用
./redis-cli info 按塊獲取資訊 | grep 需要過濾的引數
./redis-cli info stats | grep ops
互動式info命令使用
./redis-cli > info server
2.2.2 性能監控:
redis-cli info | grep ops # 每秒運算元

2.2.3 記憶體監控
記憶體監控
./redis-cli info | grep used | grep human
used_memory_human:2.99M#記憶體分配器從作業系統分配的記憶體總量
used_memory_rss_human:8.04M#作業系統看到的記憶體占用,top命令看到的記憶體
used_memory_peak_human:7.77M# redis記憶體消耗的峰值
used_memory_lua_human:37.00K# lua腳本引擎占用的記憶體大小
由于BLPOP,BRPOP,or BRPOPLPUSH而備阻塞的客戶端,使用如下命令:
./redis-cli info | grep
blocked_clientsblocked_clients:0
由于最大記憶體限制被移除的key的數量
./redis-cli info | grep
evicted_keysevicted_keys:0 #
記憶體碎片率
./redis-cli info | grep
mem_fragmentation_ratiomem_fragmentation_ratio:2.74
已使用記憶體
./redis-cli info | grep
used_memory:used_memory:3133624
2.2.4 基本活動指標
redis連接了多少客戶端 通過觀察其數量可以確認是否存在意料之外的連接,如果發現數量不對勁,就可以使用lcient list指令列出所有的客戶端鏈接地址來確定源頭,
[root@CombCloud-2020110836 src]# ./redis-cli info | grep connected_clientsconnected_clients:1
[root@CombCloud-2020110836 src]# ./redis-cli info | grep connectedconnected_clients:1 # 客戶端連接數量connected_slaves:1 # slave連接數量
2.2.5 持久性指標
持久性指標
[root@CombCloud-2020110836 src]# ./redis-cli info | grep rdb_last_save_timerdb_last_save_time:1591876204 # 最后一次持久化保存磁盤的時間戳
[root@CombCloud-2020110836 src]# ./redis-cli info | grep rdb_changes_since_last_saverdb_changes_since_last_save:0 # 自最后一次持久化以來資料庫的更改數
2.2.6 錯誤指標
由于超出最大連接數限制而被拒絕的客戶端連接次數,如果這個數字很大,則意味著服務器的最大連接數設定得過低,需要調整maxclients
[root@CombCloud-2020110836 src]# ./redis-cli info | grep
connected_clientsconnected_clients:1
key值查找失敗(沒有命中)次數,出現多次可能是被hei ke gongjji
[root@CombCloud-2020110836 src]# ./redis-cli info | grep
keyspacekeyspace_misses:0
主從斷開的持續時間(以秒為單位)
[root@CombCloud-2020110836 src]# ./redis-cli info | grep
rdb_changes_since_last_saverdb_changes_since_last_save:0
復制積壓緩沖區如果設定得太小,會導致里面的指令被覆寫掉找不到偏移量,從而觸發全量同步
[root@CombCloud-2020110836 src]# ./redis-cli info | grep
backlog_sizerepl_backlog_size:1048576
通過查看sync_partial_err變數的次數來決定是否需要擴大積壓緩沖區,它表示主從半同步復制失敗的次數
[root@CombCloud-2020110836 src]# ./redis-cli info | grep sync_partial_errsync_partial_err:1
2.3 redis性能測驗命令
./redis-benchmark -c 100 -n 5000
說明:100個連接,5000次請求對應的性能

2.4 Redis回應慢
一旦 Redis 請求延遲增加,可能就會導致業務系統雪崩
2.4.1 延遲基線測量
redis-cli 命令提供了--intrinsic-latency 選項,用來監測和統計測驗期間內的最大延遲(以毫秒為單位),這個延遲可以作為 Redis 的基線性能,
redis-cli --latency -h host -p port
比如執行如下指令:
redis-cli --intrinsic-latency 100
Max latency so far: 4 microseconds.
Max latency so far: 18 microseconds.
Max latency so far: 41 microseconds.
Max latency so far: 57 microseconds.
Max latency so far: 78 microseconds.
Max latency so far: 170 microseconds.
Max latency so far: 342 microseconds.
Max latency so far: 3079 microseconds.
45026981 total runs (avg latency: 2.2209 microseconds / 2220.89 nanoseconds per run).
Worst run took 1386x longer than the average latency.
注意:引數100是測驗將執行的秒數,我們運行測驗的時間越長,我們就越有可能發現延遲峰值,
通常運行 100 秒通常是合適的,足以發現延遲問題了,當然我們可以選擇不同時間運行幾次,避免誤差,
此次運行的最大延遲是 3079 微秒,所以基線性能是 3079 (3 毫秒)微秒,
需要注意的是,我們要在 Redis 的服務端運行,而不是客戶端,這樣,可以避免網路對基線性能的影響,
可以通過 -h host -p port 來連接服務端,如果想監測網路對 Redis 的性能影響,可以使用 Iperf 測量客戶端到服務端的網路延遲,
如果網路延遲幾百毫秒,說明網路可能有其他大流量的程式在運行導致網路擁塞,需要找運維協調網路的流量分配,
2.4.2 慢指令監控
如何判斷是否是慢指令呢?
看操作復雜度是否是O(N),官方檔案對每個命令的復雜度都有介紹,盡可能使用O(1) 和 O(log N)命令,
涉及到集合操作的復雜度一般為O(N),比如集合全量查詢HGETALL、SMEMBERS,以及集合的聚合操作:SORT、LREM、 SUNION等,
那么有監控資料可以觀測呢?代碼不是我寫的,不知道有沒有人用了慢指令,
有兩種方式可以排查到:
- 使用
Redis慢日志功能查出慢命令; latency-monitor(延遲監控)工具,
此外,可以使用自己(top、htop、prstat 等)快速檢查 Redis 主行程的 CPU消耗,如果 CPU 使用率很高而流量不高,通常表明使用了慢速命令,
2.4.2.1 慢日志功能
Redis 中的 slowlog 命令可以讓我們快速定位到那些超出指定執行時間的慢命令,默認情況下命令若是執行時間超過 10ms 就會被記錄到日志,
slowlog 只會記錄其命令執行的時間,不包含 io 往返操作,也不記錄單由網路延遲引起的回應慢,
我們可以根據基線性能來自定義慢命令的標準(配置成基線性能最大延遲的 2 倍),調整觸發記錄慢命令的閾值,
可以在 redis-cli中輸入以下命令配置記錄 6 毫秒以上的指令:
redis-cli CONFIG SET slowlog-log-slower-than 6000
也可以在 Redis.config 組態檔中設定,以微秒為單位,
想要查看所有執行時間比較慢的命令,可以通過使用 Redis-cli 工具,輸入 slowlog get 命令查看,回傳結果的第三個欄位以微秒位單位顯示命令的執行時間,
假如只需要查看最后 2 個慢命令,輸入 slowlog get 2 即可,
示例:獲取最近2個慢查詢命令
127.0.0.1:6381> SLOWLOG get 2
1) 1) (integer) 6
2) (integer) 1458734263
3) (integer) 74372
4) 1) "hgetall"
2) "max.dsp.blacklist"
2) 1) (integer) 5
2) (integer) 1458734258
3) (integer) 5411075
4) 1) "keys"
2) "max.dsp.blacklist"
以第一個 HGET 命令為例分析,每個 slowlog 物體共 4 個欄位:
欄位 1:1 個整數,表示這個slowlog出現的序號,server啟動后遞增,當前為 6,欄位 2:表示查詢執行時的Unix時間戳,欄位 3:表示查詢執行微秒數,當前是 74372 微秒,約 74ms,欄位 4: 表示查詢的命令和引數,如果引數很多或很大,只會顯示部分引數個數,當前命令是hgetall max.dsp.blacklist,
2.4.2.2 Latency Monitoring
Redis 在 2.8.13 版本引入了 Latency Monitoring 功能,用于以秒為粒度監控各種事件的發生頻率,
啟用延遲監視器的第一步是設定延遲閾值(單位毫秒),只有超過該閾值的時間才會被記錄,比如我們根據基線性能(3ms)的 3 倍設定閾值為 9 ms,
可以用 redis-cli 設定也可以在 Redis.config 中設定;
CONFIG SET latency-monitor-threshold 9
工具記錄的相關事件的詳情可查看官方檔案:https://redis.io/topics/latency-monitor
如獲取最近的 latency
127.0.0.1:6379> debug sleep 2
OK
(2.00s)
127.0.0.1:6379> latency latest
1) 1) "command"
2) (integer) 1645330616
3) (integer) 2003
4) (integer) 2003
以上欄位說明:
- 事件的名稱;
- 事件發生的最新延遲的
Unix時間戳; - 毫秒為單位的時間延遲;
- 該事件的最大延遲,
2.4.3 如何解決 Redis 變慢
Redis 的資料讀寫由單執行緒執行,如果主執行緒執行的操作時間太長,就會導致主執行緒阻塞,
那么分析下都有哪些操作會阻塞主執行緒,我們又該如何解決?
2.4.3.1 網路通信導致的延遲
客戶端使用 TCP/IP 連接或 Unix 域連接連接到 Redis,1 Gbit/s 網路的典型延遲約為 200 us,
redis 客戶端執行一條命令分 4 個程序:
發送命令-〉 命令排隊 -〉 命令執行-〉 回傳結果
這個程序稱為 Round trip time(簡稱 RTT, 往返時間),mget mset 有效節約了 RTT,但大部分命令(如 hgetall,并沒有 mhgetall)不支持批量操作,需要消耗 N次 RTT ,這個時候需要 pipeline來解決這個問題,
Redis pipeline 將多個命令連接在一起來減少網路回應往返次數,

2.4.3.2 慢指令導致的延遲
根據上文的慢指令監控查詢檔案,查詢到慢查詢指令,可以通過以下兩種方式解決:
- 在
Cluster集群中,將聚合運算等O(N)操作運行在slave上,或者在客戶端完成, - 使用高效的命令代替,使用增量迭代的方式,避免一次查詢大量資料,具體請查看
SCAN、SSCAN、HSCAN和ZSCAN命令,
除此之外,生產中禁用KEYS 命令,它只適用于除錯,因為它會遍歷所有的鍵值對,所以操作延時高,
2.4.3.3 Fork生成 RDB導致的延遲
生成 RDB 快照,Redis 必須 fork 后臺行程,fork 操作(在主執行緒中運行)本身會導致延遲
Redis 使用作業系統的多行程寫時復制技術 COW(Copy On Write)來實作快照持久化,減少記憶體占用,

但 fork 會涉及到復制大量鏈接物件,一個24 GB 的大型 Redis 實體需要 24 GB / 4 kB * 8 = 48 MB 的頁表,
執行 bgsave 時,這將涉及分配和復制 48 MB 記憶體,
此外,從庫加載 RDB 期間無法提供讀寫服務,所以主庫的資料量大小控制在 2~4G 左右,讓從庫快速的加載完成,
2.4.3.4 記憶體大頁(transparent huge pages)
常規的記憶體頁是按照 4 KB 來分配,Linux 內核從 2.6.38 開始支持記憶體大頁機制,該機制支持 2MB 大小的記憶體頁分配,
Redis 使用了 fork 生成 RDB 做持久化提供了資料可靠性保證,
當生成 RDB 快照的程序中,Redis 采用 寫時復制 技術使得主執行緒依然可以接收客戶端的寫請求,
也就是當資料被修改的時候,Redis 會復制一份這個資料,再進行修改,
采用了記憶體大頁,生成 RDB 期間,即使客戶端修改的資料只有 50B 的資料,Redis 需要復制 2MB 的大頁,當寫的指令比較多的時候就會導致大量的拷貝,導致性能變慢,
使用以下指令禁用 Linux 記憶體大頁即可:
echo never > /sys/kernel/mm/transparent_hugepage/enabled
2.4.3.5 swap:作業系統分頁
當物理記憶體(記憶體條)不夠用的時候,將部分記憶體上的資料交換到 swap 空間上,以便讓系統不會因記憶體不夠用而導致 oom 或者更致命的情況出現,
當某行程向 OS 請求記憶體發現不足時,OS 會把記憶體中暫時不用的資料交換出去,放在 SWAP 磁區中,這個程序稱為 SWAP OUT
當某行程又需要這些資料且 OS 發現還有空閑物理記憶體時,又會把 SWAP 磁區中的資料交換回物理記憶體中,這個程序稱為 SWAP IN
記憶體 swap 是作業系統里將記憶體資料在記憶體和磁盤間來回換入和換出的機制,涉及到磁盤的讀寫,
觸發 swap 的情況有哪些呢?
對于Redis而言,有兩種常見的情況:
Redis使用了比可用記憶體更多的記憶體;- 與
Redis在同一機器運行的其他行程在執行大量的檔案讀寫I/O操作(包括生成大檔案的RDB檔案和AOF后臺執行緒),檔案讀寫占用記憶體,導致Redis獲得的記憶體減少,觸發了swap,
那么如何排查是否因為 swap 導致的性能變慢呢?
Linux提供了很好的工具來排查這個問題,所以當懷疑由于交換導致的延遲時,只需按照以下步驟排查,
2.4.3.5.1 獲取 Redis 實體 pid
$ redis-cli info | grep process_id
process_id:13160
進入此行程的 /proc 檔案系統目錄:
cd /proc/13160
在這里有一個 smaps 的檔案,該檔案描述了 Redis 行程的記憶體布局,運行以下指令,用 grep 查找所有檔案中的 Swap 欄位,
$ cat smaps | egrep '^(Swap|Size)'
Size: 316 kB
Swap: 0 kB
Size: 4 kB
Swap: 0 kB
Size: 8 kB
Swap: 0 kB
Size: 40 kB
Swap: 0 kB
Size: 132 kB
Swap: 0 kB
Size: 720896 kB
Swap: 12 kB
每行 Size 表示 Redis 實體所用的一塊記憶體大小,和 Size 下方的 Swap 對應這塊 Size 大小的記憶體區域有多少資料已經被換出到磁盤上了,
如果 Size == Swap 則說明資料被完全換出了,
可以看到有一個 720896 kB 的記憶體大小有 12 kb 被換出到了磁盤上(僅交換了 12 kB),這就沒什么問題,
Redis 本身會使用很多大小不一的記憶體塊,所以,你可以看到有很多 Size行,有的很小,就是 4KB,而有的很大,例如 720896KB,不同記憶體塊被換出到磁盤上的大小也不一樣,
注意: 如果 Swap 一切都是 0 kb,或者零星的 4k ,那么一切正常,
當出現百 MB,甚至 GB 級別的 swap 大小時,就表明,此時,Redis 實體的記憶體壓力很大,很有可能會變慢,
2.4.3.5.2 解決方案
- 增加機器記憶體;
- 將
Redis放在單獨的機器上運行,避免在同一機器上運行需要大量記憶體的行程,從而滿足Redis的記憶體需求; - 增加
Cluster集群的數量分擔資料量,減少每個實體所需的記憶體,
2.4.3.6 AOF 和磁盤 I/O 導致的延遲
為了保證資料可靠性,Redis 使用 AOF 和 RDB 快照實作快速恢復和持久化,
可以使用 appendfsync 配置將 AOF 配置為以三種不同的方式在磁盤上執行 write 或者 fsync (可以在運行時使用 CONFIG SET命令修改此設定,比如:redis-cli CONFIG SET appendfsync no),
no:Redis不執行fsync,唯一的延遲來自于write呼叫,write只需要把日志記錄寫到內核緩沖區就可以回傳,everysec:Redis每秒執行一次fsync,使用后臺子執行緒異步完成fsync操作,最多丟失 1s 的資料,always:每次寫入操作都會執行fsync,然后用OK代碼回復客戶端(實際上Redis會嘗試將同時執行的許多命令聚集到單個fsync中),沒有資料丟失,在這種模式下,性能通常非常低,強烈建議使用快速磁盤和可以在短時間內執行fsync的檔案系統實作,
我們通常將 Redis 用于快取,資料丟失完全可以從資料獲取,并不需要很高的資料可靠性,建議設定成 no 或者 everysec,
除此之外,避免 AOF 檔案過大, Redis 會進行 AOF 重寫,生成縮小的 AOF 檔案,
可以把配置項 no-appendfsync-on-rewrite設定為 yes,表示在 AOF 重寫時,不進行 fsync 操作,也就是說,Redis 實體把寫命令寫到記憶體后,不呼叫后臺執行緒進行 fsync 操作,就直接回傳了,
2.4.3.7 expires淘汰過期資料
Redis 有兩種方式淘汰過期資料:
惰性洗掉:當接收請求的時候發現key已經過期,才執行洗掉;定時洗掉:每100毫秒洗掉一些過期的key,
定時洗掉的演算法如下:
- 隨機采樣
ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP個數的key,洗掉所有過期的key; - 如果發現還有超過
25%的key已過期,則執行步驟一,
ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP默認設定為 20,每秒執行 10次,洗掉 200 個 key 問題不大,
如果觸發了第二條,就會導致 Redis 一致在洗掉過期資料去釋放記憶體,而洗掉是阻塞的
那么觸發條件是什么
就是大量的
key設定了相同的時間引數,同一秒內,大量key過期,需要重復洗掉多次才能降低到25%以下,
簡而言之:大量同時到期的 key 可能會導致性能波動,
2.4.3.7.1 解決方案
如果一批 key 的確是同時過期,可以在 EXPIREAT 和 EXPIRE 的過期時間引數上,加上一個一定大小范圍內的亂數,這樣,既保證了 key 在一個鄰近時間范圍內被洗掉,又避免了同時過期造成的壓力,
2.4.3.8 bigkey
通常我們會將含有較大資料或含有大量成員、串列數的 Key 稱之為大 Key,下面我們將用幾個實際的例子對大 Key 的特征進行描述:
- 一個
STRING型別的Key,它的值為5MB(資料過大) - 一個
LIST型別的Key,它的串列數量為10000個(串列數量過多) - 一個
ZSET型別的Key,它的成員數量為10000個(成員數量過多) - 一個
HASH格式的Key,它的成員數量雖然只有1000個但這些成員的value總大小為10MB(成員體積過大)
bigkey 帶來問題如下:
Redis記憶體不斷變大引發OOM,或者達到maxmemory設定值引發寫阻塞或重要Key被逐出;Redis Cluster中的某個node記憶體遠超其余node,但因Redis Cluster的資料遷移最小粒度為Key而無法將node上的記憶體均衡化;bigkey的讀請求占用過大帶寬,自身變慢的同時影響到該服務器上的其它服務;- 洗掉一個
bigkey造成主庫較長時間的阻塞并引發同步中斷或主從切換;
2.4.3.8.1 查找bigkey
使用 redis-rdb-tools 工具以定制化方式找出大 Key
2.4.3.8.2 解決方案
-
對大
key拆分
如將一個含有數萬成員的HASH Key拆分為多個HASH Key,并確保每個Key的成員數量在合理范圍,在Redis Cluster結構中,大Key的拆分對node間的記憶體平衡能夠起到顯著作用, -
異步清理大
key
Redis自 4.0 起提供了UNLINK命令,該命令能夠以非阻塞的方式緩慢逐步的清理傳入的Key,通過UNLINK,可以安全的洗掉大Key甚至特大Key轉載:https://mp.weixin.qq.com/s/qvXm1pU8T_2mCZCjkTR7QA
本文來自博客園,作者:xiao智,轉載請注明原文鏈接:https://www.cnblogs.com/yuwen01/p/16800702.html
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/516380.html
標籤:其他
上一篇:本科畢設選題
