數倉選型必列入考慮的OLAP列式資料庫ClickHouse(中)

itxiaoshen發表於2022-05-22

實戰

案例使用

背景

ELK作為老一代日誌分析技術棧非常成熟,可以說是最為流行的大資料日誌和搜尋解決方案;主要設計元件及架構如下:

image-20220521230300714

而新一代日誌監控選型如ClickHouse、StarRocks特別是近年來對ELK地位發起較大的挑戰,不乏有許多的大公司如攜程,快手已開始把自己的日誌解決方案從 ES 遷移到了 Clickhouse,將日誌從ES遷移到ClickHouse可以節省更多的伺服器資源,總體運維成本更低,優化日誌查詢效能提升了查詢速度,特別是當使用者在緊急排障的時候,這種查詢速度的成倍提升,對使用者的使用體驗有明顯的改善,讓ClickHouse在日誌分析領域為使用者提供更大的價值。Clickhouse在大部分的查詢的效能上都明顯要優於Elastic ,在聚合場景下,Clickhouse表現異常優秀,充分發揮了列存引擎的優勢;但是個人認為ClickHouse畢竟不是ES,在某些業務場景中ES仍然不可替代,ES全文檢索的倒排索引是建立在記憶體全文檢索查詢較快但也比較耗記憶體;同樣對於ClickHouse來說也不僅處理日誌,ClickHouse還有更多的使用場景,這個在上一篇文章已經詳細介紹過了。本篇我們先從一個簡單日誌採集的場景拉開ClickHouse在日誌分析場景下的使用,以便於後續更深入學習ClickHouse的其他使用場景和技術。前面文章我們也清楚知道ClickHouse基於分片和副本實現了水平擴充套件和高可用。

  • ES叢集架構自己實現

image-20220521230414909

  • ClickHouse叢集架構依賴Zookeeper實現

image-20220521230653392

  • Elasticsearch VS Clickhouse
    • Clickhouse 在大部分的查詢的效能上都明顯要優於 Elastic。
    • 在正則查詢(Regex query)和單詞查詢(Term query)等搜尋常見的場景下,也並不遜色。
    • 在聚合場景下,Clickhouse 表現異常優秀,充分發揮了列式引擎的優勢。且在Clickhouse在沒有任何優化和沒有開啟布隆過濾器。可見 Clickhouse 確實是一款非常優秀的資料庫,可以用於某些搜尋的場景。
    • Clickhouse 在基本場景表現非常優秀,效能優於 ES。
    • Elasticsearch僅支援Json格式的資料,Elasticsearch有獨自生態和語言,學習成本相對較高。
    • Elasticsearch最擅長的主要是完全搜尋場景(where過濾後的記錄數較少),在記憶體充足執行環境下可以展現出非常出色的併發查詢能力。但是在大規模資料的分析場景下(where過濾後的記錄數較多),ClickHouse憑藉極致的列存和向量化計算會有更加出色的並發表現,並且查詢支援完備度也更好。ClickHouse的併發處理能力立足於磁碟吞吐,而Elasticsearch的併發處理能力立足於記憶體Cache,這使得兩者的成本區間有很大差異,ClickHouse更加適合低成本、大資料量的分析場景,它能夠充分利用磁碟的頻寬能力。資料匯入和儲存成本上,ClickHouse更加具有絕對的優勢。

需求簡述

滿足業務系統的業務監控需求,達到業務實時監控、實施告警、監控視覺化、高效運維的目的,涵蓋對外部系統的各種業務資料的ftp檔案上報的實現簡單業務監控機制,本篇先解決日誌的入庫,後續有時間再討論基於Clickhouse日誌分析。

總體流程

