no-image

Kylin 2.0 升級總結

                                    

我從5月中旬開始進行Kylin 2.0的升級,現在的版本是Kylin 1.5.4.1。本次升級的所有工作均由我一人完成,升級耗時和我之前估計的差不多,1個月左右,其中每天平均半天左右的時間在當“客服”(幫使用者答疑,錯誤處理,調優,追查問題)。 本次Kylin 2.0升級已經基本完成,所以寫下此文對本次Kylin 2.0升級進行總結。主要包含以下內容:

  • Kylin 2.0升級的流程
  • Kylin 2.0升級過程中遇到的一些問題
  • 可以供下次升級複用的經驗
  • 如何保證系統可靠性的一點思考
  • 感謝Kylin社群
  • 對Kylin 社群的一些建議
  • 整個升級過程的總結

首先我們簡要回顧下 Kylin 2.0的 升級節奏:

Kylin 2.0的升級節奏

(備註:我們的Kylin服務共有Dev, Test, Staging, Prod4個環境,這也應該是Kylin生產級別的標準配置,其中Dev和 Test環境共用一個HBase叢集,Staging和Prod環境共用一個HBase叢集)

  1. 5月15日 開始Kylin 2.0 程式碼合入。
  2. 5月17日 程式碼合入完成,Dev環境測試Kylin 2.0。
  3. 5月19日 Test環境升級到Kylin 2.0。
  4. 5月25日 灰度1臺Prod的QueryServer到Kylin 2.0。
  5. 6月5日 16臺Prod的QueryServer全部升級到Kylin 2.0。
  6. 6月6日 Staging環境2臺JobServer升級到Kylin 2.0。
  7. 6月8日 Prod環境3臺JobServer升級到Kylin 2.0。
  8. 6月19日 t-digest演算法原理分享(非計劃內,純粹興趣使然,在業餘時間內完成)
  9. ToDo 線上的排程指令碼支援多Segment併發構建。
  10. Doing Kylin 2.0 新特性調研和分享。

升級的大原則

穩定壓倒一切。對穩定性有影響的功能直接禁用。穩定性達不到生產環境要求的新特性就不啟用。

升級的目標

希望此次升級是一次對使用者幾乎透明,幾乎無影響,平穩上線,無case的升級。

1 Kylin 2.0 升級流程

1.1 Kylin 2.0 程式碼合入

需要程式碼合入的原因是我們內部的程式碼和社群的diff已經越來越多,所以必須將我們內部的程式碼合入到社群的2.0 版本中。程式碼合入是一個十分耗神的苦力工作。因為我們早期的commit message不是很規範,所以幾乎每條commit我都要仔細認真的check下,確認每條commit是否已經合入社群,除非是我自己印象很深的commit。程式碼合入要求我們對Kylin的程式碼本身必須是比較熟悉的,這樣當cherry-pick出現diff時我們才能快速,合理的處理。為了減輕程式碼合入的成本並減少失誤,dayue和我在今年2月份引入了以下規範:

後設資料不相容 意味著升級幾乎無法進行

Coprocessor不相容 意味著升級無法灰度,升級成本極高,升級難度極大

API不相容 其實只要能提前測試發現並提前通知使用者即可

1.2.1 後設資料相容性

首先Kylin 2.0的後設資料整體上是相容Kylin 1.5.4.1的, 不過存在以下問題:

  1. 定長編碼的相容性。KYLIN-2642
  2. Model後設資料的相容性。2.0版本之前由於Kylin前端有bug以及後端檢查較鬆,當使用者修改事實表後,會造成Model中dimensions欄位中某一列只有table欄位,但是columns列表是空;或者Model中dimensions欄位有的table已經不存在。 對於前一種情況,我直接改了程式碼,對於後一種情況,就必須得修復後設資料了。我開始是直接修改後設資料的,這種做法效率低下,而且危險係數也比較高。所以就用1個多小時開發測試了 web頁面直接修改model json的功能(KYLIN-2665)。
  3. Project後設資料的相容性。這個問題應該說是我自己給我挖的坑。現象是我在staging升級到2.0後,發現每個Project的配置資訊都沒了。原因是我當初開發這個功能的時候,project配置屬性用的是overrideKylinProps,liyang Review的時候改成了override_kylin_properties,關鍵是我當初發現了這一點,而且我還重構了ProjectRequest,但是我當時完全忘了考慮相容性的問題。

