Redis的KEYS命令引發宕機事件

摘要: 使用 Redis 的開發者必看,吸收教訓啊!前端

Fundebug經受權轉載,版權歸原做者全部。redis

最近的互聯網線上事故發生比較頻繁,2018 年 9 月 19 號順豐發生了一塊兒線上刪庫事件,在這裏就不介紹了。數據庫

在這裏講述一下最近發生在我公司的事故,以及如何避免,而且如何處理優化。緩存

間接緣由還有不少,技術跟不上業務的發展,由每日百萬量到千萬級是一個大的跨進,公司對於系統優化的處理優先級不高,技術開發人手的短缺服務器

第一次宕機

2018 年 9 月 13 號某個點,公司某服務化項目的 RDS 實例鏈接飆升,CPU 升到 100%,拒絕了其餘應用的全部請求服務網絡

整個過程以下:數據結構

  • 監控報警,顯示 RDS 的 CPU 使用率達到 80%以上,DBA 介入,準備 KILL 慢 SQL
  • 1 分鐘內,沒有發現明顯阻塞的 SQL,CPU 持續上升到 99%
  • 5 分鐘內,大量應用報警,而且拒絕服務,RDS 的監控顯示出現大量慢 SQL,聯繫服務器數據庫提供商進行協助
  • 8 分鐘內,進行數據庫主備切換(業務會受損,可是也沒辦法,沒有定位到問題)
  • 9 分鐘內,部分業務恢復,可是一些業務訂單的回調消息堆積超過 20w,備庫的 CPU 使用率也持續上升
  • 15 分鐘內,備庫 CPU 使用率超過 97%,業務再次中斷,進行切回主庫,並進行限流
  • 20 分鐘內,關閉一些次要應用的流量入口
  • 25 分鐘內,主庫 CPU 使用率恢復正常
  • 30 分鐘內,逐步開啓關閉的限流應用
  • 35 分鐘內,全部應用恢復正常
  • 接下來就是與服務器數據庫提供商成立應急小組緊急優化可能出現的慢 SQL,雖說可能解決了一些慢 SQL,但這次並無定位到具體的問題,也就爲幾天後再次發生宕機事件埋下了伏筆

事故影響架構

某服務化項目服務不可用幾十分鐘,形成訂單數減小几十萬筆,損失百萬資金。併發

緣由分析運維

當時是沒有定位到具體的緣由的,可是下面的緣由也是一部分可能引發宕機的狀況。

某服務化項目的業務增速很是快,在高峯期,數據庫 QPS 突破 35000,系統處於高負荷狀態。

在高峯期若是同時執行幾個全表掃描的 SQL,會形成數據庫壓力急劇上升,應用超時增多,前端應用超時,用戶重試,流量飆升,造成了雪崩效應。

主要緣由在與一些老項目的 SQL 查詢性能較差,而且使用的主庫,對數據庫影響較大。數據庫 QPS 過高,可是緩存方案由於人手緣由一直沒有落地,慢 SQL 的問題處理優先級應該提高

改進方案

  • 針對每一個應用建一個數據庫帳號,嚴格按照規範使用
  • 緩存優化方案即時落地,慢 SQL 問題優先處理,集中處理目前已經發現的慢 SQL(查詢時間超過 1S)
  • 升級數據庫配置
  • 遷移非核心業務到新的 RDS 實例中去

第二次宕機

因爲上一次的宕機緣由未找到,因此這次的宕機是能夠預見的。

2018 年 9 月 19 號,仍是同樣的"配方",仍是原來的"味道"。同一個 RDS,CPU 飆升至 100%,接下來就是拒絕服務,宕機。固然,有了第一次的經驗,直接主從切換,在幾十秒左右就恢復了全部業務,但仍是嚴重影響了公司的業務和形象。

緣由分析

恢復業務後,公司緊急召開了緊急事故研究會議,固然,個人級別是參與不了的。公司的高管,高層技術架構、DBA、各個項目的主負責人一塊兒進行了會議。

