ClickHouse - ClickVisual 構建日誌平臺

越來越多的互聯網公司開始嘗試 ClickHouse 存儲日誌,比如映客、快手、攜程、唯品會、石墨文檔,但是 ClickHouse 存儲日誌缺少對應的可視化方案,石墨文檔開源了 ClickVisual 用於解決這個問題。筆者初步嘗試了一下 ClickVisual,一點小小的實踐經驗,與各位分享。

簡介

ClickVisual 官方宣揚的核心功能是:輕量級日誌查詢、分析、報警可視化平臺。報警這塊有更好的方案,我這裏主要嘗試一下接入日誌、存儲、查詢日誌的整個流程。ClickVisual 的相關資料地址:

  • 文檔:https://clickvisual.net/

  • 代碼:https://github.com/clickvisual/clickvisual

架構

ClickVisual 只是一個 web 端,查詢日誌並展示,並不參與日誌流的處理,日誌流主要是通過 LogAgent、Kafka、ClickHouse 來協同處理,ClickVisual 主要是對 ClickHouse 的表結構做一些調整,來控制 ClickHouse 對日誌的處理過程。整個數據流如下:

ClickVisual 不關心採集,用什麼 agent 都行,只是對進入 Kafka 中的日誌格式有要求,要求日誌中包括時間字段和日誌原文字段。ClickVisual 官網有 fluentbit、ilogtail、loggie 的相關文檔,fluentbit 的文檔最爲詳細,看來石墨的朋友內部主要是使用 fluentbit 做採集器。後面我會使用 categraf 做數據採集,categraf 中的日誌採集邏輯是 fork 自 datadog-agent,比較穩定可靠。不過 categraf 沒有日誌清洗能力,如果想對日誌格式做清洗,需要引入 logstash 或者 vector。我這裏重點想嘗試 ClickVisual,所以採集側就簡單搞,使用 categraf 採集 json 格式的日誌,然後直接進入 Kafka。

  • ilogtail: https://ilogtail.gitbook.io/ilogtail-docs/about/readme

  • loggie: https://loggie-io.github.io/docs/

  • categraf: https://github.com/flashcatcloud/categraf

通常,不同的 log stream 進入不同的 Kafka topic,每個 Kafka topic 對應 ClickVisual 裏的一個日誌庫,日誌庫通常包含兩個 ClickHouse Table + 一個物化視圖,一個 Table 是 Kafka 引擎類型的 Table,用於消費 Kafka 中的原始日誌,然後通過物化視圖流式處理原始日誌,做一些數據 ETL 之後寫入日誌結果 Table。比如日誌原文可能是 json 格式,通過物化視圖把 json 日誌原文裏的某個字段提取出來,作爲日誌結果 Table 的一個一等公民字段,可以提升查詢篩選性能。

安裝

日誌的處理流比指標要複雜,涉及的組件比較多,這裏我會安裝 Kafka 用於日誌傳輸,Kafka 依賴 Zookeeper,Kafka 的可視化使用 Kowl,日誌採集使用 Categraf,日誌存儲使用 ClickHouse,日誌可視化使用 ClickVisual,ClickVisual 依賴 MySQL 和 Redis,所以,總共需要安裝 8 個組件,我會盡可能使用二進制安裝,方便摸清箇中原理。

Kafka

Kafka 最新的版本是 3.6.0,直接下載最新版本安裝,下載的包裏包含 Zookeeper,所以 Zookeeper 不需要單獨下載包。當然,Kafka、Zookeeper 都是依賴 JDK,JDK 請列位自行安裝和配置。

  • Kafka 下載地址:https://downloads.apache.org/kafka/3.6.0/kafka_2.13-3.6.0.tgz

下載之後解壓縮,修改一下 config/zookeeper.properties,調整 dataDir 配置,不要放 /tmp 目錄。然後啓動 Zookeeper:

./bin/zookeeper-server-start.sh -daemon config/zookeeper.properties

