《程序人生》害程序員差點被開除的P0事故

你知道的越多,你不知道的越多java


點贊再看,養成習慣git


GitHub github.com/JavaFamily上已經收錄有一線大廠面試點腦圖、我的聯繫方式和技術交流羣,歡迎Star和指教github

前言

這是帥丙真實事件,你們都知道不少公司都是有故障等級這麼一說的,這就是敖丙在公司背的P0級故障,敖丙差點所以被解僱,事情通過十分驚心動魄,個人心臟病都差點復發web

事故等級主要針對生產環境,劃分依據相似於bug等級。面試


P0屬於最高級別事故,好比崩潰,頁面沒法訪問,主流程不通,主功能未實現,或者在影響面上影響很大(即便bug自己不嚴重)。正則表達式


P1事故屬於高級別事故,通常屬於主功能上的分支,支線流程,核心次功能等,後面還有P2,P3等,主要根據企業實際狀況劃分。算法

正文

敖丙以前也負責公司的商品搜索業務,由於業務體量增速太快了,商品表中的商品數據也很快躍入千萬級別,查詢的RT(response time 響應時間)也愈來愈高了,並且產品說須要根據更多維度去查詢商品數據庫

由於以前咱們都是根據商品的名稱去查詢的,可是電商其實都會根據不少個維度去查詢商品。apache

就好比你們去淘寶的查詢的時候就會發現,你搜商品名稱、顏色、標籤等等多個維度均可以找到這個商品,就好比下圖的搜索,我只是搜了【帥丙】你會發現,名字裏面也沒有連續的帥丙兩個字,有帥和丙的出來了數組

你們知道的傳統的關係型數據庫都是用什麼 name like %帥丙% 這樣的方式查詢的,並且查詢出來的結果確定只能是name裏面帶帥丙的對吧。

那你還想搜別的字段好比什麼尺寸、關鍵詞、價格等等,都能搜到帥丙,這至關因而多個維度的了,傳統的關係型數據庫作不到呀。

作技術選型的時候,帥丙第一時間想到了搜索引擎。

當時市面是比較流行的有:Apache LuceneElasticsearchSolr

搜索引擎我後面會講ELK(Elasticsearch、Logstash、Kibana)Canal,我呀真的是太寵大家了,這樣會不會把大家慣壞了。

帥丙我呀,噼裏啪啦一頓操做,最後得出結論:

相對來說,若是考慮靜態搜索,Sorl相對更合適。


若是考慮實時,涉及到分佈式,Elasticsearch相對合適。

那咱們商品仍是要實時的呀,你後臺改了價格啥的,是否是都要實時同步出去,否則不是炸了嘛。

看到這,我想可愛的你和帥丙心中都有了答案:Elasticsearch這是個神同樣的引擎。

我這裏就作一個簡單的介紹就行了,細節的點咱們後面去他的章節講,啥都寫了,敖丙哪裏有這麼多素材寫文章?

ElasticSearch是一個基於Lucene的搜索服務器。


它提供了一個分佈式多用戶能力的全文搜索引擎,基於RESTful web接口。


Elasticsearch是用Java語言開發的,並做爲Apache許可條款下的開放源碼發佈,是一種流行的企業級搜索引擎。


