ClickHouse實戰

哥不是小蘿莉發表於2021-10-31

1.概述

最近有被留言關於ClickHouse的使用問題,今天筆者將為大家分享一下ClickHouse的安裝細節和使用方法。

2.內容

首先安裝環境如下所示:

  • Linux:CentOS7
  • ClickHouse:21.8.10.19

2.1 ClickHouse是什麼?

ClickHouse是一個真正的列式資料庫管理系統,在ClickHouse中,資料始終是按照列來進行儲存的,包括向量(向量或者列塊)執行的過程。只要有可能,操作都是基於向量進行分派的,而不是單個的值,這被稱為向量化查詢執行,它有利於降低實際的資料處理開銷。

這個想法並不新鮮,向量程式設計被大量用於科學資料處理中。即使在關係型資料庫中,這個想法也不是什麼新的東西。通常有兩種不同的加速查詢處理的方法:向量化查詢執行和執行時程式碼生成。在後者中,動態的為每一類查詢生成程式碼,消除了間接分派和動態分派。這兩種方法中,並沒有哪一種嚴格地比另一種好。執行時程式碼生成可以更好的將多個操作融合在一起,從而充分利用CPU執行單元和流水線。向量化查詢執行不是特別使用,因為它涉及必須寫到快取並讀回的臨時向量。如果L2快取容納不下臨時資料,那麼這將成為一個問題,但是向量化查詢執行更加容易利用CPU。

2.2 列

要表示記憶體中的列(實際上是列塊),需使用 IColumn 介面。該介面提供了用於實現各種關係操作符的輔助方法。幾乎所有的操作都是不可變的:這些操作不會更改原始列,但是會建立一個新的修改後的列。比如,IColumn::filter 方法接受過濾位元組掩碼,用於 WHERE 和 HAVING 關係操作符中。另外的例子:IColumn::permute 方法支援 ORDER BY 實現,IColumn::cut 方法支援 LIMIT 實現等等。
不同的 IColumn 實現(ColumnUInt8、ColumnString 等)負責不同的列記憶體佈局。記憶體佈局通常是一個連續的陣列。對於資料型別為整型的列,只是一個連續的陣列,比如 std::vector。對於 String 列和 Array 列,則由兩個向量組成:其中一個向量連續儲存所有的 String 或陣列元素,另一個儲存每一個 String 或 Array 的起始元素在第一個向量中的偏移。而 ColumnConst 則僅在記憶體中儲存一個值,但是看起來像一個列。

2.3 欄位

儘管如此,有時候也可能需要處理單個值。表示單個值,可以使用 Field。Field 是 UInt64、Int64、Float64、String 和 Array 組成的聯合。IColumn 擁有 operator[] 方法來獲取第 n 個值成為一個 Field,同時也擁有 insert 方法將一個 Field 追加到一個列的末尾。這些方法並不高效,因為它們需要處理表示單一值的臨時 Field 物件,但是有更高效的方法比如 insertFrom 和 insertRangeFrom 等。
Field 中並沒有足夠的關於一個表(table)的特定資料型別的資訊。比如,UInt8、UInt16、UInt32 和 UInt64 在 Field 中均表示為 UInt64。

2.4 資料型別

IDataType 負責序列化和反序列化:讀寫二進位制或文字形式的列或單個值構成的塊。IDataType 直接與表的資料型別相對應。比如,有 DataTypeUInt32、DataTypeDateTime、DataTypeString 等資料型別。
IDataType 與 IColumn 之間的關聯並不大。不同的資料型別在記憶體中能夠用相同的 IColumn 實現來表示。比如,DataTypeUInt32 和 DataTypeDateTime 都是用 ColumnUInt32 或 ColumnConstUInt32 來表示的。另外,相同的資料型別也可以用不同的 IColumn 實現來表示。比如,DataTypeUInt8 既可以使用 ColumnUInt8 來表示,也可以使用過 ColumnConstUInt8 來表示。
IDataType 僅儲存後設資料。比如,DataTypeUInt8 不儲存任何東西(除了 vptr);DataTypeFixedString 僅儲存 N(固定長度字串的串長度)。
IDataType 具有針對各種資料格式的輔助函式。比如如下一些輔助函式:序列化一個值並加上可能的引號;序列化一個值用於 JSON 格式;序列化一個值作為 XML 格式的一部分。輔助函式與資料格式並沒有直接的對應。比如,兩種不同的資料格式 Pretty 和 TabSeparated 均可以使用 IDataType 介面提供的 serializeTextEscaped 這一輔助函式。

