Clickhouse 分佈式表&本地表 &ClickHouse實現時序數據管理和挖掘

一、CK 分佈式表和本地表

(1)CK是一個純列式存儲的數據庫,一個列就是硬盤上的一個或多個文件(多個分區有多個文件),關於列式存儲這裏就不展開了,總之列存對於分析來講好處更大,因爲每個列單獨存儲,所以每一列數據可以壓縮,不僅節省了硬盤,還可以降低磁盤IO。

(2)CK是多核並行處理的,爲了充分利用CPU資源,多線程和多核必不可少,同時向量化執行也會大幅提高速度。

(3)提供SQL查詢接口,CK的客戶端連接方式分爲HTTP和TCP,TCP更加底層和高效,HTTP更容易使用和擴展,一般來說HTTP足矣,社區已經有很多各種語言的連接客戶端。

(4)CK不支持事務,大數據場景下對事務的要求沒這麼高。

(5)不建議按行更新和刪除,CK的刪除操作也會轉化爲增加操作,粒度太低嚴重影響效率。

生產環境中通常是使用集羣部署,CK的集羣與Hadoop等集羣稍微有些不一樣。如圖所示,CK集羣共包含以下幾個關鍵概念:

(1)CK實例。可以一臺主機上起多個CK實例,端口不同即可,也可以一臺主機一個CK實例。

(2)分片。數據的水平劃分,例如隨機劃分時,圖5中每個分片各有大約一半數據。

(3)副本。數據的冗餘備份,同時也可作爲查詢節點。多個副本同時提供數據查詢服務,能夠加快數據的查詢效率,提高併發度。圖5中CK實例1和示例3存儲了相同數據。

(4)多主集羣模式。CK的每個實例都可以叫做副本,每個實體都可以提供查詢,不區分主從,只是在寫入數據時會在每個分片裏臨時選一個主副本,來提供數據同步服務,具體見下文中的寫入過程。

ck的表分爲兩種:

  • 分佈式表

    一個邏輯上的表, 可以理解爲數據庫中的視圖, 一般查詢都查詢分佈式表. 分佈式表引擎會將我們的查詢請求路由本地表進行查詢, 然後進行彙總最終返回給用戶.

  • 本地表:

    實際存儲數據的表

1、Replication & Sharding

ClickHouse像ElasticSearch一樣具有數據分片(shard)的概念,這也是分佈式存儲的特點之一,即通過並行讀寫提高效率。ClickHouse依靠Distributed引擎實現了分佈式表機制,在所有分片(本地表)上建立視圖進行分佈式查詢,使用很方便。ClickHouse依靠ReplicatedMergeTree引擎族與ZooKeeper實現了複製表機制,成爲其高可用的基礎。

 2、一般 不寫分佈式表的原因

  1. 分佈式表接收到數據後會將數據拆分成多個parts, 並轉發數據到其它服務器, 會引起服務器間網絡流量增加、服務器merge的工作量增加, 導致寫入速度變慢, 並且增加了Too many parts的可能性.
  2. 數據的一致性問題, 先在分佈式表所在的機器進行落盤, 然後異步的發送到本地表所在機器進行存儲,中間沒有一致性的校驗, 而且在分佈式表所在機器時如果機器出現down機, 會存在數據丟失風險.
  3. 數據寫入默認是異步的,短時間內可能造成不一致.
  4. 對zookeeper的壓力比較大(待驗證). 沒經過正式測試, 只是看到了有人提出.

3、Replicated Table & ReplicatedMergeTree Engines

ClickHouse的副本機制之所以叫“複製表”,是因爲它工作在表級別,而不是集羣級別(如HDFS)。也就是說,用戶在創建表時可以通過指定引擎選擇該表是否高可用,每張表的分片與副本都是互相獨立的。

目前支持複製表的引擎是ReplicatedMergeTree引擎族,它與平時最常用的MergeTree引擎族是正交的,如下圖所示。

不同於HDFS的副本機制(基於集羣實現), Clickhouse的副本機制是基於表實現的. 用戶在創建每張表的時候, 可以決定該表是否高可用.

Local_table

CREATE TABLE IF NOT EXISTS {local_table} ({columns}) 
ENGINE = ReplicatedMergeTree('/clickhouse/tables/#_tenant_id_#/#__appname__#/#_at_date_#/{shard}/hits', '{replica}')
partition by toString(_at_date_) sample by intHash64(toInt64(toDateTime(_at_timestamp_)))
order by (_at_date_, _at_timestamp_, intHash64(toInt64(toDateTime(_at_timestamp_))))

ReplicatedMergeTree

CREATE TABLE IF NOT EXISTS test.events_local ON CLUSTER '{cluster}' (
  ts_date Date,
  ts_date_time DateTime,
  user_id Int64,
  event_type String,
  site_id Int64,
  groupon_id Int64,
  category_id Int64,
  merchandise_id Int64,
  search_text String
  -- A lot more columns...
)
ENGINE = ReplicatedMergeTree('/clickhouse/tables/{shard}/test/events_local','{replica}')
PARTITION BY ts_date
ORDER BY (ts_date_time,site_id,event_type)
SETTINGS index_granularity = 8192;

