人类或外星技术的 ClickHouse 数据库

MKB 信息技术局远程服务渠道能力中心负责人 Aleksey Lizunov

人类或外星技术的 ClickHouse 数据库

作为 ELK 堆栈(ElasticSearch、Logstash、Kibana)的替代方案,我们正在研究使用 ClickHouse 数据库作为日志的数据存储。

在这篇文章中,我们想谈谈我们使用ClickHouse数据库的经验和试点运行的初步结果。 应该立即注意到结果令人印象深刻。


人类或外星技术的 ClickHouse 数据库

接下来,我们将更详细地描述我们的系统是如何配置的,以及它由哪些组件组成。 但现在我想从整体上谈谈这个数据库,以及为什么它值得关注。 ClickHouse 数据库是来自 Yandex 的高性能分析型柱状数据库。 它用于 Yandex 服务,最初它是 Yandex.Metrica 的主要数据存储。 开源系统,免费。 从开发人员的角度来看,我一直想知道他们是如何实现它的,因为有非常大的数据。 而且 Metrica 的用户界面本身非常灵活和快速。 初识这个数据库时,印象是:“嗯,终于! 为人民而生! 从安装过程开始到发送请求结束。

该数据库的入门门槛非常低。 即使是一般技能的开发人员也可以在几分钟内安装此数据库并开始使用它。 一切都很清楚。 即使是 Linux 新手也能很快搞定安装并进行最简单的操作。 如果说早前,大数据、Hadoop、Google BigTable、HDFS这些词,一个普通的开发者认为是一些TB、PB,一些超人在为这些系统进行设置和开发,那么随着ClickHouse的出现数据库,我们得到了一个简单易懂的工具,您可以使用它来解决以前无法完成的任务范围。 安装一台相当普通的机器只需五分钟。 也就是说,我们得到了一个数据库,例如 MySql,但只能存储数十亿条记录! 某超级存档用SQL语言。 这就像人们被交给了外星人的武器。

关于我们的日志系统

为了收集信息,使用标准格式的Web应用程序的IIS日志文件(我们目前也在解析应用程序日志,但试点阶段的主要目标是收集IIS日志)。

由于各种原因,我们不能完全放弃 ELK 堆栈,我们继续使用 LogStash 和 Filebeat 组件,它们已经证明自己很好并且工作非常可靠且可预测。

一般的日志记录方案如下图所示:

人类或外星技术的 ClickHouse 数据库

向ClickHouse数据库写入数据的一个特点是不频繁(每秒一次)大批量插入记录。 显然,这是您第一次体验使用 ClickHouse 数据库时遇到的最“有问题”的部分:方案变得有点复杂。
直接向 ClickHouse 插入数据的 LogStash 插件在这里帮了大忙。 该组件部署在与数据库本身相同的服务器上。 所以,一般来说,不建议这样做,但是从实用的角度来说,不要在部署在同一台服务器上的时候,产生单独的服务器。 我们没有观察到任何故障或与数据库的资源冲突。 另外需要注意的是,插件有出错重试机制。 并且在出错的情况下,插件将一批无法插入的数据写入磁盘(文件格式很方便:编辑后,您可以使用 clickhouse-client 轻松插入更正的批次)。

该方案中使用的软件的完整列表如下表所示:

使用的软件列表

名称

使用说明

分发链接

NGINX

反向代理限制端口访问和组织授权

目前方案中未使用

https://nginx.org/ru/download.html

https://nginx.org/download/nginx-1.16.0.tar.gz

档案节拍

传输文件日志。

https://www.elastic.co/downloads/beats/filebeat (适用于 Windows 64 位的分发包)。

https://artifacts.elastic.co/downloads/beats/filebeat/filebeat-7.3.0-windows-x86_64.zip

日志存储

日志收集器。

用于从 FileBeat 收集日志,以及从 RabbitMQ 队列收集日志(对于位于 DMZ 中的服务器。)

https://www.elastic.co/products/logstash

https://artifacts.elastic.co/downloads/logstash/logstash-7.0.1.rpm

Logstash-output-clickhouse

Loagstash插件,用于将日志批量传输到ClickHouse数据库

https://github.com/mikechris/logstash-output-clickhouse

/usr/share/logstash/bin/logstash-plugin 安装 logstash-output-clickhouse

/usr/share/logstash/bin/logstash-plugin 安装 logstash-filter-prune

/usr/share/logstash/bin/logstash-plugin 安装 logstash-filter-multiline

