目錄
一、Redis – 從問題說起
二、Redis – 不要觸碰邊界
三、Redis – 阿裡内部開發規約
四、Redis – 常見問題處理
1)Run-to-Completion in a solo thread – Redis最大的問題
Redis最大的問題是背景主要線程是一個單線程,如下圖所示,使用者所有的來自不同client的請求,實際上在每個event到來後,交由後端單線程執行。等每個event處理完成後,才處理下一個;單線程run-to-completion 就是沒有dispatcher,沒有後端的multi-worker。是以如果慢查詢,諸如單機版的keys、lrange、hgetall等拖慢了一次查詢,那麼後面的請求就會被拖慢。

使用Sentinel判活的trick:
• Ping指令判活:ping指令同樣受到慢查詢影響,如果引擎被卡住,則ping失敗;
• Duplex Failure:sentinel由于慢查詢切備(備變主)再遇到慢查詢,Redis将出現OOS。
2)擴充為叢集版,問題可解?
既然一個Redis程序不行,采用分布式方案擴充成叢集版可以嗎?叢集闆确實能解決一部分問題,常見的請求是可以分散到不同DB上的。
但是,叢集版也還是解決不了單個DB被卡住的問題,因為Redis的key hash規則是按照外面的一層PK來做的,沒有按照裡面的子key或者是field的來做,如果使用者調用了跨分片的指令,如mget,通路到出問題的db,仍會block住,問題還是會存在。
3)Protocol問題 – 大量用戶端與引擎Fully-Meshed問題
采用Redis協定(RESP)的問題:
• 擴充性太差:基于Question-Answer模式,由于在Question/Answer裡面沒有對應的Sequence的存在,(如果不做複雜的轉換wrapper層)存儲引擎端沒法match請求和響應,隻能采用Run-To-Completion來挂住連結;
• C10K的問題:當引擎挂住太多active連結的時候,性能下降太多。測試結果是當有10k active連接配接時,性能下降30-35%,由于引擎端挂住的連結不能被傳回,使用者大量報錯。
4)“Could not get a resource from the pool”
如下圖所示,由于Redis執行Run-To-Completion特性,用戶端隻能采用連接配接池的方案;Redis協定不支援連接配接池收斂,是因為Message沒有ID,是以Request和Response對不起來。連接配接池具體運作方式是每次查詢,都要先從連接配接池拿出一根連接配接來,當服務端傳回結果後,再放回連接配接池。
如果使用者傳回的及時,那麼連接配接池一直保有的連接配接數并不高,但是一旦服務端未及時傳回,用戶端又有新的請求,就隻能再checkout一根連接配接。
當Engine層出現慢查詢,就會讓請求傳回的慢,造成的後果是很容易讓使用者把連接配接池用光,當應用機器特别多的情況,按每個client 連接配接池50個max link來算,很容易打到10K連結的限制,導緻engine回調速度慢。
當連接配接池被checkout完,就會爆沒有連接配接的異常:"Could not get a resource from the pool",這是非常常見的錯誤,有惡性循環的邏輯在裡面。比如說服務端傳回的慢,連接配接池的連接配接就會建立的很快,使用者很容易達到1萬條,建立的連接配接越多,性能越差,傳回越慢,服務容易血崩。
Redis的邊界
--紅色區域代表危險
上述羅列的問題,是為了讓我們在開發業務的時候,不要觸碰Redis的邊界。下面從計算、存儲、網絡三個次元出發,總結了這張圖:
計算方面:Wildcard、Lua并發、1對N PUBSUB、全局配置/熱點,會大量消耗計算資源(高計算消耗);
存儲方面:Streaming慢消費、Bigkey等,造成高存儲消耗。
網絡方面:Keys等掃全表、Huge Batch mget/mset、大Value、Bigkey Range (如hgetall,smembers),造成高網絡消耗。
Redis的邊界總結:
• 高并發不等于高吞吐
• 大 Value 的問題:高速存儲并不會有特别大的高吞吐收益,相反會很危險;
• 資料傾斜和算力傾斜
• bigKey 的問題:break掉存儲的配置設定律;
• 熱點的問題,本質上是cpu上的配置設定律不滿足;
• 大 Range 的問題:對NoSQL的慢查詢和導緻的危害沒有足夠的重視。
• 存儲邊界
• Lua使用不當造成的成本直線上升;
• 資料傾斜帶來的成本飙升,無法有效利用;
• 對于 Latency 的了解問題(RT高)
• 存儲引擎的 Latency都是P99 Latency,如:99.99%在1ms以内,99.5%在3ms以内,等;
• 偶發性時延高是必然的。這個根因在于存儲引擎内部的複雜性和熵。
Redis的使用建議:
推薦:
• 确定場景,是緩存(cache)還是存儲型;
• Cache的使用原則是:“無它也可,有它更強”;
• 永遠不要強依賴Cache,它會丢,也會被淘汰;
• 優先設計合理的資料結構和邏輯;
• 設計避免bigKey,就避免了80%的問題;
• Keyspace能分開,就多申請幾個Redis執行個體;
• pubsub不适合做消息分發;
• 盡量避免用lua做事務。
不建議:
• 我的服務對RT很敏感。 >> 低RT能讓我的服務運作的更好;
• 我把存儲都公用在一個redis裡。 >> 區分cache和記憶體資料庫用法,區分應用;
• 我有一個大排行榜/大集合/大連結清單/消息隊列;我覺得服務能力足夠了。 >> 盡量拆散,服務能力不夠可通過分布式叢集版可以打散;
• 我有一個特别大的Value,存在redis裡,通路能好些。>> redis吞吐量有瓶頸。
1)BigKey – 洪水猛獸
BigKey,我們稱之為洪水猛獸,據初步統計,80%問題由bigKey導緻。如下圖所示:叢集中有4個分片,每個分片大約有102個key,實際上是均勻分布。圖中第三個key叫 key301,hash301,中間有一個放了200w 的hash,但因為根據hash301打散的這個key是個 bigkey,嚴重造成資料傾斜。
别的key隻用了10%或20%的記憶體,key301用了約80%,而且大機率是熱點。上圖的使用用法,有可能造成有一個分片記憶體滿了,通路出了問題,但是其他分片卻用的很閑。問題分片的通路比較熱,造成網卡打滿,或者CPU打滿,導緻限流,服務可能就夯住了。
2)Redis LUA JIT
下面的示意圖表示了一次腳本的執行過程,用戶端調用EVAL script之後會産生SCRIPT Load的行為,Lua JIT開始編譯生成位元組碼,這時産生一個SHA字元串,表示 bytecode的緩存。Loading bytecode之後,開始執行腳本,還需要保證在副本上執行成功,最後unload和Cleaning,整個過程結束。
示意圖中有3個火形圖示,表示耗費CPU的程度,腳本的compile-load-run-unload非常耗費CPU。整個lua相當于把複雜事務推送到Redis中執行,如果稍有不慎CPU會爆,引擎算力耗光後挂住redis。
對上述的情況,Redis做了一些優化,比如“Script + EVALSHA”,可以先把腳本在redis中預編譯和加載(不會unload和clean),使用EVALSHA執行,會比純EVAL省CPU,但是Redis重新開機/切換/變配 bytecode cache會失效,需要reload,仍是缺陷方案。建議使用複雜資料結構,或者module來取代lua。
• 對于JIT技術在存儲引擎中而言,“EVAL is evil”,盡量避免使用lua耗費記憶體和計算資源(省事不省心);
• 某些SDK(如Redisson)很多進階實作都内置使用lua,開發者可能莫名走入CPU運算風暴中,須謹慎。
3)Pubsub/Transaction/Pipeline
• Pubsub的典型場景
Pubsub适合悲觀鎖和簡單信号,不适合穩定的更新,因為可能會丢消息。在1對N的消息轉發通道中,服務瓶頸。還有模糊通知方面,算力瓶頸。在channel和client比較多的情況下,造成CPU打滿、服務夯住。
• Transaction
Transaction是一種僞事物,沒有復原條件;叢集版需要所有key使用hashtag保證,代碼比較複雜,hashtag也可能導緻算力和存儲傾斜;Lua中封裝了multi-exec,但更耗費CPU,比如編譯、加載時,經常出現問題。
• Pipeline
Pipeline用的比較多,如下面的示意圖,實際上是把多個請求封裝在一個請求中,合并在一個請求裡發送,服務端一次性傳回,能夠有效減少IO,提高執行效率。需要注意的是,使用者需要聚合小的指令,避免在pipeline裡做大range。注意Pipeline中的批量任務不是原子執行的(從來不是),是以要處理Pipeline其中部分指令失敗的場景。
4)KEYS指令
KEYS 指令,一定會出問題,即使目前沒有,客戶資料量上漲後必然引發慢查,出現後無能為力。這種情況,需要在一開始就提前預防,可以在控制台通過危險指令禁用,禁止掉keys指令,出現時也可以使用一些手段優化。
KEYS指令的模糊比對:
·Redis存儲key是無序的,比對時必然全表掃描, key數目一多必然卡住,是以一定要去優化。
如下圖所例子中所示,修改為hash結構:
·可以從全表掃描變為點查/部分range, 雖然hash結構中field太多也會慢,但比keys性能提升一個到兩個數量級。
這個例子裡面, Product1字首可以提取成為hash的KEY,如果要去product1字首的所有東西,其實可以下發一個HGETALL,這樣的就是優化了。
5)除去KEYS,下面指令依然危險
• hgetall,smembers,lrange,zrange,exhgetall
• 直接與資料結構的subkey(field)多少相關,O(n),攜帶value爆網卡。
• 建議使用scan來替代。
• bitop,bitset
• 設定過遠的bit會直接導緻OOM。
• flushall,flushdb
• 資料丢失。
使用者在操作的時候,需要很小心,因為會清空資料庫。在阿裡雲Redis控制台裡面點清除資料時,需要使用二次校驗,避免随意清除資料。另外還可以單獨清理過期資料,對其他正常通路的資料沒有影響。
• 配置中和ziplist相關的參數
• Redis在存儲相關資料結構時,資料量比較小,底層使用了ziplist結構,達到一定的量級,比如key/field變多了,會轉換資料結構。當結構在ziplist結構體下時,算力開銷變大,部分查詢變O(n)級别,比對變O(m*n),極端情況容易打滿CPU,不過占用的記憶體确實變少了(需要評估帶來的收益是否比對付出的代價?)。
•建議使用者盡量使用預設參數。
規範總結 [Just FYI]
1.選型:使用者需要确定場景是cache還是記憶體資料庫使用
• Cache場景,關閉AOF;記憶體資料庫選擇雙副本
• 如果keyspace能夠分開,就申請不同的執行個體來隔離
2.使用:避免觸發高速存儲的邊界
• set/hash/zset/list/tairhash/bloom/gis等大key(内部叫做godkey)不要超過3000,會記錄sillylog
• 避免使用keys,hgetall,lrange0-1等大range(使用scan替代)
• 避免使用大value(10k以上就算大value,50k會記錄)
3.SDK:使用規範
• 嚴禁設定低讀逾時和緊密重試(建議設定200ms以下read timeout)
• 需要接受P99時延,對逾時和慢做容錯處理
• 盡量使用擴充資料結構,避免使用lua
• 盡量避免pubsub和blocking的API
4.接受主動運維
在阿裡雲上,如果機器當機,或者是機器後面有風險,我們會做主動運維保證服務的穩定性。
1)Tair/Redis記憶體模型
記憶體控制是Redis的精華部分,大部分遇到的問題都是跟記憶體有,Tair/Redis記憶體模型,如下圖所示,總記憶體分為3個部分:鍊路記憶體(動态)、資料記憶體、管理記憶體(靜态)。
·鍊路記憶體(動态):主要包括Input buff、Output buff等,Input buff與Output buff跟每個用戶端的連接配接有關系,正常情況下比較小,但是當Range操作的時候,或者有大key收發比較慢的時候,這兩個區的記憶體會增大,影響資料區,甚至會造成OOM。還包括JIT Overhead、Fake Lua Link,包含了Code cache執行緩存等等。
·資料記憶體:使用者資料區,就是使用者實際存儲的value。
·管理記憶體(靜态):是靜态buff,啟動的時候比較小,比較恒定。這個區域主要管理data的hash開銷,當 key非常多的時候,比如幾千萬、幾個億,會占用非常大的記憶體。還包括Repl-buff、aof-buff(32M~64M)通常來說比較小。
總記憶體 = 鍊路記憶體 (動态) + 資料記憶體 + 管理記憶體(靜态)
OOM場景,大都是動态記憶體管理失效,例如限流的影響(plus timer mem),限流的時候請求出不去,導緻請求堆積後動态記憶體極速飙升,造成OOM;無所畏懼的Lua腳本也有可能造成OOM。
原生的Redis被定義為“緩存”,在動态記憶體上控制比較粗糙。Tair對這部分做了加強,緻力于footprint control,售賣記憶體接近User Dataset。
2)緩存分析 – 記憶體分布統計、bigKey,key pattern
對于記憶體,阿裡雲有現成的功能一鍵分析,使用入口在“執行個體管理”-》“CloudDBA”下面的“緩存分析”,熱Key分析無需主動觸發。資料源支援曆史備份集,現有備份集,可以準實時或者對曆史備份做分析。支援線上所有的社群版和企業版。也支援線上所有的架構,包括标準版、讀寫分離版、叢集版。
• 使用入口
• “執行個體管理” -->“CloudDBA” --> “緩存分析” --> “立即分析”;熱Key分析無需主動觸發。
• 資料源
• 支援已有備份集;
• 支援自動建立備份集。
• 支援版本
• 社群版(2.8~6.0);
• 企業版(Tair)。
• 支援架構
• 标準版;
• 讀寫分離版;
• 叢集版。
下圖所示,是阿裡雲控制台使用截圖,這個功能比較常用,已開放OpenApi,可被內建。
下圖所示,是緩存分析報告,可以看到每一個DB記憶體分布統計,包括不同類型的資料結構記憶體統計,key對應的元素數分級統計,可以統計到總體上大概有多少個大key;統計 key過期時間分布,可以發現過期時間設定的是否合理。 Top 100 BigKey(按記憶體),可以發現具體有哪些大key,業務上可以參照這個做優化。 Top 100 BigKey字首是做了key pattern統計,如果key是按照業務子產品來制定的字首,可以統計到各個業務上用了多少記憶體,也可以大體上指導業務優化。
3)熱Key分析
阿裡雲提供了線上和離線兩種熱Key分析方式:
• 線上實時分析熱key
• 使用入口:“執行個體管理”--> “CloudDBA” --> “緩存分析” -->
“HotKey”;
• 使用須知:Tair版,或Redis版本>=redis4.0;
• 精确統計(非采樣),能抓出目前所有 Per Key QPS > 3000的記錄;
• 參考文檔:
https://help.aliyun.com/document_detail/160585.html。
• 離線分析熱key
• 方法1:緩存分析也可以分析出相對較熱的key,通過工具實作;
• 方法2:最佳實踐,imonitor指令 + redis-faina 分析出熱點Key;
• 方法3:使用審計日志查詢曆史熱Key,參考文檔
https://help.aliyun.com/document_detail/181195.html4)Tair/Redis全鍊路診斷
Tair/Redis全鍊路診斷,從“APP端的SDK”到“網絡”到“VIP”到“Proxy”再到“DB”,每個部分都有可能會出問題。
問題排查包括:前端排查和後端排查。前段排查首先需要确定是一台出問題,還是全部有問題,如果是一台出問題,大機率是用戶端自己的問題,包括:
• ECS
1. Load,記憶體等;
2. PPS限制。
• 用戶端
1. 連結池滿;
2. RT高(跨地域,gc等);
3. 建連結慢(K8s DNS等);
4. 大查詢,發快收慢。
• 網絡
1. 丢包,收斂;
2. 營運商網絡抖動。
後端排查:主要是慢查和CPU排查,包括“VIP”、“ Proxy”、“DB”。 Tair/Redis 80%的問題是RT(latency)相關。
• VIP(SLB/NGLB)
1. 建連結瓶頸(極少);
2. 流量不均衡(少);
3. 流量瓶頸(極少)。
• Proxy
1. 分發慢查;
2. 流量高(擴容proxy);
3. 消息堆積;
4. Backend網絡抖動。
• DB
1. 容量,CPU,流量(見前文);
2. 主機故障,HA速度;
3. 慢查詢。
5)Tair/Redis診斷報告
對于全鍊路診斷,我們推出了診斷報告功能,可以對某個時間段發起“一鍵診斷”,這裡主要是後端排查,目前都是“DB”相關,可以看到有哪些異常情況發生。如下圖所示:
核心曲線:核心名額的曲線,可以看哪些時間點,哪些節點有峰值。
慢請求:展示了Top 10節點的Top 10慢指令統計;
性能水位:可以看到哪些名額、哪些節點超過了預設水位,或者是這些節點是不是發生了傾斜,對發現問題有很大的幫助。
診斷:準實時的對過去最近半小時,1小時,或者對過去某一天、某幾天的診斷。目前還沒有完全對外開放,如果有興趣,可以在阿裡雲上提工單,我們會單獨開放通路。
6)Tair/Redis慢日志
• 設定合理的Proxy和DB慢日志采集參數
•slowlog-log-slower-than:DB分片上慢日志門檻值,不可設定過低!;
•slowlog-max-len:DB分片slowlog連結清單最大保持長度;
•rt_threshold_ms:Proxy上慢日志門檻值,不可設定過低!。
以上建議使用預設的參數,不要設定過小,因為如果這些門檻值設定的過小,那麼 DB在采集慢日志的時候會頻繁記錄,可能造成引擎的性能降低,是以盡量使用預設參數。
慢日志查詢功能分為曆史慢日志和實時慢日志,入口也不相同,差別在于曆史慢日志可擷取近72小時内的慢日志。實時慢日志能抓出目前所有分片slowlog,但是有一個局限性,如果節點發生了HA或者手動清理慢日志,這部分慢日志就沒有了。使用入口如下圖所示:
• 曆史慢日志
• 使用入口:“執行個體管理”--> “日志管理” --> “慢日志”;
• 使用須知:Tair版,或Redis版本>=redis4.0,具體檢視幫助文檔;
• 可擷取近72小時内的慢日志。
• 實時慢日志
• 使用入口:“執行個體管理”--> “CloudDBA” --> “慢請求”;
• 實時擷取,能抓出目前所有分片slowlog。
7)資源的規劃 – 自建 VS 雲Redis
采購目标:一個24G Redis主從版。上雲方案包括ECS自建Redis與雲Redis服務(Redis/Tair)。
ECS自建Redis:
優點:
• 便宜;
• 擁有最高權限,完全自主可控,操控性強。
缺點:
• 不能做到快速彈性的資源建立,業務突發高峰無法快速滿足系統性能要求;
• 需要專職DBA甚至是基礎架構開發人員長期維護與技術演進;
• 管控節點/平台需要使用第三方工具或額外研發,而且要額外購買資源安裝部署;
• Redis原生社群版核心無優化;
• 無專家服務兜底。
規格配置:
• 執行個體規格和數量:
•ecs.r6e.xlarge (4 vCPU 32 GiB,記憶體平衡增強型 r6e);
• ecs.g6a.large(2 vCPU 8 GiB,通用型 g6a);
• 執行個體數量: 2 + 3 (管控系統:2*APP+ 資料庫主從/Sentinel * 3);
• 部署資源分布:主從各24G,同時按照最普遍情況(見下文計算原理)主從各預留8GB作為COW的資源,另外包含三台伺服器作為管控系統的應用伺服器與資料庫伺服器,以及sentinel程序部署。
清單價(元/月):
2033.6(700*2+211.2*3)。
雲Redis服務(Redis/Tair)
• 開箱即用,随時彈性升降配和産品類型轉換;
• 核心優化,如簡化叢集使用并支援跨SLOT多key操作的Proxy,安全加強,賬号權限控制;
• 衆多企業級管控增值功能特性,如:高可用無腦裂、賬号鑒權體系、操作審計、RDB+AOF備份、5秒監控粒度、全量大key分析、指令讀寫統計等;
• 性能強需求可選Tair性能增強型:具備多線程(性能是社群版的3倍)和豐富實用的資料結構,同時擁有秒級資料恢複、全球多活等強大功能;
• 成本與大規格強需求可選Tair持久記憶體型與容量存儲型,多種形态存儲形态針對不同性能容量要求可以進一步降低成本,并提升資料持久化能力(Tair持久記憶體型較記憶體版降成本25%、Tair容量存儲型較記憶體版降成本85%);
• 7*24小時專家服務支援;即使出現重大問題,有阿裡雲專家線上支援,可以快速止血。
• 黑盒子,不開放最高權限。
規格配置:
• 版本類型:社群版;
• 版本号:Redis 5.0;
• 架構類型:标準版;
• 節點類型:雙副本;
• 執行個體規格:24G标準版。
1950(成本稍低于自建,如果負載壓力滿足條件,進一步降低成本可以使用Tair持久記憶體型,可以進一步降低30%的成本,而使用Tair容量存儲型會是ECS自建的1/5成本)。