天天看點

Redis的開發規範和常見問題 --曉翌

目錄

一、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等拖慢了一次查詢,那麼後面的請求就會被拖慢。

Redis的開發規範和常見問題 --曉翌

使用Sentinel判活的trick:

• Ping指令判活:ping指令同樣受到慢查詢影響,如果引擎被卡住,則ping失敗;

• Duplex Failure:sentinel由于慢查詢切備(備變主)再遇到慢查詢,Redis将出現OOS。

2)擴充為叢集版,問題可解?

既然一個Redis程序不行,采用分布式方案擴充成叢集版可以嗎?叢集闆确實能解決一部分問題,常見的請求是可以分散到不同DB上的。

但是,叢集版也還是解決不了單個DB被卡住的問題,因為Redis的key hash規則是按照外面的一層PK來做的,沒有按照裡面的子key或者是field的來做,如果使用者調用了跨分片的指令,如mget,通路到出問題的db,仍會block住,問題還是會存在。

Redis的開發規範和常見問題 --曉翌

3)Protocol問題 – 大量用戶端與引擎Fully-Meshed問題

采用Redis協定(RESP)的問題:

• 擴充性太差:基于Question-Answer模式,由于在Question/Answer裡面沒有對應的Sequence的存在,(如果不做複雜的轉換wrapper層)存儲引擎端沒法match請求和響應,隻能采用Run-To-Completion來挂住連結;

Redis的開發規範和常見問題 --曉翌

• C10K的問題:當引擎挂住太多active連結的時候,性能下降太多。測試結果是當有10k active連接配接時,性能下降30-35%,由于引擎端挂住的連結不能被傳回,使用者大量報錯。

Redis的開發規範和常見問題 --曉翌

4)“Could not get a resource from the pool”

如下圖所示,由于Redis執行Run-To-Completion特性,用戶端隻能采用連接配接池的方案;Redis協定不支援連接配接池收斂,是因為Message沒有ID,是以Request和Response對不起來。連接配接池具體運作方式是每次查詢,都要先從連接配接池拿出一根連接配接來,當服務端傳回結果後,再放回連接配接池。

Redis的開發規範和常見問題 --曉翌

如果使用者傳回的及時,那麼連接配接池一直保有的連接配接數并不高,但是一旦服務端未及時傳回,用戶端又有新的請求,就隻能再checkout一根連接配接。

當Engine層出現慢查詢,就會讓請求傳回的慢,造成的後果是很容易讓使用者把連接配接池用光,當應用機器特别多的情況,按每個client 連接配接池50個max link來算,很容易打到10K連結的限制,導緻engine回調速度慢。

當連接配接池被checkout完,就會爆沒有連接配接的異常:"Could not get a resource from the pool",這是非常常見的錯誤,有惡性循環的邏輯在裡面。比如說服務端傳回的慢,連接配接池的連接配接就會建立的很快,使用者很容易達到1萬條,建立的連接配接越多,性能越差,傳回越慢,服務容易血崩。

Redis的邊界

--紅色區域代表危險

上述羅列的問題,是為了讓我們在開發業務的時候,不要觸碰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,嚴重造成資料傾斜。

Redis的開發規範和常見問題 --曉翌

别的key隻用了10%或20%的記憶體,key301用了約80%,而且大機率是熱點。上圖的使用用法,有可能造成有一個分片記憶體滿了,通路出了問題,但是其他分片卻用的很閑。問題分片的通路比較熱,造成網卡打滿,或者CPU打滿,導緻限流,服務可能就夯住了。

2)Redis LUA JIT

下面的示意圖表示了一次腳本的執行過程,用戶端調用EVAL script之後會産生SCRIPT Load的行為,Lua JIT開始編譯生成位元組碼,這時産生一個SHA字元串,表示 bytecode的緩存。Loading bytecode之後,開始執行腳本,還需要保證在副本上執行成功,最後unload和Cleaning,整個過程結束。

Redis的開發規範和常見問題 --曉翌

示意圖中有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其中部分指令失敗的場景。

Redis的開發規範和常見問題 --曉翌

4)KEYS指令

