Redis 開發設計規範及案例分析

作者:張家江

來自:樂得技術 (ID:lede_tech)

redis 不是垃圾桶也不是 SUPER MAN,能力和資源都有限,不合理的使用會降低它的健康度,嚴重時甚至會引起 redis 抖動、阻塞等進而導致服務不可用,每一個使用 redis 的開發人員都應當掌握規範的開發和使用方法。本文整理出 redis 開發過程中七個較常出現的使用不合理的場景,並輔以案例進行分析說明。

01

合理使用集合類

案例

某活動需求,每天 10 點對昨天參加某活動的用戶進行推送提醒。開發人員使用 redis 存儲每天參加活動的用戶,通過 ZRANGEBYSCORE 命令獲取目標用戶進行提醒,提醒完後使用 ZREMRANGEBYSCORE 命令從 redis 中清除這批用戶。某一天 ZRANGEBYSCORE、ZREMRANGEBYSCORE 均出現了慢日誌報警,排查發現這一天參加該活動的用戶約有 5 萬。

分析

案例中使用了 redis 的 sortedset 來儲存用戶信息,其中 value 是用戶的賬號、score 是用戶參加活動的時間,由於 ZRANGEBYSCORE 和 ZREMRANGEBYSCORE 命令的時間複雜度是 O(log(N) + M),其中 M 是操作的元素個數,N 是集合元素總數,本例中當用戶數量爲 5 萬時出現慢日誌。可以通過縮小每次查詢的集合數量,可以將一天分成多段,分批次查詢,比如把查 24 小時範圍的用戶改爲查 4 小時範圍的用戶,分別查 6 次處理即可。

Q

如果用戶參加活動的時間很集中,在某一

時間段比如晚 18 點到 22 點查出來

數量還是特別多怎麼辦?

A

可以把粒度分得更細一些比如 1 小時或者 30 分鐘,如果確定用戶參加活動集中在某個時間點,可以考慮使用 ZSCAN 遍歷操作並刪除。另外,對於目標時間範圍有確定的首尾元素時,還可以通過 ZRANK 命令查出元素的位置,通過 ZRANGE 以及 ZREMRANGEBYRANK 來進行查詢和刪除操作,這樣每次操作可以控制操作數量,有效避免慢日誌。

小結

使用 sortedset、set、list、hash 等集合類的 O(N) 操作時要評估當前元素個數的規模以及將來的增長規模,對於短期就可能變爲大集合的 key,要預估 O(N) 操作的元素數量,避免全量操作,可以使用 HSCAN、SSCAN、ZSCAN 進行漸進操作。集合元素數量過大在使用過程中會影響 redis 的實際性能,元素個數建議儘量不要超過 5000,元素數量過大可考慮拆分成多個 key 進行處理。

02

合理設置過期時間

案例 1

某投票功能,用於統計今日環比昨日的增長數量,開發人員使用 redis 存儲每天的投票數,key 設計爲 vote_count_{date},其中 {date} 爲當天的日期,由於沒有設置過期時間,一年以後產生了 360 多個 key,實際在用的 key 始終只有 2 個。

分析

該案例中,每個生成的 key 在 2 天以後都不會再使用了,可將 key 加上過期時間。

案例 2

某統計功能,用戶會不定期的導入一批數據進 redis,每一批數據需要在 30 分鐘後、1 天后、3 天后、7 天后進行計算統計,統計結果發給用戶。開發人員使用 redis 的同一個 sortedset 存儲這些導入的數據,每天定時任務執行計算任務。由於沒有清理,導致大量結束計算任務的廢棄數據殘留 redis。

分析

該案例中,每一批數據都有相應的生命週期,在導入的第 7 天執行完最後一次計算任務生命週期結束,由於集合裏的元素不能單獨設置過期時間,可在代碼邏輯中對最後一次使用這批數據後進行清理操作。

小結

如果 key 沒有設置超時時間,會導致一直佔用內存。對於可以預估使用生命週期的 key 應當設置合理的過期時間或在最後一次操作時進行清理,避免垃圾數據殘留 redis。

03

合理利用批操作命令

案例

某運營需求,需要給用戶生成短鏈,短鏈由短鏈前綴 + 短碼組成,根據短碼找到用戶對應的手機號,開發人員使用 redis hash 結構存儲短碼到手機號的映射。接口每次會導入 5 萬個手機號。

分析

下面是開發人員的三種操作 redis 方案的僞代碼