1.2.2 Coprocessor相容性

Coprocessor相容性主要是指Kylin QueryServer和HBase RegionServer的Coprocessor通訊時 序列化和反序列化的相容性。 主要是KYLIN-2603和KYLIN-2212打破了相容性。我處理方式比較簡單直接,就是Revert掉相關Commit。當然,我們也可以選擇讓這些功能變成可配置的,或者直接讓這些功能變成相容的。顯然,後兩種的成本會高一些。

1.2.3 API相容性

實際上本次升級我幾乎沒有測試API相容性,這次升級只暴露出一個API相容性的問題,是我們的使用者反饋出來的。就是cube_desc的Dimension資訊中的table欄位內容格式被修改。2.0前table欄位的格式是DBname.Tablename,2.0後table欄位的格式是table的別名。其實這個合理的做法應該是新加個alias的欄位來表示別名。

1.4 Cube構建測試

構建測試就是抽取了30多個Cube進行build測試,為了節省資源,快速出結果,我沒有選取一些複雜的大cube,這也導致了在測試的時候沒有發現Cube構建的效能問題。

1.5 Cube查詢測試

查詢測試主要是利用我在上次升級時開發的線上查詢回放測試工具。原理是一個執行緒用Presto從Hive表獲取某個cube某天的所有SQL,然後再用一個執行緒池去查Kylin。其中查Kylin的時候每100條會對新舊兩個版本的查詢結果進行校驗。最後會統計輸出每個cube查詢的成功率,查詢時延,失敗的具體SQL和異常。但是對於PreparedStatement的查詢,由於獲取不到具體的SQL,我只能向使用者要了10條左右的SQL進行手動驗證。

1.6 web測試

測試的過程主要是把project,cube,model,job的操作過了一遍,我測試的時候沒有發現什麼問題。 其實web前端的問題我主要是靠Staging 環境的使用者發現並反饋,因為web的具體操作很多,我不可能把所有細節都測試一遍。 而且web出問題影響也不大,因為web的問題不會影響生產,而且只要web發現問題,一般我都可以較快修復。

2 升級中暴露的問題

2.1 查詢機灰度中暴露的問題

  • KYLIN-2652 現象是1臺QuerServer灰度後,隔了大半天的時間,導致所有線上QuerServer查詢都會隨機失敗。 主要原因是KYLIN-2195忽略了 kylin.query.endpoint.compression.result這條配置的特殊性以及CubeVisitService中的KylinConfig不是執行緒安全的,這會導致HBase返回的查詢結果是壓縮的,但是QuerServer按照不壓縮的去反序列化。
  • KYLIN-2647
  • case A when 0 then 0 else 1.0 * B/A end Kylin 2.0中如果A是null查詢則會失敗,列出這個問題是因為這個問題影響比較大,我們的多個使用者都有這種寫法。原因是新版Calcite 生成的程式碼有問題,沒有處理A是null的情況。解決辦法是SQL改寫為 case A when 0 then 0 else cast (B as double) /A end,Calcite對這個SQL生成的程式碼對A等於null時有特殊處理,直接返回null。
  • PreparedStatement的 Date型別的bug。起初我除錯程式碼,以為是Calcite的bug,後來看了下Calcite的程式碼,才發現是Kylin的bug。Calcite對Date型別有特殊處理,會將Date型別的值轉成epoch time,比如2015-01-01 轉為 16436,所以Kylin對Date型別也有特殊處理。

對於Statement,Kylin中Date的轉換格式如下:

1 Date型別的2015-01-01 在OLAPFilterRel.cast中 轉為 1420070400000

2 1420070400000 在請求HBase前後會有編碼和解碼

3 1420070400000 在Tuple的convertOptiqCellValue轉為16436(epoch time)

對於PreparedStatement: Kylin的處理過程如下:

1 KylinClient的AvaticaPreparedStatement 將2015-01-01 轉為 16436

2 KylinClient的KylinPreparedStatement 將16436 轉為 2015-01-01。

