趣头条基于ClickHouse玩转每天1000亿数据量
本文根據(jù)dbaplus社群第199期線上分享整理而成,文末還有直播回放~
王海勝
趣頭條數(shù)據(jù)中心大數(shù)據(jù)開發(fā)工程師
8年互聯(lián)網(wǎng)工作經(jīng)驗,曾在eBay、唯品會、趣頭條等公司從事大數(shù)據(jù)開發(fā)相關(guān)工作,有豐富的大數(shù)據(jù)落地經(jīng)驗。
業(yè)務背景
隨著公司規(guī)模越來越大,業(yè)務線越來越多,公司的指標規(guī)模也在急速增長,現(xiàn)有的基于storm實時計算的指標計算架構(gòu)的缺點越來越凸顯,所以我們急需對現(xiàn)有的架構(gòu)進行調(diào)整。
1、基于storm的指標平臺存在的問題
指標口徑不夠直觀
數(shù)據(jù)無法回溯
穩(wěn)定性不夠
2、什么是我們需要的?
我們需要一個穩(wěn)定的、基于SQL、方便進行數(shù)據(jù)回溯、并且要足夠快速的引擎,支持我們的實時指標平臺。
1)穩(wěn)定性是最主要的,基于storm的架構(gòu)數(shù)據(jù)都是存儲在內(nèi)存中的,如果指標配置有問題,很容易導致OOM,需要清理全部的數(shù)據(jù)才能夠恢復。
2)基于SQL是避免像storm架構(gòu)下離線SQL到storm topology轉(zhuǎn)換的尷尬經(jīng)歷。
3)方便回溯是數(shù)據(jù)出現(xiàn)問題以后,我們可以簡單的從新刷一下就可以恢復正常,在storm架構(gòu)下有些場景無法完成。
4)快速那是必須的,指標數(shù)越來越多,如果不能再5分鐘周期內(nèi)完成所有的指標計算是不能接受的。
clickhouse
1、為什么選擇clickhouse?
足夠快,在選擇clickhouse以前我們也有調(diào)研過presto、druid等方案,presto的速度不夠快,無法在5分鐘內(nèi)完成這么多次的查詢。
druid的預計算挺好的,但是維度固定,我們的指標的維度下鉆都是很靈活的,并且druid的角色太多維護成本也太高,所以也被pass了。
最終我們選擇了clickhouse,在我們使用之前,部門內(nèi)部其實已經(jīng)有使用單機版對離線數(shù)據(jù)的查詢進行加速了,所以選擇clickhouse也算是順理成章。
2、clickhouse和presto查詢速度比較
clickhouse集群現(xiàn)狀:32核128G內(nèi)存機器60臺,使用ReplicatedMergeTree引擎,每個shard有兩個replica。
presto集群的現(xiàn)狀:32核128G內(nèi)存機器100臺。
1)最簡單的count()的case
從上圖可以看到clickhouse在count一個1100億數(shù)據(jù)表只需要2s不到的時間, 由于數(shù)據(jù)冗余存儲的關(guān)系,clickhouse實際響應該次查詢的機器數(shù)只有30臺(60 / 2),presto在count一個400億的數(shù)據(jù)表耗時80秒左右的時候,100臺機器同時在處理這個count的查詢。
2)常規(guī)指標維度下鉆計算count() + group by + order by + limit
同樣在1100億數(shù)據(jù)表中clickhouse在該case上面的執(zhí)行時間也是非常不錯的耗時5s左右,presto在400億的數(shù)據(jù)集上完成該查詢需要100s左右的時間。
從上面兩個常規(guī)的case的執(zhí)行時間我們可以看出,clickhouse的查詢速度比presto的查詢速度還是要快非常多的。
3、clickhouse為什么如此快
1)優(yōu)秀的代碼,對性能的極致追求
clickhouse是CPP編寫的,代碼中大量使用了CPP最新的特性來對查詢進行加速。
2)優(yōu)秀的執(zhí)行引擎以及存儲引擎
clickhouse是基于列式存儲的,使用了向量化的執(zhí)行引擎,利用SIMD指令進行處理加速,同時使用LLVM加快函數(shù)編譯執(zhí)行,當然了Presto也大量的使用了這樣的特性。
3)稀疏索引
相比于傳統(tǒng)基于HDFS的OLAP引擎,clickhouse不僅有基于分區(qū)的過濾,還有基于列級別的稀疏索引,這樣在進行條件查詢的時候可以過濾到很多不需要掃描的塊,這樣對提升查詢速度是很有幫助的。
4)存儲執(zhí)行耦合
存儲和執(zhí)行分離是一個趨勢,但是存儲和執(zhí)行耦合也是有優(yōu)勢的,避免了網(wǎng)絡(luò)的開銷,CPU的極致壓榨加上SSD的加持,每秒的數(shù)據(jù)傳輸對于網(wǎng)絡(luò)帶寬的壓力是非常大的,耦合部署可以避免該問題。
5)數(shù)據(jù)存儲在SSD,極高的iops。
4、clickhouse的insert和select
1)clickhouse如何完成一次完整的select
這里有個概念需要澄清一下,clickhouse的表分為兩種,一種是本地表另一種是分布式表。本地表是實際存儲數(shù)據(jù)的而分布式表是一個邏輯上的表,不存儲數(shù)據(jù)的只是做一個路由使用,一般在查詢的時候都是直接使用分布式表,分布式表引擎會將我們的查詢請求路由本地表進行查詢,然后進行匯總最終返回給用戶。
2)索引在查詢中的使用
索引是clickhouse查詢速度比較快的一個重要原因,正是因為有索引可以避免不必要的數(shù)據(jù)的掃描和處理。傳統(tǒng)基于hdfs的olap引擎都是不支持索引的,基本的數(shù)據(jù)過濾只能支持分區(qū)進行過濾,這樣會掃描處理很多不必要的數(shù)據(jù)。
clickhouse不僅支持分區(qū)的過濾也支持列級別的稀疏索引。clickhouse的基礎(chǔ)索引是使用了和kafka一樣的稀疏索引,索引粒度默認是8192,即每8192條數(shù)據(jù)進行一次記錄,這樣對于1億的數(shù)據(jù)只需要記錄12207條記錄,這樣可以很好的節(jié)約空間。
二分查找+遍歷也可以快速的索引到指定的數(shù)據(jù),當然相對于稠密索引,肯定會有一定的性能損失,但是在大數(shù)據(jù)量的場景下,使用稠密索引對存儲也是有壓力的。
下面我們通過舉例看下索引在clickhouse的一次select中的應用,該表的排序情況為order by CounterID, Date 第一排序字段為CounterID,第二排序字段為Date,即先按照CounterID進行排序,如果CounterID相同再按照Date進行排序。
場景1 where CounterId=’a’
CounterID是第一索引列,可以直接定位到CounterId=’a’的數(shù)據(jù)是在[0,3]數(shù)據(jù)塊中。
場景2 where Date=’3’
Date為第二索引列,索引起來有點費勁,過濾效果還不是特別的好,Date=’3’的數(shù)據(jù)定位在[2,10]數(shù)據(jù)塊中。
場景3 where CounterId=’a’ and Date=’3’
第一索引 + 第二索引同時過濾,[0,3] 和 [2,10]的交集,所以為[2,3]數(shù)據(jù)塊中。
場景4 where noIndexColumn=’xxx’
對于這樣沒有索引字段的查詢就需要直接掃描全部的數(shù)據(jù)塊[0,10]。
3)clickhouse如何完成一次插入
clickhouse的插入是基于Batch的,它不能夠像傳統(tǒng)的mysql那樣頻繁的單條記錄插入,批次的大小從幾千到幾十萬不等,需要和列的數(shù)量以及數(shù)據(jù)的特性一起考慮,clickhouse的寫入和Hbase的寫入有點”像”(類LSM-Tree),主要區(qū)別有:
沒有內(nèi)存表;
不進行日志的記錄。
clickhouse寫入的時候是直接落盤的, 在落盤之前會對數(shù)據(jù)進行排序以及必要的拆分(如不同分區(qū)的數(shù)據(jù)會拆分成多個文件夾),如果使用的是ReplicatedMergeTree引擎還需要與zookeeper進行交互,最終會有線程在后臺把數(shù)據(jù)(文件夾)進行合并(merge),將小文件夾合并生成大文件夾方便查詢的時候進行讀取(小文件會影響查詢性能)。
5、關(guān)于集群的搭建
1)單副本
缺點:
集群中任何一臺機器出現(xiàn)故障集群不可用;
如果磁盤出現(xiàn)問題不可恢復數(shù)據(jù)永久丟失;
集群升級期間不可用(clickhouse版本更新快)。
2)多副本
多副本可以完美的解決單副本的所有的問題,多副本有2個解決方案:
RAID磁盤陣列;
使用ReplicatedMergeTree引擎,clickhouse原生支持同步的引擎(基于zookeeper)。
兩種方案的優(yōu)缺點:
基于RAID磁盤陣列的解決方案,在版本升級,機器down機的情況下無法解決單副本的缺陷;
基于zookeeper的同步,需要雙倍的機器(費錢),同時對zookeeper依賴太重,zookeeper會成為集群的瓶頸,當zookeeper有問題的時候集群不可寫入(ready only mode);
副本不僅僅讓數(shù)據(jù)更安全,查詢的請求也可以路由到副本所在的機器,這樣對查詢并發(fā)度的提升也是有幫助的,如果查詢性能跟不上添加副本的數(shù)量也是一個解決方案。
6、常見的引擎(MergeTree家族)
1)(Replicated)MergeTree
該引擎為最簡單的引擎,存儲最原始數(shù)據(jù)不做任何的預計算,任何在該引擎上的select語句都是在原始數(shù)據(jù)上進行操作的,常規(guī)場景使用最為廣泛,其他引擎都是該引擎的一個變種。
2)(Replicated)SummingMergeTree
該引擎擁有“預計算(加法)”的功能。
實現(xiàn)原理:在merge階段把數(shù)據(jù)加起來(對于需要加的列需要在建表的時候進行指定),對于不可加的列,會取一個最先出現(xiàn)的值。
3)(Replicated)ReplacingMergeTree
該引擎擁有“處理重復數(shù)據(jù)”的功能。
使用場景:“最新值”,“實時數(shù)據(jù)”。
4)(Replicated)AggregatingMergeTree
該引擎擁有“預聚合”的功能。
使用場景:配合”物化視圖”來一起使用,擁有毫秒級計算UV和PV的能力。
5)(Replicated)CollapsingMergeTree
該引擎和ReplacingMergeTree的功能有點類似,就是通過一個sign位去除重復數(shù)據(jù)的。
需要注意的是,上述所有擁有"預聚合"能力的引擎都在"Merge"過程中實現(xiàn)的,所以在表上進行查詢的時候SQL是需要進行特殊處理的。
如SummingMergeTree引擎需要自己sum(), ReplacingMergeTree引擎需要使用時間+版本進行order by + limit來取到最新的值,由于數(shù)據(jù)做了預處理,數(shù)據(jù)量已經(jīng)減少了很多,所以查詢速度相對會快非常多。
7、最佳實踐
1)實時寫入使用本地表,不要使用分布式表
分布式表引擎會幫我們將數(shù)據(jù)自動路由到健康的數(shù)據(jù)表進行數(shù)據(jù)的存儲,所以使用分布式表相對來說比較簡單,對于Producer不需要有太多的考慮,但是分布式表有些致命的缺點。
數(shù)據(jù)的一致性問題,先在分布式表所在的機器進行落盤,然后異步的發(fā)送到本地表所在機器進行存儲,中間沒有一致性的校驗,而且在分布式表所在機器時如果機器出現(xiàn)down機,會存在數(shù)據(jù)丟失風險;
據(jù)說對zookeeper的壓力比較大(待驗證)。
2)推薦使用(*)MergeTree引擎,該引擎是clickhouse最核心的組件,也是社區(qū)優(yōu)化的重點
數(shù)據(jù)有保障,查詢有保障,升級無感知。
3)謹慎使用on cluster的SQL
使用該類型SQL hang住的案例不少,我們也有遇到,可以直接寫個腳本直接操作集群的每臺進行處理。
8、常見參數(shù)配置推薦
1)max_concurrent_queries
最大并發(fā)處理的請求數(shù)(包含select,insert等),默認值100,推薦150(不夠再加),在我們的集群中出現(xiàn)過”max concurrent queries”的問題。
2)max_bytes_before_external_sort
當order by已使用max_bytes_before_external_sort內(nèi)存就進行溢寫磁盤(基于磁盤排序),如果不設(shè)置該值,那么當內(nèi)存不夠時直接拋錯,設(shè)置了該值order by可以正常完成,但是速度相對存內(nèi)存來說肯定要慢點(實測慢的非常多,無法接受)。
3)background_pool_size
后臺線程池的大小,merge線程就是在該線程池中執(zhí)行,當然該線程池不僅僅是給merge線程用的,默認值16,推薦32提升merge的速度(CPU允許的前提下)。
4)max_memory_usage
單個SQL在單臺機器最大內(nèi)存使用量,該值可以設(shè)置的比較大,這樣可以提升集群查詢的上限。
5)max_memory_usage_for_all_queries
單機最大的內(nèi)存使用量可以設(shè)置略小于機器的物理內(nèi)存(留一點內(nèi)操作系統(tǒng))。
6)max_bytes_before_external_group_by
在進行g(shù)roup by的時候,內(nèi)存使用量已經(jīng)達到了max_bytes_before_external_group_by的時候就進行寫磁盤(基于磁盤的group by相對于基于磁盤的order by性能損耗要好很多的),一般max_bytes_before_external_group_by設(shè)置為max_memory_usage / 2,原因是在clickhouse中聚合分兩個階段:
查詢并且建立中間數(shù)據(jù);
合并中間數(shù)據(jù) 寫磁盤在第一個階段,如果無須寫磁盤,clickhouse在第一個和第二個階段需要使用相同的內(nèi)存。
這些內(nèi)存參數(shù)強烈推薦配置上,增強集群的穩(wěn)定性避免在使用過程中出現(xiàn)莫名其妙的異常。
9、那些年我們遇到過的問題
1)Too many parts(304). Merges are processing significantly slower than inserts
相信很多同學在剛開始使用clickhouse的時候都有遇到過該異常,出現(xiàn)異常的原因是因為MergeTree的merge的速度跟不上目錄生成的速度, 數(shù)據(jù)目錄越來越多就會拋出這個異常, 所以一般情況下遇到這個異常,降低一下插入頻次就ok了,單純調(diào)整background_pool_size的大小是治標不治本的。
我們的場景:
我們的插入速度是嚴格按照官方文檔上面的推薦”每秒不超過1次的insert request”,但是有個插入程序在運行一段時間以后拋出了該異常,很奇怪。
問題排查:
排查發(fā)現(xiàn)失敗的這個表的數(shù)據(jù)有一個特性,它雖然是實時數(shù)據(jù)但是數(shù)據(jù)的eventTime是最近一周內(nèi)的任何時間點,我們的表又是按照day + hour組合分區(qū)的那么在極限情況下,我們的一個插入請求會涉及7*24分區(qū)的數(shù)據(jù),也就是我們一次插入會在磁盤上生成168個數(shù)據(jù)目錄(文件夾),文件夾的生成速度太快,merge速度跟不上了,所以官方文檔的上每秒不超過1個插入請求,更準確的說是每秒不超過1個數(shù)據(jù)目錄。
case study:
分區(qū)字段的設(shè)置要慎重考慮,如果每次插入涉及的分區(qū)太多,那么不僅容易出現(xiàn)上面的異常,同時在插入的時候也比較耗時,原因是每個數(shù)據(jù)目錄都需要和zookeeper進行交互。
2)DB::NetException: Connection reset by peer, while reading from socket xxx
查詢過程中clickhouse-server進程掛掉。
問題排查:
排查發(fā)現(xiàn)在這個異常拋出的時間點有出現(xiàn)clickhouse-server的重啟,通過監(jiān)控系統(tǒng)看到機器的內(nèi)存使用在該時間點出現(xiàn)高峰,在初期集群"裸奔"的時期,很多內(nèi)存參數(shù)都沒有進行限制,導致clickhouse-server內(nèi)存使用量太高被OS KILL掉。
case study:
上面推薦的內(nèi)存參數(shù)強烈推薦全部加上,max_memory_usage_for_all_queries該參數(shù)沒有正確設(shè)置是導致該case觸發(fā)的主要原因。
3)Memory limit (for query) exceeded:would use 9.37 GiB (attempt to allocate chunk of 301989888 bytes), maximum: 9.31 GiB
該異常很直接,就是我們限制了SQL的查詢內(nèi)存(max_memory_usage)使用的上線,當內(nèi)存使用量大于該值的時候,查詢被強制KILL。
對于常規(guī)的如下簡單的SQL, 查詢的空間復雜度為O(1) 。
select count(1) from table where condition1 and condition2?
select c1, c2 from table where condition1 and condition2
對于group by, order by , count distinct,join這樣的復雜的SQL,查詢的空間復雜度就不是O(1)了,需要使用大量的內(nèi)存。
如果是group by內(nèi)存不夠,推薦配置上max_bytes_before_external_group_by參數(shù),當使用內(nèi)存到達該閾值,進行磁盤group by
如果是order by內(nèi)存不夠,推薦配置上max_bytes_before_external_sort參數(shù),當使用內(nèi)存到達該閾值,進行磁盤order by
如果是count distinct內(nèi)存不夠,推薦使用一些預估函數(shù)(如果業(yè)務場景允許),這樣不僅可以減少內(nèi)存的使用同時還會提示查詢速度
對于JOIN場景,我們需要注意的是clickhouse在進行JOIN的時候都是將"右表"進行多節(jié)點的傳輸?shù)?右表廣播),如果你已經(jīng)遵循了該原則還是無法跑出來,那么好像也沒有什么好辦法了
4)zookeeper的snapshot文件太大,follower從leader同步文件時超時
上面有說過clickhouse對zookeeper的依賴非常的重,表的元數(shù)據(jù)信息,每個數(shù)據(jù)塊的信息,每次插入的時候,數(shù)據(jù)同步的時候,都需要和zookeeper進行交互,上面存儲的數(shù)據(jù)非常的多。
就拿我們自己的集群舉例,我們集群有60臺機器30張左右的表,數(shù)據(jù)一般只存儲2天,我們zookeeper集群的壓力 已經(jīng)非常的大了,zookeeper的節(jié)點數(shù)據(jù)已經(jīng)到達500w左右,一個snapshot文件已經(jīng)有2G+左右的大小了,zookeeper節(jié)點之間的數(shù)據(jù)同步已經(jīng)經(jīng)常性的出現(xiàn)超時。?
問題解決:
zookeeper的snapshot文件存儲盤不低于1T,注意清理策略,不然磁盤報警報到你懷疑人生,如果磁盤爆了那集群就處于“殘廢”狀態(tài);?
zookeeper集群的znode最好能在400w以下;?
建表的時候添加use_minimalistic_part_header_in_zookeeper參數(shù),對元數(shù)據(jù)進行壓縮存儲,對于高版本的clickhouse可以直接在原表上面修改該setting信息,注意修改完了以后無法再回滾的。
5)zookeeper壓力太大,clickhouse表處于”read only mode”,插入失敗
zookeeper機器的snapshot文件和log文件最好分盤存儲(推薦SSD)提高ZK的響應;
做好zookeeper集群和clickhouse集群的規(guī)劃,可以多套zookeeper集群服務一套clickhouse集群。
>>>>
直播回放
https://m.qlchat.com/topic/details?pro_cl=link&ch_r=shareR1&userSourceId=71afe6749b850&shareSourceId=oeh5hk16f54e3143c&topicId=2000006223835755
>>>>
活動推薦
2020年4月17日,北京,Gdevops全球敏捷運維峰會將開啟年度首站!重點圍繞數(shù)據(jù)庫、智慧運維、Fintech金融科技領(lǐng)域,攜手阿里、騰訊、螞蟻金服、中國銀行、平安銀行、中郵消費金融、中國農(nóng)業(yè)銀行、中國民生銀行、中國聯(lián)通大數(shù)據(jù)、浙江移動、新炬網(wǎng)絡(luò)等技術(shù)代表,展望云時代下數(shù)據(jù)庫發(fā)展趨勢、破解運維轉(zhuǎn)型困局。
總結(jié)
以上是生活随笔為你收集整理的趣头条基于ClickHouse玩转每天1000亿数据量的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 计算机网络(五)传输层详解
- 下一篇: mstsc远程连接发生身份验证错误要求的