其中,ON CLUSTER語法表示分佈式DDL,即執行一次就可在集羣所有實例上創建同樣的本地表。集羣標識符{cluster}、分片標識符{shard}和副本標識符{replica}來自之前提到過的複製表宏配置,即config.xml中<macros>一節的內容,配合ON CLUSTER語法一同使用,可以避免建表時在每個實例上反覆修改這些值。

ReplicatedMergeTree引擎族接收兩個參數:

  • ZK中該表相關數據的存儲路徑,ClickHouse官方建議規範化,如上面的格式/clickhouse/tables/{shard}/[database_name]/[table_name]
  • 副本名稱,一般用{replica}即可。

支持複製表的引擎都是ReplicatedMergeTree引擎族, 具體可以查看官網:

Data Replication

作者:張永清 來源於博客園https://www.cnblogs.com/laoqing/p/15954171.html

ReplicatedMergeTree引擎族接收兩個參數:

  • ZK中該表相關數據的存儲路徑, ClickHouse官方建議規範化, 例如: /clickhouse/tables/{shard}/[database_name]/[table_name].
  • 副本名稱, 一般用{replica}即可.

ReplicatedMergeTree引擎族非常依賴於zookeeper, 它在zookeeper中存儲了大量的數據:

表結構信息、元數據、操作日誌、副本狀態、數據塊校驗值、數據part merge過程中的選主信息...

同時, zookeeper又在複製表急之下扮演了三種角色:

元數據存儲、日誌框架、分佈式協調服務

可以說當使用了ReplicatedMergeTree時, zookeeper壓力特別重, 一定要保證zookeeper集羣的高可用和資源.

3.1. 數據同步的流程

ReplicatedMergeTree引擎族在ZK中存儲大量數據,包括且不限於表結構信息、元數據、操作日誌、副本狀態、數據塊校驗值、數據part merge過程中的選主信息等等。可見,ZK在複製表機制下扮演了元數據存儲、日誌框架、分佈式協調服務三重角色,任務很重,所以需要額外保證ZK集羣的可用性以及資源(尤其是硬盤資源)。
下圖大致示出複製表執行插入操作時的流程(internal_replication配置項爲true)。即先寫入一個副本,再通過config.xml中配置的interserver HTTP port端口(默認是9009)將數據複製到其他實例上去,同時更新ZK集羣上記錄的信息。

數據同步

  1. 寫入到一個節點
  2. 通過interserver HTTP port端口同步到其他實例上
  3. 更新zookeeper集羣記錄的信息

3.2. 重度依賴Zookeeper導致的問題

ck的replicatedMergeTree引擎方案有太多的信息存儲在zk上, 當數據量增大, ck節點數增多, 會導致服務非常不穩定, 目前我們的ck集羣規模還小, 這個問題還不嚴重, 但依舊會出現很多和zk有關的問題(詳見遇到的問題).

實際上 ClickHouse 把 ZK 當成了三種服務的結合, 而不僅把它當作一個 Coordinate service(協調服務), 可能這也是大家使用 ZK 的常用用法。ClickHouse 還會把它當作 Log Service(日誌服務),很多行爲日誌等數字的信息也會存在 ZK 上;還會作爲表的 catalog service(元數據存儲),像表的一些 schema 信息也會在 ZK 上做校驗,這就會導致 ZK 上接入的數量與數據總量會成線性關係。

作者:張永清 來源於博客園https://www.cnblogs.com/laoqing/p/15954171.html

目前針對這個問題, clickhouse社區提出了一個mini checksum方案, 但是這並沒有徹底解決 znode 與數據量成線性關係的問題. 目前看到比較好的方案是字節的:

我們就基於 MergeTree 存儲引擎開發了一套自己的高可用方案。我們的想法很簡單,就是把更多 ZK 上的信息卸載下來,ZK 只作爲 coordinate Service。只讓它做三件簡單的事情:行爲日誌的 Sequence Number 分配、Block ID 的分配和數據的元信息,這樣就能保證數據和行爲在全局內是唯一的。

關於節點,它維護自身的數據信息和行爲日誌信息,Log 和數據的信息在一個 shard 內部的副本之間,通過 Gossip 協議進行交互。我們保留了原生的 multi-master 寫入特性,這樣多個副本都是可以寫的,好處就是能夠簡化數據導入。圖 6 是一個簡單的框架圖。

以這個圖爲例,如果往 Replica 1 上寫,它會從 ZK 上獲得一個 ID,就是 Log ID,然後把這些行爲和 Log Push 到集羣內部 shard 內部活着的副本上去,然後當其他副本收到這些信息之後,它會主動去 Pull 數據,實現數據的最終一致性。我們現在所有集羣加起來 znode 數不超過三百萬,服務的高可用基本上得到了保障,壓力也不會隨着數據增加而增加。