在這次會議中,通過查看各個項目的日誌,後臺的監控數據,發如今那臺 RDS 數據庫 CPU 飆升時,有一臺 Redis 數據庫內存將近 100%,而後急劇降低。聯繫第一次的宕機狀況,也是相似的。

接下來就是聯繫服務器數據庫提供商,將那臺 Redis 最近一週的命令所有調用出來,最後發現,在那個時間點運行了一條keys *...*命令。公司的一個工程師執行 keys 模糊的匹配命令是爲了清理沒用的鍵,可是沒有考慮到keys *進行模糊匹配引起 Redis 鎖,形成 Redis 鎖住,CPU 飆升,引發了全部調用鏈路的超時而且卡住,等 Redis 鎖的那幾秒結束,全部的請求流量所有請求到 RDS 數據庫中,使數據庫產生了雪崩,使數據庫宕機。

改進方案

  • 全部線上操做,所有要通過運維經過後方可執行,運維部門逐步快速收回各項權限
  • 新增 Redis 實例,進行分離
  • 若是有使用相似 keys 正則命令需求,使用 scan 命令代替

總結

該事件中出現的兩次事故,徹底是因爲人爲操做引發的,若是那位工程師,看過 Redis 的開發規範,會發現是建議禁用 keys 命令的。另外,有線上的命令操做,必定要通過運維評估後方可進行操做,估計那個工程師是老員工吧,有權限,而後直接就進行操做了。

另外,公司的業務發展確實很快,技術跟不上,這是很是很是危險的,極大的增長了宕機的機率。

在業務量不大的狀況下,那位工程師的操做是徹底沒什麼問題的,畢竟併發也不大,可是如今,隨着公司的發展,業務量的成倍成倍增長,技術的擴展卻沒有隨着增加那麼快。

公司的技術人手不足也是一方面,絕大多數人都是邊維護老項目邊作新功能,可是對於項目的重構優化,人手卻少了不少,項目優化的優先級不高,這也是很大的一個緣由,極有可能出現相似的狀況,新服務化構建迫在眉睫。

最後的最後,線上操做的任何一條命令,再當心也不爲過,由於因爲你的一個符號而引發的事故多是你所承擔不起的。

Redis 開發建議

最後附上 Redis 的一些開發規範和建議

1. 冷熱數據分離,不要將全部數據所有都放到 Redis 中

雖然 Redis 支持持久化,可是 Redis 的數據存儲所有都是在內存中的,成本昂貴。建議根據業務只將高頻熱數據存儲到 Redis 中【QPS 大於 5000】,對於低頻冷數據可使用 MySQL/ElasticSearch/MongoDB 等基於磁盤的存儲方式,不只節省內存成本,並且數據量小在操做時速度更快、效率更高!

2. 不一樣的業務數據要分開存儲

不要將不相關的業務數據都放到一個 Redis 實例中,建議新業務申請新的單獨實例。由於 Redis 爲單線程處理,獨立存儲會減小不一樣業務相互操做的影響,提升請求響應速度;同時也避免單個實例內存數據量膨脹過大,在出現異常狀況時能夠更快恢復服務! 在實際的使用過程當中,redis 最大的瓶頸通常是 CPU,因爲它是單線程做業因此很容易跑滿一個邏輯 CPU,可使用 redis 代理或者是分佈式方案來提高 redis 的 CPU 使用率。

3. 存儲的 Key 必定要設置超時時間

若是應用將 Redis 定位爲緩存 Cache 使用,對於存放的 Key 必定要設置超時時間!由於若不設置,這些 Key 會一直佔用內存不釋放,形成極大的浪費,並且隨着時間的推移會致使內存佔用愈來愈大,直到達到服務器內存上限!另外 Key 的超時長短要根據業務綜合評估,而不是越長越好!

4. 對於必需要存儲的大文本數據必定要壓縮後存儲

對於大文本【+超過 500 字節】寫入到 Redis 時,必定要壓縮後存儲!大文本數據存入 Redis,除了帶來極大的內存佔用外,在訪問量高時,很容易就會將網卡流量佔滿,進而形成整個服務器上的全部服務不可用,並引起雪崩效應,形成各個系統癱瘓!