然後修改 Kafka 的配置:config/server.properties,修改 log.dirs,也是更換一下目錄,不要使用 /tmp。然後啓動 Kafka:

nohup bin/kafka-server-start.sh config/server.properties &> kafka.stdout &

請自行檢查 Zookeeper 和 Kafka 是否啓動成功,可以使用 jps 命令查看進程,也可以使用 netstat -tunlp 查看端口。

[root@VM-0-33-debian:~/tarball/kafka_2.13-3.6.0# jps

1148293 Jps

606735 QuorumPeerMain

608066 Kafka

Kafka 數據查看使用 Kowl,不過 Kowl 沒有找到二進制,官網建議使用容器,但是我的 Kafka 和 Zookeeper 都沒有用容器,所以 Kowl 使用容器安裝,但是要使用 host network,命令如下:

docker run --network=host -d -e KAFKA_BROKERS=localhost:9092 quay.io/cloudhut/kowl:master

kowl 如果啓動成功,會監聽在 8080 端口,頁面長這樣:

ClickHouse

ClickHouse 的安裝比較簡單,官方提供安裝腳本,直接下載執行即可,curl 命令結束之後會拿到一個 clickhouse 二進制,然後執行 ./clickhouse install 就可以安裝了,安裝的時候會提示設置密碼,我這裏測試,設置爲 1234。

curl https://clickhouse.com/ | sh

./clickhouse install

clickhouse start

ClickVisual

ClickVisual 的安裝依賴 MySQL 和 Redis,這倆太常見了大家自行搞定。ClickVisual 我也直接體驗最新版本,v1.0.0-rc9,下載之後解壓縮,看一下 help 信息:

mkdir clickvisual && cd clickvisual

wget https://github.com/clickvisual/clickvisual/releases/download/v1.0.0-rc9/clickvisual-v1.0.0-rc9-linux-amd64.tar.gz

tar zxvf clickvisual-v1.0.0-rc9-linux-amd64.tar.gz



[root@VM-0-33-debian:~/tarball/clickvisual# ./clickvisual --help

Usage:

  clickvisual [command]



Available Commands:

  agent       啓動 clickvisual agent 服務端

  command     啓動 clickvisual 命令行

  completion  Generate the autocompletion script for the specified shell

  help        Help about any command

  server      啓動 clickvisual server 服務端



Flags:

  -c, --config string   指定配置文件,默認 config/default.toml (default "config/default.toml")

  -h, --help            help for clickvisual



Use "clickvisual [command] --help" for more information about a command.

從命令中可以看出,啓動 ClickVisual 應該是使用 server 參數,通過 -c 傳入配置文件,默認配置文件是 config/default.toml,我們要調整這個配置文件中的 MySQL 和 Redis 的認證信息,我的環境配置如下:

[redis]

debug = true

addr = "127.0.0.1:6379"

writeTimeout = "3s"

password = ""



[mysql]

debug = true

# database DSN

dsn = "root:1234@tcp(127.0.0.1:3307)/clickvisual?charset=utf8mb4&collation=utf8mb4_general_ci&parseTime=True&loc=Local&readTimeout=1s&timeout=1s&writeTimeout=3s"

# log level

level = "debug"

# maximum number of connections in the idle connection pool for database

maxIdleConns = 5

# maximum number of open connections for database

maxOpenConns = 10

# maximum amount of time a connection

connMaxLifetime = "300s"

OK,啓動 ClickVisual:

nohup ./clickvisual server &>stdout.log &

ClickVisual 啓動之後監聽在 19011 端口,可以檢查這個端口是否存活:

ss -tlnp|grep 19011

ClickVisual 解壓縮之後,裏邊有個 sql 腳本,位於 scripts/migration/database.sql,需要把這個 sql 導入 MySQL:

mysql -uroot -p < scripts/migration/database.sql

之後就可以瀏覽器訪問 19011 了,ClickVisual 會提示你進行表結構初始化,初始賬號密碼是 clickvisual/clickvisual

Categraf

最後一個要安裝的組件是日誌採集器,我這裏使用 categraf,下載地址如下:

  • 下載:https://flashcat.cloud/download/categraf/

  • 代碼:https://github.com/flashcatcloud/categraf

這裏選擇 v0.3.38 版本,下載解壓縮,重點需要 categraf 二進制以及 conf 目錄下的 logs.toml,其他所有 input. 打頭的配置都是指標採集插件,全部刪除,另外也刪除 conf 目錄下的 traces.yaml,搞的乾淨點。然後修改兩個配置文件。

1、修改 config.toml,關閉 heartbeat:

[heartbeat]

enable = false

Categraf 和 Nightingale 配合工作,主要處理指標場景,我們現在不測試指標,只是測試日誌採集,所以不需要 Nightingale,關閉 Heartbeat。

2、修改 logs.toml,要給出要採集的日誌路徑以及要發往的 Kafka 地址。

[logs]

api_key = "x"

enable = true

send_to = "127.0.0.1:9092"

send_type = "kafka"

topic = "categraf"

use_compress = false

send_with_tls = false

batch_wait = 5

run_path = "/opt/categraf/run"

open_files_limit = 100

scan_period = 10

frame_size = 9000

collect_container_all = false



[[logs.items]]

type = "file"

path = "/root/works/catpaw/stdout.log"

source = "app"

service = "catpaw"

topic = "catpaw"

accuracy = "s"

其中 send_to 字段是配置了 Kafka 的地址,send_type 配置爲 kafka,collect_container_all 設置爲 false 避免一些非 K8s 環境下的報錯日誌,[[logs.items]] 是雙中括號,在 toml 裏表示數組,即可以配置多個 [[logs.items]] 段,這裏我採集了 catpaw 的 stdout.log,source、service 都是標籤,topic 是日誌發往 Kafka 的 Topic。

stdout.log 的日誌內容,給大家看一行例子:

{"level":"error","ts":"2023-11-01T16:57:15+08:00","caller":"http/http.go:236","msg":"failed to send http request","error":"Get \"http://a.cn\": dial tcp: lookup a.cn on 183.60.83.19:53: no such host","plugin":"http","target":"http://a.cn"}

這是一個 json 格式的日誌,不需要額外的數據清洗了,直接採集即可。推薦大家寫的程序都打印 json 格式的日誌,對於日誌採集非常方便。

啓動 categraf:

nohup ./categraf &> categraf.log &

通過 ps 查看 categraf 進程是否啓動成功,查看 categraf.log 是否有異常日誌,如果一切正常,咱們就可以去 Kowl 查看日誌了。

查看 Kafka 中的日誌

其中 message 字段是日誌原文,timestamp 是採集日誌時的時間戳,有這倆字段,ClickVisual 就可以處理了。其他字段是 categraf 自動添加的,比如 source、service、topic,即便沒有這些額外的字段,也不影響 ClickVisual 的使用。

在 ClickVisual 配置日誌庫

終於到了最後一步了,到 ClickVisual 配置日誌庫。首先去系統管理裏新增 ClickHouse 實例:

我之前創建過,現在點擊編輯給大家看一下內容:

進入日誌菜單,可以看到剛纔添加的 ClickHouse 實例,右鍵添加數據庫(一個 ClickHouse 實例裏可以創建多個數據庫,跟 MySQL 一樣,我這裏直接取名 db01,你隨意 ):

之後在 db01 上右鍵,新增日誌庫。數據表通常填成 topic 名字就行,其實就是 log stream 的名字。source 字段很關鍵,ClickVisual 會根據 source 來提取日誌,Kowl 的截圖中大家看到了,我的日誌裏有好幾個字段:message、status、timestamp、agent_hostname、fcservice、fcsource 等,但是我在 source 裏故意少填幾個字段,填入如下內容:

{

    "message": "x",

    "timestamp": 1698829486,

    "status": "y"

}

source 裏填 json 結構,不用填真實內容,只要填一個假數據結構,ClickVisual 能推斷出各個字段的類型就行,我呢,就填了上面三個字段。點擊轉換,選擇時間字段和日誌詳情字段:

確定之後,ClickVisual 自動填充了相關字段,然後,我們補齊剩下的 Kafka 信息即可:

確定之後,稍等幾秒鐘,就可以看到數據了,我的截圖如下:

其實,剛開始日誌字段下面是空的,右側日誌詳情裏的 level 字段也沒有背景色。基礎字段裏有 status,顯然,因爲配置日誌庫的時候,source 樣例只給了 message、timestamp、status 三個字段,所以,ClickHouse 只拿到一個基本字段 status,如果當時要是把 fcservice、fcsource 也作爲 source 樣例寫上,基礎字段裏大概率就會有了。

查看 ClickHouse 中的庫表

clickhouse client 進入 ClickHouse 客戶端,可以看到相關庫表:

localhost.localdomain :) use db01



USE db01



Query id: a96ccd16-990c-4c8a-9d07-7bba0d0c4425



Ok.



0 rows in set. Elapsed: 0.001 sec.



localhost.localdomain :) show tables;