2.5 塊

Block 是表示記憶體中表的子集(chunk)的容器,是由三元組:(IColumn, IDataType, 列名) 構成的集合。在查詢執行期間,資料是按 Block 進行處理的。如果我們有一個 Block,那麼就有了資料(在 IColumn 物件中),有了資料的型別資訊告訴我們如何處理該列,同時也有了列名(來自表的原始列名,或人為指定的用於臨時計算結果的名字)。
當我們遍歷一個塊中的列進行某些函式計算時,會把結果列加入到塊中,但不會更改函式引數中的列,因為操作是不可變的。之後,不需要的列可以從塊中刪除,但不是修改。這對於消除公共子表示式非常方便。
Block 用於處理資料塊。注意,對於相同型別的計算,列名和型別對不同的塊保持相同,僅列資料不同。最好把塊資料(block data)和塊頭(block header)分離開來,因為小塊大小會因複製共享指標和列名而帶來很高的臨時字串開銷。

3.安裝ClickHouse

在Github上搜尋ClickHouse,並下載二進位制安裝包,下載內容如下:

 

 

 下載地址如下:

https://github.com/ClickHouse/ClickHouse/releases

安裝步驟如下:

## 第1步
# 解壓common包
tar -zxvf clickhouse-common-static-21.8.10.19.tgz
tar -zxvf clickhouse-common-static-dbg-21.8.10.19.tgz

# 執行指令碼安裝
sudo doinst.sh

## 第2步
# 解壓server包
tar -zxvf clickhouse-server-21.8.10.19.tgz

# 修改config.xml儲存路徑和監聽埠
<!-- 實際資料儲存路徑,建議用一個磁碟空間較大的目錄 -->
<path>/appcom/clickhouse/data/</path>
<tmp_path>/appcom/clickhouse/data/tmp/</tmp_path>

<!-- 開啟遠端訪問 -->
<listen_host>::</listen_host>

# 執行指令碼安裝
sudo doinst.sh

## 第3步
# 解壓client包
tar -zxvf clickhouse-client-21.8.10.19.tgz

# 執行指令碼安裝
sudo doinst.sh

## 第4步
# 啟動ClickHouse
/etc/init.d/clickhouse-server start

## 第5步
# 啟動Client
clickhouse-client -m -h 127.0.0.1 --user default --port 9000 --password ckxxx

4.實戰

4.1 準備實戰資料

在Linux控制檯中執行如下命令,獲取表資料。命令如下所示:

# 獲取表資料
curl https://datasets.clickhouse.com/hits/tsv/hits_v1.tsv.xz | unxz --threads=`nproc` > hits_v1.tsv

curl https://datasets.clickhouse.com/visits/tsv/visits_v1.tsv.xz | unxz --threads=`nproc` > visits_v1.tsv

4.2 建立資料庫和表

如果是使用ClickHouse Client執行建表語句,啟動Client時,需要指定 -m 的引數,具體命令如下:

-- 建立資料庫
CREATE DATABASE IF NOT EXISTS tutorial;