為了可用於生產設計,我們使用帶有副本機制MergeTree家族的ReplicatedMergeTree表引擎(如果單表資料量很大再考慮使用分佈表,利用shard),其中shard和replica在前面文章部署ClickHouse作為macros定義配置在每一個clickhouse-server的配置檔案裡。我們先嚐試一個從FileBeat採集日誌->Logstash(解析日誌)->Kafka->clickhouse_sinker(housepower)->ClickHouse(當然也可以採用FileBeat採集日誌->Kafka->Logstash(解析日誌)>logstash-clickhouse->ClickHouse,這樣更合理,可以用其他技術棧實現對於Kafka流式資料的日誌實時監控需求)。

  • 日誌採集採用Beats平臺的Beat工具,本篇主要先基於日誌的Filebeat採集元件,很輕量,效能也高,不影響核心服務,將日誌檔案打上標籤全量上報給Logstash;Beat工具部署在程式模組和需要採集日誌的伺服器上。也可以在Java程式直接引入logstash-logback-encoder依賴(net.logstash.logback),通過與logback整合實現程式上報日誌。

image-20220521230218808

  • Logstash較重對伺服器效能有消耗,Logstash對全量日誌進行過濾和轉換等處理,將處理後資料寫到公共叢集Kafka對應的Topic裡。Logstash在每個業務叢集可以一個或多個,根據實際情況調整。
  • 通過第三方housepower的clickhouse_sinker外掛從公共叢集Kafka不斷讀取配置Topic的資料寫入ClickHouse對應庫中的日誌表。

ClickHouse建表

由於業務日誌的資料量規模不大,這裡建立資料庫和建立帶有副本的ftp_log、import_log日誌表,後續有其他日誌可以再建立對應的表。當然如果想採集所有執行日誌,可以直接通過標準logback如(時間、執行緒、日誌級別、方法、內容資訊),並加上服務名稱、IP等輸出儲存日誌記錄的資料,通過對內容資訊like模糊查詢實現ELK中日誌全文檢索的系統維護工作。

CREATE DATABASE log_monitor ENGINE = Atomic;
use log_monitor;
# FTP上報日誌表,通過分析ftp伺服器vsftpd的日誌監控上報檔案的基本資訊
CREATE TABLE ftp_log
(
    host_name String COMMENT '主機名',
    host_ip String COMMENT '主機IP',
    service_name String COMMENT '服務名稱',
    file_type String COMMENT '檔案型別',
    file_name String COMMENT '檔名稱',
    file_size INT COMMENT '檔案大小',
    upload_time DateTime COMMENT '上傳時間',
    source_ip String COMMENT '上傳客戶端IP'
) ENGINE = ReplicatedMergeTree('/clickhouse/tables/{shard}/log_monitor/ftp_log', '{replica}')
PARTITION BY toYYYYMM(upload_time)
ORDER BY (file_type, upload_time);

# 匯入大資料平臺的日誌表
CREATE TABLE bussiness_log
(
    host_name String COMMENT '主機名',
    host_ip String COMMENT '主機IP',
    service_name String COMMENT '服務名稱',
    file_type String COMMENT '檔案型別',
    file_name String COMMENT '檔名稱',
    file_size INT COMMENT '檔案大小',
    valid_num INT COMMENT '有效記錄數',
    valid_size INT COMMENT '有效記錄大小',
    invalid_num INT COMMENT '無效記錄數',
    invalid_size INT COMMENT '有效記錄大小',
    start_time DateTime COMMENT '匯入開始時間',
    end_time DateTime COMMENT '匯入結束時間',
    time_consuming INT COMMENT '匯入耗時,單位毫秒',
    process_status Int8 COMMENT '狀態,1成功 0失敗',
    process_desc String COMMENT '說明,如匯入失敗原因'
) ENGINE = ReplicatedMergeTree('/clickhouse/tables/{shard}/log_monitor/bussiness_log', '{replica}')
PARTITION BY toYYYYMM(end_time)
ORDER BY (file_type, end_time);
clickhouse-client -m
use log_monitor;

通過clickhouse-client建立好兩張表

image-20220522010620893

image-20220522012111857

FileBeat採集日誌檔案配置

