本文轉(zhuǎn)自 | 攜程技術(shù)中心 作者 | 蔡岳毅
作者簡(jiǎn)介:
蔡岳毅,攜程酒店大數(shù)據(jù)高級(jí)研發(fā)經(jīng)理,負(fù)責(zé)酒店數(shù)據(jù)智能平臺(tái)研發(fā),大數(shù)據(jù)技術(shù)創(chuàng)新工作。喜歡探索研究大數(shù)據(jù)的開(kāi)源技術(shù)框架。
1 - 背景
- 攜程酒店每天有上千表,累計(jì)十多億數(shù)據(jù)更新,如何保證數(shù)據(jù)更新過(guò)程中生產(chǎn)應(yīng)用高可用;
- 每天有將近百萬(wàn)次數(shù)據(jù)查詢(xún)請(qǐng)求,用戶(hù)可以從粗粒度國(guó)家省份城市匯總不斷下鉆到酒店,房型粒度的數(shù)據(jù),我們往往無(wú)法對(duì)海量的明細(xì)數(shù)據(jù)做進(jìn)一步層次的預(yù)聚合,大量的關(guān)鍵業(yè)務(wù)數(shù)據(jù)都是好幾億數(shù)據(jù)關(guān)聯(lián)權(quán)限,關(guān)聯(lián)基礎(chǔ)信息,根據(jù)用戶(hù)場(chǎng)景獲取不同維度的匯總數(shù)據(jù);
- 為了讓用戶(hù)無(wú)論在app端還是pc端查詢(xún)數(shù)據(jù)提供秒出的效果,我們需要不斷的探索,研究找到最合適的技術(shù)框架;
對(duì)此,我們嘗試過(guò)關(guān)系型數(shù)據(jù)庫(kù),但千萬(wàn)級(jí)表關(guān)聯(lián)數(shù)據(jù)庫(kù)基本上不太可能做到秒出,考慮過(guò)Sharding,但數(shù)據(jù)量大,各種成本都很高。熱數(shù)據(jù)存儲(chǔ)到ElasticSearch,但無(wú)法跨索引關(guān)聯(lián),導(dǎo)致不得不做寬表,因?yàn)闄?quán)限,酒店信息會(huì)變,所以每次要刷全量數(shù)據(jù),不適用于大表更新,維護(hù)成本也很高。Redis鍵值對(duì)存儲(chǔ)無(wú)法做到實(shí)時(shí)匯總,也測(cè)試過(guò)Presto,GreenPlum,kylin,真正讓我們停下來(lái)深入研究,不斷的擴(kuò)展使用場(chǎng)景的是ClickHouse。
2 - ClickHouse介紹
ClickHouse是一款用于大數(shù)據(jù)實(shí)時(shí)分析的列式數(shù)據(jù)庫(kù)管理系統(tǒng),而非數(shù)據(jù)庫(kù)。通過(guò)向量化執(zhí)行以及對(duì)cpu底層指令集(SIMD)的使用,它可以對(duì)海量數(shù)據(jù)進(jìn)行并行處理,從而加快數(shù)據(jù)的處理速度。
主要優(yōu)點(diǎn)有:
- 為了高效的使用CPU,數(shù)據(jù)不僅僅按列存儲(chǔ),同時(shí)還按向量進(jìn)行處理;
- 數(shù)據(jù)壓縮空間大,減少io;處理單查詢(xún)高吞吐量每臺(tái)服務(wù)器每秒最多數(shù)十億行;
- 索引非B樹(shù)結(jié)構(gòu),不需要滿(mǎn)足最左原則;只要過(guò)濾條件在索引列中包含即可;即使在使用的數(shù)據(jù)不在索引中,由于各種并行處理機(jī)制ClickHouse全表掃描的速度也很快;
- 寫(xiě)入速度非常快,50-200M/s,對(duì)于大量的數(shù)據(jù)更新非常適用;
ClickHouse并非萬(wàn)能的,正因?yàn)镃lickHouse處理速度快,所以也是需要為“快”付出代價(jià)。選擇ClickHouse需要有下面注意以下幾點(diǎn):
- 不支持事務(wù),不支持真正的刪除/更新;
- 不支持高并發(fā),官方建議qps為100,可以通過(guò)修改配置文件增加連接數(shù),但是在服務(wù)器足夠好的情況下;
- sql滿(mǎn)足日常使用80%以上的語(yǔ)法,join寫(xiě)法比較特殊;最新版已支持類(lèi)似sql的join,但性能不好;
- 盡量做1000條以上批量的寫(xiě)入,避免逐行insert或小批量的insert,update,delete操作,因?yàn)镃lickHouse底層會(huì)不斷的做異步的數(shù)據(jù)合并,會(huì)影響查詢(xún)性能,這個(gè)在做實(shí)時(shí)數(shù)據(jù)寫(xiě)入的時(shí)候要盡量避開(kāi);
- Clickhouse快是因?yàn)椴捎昧瞬⑿刑幚頇C(jī)制,即使一個(gè)查詢(xún),也會(huì)用服務(wù)器一半的cpu去執(zhí)行,所以ClickHouse不能支持高并發(fā)的使用場(chǎng)景,默認(rèn)單查詢(xún)使用cpu核數(shù)為服務(wù)器核數(shù)的一半,安裝時(shí)會(huì)自動(dòng)識(shí)別服務(wù)器核數(shù),可以通過(guò)配置文件修改該參數(shù);
3 - ClickHouse在酒店數(shù)據(jù)智能平臺(tái)的實(shí)踐
3.1 - 數(shù)據(jù)更新
我們的主要數(shù)據(jù)源是Hive到ClickHouse,現(xiàn)在主要采用如下兩種方式:
Hive到MySql,再導(dǎo)入到ClickHouse
初期在DataX不支持hive到ClickHouse的數(shù)據(jù)導(dǎo)入,我們是通過(guò)DataX將數(shù)據(jù)先導(dǎo)入mysql,再通過(guò)ClickHouse原生api將數(shù)據(jù)從mysql導(dǎo)入到ClickHouse。
為此我們?cè)O(shè)計(jì)了一套完整的數(shù)據(jù)導(dǎo)入流程,保證數(shù)據(jù)從hive到mysql再到ClickHouse能自動(dòng)化,穩(wěn)定的運(yùn)行,并保證數(shù)據(jù)在同步過(guò)程中線(xiàn)上應(yīng)用的高可用。
Hive到ClickHouse
DataX現(xiàn)在支持hive到ClickHouse,我們部分?jǐn)?shù)據(jù)是通過(guò)DataX直接導(dǎo)入ClickHouse。但DataX暫時(shí)只支持導(dǎo)入,因?yàn)橐WC線(xiàn)上的高可用,所以?xún)H僅導(dǎo)入是不夠的,還需要繼續(xù)依賴(lài)我們上面的一套流程來(lái)做ReName,增量數(shù)據(jù)更新等操作。
針對(duì)數(shù)據(jù)高可用,我們對(duì)數(shù)據(jù)更新機(jī)制做了如下設(shè)計(jì):
全量數(shù)據(jù)導(dǎo)入流程
全量數(shù)據(jù)的導(dǎo)入過(guò)程比較簡(jiǎn)單,僅需要將數(shù)據(jù)先導(dǎo)入到臨時(shí)表中,導(dǎo)入完成之后,再通過(guò)對(duì)正式表和臨時(shí)表進(jìn)行ReName操作,將對(duì)數(shù)據(jù)的讀取從老數(shù)據(jù)切換到新數(shù)據(jù)上來(lái)。
增量數(shù)據(jù)的導(dǎo)入過(guò)程
增量數(shù)據(jù)的導(dǎo)入過(guò)程,我們使用過(guò)兩個(gè)版本。
由于ClickHouse的delete操作過(guò)于沉重,所以最早是通過(guò)刪除指定分區(qū),再把增量數(shù)據(jù)導(dǎo)入正式表的方式來(lái)實(shí)現(xiàn)的。
這種方式存在如下問(wèn)題:一是在增量數(shù)據(jù)導(dǎo)入的過(guò)程中,數(shù)據(jù)的準(zhǔn)確性是不可保證的,如果增量數(shù)據(jù)越多,數(shù)據(jù)不可用的時(shí)間就越長(zhǎng);二是ClickHouse刪除分區(qū)的動(dòng)作,是在接收到刪除指令之后內(nèi)異步執(zhí)行,執(zhí)行完成時(shí)間是未知的。如果增量數(shù)據(jù)導(dǎo)入后,刪除指令也還在異步執(zhí)行中,會(huì)導(dǎo)致增量數(shù)據(jù)也會(huì)被刪除。最新版的更新日志說(shuō)已修復(fù)這個(gè)問(wèn)題。
針對(duì)以上情況,我們修改了增量數(shù)據(jù)的同步方案。在增量數(shù)據(jù)從Hive同步到ClickHouse的臨時(shí)表之后,將正式表中數(shù)據(jù)反寫(xiě)到臨時(shí)表中,然后通過(guò)ReName方法切換正式表和臨時(shí)表。
通過(guò)以上流程,基本可以保證用戶(hù)對(duì)數(shù)據(jù)的導(dǎo)入過(guò)程是無(wú)感知的。
3.2 - 數(shù)據(jù)導(dǎo)入過(guò)程的監(jiān)控與預(yù)警
由于數(shù)據(jù)量大,數(shù)據(jù)同步的語(yǔ)句經(jīng)常性超時(shí)。為保證數(shù)據(jù)同步的每一個(gè)過(guò)程都是可監(jiān)控的,我們沒(méi)有使用ClickHouse提供的JDBC來(lái)執(zhí)行數(shù)據(jù)同步語(yǔ)句,所有的數(shù)據(jù)同步語(yǔ)句都是通過(guò)調(diào)用ClickHouse的RestfulAPI來(lái)實(shí)現(xiàn)的。
調(diào)用RestfulAPI的時(shí)候,可以指定本次查詢(xún)的QueryID。在數(shù)據(jù)同步語(yǔ)句超時(shí)的情況下,通過(guò)輪詢(xún)來(lái)獲得某QueryID的執(zhí)行進(jìn)度。這樣保證了整個(gè)查詢(xún)過(guò)程的有序運(yùn)行。在輪詢(xún)的過(guò)程中,會(huì)對(duì)異常情況進(jìn)行記錄,如果異常情況出現(xiàn)的頻次超過(guò)閾值,JOB會(huì)通過(guò)短信給相關(guān)人員發(fā)出預(yù)警短信。
3.3 - 服務(wù)器分布與運(yùn)維
現(xiàn)在主要根據(jù)場(chǎng)景分國(guó)內(nèi),海外/供應(yīng)商,實(shí)時(shí)數(shù)據(jù),風(fēng)控?cái)?shù)據(jù)4個(gè)集群。每個(gè)集群對(duì)應(yīng)的兩到三臺(tái)服務(wù)器,相互之間做主備,程序內(nèi)部將查詢(xún)請(qǐng)求分散到不同的服務(wù)器上做負(fù)載均衡。
假如某一臺(tái)服務(wù)器出現(xiàn)故障,通過(guò)配置界面修改某個(gè)集群的服務(wù)器節(jié)點(diǎn),該集群的請(qǐng)求就不會(huì)落到有故障的服務(wù)器上。如果在某個(gè)時(shí)間段某個(gè)特定的數(shù)據(jù)查詢(xún)量比較大,組建虛擬集群,將所有的請(qǐng)求分散到其他資源富于的物理集群上。
下半年計(jì)劃把每個(gè)集群的兩臺(tái)機(jī)器分散到不同的機(jī)房,可以繼續(xù)起到現(xiàn)有的主備,負(fù)載均衡的作用還能起到dr的作用。同時(shí)為了保障線(xiàn)上應(yīng)用的高可用,我們會(huì)實(shí)現(xiàn)自動(dòng)健康檢測(cè)機(jī)制,針對(duì)突發(fā)異常的服務(wù)器自動(dòng)拉出我們的虛擬集群。
我們會(huì)監(jiān)控每臺(tái)服務(wù)器每天的查詢(xún)量,每個(gè)語(yǔ)句的執(zhí)行時(shí)間,服務(wù)器CPU,內(nèi)存相關(guān)指標(biāo),以便于及時(shí)調(diào)整服務(wù)器上查詢(xún)量比較高的請(qǐng)求到其他服務(wù)器。
4 - ClickHouse使用探索
我們?cè)谑褂肅lickHouse的過(guò)程中遇到過(guò)各種各樣的問(wèn)題,總結(jié)出來(lái)供大家參考。
關(guān)閉Linux虛擬內(nèi)存。在一次ClickHouse服務(wù)器內(nèi)存耗盡的情況下,我們Kill掉占用內(nèi)存最多的Query之后發(fā)現(xiàn),這臺(tái)ClickHouse服務(wù)器并沒(méi)有如預(yù)期的那樣恢復(fù)正常,所有的查詢(xún)依然運(yùn)行的十分緩慢。通過(guò)查看服務(wù)器的各項(xiàng)指標(biāo),發(fā)現(xiàn)虛擬內(nèi)存占用量異常。因?yàn)榇嬖诖罅康奈锢韮?nèi)存和虛擬內(nèi)存的數(shù)據(jù)交換,導(dǎo)致查詢(xún)速度十分緩慢。關(guān)閉虛擬內(nèi)存,并重啟服務(wù)后,應(yīng)用恢復(fù)正常。
為每一個(gè)賬戶(hù)添加join_use_nulls配置。ClickHouse的SQL語(yǔ)法是非標(biāo)準(zhǔn)的,默認(rèn)情況下,以L(fǎng)eft Join為例,如果左表中的一條記錄在右表中不存在,右表的相應(yīng)字段會(huì)返回該字段相應(yīng)數(shù)據(jù)類(lèi)型的默認(rèn)值,而不是標(biāo)準(zhǔn)SQL中的Null值。對(duì)于習(xí)慣了標(biāo)準(zhǔn)SQL的我們來(lái)說(shuō),這種返回值經(jīng)常會(huì)造成困擾。
JOIN操作時(shí)一定要把數(shù)據(jù)量小的表放在右邊,ClickHouse中無(wú)論是Left Join 、Right Join還是Inner Join永遠(yuǎn)都是拿著右表中的每一條記錄到左表中查找該記錄是否存在,所以右表必須是小表。
通過(guò)ClickHouse官方的JDBC向ClickHouse中批量寫(xiě)入數(shù)據(jù)時(shí),必須控制每個(gè)批次的數(shù)據(jù)中涉及到的分區(qū)的數(shù)量,在寫(xiě)入之前最好通過(guò)Order By語(yǔ)句對(duì)需要導(dǎo)入的數(shù)據(jù)進(jìn)行排序。無(wú)序的數(shù)據(jù)或者數(shù)據(jù)中涉及的分區(qū)太多,會(huì)導(dǎo)致ClickHouse無(wú)法及時(shí)的對(duì)新導(dǎo)入的數(shù)據(jù)進(jìn)行合并,從而影響查詢(xún)性能。
盡量減少JOIN時(shí)的左右表的數(shù)據(jù)量,必要時(shí)可以提前對(duì)某張表進(jìn)行聚合操作,減少數(shù)據(jù)條數(shù)。有些時(shí)候,先GROUP BY再JOIN比先JOIN再GROUP BY查詢(xún)時(shí)間更短。
ClickHouse版本迭代很快,建議用去年的穩(wěn)定版,不能太激進(jìn),新版本我們?cè)谑褂眠^(guò)程中遇到過(guò)一些bug,內(nèi)存泄漏,語(yǔ)法不兼容但也不報(bào)錯(cuò),配置文件并發(fā)數(shù)修改后無(wú)法生效等問(wèn)題。
避免使用分布式表,ClickHouse的分布式表性能上性?xún)r(jià)比不如物理表高,建表分區(qū)字段值不宜過(guò)多,太多的分區(qū)數(shù)據(jù)導(dǎo)入過(guò)程磁盤(pán)可能會(huì)被打滿(mǎn)。
服務(wù)器CPU一般在50%左右會(huì)出現(xiàn)查詢(xún)波動(dòng),CPU達(dá)到70%會(huì)出現(xiàn)大范圍的查詢(xún)超時(shí),所以ClickHouse最關(guān)鍵的指標(biāo)CPU要非常關(guān)注。我們內(nèi)部對(duì)所有ClickHouse查詢(xún)都有監(jiān)控,當(dāng)出現(xiàn)查詢(xún)波動(dòng)的時(shí)候會(huì)有郵件預(yù)警。
查詢(xún)測(cè)試Case有:6000W數(shù)據(jù)關(guān)聯(lián)1000W數(shù)據(jù)再關(guān)聯(lián)2000W數(shù)據(jù)sum一個(gè)月間夜量返回結(jié)果:190ms;2.4億數(shù)據(jù)關(guān)聯(lián)2000W的數(shù)據(jù)group by一個(gè)月的數(shù)據(jù)大概390ms。但ClickHouse并非無(wú)所不能,查詢(xún)語(yǔ)句需要不斷的調(diào)優(yōu),可能與查詢(xún)條件有關(guān),不同的查詢(xún)條件表是左join還是右join也是很有講究的。
5 - 總結(jié)
酒店數(shù)據(jù)智能平臺(tái)從去年7月份試點(diǎn),到現(xiàn)在80%以上的業(yè)務(wù)都已接入ClickHouse。滿(mǎn)足每天十多億的數(shù)據(jù)更新和近百萬(wàn)次的數(shù)據(jù)查詢(xún),支撐app性能98.3%在1秒內(nèi)返回結(jié)果,pc端98.5%在3秒內(nèi)返回結(jié)果。
從使用的角度,查詢(xún)性能不是數(shù)據(jù)庫(kù)能相比的,從成本上也是遠(yuǎn)低于關(guān)系型數(shù)據(jù)庫(kù)成本的,單機(jī)支撐40億以上的數(shù)據(jù)查詢(xún)毫無(wú)壓力。與ElasticSearch,Redis相比ClickHouse可以滿(mǎn)足我們大部分使用場(chǎng)景。
我們會(huì)繼續(xù)更深入的研究ClickHouse,跟進(jìn)最新的版本,同時(shí)也會(huì)繼續(xù)保持對(duì)外界更好的開(kāi)源框架的研究,嘗試,尋找到更合適我們的技術(shù)框架。