-- 建立表(hits_v1)所有使用者在服務所涵蓋的所有網站上完成的每個操作
CREATE TABLE tutorial.hits_v1
(
    `WatchID` UInt64,
    `JavaEnable` UInt8,
    `Title` String,
    `GoodEvent` Int16,
    `EventTime` DateTime,
    `EventDate` Date,
    `CounterID` UInt32,
    `ClientIP` UInt32,
    `ClientIP6` FixedString(16),
    `RegionID` UInt32,
    `UserID` UInt64,
    `CounterClass` Int8,
    `OS` UInt8,
    `UserAgent` UInt8,
    `URL` String,
    `Referer` String,
    `URLDomain` String,
    `RefererDomain` String,
    `Refresh` UInt8,
    `IsRobot` UInt8,
    `RefererCategories` Array(UInt16),
    `URLCategories` Array(UInt16),
    `URLRegions` Array(UInt32),
    `RefererRegions` Array(UInt32),
    `ResolutionWidth` UInt16,
    `ResolutionHeight` UInt16,
    `ResolutionDepth` UInt8,
    `FlashMajor` UInt8,
    `FlashMinor` UInt8,
    `FlashMinor2` String,
    `NetMajor` UInt8,
    `NetMinor` UInt8,
    `UserAgentMajor` UInt16,
    `UserAgentMinor` FixedString(2),
    `CookieEnable` UInt8,
    `JavascriptEnable` UInt8,
    `IsMobile` UInt8,
    `MobilePhone` UInt8,
    `MobilePhoneModel` String,
    `Params` String,
    `IPNetworkID` UInt32,
    `TraficSourceID` Int8,
    `SearchEngineID` UInt16,
    `SearchPhrase` String,
    `AdvEngineID` UInt8,
    `IsArtifical` UInt8,
    `WindowClientWidth` UInt16,
    `WindowClientHeight` UInt16,
    `ClientTimeZone` Int16,
    `ClientEventTime` DateTime,
    `SilverlightVersion1` UInt8,
    `SilverlightVersion2` UInt8,
    `SilverlightVersion3` UInt32,
    `SilverlightVersion4` UInt16,
    `PageCharset` String,
    `CodeVersion` UInt32,
    `IsLink` UInt8,
    `IsDownload` UInt8,
    `IsNotBounce` UInt8,
    `FUniqID` UInt64,
    `HID` UInt32,
    `IsOldCounter` UInt8,
    `IsEvent` UInt8,
    `IsParameter` UInt8,
    `DontCountHits` UInt8,
    `WithHash` UInt8,
    `HitColor` FixedString(1),
    `UTCEventTime` DateTime,
    `Age` UInt8,
    `Sex` UInt8,
    `Income` UInt8,
    `Interests` UInt16,
    `Robotness` UInt8,
    `GeneralInterests` Array(UInt16),
    `RemoteIP` UInt32,
    `RemoteIP6` FixedString(16),
    `WindowName` Int32,
    `OpenerName` Int32,
    `HistoryLength` Int16,
    `BrowserLanguage` FixedString(2),
    `BrowserCountry` FixedString(2),
    `SocialNetwork` String,
    `SocialAction` String,
    `HTTPError` UInt16,
    `SendTiming` Int32,
    `DNSTiming` Int32,
    `ConnectTiming` Int32,
    `ResponseStartTiming` Int32,
    `ResponseEndTiming` Int32,
    `FetchTiming` Int32,
    `RedirectTiming` Int32,
    `DOMInteractiveTiming` Int32,
    `DOMContentLoadedTiming` Int32,
    `DOMCompleteTiming` Int32,
    `LoadEventStartTiming` Int32,
    `LoadEventEndTiming` Int32,
    `NSToDOMContentLoadedTiming` Int32,
    `FirstPaintTiming` Int32,
    `RedirectCount` Int8,
    `SocialSourceNetworkID` UInt8,
    `SocialSourcePage` String,
    `ParamPrice` Int64,
    `ParamOrderID` String,
    `ParamCurrency` FixedString(3),
    `ParamCurrencyID` UInt16,
    `GoalsReached` Array(UInt32),
    `OpenstatServiceName` String,
    `OpenstatCampaignID` String,
    `OpenstatAdID` String,
    `OpenstatSourceID` String,
    `UTMSource` String,
    `UTMMedium` String,
    `UTMCampaign` String,
    `UTMContent` String,
    `UTMTerm` String,
    `FromTag` String,
    `HasGCLID` UInt8,
    `RefererHash` UInt64,
    `URLHash` UInt64,
    `CLID` UInt32,
    `YCLID` UInt64,
    `ShareService` String,
    `ShareURL` String,
    `ShareTitle` String,
    `ParsedParams` Nested(
        Key1 String,
        Key2 String,
        Key3 String,
        Key4 String,
        Key5 String,
        ValueDouble Float64),
    `IslandID` FixedString(16),
    `RequestNum` UInt32,
    `RequestTry` UInt8
)
ENGINE = MergeTree()
PARTITION BY toYYYYMM(EventDate)
ORDER BY (CounterID, EventDate, intHash32(UserID))
SAMPLE BY intHash32(UserID);