3 KylinServer在setParam時 AvaticaPreparedStatement 再次將2015-01-01 轉為 16436

4 在OLAPEnumerator.bindVariable() 中對PreparedStatement的Date型別 還有特殊轉換,不過此處有問題, 因為此處Kylin認為2015-01-01的格式應該是2015-01-01,但實際上是16436。

所以我認為該問題的解決方法可以是:

在OLAPEnumerator.bindVariable() 將16436 轉為1420070400000,應該只需改一行程式碼。

2.2 上線Staging和Prod後Cube構建暴露的效能問題

  • 字典的MR構建對基數超高的列效能很低下。全部禁用了字典的MR構建。
  • 構建Base cuboid時全域性字典頻繁換入換出。 這個也是我自己埋的坑,因為我解決這個問題時在我們內部用的配置和我最終合入社群的配置不一致。社群的配置在Review時按照建議修改了。
  • 計算列基數這一步變的異常慢。原因是kylin.engine.mr.uhc-reducer-count 預設值變成了1。我清晰的記得我當初專門把這個值改成了3,為了讓IT可以cover這個feature。
  • HFile Reducer個數偏少。 我看log發現cuboid總大小估計的比較小,我開始以為是我設定的kylin.cube.size-estimate-ratio 和 kylin.cube.size-estimate-countdistinct-ratio 引數有問題,或者是新版估計cuboid大小演算法有變化。 我新確認了cuboid大小估計演算法的diff,發現雖然有略微區別,但本質上是一樣的。後來我嘗試掉了幾次這兩個引數的大小,發現並沒有明顯效果。後來當我注意到cuboid總大小,Region大小,HFile的大小關係時,才發現好幾個Cube HFile的大小是egion大小的一半。這時我才注意到計算HFile時的這個分支:
     if (hfileSizeMB > 0.0 && kylinConfig.isDevEnv()) {        
         hfileSizeMB = mbPerRegion / 2;        
     }
    

    我開始一直以為kylinConfig.isDevEnv()是false,就忽略這段程式碼,結果點進去後發現kylinConfig.isDevEnv()的預設值是true,而我這次梳理配置時把kylin.env的這個配置刪除了,我覺得這個引數沒啥用。而實際上,估算hfileSizeMB依賴kylin.env的配置肯定是不合理的,想cover IT,直接把sandbox的Hfile的大小調小就可以。

2.3 我自己程式碼的問題

  • 精確去重的Segment粒度的AppendTrieDict之前不支援segment併發,現在已經支援。
  • KYLIN-2606 對精準聚合的精確去重查詢的優化。我之前在判斷一個SQL是否是精準聚合時,遺漏了下面的情況:
SELECT MIN(A) A FROM table WHERE A = '2017-06-05'  //其中 A 是維度
解決辦法就是將 維度作為指標的情況 判為 非精準聚合

3 可供下次升級複用的經驗

  1. 相容性測試時後設資料相容性,Coprocessor相容性, API相容性這3點都需要考慮。
  2. Cube構建測試時需要選取一些複雜的大Cube進行測試,需要重點關注構建效能。
  3. 增大線上查詢回放的Cube個數,對更多的查詢進行測試,重點不在於能測試多少條查詢,在於能測試多少種不同型別的查詢。
  4. 升級順序可以和本次一樣,可以先灰度線上QueryServer,確定查詢沒有問題後,再升級Staging和Prod的JobServer。
  5. 升級Staging和Prod的JobServer前,需要先確認舊版的程式碼是否可以構建新版的cube。如果可以,Staging和Prod的升級間隔就可以拉的很長,甚至可以在staging把Prod上全部cube構建一遍後再升級Prod。 否則,升級完Staging後就需要較快的升級Prod,因為沒法切Cube,會對使用者造成影響。 本次升級就屬於後者,所以升級壓力就會很大,必須快速解決暴露的所有問題。
  6. 應該考慮上線回滾方案,可以參考 來自 Google 的高可用架構理念與實踐
  7. 我們向社群貢獻程式碼時,應該保證合入社群的配置名稱,配置預設值,後設資料的屬性和我們內部一致。

4 我們如何保證複雜系統的可靠性

