魔笛活動平臺要記錄每個活動的用戶行為數據,幫助客服、運營、產品、研發等快速處理客訴、解決線上問題併進行相關數據分析和報警。可以預見到需要存儲和分析海量數據,預估至少幾十億甚至上百億的數據量,所以需要選擇一款能存儲海量數據的資料庫。由於是通過接收MQ存儲或者API方式存儲,所以對實時寫入性能也有一定要... ...
1、背景
魔笛活動平臺要記錄每個活動的用戶行為數據,幫助客服、運營、產品、研發等快速處理客訴、解決線上問題併進行相關數據分析和報警。可以預見到需要存儲和分析海量數據,預估至少幾十億甚至上百億的數據量,所以需要選擇一款能存儲海量數據的資料庫。由於是通過接收MQ存儲或者API方式存儲,所以對實時寫入性能也有一定要求。同時可能後續還需要一些實時數據分析等。這裡總結一下需求點:
1.可以存儲海量數據;
2.寫入性能好;
3.可以進行實時計算分析;
4.查詢性能最好不要太差。
2、技術選型
2.1 MySQL單表
MySQL資料庫我們是算用得最多了。但眾所周知,MySQL是單機的。MySQL能存儲多少數據,取決於那台伺服器的硬碟大小。很多時候MySQL是沒法存儲那麼多數據的,根據行記錄頭信息、可變欄位列表、事務ID、指針欄位、欄位內容信息等不同存儲量極限也會不同,數據存儲量範圍為一百多萬條到將近5億條數據,業界公認MySQL單表容量在1KW量級是最佳狀態,這個感興趣的可以自己去看看,這裡就不再贅述,肯定不能存儲幾十億條數據,所以MySQL單表不適合。
2.2 MySQL分庫分表
分庫分表確實可以存儲更多的數據量,分散式事務和非同步複製等技術也進一步提高了寫入性能和數據的可靠性,將數據分散到多個物理伺服器或表中,減少單個伺服器或表的負擔,查詢性能也還可以,支持線上事務處理。但是有以下不足:
1.MySQL存儲數據耗費的資源更多,當數據量達到幾十億時候,如果每列欄位都加上索引,索引占用空間的比例甚至超過數據本身的存儲空間;;
2.線上分析處理能力一般。因為線上分析處理(OLAP)則需要進行複雜的查詢和分析,通常需要使用聚合函數等操作,這些操作會涉及到大量的數據讀取和計算,因此需要大量的計算資源和記憶體空間,在MySQL分庫分表中,數據被分散存儲在多個節點上,查詢數據需要通過網路進行數據的傳輸和計算,這會導致查詢速度的降低和延遲的增加;
3.由於數據的分散存儲,需要進行多個節點的數據聚合和計算,這也會增加系統的負擔和延遲;
4.同時,這也導致當數據量達到幾十億時候,查詢性能也會受到很大影響。
所以MySQL分庫分表不太適合。
2.3 Elasticsearch
Elasticsearch是一個分散式的搜索引擎,並採用數據分片和高可用性等技術,可以存儲海量數據。採用倒排索引的方式存儲數據,可以快速檢索數據。也可以進行實時數據分析。但是有以下不足:
1.由於分詞等特性,寫吞吐量上有著明顯的瓶頸,;
2.分詞會增加寫入操作的延遲和負載;
3.熱點問題比較難解決。如果資源冗餘不足,就會導致穩定性下降,數據寫入會發生延遲;
4.當數據量增加時,Elasticsearch 的查詢速度會變慢,因為它必須掃描整個索引才能找到符合查詢條件的數據;
5.壓縮率不高,存儲成本也比較高;
2.4 Hbase
HBase是基於HDFS分散式文件系統去構建的,集群的管理基於 ZooKeeper 實現,設計是為了海量數據的快速存儲和隨機訪問,列式存儲也減少數據的讀取量。列族設計、MemStore 緩存、批量寫入、數據壓縮等使其寫入性能也非常優秀。但是有以下不足:
1.它並不是一個實時計算和數據分析的框架;
-
數據存儲方式問題:HBase的數據存儲方式是以列族和列的方式存儲數據,這種方式適合存儲結構化數據,但是在存儲非結構化數據時效率較低。在實時數據分析場景下,數據可能是半結構化或者非結構化的,這種數據存儲在HBase中需要進行額外的處理,導致效率下降;
-
RowKey的設計對查詢也有一定限制,所以Hbase不太適合;
-
數據讀取效率問題:HBase的數據讀取方式是通過掃描整個表或者通過索引查找特定行來實現的,這種方式在處理大量數據時效率較低,尤其是在實時數據分析場景下,需要快速響應用戶的查詢請求,但是HBase的讀取速度無法滿足這個需求。
2.5 ClickHouse
ClickHouse的特點是高速、可擴展、高效、低成本,它可以適應各種數據存儲和處理需求,包括線上分析處理(OLAP)、實時數據分析、數據倉庫、日誌分析等場景。它支持SQL語言和多種數據格式,包括CSV(逗號分隔值)、JSON、XML等,並且可以通過JDBC、ODBC和HTTP等協議進行訪問。ClickHouse的性能非常出色,可以在秒級別內處理數十億條數據,而且它支持數據壓縮和分區等功能,可以大大降低存儲和查詢成本。基於以上特性,選擇ClickHouse來存儲、查詢和分析數據。
3、ClickHouse詳細介紹
3.1 來源
ClickHouse是俄羅斯的搜索巨頭Yandex公司開發的面向列式存儲的關係型資料庫(DBMS),於2016年開源,使用C++編寫的,主要用於線上分析處理查詢(OLAP),能夠使用SQL查詢實時生成分析數據報告。ClickHouse 是過去兩年中 OLAP 領域中最熱門的。
ClickHouse的初始設計目的是為了服務於自己公司的一款名叫Yandex.Metrica的產品。Metrica是一款Web流量分析工具,基於前方探針採集行為數據,然後進行一系列的數據分析,類似數據倉庫的OLAP分析。而在採集數據的過程中,一次頁面click(點擊),會產生一個event(事件)。所以,整個系統的邏輯就是基於頁面的點擊事件流,面向數據倉庫進行OLAP分析。所以ClickHouse的全稱是Click Stream(點擊流),Data WareHouse(數據倉庫),簡稱ClickHouse。
3.2 架構
ClickHouse則採用Multi-Master多主架構,集群中每個角色對等,客戶端訪問任意一個節點都能得到相同的效果。
Single-Master架構對於查詢場景,部分查詢的最後階段會在Master節點上進行最終的數據處理,需要消耗一定的CPU以及記憶體資源。對於寫入場景,大量的實時插入、更新、刪除的需要高性能保證。同時併發連接數很大的情況Single-Master結構也較難處理。
Multi-Master通過水平擴展Master節點突破了原架構單Master的限制,配合Segment節點(計算節點)的彈性,系統整體能力尤其是連接數及讀寫性能得到進一步提升,更好地滿足實時數倉及HTAP等業務場景的需求。
集群部署架構:
在每個節點創建一個數據表,作為一個數據分片,使用ReplicatedMergeTree表引擎實現數據副本,而分佈表作為數據寫入和查詢的入口。
3.3 特點
3.3.1 列式存儲
首先我們先看行存儲:
按行存儲的時候,一行記錄的屬性值存儲在臨近的空間,然後接著是下一條記錄的屬性值。好處是想查某個人所有的屬性時,可以通過一次磁碟查找加順序讀取就可以。但是當想查所有人的年齡時,需要不停的查找,或者全表掃描才行,遍歷的很多數據都是不需要的,大量磁碟轉動定址的操作使得讀取效率大大降低。
id | name | dept |
---|---|---|
1 | 張三 | A |
2 | 李四 | B |
3 | 王五 | A |
數據在磁碟上是以行的形式存儲在磁碟上,同一行的數據緊挨著存放在一起。由於 dept 的值是離散地存儲在磁碟中,在查詢過程中,需要磁碟轉動多次,才能完成數據的定位和返回結果。
列存儲:
對於 OLAP 場景,一個典型的查詢需要遍歷整個表,進行分組、排序、聚合等操作,這樣一來行式存儲中把一整行記錄存放在一起的優勢就不復存在了。而且,分析型 SQL 常常不會用到所有的列,而僅僅對其中某些需要的的列做運算,那一行中無關的列也不得不參與掃描。
id | 1 | 2 | 3 |
---|---|---|---|
name | 張三 | 李四 | 王五 |
dept | A | B | A |
然而在列式存儲中,而按列存儲的時候,單個屬性所有的值存儲在臨近的的空間,即一列的所有數據連續存儲的,每個屬性有不同的空間。由於同一列的數據被緊挨著存放在了一起,那麼基於需求欄位查詢和返回結果時,就不許對每一行數據進行掃描,按照列找到需要的數據,磁碟的轉動次數少,性能也會提高。
列的組成都是靈活的,行與行之間的列不需要相同。三行數據實際在CK中數一行數據。
列名 | Columns |
---|---|
row1 | |
row2 |
列式存儲優點:
1.對於列的聚合,計數,求和等統計操作要優於行式存儲。
2.由於某一列的數據類型都是相同的,針對於數據存儲更容易進行數據壓縮,每一列選擇更優的數據壓縮演算法,大大提高了數據的壓縮比重。
3.由於數據壓縮比更好,一方面節省了磁碟空間,另一方面對於cache也有了更大的發揮空間。
3.3.2 完備的DBMS功能
ClickHouse擁有完備的管理功能,所以它是真正的列式資料庫管理系統,而不僅是一個資料庫。作為一個DBMS,它具備了一些基本功能。
1.DDL ( 數據定義語言 ):可以動態地創建、修改或刪除資料庫、表和視圖,而無須重啟服務。
2.DML ( 數據操作語言 ):可以動態查詢、插入、修改或刪除數據。
3.許可權控制:可以按照用戶粒度設置資料庫或者表的操作許可權,保障數據的安全性。
4.數據備份與恢復:提供了數據備份導出與導入恢復機制,滿足生產環境的要求。
5.分散式管理:提供集群模式,能夠自動管理多個資料庫節點。
3.3.3 數據壓縮
ClickHouse數據預設使用LZ4演算法壓縮,它使用374台伺服器的集群,存儲了20.3萬億行的數據。在去除重覆與副本數據的情況下,壓縮後的數據達到了2PB,未壓縮前(TSV格式)大概有17PB,數據總體的壓縮比可以達到8:1。
ClickHouse採用列式存儲,列式存儲相對於行式存儲另一個優勢就是對數據壓縮的友好性。例如:有兩個字元串“ABCDE”,“BCD”,現在對它們進行壓縮:
壓縮前:ABCDE_BCD 壓縮後:ABCDE_(5,3)
通過上面例子可以看到,壓縮的本質是按照一定步長對數據進行匹配掃描,當發現重覆部分的時候就進行編碼轉換。例如:(5,3)代表從下劃線往前數5個位元組,會匹配上3個位元組長度的重覆項,即:“BCD”。當然,真實的壓縮演算法比以上舉例更複雜,但壓縮的本質就是如此,數據中重覆性項越多,則壓縮率越高,壓縮率越高,則數據體量越小,而數據體量越小,則數據在網路中的傳輸越快,對網路帶寬和磁碟IO的壓力也就越小。 列式存儲中同一個列的數據由於它們擁有相同的數據類型和現實語義,可能具備重覆項的可能性更高,更利於數據的壓縮。所以ClickHouse在數據壓縮上比例很大。
壓縮必然帶來壓縮和解壓縮的CPU消耗,這是一個利用CPU時間換I/O時間的手段。事務資料庫由於大部分情況下是針對行的操作,因此如果對每一行都進行一次壓縮解壓縮,帶來的時間消耗是遠大於磁碟I/O時間的。這就是事務資料庫沒有使用壓縮技術的原因。而ClickHouse則不同,ClickHouse的最小處理單元是塊,塊一般由8192行數據組成,ClickHouse的一次壓縮針對的是8192行數據,這就極大降低CPU的壓縮和解壓縮時間。同時,ClickHouse是列存資料庫,同一列的數據相對更有規律,因此能夠帶來比較大的壓縮比。因此,塊+壓縮在ClickHouse中成為一個非常關鍵的優化手段。
3.3.4 向量化執行引擎
向量化執行,可以簡單地看作一項消除程式中迴圈的優化。
我們看下麵一個簡單的代碼:
for (size_t i = 0; i < 100; ++i)
c[i] = a[i] + b[i];
這個代碼會迴圈100次,將a和b數組對應下標的數字相加如何賦值給c,那麼如何加速這樣的計算呢,一個朴素的想法就是寫出如下的代碼,這是非向量化執行的方式:
c[0] = a[0] + b[0];
c[1] = a[1] + b[1];
... ...
向量化執行的方式就是並行執行一次。
為了實現向量化執行,需要利用CPU的SIMD指令。SIMD的全稱是Single Instruction Multiple Data,即用單條指令操作多條數據。現代電腦系統概念中,它是通過數據並行以提高性能的一種實現方式(其他的還有指令級並行和線程級並行),它的原理是在CPU寄存器層面實現數據的並行操作。
如果這時候CPU也可以並行的計算我們寫的代碼,那麼理論上我們的處理速度就會是之前代碼的100倍,幸運的是SIMD指令就是完成這樣的工作的,用SIMD指令去完成這樣代碼設計和執行就叫做向量化。
從上圖中可以看到,CPU、CPU三級緩存、記憶體、磁碟數據容量與數據讀取速度對比,從左向右,距離CPU越遠,則數據的訪問速度越慢。從寄存器中訪問數據的速度,是從記憶體訪問數據速度的300倍,是從磁碟中訪問數據速度的3000萬倍。所以利用CPU向量化執行的特性,對於程式的性能提升意義非凡。
ClickHouse提供了很多內置函數,在使用這些內置函數時,ClickHouse會自動進行向量化優化。因此儘可能使用提供的內置函數進行計算,而不是自己寫SQL語句。下麵展示錯誤的SQL寫法以及正確的寫法。
SELECT (2/(1.0 + exp(-2 * x))-1) as tanh_x …… // 錯誤的寫法
SELECT tanh(x) as tanh_x …… // 正確的寫法,直接使用ClickHouse的內置函數
3.3.5 數據分片與分散式查詢
數據分片是將數據進行橫向切分,這是一種在面對海量數據的場景下,解決存儲和查詢瓶頸的有效手段,是一種分治思想的體現。ClickHouse支持分片,而分片則依賴集群。每個集群由1到多個分片組成,而每個分片則對應了ClickHouse的1個服務節點。分片的數量上限取決於節點數量(1個分片只能對應1個服務節點)。
ClickHouse 分片可以理解為就是 ClickHouse 一個單機資料庫實例(副本節點也算),多個這種單機資料庫實例構成一個 ClickHouse 集群。分片是指包含數據不同部分的伺服器(要讀取所有數據,必須訪問所有分片)。ClickHouse 通過分片,將一張表的數據水平分割在不同的節點上,隨著業務的發展,當表數據的大小增加到很大時,也能夠通過水平擴容, 保證數據的存儲。
ClickHouse擁有高度自動化的分片功能。ClickHouse提供了本地表 ( Local Table ) 與分散式表 ( Distributed Table ) 的概念。一張本地表等同於一份數據的分片。而分散式表本身不存儲任何數據,它是本地表的訪問代理,其作用類似分庫中間件。藉助分散式表,能夠代理訪問多個數據分片,從而實現分散式查詢。簡單理解,Distributed 表引擎只是你真實數據表(本地表)的代理,在進行數據查詢時,它會將查詢請求發送到各個分片上,結合索引(如果有),並行進行查詢計算,最終將結果進行合併,返回到 Client。
這種設計類似資料庫的分庫和分表,十分靈活。例如在業務系統上線的初期,數據體量並不高,此時數據表並不需要多個分片。所以使用單個節點的本地表(單個數據分片)即可滿足業務需求,待到業務增長、數據量增大的時候,再通過新增數據分片的方式分流數據,並通過分散式表實現分散式查詢。
3.3.6 多線程
向量化執行是通過數據級並行的方式提升了性能,多線程處理是通過線程級並行的方式實現了性能的提升。相比基於底層硬體實現的向量化執行SIMD,線程級並行通常由更高層次的軟體層面控制,目前市面上的伺服器都支持多核心多線程處理能力。由於SIMD不適合用於帶有較多分支判斷的場景,ClickHouse也大量使用了多線程技術以實現提速,以此和向量化執行形成互補。
ClickHouse在數據存取方面,既支持分區(縱向擴展,利用多線程原理 ),也支持分片(橫向擴展,利用分散式原理),可以說是將多線程和分散式的技術應用到了極致。
3.3.7 關係模型與標準SQL查詢
ClickHouse 和 MySQL 類似,把表級的存儲引擎插件化,根據表的不同需求可以設定不同的存儲引擎。目前包括合併樹、日誌、介面和其他四大類 20 多種引擎。
相比HBase、Redis、MongoDB這類NoSQL資料庫,ClickHouse使用關係模型描述數據並提供了傳統資料庫的概念(資料庫、表、視圖和函數等)。ClickHouse完全使用SQL作為查詢語言(支持GROUP BY、ORDER BY、JOIN、IN等大部分標準SQL),ClickHouse提供了標準協議的SQL查詢介面,可以與第三方分析可視化系統無縫集成對接。支持mybatis和mybatis-plus,但是mybatis-plus分頁支持還不是很友好,但是通過一定方式也可以實現。在SQL解析方面,ClickHouse是大小寫敏感,SELECT a 和 SELECT A所代表的語義不同。
3.3.8 多主架構
Spark、HBase和Elasticsearch這類分散式系統,都採用了Master-Slave主從架構,由一個管控節點作為Leader統籌全局。而ClickHouse則採用Multi-Master多主架構,集群中的每個節點角色對等,客戶端訪問任意一個節點都能得到相同的效果。這種多主的架構有許多優勢,例如對等的角色使系統架構變得更加簡單,不用再區分主控節點、數據節點和計算節點,集群中的所有節點功能相同。所以它天然規避了單點故障的問題。
3.4 數據類型
3.4.1 數值
Int Ranges
Int8 — [-128 : 127]
Int16 — [-32768 : 32767]
Int32 — [-2147483648 : 2147483647]
Int64 — [-9223372036854775808 : 9223372036854775807]
Int128 — [-170141183460469231731687303715884105728 : 170141183460469231731687303715884105727]
Int256 — [-57896044618658097711785492504343953926634992332820282019728792003956564819968 : 57896044618658097711785492504343953926634992332820282019728792003956564819967]
推薦使用: UInt
Uint Ranges
UInt8 — [0 : 255]
UInt16 — [0 : 65535]
UInt32 — [0 : 4294967295]
UInt64 — [0 : 18446744073709551615]
UInt256 — [0 : 115792089237316195423570985008687907853269984665640564039457584007913129639935]
3.4.2 浮點數
單精度浮點: Float32 4個位元組, 有效精度7
雙精度浮點: Float64 8個位元組, 有效精度16
位數超過會發生溢出;
註意: 浮點數支持正無窮(inf)、負無窮(-inf)及非數字(nan)的表達式
3.4.3 Decimal
如果需要使用更高精度運算,需要使用Decimal
Decimal32(S),Decimal64(S),Decimal128(S)
或者Decimal(P,S)
3.4.4 String 字元串
可以存儲任意長度字元串,包括Null
FixedString 定長字元串
FixedString 與Char都是定長字元串,對於字元有明確長度的場合建議使用固定長度字元串;
定長申明FixedString(N), X表示字元長度;
FixedString與Char不同的是 FixedString 使用null填充末尾字元,Char使用空格填充;
3.4.5 UUID
使用 generateUUIDv4()生成
3.4.6 時間類型
1, DateTime 日期時間,精確到秒, 如: 2021-03-01 00:00:00
2, DateTime64 日期時間,精確到亞秒, 如: 2021-03-01 00:00:00:00
3, Date 日期, 精確到天; 如: 2021-03-01
3.4.7 數組Array
數組裡面可以有不同類型的元素,但是類型必須相容;
類型: c1 Array(Int8) Comment '數組example';
array(1,2.0 ,3.1)
3.4.8 枚舉Enum
Enum8: 底層實際存儲: (String:Int8) Key/Value
Enum16: 底層存儲: (String:Int16) Key/Value
1, Key和Value需要保證唯一性;
2, Key可以為空,但Key和Value不可以同時為空;
欄位定義: c1 Enum('ready' = 1,'start' = 2,'success' = 3,'error' = 4) comment '枚舉值舉例';
eg: INSERT INTO Enum_TB VALUES('ready')
3.4.9 CK與其他關係型資料庫類型對比
Int8 — TINYINT, BOOL, BOOLEAN, INT1
Int16 — SMALLINT, INT2.
Int32 — INT, INT4, INTEGER.
Int64 — BIGINT.
Float32 — float.
Float64 — double.
String - VARCHAR, BLOB, TEXT
FixedString - Char
DateTime - datetime
3.5 表引擎 - MergeTree引擎
在這眾多的表引擎中,最常用的是合併樹(MergeTree)表引擎及其家族系列(*MergeTree),因為只有合併樹系列的表引擎才支持主鍵索引、數據分區、數據副本和數據採樣這些特性,同時也只有此系列的表引擎支持ALTER相關操作。
MergeTree 的主要特點為:
1.存儲的數據按主鍵排序:這樣可以創建一個小型的稀疏索引來加快數據檢索
2.支持數據分區:數據分區可以僅掃描指定分區數據,提高性能
3.支持數據副本
4.支持數據採樣
3.5.1 MergeTree的創建
CREATE TABLE [IF NOT EXISTS] [db_name.]table_name (
name1 [type] [DEFAULT|MATERIALIZED|ALIAS expr],
name2 [type] [DEFAULT|MATERIALIZED|ALIAS expr],
省略...
) ENGINE = MergeTree()
[PARTITION BY expr]
[ORDER BY expr]
[PRIMARY KEY expr]
[SAMPLE BY expr]
[SETTINGS name=value, 省略...]
PARTITION BY [選填] :分區鍵,用於指定表數據以何種標準進行分區。分區鍵既可以是單個列欄位,也可以通過元組的形式使用多個列欄位,同時它也支持使用列表達式。如果不聲明分區鍵,則ClickHouse會生成一個名為all的分區。合理使用數據分區,可以有效減少查詢時數據文件的掃描範圍
ORDER BY [必填] :排序鍵,用於指定在一個數據片段內,數據以何種標準排序。預設情況下主鍵(PRIMARY KEY)與排序鍵相同。排序鍵既可以是單個列欄位,例如ORDER BY CounterID,也可以通過元組的形式使用多個列欄位,例如ORDER BY(CounterID,EventDate)。當使用多個列欄位排序時,以ORDER BY(CounterID,EventDate)為例,在單個數據片段內,數據首先會以CounterID排序,相同CounterID的數據再按EventDate排序
PRIMARY KEY [選填] :主鍵,顧名思義,聲明後會依照主鍵欄位生成一級索引,用於加速表查詢。預設情況下,主鍵與排序鍵(ORDER BY)相同,所以通常直接使用ORDER BY代為指定主鍵,無須刻意通過PRIMARY KEY聲明。所以在一般情況下,在單個數據片段內,數據與一級索引以相同的規則升序排列。與其他資料庫不同,MergeTree主鍵允許存在重覆數據(ReplacingMergeTree引擎可以去重)
SAMPLE BY [選填] :用於抽樣的表達式,可選項。如果要用抽樣表達式,主鍵中必須包含這個表達式。例如:SAMPLE BY intHash32(UserID) ORDER BY (CounterID, EventDate, intHash32(UserID))。
SETTINGS:index_granularity [選填] :index_granularity對於MergeTree而言是一項非常重要的參數,它表示索引的粒度,預設值為8192。也就是說,MergeTree的索引在預設情況下,每間隔8192行數據才生成一條索引
SETTINGS:index_granularity_bytes [選填] :在19.11版本之前,ClickHouse只支持固定大小的索引間隔,由index_granularity控制,預設為8192。在新版本中,它增加了自適應間隔大小的特性,即根據每一批次寫入數據的體量大小,動態劃分間隔大小。而數據的體量大小,正是由index_granularity_bytes參數控制的,預設為10M(10×1024×1024),設置為0表示不啟動自適應功能。
數據 TTL: 支持整個表數據的有效期設置和單欄位有效期設置。
3.6 索引
稠密索引:一條數據創建一條索引
稀疏索引:一段數據創建一條索引
3.6.1 一級索引
一級索引是稀疏索引,意思就是說:每一段數據生成一條索引記錄,而不是每一條數據都生成索引, 如果是每一條數據都生成索引,則是稠密索引。用一個形象的例子來說明:如果把MergeTree比作一本書,那麼稀疏索引就好比是這本書的一級章節目錄。一級章節目錄不會具體對應到每個字的位置,只會記錄每個章節的起始頁碼。
MergeTree 的主鍵使用 PRIMARY KEY 定義,待主鍵定義之後,MergeTree 會依據 index_granularity 間隔(預設 8192 行),為數據表生成一級索引並保存至 primary.idx 文件內。
稀疏索引的優勢是顯而易見的,它僅需使用少量的索引標記就能夠記錄大量數據的區間位置信息,且數據量越大優勢越為明顯。以預設的索引粒度(8192)為例,MergeTree只需要12208行索引標記就能為1億行數據記錄提供索引。由於稀疏索引占用空間小,所以primary.idx內的索引數據常駐記憶體,取用速度自然極快。
在 ClickHouse 中,一級索引常駐記憶體。總的來說:一級索引和標記文件一一對齊,兩個 索引標記之間的數據,就是一個數據區間,在數據文件中,這個數據區間的所有數據,生成一個壓縮數據塊。每列壓縮數據文件,存儲每一列的數據,每一列欄位都有獨立的數據文件,每一列都有對應的標記文件,保存了列壓縮文件中數據的偏移量信息,與稀疏索引對齊,又與壓縮文件對應,建立了稀疏索引與數據文件的映射關係。不能常駐記憶體,使用LRU緩存策略加快其取用速度。
需要註意的是:ClickHouse 的主鍵索引與 MySQL 等資料庫不同,它並不用於去重,即便 primary key 相同的行,也可以同時存在於資料庫中。 要想實現去重效果,需要結合具體的表引擎 ReplacingMergeTree、CollapsingMergeTree、VersionedCollapsingMergeTree 實現。
3.6.2 二級索引
二級索引:又稱之為跳數索引。目的和一級索引一樣,是為了減少待搜尋的數據的範圍。
跳數索引的預設是關閉的,需要通過參數來開啟,索引生成粒度由 granularity 控制,如果生成了二級索引,則會在分區目錄下生成額外的:skp_idx_[Column].idx 與 skp_idx_[Column].mrk 文件。
跳數索引的生成規則:按照特定規則每隔 granularity 個 index_granularity 條數據,就會生成一條跳數索引。
比如 minmax 跳數索引,生成的是:granularity 個 index_granularity 條數據內的最大值最小值生成一條索引,如果將來需要針對構建二級索引的這個欄位求最大值最小值,則可以幫助提高效率。
跳數索引一共支持四種類型:minmax(最大最小)、set(去重集合)、 ngrambf_v1(ngram 分詞布隆索引)和 tokenbf_v1(標點符號分詞布隆索引),一張數據表支持同時聲明多個跳數索引。
3.6.3 索引粒度
數據以index_granularity(8192)被標記為多個小的區間,其中每個區間做多8192行數據。MergeTree使用MarkRange表示一個具體的區間,並通過start和end表示其具體的範圍。index_granularity不但作用於一級索引還會影響標記文件和數據文件。因為僅有一級索引文件是無法完成查詢工作的,需要藉助於標記來定位數據,所以一級索引和和數據標記的間隔粒度相同,彼此對齊,而數據文件也會按照index_granularity的間隔粒度生成壓縮數據塊。
3.6.4 查詢和寫入
查詢過程:
數據查詢的本質,可以看作一個不斷減小數據範圍的過程。我們可以總結出數據查詢流程:MergeTree 首先可以依次藉助分區索引、一級索引和二級索引,將數據 掃描範圍縮至最小。然後再藉助數據標記,將需要解壓與計算的數據範圍縮至最小。
寫入過程:
第一步是生產分區目錄,伴隨著每一批數據的寫入,都會生成一個新的分區目錄。在後續的某一時刻,屬於相同分區的目錄會按照規則合併到一起;接著按照index_granularity索引粒度,會分別生成primary.idx一級索引(若聲明瞭二級索引則會穿創建二級索引文件)。每批數據的寫入,都會生成一個新的分區目錄,後續會非同步的將相同分區的目錄進行合併。按照索引粒度,會分別生成一級索引文件、每個欄位的標記和壓縮數據文件
4、ClickHouse應用場景
4.1 使用場景
1.絕大多數請求都是用於讀訪問的,數據一次寫入,多次查詢
2.數據需要以大批次(大於1000行)進行更新,而不是單行更新;或者根本沒有更新操作
3.數據只是添加到資料庫,沒有必要修改
4.讀取數據時,會從資料庫中提取出大量的行,但只用到一小部分列
5.表很“寬”,即表中包含大量的列
6.查詢頻率相對較低(通常每台伺服器每秒查詢數百次或更少)
7.對於簡單查詢,允許大約50毫秒的延遲
8.列的值是比較小的數值和短字元串(例如,每個URL只有60個位元組)
9.在處理單個查詢時需要高吞吐量(每台伺服器每秒高達數十億行)
10.不需要事務
11.數據一致性要求較低
4.2 業務場景
流量分析、精準營銷、廣告實時競價、BI 報表分析、用戶行為分析、日誌分析、實時大屏等。
1.數據倉庫:ClickHouse 可以快速地處理大量的數據,支持高併發查詢和複雜的聚合分析,是數據倉庫和數據分析的首選工具。
2.實時數據分析:ClickHouse 支持實時數據分析和實時查詢,可以快速地處理實時數據流,是實時數據分析和實時監控的首選工具。
3.時序數據存儲:ClickHouse 支持時序數據存儲和時序數據分析,可以快速地處理時間序列數據,是時序數據存儲和時序數據分析的首選工具。
4.數據可視化:ClickHouse 支持數據可視化和報表生成,可以快速地生成各種類型的報表和圖表,是數據可視化和報表生成的首選工具。
5、總結
5.1 缺點
1.沒有完整的事務支持;
2.不支持分詞查詢;
3.缺少高頻率,低延遲的修改或刪除已存在數據的能力。僅能用於批量刪除或修改數據;
4.不擅長 join 操作;
5.不支持高併發,官方建議qps為100。原因: ClickHouse 將數據劃分為多個 partition,每個 partition 再進一步劃分為多個 index_granularity(索引粒度),然後通過多個 CPU核心分別處理其中的一部分來實現並行數據處理。在這種設計下, 單條 Query 就能利用整機所有 CPU。 極致的並行處理能力,極大的降低了查詢延時。所以,ClickHouse 即使對於大量數據的查詢也能夠化整為零平行處理。但是有一個弊端就是對於單條查詢使用多 cpu,就不利於同時併發多條查詢。所以對於高 qps 的查詢業務, ClickHouse 並不是強項。
5.2 為什麼查詢這麼快
利用存儲引擎的特殊設計充分減少磁碟I/O對查詢速度的影響。從用戶提交一條SQL語句進行查詢到最終輸出結果的過程中,大量的時間是消耗在了磁碟I/O上,在很多情況下,I/O所占用的時間可以達到整個時間的90%以上。對存儲引擎磁碟I/O的優化可以獲得非常大的收益。ClickHouse的存儲引擎設計中大量優化的目的也是為了減少磁碟I/O。
1.列存
2.預排序:在實現範圍查找時,可以將大量的隨機讀轉換為順序讀,從而有效提高I/O效率,降低範圍查詢時的I/O時間;
3.數據壓縮:可以減少讀取和寫入的數據量,從而減少I/O時間。
4.向量化引擎,儘可能多地使用內置函數
5.儘可能避免Join操作,可以用Spark替代
6.ClickHouse 會在記憶體中進行 GROUP BY,並且使用 HashTable 裝載數據。
7.索引
8.多線程和分散式
9.演算法:ClickHouse針對不同的應用場景,選擇不同的演算法:
10.對於常量字元串查詢,使用volnitsky演算法
11.對於非常量字元串,使用CPU的向量化執行SIMD,進行暴力優化
12.對於字元串正則匹配,使用re2和hyperscan演算法
5.3 為什麼寫入性能這麼好
1.列存
2.數據壓縮:能夠有效地減少數據的存儲空間,從而提高寫入性能
3.分散式架構
4.多線程寫入:ClickHouse採用多線程寫入機制,能夠同時處理多個寫入請求,從而提高寫入性能。
LSM-Tree存儲結構。
先明白一個測試數據:磁碟順序讀寫和隨機讀寫的性能差距大概是1千到5千倍之間
連續 I/O 順序讀寫,磁頭幾乎不用換道,或者換道的時間很短,性能很高,比如0.03 * 2000 MB /s
隨機 I/O 隨機讀寫,會導致磁頭不停地換道,造成效率的極大降低,0.03MB/s
ClickHouse中的MergeTree也是類LSM樹的思想,日誌結構合併樹,但不是樹,而是利用磁碟順序讀寫能力,實現一個多層讀寫的存儲結構 是一種分層,有序,面向磁碟的數據結構,核心思想是利用了磁碟批量的順序寫要遠比隨機寫性能高出很多 大大提升了數據的寫入能力。
充分利用了磁碟順序寫的特性,實現高吞吐寫能力,數據寫入後定期在後臺Compaction。在數據導入時全部是順序append寫,在後臺合併時也是多個段merge sort後順序寫回磁碟。官方公開benchmark測試顯示能夠達到50MB-200MB/s的寫入吞吐能力,按照每行100Byte估算,大約相當於50W-200W條/s的寫入速度。
作者:京東科技 苗元
來源:京東雲開發者社區