-- 建立表(visits_v1)預先構建的會話,而不是單個操作
CREATE TABLE tutorial.visits_v1
(
    `CounterID` UInt32,
    `StartDate` Date,
    `Sign` Int8,
    `IsNew` UInt8,
    `VisitID` UInt64,
    `UserID` UInt64,
    `StartTime` DateTime,
    `Duration` UInt32,
    `UTCStartTime` DateTime,
    `PageViews` Int32,
    `Hits` Int32,
    `IsBounce` UInt8,
    `Referer` String,
    `StartURL` String,
    `RefererDomain` String,
    `StartURLDomain` String,
    `EndURL` String,
    `LinkURL` String,
    `IsDownload` UInt8,
    `TraficSourceID` Int8,
    `SearchEngineID` UInt16,
    `SearchPhrase` String,
    `AdvEngineID` UInt8,
    `PlaceID` Int32,
    `RefererCategories` Array(UInt16),
    `URLCategories` Array(UInt16),
    `URLRegions` Array(UInt32),
    `RefererRegions` Array(UInt32),
    `IsYandex` UInt8,
    `GoalReachesDepth` Int32,
    `GoalReachesURL` Int32,
    `GoalReachesAny` Int32,
    `SocialSourceNetworkID` UInt8,
    `SocialSourcePage` String,
    `MobilePhoneModel` String,
    `ClientEventTime` DateTime,
    `RegionID` UInt32,
    `ClientIP` UInt32,
    `ClientIP6` FixedString(16),
    `RemoteIP` UInt32,
    `RemoteIP6` FixedString(16),
    `IPNetworkID` UInt32,
    `SilverlightVersion3` UInt32,
    `CodeVersion` UInt32,
    `ResolutionWidth` UInt16,
    `ResolutionHeight` UInt16,
    `UserAgentMajor` UInt16,
    `UserAgentMinor` UInt16,
    `WindowClientWidth` UInt16,
    `WindowClientHeight` UInt16,
    `SilverlightVersion2` UInt8,
    `SilverlightVersion4` UInt16,
    `FlashVersion3` UInt16,
    `FlashVersion4` UInt16,
    `ClientTimeZone` Int16,
    `OS` UInt8,
    `UserAgent` UInt8,
    `ResolutionDepth` UInt8,
    `FlashMajor` UInt8,
    `FlashMinor` UInt8,
    `NetMajor` UInt8,
    `NetMinor` UInt8,
    `MobilePhone` UInt8,
    `SilverlightVersion1` UInt8,
    `Age` UInt8,
    `Sex` UInt8,
    `Income` UInt8,
    `JavaEnable` UInt8,
    `CookieEnable` UInt8,
    `JavascriptEnable` UInt8,
    `IsMobile` UInt8,
    `BrowserLanguage` UInt16,
    `BrowserCountry` UInt16,
    `Interests` UInt16,
    `Robotness` UInt8,
    `GeneralInterests` Array(UInt16),
    `Params` Array(String),
    `Goals` Nested(
        ID UInt32,
        Serial UInt32,
        EventTime DateTime,
        Price Int64,
        OrderID String,
        CurrencyID UInt32),
    `WatchIDs` Array(UInt64),
    `ParamSumPrice` Int64,
    `ParamCurrency` FixedString(3),
    `ParamCurrencyID` UInt16,
    `ClickLogID` UInt64,
    `ClickEventID` Int32,
    `ClickGoodEvent` Int32,
    `ClickEventTime` DateTime,
    `ClickPriorityID` Int32,
    `ClickPhraseID` Int32,
    `ClickPageID` Int32,
    `ClickPlaceID` Int32,
    `ClickTypeID` Int32,
    `ClickResourceID` Int32,
    `ClickCost` UInt32,
    `ClickClientIP` UInt32,
    `ClickDomainID` UInt32,
    `ClickURL` String,
    `ClickAttempt` UInt8,
    `ClickOrderID` UInt32,
    `ClickBannerID` UInt32,
    `ClickMarketCategoryID` UInt32,
    `ClickMarketPP` UInt32,
    `ClickMarketCategoryName` String,
    `ClickMarketPPName` String,
    `ClickAWAPSCampaignName` String,
    `ClickPageName` String,
    `ClickTargetType` UInt16,
    `ClickTargetPhraseID` UInt64,
    `ClickContextType` UInt8,
    `ClickSelectType` Int8,
    `ClickOptions` String,
    `ClickGroupBannerID` Int32,
    `OpenstatServiceName` String,
    `OpenstatCampaignID` String,
    `OpenstatAdID` String,
    `OpenstatSourceID` String,
    `UTMSource` String,
    `UTMMedium` String,
    `UTMCampaign` String,
    `UTMContent` String,
    `UTMTerm` String,
    `FromTag` String,
    `HasGCLID` UInt8,
    `FirstVisit` DateTime,
    `PredLastVisit` Date,
    `LastVisit` Date,
    `TotalVisits` UInt32,
    `TraficSource` Nested(
        ID Int8,
        SearchEngineID UInt16,
        AdvEngineID UInt8,
        PlaceID UInt16,
        SocialSourceNetworkID UInt8,
        Domain String,
        SearchPhrase String,
        SocialSourcePage String),
    `Attendance` FixedString(16),
    `CLID` UInt32,
    `YCLID` UInt64,
    `NormalizedRefererHash` UInt64,
    `SearchPhraseHash` UInt64,
    `RefererDomainHash` UInt64,
    `NormalizedStartURLHash` UInt64,
    `StartURLDomainHash` UInt64,
    `NormalizedEndURLHash` UInt64,
    `TopLevelDomain` UInt64,
    `URLScheme` UInt64,
    `OpenstatServiceNameHash` UInt64,
    `OpenstatCampaignIDHash` UInt64,
    `OpenstatAdIDHash` UInt64,
    `OpenstatSourceIDHash` UInt64,
    `UTMSourceHash` UInt64,
    `UTMMediumHash` UInt64,
    `UTMCampaignHash` UInt64,
    `UTMContentHash` UInt64,
    `UTMTermHash` UInt64,
    `FromHash` UInt64,
    `WebVisorEnabled` UInt8,
    `WebVisorActivity` UInt32,
    `ParsedParams` Nested(
        Key1 String,
        Key2 String,
        Key3 String,
        Key4 String,
        Key5 String,
        ValueDouble Float64),
    `Market` Nested(
        Type UInt8,
        GoalID UInt32,
        OrderID String,
        OrderPrice Int64,
        PP UInt32,
        DirectPlaceID UInt32,
        DirectOrderID UInt32,
        DirectBannerID UInt32,
        GoodID String,
        GoodName String,
        GoodQuantity Int32,
        GoodPrice Int64),
    `IslandID` FixedString(16)
)
ENGINE = CollapsingMergeTree(Sign)
PARTITION BY toYYYYMM(StartDate)
ORDER BY (CounterID, StartDate, intHash32(UserID), VisitID)
SAMPLE BY intHash32(UserID);