方案 1:直接使用 redis 的 HSET 逐個設置

for(50000;)
HSET(key, 短碼,手機號)
結果:失敗。redis ops 飆升,同時接口響應超時

方案 2:改用 redis 的 HMSET 一次將所有元素設置到 hash 中

map <短碼,手機號> 50000 個元素
HMSET(key,map)
結果:失敗。出現 redis 慢日誌

方案 3:依然使用 HMSET,只是每次設置 500 個,循環 100 次

map <短碼,手機號> 500 個元素
for(100;)
HMSET(key,map)
結果:成功

對於大量頻繁的 hset 操作可以使用 HMSET 替代減少 redis 操作次數同時提升處理速度,但是要考慮單次請求操作的數量,避免慢日誌。

小結

在 redis 使用過程中,要正視網絡往返時間,合理利用批量操作命令,減少通訊時延和 redis 訪問頻次。redis 爲了減少大量小數據 CMD 操作的網絡通訊時間開銷 RTT (Round Trip Time),支持多種批操作技術:

04

減少不必要的請求

案例

某業務系統,當用戶進入某個頁面時會同時請求多個接口,每個接口都會校驗用戶狀態是否有效,用戶狀態存在 redis 裏並設置有過期時間,對於 key 未過期但是過期時間大於指定閾值的,需要重新設置有效時間,否則需要使用 del 命令刪除掉。但是部分 key 由於過期其實已經不存在了,所以出現部分無效 del 命令。用戶越多,就會有越多的無效命令。

分析

ttl 命令對於 key 不存在的情況會返回 - 2,若 key 不存在則不需要再調用 del 命令,可減少無效請求。

小結

redis 的所有請求對於不存在的 key 都會有輸出返回,合理利用返回值處理,避免不必要的請求,提升業務吞吐量。

05

避免 value 設置過大

案例

某開發人員將一個商品集合信息序列化後用 redis 的字符串類型存儲,使用的時候再反序列化成對象列表使用,大小超過 1MB,在網絡傳輸的時候由於數據比較大會觸發拆包,會降低 redis 的吞吐量。

分析

數量比較多時可以考慮改用 hash 結構存儲,每一個 field 是商品 id,value 是該商品對象,如果數量較大可使用 hscan 獲取。

小結

String 類型儘量控制在 10KB 以內。雖然 redis 對單個 key 可以緩存的對象長度能夠支持的很大,但是實際使用場合一定要合理拆分過大的緩存項,1k 基本是 redis 性能的一個拐點。當緩存項超過 10k、100k、1m 性能下降會特別明顯。關於吞吐量與數據大小的關係可見下面官方網站提供的示意圖。

吞吐量與數據大小的關係

在局域網環境下只要傳輸的包不超過一個 MTU(以太網下大約 1500 bytes),那麼對於 10、100、1000 bytes 不同包大小的處理吞吐能力實際結果差不多。

06

設計規範的 key 名

可讀性

以業務名爲前綴,用冒號分隔,可使用業務名:子業務名:id 的結構命名,子業務下多單詞可再用下劃線分隔

舉例:活動系統 - 人拉人紅包活動 - id,可命名爲 ACTIVITY:INVITE_REDPACKET:001

簡潔性

保證語義的前提下,控制 key 的長度,當 key 較多時,內存佔用也不容忽視

不包含轉義字符

不包含空格、換行、單雙引號以及其他轉義字符

07

留心禁用命令

keys、monitor、flushall、flushdb 應當通過 redis 的 rename 機制禁掉命令,若沒有禁用,開發人員要謹慎使用。其中 flushall、flushdb 會清空 redis 數據;keys 命令可能會引起慢日誌;monitor 命令在開啓的情況下會降低 redis 的吞吐量,根據壓測結果大概會降低 redis50% 的吞吐量,越多客戶端開啓該命令,吞吐量下降會越多。

keys 和 monitor 在一些必要的情況下還是有助於排查線上問題的,建議可在重命名後在必要情況下由 redis 相關負責人員在 redis 備機使用,monitor 命令可藉助 redis-faina 等腳本工具進行輔助分析,能更快排查線上 ops 飆升等問題。

  總      結  

本文整理出的幾點 redis 開發規範主要是涉及 redis 客戶端的使用部分,每個開發人員在使用 redis 開發過程中幾乎都會涉及到上述提到的幾個問題,需要多多留心,提高代碼質量,提升 redis 的健康度。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/3GRyh0tPdXzcwGeMJmqZBA