Hive MetaStore 在快手遇到的挑戰(zhàn)與優(yōu)化
分享嘉賓:王磊@快手
編輯整理:Frank
導讀:快手基于Hive構(gòu)建數(shù)據(jù)倉庫,并把Hive的元數(shù)據(jù)信息存儲在MySql中,隨著業(yè)務(wù)發(fā)展和數(shù)據(jù)增長,一方面對于計算引擎提出了更高的要求,同時也給Hive元數(shù)據(jù)庫的服務(wù)穩(wěn)定性帶來了巨大的挑戰(zhàn)。本文將主要介紹Hive MetaStore服務(wù)在快手的挑戰(zhàn)與優(yōu)化,包括:
-
快手SQL on Hadoop智能引擎架構(gòu)
-
Hive MetaStore在快手的挑戰(zhàn)
-
Hive MetaStore在快手的優(yōu)化
-
快手SQL on Hadoop的技術(shù)規(guī)劃
Apache Hive是由Facebook開源的數(shù)據(jù)倉庫系統(tǒng),提供SQL查詢能力,快手基于Hive搭建數(shù)據(jù)倉庫,隨著業(yè)務(wù)迅速發(fā)展和數(shù)據(jù)規(guī)模增長,Hive的性能開始成為瓶頸,無法滿足業(yè)務(wù)需求。
Hive把用戶SQL通過解釋器轉(zhuǎn)換為一系列MR作業(yè)提交到hadoop環(huán)境中運行,MR存在作業(yè)啟動、調(diào)度開銷大、落盤多磁盤IO重的問題,這導致其性能注定無法太好,針對Hive查詢速度慢的問題,業(yè)界先后推出了包括presto/impala/spark等查詢引擎,在實現(xiàn)和適用場景上各有優(yōu)缺點。
在計算引擎層面我們所面臨的幾個挑戰(zhàn)是:
-
高性能:業(yè)務(wù)要求更高的查詢性能,需要引入更高效的計算引擎
-
易用性:由于不同引擎在語法以及適用場景上各有優(yōu)缺點,對于業(yè)務(wù)來說存在學習和使用門檻,需要通過技術(shù)手段來降低或者消除這種門檻
-
擴展性:技術(shù)是發(fā)展非??斓?,未來隨著技術(shù)發(fā)展可能還會有其他更高效的引擎不斷出現(xiàn),我們在架構(gòu)設(shè)計上需要能夠考慮到很好地擴展性支持這些新的計算引擎,需要做到計算引擎的可插拔、易擴展
-
低成本:圍繞Hive我們構(gòu)建了大量周邊工具及服務(wù),包括資源管理、血緣管理、權(quán)限控制等各個方面。如果每引入一個引擎都再各自開發(fā)一套周邊工具及服務(wù)的建設(shè)會是一個非常昂貴的事,所以這一塊需要做到低成本接入
基于上述考慮,我們最終基于HiveServer本身的Hook架構(gòu),實現(xiàn)一個BeaconServer。所有的查詢?nèi)匀灰訦iveServer作為統(tǒng)一入口,從而解決易用性和低成本的問題。
BeaconServer作為后端Hook Server服務(wù),配合HS2中的Hook,在HS2 服務(wù)之外實現(xiàn)了所需的功能,包括根據(jù)一定規(guī)則路由SQL到適當?shù)囊?,從而起到查詢加速的效果。當前支持的模塊包括路由、審計、SQL 重寫、錯誤分析、優(yōu)化建議等。
BeaconServer本身是一個無狀態(tài)服務(wù),我們可以很方便進行水平擴容,并且BeaconServer服務(wù)調(diào)整升級不影響HiveServer服務(wù)本身。
基于上述架構(gòu),我們很好的應(yīng)對了前面所提到的四大挑戰(zhàn),引入了更高效的計算引擎,在業(yè)務(wù)無感知的前提下大幅提升了查詢效率。
這里特別提一下,除了引入presto、spark等高效計算引擎并對齊進行優(yōu)化之外,我們針對Hive本身的FetchTask機制(本地讀取hdfs文件返回結(jié)果,不存在作業(yè)提交開銷)也進行了系列改進,使其適用場景更廣,查詢效率更高,在日常查詢中也占了很大比重。
02
Hive MetaStore在快手的挑戰(zhàn)
在介紹完我們智能引擎架構(gòu)之后,接下來進入今天重點的主題Hive MetaStore在快手的挑戰(zhàn)。
首先,我們基于整體服務(wù)架構(gòu)簡單說明一下Hive MetaStore服務(wù)的作用以及其重要性:Hive Metastore是hive用來管理元數(shù)據(jù)的服務(wù),包含database、table、partition等元信息,presto/spark也都以Hive Metastore作為統(tǒng)一的元數(shù)據(jù)中心。
除了計算引擎本身,數(shù)據(jù)血緣、數(shù)據(jù)地圖、數(shù)據(jù)依賴等上層服務(wù)也重度依賴Hive Metatstore。
接下來介紹一下Hive MetaStore服務(wù)當前所面臨的挑戰(zhàn)。
由于快手業(yè)務(wù)使用場景需要大面積使用動態(tài)分區(qū),同時數(shù)據(jù)量和查詢量也在隨著業(yè)務(wù)快速增長,這對Hive MetaStore服務(wù)的性能和穩(wěn)定性帶來挑戰(zhàn):
-
訪問量非常大,目前每天查詢量是50萬+,此外數(shù)據(jù)地圖、數(shù)據(jù)依賴服務(wù)也會直接調(diào)用Hive MetaStore服務(wù)API
-
動態(tài)分區(qū)大量使用,很多業(yè)務(wù)還需要采用多級動態(tài)分區(qū),單表一天的子分區(qū)數(shù)可能上萬,部分頭部表的總分區(qū)數(shù)達到數(shù)百萬規(guī)模
-
Hive表總分區(qū)達到1.5億,由于多級分區(qū)的存在,元數(shù)據(jù)庫中存儲分區(qū)信息的單表記錄已超過10億
-
最后就是分區(qū)增速快,當前單日新增分區(qū)數(shù)近50萬
03
Hive MetaStore在快手的優(yōu)化
針對上述問題,我們考慮從幾個方面進行優(yōu)化:
首先,訪問量多的問題,在大數(shù)據(jù)場景下,存在寫少讀多的特定,對于元數(shù)據(jù)主庫,當前壓力也主要來自于大量的讀操作導致QPS過高,因此第一個優(yōu)化方向是通過讀寫分離來降低主庫壓力;
其次,在HIVE的元數(shù)據(jù)查詢上,存在大量的多表聯(lián)合查詢,尤其存儲分區(qū)信息的兩個大表(PARTITONS和PARTITION_KEY_VALS)之間的聯(lián)合查詢,會對服務(wù)帶來很大壓力,可能導致查詢超時以及慢查詢等問題,因此第二個優(yōu)化方向是優(yōu)化元數(shù)據(jù)API調(diào)用;
最后,從長遠考慮,隨著業(yè)務(wù)發(fā)展,數(shù)據(jù)量和訪問量還會持續(xù)上漲,我們需要具備在極端情況下對于不同優(yōu)先級的訪問進行流量控制的能力,滿足服務(wù)分級保障的需求,同時具備在服務(wù)容量不足時對服務(wù)進行水平擴容的能力。
接下來我們從四個優(yōu)化方向分別進行介紹:
1. MetaStore讀寫分離架構(gòu)設(shè)計
首先,我們介紹一下MetaStore讀寫分離架構(gòu)設(shè)計。
根據(jù)業(yè)務(wù)應(yīng)用場景和需求不同,例如數(shù)據(jù)血緣、數(shù)據(jù)地圖、數(shù)據(jù)依賴等服務(wù)只有讀請求,我們可以直接把MetaStore服務(wù)拆分為讀寫和只讀,只讀服務(wù)鏈接從庫來承接這部分讀請求。從庫本身可水平擴展,能夠很好的降低服務(wù)QPS壓力,把服務(wù)訪問延遲控制在較好的水平,滿足業(yè)務(wù)需求。
在查詢場景中,既有讀請求也有寫請求,沒有辦法直接從服務(wù)層面進行拆分。由于大數(shù)據(jù)場景下普遍寫少讀多,大量讀請求直接發(fā)送到主庫會導致QPS峰值高,服務(wù)抖動引發(fā)慢查詢,進而影響服務(wù)穩(wěn)定性。對此我們的優(yōu)化方案是在查詢層面實現(xiàn)HiveMetaStore API粒度的讀寫分離,通過把對主庫的讀請求盡量路由到從庫,從而有效降低主庫的QPS壓力。這個方案要解決的一個主要問題是如何保證數(shù)據(jù)一致性,避免由于主從同步延遲,導致讀請求在從庫中漏讀數(shù)據(jù)或者讀取到錯誤的過期數(shù)據(jù)。
整體解決思路也很簡單,我們在把讀請求路由到從庫之前,先確保當前服務(wù)所連接的從庫已經(jīng)完成數(shù)據(jù)同步即可。
具體流程為:在HiveServer或者Spark提交SQL創(chuàng)建會話鏈接時,會首先從主庫獲取并保存當前最新的GTID,在同一個會話中,每次寫請求操作完成后,都會更新當前會話所持有的GTID;對于讀請求,會首先獲取從庫當前的GTID,通過比較GTID來判斷從庫是否已經(jīng)完成了數(shù)據(jù)同步,只有當從庫GTID大于等于當前會話持有的GTID時,這次讀操作才會被真正路由到從庫。
通過上述讀寫分離方案,我們主庫的QPS負載下降70%+,并且由于壓力下降,主庫的慢查詢問題也同步大幅減少,有效提升了服務(wù)穩(wěn)定性。
2. MetaStore API優(yōu)化
我們通過讀寫分離手段卸載了主庫壓力,把大量訪問請求轉(zhuǎn)移到從庫,一方面我們可以通過水平擴容進行負載均衡來緩解從庫壓力,另一方面通過優(yōu)化MetaStore接口調(diào)用,也能夠有效提升服務(wù)性能和穩(wěn)定性。
根據(jù)我們分析定位,MetaStore API調(diào)用當前主要面臨的問題包括:
第一,查詢層面存在大量的API調(diào)用,造成底層服務(wù)QPS過高;
第二,在Hive MetaStore層,單次API調(diào)用訪問的數(shù)據(jù)量過大,容易導致服務(wù)瞬時壓力大;
第三,對于存儲分區(qū)信息的兩個大表(單表記錄超10億)查詢時延過高。
第二個單次訪問數(shù)據(jù)量大,造成服務(wù)瞬時壓力高,改成分批方式返回,就能起到削峰作用;第三個分析對兩張大表的查詢性能瓶頸,針對具體問題采用合適的優(yōu)化方案。
首先針對API調(diào)用量大的問題,我們需要查一下為什么有這么多的調(diào)用,是不是都是正常必要的調(diào)用以及如何減少冗余API的調(diào)用。這里我們主要進行了兩點優(yōu)化:
HIVE的DDL DESC TABLE命令,社區(qū)默認行為除了返回表相關(guān)元信息外,還會遍歷獲取這個表所有的分區(qū)信息,對于一個包含大量分區(qū)的表來說,這個操作會非常耗時同時也是不必要的。對此我們做的優(yōu)化是默認跳過這個遍歷獲取分區(qū)元信息的操作。通過測試對比,對于一個包含十萬分區(qū)的表執(zhí)行DESC命令,優(yōu)化前需要兩百多秒,優(yōu)化之后只需要0.2秒。
然后通過對于MetaStore API調(diào)用占比進一步分析發(fā)現(xiàn),get_functions/get_function這兩個接口被大量調(diào)用,這個不太符合預期。經(jīng)過排查發(fā)現(xiàn)這個調(diào)用行為是Spark SQL在初始化Hive MetaStore的時候所觸發(fā)。社區(qū)Spark在3.0版本之前底層所依賴的Hive版本一直是1.2,在這個版本中的初始化實現(xiàn)會先通過get_database獲取所有的HIVE庫,然后針對每個HIVE庫再逐個調(diào)用get_functions接口,接口調(diào)用次數(shù)和總的HIVE庫數(shù)量成正比,導致了大量冗余調(diào)用。在Hive2.3版本中這塊行為已經(jīng)得到了優(yōu)化,我們通過升級Spark所依賴的HIVE包到2.3版本解決了該問題。根據(jù)我們的統(tǒng)計,優(yōu)化后整體API調(diào)用次數(shù)減少近30%。
其次針對單次訪問數(shù)據(jù)量大,造成服務(wù)瞬時壓力高的問題,我們可以考慮改成分批方式完成大數(shù)據(jù)量的掃描,從而起到削峰作用。
例如查詢一個大表某個時間范圍內(nèi)的所有分區(qū),涉及分區(qū)數(shù)11W,優(yōu)化前由于需要一次性掃描大量數(shù)據(jù)并返回,導致元數(shù)據(jù)服務(wù)壓力過大,接口調(diào)用超時,任務(wù)查詢失?。晃覀兺ㄟ^把一次大查詢拆分成一系列小查詢,分批輪次返回需要的數(shù)據(jù),就能有效規(guī)避服務(wù)層面瞬時壓力過大造成的一系列不良后果,優(yōu)化后這次查詢總共耗時17115毫秒。通過上圖元數(shù)據(jù)服務(wù)測試時的網(wǎng)絡(luò)壓力等指標變化,也可以間接反映優(yōu)化效果。
然后我們再分析一下存儲分區(qū)信息的兩個大表查詢時延過高的問題,看看性能瓶頸究竟在什么地方以及如何進行針對性優(yōu)化。
對于select * from table where p_date=‘20200101’ and p_product=‘a(chǎn)’這樣一條Hive查詢,在進行分區(qū)下推時發(fā)送給元數(shù)據(jù)服務(wù)的查詢表達式為:where ((( “FILTER0”.“PART_KEY_VAL”= ?) and (“FILTER2”.“PART_KEY_VAL”= ?)))。
這個查詢表達式使用PARTITION_KEY_VAL表中的PART_KEY_VAL字段來進行匹配過濾,存在的問題是:PARTITION_KEY_VAL表中沒有TBL_ID字段,導致會掃描到無關(guān)表的同名分區(qū);PARTITION_KEY_VAL表中沒有索引列,無法通過索引加速。
針對上述問題,我們的優(yōu)化方案是應(yīng)用PARTITONS表中的分區(qū)名索引加速查詢,并且PARTITIONS表中包含TBL_ID字段,也能夠有效避免對無關(guān)表的分區(qū)掃描。
通過分析expresssionTree,解析時間范圍子樹,獲取最長子串前綴:‘20200101’,從而得到優(yōu)化后的查詢表達式為:where ((( “FILTER0”.“PART_KEY_VAL”= ?) and (“FILTER2”.“PART_KEY_VAL”= ?))) and(“PARTITIONS”.“PART_NAME”like ? )。
這個查詢優(yōu)化前后的耗時對比為2662ms VS 786ms,取得了很大提升。
接下來我們再看另一種可優(yōu)化的場景,對于select * from table where p_date=20200101 and p_hourmin=1000這樣一條Hive查詢,由于其分區(qū)字段類型是string類型,但是Hive查詢中給的是整型值,導致無法通過分區(qū)名進行過濾,會命中該表的全部子分區(qū)。
優(yōu)化方案也很簡單,在SQL解析時,如果filter字段為分區(qū)字段,并且類型為string,強制轉(zhuǎn)換constantValue到string類型。
優(yōu)化前耗時:32288ms,優(yōu)化后耗時:586ms。
3. MetaStore流量控制架構(gòu)設(shè)計
我們接著聊一下MetaStore流量控制架構(gòu)設(shè)計:
Hive MetaStore作為核心的底層依賴服務(wù),需要具備服務(wù)分級保障能力,當服務(wù)壓力過高響應(yīng)能力出現(xiàn)瓶頸時,要能夠優(yōu)先滿足高優(yōu)先級任務(wù)請求、限制或者阻斷低優(yōu)先級請求的能力,防止元數(shù)據(jù)庫出現(xiàn)雪崩狀況。
整體流量控制架構(gòu)設(shè)計如上圖所示,核心點在于引入BeaconServer服務(wù)作為中控層。Beacon Server作為中控層,支持動態(tài)更新設(shè)置流控策略,以及實時獲取當前元數(shù)據(jù)服務(wù)壓力狀況。Hive MetaStore作為客戶端會周期性去中控層獲取當前最新的元數(shù)據(jù)服務(wù)壓力狀況和流控策略,并針對不同優(yōu)先級的API調(diào)用請求采取對應(yīng)的流控措施。
基于上述架構(gòu),我們可以實現(xiàn)在服務(wù)流量高峰期出現(xiàn)性能瓶頸時,能夠按比例延遲或阻斷部分低優(yōu)先級的訪問請求,保證高優(yōu)先級請求繼續(xù)得到正常響應(yīng),當服務(wù)壓力緩解狀態(tài)恢復正常后,再自動恢復對低優(yōu)先級訪問請求的響應(yīng)。
流量控制架構(gòu)在上線后,有效緩解了生命周期管理服務(wù)定期清理大批量分區(qū)時對于元數(shù)據(jù)服務(wù)造成TPS過高的壓力。
4. MetaStore Federation架構(gòu)設(shè)計
最后介紹一下MetaStore Federation架構(gòu)設(shè)計,長遠看,隨著業(yè)務(wù)量持續(xù)增加,MySQL單機依然會存在性能及存儲瓶頸的風險。解決MySQL單機瓶頸和壓力,業(yè)界通用方案是分庫分表,由于Hive元數(shù)據(jù)信息存儲采用三范式設(shè)計,表關(guān)聯(lián)較多,直接在MySQL層面進行拆庫拆表會存在改造成本大、風險高且不利于未來擴容的問題,因此我們考慮采取HiveMetaStore層面的Federation方案,實現(xiàn)元數(shù)據(jù)水平擴展能力。
首先看一下Hive MetaStore內(nèi)部實現(xiàn)邏輯,持久化元數(shù)據(jù)層被抽象成了RawStore,比如MySQL對應(yīng)的實現(xiàn)時ObjectStore,HBase對應(yīng)的實現(xiàn)則是HBaseStore。
基于上述原理,我們首先想到的方案1是基于ObjectStore已有功能和代碼實現(xiàn)KwaiStore,在KwaiStore中實現(xiàn)Hive DB路由數(shù)據(jù)源的功能,配置不同Hive DB到對應(yīng)MySQL數(shù)據(jù)源的映射關(guān)系。
方案1的優(yōu)點在于可以保持包和配置的統(tǒng)一,降低韻味成本;缺點在于對Hive的侵入性較大,并且上線時如果要做到數(shù)據(jù)完全一致,需要暫停服務(wù)。
方案2是通過引入路由層,使用代理轉(zhuǎn)發(fā)請求的方式來實現(xiàn)。這個方案下Metastore代碼不用做任何改動,新增Router層,根據(jù)請求數(shù)據(jù)的Hive DBName來決定路由到哪個Metastore上去。Router層可以水平擴容,可以在Router層做很多擴展功能,白名單、多數(shù)據(jù)源支持(統(tǒng)一元數(shù)據(jù))、Hive DB 禁用、元數(shù)據(jù)權(quán)限等操作。在擴容時,可以在Router層添加規(guī)則,指定某個Hive DB 暫時不可訪問,待數(shù)據(jù)源完全準備好后,再添加路由規(guī)則到Router層,同時取消DB不可用限制;可以做到只影響部分Hive DB的使用。
總結(jié)一下,方案2的優(yōu)點在于對Hive 沒有侵入性,升級版本比較容易,可以靈活定制Router層策略,HA水平擴容,擴容MySQL時相對影響較小,上線風險較小,統(tǒng)一元數(shù)據(jù)入口,方便審計和溯源;不足之處在于新引入服務(wù)層,增加運維成本,Metastore被劃分標簽,配置不完全統(tǒng)一。
綜合考慮方案1和方案2的優(yōu)缺點,我們最終選擇了在HMHandler層來實現(xiàn)路由功能,在HMSHandler中維護一組HiveDB與RawStore的映射關(guān)系,在getMS()時傳入 Hive DB,路由根據(jù)db判斷應(yīng)該使用那個RawStore,不修改RawStore中API的實現(xiàn),不涉及到持久化層的侵入改造。
這個方案的優(yōu)點是配置統(tǒng)一,運維成本低;不修改持久化層,改造難度??;缺點是需要調(diào)整大量Thrift API,在調(diào)用時傳入DB。
▌結(jié)束語
以上是本次關(guān)于HiveMetaStore服務(wù)在快手遇到的挑戰(zhàn)與優(yōu)化的全部內(nèi)容。今天的分享就到這里,謝謝大家。
免責聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺僅提供信息存儲服務(wù)。文章僅代表作者個人觀點,不代表本平臺立場,如有問題,請聯(lián)系我們,謝謝!