4.3 載入資料

資料匯入到ClickHouse是通過INSERT INTO方式完成的,查詢類似許多SQL資料庫。然而,資料通常是在一個提供支援序列化格式而不是VALUES子句(也支援)。執行命令如下所示:

# 匯入hits_v1表資料
clickhouse-client --password ckxxx --query "INSERT INTO tutorial.hits_v1 FORMAT TSV" --max_insert_block_size=100000 < hits_v1.tsv

# 匯入visits_v1表資料
clickhouse-client --password ckxxx --query "INSERT INTO tutorial.visits_v1 FORMAT TSV" --max_insert_block_size=100000 < visits_v1.tsv

4.4 查詢

這裡查詢,我們使用Hue來作為視覺化介面,這裡推薦大家使用JDBC的方式來連線ClickHouse,由於Hue的Python庫sqlalchemy-clickhouse對ClickHouse的相容很差,如果使用sqlalchemy的方式來連線ClickHouse,會出現很多不相容的情況,同時,在使用JDBC的方式時,也推薦大家使用clickhouse4j-1.4.4.jar的驅動包,而不是clickhouse-jdbc驅動包,即使用cc.blynk.clickhouse.ClickHouseDriver來替代ru.yandex.clickhouse.ClickHouseDriver。Hue系統檔案hue.ini的配置內容如下所示:

 [[[clickhouse]]]
       name=ClickHouse
       interface=jdbc
    #   ## Specific options for connecting to the ClickHouse server.
    #   ## The JDBC driver clickhouse-jdbc.jar and its related jars need to be in the CLASSPATH environment variable.
       options='{"url": "jdbc:clickhouse://localhost:8123", "driver": "cc.blynk.clickhouse.ClickHouseDriver", "user": "default", "password": "ckxxx"}'