SHOW TABLES



Query id: 3b0849f7-cd05-481e-b786-5c8c1870caaf



┌─name────────────────────┐

│ catpaw                  │

│ catpaw_stream           │

│ catpaw_view             │

│ qinxiaohuisyslog        │

│ qinxiaohuisyslog_stream │

│ qinxiaohuisyslog_view   │

└─────────────────────────┘



6 rows in set. Elapsed: 0.001 sec.

catpaw 相關的三個表就是我剛纔一通操作產生的,qinxiaohuisyslog 相關的三個表不用關注,那是之前測試的時候生成的。看一下 stream 表的表結構:

localhost.localdomain :) show create table catpaw_stream\G

SHOW CREATE TABLE catpaw_stream

Query id: 1c75d947-d122-48cf-ad0e-e4f4049795b5

Row 1:
──────
statement: CREATE TABLE db01.catpaw_stream
(
    `status` String,
    `timestamp` Float64,
    `message` String CODEC(ZSTD(1))
)
ENGINE = Kafka
SETTINGS kafka_broker_list = '127.0.0.1:9092', kafka_topic_list = 'catpaw', kafka_group_name = 'db01_catpaw', kafka_format = 'JSONEachRow', kafka_num_consumers = 1, kafka_skip_broken_messages = 0