這個問題可以近似等價於以下問題:

  • 我們如何確保我們的每次升級或者上線是一定沒問題的?
  • 我們有沒有可能寫出沒有bug的複雜系統?
  • 我們的測試到底需要測到什麼程度?

首先,這個世界上沒有完全沒有bug的系統,也不存在100%可用的系統。我們的目標只能是提供可用性儘可能高的系統,比如3個9的可用性,4個9的可用性。關於系統可用性的概念可以參考來自 Google 的高可用架構理念與實踐或者關於高可用的系統

4.1 為什麼複雜系統很難保證可靠性

我認為可能有以下幾點:

1 複雜系統必然有很多模組,那麼這些模組這件的相互影響就會比較複雜。 如果只寫一個二分搜尋或者快排函式,那麼我們可以很容易確定我們的函式是沒有問題的。 因為輸入和輸出是簡單的,各種邊界情況和異常情況也是有限的。 但是在複雜系統中,你有時候一個看似很簡單的獨立改動,也會對其他模組造成影響。 比如KYLIN-2619,我只是換個執行緒池,結果UT掛了,本質原因是Kylin使用的HTTPclient是不支援併發的。 比如KYLIN-2672,我只是優化了Cube遷移的快取更新,結果沒想到切完Cube後導致整個線上的查詢掛了,本質原因是TblColRef在檢查TableDesc一致性的時候用了 == 而不是 equals。其實我們應該使用equals。

2 複雜系統實際應用時的具體環境和引數都是不同的,而不同的context可能會導致不一致的表現。很多時候系統會出現只是某一部分模組cover了所有已知的環境,但是某些模組只cover了部分。

3 複雜系統的依賴一般比較多,越多的依賴必然引入越多的穩定性風險。比如Kylin很好的融入了Hadoop社群,這是其優點,也是其顯著的缺點,比如HBase,Mapreduce,Yarn,HDFS,Hive隨便一個系統出點問題或者有bug,都會給Kylin帶來顯著影響。新版還加入了Spark和Kafka的依賴。 此外,越多的系統依賴,也使得Kylin的日常運維成本極高,此處的運維不僅指你需要確保Kylin所依賴系統的穩定性,瞭解Kylin所依賴系統的原理,這都是應該的,最主要的是你還需要教給你的使用者這些系統的簡單原理,它們在Kylin中的作用,出了問題怎麼排查。

4 現在的複雜系統一般都是分散式系統,而我們知道分散式系統天生就有許多難題:不可靠的網路,不可靠的時鐘,程序無響應,單機掛掉等。

說了這麼多,我其實一直挺好奇像神舟飛船這種完全不能出錯的系統到底是怎麼保障可靠性的?

4.2 航天系統是如何保證系統可靠性的

我谷歌了下,發現盡然有專門的大學專業:可靠性系統工程。 還買了兩篇相關的論文讀了下:《可靠性系統工程的理論與技術框架》,《航天器環境試驗和航天產品的質量與可靠性保證》。結果發現這教授寫的論文和我的本科論文一樣水,沒啥乾貨。最後在《握手太空的航天科技》書中谷歌到一點答案,其實發現和我們保證一個高可用的軟體系統原理是一樣的:

首先是大量,嚴密的測試確保飛船的一些元件,功能是正常的。 和軟體系統一樣。

其次是關鍵部件的備份,冗餘,takeover,關鍵裝置都是3份同時工作。 和軟體系統一樣。

最後是分析飛船可能出現的所有故障情況,並給出應急方案。 也和軟體系統一樣,我們既然不能保證不出case,那麼我就儘可能保證出了case立即發現,立即處理,立即恢復。

可以發現,保證系統可靠性的原理和思路在任何領域都是一致的 除了以上幾點,可靠性的系統當然需要可靠優秀的總體設計或者架構設計,也需要可靠的細節實現或者程式碼實現。