字節跳動 Insert

4、Distributed Table & Distributed Engine

ClickHouse分佈式表的本質並不是一張表,而是一些本地物理表(分片)的分佈式視圖,本身並不存儲數據。

支持分佈式表的引擎是Distributed,建表DDL語句示例如下,_all只是分佈式表名比較通用的後綴而已。

CREATE TABLE IF NOT EXISTS test.events_all ON CLUSTER sht_ck_cluster_1
AS test.events_local
ENGINE = Distributed(sht_ck_cluster_1,test,events_local,rand());

Distributed引擎需要以下幾個參數:

  • 集羣標識符
    注意不是複製表宏中的標識符,而是<remote_servers>中指定的那個。
  • 本地表所在的數據庫名稱
  • 本地表名稱
  • (可選的)分片鍵(sharding key)
    該鍵與config.xml中配置的分片權重(weight)一同決定寫入分佈式表時的路由,即數據最終落到哪個物理表上。它可以是表中一列的原始數據(如site_id),也可以是函數調用的結果,如上面的SQL語句採用了隨機值rand()。注意該鍵要儘量保證數據均勻分佈,另外一個常用的操作是採用區分度較高的列的哈希值,如intHash64(user_id)

在分佈式表上執行查詢的流程簡圖如下所示。發出查詢後,各個實例之間會交換自己持有的分片的表數據,最終彙總到同一個實例上返回給用戶。

而在寫入時,我們有兩種選擇:一是寫分佈式表,二是寫underlying的本地表。孰優孰劣呢?

直接寫分佈式表的優點自然是可以讓ClickHouse控制數據到分片的路由,缺點就多一些:

  • 數據是先寫到一個分佈式表的實例中並緩存起來,再逐漸分發到各個分片上去,實際是雙寫了數據(寫入放大),浪費資源;
  • 數據寫入默認是異步的,短時間內可能造成不一致;
  • 目標表中會產生較多的小parts,使merge(即compaction)過程壓力增大。

相對而言,直接寫本地表是同步操作,更快,parts的大小也比較合適,但是就要求應用層額外實現sharding和路由邏輯,如輪詢或者隨機等。

 在生產環境中總是推薦寫本地表、讀分佈式表,採用了隨機路由,部分代碼如下

 private Request buildRequest(ClickhouseRequestBlank requestBlank) {
        String resultCSV = String.join(" , ", requestBlank.getValues());
        String query = String.format("INSERT INTO %s VALUES %s", requestBlank.getTargetTable(), resultCSV);
        String host = sinkSettings.getClickhouseClusterSettings().getRandomHostUrl();

        BoundRequestBuilder builder = asyncHttpClient
                .preparePost(host)
                .setHeader(HttpHeaders.Names.CONTENT_TYPE, "text/plain; charset=utf-8")
                .setBody(query);

        if (sinkSettings.getClickhouseClusterSettings().isAuthorizationRequired()) {
            builder.setHeader(HttpHeaders.Names.AUTHORIZATION, "Basic " + sinkSettings.getClickhouseClusterSettings().getCredentials());
        }

        return builder.build();
    }

    public String getRandomHostUrl() {
        currentHostId = ThreadLocalRandom.current().nextInt(hostsWithPorts.size());
        return hostsWithPorts.get(currentHostId);
    }

完整代碼參考:https://github.com/ivi-ru/flink-clickhouse-sink

作者:張永清 來源於博客園https://www.cnblogs.com/laoqing/p/15954171.html

二、ClickHouse實現時序數據管理和挖掘

ClickHouse在時序數據庫上的能力體現:

(1)時間:時間是必不可少的,按照時間分區能夠大幅降低數據掃描範圍;

(2)過濾:對條件的過濾一般基於某些列,對於列式存儲來說優勢明顯;

(3)降採樣:對於時序來說非常重要的功能,可以通過聚合實現,CK自帶時間各個粒度的時間轉換函數以及強大的聚合能力,可以滿足要求;

(4)分析挖掘:可以開發擴展的函數來支持。

另外CK作爲一個大數據系統,也滿足以下基礎要求:

(1)高吞吐寫入;

(2)海量數據存儲:冷熱備份,TTL;

(3)高效實時的查詢;

(4)高可用;

(5)可擴展性:可以實現自定義開發;

(6)易於使用:提供了JDBC和HTTP接口;

(7)易於維護:數據遷移方便,恢復容易,後續可能會將依賴的ZK去掉,內置分佈式功能。

時序查詢場景會有很多聚合查詢,對於特定場景,如果使用的非常頻繁且數據量非常大,我們可以採用物化視圖進行預聚合,然後查詢物化視圖

未完待續,敬請期待.....

參考# 

Clickhouse Overview

ClickHouse複製表、分佈式表機制與使用方法

最快開源 OLAP 引擎! ClickHouse 在頭條的技術演進

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章