1 row in set. Elapsed: 0.001 sec.

這是一個引擎類型爲 Kafka 的 Table,再看一下 catpaw_view:

localhost.localdomain :) show create table catpaw_view\G

SHOW CREATE TABLE catpaw_view

Query id: 99c22d6a-a617-43fc-9963-3575778b0623

Row 1:
──────
statement: CREATE MATERIALIZED VIEW db01.catpaw_view TO db01.catpaw
(
    `status` String,
    `_time_second_` DateTime,
    `_time_nanosecond_` DateTime64(9),
    `_raw_log_` String
) AS
SELECT
    status,
    toDateTime(toInt64(timestamp)) AS _time_second_,
    fromUnixTimestamp64Nano(toInt64(timestamp * 1000000000)) AS _time_nanosecond_,
    message AS _raw_log_
FROM db01.catpaw_stream
WHERE 1 = 1

1 row in set. Elapsed: 0.001 sec.

這是一個 ClickHouse 物化視圖,查詢 stream 表的數據,塞入日誌結果表 catpaw,我們看一下日誌結果表 catpaw 的表結構:

localhost.localdomain :) show create table catpaw\G

SHOW CREATE TABLE catpaw

Query id: d06e11b9-4b5a-4594-97af-877435b93238

Row 1:
──────
statement: CREATE TABLE db01.catpaw
(
    `status` String,
    `_time_second_` DateTime,
    `_time_nanosecond_` DateTime64(9),
    `_raw_log_` String CODEC(ZSTD(1))
    INDEX idx_raw_log _raw_log_ TYPE tokenbf_v1(30720, 2, 0) GRANULARITY 1
)
ENGINE = MergeTree
PARTITION BY toYYYYMMDD(_time_second_)
ORDER BY _time_second_
TTL toDateTime(_time_second_) + toIntervalDay(1)
SETTINGS index_granularity = 8192