當然,還有個顯然的問題就是,神舟飛船在地面怎麼測試太空的場景?答案是模擬。 所以我們現在可以回答 我們的測試到底需要測到什麼程度 這個問題。答案是,如果你能線上下造出和線上完全相同的環境,那麼你就可以用線上真實的資料或者case進行測試,Kylin在升級時其實是完全可以做到的,只不過這種做法成本太高,所以我們就需要模擬。有人會問,網路隔離,磁碟掛掉,機器down掉,CPU持續飆高等情況可以模擬嗎,答案是可以模擬的,請參考以下篇文章:

分散式系統測試那些事兒——理念

分散式系統測試那些事兒——錯誤注入

分散式系統測試那些事兒——信心的毀滅與重建

這3片文章對測試的講解十分深入,值得大家一讀,大家也可以反思自己系統的測試。

4.3 如何打造高可靠的軟體系統

  • 可靠的系統設計
  • 可靠的理論基礎
  • 可靠的程式碼實現
  • 可靠,充分,全面的測試
  • 充分的冗餘,備份,多活,takeover等
  • 可靠的監控和運維繫統
  • 可靠的故障恢復機制
  • 高效,自動化的工具

具體大家可以參考下面的參考資料,引用陳皓的話總結:高可靠的系統是一個系統化的工程,這不是一個人或者幾個人可以做到的,取決於全公司的技術實力和工程素養。 比如可用性4個9以上的系統,小公司基本不太可能做的出來。

來自 Google 的高可用架構理念與實踐

如何建設高可用系統

關於高可用的系統

TiDB 架構的演進和開發哲學

《designing-data-intensive-applications》 5星力薦,很讚的一本書。

5 感謝Kylin社群

Kylin 2.0為我們帶來多Segment併發重導,TrieDictionaryForest字典,雪花模型,百分位函式,Steaming cubing,Spark cubing等實用功能和新特性,以及若干bug Fix 和效能提升。十分感謝Kylin社群,身為Kylin commiter,能夠理解每位Kylin contributor的付出,因為很多時候,我們都是在自己的業餘時間和假期向Kylin社群貢獻。

6 給Kylin社群的建議

  1. 我們每個Kylin的contributor和commiter都應該考慮相容性問題,具體包括後設資料的相容性,Coprocessor相容性, API相容性。比如像1.2.3 API相容性中舉的例子,只要我們有考慮到這一點,這個相容性問題完全可以避免掉。
  2. 我們每個JIRA的Assignee都應該在JIRA中描述必要的資訊。 Bug型別的issue應該描述bug產生的原因,Fix bug的思路。Improve型別的issue應該描述是如何改進的,如果有效能對比則更好。New Feature型別的issue應該描述清楚背景或動機,並簡要描述實現思路。 Issue Fix後應該在JIRA中給出github commit的連結。現在Kylin的大多數JIRA描述資訊太過簡單,要想知道基本的實現思路,必須自己去讀程式碼,而且具體的commit資訊還得根據JIRA號去找。

  3. 建議用Github的PR代替patch。 好處是首先程式碼閱讀更方便,程式碼Review更方便,這樣commit中就不會有那麼多code review的commit。其次是Github可以和很多自動化工具整合,目前kylin中commit中經常有Fix UT和FIX IT,如果可以讓每個PR自動跑UT和IT,只有通過後才允許合入Master,就不會有這個問題。

  4. 每個Bug Fix後,可以加Test的話,儘量應該加上對應的test。比如KYLIN-1817在1.5.3 版本Fix後,我測試的1.5.4.1和2.0版本依舊都有這個bug。

  5. 建議Kylin參考下其他開源系統,實現自動化測試,甚至是錯誤注入。

  6. 建議Kylin豐富下測試集,現有的Sample cube許多效能問題都無法暴露,或者在新版本正式release前,找幾個合作伙伴用生產級別的資料測試下。

7 Kylin 2.0升級總結。

本次升級基本符合目標。

一個意外是在所有環境升級2.0的4天之後,發生了一次查詢事故。事故的原因是升級2.0後,由於新版的Coprocessor會載入更多的類,所以HBase RegionServer的PermGen增加了10M左右,超過了MaxPermSize,所以PermGen就OOM了。事故的本質原因是RegionServer的PermGen配置較小和Kylin Coprocessor 中catch了OOM異常。而事實上OOM異常幾乎沒有理由去catch,Kylin Coprocessor中更不應該去catch OOM

關聯文章