導語
ClickHouse
叢集資料在寫入時,雖然可以通過Distributed
引擎的sharding_key
指定策略,從而保證一定程度的資料均衡,但這並不是最終解決方案。
比如rand()
均衡策略雖然可以保證資料的相對均衡,但是可能會破壞資料的內在業務邏輯。舉個簡單的例子,我們想要將kafka
的資料寫入clickhouse
叢集,如果採用rand()
的策略,則可能將同一個partition
的資料拆分到clickhouse
叢集不同的shard
中,為後續的資料分析等造成了一定的麻煩。
雖然有類似clickhouse-sinker之類的資料匯入工具,可以做到資料匯入時的均衡,但是一旦叢集擴充套件了節點,仍然無法將存量資料均衡到新增加的節點中去。這樣就造成了存量節點的資料仍然很多,新增節點的資料相對較少,並不能起到很好的負載均衡的作用。
資料均衡方案探討
我們在討論資料均衡方案的時候,首先需要明確兩個前提:
- 針對
clickhouse
叢集,而不是單點 - 針對
MergeTree
家族的引擎資料(其他引擎的資料表由於無法通過分散式表去讀寫,因此也不具備資料均衡的意義)
我們知道,clickhouse
儲存資料是完完全全的列式儲存,這也就意味著,在同一個partition
下,資料很難再一條條的進行拆分(雖然可以做到,但比較麻煩)。因此,資料均衡最科學的方案是以partition
為單位,整個partition
進行搬遷。這也就意味著,分割槽的粒度越小,最終的資料越接近均衡。
另一個我們要思考的問題就是,如果其中某一個分割槽我們一直在寫入資料,我們是無法獲取該分割槽的實際大小的(因為一直在變化)。那麼,如果該分割槽資料也引數資料均衡的話,可能參與均衡的partition
並不是一個完整的分割槽,就會導致分割槽資料被拆散,從而造成不可預知的問題。所以,我們希望最新的一個分割槽,不參與資料均衡的運算。
如何能獲取到最新的分割槽呢?其實可以通過SQL
查詢到:
SELECT argMax(partition, modification_time) FROM system.parts WHERE database='?' AND table='?'
以上SQL
查詢出來的,就是指定的資料庫表最新的分割槽。將這個分割槽排除在外,那麼剩下的分割槽就是都可以參與資料均衡的分割槽了。
另一個核心問題是,如何將partition
資料在不同節點之間進行移動?我們很容易想到 attach
和detach
,但attach
和detach
的前提是,我們必須要設定配置檔案中當前操作使用者allow_drop_detached
標誌為1
。對於帶副本的叢集,我們總能通過zookeeper
的路徑非常方便地將分割槽資料在不同節點間fetch
過來。
-- 在目標節點執行
ALTER TABLE {{.tbname}} FETCH PARTITION '{{.partition}}' FROM '{{.zoopath}}'
ALTER TABLE {{.tbname}} ATTACH PARTITION '{{.partition}}'
-- 在原始節點執行
ALTER TABLE {{.tbname}} DROP PARTITION '{{.partition}}'
但是對於非副本模式的叢集則沒那麼簡單了。因為我們無法知道zoopath
,所以不能通過fetch
的方式獲取到資料,因此,只能使用物理的方式將資料進行傳輸(比如scp
, rsync
等)到指定節點上。考慮到傳輸效率,這裡我們使用rsync
的方式。
-- 原始節點執行
ALTER TABLE {{.tbname}} DETACH PARTITION '{{.partition}}'
# 原始節點執行
rsync -e "ssh -o StrictHostKeyChecking=false" -avp /{{.datapath}}/clickhouse/data/{{.database}}/{{.table}}/detached dstHost:/{{.datapath}}/clickhouse/data/{{.database}}/{{.table}}/detached
rm -fr /{{.datapath}}/clickhouse/data/{{.database}}/{{.table}}/detached
-- 目標節點執行
ALTER TABLE {{.tbname}} ATTACH PARTITION '{{.partition}}'
-- 原始節點執行
ALTER TABLE {{.tbname}} DROP DETACHED PARTITION '{{.partition}}'
但是,通過rsync
的方式需要有前提,那就是首先必須在各個節點上已經安裝過rsync
工具了,如果沒有安裝,可通過下面的命令安裝:
yum install -y rsync
其次,需要配置各節點之間的互信(主要是moveout
的節點到movein
節點之間的互信,但其實我們並不知道資料在節點間數如何移動的,因此最好全部配置)。
以上問題解決後,那麼就剩下最核心的一個問題了。資料如何均衡?
這裡需要說明的是,由於是整個partition
的移動,因此,無法做到絕對的均衡,而是隻能做到相對的資料均衡。partition
的粒度越小,均衡越精確。
一種比較科學的方案是,將各個節點的分割槽資料按大小排列之後,將最大的節點資料移動到最小的節點中去,次大的節點移到次小的節點,以此類推,不斷向中間靠攏,直到滿足某一個閾值,則不再移動。
這一段的程式碼實現我們已經通過ckman
專案開源出來了,如果感興趣的朋友可以通過下面的連結閱讀原始碼:ckman:rebalancer。
因此,不難發現,資料均衡的過程中,分割槽資料由於可能已經被
detach
,但是還沒來得及在新的節點上attach
,這時候去做查詢,可能存在一定機率的不準確。所以,在做資料均衡的過程中,最好不要有查詢操作。
插入操作反而不受影響,因為我們已經排除了最新的分割槽不參與均衡運算。
ckman如何實現資料均衡
ckman作為一款管理和監控ClickHouse
叢集的視覺化工具,天然整合了資料均衡的功能。只需要點選叢集管理頁面的"均衡叢集"按鈕,即可實現資料均衡的操作。
與此同時,ckman
還提供了命令列方式的資料均衡工具rebalancer
, 其引數如下:
-ch-data-dir
clickhouse
叢集資料目錄
-ch-hosts
- 節點列表(每個
shard
只需列出一個,如果shard
有多個副本,無需全部列出)
-ch-password
clickhouse
使用者密碼
-ch-port
clickhouse
的TCP
埠,預設9000
-ch-user
clickhouse
的使用者,介面操作時,使用default
使用者
-os-password
- 節點的
ssh
登入密碼(非副本模式時需要)
-os-port
- 節點的
ssh
埠,預設22
(非副本模式時需要)
-os-user
- 節點的
ssh
使用者(非副本模式時需要)
如:
rebalancer -ch-data-dir=/var/lib/ --ch-hosts=192.168.0.1,192.168.0.2,192.168.0.3 --ch-password=123123 --ch-port=9000 --ch-user=default --os-password=123456 --os-port=22 --os-user=root
實操案例
我們在ckman
中準備了一個名為eoi
的叢集,該叢集有三個節點,分別為192.168.21.73
,192.168.21.74
,192.168.21.75
,叢集為非副本模式。
我們從官方文件給出的資料集中匯入如下資料:https://clickhouse.com/docs/e...
該資料是從2019年1月到2021年5月,共計30個月的航空資料,為了更直觀地展示資料均衡,本文將官方的建表語句做了微調,按照月進行分割槽,並且在叢集各個節點都建立表:
CREATE TABLE opensky ON CLUSTER eoi
(
callsign String,
number String,
icao24 String,
registration String,
typecode String,
origin String,
destination String,
firstseen DateTime,
lastseen DateTime,
day DateTime,
latitude_1 Float64,
longitude_1 Float64,
altitude_1 Float64,
latitude_2 Float64,
longitude_2 Float64,
altitude_2 Float64
) ENGINE = MergeTree
PARTITION BY toYYYYMM(day)
ORDER BY (origin, destination, callsign);
並建立分散式表:
CREATE TABLE dist_opensky ON CLUSTER eoi AS opensky
ENGINE = Distributed(eoi, default, opensky, rand())
下載資料:
wget -O- https://zenodo.org/record/5092942 | grep -oP 'https://zenodo.org/record/5092942/files/flightlist_\d+_\d+\.csv\.gz' | xargs wget
資料下載完成大約4.3G
。
使用下面的指令碼將資料匯入到其中一個節點:
for file in flightlist_*.csv.gz; do gzip -c -d "$file" | clickhouse-client --password 123123 --date_time_input_format best_effort --query "INSERT INTO opensky FORMAT CSVWithNames"; done
匯入完成後,分別檢視各節點資料如下:
-- 總資料
master :) select count() from dist_opensky;
SELECT count()
FROM dist_opensky
Query id: b7bf794b-086b-4986-b616-aef1d40963e3
┌──count()─┐
│ 66010819 │
└──────────┘
1 rows in set. Elapsed: 0.024 sec.
-- node 21.73
master :) select count() from opensky;
SELECT count()
FROM opensky
Query id: 5339e93c-b2ed-4085-9f58-da099a641f8f
┌──count()─┐
│ 66010819 │
└──────────┘
1 rows in set. Elapsed: 0.002 sec.
-- node 21.74
worker-1 :) select count() from opensky;
SELECT count()
FROM opensky
Query id: 60155715-064e-4c4a-9103-4fd6bf9b7667
┌─count()─┐
│ 0 │
└─────────┘
1 rows in set. Elapsed: 0.002 sec.
-- node 21.75
worker-2 :) select count() from opensky;
SELECT count()
FROM opensky
Query id: d04f42df-d1a4-4d90-ad47-f944b7a32a3d
┌─count()─┐
│ 0 │
└─────────┘
1 rows in set. Elapsed: 0.002 sec.
從以上資訊,我們可以知道,原始資料6600
萬條全部在21.73
這個節點上,另外兩個節點21.74
和21.75
沒有資料。
從ckman
介面可以看到如下資訊:
然後點選資料均衡,等待一段時間後,會看到介面提示資料均衡成功,再次檢視各節點資料:
-- 總資料
master :) select count() from dist_opensky;
SELECT count()
FROM dist_opensky
Query id: bc4d27a9-12bf-4993-b37c-9f332ed958c9
┌──count()─┐
│ 66010819 │
└──────────┘
1 rows in set. Elapsed: 0.006 sec.
-- node 21.73
master :) select count() from opensky;
SELECT count()
FROM opensky
Query id: a4da9246-190c-4663-8091-d09b2a9a2ea3
┌──count()─┐
│ 24304792 │
└──────────┘
1 rows in set. Elapsed: 0.002 sec.
-- node 21.74
worker-1 :) select count() from opensky;
SELECT count()
FROM opensky
Query id: 5f6a8c89-c21a-4ae1-b69f-2755246ca5d7
┌──count()─┐
│ 20529143 │
└──────────┘
1 rows in set. Elapsed: 0.002 sec.
-- node 21.75
worker-2 :) select count() from opensky;
SELECT count()
FROM opensky
Query id: 569d7c63-5279-48ad-a296-013dc1df6756
┌──count()─┐
│ 21176884 │
└──────────┘
1 rows in set. Elapsed: 0.002 sec.
通過上述操作,簡單演示了資料均衡在ckman
中的實現,原始資料6600
萬條全部在node1
,通過均衡之後,其中node1
資料為2400
萬條,node2
位2000
萬條,node3
位2100
萬條,實現了大致的資料均衡。
結語
雖然我們可以通過ckman
之類的工具可以實現資料的大致均衡,大大改善了操作的便利性,但資料均衡本身就是一個非常複雜的命題,一旦涉及到儲存策略(如資料儲存在遠端HDFS
上),那麼又會增加資料均衡的複雜性,這些都是ckman
目前所不能支援的操作(遠端資料做資料均衡沒有意義,但是可以均衡其後設資料,這樣可以在查詢時充分利用各節點的CPU
效能)。因此,資料均衡想要做得科學而精確,仍然需要更多的努力。