同時,在~/.bash_profile中配置clickhouse4j-1.4.4.jar的環境變數,內容如下:

export CLASSPATH=/appcom/clickhouse/app/clickhouse4j-1.4.4.jar

執行source ~/.bash_profile使環境變數立即生效。

4.4.1 AVG查詢

SELECT
    StartURL AS URL,
    AVG(Duration) AS AvgDuration
FROM tutorial.visits_v1
WHERE StartDate BETWEEN '2014-03-23' AND '2014-03-30'
GROUP BY URL
ORDER BY AvgDuration DESC
LIMIT 10

預覽截圖如下:

 

 4.4.2 SUM查詢

SELECT
    sum(Sign) AS visits,
    sumIf(Sign, has(Goals.ID, 1105530)) AS goal_visits,
    (100. * goal_visits) / visits AS goal_percent
FROM tutorial.visits_v1
WHERE (CounterID = 912887) AND (toYYYYMM(StartDate) = 201403) AND (domain(StartURL) = 'yandex.ru')

預覽截圖如下:

 

5.總結

ClickHouse屬於靈活的MPP架構,支援線上擴充套件,簡單方便,可元件高可用分散式叢集。多伺服器分散式處理資料,擁有完整的DBMS系統。底層資料列式儲存,支援壓縮,優化資料儲存,優化索引資料,優化底層儲存。同時,支援資料統計分析各種場景,支援類SQL查詢,異地複製部署等。

6.結束語

這篇部落格就和大家分享到這裡,如果大家在研究學習的過程當中有什麼問題,可以加群進行討論或傳送郵件給我,我會盡我所能為您解答,與君共勉!

另外,博主出書了《Kafka並不難學》和《Hadoop大資料探勘從入門到進階實戰》,喜歡的朋友或同學, 可以在公告欄那裡點選購買連結購買博主的書進行學習,在此感謝大家的支援。關注下面公眾號,根據提示,可免費獲取書籍的教學視訊。

相關文章