ElasticSearch用於雲計算中,可以達到實時搜索,穩定,可靠,快速,安裝使用方便。官方客戶端在Java、.NET(C#)、PHP、Python、Apache Groovy、Ruby和許多其餘語言中都是可用的。


根據DB-Engines的排名顯示,Elasticsearch是最受歡迎的企業搜索引擎,其次是Apache Solr,也是基於Lucene。

看過敖丙以前文章的朋友都知道,咱們作技術選型以前,要作什麼呀,設計

咱們要去了解這玩意的好處壞處常見的坑出了問題的應急預案等等,還有他的數據同步機制啊,持久化機制啥的,就是高可用嘛。

一樣的我不大篇幅介紹了,之後都會寫的嘛,我就給你們看看我當時作的設計吧。

這個只是最初的demo,詳細的終稿我就不給你們看了,由於有不少公司內部的邏輯。

不過你們仍是能夠看到敖丙真的考慮了不少,仍是那句話,不打沒把握的仗!

設計作好敖丙就卡卡卡的用起來了。

說實話,真香,這玩意真的好用,學習成本也很低,查詢語句分分鐘掌握了,官網文檔把功能介紹得清晰無比。

https://www.elastic.co/cn/

用着用着重頭戲來了,大家都知道敖丙我是作電商活動的,都是什麼很高的流量打進來這樣,仍是如往常同樣上線了一個活動。

這是一個月飛風高的夜晚,絲絲涼風迎面吹來,敖丙清閒的坐在椅子上,手裏拿着破舊的茶杯,喝着外婆炒的苦荊茶,享受着這愜意的時光。

忽然,說時遲那時快,運維打來了緊急電話ES集羣CPU打到了99%要掛了,個人心驀然一痛,內心還在慶幸仍是集羣沒崩。

而後他接着說了一句,很差集羣掛了!

敖丙卒,本篇完….

開玩笑的哈,不過當時敖丙真的要死的心真的都要有了,就在崩掉的1分鐘內,就有用戶反饋搜索未響應,我第一時間想到的就是重啓,因而我一個健步衝出去,開啓電腦,進機器,輸入了重啓命令。

好了,是的好了,還好有驚無險,不過只過了10秒,集羣又99%了,吶呢?

我又只能重啓了,此次沒掛,過了好久好久,直到活動結束,仍是沒掛。

查找問題

可是此次影響到線上,3分鐘的搜索未響應,我想我估計明天是要去財務領工資,提早回家過年了。

還好Leader說沒事,先找到問題,把他修復掉。

大家都知道敖丙天才來的,我第一時間想到的就是看日誌,我登上去看es沒報錯,再看自己的服務,除了超時的錯誤啥都沒有,臥槽,是的當時我腦殼嗡嗡響。

不過我繼續想爲啥是個人搜索掛了,會不會是有人搜了什麼奇怪的東西?

我打開了個人搜索日誌!!!

臥槽這不是吧,哪一個坑爹玩意搜這麼長的一串中文,差很少250個字吧。

可是我一想,搜這麼長也不該該打掛服務啊,會不會是我寫了bug

我臉頰流下一滴汗水💦,我看了看周圍,發現沒人注意到個人緊張,我故做鎮定的把它擦掉。

我仔細一想,別人查詢雖然長,就算查數據庫也沒事啊,爲啥es就報錯了?會不會?

Es有Bug!沒錯確定是Es的鍋。

那爲啥會這樣呢,我直接跟老大這樣解釋也好像不行啊,仍是要被開除的吧!

因而我去看看看代碼,我在關鍵詞使用了通配符,我當時是爲了匹配更多內容才這麼作的,相似數據庫的like,Es的通配符就是: * 帥丙 * 這樣在關鍵詞先後加「*」號去查詢

後面我發現就是通配符的鍋,那柯南丙就說一下爲啥會這樣的問題出現。

許多有RDBMS/SQL背景的開發者,在初次踏入ElasticSearch世界的時候,很容易就想到使用通配符(Wildcard Query)來實現模糊查詢(好比用戶輸入補全),由於這是和SQL裏like操做最類似的查詢方式,用起來感受很是溫馨。

然而帥丙的故障就揭示了,濫用Wildcard query可能帶來災難性的後果

我當時首先復現了問題

復現方法

  1. 建立一個只有一條文檔的索引

POST test_index/type1/?refresh=true

{

"foo": "bar"

}

2.使用wildcard query執行一個首尾帶有通配符*的長字符串查詢

POST /test_index/_search

{

"query": {

"wildcard": {

"foo": {

​ "value": "輕輕的我走了,正如我輕輕的來;我輕輕的招手,道別西天的雲彩。那河畔的金柳,是夕陽中的新娘;波光裏的豔影,在個人心頭盪漾。軟泥上的青荇,油油的在水底招搖;在康河的柔波里,我甘心作一條水草!那榆蔭下的一潭,不是清泉,是天上虹;揉碎在浮藻間,沉澱着彩虹似的夢。尋夢?撐一支長篙,向青草更青處漫溯;滿載一船星輝,在星輝斑斕裏放歌。但我不能放歌,悄悄是別離的笙簫;夏蟲也爲我沉默,沉默是今晚的康橋!悄悄的我走了,正如我悄悄的來;我揮一揮衣袖,不帶走一片雲彩。"

   }

  }

 }

}

  1. 查看結果

{

"took": 3445,

"timed_out": false,

"_shards": {

"total": 5,

"successful": 5,

"failed": 0

},

"hits": {

"total": 0,

"max_score": null,

"hits":

 }

}

即便no hits,耗時倒是驚人的3.4秒 (測試機是macbook pro, i7 CPU),而且執行過程當中,CPU有一個很高的尖峯。

線上的查詢比我這個範例要複雜得多,會同時查幾個字段,實際測試下來,一個查詢可能會執行十幾秒鐘。

再有比較多長字符串查詢的時候,集羣可能就DOS了。

探查深層次根源

爲何對只有一條數據的索引作這個查詢開銷這麼高? 直覺上應該是瞬間返回結果纔對!

回答這個問題前,能夠再作個測試,若是繼續加大查詢字符串的長度,到了必定長度後,ES直接拋異常了,服務ES裏異常給出的cause以下:

Caused by: org.apache.lucene.util.automaton.TooComplexToDeterminizeException: Determinizing automaton with 22082 states and 34182 transitions would result in more than 10000 states. at org.apache.lucene.util.automaton.Operations.determinize(Operations.java:741) ~[lucene-core-6.4.1.jar:6.4.1

解釋:該異常來自org.apache.lucene.util.automaton這個包,異常緣由的字面含義是說「自動機過於複雜而沒法肯定狀態: 因爲狀態和轉換太多,肯定一個自動機須要生成的狀態超過10000個上限"

柯南丙網上查找了大量資料後,終於搞清楚了問題的前因後果。

爲了加速通配符和正則表達式的匹配速度,Lucene4.0開始會將輸入的字符串模式構建成一個DFA (Deterministic Finite Automaton),帶有通配符的pattern構造出來的DFA可能會很複雜,開銷很大

好比a*bc構造出來的DFA就像下面這個圖同樣:

Lucene構造DFA的實現

看了一下Lucene的裏相關的代碼,構建過程大體以下:

  1. org.apache.lucene.search.WildcardQuery裏的toAutomaton方法,遍歷輸入的通配符pattern,將每一個字符變成一個自動機(automaton),而後將每一個字符的自動機連接起來生成一個新的自動機。
public static Automaton toAutomaton(Term wildcardquery) {
        List<Automaton> automata = new ArrayList<>();
        String wildcardText = wildcardquery.text();
        for (int i = 0; i < wildcardText.length();) {
            final int c = wildcardText.codePointAt(i);
            int length = Character.charCount(c);
            switch(c) {
                case WILDCARD_STRING:
                    automata.add(Automata.makeAnyString());
                    break;
                case WILDCARD_CHAR:
                    automata.add(Automata.makeAnyChar());
                    break;
                case WILDCARD_ESCAPE:
                    // add the next codepoint instead, if it exists
                    if (i + length < wildcardText.length()) {
                        final int nextChar = wildcardText.codePointAt(i + length);
                        length += Character.charCount(nextChar);
                        automata.add(Automata.makeChar(nextChar));
                        break;
                    } // else fallthru, lenient parsing with a trailing \
                default:
                    automata.add(Automata.makeChar(c));
            }
            i += length;
        }
        return Operations.concatenate(automata);
    }
複製代碼
  1. 此時生成的狀態機是不肯定狀態機,也就是Non-deterministic Finite Automaton(NFA)。

  2. org.apache.lucene.util.automaton.Operations類裏的determinize方法則會將NFA轉換爲DFA

/**
  \* Determinizes the given automaton.
  \* <p>
  \* Worst case complexity: exponential in number of states.
  \* @param maxDeterminizedStates Maximum number of states created when
  \*  determinizing. Higher numbers allow this operation to consume more
  \*  memory but allow more complex automatons. Use
  \*  DEFAULT_MAX_DETERMINIZED_STATES as a decent default if you don't know
  \*  how many to allow.
  \* @throws TooComplexToDeterminizeException if determinizing a creates an
  \*  automaton with more than maxDeterminizedStates
  */

複製代碼

代碼註釋裏說這個過程的時間複雜度最差狀況下是狀態數量的指數級別!

爲防止產生的狀態過多,消耗過多的內存和CPU,類裏面對最大狀態數量作了限制

 /**
  * Default maximum number of states that {@link Operations#determinize} should create.
  */

 public static final int DEFAULT_MAX_DETERMINIZED_STATES = 10000;
複製代碼

在有首尾通配符,而且字符串很長的狀況下,這個determinize過程會產生大量的state,甚至會超過上限。

至於NFA和DFA的區別是什麼? 如何相互轉換?

網上有不少數學層面的資料和論文,限於帥丙算法方面有限的知識,無精力去深刻探究。

可是一個粗淺的理解是: NFA在輸入一個條件的狀況下,能夠從一個狀態轉移到多種狀態,而DFA只會有一個肯定的狀態能夠轉移,所以DFA在字符串匹配時速度更快。

DFA雖然搜索的時候快,可是構造方面的時間複雜度可能比較高,特別是帶有首部通配符+長字符串的時候。

回想Elasticsearch官方文檔裏對於Wildcard query有特別說明,要避免使用通配符開頭的term

" Note that this query can be slow, as it needs to iterate over many terms. In order to prevent extremely slow wildcard queries, a wildcard term should not start with one of the wildcards * or ?."

結合對上面Wildcard query底層實現的探究,也就不難理解這句話的含義了!

小結: Wildcard query應杜絕使用通配符打頭,實在不得已要這麼作,就必定須要限制用戶輸入的字符串長度。

最好換一種實現方式,經過在index time作文章,選用合適的分詞器,好比nGram tokenizer預處理數據,而後使用更廉價的term query來實現同等的模糊搜索功能。

對於部分輸入即提示的應用場景,能夠考慮優先使用completion suggester, phrase/term suggeter一類性能更好,模糊程度略差的方式查詢,待suggester沒有匹配結果的時候,再fall back到更模糊但性能較差的wildcard, regex, fuzzy一類的查詢。

補充: 有同窗問regex, fuzzy query是否有一樣的問題,答案是有,緣由在於他們底層和wildcard同樣,都是經過將pattern構形成DFA來加速字符串匹配速度的。

回憶:爲啥以前掛了一次重啓恢復了,立刻又掛了?用戶搜了兩次。。。

解決方案

其實解決這種問題很簡單,既然知道關鍵詞長了會有問題,我就作限制嘛,你們能夠去看看搜索引擎某度、某寶啥的,是否是都作了長度限制?

我複製了很長的一段漢字進去百度就是這個結果咯,某寶過長都返回默認頁面了。

image-20191204205715057
image-20191204205715057

若是你的產品必定要給用戶一點東西,簡單,找出一些熱詞分析出來就行了,或者給點熱搜商品兜底

我怎麼作的呢?判斷字符串長度大於50我就直接返回空數組了,這樣對用戶體驗好點,你返回個參數錯誤或者默認錯誤別人還覺得你有Bug呢對吧。

總結

其實敖丙我啥事故等級都沒背哈哈,這個算是事故,可是敖丙我這麼可愛,領導也心疼我啊,確定不會怪個人拉,主要是我設計都考慮了不少方案和場景了,沒想到有這個坑。(yy:敖丙你個渣男,又是標題黨,人家還覺得你沒工做了要養你呢!)

你們也能夠經過此次事故體會到,技術選型的時候,方案的重要性了吧,就算你考慮不全,可是不至於真正的問題來了手足無措啊,並非全部的事故均可以像此次這樣重啓就搞定了,不要存有僥倖心理,心存敬畏

恰飯環節:沒需求的小夥伴跳過就行了,我幫阿里推廣服務89/年,229/3年,買來送本身,送女友立刻過年再合適不過了,買了搭建個項目給面試官看也香,還能夠熟悉技術棧,我明天會出一個服務器搭建我的項目的教程(老用戶用家人的購買,我用我媽的😂)。
掃碼或者點擊購買

絮叨

敖丙啊,又有牌面了,獲得阿里消息中間件團隊小夥伴的承認,而且發現竟然是我學姐-風雲(花名)!!!

她是個好學的小姐姐,你們多多像優秀的仔學習,學姐不是作技術的,可是都在不斷學習,說實話個人眼角又溼了。

別跑,投票!!!

我準備把個人公衆號JavaFamily 這個名字改了,這個名字仍是差點意思,可是又不能叫敖丙了,被註冊商標了,我就問了下羣裏的人才,目前有兩個我比較喜歡的

  • 帥丙
  • 三太子敖丙
  • 其餘給我留言

由於這個可能會陪伴我好久,甚至直到死去,但願你們都給點建議哈哈。

別問我爲啥要跟敖丙這個名字相關,再問自殺

我花名就叫這個,因此😂

點關注,不迷路

好了各位,以上就是這篇文章的所有內容了,能看到這裏的人呀,都是人才

我後面會每週都更新幾篇一線互聯網大廠面試和經常使用技術棧相關的文章,很是感謝人才們能看到這裏,若是這個文章寫得還不錯,以爲「敖丙」我有點東西的話 求點贊👍 求關注❤️ 求分享👥 對暖男我來講真的 很是有用!!!

創做不易,各位的支持和承認,就是我創做的最大動力,咱們下篇文章見!

敖丙 | 文 【原創】

若是本篇博客有任何錯誤,請批評指教,不勝感激 !


文章每週持續更新,能夠微信搜索「 三太子敖丙 」第一時間閱讀和催更(公衆號比博客早一到兩篇喲),本文 GitHub github.com/JavaFamily 已經收錄,有一線大廠面試點思惟導圖,也整理了不少個人文檔,歡迎Star和完善,你們面試能夠參照考點複習,但願咱們一塊兒有點東西。

相關文章
相關標籤/搜索