攀爬蝸牛
2017-10-04 20:35
問題:
位址服務使用redis做資料快取,原本的技術方案是將位址物件轉化成json格式字串進行儲存,使用資料格式為string型別。
但隨著資料的增長,出現了兩個問題:
1、記憶體占用達到4g,並成增長趨勢。 超出原始估算值4g。
2、檢視值分布,發現存在特別大的value值,有些超過200k。
記憶體增長超出預期,需要找到記憶體增長過快的原因; 值過大可能會導致請求過多時將網絡卡佔滿,導致無法服務,需要分析部分值過大的原因
分析和解決過程:
現有的儲存資料主要在業務上包含兩部分, 一部分為使用者id緯度的,會記錄當前使用者下的位址列表資訊(json格式)。一部分為使用者id和位址id緯度的, 用於記錄當前位址id所對應的位址資訊(json格式)。
格式如下圖所示:
初步優化:
原始資料會快取2天的時間,將快取時間縮減到1天, 在業務上也可以滿足正常訪問需求。
快取時間調整後,整體記憶體占用有所下降,但下降幅度不大。 (猜測原因為資料天重合比例比較高)
2.資料儲存前壓縮。減少空間占用。
快取時間的調整,帶來了記憶體的占用降低,但並未降低單個value值佔空間過大的問題,依舊可能會導致網絡卡被打滿。
3.選擇性壓縮,非全部壓縮。
啟動壓縮的時候,會帶來兩個問題, 乙個是壓縮和解壓縮需要增加cpu使用,耗時會相應增加。另外乙個是字串長度偏小的時候,壓縮後占用的空間反而會大於原有的字串占用的空間。
所以,需要在壓縮的時候做下處理,僅壓縮字串較長的資料。
我們測試的情況下, 長度在300位元組以上的壓縮才有效果,小於300位元組的壓縮後反而空間佔比更大。當然這個資料也和資料內容有關係。
在位址服務的場景下,單位址id的資料對應一般都比較小,所以不做壓縮。轉而,壓縮的是乙個使用者下的位址列表。
效果:
啟用壓縮後的前後記憶體佔比情況:
7.27 4924m
8.19 3530m
記憶體減少占用 (4924-3530)/ 4924 =28.3%
大value值的占用情況對比:
壓縮前:
壓縮後:
200-500k的資料訪問少了,轉而50-100k的資料訪問多了,說明大部分大的value值,被壓縮到了50-100k
繼續優化:
新增壓縮之後,發現服務的響應處理時間變長,通過服務管理平台看到的執行時間如下圖:
服務呼叫耗時基本在10ms+, 但是服務執行耗時卻僅為0.5ms左右。說明,框架在消耗任務佇列的時候,有部分的響應速度過慢導致整個佇列的時間變長。
初步猜測原因為:快取key的變更,導致大量的資料在快取中失效,所以需要大量資料從db中讀取,然後放到快取中,這個過程增加了耗時。所以,隨著時間的推移,這種超時情況理論上應該變好。
為了快速印證這個猜測,我們把可能訪問到的資料預熱到了快取中,但是情況依舊沒有變好。
於是,對比優化前和優化後的變化,僅有未壓縮和壓縮的區別,所以,猜測問題可能為壓縮和解壓縮過程耗時較長導致。
我們在耗時超過10ms以上的資料上新增了日誌列印,記錄了當前的uid和value大小情況。
發現了乙個非常大的value值,長度超過了1000k,但是uid卻為0。 並且比較穩定的是, 這個uid=0的查詢一直在穩定的被查詢到。 所以猜測,原因可能是因為這個資料的頻繁訪問帶來的解壓縮和初始化string的過程導致了框架的整體超時情況。
整體的時間在0.5ms左右。
這裡其實引伸出來幾個專案中沒有考慮到的點:
1)、更換key值時,必然會帶來大量的key值同時失效,也就意味著會有大量的db訪問, 這本身就是乙個很危險的過程,容易導致系統乃至db出現不可用的情況,所以,類似這種key值需要變更的請款下,如何保證系統的平穩過度,保證快取不大批量的失效?
我們這次操作沒有帶來很大的問題的乙個原因是我們僅壓縮了列表資料,單個位址id的資料並未做壓縮。並且,單個位址id的訪問量是最大的,所以壓縮列表資料本質上並沒有產生特別多的資料庫訪問,所以服務和db還算穩定,沒有出現宕機的情況,也算是這次的幸運。
2)、對外提供服務時,對資料入參的校驗是很有必要的。非法資料不需要訪問快取,不需要訪問db,可減少快取和db之間的壓力。
像我們這次的訪問耗時過長的情況,原因在資料庫中有uid=0的髒資料,並且資料量特別大。而且,uid=0的資料還會有呼叫方直接呼叫,並且頻率不低,所以導致了系統做了一些無用功:查詢快取,解壓縮,生成字串。 這部分本身是有耗時的,而且資料量越大,耗時越長。
所以在開發中,一定要關注入參,控制入參質量。 服務呼叫方傳入的資料一定是不能保證完全可靠的,需要服務本身保證資料可靠。
另外一種優化思路:
位址服務的場景下 ,查詢一定會有uid這個條件, 所以會存在兩種, 一種是uid的查詢, 一種是uid和位址id的查詢。
那麼如果是這樣, redis中的hash資料結構會比較適合位址服務的儲存。
現在uid會儲存乙份資料, uid+workid也會儲存乙份,所以極端情況下uid和workid的儲存會占用兩份相同的空間,這時候,使用hash資料結構可以只儲存乙份空間即可,最多減少1/2的記憶體占用。
同時,資料量少的時候, redis的hash儲存,是會採用ziplist來做資料壓縮的,那麼儲存的空間占用上會低於現有的1/2。
redis在儲存hash結構的資料的時候,如果滿足一下兩個條件,則會使用ziplist壓縮資料,否則會使用hashtable的方式。
試圖往列表新新增乙個字串值,且這個字串的長度超過 server.list_max_ziplist_value(預設值為 64)。
ziplist 包含的節點超過 server.list_max_ziplist_entries(預設值為 512)。
位址服務滿足第二點,我們要求每個uid下的位址個數最多為200個(ps:之前提到的髒資料0除外)。所以滿足ziplist節點個數的要求,但是不滿足字串長度的大小要求 。 位址資料採用json格式儲存,其中有個位址描述部分,這部分不限定長度,所以整體下來的資料長度不確定是否滿足64位元組這個最大限制,而且,即使調整list_max_ziplist_value的大小,也很難保證cover所有的資料。
所以,在位址服務中,hash的資料結構,並不能保證資料會被壓縮。
另外一點, 在位址服務中需要解決的一點是,從redis中拿資料時,不能過大,否則可能會導致網絡卡占用過高,這時候,hash的資料本質上沒有壓縮,所以當取使用者所有的資料的時候, 可能會產生較大的資料。 所以不能解決網絡卡可能被打滿的問題。
對於位址服務,不太適合用hash來做資料結構儲存進行優化。
但hash的這種資料結構在專案中還是比較常見的,可以借助redis本身對他的一些優化來進行一些資料的處理。
關於這個專案優化的另外乙個思路。
未實踐,僅供參考。
資料分片+redis分片, 分治。
系統還存在什麼問題?
資料雖然被壓縮了,但是仍舊存在某些value值比較大的情況,比如依舊存在100k以上的資料,甚至在不久的將來可能存在壓縮後依舊大於200k的資料,那麼這部分資料仍然會是個隱患。
那該如何解決呢?
有一種不成熟的解決方案,未實踐,僅供參考。
本質上從業務屬性來講,這種value值過大的被訪問的可能性也不大,所以與其將精力放在如何減少value大小上,而不如轉而放在如何控制這部分的資料的訪問,換句話說,限制大value的訪問頻度,減少出現網絡卡被打滿的風險。
同時,上面提到的分片的思路也能解決這種情況。
redis配置優化 記一次線上redis問題排查
在通過redis快取進行了一系列的介面效能優化後,大部分介面返回在1ms 200ms間,這都是redis的功勞,但隨著介面redis快取越來越多,新的問題產生了,從redis取資料竟然用了5s 通過觀察日誌,並不是每次取資料都是5s,大部分情況從redis取資料還是很快的不會超過5ms.1 在檢視 ...
記一次SQL優化
問題發生在關聯主表a 4w資料量 和副表b 4w資料量 關聯欄位都是openid 當時用的是 left join 直接跑sql,卡死 伺服器也是差 優化1 改left join 為join,兩者區別就是left join查詢時已主表為依據,該是幾條就幾條 就算副表沒有關聯的資料 join如果副表沒有...
記一次redis使用keys
使用場景 專案啟動時,索引所有redis快取刪除後重新錄入 分析 redistemplete.keys 優化原因 不能用於生產環境 官方文件描述 原因 解決方案 使用scan命令 優點 實現同樣的功能,不會造成redis阻塞 scan 實現 param pattern 表示式 param consu...