1 row in set. Elapsed: 0.001 sec.

如果根據 status 字段來篩選,速度是比較快的,但是如果想根據 _raw_log_ 裏的信息來篩選,比如根據 level 字段來篩選,level 是日誌原文 json 裏的一個字段,不是一等公民字段,速度就慢了,ClickVisual 官方建議,這種情況,應該把日誌原文裏的過濾字段單獨出來作爲一個字段,點擊日誌字段右側的小齒輪:

可以把日誌原文那個 json 裏的 level 字段單獨提取出來,配置如下

如上操作之後,重新查看 catpaw 和 catpaw_view 的表結構:

localhost.localdomain :) show create table catpaw\G

SHOW CREATE TABLE catpaw

Query id: d06e11b9-4b5a-4594-97af-877435b93238

Row 1:
──────
statement: CREATE TABLE db01.catpaw
(
    `status` String,
    `_time_second_` DateTime,
    `_time_nanosecond_` DateTime64(9),
    `_raw_log_` String CODEC(ZSTD(1)),
    `level` Nullable(String),
    INDEX idx_raw_log _raw_log_ TYPE tokenbf_v1(30720, 2, 0) GRANULARITY 1
)
ENGINE = MergeTree
PARTITION BY toYYYYMMDD(_time_second_)
ORDER BY _time_second_
TTL toDateTime(_time_second_) + toIntervalDay(1)
SETTINGS index_granularity = 8192

1 row in set. Elapsed: 0.001 sec.

localhost.localdomain :) show create table catpaw_view\G

SHOW CREATE TABLE catpaw_view

Query id: ebfebeb7-d8f5-4427-b3ba-33c0d07d24b1

Row 1:
──────
statement: CREATE MATERIALIZED VIEW db01.catpaw_view TO db01.catpaw
(
    `status` String,
    `_time_second_` DateTime,
    `_time_nanosecond_` DateTime64(9),
    `_raw_log_` String,
    `level` Nullable(String)
) AS
SELECT
    status,
    toDateTime(toInt64(timestamp)) AS _time_second_,
    fromUnixTimestamp64Nano(toInt64(timestamp * 1000000000)) AS _time_nanosecond_,
    message AS _raw_log_,
    toNullable(toString(replaceAll(JSONExtractRaw(message, 'level'), '"', ''))) AS level
FROM db01.catpaw_stream
WHERE 1 = 1

1 row in set. Elapsed: 0.001 sec.

霧化視圖 catpaw_view 裏,增加了對 level 字段的提取,日誌結果表 catpaw 裏也新增了一個 level 字段。看來 ClickVisual 是執行了一些 alter table 的語句。之後就可以這麼查了(不用像之前使用 like 語句):

總結

ClickVisual 的整體思路設計挺巧妙的,不過業界使用 ClickHouse 存儲日誌,大都是使用的雙 array 存儲動態字段。你們公司是如何做的呢?有在生產環境使用 ClickVisual 麼?感覺如何?歡迎大家留言交流。

作者:秦曉輝,Open-Falcon、Nightingale、Categraf 等開源項目創始研發人員,極客時間專欄《運維監控系統實戰筆記》作者,目前在創業,提供可觀測性產品,微信 picobyte,歡迎加好友交流,加好友請備註公司。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/xst2Qyr4zATFr3OlmIKQ6g