点击之家

日志存储 https://clickhouse.yandex/docs/ru/

https://packagecloud.io/Altinity/clickhouse/packages/el/7/clickhouse-server-19.5.3.8-1.el7.x86_64.rpm

https://packagecloud.io/Altinity/clickhouse/packages/el/7/clickhouse-client-19.5.3.8-1.el7.x86_64.rpm

笔记。 从 2018 年 XNUMX 月开始,RHEL 的“正常”rpm 构建出现在 Yandex 存储库中,因此您可以尝试使用它们。 在安装时,我们使用的是 Altinity 构建的包。

格拉法纳

日志可视化。 设置仪表板

https://grafana.com/

https://grafana.com/grafana/download

Redhat & Centos(64 位) - 最新版本

Grafana 4.6+ 的 ClickHouse 数据源

带有 ClickHouse 数据源的 Grafana 插件

https://grafana.com/plugins/vertamedia-clickhouse-datasource

https://grafana.com/api/plugins/vertamedia-clickhouse-datasource/versions/1.8.1/download

日志存储

从 FileBeat 到 RabbitMQ 队列的日志路由器。

笔记。 不幸的是,FileBeat 没有直接输出到 RabbitMQ,因此需要一个 Logstash 形式的中间链接

https://www.elastic.co/products/logstash

https://artifacts.elastic.co/downloads/logstash/logstash-7.0.1.rpm

的RabbitMQ

消息队列。 这是 DMZ 中的日志缓冲区

https://www.rabbitmq.com/download.html

https://github.com/rabbitmq/rabbitmq-server/releases/download/v3.7.14/rabbitmq-server-3.7.14-1.el7.noarch.rpm

Erlang 运行时(RabbitMQ 需要)

二郎运行时。 RabbitMQ 工作所必需的

http://www.erlang.org/download.html

https://www.rabbitmq.com/install-rpm.html#install-erlang http://www.erlang.org/downloads/21.3

下表显示了带有 ClickHouse 数据库的服务器配置:

名称

注意

布局

硬盘:40GB
RAM:8GB
处理器:Core 2 2Ghz