Filebeat 提供了一套預構建的模組,讓您可以快速實施和部署日誌監視解決方案,並附帶示例儀表板和資料視覺化。這些模組支援常見的日誌格式,例如Nginx,Apache2和MySQL 等,這些後續我們在ELK章節再詳細展開,本篇主要是ClickHouse為主。

# 下載filebeat,這裡使用最新8.2.0版本
wget https://artifacts.elastic.co/downloads/beats/filebeat/filebeat-8.2.0-linux-x86_64.tar.gz
# 解壓
tar -xvf filebeat-8.2.0-linux-x86_64.tar.gz
# 進入根目錄
cd filebeat-8.2.0-linux-x86_64
# 在根目錄下配置filebeat.yml

vim filebeat.yml

filebeat.inputs:
- type: filestream
  id: ftplog
  paths:
    # Filebeat處理檔案的絕對路徑,預設部署vsftpd是在/var/log/vsftpd.log
    - /home/monitor/ftplog/vsftpd*.log
  # 使用 fields 模組新增欄位
  fields:
    app_id: ftplog
    # host_ip 為欄位名稱,後面的值為 SERVER_IP 變數值,該變數為系統變數,可以先通過shell指令碼比寫入環境變數MONITOR_SERVER_IP中
    host_ip: ${MONITOR_SERVER_IP}
    service_name: vsftpd 
  # 將新增的欄位放在頂級,收集後欄位名稱顯示 host_ip。如果設定為 false,則放在子集,收集後顯示為 fields.host_ip
  fields_under_root: true
  