KEYS 指令,一定會出問題,即使目前沒有,客戶資料量上漲後必然引發慢查,出現後無能為力。這種情況,需要在一開始就提前預防,可以在控制台通過危險指令禁用,禁止掉keys指令,出現時也可以使用一些手段優化。

KEYS指令的模糊比對:

·Redis存儲key是無序的,比對時必然全表掃描, key數目一多必然卡住,是以一定要去優化。

如下圖所例子中所示,修改為hash結構:

·可以從全表掃描變為點查/部分range, 雖然hash結構中field太多也會慢,但比keys性能提升一個到兩個數量級。

這個例子裡面, Product1字首可以提取成為hash的KEY,如果要去product1字首的所有東西,其實可以下發一個HGETALL,這樣的就是優化了。

Redis的開發規範和常見問題 --曉翌

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)通常來說比較小。

Redis的開發規範和常見問題 --曉翌

總記憶體 = 鍊路記憶體 (動态) + 資料記憶體 + 管理記憶體(靜态)

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,可被內建。

Redis的開發規範和常見問題 --曉翌

下圖所示,是緩存分析報告,可以看到每一個DB記憶體分布統計,包括不同類型的資料結構記憶體統計,key對應的元素數分級統計,可以統計到總體上大概有多少個大key;統計 key過期時間分布,可以發現過期時間設定的是否合理。 Top 100 BigKey(按記憶體),可以發現具體有哪些大key,業務上可以參照這個做優化。 Top 100 BigKey字首是做了key pattern統計,如果key是按照業務子產品來制定的字首,可以統計到各個業務上用了多少記憶體,也可以大體上指導業務優化。

Redis的開發規範和常見問題 --曉翌

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.html
Redis的開發規範和常見問題 --曉翌

4)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. 慢查詢。

Redis的開發規範和常見問題 --曉翌

5)Tair/Redis診斷報告

對于全鍊路診斷,我們推出了診斷報告功能,可以對某個時間段發起“一鍵診斷”,這裡主要是後端排查,目前都是“DB”相關,可以看到有哪些異常情況發生。如下圖所示:

核心曲線:核心名額的曲線,可以看哪些時間點,哪些節點有峰值。

慢請求:展示了Top 10節點的Top 10慢指令統計;

性能水位:可以看到哪些名額、哪些節點超過了預設水位,或者是這些節點是不是發生了傾斜,對發現問題有很大的幫助。

診斷:準實時的對過去最近半小時,1小時,或者對過去某一天、某幾天的診斷。目前還沒有完全對外開放,如果有興趣,可以在阿裡雲上提工單,我們會單獨開放通路。

Redis的開發規範和常見問題 --曉翌

6)Tair/Redis慢日志

• 設定合理的Proxy和DB慢日志采集參數

•slowlog-log-slower-than:DB分片上慢日志門檻值,不可設定過低!;

•slowlog-max-len:DB分片slowlog連結清單最大保持長度;

•rt_threshold_ms:Proxy上慢日志門檻值,不可設定過低!。

Redis的開發規範和常見問題 --曉翌

以上建議使用預設的參數,不要設定過小,因為如果這些門檻值設定的過小,那麼 DB在采集慢日志的時候會頻繁記錄,可能造成引擎的性能降低,是以盡量使用預設參數。

慢日志查詢功能分為曆史慢日志和實時慢日志,入口也不相同,差別在于曆史慢日志可擷取近72小時内的慢日志。實時慢日志能抓出目前所有分片slowlog,但是有一個局限性,如果節點發生了HA或者手動清理慢日志,這部分慢日志就沒有了。使用入口如下圖所示:

• 曆史慢日志

• 使用入口:“執行個體管理”--> “日志管理” --> “慢日志”;

• 使用須知:Tair版,或Redis版本>=redis4.0,具體檢視幫助文檔;

• 可擷取近72小時内的慢日志。

• 實時慢日志

• 使用入口:“執行個體管理”--> “CloudDBA” --> “慢請求”;

• 實時擷取,能抓出目前所有分片slowlog。

Redis的開發規範和常見問題 --曉翌

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成本)。