需要注意操作ClickHouse数据库的小技巧(https://clickhouse.yandex/docs/ru/operations/tips/)

通用系统软件

操作系统:Red Hat Enterprise Linux Server(麦坡)

JRE(Java 8)

 

如您所见,这是一个普通的工作站。

存放日志的表结构如下:

log_web.sql

CREATE TABLE log_web (
  logdate Date,
  logdatetime DateTime CODEC(Delta, LZ4HC),
   
  fld_log_file_name LowCardinality( String ),
  fld_server_name LowCardinality( String ),
  fld_app_name LowCardinality( String ),
  fld_app_module LowCardinality( String ),
  fld_website_name LowCardinality( String ),
 
  serverIP LowCardinality( String ),
  method LowCardinality( String ),
  uriStem String,
  uriQuery String,
  port UInt32,
  username LowCardinality( String ),
  clientIP String,
  clientRealIP String,
  userAgent String,
  referer String,
  response String,
  subresponse String,
  win32response String,
  timetaken UInt64
   
  , uriQuery__utm_medium String
  , uriQuery__utm_source String
  , uriQuery__utm_campaign String
  , uriQuery__utm_term String
  , uriQuery__utm_content String
  , uriQuery__yclid String
  , uriQuery__region String
 
) Engine = MergeTree()
PARTITION BY toYYYYMM(logdate)
ORDER BY (fld_app_name, fld_app_module, logdatetime)
SETTINGS index_granularity = 8192;

我们使用默认分区(按月)和索引粒度。 所有字段实际上都对应于记录 http 请求的 IIS 日志条目。 另外,我们注意到有单独的字段用于存储 utm-tags(它们在从查询字符串字段插入表的阶段被解析)。

此外,表中还添加了几个系统字段,用于存储有关系统、组件、服务器的信息。 有关这些字段的说明,请参见下表。 在一张表中,我们存储了多个系统的日志。

名称

使用说明

例子

fld_应用程序名称

应用程序/系统名称
有效值:

  • site1.domain.com 外部站点 1
  • site2.domain.com 外部站点 2
  • internal-site1.domain.local 内部站点 1

site1.domain.com

fld_app_模块

系统模块
有效值:

  • 网站 - 网站
  • svc - 网站服务
  • intgr - 集成 Web 服务
  • bo - 管理员(后台)

卷筒纸

fld_网站名称

IIS 中的站点名称

一台服务器上可以部署多个系统,甚至可以是一个系统模块的多个实例

网站主

fld_服务器_名称

服务器名称

web1.domain.com

fld_日志_文件名

服务器上日志文件的路径

C:inetpublogs日志文件
W3SVC1u_ex190711.log

这使您可以在 Grafana 中高效地构建图形。 例如,查看来自特定系统前端的请求。 这类似于 Yandex.Metrica 中的站点计数器。

下面是两个月数据库使用情况的一些统计数据。

按系统及其组件细分的记录数

SELECT
    fld_app_name,
    fld_app_module,
    count(fld_app_name) AS rows_count
FROM log_web
GROUP BY
    fld_app_name,
    fld_app_module
    WITH TOTALS
ORDER BY
    fld_app_name ASC,
    rows_count DESC
 
┌─fld_app_name─────┬─fld_app_module─┬─rows_count─┐
│ site1.domain.ru  │ web            │     131441 │
│ site2.domain.ru  │ web            │    1751081 │
│ site3.domain.ru  │ web            │  106887543 │
│ site3.domain.ru  │ svc            │   44908603 │
│ site3.domain.ru  │ intgr          │    9813911 │
│ site4.domain.ru  │ web            │     772095 │
│ site5.domain.ru  │ web            │   17037221 │
│ site5.domain.ru  │ intgr          │     838559 │
│ site5.domain.ru  │ bo             │       7404 │
│ site6.domain.ru  │ web            │     595877 │
│ site7.domain.ru  │ web            │   27778858 │
└──────────────────┴────────────────┴────────────┘
 
Totals:
┌─fld_app_name─┬─fld_app_module─┬─rows_count─┐
│              │                │  210522593 │
└──────────────┴────────────────┴────────────┘
 
11 rows in set. Elapsed: 4.874 sec. Processed 210.52 million rows, 421.67 MB (43.19 million rows/s., 86.51 MB/s.)

磁盘上的数据量

SELECT
    formatReadableSize(sum(data_uncompressed_bytes)) AS uncompressed,
    formatReadableSize(sum(data_compressed_bytes)) AS compressed,
    sum(rows) AS total_rows
FROM system.parts
WHERE table = 'log_web'
 
┌─uncompressed─┬─compressed─┬─total_rows─┐
│ 54.50 GiB    │ 4.86 GiB   │  211427094 │
└──────────────┴────────────┴────────────┘
 
1 rows in set. Elapsed: 0.035 sec.

列中的数据压缩程度

SELECT
    name,
    formatReadableSize(data_uncompressed_bytes) AS uncompressed,
    formatReadableSize(data_compressed_bytes) AS compressed,
    data_uncompressed_bytes / data_compressed_bytes AS compress_ratio
FROM system.columns
WHERE table = 'log_web'
 
┌─name───────────────────┬─uncompressed─┬─compressed─┬─────compress_ratio─┐
│ logdate                │ 401.53 MiB   │ 1.80 MiB   │ 223.16665968777315 │
│ logdatetime            │ 803.06 MiB   │ 35.91 MiB  │ 22.363966401202305 │
│ fld_log_file_name      │ 220.66 MiB   │ 2.60 MiB   │  84.99905736932571 │
│ fld_server_name        │ 201.54 MiB   │ 50.63 MiB  │  3.980924816977078 │
│ fld_app_name           │ 201.17 MiB   │ 969.17 KiB │ 212.55518183686877 │
│ fld_app_module         │ 201.17 MiB   │ 968.60 KiB │ 212.67805817411906 │
│ fld_website_name       │ 201.54 MiB   │ 1.24 MiB   │  162.7204926761546 │
│ serverIP               │ 201.54 MiB   │ 50.25 MiB  │  4.010824061219731 │
│ method                 │ 201.53 MiB   │ 43.64 MiB  │  4.617721053304486 │
│ uriStem                │ 5.13 GiB     │ 832.51 MiB │  6.311522291936919 │
│ uriQuery               │ 2.58 GiB     │ 501.06 MiB │  5.269731450124478 │
│ port                   │ 803.06 MiB   │ 3.98 MiB   │ 201.91673864241824 │
│ username               │ 318.08 MiB   │ 26.93 MiB  │ 11.812513794583598 │
│ clientIP               │ 2.35 GiB     │ 82.59 MiB  │ 29.132328640073343 │
│ clientRealIP           │ 2.49 GiB     │ 465.05 MiB │  5.478382297052563 │
│ userAgent              │ 18.34 GiB    │ 764.08 MiB │  24.57905114484208 │
│ referer                │ 14.71 GiB    │ 1.37 GiB   │ 10.736792723669906 │
│ response               │ 803.06 MiB   │ 83.81 MiB  │  9.582334090987247 │
│ subresponse            │ 399.87 MiB   │ 1.83 MiB   │  218.4831068635027 │
│ win32response          │ 407.86 MiB   │ 7.41 MiB   │ 55.050315514606815 │
│ timetaken              │ 1.57 GiB     │ 402.06 MiB │ 3.9947395692010637 │
│ uriQuery__utm_medium   │ 208.17 MiB   │ 12.29 MiB  │ 16.936148912472955 │
│ uriQuery__utm_source   │ 215.18 MiB   │ 13.00 MiB  │ 16.548367623199912 │
│ uriQuery__utm_campaign │ 381.46 MiB   │ 37.94 MiB  │ 10.055156353418509 │
│ uriQuery__utm_term     │ 231.82 MiB   │ 10.78 MiB  │ 21.502540454070672 │
│ uriQuery__utm_content  │ 441.34 MiB   │ 87.60 MiB  │  5.038260760449327 │
│ uriQuery__yclid        │ 216.88 MiB   │ 16.58 MiB  │  13.07721335008116 │
│ uriQuery__region       │ 204.35 MiB   │ 9.49 MiB   │  21.52661903446796 │
└────────────────────────┴──────────────┴────────────┴────────────────────┘
 
28 rows in set. Elapsed: 0.005 sec.

使用组件的描述

文件节拍。 传输文件日志

该组件跟踪磁盘上日志文件的更改并将信息传递给 LogStash。 安装在所有写入日志文件的服务器上(通常是 IIS)。 在尾部模式下工作(即仅将添加的记录传输到文件)。 但它可以单独配置为传输整个文件。 当您需要下载前几个月的数据时,这很有用。 只需将日志文件放在一个文件夹中,它就会完整读取它。

当服务停止时,数据不再进一步传输到存储。

示例配置如下所示:

文件beat.yml

filebeat.inputs:
- type: log
  enabled: true
  paths:
    - C:/inetpub/logs/LogFiles/W3SVC1/*.log
  exclude_files: ['.gz$','.zip$']
  tail_files: true
  ignore_older: 24h
  fields:
    fld_server_name: "site1.domain.ru"
    fld_app_name: "site1.domain.ru"
    fld_app_module: "web"
    fld_website_name: "web-main"
 
- type: log
  enabled: true
  paths:
    - C:/inetpub/logs/LogFiles/__Import/access_log-*
  exclude_files: ['.gz$','.zip$']
  tail_files: false
  fields:
    fld_server_name: "site2.domain.ru"
    fld_app_name: "site2.domain.ru"
    fld_app_module: "web"
    fld_website_name: "web-main"
    fld_logformat: "logformat__apache"
 
 
filebeat.config.modules:
  path: ${path.config}/modules.d/*.yml
  reload.enabled: false
  reload.period: 2s
 
output.logstash:
  hosts: ["log.domain.com:5044"]
 
  ssl.enabled: true
  ssl.certificate_authorities: ["C:/filebeat/certs/ca.pem", "C:/filebeat/certs/ca-issuing.pem"]
  ssl.certificate: "C:/filebeat/certs/site1.domain.ru.cer"
  ssl.key: "C:/filebeat/certs/site1.domain.ru.key"
 
#================================ Processors =====================================
 
processors:
  - add_host_metadata: ~
  - add_cloud_metadata: ~

日志存储。 日志收集器

该组件旨在从 FileBeat(或通过 RabbitMQ 队列)接收日志条目,解析批次并将其插入 ClickHouse 数据库。

为了插入到 ClickHouse,使用了 Logstash-output-clickhouse 插件。 Logstash 插件有一个请求重试机制,但是定期关闭,最好停止服务本身。 停止时,消息会堆积在RabbitMQ队列中,所以如果停止的时间较长,那么最好停止服务器上的Filebeats。 在不使用 RabbitMQ 的方案中(在本地网络上,Filebeat 直接将日志发送到 Logstash),Filebeats 的工作是相当可接受且安全的,因此对于它们来说,输出的不可用性不会产生任何后果。

示例配置如下所示:

log_web__filebeat_clickhouse.conf

input {
 
    beats {
        port => 5044
        type => 'iis'
        ssl => true
        ssl_certificate_authorities => ["/etc/logstash/certs/ca.cer", "/etc/logstash/certs/ca-issuing.cer"]
        ssl_certificate => "/etc/logstash/certs/server.cer"
        ssl_key => "/etc/logstash/certs/server-pkcs8.key"
        ssl_verify_mode => "peer"
 
            add_field => {
                "fld_server_name" => "%{[fields][fld_server_name]}"
                "fld_app_name" => "%{[fields][fld_app_name]}"
                "fld_app_module" => "%{[fields][fld_app_module]}"
                "fld_website_name" => "%{[fields][fld_website_name]}"
                "fld_log_file_name" => "%{source}"
                "fld_logformat" => "%{[fields][fld_logformat]}"
            }
    }
 
    rabbitmq {
        host => "queue.domain.com"
        port => 5671
        user => "q-reader"
        password => "password"
        queue => "web_log"
        heartbeat => 30
        durable => true
        ssl => true
        #ssl_certificate_path => "/etc/logstash/certs/server.p12"
        #ssl_certificate_password => "password"
 
        add_field => {
            "fld_server_name" => "%{[fields][fld_server_name]}"
            "fld_app_name" => "%{[fields][fld_app_name]}"
            "fld_app_module" => "%{[fields][fld_app_module]}"
            "fld_website_name" => "%{[fields][fld_website_name]}"
            "fld_log_file_name" => "%{source}"
            "fld_logformat" => "%{[fields][fld_logformat]}"
        }
    }
 
}
 
filter { 
 
      if [message] =~ "^#" {
        drop {}
      }
 
      if [fld_logformat] == "logformat__iis_with_xrealip" {
     
          grok {
            match => ["message", "%{TIMESTAMP_ISO8601:log_timestamp} %{IP:serverIP} %{WORD:method} %{NOTSPACE:uriStem} %{NOTSPACE:uriQuery} %{NUMBER:port} %{NOTSPACE:username} %{IPORHOST:clientIP} %{NOTSPACE:userAgent} %{NOTSPACE:referer} %{NUMBER:response} %{NUMBER:subresponse} %{NUMBER:win32response} %{NUMBER:timetaken} %{NOTSPACE:xrealIP} %{NOTSPACE:xforwarderfor}"]
          }
      } else {
   
          grok {
             match => ["message", "%{TIMESTAMP_ISO8601:log_timestamp} %{IP:serverIP} %{WORD:method} %{NOTSPACE:uriStem} %{NOTSPACE:uriQuery} %{NUMBER:port} %{NOTSPACE:username} %{IPORHOST:clientIP} %{NOTSPACE:userAgent} %{NOTSPACE:referer} %{NUMBER:response} %{NUMBER:subresponse} %{NUMBER:win32response} %{NUMBER:timetaken}"]
          }
 
      }
 
      date {
        match => [ "log_timestamp", "YYYY-MM-dd HH:mm:ss" ]
          timezone => "Etc/UTC"
        remove_field => [ "log_timestamp", "@timestamp" ]
        target => [ "log_timestamp2" ]
      }
 
        ruby {
            code => "tstamp = event.get('log_timestamp2').to_i
                        event.set('logdatetime', Time.at(tstamp).strftime('%Y-%m-%d %H:%M:%S'))
                        event.set('logdate', Time.at(tstamp).strftime('%Y-%m-%d'))"
        }
 
      if [bytesSent] {
        ruby {
          code => "event['kilobytesSent'] = event['bytesSent'].to_i / 1024.0"
        }
      }
 
 
      if [bytesReceived] {
        ruby {
          code => "event['kilobytesReceived'] = event['bytesReceived'].to_i / 1024.0"
        }
      }
 
   
        ruby {
            code => "event.set('clientRealIP', event.get('clientIP'))"
        }
        if [xrealIP] {
            ruby {
                code => "event.set('clientRealIP', event.get('xrealIP'))"
            }
        }
        if [xforwarderfor] {
            ruby {
                code => "event.set('clientRealIP', event.get('xforwarderfor'))"
            }
        }
 
      mutate {
        convert => ["bytesSent", "integer"]
        convert => ["bytesReceived", "integer"]
        convert => ["timetaken", "integer"] 
        convert => ["port", "integer"]
 
        add_field => {
            "clientHostname" => "%{clientIP}"
        }
      }
 
        useragent {
            source=> "useragent"
            prefix=> "browser"
        }
 
        kv {
            source => "uriQuery"
            prefix => "uriQuery__"
            allow_duplicate_values => false
            field_split => "&"
            include_keys => [ "utm_medium", "utm_source", "utm_campaign", "utm_term", "utm_content", "yclid", "region" ]
        }
 
        mutate {
            join => { "uriQuery__utm_source" => "," }
            join => { "uriQuery__utm_medium" => "," }
            join => { "uriQuery__utm_campaign" => "," }
            join => { "uriQuery__utm_term" => "," }
            join => { "uriQuery__utm_content" => "," }
            join => { "uriQuery__yclid" => "," }
            join => { "uriQuery__region" => "," }
        }
 
}
 
output { 
  #stdout {codec => rubydebug}
    clickhouse {
      headers => ["Authorization", "Basic abcdsfks..."]
      http_hosts => ["http://127.0.0.1:8123"]
      save_dir => "/etc/logstash/tmp"
      table => "log_web"
      request_tolerance => 1
      flush_size => 10000
      idle_flush_time => 1
        mutations => {
            "fld_log_file_name" => "fld_log_file_name"
            "fld_server_name" => "fld_server_name"
            "fld_app_name" => "fld_app_name"
            "fld_app_module" => "fld_app_module"
            "fld_website_name" => "fld_website_name"
 
            "logdatetime" => "logdatetime"
            "logdate" => "logdate"
            "serverIP" => "serverIP"
            "method" => "method"
            "uriStem" => "uriStem"
            "uriQuery" => "uriQuery"
            "port" => "port"
            "username" => "username"
            "clientIP" => "clientIP"
            "clientRealIP" => "clientRealIP"
            "userAgent" => "userAgent"
            "referer" => "referer"
            "response" => "response"
            "subresponse" => "subresponse"
            "win32response" => "win32response"
            "timetaken" => "timetaken"
             
            "uriQuery__utm_medium" => "uriQuery__utm_medium"
            "uriQuery__utm_source" => "uriQuery__utm_source"
            "uriQuery__utm_campaign" => "uriQuery__utm_campaign"
            "uriQuery__utm_term" => "uriQuery__utm_term"
            "uriQuery__utm_content" => "uriQuery__utm_content"
            "uriQuery__yclid" => "uriQuery__yclid"
            "uriQuery__region" => "uriQuery__region"
        }
    }
 
}

管道.yml

# This file is where you define your pipelines. You can define multiple.
# For more information on multiple pipelines, see the documentation:
#   https://www.elastic.co/guide/en/logstash/current/multiple-pipelines.html
 
- pipeline.id: log_web__filebeat_clickhouse
  path.config: "/etc/logstash/log_web__filebeat_clickhouse.conf"

点击之家。 日志存储

所有系统的日志都存储在一个表中(请参阅本文开头)。 它旨在存储有关请求的信息:所有参数对于不同的格式都是相似的,例如 IIS 日志、apache 和 nginx 日志。 对于记录了错误、信息消息、警告等的应用程序日志,将提供一个具有适当结构的单独表格(目前处于设计阶段)。

在设计表时,确定主键(数据在存储期间将按其排序)非常重要。 数据压缩程度和查询速度取决于此。 在我们的例子中,关键是
ORDER BY (fld_app_name, fld_app_module, logdatetime)
也就是说,通过系统的名称、系统组件的名称和事件的日期。 最初,事件的日期排在第一位。 将其移至最后一个位置后,查询开始以大约两倍的速度运行。 更改主键将需要重新创建表并重新加载数据,以便 ClickHouse 重新排序磁盘上的数据。 这是一项繁重的操作,因此最好仔细考虑排序键中应包含的内容。

还应该注意的是,LowCardinality 数据类型出现在相对较新的版本中。 使用它时,对于那些具有低基数(很少选项)的字段,压缩数据的大小会大大减少。

目前正在使用 19.6 版本,我们计划尝试更新到最新版本。 例如,它们具有自适应粒度、跳过索引和 DoubleDelta 编解码器等出色功能。

默认情况下,在安装期间,日志记录级别设置为跟踪。 日志被轮换和归档,但同时它们扩展到 XNUMX GB。 如果没有必要,那么你可以设置警告级别,然后日志的大小会大大减少。 日志记录设置在 config.xml 文件中设置:

<!-- Possible levels: https://github.com/pocoproject/poco/blob/develop/Foundation/include/Poco/Logger. h#L105 -->
<level>warning</level>

一些有用的命令

Поскольку оригинальные пакеты установки собираются по Debian, то для других версий Linux необходимо использовать пакеты собранные компанией Altinity.
 
Вот по этой ссылке есть инструкции с ссылками на их репозиторий: https://www.altinity.com/blog/2017/12/18/logstash-with-clickhouse
sudo yum search clickhouse-server
sudo yum install clickhouse-server.noarch
  
1. проверка статуса
sudo systemctl status clickhouse-server
 
2. остановка сервера
sudo systemctl stop clickhouse-server
 
3. запуск сервера
sudo systemctl start clickhouse-server
 
Запуск для выполнения запросов в многострочном режиме (выполнение после знака ";")
clickhouse-client --multiline
clickhouse-client --multiline --host 127.0.0.1 --password pa55w0rd
clickhouse-client --multiline --host 127.0.0.1 --port 9440 --secure --user default --password pa55w0rd
 
Плагин кликлауза для логстеш в случае ошибки в одной строке сохраняет всю пачку в файл /tmp/log_web_failed.json
Можно вручную исправить этот файл и попробовать залить его в БД вручную:
clickhouse-client --host 127.0.0.1 --password password --query="INSERT INTO log_web FORMAT JSONEachRow" < /tmp/log_web_failed__fixed.json
 
sudo mv /etc/logstash/tmp/log_web_failed.json /etc/logstash/tmp/log_web_failed__fixed.json
sudo chown user_dev /etc/logstash/tmp/log_web_failed__fixed.json
sudo clickhouse-client --host 127.0.0.1 --password password --query="INSERT INTO log_web FORMAT JSONEachRow" < /etc/logstash/tmp/log_web_failed__fixed.json
sudo mv /etc/logstash/tmp/log_web_failed__fixed.json /etc/logstash/tmp/log_web_failed__fixed_.json
 
выход из командной строки
quit;
## Настройка TLS
https://www.altinity.com/blog/2019/3/5/clickhouse-networking-part-2
 
openssl s_client -connect log.domain.com:9440 < /dev/null

日志存储。 从 FileBeat 到 RabbitMQ 队列的日志路由器

该组件用于将来自 FileBeat 的日志路由到 RabbitMQ 队列。 这里有两点:

  1. 不幸的是,FileBeat 没有直接写入 RabbitMQ 的输出插件。 从他们 github 上的问题来看,此类功能并未计划实施。 Kafka 有一个插件,但由于某些原因我们不能在家里使用它。
  2. 在DMZ区收集日志有要求。 基于它们,必须首先将日志添加到队列中,然后 LogStash 从外部读取队列中的条目。

因此,对于服务器位于DMZ内的情况,不得不使用这种稍微复杂的方案。 示例配置如下所示:

iis_w3c_logs__filebeat_rabbitmq.conf

input {
 
    beats {
        port => 5044
        type => 'iis'
        ssl => true
        ssl_certificate_authorities => ["/etc/pki/tls/certs/app/ca.pem", "/etc/pki/tls/certs/app/ca-issuing.pem"]
        ssl_certificate => "/etc/pki/tls/certs/app/queue.domain.com.cer"
        ssl_key => "/etc/pki/tls/certs/app/queue.domain.com-pkcs8.key"
        ssl_verify_mode => "peer"
    }
 
}
 
output { 
  #stdout {codec => rubydebug}
 
    rabbitmq {
        host => "127.0.0.1"
        port => 5672
        exchange => "monitor.direct"
        exchange_type => "direct"
        key => "%{[fields][fld_app_name]}"
        user => "q-writer"
        password => "password"
        ssl => false
    }
}

兔子MQ。 消息队列

该组件用于缓冲 DMZ 中的日志条目。 记录是通过一堆 Filebeat → LogStash 完成的。 读取是通过 LogStash 从 DMZ 外部完成的。 通过 RabboitMQ 操作时,每秒处理大约 4 条消息。

消息路由由系统名称配置,即基于 FileBeat 配置数据。 所有消息都进入一个队列。 如果由于某种原因队列服务停止,那么这不会导致消息丢失:FileBeats 会收到连接错误并暂时停止发送。 而从队列中读取的LogStash也会收到网络错误,等待连接恢复。 在这种情况下,数据当然不会再写入数据库。

以下说明用于创建和配置队列:

sudo /usr/local/bin/rabbitmqadmin/rabbitmqadmin declare exchange --vhost=/ name=monitor.direct type=direct sudo /usr/local/bin/rabbitmqadmin/rabbitmqadmin declare queue --vhost=/ name=web_log durable=true
sudo /usr/local/bin/rabbitmqadmin/rabbitmqadmin --vhost="/" declare binding source="monitor.direct" destination_type="queue" destination="web_log" routing_key="site1.domain.ru"
sudo /usr/local/bin/rabbitmqadmin/rabbitmqadmin --vhost="/" declare binding source="monitor.direct" destination_type="queue" destination="web_log" routing_key="site2.domain.ru"

格拉法纳。 仪表板

该组件用于可视化监控数据。 在这种情况下,您需要为 Grafana 4.6+ 插件安装 ClickHouse 数据源。 我们不得不对其进行一些调整,以提高在仪表板上处理 SQL 过滤器的效率。

例如,我们使用变量,如果它们没有在过滤器字段中设置,那么我们希望它不要在表单的 WHERE 中生成条件( uriStem = » AND uriStem != » )。 在这种情况下,ClickHouse 将读取 uriStem 列。 通常,我们尝试了不同的选项并最终更正了插件($valueIfEmpty 宏),以便在空值的情况下它返回 1,而不提及列本身。

现在您可以将此查询用于图形

$columns(response, count(*) c) from $table where $adhoc
and $valueIfEmpty($fld_app_name, 1, fld_app_name = '$fld_app_name')
and $valueIfEmpty($fld_app_module, 1, fld_app_module = '$fld_app_module') and $valueIfEmpty($fld_server_name, 1, fld_server_name = '$fld_server_name') and $valueIfEmpty($uriStem, 1, uriStem like '%$uriStem%')
and $valueIfEmpty($clientRealIP, 1, clientRealIP = '$clientRealIP')

转换为此 SQL(请注意,空的 uriStem 字段已转换为仅 1)

SELECT
t,
groupArray((response, c)) AS groupArr
FROM (
SELECT
(intDiv(toUInt32(logdatetime), 60) * 60) * 1000 AS t, response,
count(*) AS c FROM default.log_web
WHERE (logdate >= toDate(1565061982)) AND (logdatetime >= toDateTime(1565061982)) AND 1 AND (fld_app_name = 'site1.domain.ru') AND (fld_app_module = 'web') AND 1 AND 1 AND 1
GROUP BY
t, response
ORDER BY
t ASC,
response ASC
)
GROUP BY t ORDER BY t ASC

结论

ClickHouse数据库的出现已经成为市场上的标志性事件。 很难想象,在完全免费的情况下,我们瞬间就拥有了一个强大而实用的大数据处理工具。 当然,随着需求的增加(比如分片、复制到多台服务器),方案会变得更加复杂。 但第一印象是,使用这个数据库非常愉快。 可见产品是“为人”而生。

与ElasticSearch相比,存储和处理日志的成本估计可以降低五到十倍。 换句话说,如果对于当前的数据量,我们必须建立一个由几台机器组成的集群,那么在使用 ClickHouse 时,一台低功耗机器就足够了。 是的,当然,ElasticSearch 也有磁盘上的数据压缩机制和其他可以显着减少资源消耗的特性,但是相对于 ClickHouse,这个会更昂贵。

在我们没有任何特殊优化的情况下,在默认设置下,加载数据和从数据库中选择以惊人的速度工作。 我们还没有太多数据(大约 200 亿条记录),但服务器本身很弱。 我们将来可以将此工具用于与存储日志无关的其他目的。 例如,对于安全、机器学习领域的端到端分析。

最后说一下优缺点。

缺点

  1. 大批量加载记录。 一方面,这是一项功能,但您仍然必须使用额外的组件来缓冲记录。 这项任务并不总是那么容易,但仍然可以解决。 我想简化方案。
  2. 一些奇特的功能或新特性经常在新版本中失效。 这引起了关注,降低了升级到新版本的愿望。 例如,Kafka 表引擎是一个非常有用的功能,它允许您直接从 Kafka 读取事件,而无需实现消费者。 但是从github上的Issues数量来看,我们还是小心翼翼的不在生产中使用这个引擎。 但是,如果您不突然向侧面做手势并使用主要功能,则它可以稳定运行。

优点

  1. 不会慢下来。
  2. 入门门槛低。
  3. 开源。
  4. 自由的。
  5. 扩展性好(开箱即用的分片/复制)
  6. 列入交通部推荐的俄​​罗斯软件名录。
  7. Yandex 官方支持的存在。

来源: habr.com

添加评论