- type: filestream
  id: bussinesslog
  paths:
    # Filebeat處理檔案的絕對路徑
    - /home/monitor/bussinesslog/*.log
  # 使用 fields 模組新增欄位
  fields:
    app_id: bussinesslog
  # 將新增的欄位放在頂級
  fields_under_root: true 
output.logstash:
  hosts: ["192.168.12.27:5044"]
# 獲取本機IP,可以根據實際編寫獲取本機IP指令碼
export MONITOR_SERVER_IP=$(/sbin/ifconfig -a|grep inet|grep -v 127.0.0.1|grep -v inet6|awk '{print $2}'|tr -d "addr:")
# 這裡是演示直接在會話視窗前臺啟動執行filebeat,nohup ./filebeat -e -c filebeat.yml > logs/filebeat.log 2>&1 &
filebeat -e -c filebeat.yml

image-20220522012036081

建立Kafka Topic

# topic:ftp_log,進入kafka的根目錄
bin/kafka-topics.sh --create --zookeeper huawei27:2181,huawei29:2181,huawei29:2181 --replication-factor 3 --partitions 6 --topic ftp_log

# topic:bussiness_log
bin/kafka-topics.sh --create --zookeeper huawei27:2181,huawei29:2181,huawei29:2181 --replication-factor 3 --partitions 6 --topic bussiness_log

Logstash配置

# 下載Logstash,這裡使用最新8.2.0版本,最新版本JDK要求11或17,下面二進位制檔案已包含JDK因此檔案較大有336M
wget https://artifacts.elastic.co/downloads/logstash/logstash-8.2.0-linux-x86_64.tar.gz
# 解壓
tar -xvf logstash-8.2.0-linux-x86_64.tar.gz
# 進入根目錄,
cd logstash-8.2.0
# 在conf目錄下配置logstash.conf

針對grok語法表示式我們可以在Kibana視覺化頁面裡面除錯,也可以找一些grok語法線上除錯網站或者工具

ftplog日誌格式示例2022-05-22 01:29:53 [19233][ftpuser][121.12.26.103]End upload into file '///0x01+0x03a0+20220522012522001.tar.gz' (2206805 bytes)

image-20220522012926277

bussinesslog為程式輸出的日誌格式可以定義如下

2022-05-22 01:30:00.016 [Thread-31] INFO com.itxs.xxx.xxx.xxx - itxiaoshen_bussiness_monitor_detail,host01,10.10.2.100,bussiness-log-service,1,0x01+0x01a1+20220522012522001.tar.gz,2206805,900,1500000,100,700000,2022-05-22 01:28:00,2022-05-22 01:30:00,2500,1,ok

image-20220522014433073

vim conf/logstash.conf

input {
    beats {
        port => "5044"
        client_inactivity_timeout => 3000
        add_field => {"[logstash_host_ip]" => "${LOGSTASH_SERVER_IP}"}
    }
}
filter {
  if [app_id] == "ftplog" {
    grok {
      match => {
        "message" => ["%{DATA:upload_time}\s\[%{NUMBER:port}\]\[%{DATA:user_name}\]\[%{IP:source_ip}\]End upload into file \'///%{DATA:file_name}\'\s\(%{NUMBER:file_size}\s%{DATA:unit}\)"]
      }
    }
    date {
      match => [ "upload_time" , "dd/MMM/YYYY:HH:mm:ss" ]
    }
    mutate {
      convert => ["file_size", "integer"]
      add_field => { "host_name" => "%{[host][name]}" }
      copy => { "file_name" => "file_name_arr" }
    }
    mutate {
      split=>["file_name_arr","+"]
      add_field => {"file_type_arr" => "%{[file_name_arr][1]}"} 
    }
    
    grok {
      match => {
        "file_type_arr" => "(?<file_type_tmp>(?<=0x)(.{4}))"                
      }    
    }
    
    mutate {
      convert => ["source_flag", "integer"]
      add_field => { "file_type" => "f%{[file_type_tmp]}" }
    }    
  }
  
  if [app_id] == "bussinesslog" {
    grok {
      match => {
        "message" => ["%{DATA:log_time}\s\[%{DATA:thread_name}\]\s%{DATA:log_level}\s%{DATA:method_name}\s-\sitxiaoshen_bussiness_monitor_detail,%{DATA:host_name},%{IP:host_ip},%{DATA:service_name},%{DATA:file_type},%{DATA:file_name},%{NUMBER:file_size},%{NUMBER:valid_num},%{NUMBER:valid_size},%{NUMBER:invalid_num},%{NUMBER:invalid_size},%{DATA:start_time},%{DATA:end_time},%{NUMBER:time_consuming},%{NUMBER:process_status},*(?<collect_time>.*)"]
      }
    }
    mutate {
      convert => ["file_size", "integer"]
      convert => ["valid_num", "integer"]
      convert => ["valid_size", "integer"]
      convert => ["invalid_num", "integer"]
      convert => ["invalid_size", "integer"]
      convert => ["time_consuming", "integer"]
      convert => ["process_status", "integer"]
    }     
  }
  
  if "_grokparsefailure" in [tags] {
	drop {}
  }
}
output {
  if [app_id] == "ftplog" {
    kafka {
      codec => json
      topic_id => "ftp_log"
	  bootstrap_servers => "huawei27:9092,huawei28:9092,huawei29:9092"
    }  
  }
  if [app_id] == "bussinesslog" {
    kafka {
      codec => json
      topic_id => "bussiness_log"
	  bootstrap_servers => "huawei27:9092,huawei28:9092,huawei29:9092"
    } 
  }
  stdout {
	codec => json_lines
  }
}
# 獲取本機IP,可以根據實際編寫獲取本機IP指令碼
export LOGSTASH_SERVER_IP=$(/sbin/ifconfig -a|grep inet|grep -v 127.0.0.1|grep -v inet6|awk '{print $2}'|tr -d "addr:")
# 檢查配置檔案語法是否正確
bin/logstash -f config/logstash.conf --config.test_and_exit
# 這裡是演示直接在會話視窗前臺啟動執行logstash,如果通過了檔案檢查, 我們就可以執行下面這條命令指定配置檔案來執行Logstash,--config.reload.automatic可以在Logstash不重啟的情況下自動載入配置檔案,正式使用通過nohup &放在後臺執行即可
bin/logstash -f config/logstash.conf --config.reload.automatic

image-20220522020527486

clickhouse_sinker

# github上下載housepower的clickhouse_sinker,這裡使用最新2.4.0版本
wget https://github.com/housepower/clickhouse_sinker/releases/download/v2.4.0/clickhouse_sinker_2.4.0_Linux_x86_64.tar.gz
# 解壓
tar -xvf clickhouse_sinker_2.4.0_Linux_x86_64.tar.gz

Nacos上建立名稱空間monitor,id為monitord82caf3f-33ee-4bb2-a9b2-630fee68995d,建立ftp-log和bussiness-log兩個Json格式的配置檔案,所屬組為log-monitor。ftp-log配置內容如下

{
  "clickhouse": {
    "hosts": [
      [
        "192.168.5.52",
        "192.168.5.53",
        "192.168.12.27"
      ]
    ],
    "port": 9000,
    "db": "log_monitor",
    "username": "default",
    "password": "",
    "retryTimes": 0
  },
  "kafka": {
    "brokers": "huawei27:9092,huawei28:9092,huawei29:9092",
    "version": "2.5.0"
  },
  "task": {
    "name": "ftp_log",
    "topic": "ftp_log",
    "consumerGroup": "ftp_log",
    "earliest": true,
    "parser": "json",
    "autoSchema": true,
    "tableName": "ftp_log",
    "excludeColumns": [
      "day"
    ],
    "flushInterval": 5,
    "bufferSize": 50000
  },
  "logLevel": "debug"
}

image-20220522015500058

和上面相似ftp-log,bussiness-log配置內容如下:

{
  "clickhouse": {
    "hosts": [
      [
        "192.168.5.52",
        "192.168.5.53",
        "192.168.12.27"
      ]
    ],
    "port": 9000,
    "db": "log_monitor",
    "username": "default",
    "password": "",
    "retryTimes": 0
  },
  "kafka": {
    "brokers": "huawei27:9092,huawei28:9092,huawei29:9092",
    "version": "2.5.0"
  },
  "task": {
    "name": "bussiness_log",
    "topic": "bussiness_log",
    "consumerGroup": "bussiness_log",
    "earliest": true,
    "parser": "json",
    "autoSchema": true,
    "tableName": "bussiness_log",
    "excludeColumns": [
      "day"
    ],
    "flushInterval": 5,
    "bufferSize": 50000
  },
  "logLevel": "debug"
}

image-20220522015623429

# 這裡採用Nacos配置啟動方式,簡單的也可以直接指定本地配置檔案的方式,clickhouse_sinker可以部署多個實現負載均衡和高可用,正式使用通過nohup &放在後臺執行即可
./clickhouse_sinker --nacos-addr 192.168.50.95:8848 --nacos-username nacos --nacos-password nacos --nacos-namespace-id d82caf3f-33ee-4bb2-a9b2-630fee68995d --nacos-group log-monitor --nacos-dataid ftp-log
# 可以另開一個視窗啟動第二個sinker,正式使用通過nohup &放在後臺執行即可
./clickhouse_sinker --nacos-addr 192.168.50.95:8848 --nacos-username nacos --nacos-password nacos --nacos-namespace-id d82caf3f-33ee-4bb2-a9b2-630fee68995d --nacos-group log-monitor --nacos-dataid bussiness-log

啟動後日志輸出

日誌測試

這裡我們簡單模擬寫日誌檔案方式,後續可以通過實際業務流程進行測試。在/home/monitor/ftplog寫入日誌

image-20220522020656746

Logstash輸出日誌如下

image-20220522020740199

檢視ClickHouse的ftp_log表資料已成功寫入

image-20220522020829262

在/home/monitor/ftplog寫入日誌

image-20220522021526122

Logstash輸出日誌如下

image-20220522021509587

檢視ClickHouse的ftp_log表資料已成功寫入

image-20220522021443698

至此,一個完整流程完畢

相關文章