最近的互聯(lián)網(wǎng)線上事故發(fā)生比較頻繁缩滨,9月19日網(wǎng)上爆料出順豐近期發(fā)生了一起線上刪庫事件,在這里就不介紹了泉瞻。
在這里講述一下最近發(fā)生在我公司的事故脉漏,以及如何避免,并且如何處理優(yōu)化袖牙。 該宕機的直接原因是使用 Redis 的?keys *?命令引起的侧巨,一共造成了某個服務化項目的兩次宕機。
間接原因還有很多鞭达,技術(shù)跟不上業(yè)務的發(fā)展司忱,由每日百萬量到千萬級是一個大的跨進皇忿,公司對于系統(tǒng)優(yōu)化的處理優(yōu)先級不高,技術(shù)開發(fā)人手的短缺坦仍。
第一次宕機
2018年9月13日的某個點鳍烁,公司某服務化項目的 RDS 實例連接飆升,CPU 升到 100%繁扎,拒絕了其他應用的所有請求服務幔荒。
整個過程如下:
監(jiān)控報警,顯示RDS的CPU使用率達到80%以上梳玫,DBA介入爹梁,準備KILL慢SQL
1分鐘內(nèi),沒有發(fā)現(xiàn)明顯阻塞的SQL提澎,CPU持續(xù)上升到99%
5分鐘內(nèi)姚垃,大量應用報警,并且拒絕服務虱朵,RDS的監(jiān)控顯示出現(xiàn)大量慢SQL莉炉,聯(lián)系服務器數(shù)據(jù)庫提供商進行協(xié)助
8分鐘內(nèi),進行數(shù)據(jù)庫主備切換(業(yè)務會受損碴犬,但是也沒辦法絮宁,沒有定位到問題)
9分鐘內(nèi),部分業(yè)務恢復服协,但是一些業(yè)務訂單的回調(diào)消息堆積超過20w绍昂,備庫的CPU使用率也持續(xù)上升
15分鐘內(nèi),備庫CPU使用率超過97%偿荷,業(yè)務再次中斷窘游,進行切回主庫,并進行限流
20分鐘內(nèi)跳纳,關(guān)閉一些次要應用的流量入口
25分鐘內(nèi)忍饰,主庫CPU使用率恢復正常
30分鐘內(nèi),逐步開啟關(guān)閉的限流應用
35分鐘內(nèi)寺庄,所有應用恢復正常
接下來就是與服務器數(shù)據(jù)庫提供商成立應急小組緊急優(yōu)化可能出現(xiàn)的慢SQL艾蓝,雖然說可能解決了一些慢SQL,但此次并沒有定位到具體的問題斗塘,也就為幾天后再次發(fā)生宕機事件埋下了伏筆
事故影響
某服務化項目服務不可用幾十分鐘赢织,造成訂單數(shù)減少幾十萬筆,損失百萬資金馍盟。
原因分析
當時是沒有定位到具體的原因的于置,但是下面的原因也是一部分可能引起宕機的情況。
某服務化項目的業(yè)務增速非痴炅耄快八毯,在高峰期搓侄,數(shù)據(jù)庫QPS突破35000,系統(tǒng)處于高負荷狀態(tài)宪彩。
在高峰期如果同時執(zhí)行幾個全表掃描的SQL休讳,會造成數(shù)據(jù)庫壓力急劇上升,應用超時增多尿孔,前端應用超時俊柔,用戶重試,流量飆升活合,形成了雪崩效應雏婶。
主要原因在與一些老項目的SQL查詢性能較差,并且使用的主庫白指,對數(shù)據(jù)庫影響較大留晚。數(shù)據(jù)庫QPS太高,但是緩存方案因為人手原因一直沒有落地告嘲,慢SQL的問題處理優(yōu)先級應該提升错维。
改進方案
針對每個應用建一個數(shù)據(jù)庫賬號,嚴格按照規(guī)范使用
緩存優(yōu)化方案即時落地橄唬,慢SQL問題優(yōu)先處理赋焕,集中處理目前已經(jīng)發(fā)現(xiàn)的慢SQL(查詢時間超過1S)
升級數(shù)據(jù)庫配置
遷移非核心業(yè)務到新的RDS實例中去
第二次宕機
由于上一次的宕機原因未找到,所以此次的宕機是可以預見的仰楚。
20180919隆判,還是一樣的”配方”,還是原來的”味道”僧界。同一個RDS侨嘀,CPU飆升至100%,接下來就是拒絕服務捂襟,宕機咬腕。當然,有了第一次的經(jīng)驗葬荷,直接主從切換涨共,在幾十秒左右就恢復了所有業(yè)務,但還是嚴重影響了公司的業(yè)務和形象
原因分析
恢復業(yè)務后闯狱,公司緊急召開了緊急事故研究會議煞赢,當然抛计,我的級別是參與不了的哄孤。公司的高管,高層技術(shù)架構(gòu)吹截、DBA瘦陈、各個項目的主負責人一起進行了會議凝危。
在此次會議中,經(jīng)過查看各個項目的日志晨逝,后臺的監(jiān)控數(shù)據(jù)蛾默,發(fā)現(xiàn)在那臺RDS數(shù)據(jù)庫CPU飆升時,有一臺Redis數(shù)據(jù)庫內(nèi)存將近100%捉貌,然后急劇下降支鸡。聯(lián)系第一次的宕機情況,也是類似的趁窃。
接下來就是聯(lián)系服務器數(shù)據(jù)庫提供商牧挣,將那臺Redis最近一周的命令全部調(diào)用出來,最后發(fā)現(xiàn)醒陆,在那個時間點運行了一條keys *...*命令瀑构。公司的一個工程師執(zhí)行keys模糊的匹配命令是為了清理沒用的鍵,但是沒有考慮到keys *進行模糊匹配引發(fā)Redis鎖刨摩,造成Redis鎖住寺晌,CPU飆升,引起了所有調(diào)用鏈路的超時并且卡住澡刹,等Redis鎖的那幾秒結(jié)束呻征,所有的請求流量全部請求到RDS數(shù)據(jù)庫中,使數(shù)據(jù)庫產(chǎn)生了雪崩像屋,使數(shù)據(jù)庫宕機怕犁。
改進方案
所有線上操作,全部要經(jīng)過運維通過后方可執(zhí)行己莺,運維部門逐步快速收回各項權(quán)限
新增Redis實例奏甫,進行分離
如果有使用類似keys正則命令需求,使用scan命令代替
總結(jié)
該事件中出現(xiàn)的兩次事故凌受,完全是由于人為操作引起的阵子,如果那位工程師,看過Redis的開發(fā)規(guī)范胜蛉,會發(fā)現(xiàn)是建議禁用keys命令的挠进。另外,有線上的命令操作誊册,一定要經(jīng)過運維評估后方可進行操作领突,估計那個工程師是老員工吧,有權(quán)限案怯,然后直接就進行操作了君旦。
另外,公司的業(yè)務發(fā)展確實很快,技術(shù)跟不上金砍,這是非常非常危險的局蚀,極大的增加了宕機的概率。
在業(yè)務量不大的情況下恕稠,那位工程師的操作是完全沒什么問題的琅绅,畢竟并發(fā)也不大,但是現(xiàn)在鹅巍,隨著公司的發(fā)展千扶,業(yè)務量的成倍成倍增加,技術(shù)的擴展卻沒有隨著增長那么快骆捧。
公司的技術(shù)人手不足也是一方面县貌,絕大多數(shù)人都是邊維護老項目邊做新功能,但是對于項目的重構(gòu)優(yōu)化凑懂,人手卻少了很多煤痕,項目優(yōu)化的優(yōu)先級不高,這也是很大的一個原因接谨,極有可能出現(xiàn)類似的情況摆碉,新服務化構(gòu)建迫在眉睫。
最后的最后脓豪,線上操作的任何一條命令巷帝,再小心也不為過。
因為由于你的一個符號而引起的事故可能是你所承擔不起的
Redis開發(fā)建議
最后附上Redis的一些開發(fā)規(guī)范和建議:
1.冷熱數(shù)據(jù)分離扫夜,不要將所有數(shù)據(jù)全部都放到Redis中
雖然Redis支持持久化楞泼,但是Redis的數(shù)據(jù)存儲全部都是在內(nèi)存中的,成本昂貴笤闯。建議根據(jù)業(yè)務只將高頻熱數(shù)據(jù)存儲到Redis中【QPS大于5000】堕阔,對于低頻冷數(shù)據(jù)可以使用MySQL/ElasticSearch/MongoDB等基于磁盤的存儲方式,不僅節(jié)省內(nèi)存成本颗味,而且數(shù)據(jù)量小在操作時速度更快超陆、效率更高!
2.不同的業(yè)務數(shù)據(jù)要分開存儲
不要將不相關(guān)的業(yè)務數(shù)據(jù)都放到一個Redis實例中浦马,建議新業(yè)務申請新的單獨實例时呀。因為Redis為單線程處理,獨立存儲會減少不同業(yè)務相互操作的影響晶默,提高請求響應速度谨娜;同時也避免單個實例內(nèi)存數(shù)據(jù)量膨脹過大,在出現(xiàn)異常情況時可以更快恢復服務磺陡! 在實際的使用過程中趴梢,redis最大的瓶頸一般是CPU屎债,由于它是單線程作業(yè)所以很容易跑滿一個邏輯CPU,可以使用redis代理或者是分布式方案來提升redis的CPU使用率垢油。
3.存儲的Key一定要設(shè)置超時時間
如果應用將Redis定位為緩存Cache使用,對于存放的Key一定要設(shè)置超時時間圆丹!因為若不設(shè)置滩愁,這些Key會一直占用內(nèi)存不釋放,造成極大的浪費辫封,而且隨著時間的推移會導致內(nèi)存占用越來越大硝枉,直到達到服務器內(nèi)存上限!另外Key的超時長短要根據(jù)業(yè)務綜合評估倦微,而不是越長越好妻味!
4.對于必須要存儲的大文本數(shù)據(jù)一定要壓縮后存儲
對于大文本【+超過500字節(jié)】寫入到Redis時,一定要壓縮后存儲欣福!大文本數(shù)據(jù)存入Redis责球,除了帶來極大的內(nèi)存占用外,在訪問量高時拓劝,很容易就會將網(wǎng)卡流量占滿雏逾,進而造成整個服務器上的所有服務不可用,并引發(fā)雪崩效應郑临,造成各個系統(tǒng)癱瘓栖博!
5.線上Redis禁止使用Keys正則匹配操作
Redis是單線程處理,在線上KEY數(shù)量較多時厢洞,操作效率極低【時間復雜度為O(N)】仇让,該命令一旦執(zhí)行會嚴重阻塞線上其它命令的正常請求,而且在高QPS情況下會直接造成Redis服務崩潰躺翻!如果有類似需求丧叽,請使用scan命令代替!
6.可靠的消息隊列服務
Redis List經(jīng)常被用于消息隊列服務公你。假設(shè)消費者程序在從隊列中取出消息后立刻崩潰蠢正,但由于該消息已經(jīng)被取出且沒有被正常處理,那么可以認為該消息已經(jīng)丟失省店,由此可能會導致業(yè)務數(shù)據(jù)丟失嚣崭,或業(yè)務狀態(tài)不一致等現(xiàn)象發(fā)生。
為了避免這種情況懦傍,Redis提供了RPOPLPUSH命令雹舀,消費者程序會原子性的從主消息隊列中取出消息并將其插入到備份隊列中,直到消費者程序完成正常的處理邏輯后再將該消息從備份隊列中刪除粗俱。同時還可以提供一個守護進程说榆,當發(fā)現(xiàn)備份隊列中的消息過期時,可以重新將其再放回到主消息隊列中,以便其它的消費者程序繼續(xù)處理签财。
7.謹慎全量操作Hash串慰、Set等集合結(jié)構(gòu)
在使用HASH結(jié)構(gòu)存儲對象屬性時,開始只有有限的十幾個field唱蒸,往往使用HGETALL獲取所有成員邦鲫,效率也很高,但是隨著業(yè)務發(fā)展神汹,會將field擴張到上百個甚至幾百個庆捺,此時還使用HGETALL會出現(xiàn)效率急劇下降、網(wǎng)卡頻繁打滿等問題【時間復雜度O(N)】,此時建議根據(jù)業(yè)務拆分為多個Hash結(jié)構(gòu)屁魏;或者如果大部分都是獲取所有屬性的操作,可以將所有屬性序列化為一個STRING類型存儲滔以!同樣在使用SMEMBERS操作SET結(jié)構(gòu)類型時也是相同的情況!
8.根據(jù)業(yè)務場景合理使用不同的數(shù)據(jù)結(jié)構(gòu)類型
目前Redis支持的數(shù)據(jù)庫結(jié)構(gòu)類型較多:字符串(String)氓拼,哈希(Hash)你画,列表(List),集合(Set)桃漾,有序集合(Sorted Set), Bitmap, HyperLogLog和地理空間索引(geospatial)等,需要根據(jù)業(yè)務場景選擇合適的類型撬即。
常見的如:String可以用作普通的K-V、計數(shù)類呈队;Hash可以用作對象如商品剥槐、經(jīng)紀人等,包含較多屬性的信息宪摧;List可以用作消息隊列粒竖、粉絲/關(guān)注列表等;Set可以用于推薦几于;Sorted Set可以用于排行榜等蕊苗!
9.命名規(guī)范
雖然說Redis支持多個數(shù)據(jù)庫(默認32個,可以配置更多)沿彭,但是除了默認的0號庫以外朽砰,其它的都需要通過一個額外請求才能使用。所以用前綴作為命名空間可能會更明智一點喉刘。
另外瞧柔,在使用前綴作為命名空間區(qū)隔不同key的時候,最好在程序中使用全局配置來實現(xiàn)睦裳,直接在代碼里寫前綴的做法要嚴格避免造锅,這樣可維護性實在太差了。
如:系統(tǒng)名:業(yè)務名:業(yè)務數(shù)據(jù):其他
但是注意廉邑,key的名稱不要過長哥蔚,盡量清晰明了倒谷,容易理解,需要自己衡量
10.線上禁止使用monitor命令
禁止生產(chǎn)環(huán)境使用monitor命令糙箍,monitor命令在高并發(fā)條件下渤愁,會存在內(nèi)存暴增和影響Redis性能的隱患
11.禁止大string
核心集群禁用1mb的string大key(雖然redis支持512MB大小的string),如果1mb的key每秒重復寫入10次深夯,就會導致寫入網(wǎng)絡(luò)IO達10MB;
12.redis容量
單實例的內(nèi)存大小不建議過大抖格,建議在10~20GB以內(nèi)。
redis實例包含的鍵個數(shù)建議控制在1kw內(nèi)塌西,單實例鍵個數(shù)過大,可能導致過期鍵的回收不及時筝尾。
13 可靠性
需要定時監(jiān)控redis的健康情況:使用各種redis健康監(jiān)控工具捡需,實在不行可以定時返回redis 的 info信息。
客戶端連接盡量使用連接池(長鏈接和自動重連)
加群:835638062 點擊鏈接加入群聊【Java高級架構(gòu)】:https://jq.qq.com/?_wv=1027&k=5S3kL3v即可獲取Java工程化筹淫、高性能及分布式站辉、高性能、高架構(gòu)损姜。性能調(diào)優(yōu)饰剥、Spring,MyBatis摧阅,Netty源碼分析和大數(shù)據(jù)等多個知識點高級進階干貨的直播免費學習權(quán)限及領(lǐng)取相關(guān)資料