5. 線上 Redis 禁止使用 Keys 正則匹配操做

Redis 是單線程處理,在線上 KEY 數量較多時,操做效率極低【時間複雜度爲 O(N)】,該命令一旦執行會嚴重阻塞線上其它命令的正常請求,並且在高 QPS 狀況下會直接形成 Redis 服務崩潰!若是有相似需求,請使用 scan 命令代替!

6. 可靠的消息隊列服務

Redis List 常常被用於消息隊列服務。假設消費者程序在從隊列中取出消息後馬上崩潰,但因爲該消息已經被取出且沒有被正常處理,那麼能夠認爲該消息已經丟失,由此可能會致使業務數據丟失,或業務狀態不一致等現象發生。

爲了不這種狀況,Redis 提供了 RPOPLPUSH 命令,消費者程序會原子性的從主消息隊列中取出消息並將其插入到備份隊列中,直到消費者程序完成正常的處理邏輯後再將該消息從備份隊列中刪除。同時還能夠提供一個守護進程,當發現備份隊列中的消息過時時,能夠從新將其再放回到主消息隊列中,以便其它的消費者程序繼續處理。

7. 謹慎全量操做 Hash、Set 等集合結構

在使用 HASH 結構存儲對象屬性時,開始只有有限的十幾個 field,每每使用 HGETALL 獲取全部成員,效率也很高,可是隨着業務發展,會將 field 擴張到上百個甚至幾百個,此時還使用 HGETALL 會出現效率急劇降低、網卡頻繁打滿等問題【時間複雜度 O(N)】,此時建議根據業務拆分爲多個 Hash 結構;或者若是大部分都是獲取全部屬性的操做,能夠將全部屬性序列化爲一個 STRING 類型存儲!一樣在使用 SMEMBERS 操做 SET 結構類型時也是相同的狀況!

8. 根據業務場景合理使用不一樣的數據結構類型

目前 Redis 支持的數據庫結構類型較多:字符串(String),哈希(Hash),列表(List),集合(Set),有序集合(Sorted Set), Bitmap, HyperLogLog 和地理空間索引(geospatial)等,須要根據業務場景選擇合適的類型。

常見的如:String 能夠用做普通的 K-V、計數類;Hash 能夠用做對象如商品、經紀人等,包含較多屬性的信息;List 能夠用做消息隊列、粉絲/關注列表等;Set 能夠用於推薦;Sorted Set 能夠用於排行榜等!

9. 命名規範

雖說 Redis 支持多個數據庫(默認 32 個,能夠配置更多),可是除了默認的 0 號庫之外,其它的都須要經過一個額外請求才能使用。因此用前綴做爲命名空間可能會更明智一點。

另外,在使用前綴做爲命名空間區隔不一樣 key 的時候,最好在程序中使用全局配置來實現,直接在代碼裏寫前綴的作法要嚴格避免,這樣可維護性實在太差了。

如:系統名:業務名:業務數據:其餘

可是注意,key 的名稱不要過長,儘可能清晰明瞭,容易理解,須要本身衡量

10. 線上禁止使用 monitor 命令

禁止生產環境使用 monitor 命令,monitor 命令在高併發條件下,會存在內存暴增和影響 Redis 性能的隱患

11. 禁止大 string

核心集羣禁用 1mb 的 string 大 key(雖然 redis 支持 512MB 大小的 string),若是 1mb 的 key 每秒重複寫入 10 次,就會致使寫入網絡 IO 達 10MB;

12. redis 容量

單實例的內存大小不建議過大,建議在 10~20GB 之內。redis 實例包含的鍵個數建議控制在 1kw 內,單實例鍵個數過大,可能致使過時鍵的回收不及時。

13. 可靠性

須要定時監控 redis 的健康狀況:使用各類 redis 健康監控工具,實在不行能夠定時返回 redis 的 info 信息。客戶端鏈接儘可能使用鏈接池(長連接和自動重連)。

相關文章
相關標籤/搜索