Baza danych ClickHouse dla ludzi, czyli technologie obcych

Aleksey Lizunov, kierownik Centrum Kompetencyjnego ds. Kanałów Obsługi Zdalnej Dyrekcji Technologii Informatycznych MKB

Baza danych ClickHouse dla ludzi, czyli technologie obcych

Jako alternatywę dla stosu ELK (ElasticSearch, Logstash, Kibana) prowadzimy badania nad wykorzystaniem bazy danych ClickHouse jako magazynu danych dla logów.

W tym artykule chcielibyśmy opowiedzieć o naszych doświadczeniach w korzystaniu z bazy ClickHouse oraz wstępnych wynikach pilotażowej operacji. Należy od razu zauważyć, że wyniki były imponujące.


Baza danych ClickHouse dla ludzi, czyli technologie obcych

Następnie opiszemy bardziej szczegółowo, jak jest skonfigurowany nasz system i z jakich komponentów się składa. Ale teraz chciałbym porozmawiać trochę o tej bazie danych jako całości i dlaczego warto na nią zwrócić uwagę. Baza danych ClickHouse to wysokowydajna analityczna kolumnowa baza danych firmy Yandex. Jest używany w usługach Yandex, początkowo jest to główny magazyn danych dla Yandex.Metrica. System open source, bezpłatny. Z punktu widzenia programisty zawsze zastanawiałem się, jak to zaimplementowali, ponieważ są tam fantastycznie duże dane. Sam interfejs użytkownika Metrica jest bardzo elastyczny i szybki. Przy pierwszym zapoznaniu się z tą bazą danych wrażenie jest takie: „No, wreszcie! Stworzony dla ludzi! Począwszy od procesu instalacji, a skończywszy na wysyłaniu zapytań.

Ta baza danych ma bardzo niski próg wejścia. Nawet średnio wykwalifikowany programista może zainstalować tę bazę danych w ciągu kilku minut i zacząć z niej korzystać. Wszystko działa wyraźnie. Nawet osoby, które są nowe w Linuksie, mogą szybko poradzić sobie z instalacją i wykonać najprostsze operacje. Jeśli wcześniej, przy słowach Big Data, Hadoop, Google BigTable, HDFS, zwykły programista wpadł na pomysł, że chodzi o jakieś terabajty, petabajty, że niektórzy nadludzie zajmują się ustawieniami i rozwojem tych systemów, to wraz z pojawieniem się ClickHouse bazy danych, otrzymaliśmy proste, zrozumiałe narzędzie, za pomocą którego można rozwiązać nieosiągalny wcześniej zakres zadań. Instalacja zajmuje tylko jedną dość przeciętną maszynę i pięć minut. Czyli dostaliśmy taką bazę jak na przykład MySql, ale tylko do przechowywania miliardów rekordów! Pewien superarchiwizator z językiem SQL. To tak, jakby ludziom wręczono broń kosmitów.

O naszym systemie logowania

Do zbierania informacji używane są pliki dziennika IIS aplikacji internetowych w standardowym formacie (obecnie analizujemy również dzienniki aplikacji, ale głównym celem na etapie pilotażowym jest zebranie dzienników IIS).

Z różnych powodów nie mogliśmy całkowicie zrezygnować ze stosu ELK i nadal używamy komponentów LogStash i Filebeat, które sprawdziły się i działają dość niezawodnie i przewidywalnie.

Ogólny schemat logowania przedstawiono na poniższym rysunku:

Baza danych ClickHouse dla ludzi, czyli technologie obcych

Cechą zapisywania danych do bazy ClickHouse jest rzadkie (raz na sekundę) wstawianie rekordów w dużych partiach. Najwyraźniej jest to najbardziej „problematyczna” część, z którą spotykasz się podczas pierwszej pracy z bazą danych ClickHouse: schemat staje się nieco bardziej skomplikowany.
Bardzo pomogła tu wtyczka do LogStash, która bezpośrednio wstawia dane do ClickHouse. Ten komponent jest wdrażany na tym samym serwerze co sama baza danych. Ogólnie rzecz biorąc, nie zaleca się tego robić, ale z praktycznego punktu widzenia, aby nie tworzyć oddzielnych serwerów, gdy jest wdrażany na tym samym serwerze. Nie zaobserwowaliśmy żadnych awarii ani konfliktów zasobów z bazą danych. Dodatkowo należy zaznaczyć, że wtyczka posiada mechanizm ponawiania próby w przypadku błędów. A w przypadku błędów wtyczka zapisuje na dysku partię danych, których nie można było wstawić (format pliku jest wygodny: po edycji można łatwo wstawić poprawioną partię za pomocą klienta Clickhouse).

Pełną listę oprogramowania użytego w schemacie przedstawiono w tabeli:

Lista używanego oprogramowania

Nazwa

Opis

Łącze dystrybucji

nginx

Reverse-proxy, aby ograniczyć dostęp przez porty i zorganizować autoryzację

Obecnie nie używany w schemacie

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

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

PlikBeat

Przesyłanie dzienników plików.

https://www.elastic.co/downloads/beats/filebeat (zestaw dystrybucyjny dla Windows 64bit).

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

logstash

Kolekcjoner dzienników.

Służy do zbierania logów z FileBeat, a także do zbierania logów z kolejki RabbitMQ (dla serwerów znajdujących się w strefie DMZ).

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

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

Logstash-output-clickhouse

Wtyczka Loagstash do przenoszenia logów do bazy ClickHouse partiami

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

/usr/share/logstash/bin/logstash-plugin zainstaluj logstash-output-clickhouse

/usr/share/logstash/bin/logstash-plugin zainstaluj logstash-filter-prune

/usr/share/logstash/bin/logstash-plugin zainstaluj logstash-filter-multiline

Kliknij Dom

Przechowywanie dziennika 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

Notatka. Począwszy od sierpnia 2018 r. w repozytorium Yandex pojawiły się „normalne” kompilacje rpm dla RHEL, więc można spróbować ich użyć. W czasie instalacji korzystaliśmy z pakietów zbudowanych przez Altinity.

grafana

Wizualizacja dziennika. Konfigurowanie pulpitów nawigacyjnych

https://grafana.com/

https://grafana.com/grafana/download

Redhat i Centos (64-bitowy) — najnowsza wersja

Źródło danych ClickHouse dla Grafana 4.6+

Wtyczka do Grafany ze źródłem danych ClickHouse

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

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

logstash

Zaloguj router z FileBeat do kolejki RabbitMQ.

Notatka. Niestety FileBeat nie wysyła danych bezpośrednio do RabbitMQ, więc wymagane jest pośrednie łącze w postaci Logstash

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

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

RabbitMQ

kolejka wiadomości. To jest bufor dziennika w strefie 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

Środowisko wykonawcze Erlang (wymagane dla RabbitMQ)

Środowisko wykonawcze Erlanga. Wymagane do działania RabbitMQ

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

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

Konfigurację serwera z bazą ClickHouse przedstawia poniższa tabela:

Nazwa

Wartość

Operacja

Konfiguracja

HDD: 40GB
RAM: 8GB
Procesor: Rdzeń 2 2 GHz

Należy koniecznie zwrócić uwagę na wskazówki dotyczące obsługi bazy ClickHouse (https://clickhouse.yandex/docs/ru/operations/tips/)

Ogólne oprogramowanie systemowe

System operacyjny: Red Hat Enterprise Linux Server (Maipo)

JRE (Java 8)

 

Jak widać, jest to zwykła stacja robocza.

Struktura tabeli do przechowywania logów jest następująca:

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;

Używamy domyślnego partycjonowania (według miesiąca) i szczegółowości indeksu. Wszystkie pola praktycznie odpowiadają wpisom dziennika IIS do rejestrowania żądań http. Osobno zauważamy, że istnieją osobne pola do przechowywania tagów utm (są one analizowane na etapie wstawiania do tabeli z pola ciągu zapytania).

Ponadto do tabeli dodano kilka pól systemowych do przechowywania informacji o systemach, komponentach, serwerach. Zobacz tabelę poniżej, aby zapoznać się z opisem tych pól. W jednej tabeli przechowujemy logi dla kilku systemów.

Nazwa

Opis

Przykład

nazwa_pliku_pliku

Nazwa aplikacji/systemu
Prawidłowe wartości:

  • site1.domain.com Strona zewnętrzna 1
  • site2.domain.com Strona zewnętrzna 2
  • strona-wewnętrzna1.domena.lokalna Strona wewnętrzna 1

witryna1.domena.com

moduł_aplikacji fld

Moduł systemowy
Prawidłowe wartości:

  • sieć — witryna internetowa
  • svc — usługa witryny sieci Web
  • intgr — usługa sieciowa integracji
  • bo — administrator (BackOffice)

sieć

fld_nazwa_witryny_internetowej

Nazwa witryny w usługach IIS

Na jednym serwerze można zainstalować kilka systemów, a nawet kilka instancji jednego modułu systemu

strona internetowa

nazwa_serwera_fld

Nazwa serwera

web1.domena.com

nazwa_pliku_fld_loga

Ścieżka do pliku dziennika na serwerze

C:inetpublogsLogFiles
W3SVC1u_ex190711.log

Pozwala to na sprawne budowanie wykresów w Grafanie. Na przykład przeglądaj żądania z interfejsu użytkownika określonego systemu. Jest to podobne do licznika witryn w Yandex.Metrica.

Oto kilka statystyk dotyczących korzystania z bazy danych przez dwa miesiące.

Liczba rekordów w podziale na systemy i ich komponenty

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.)

Ilość danych na dysku

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.

Stopień kompresji danych w kolumnach

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.

Opis użytych komponentów

FileBeat. Przesyłanie dzienników plików

Ten komponent śledzi zmiany w plikach dziennika na dysku i przekazuje informacje do LogStash. Instalowany na wszystkich serwerach, na których zapisywane są pliki dziennika (zazwyczaj IIS). Działa w trybie ogona (tj. przenosi do pliku tylko dodane rekordy). Ale osobno można go skonfigurować do przesyłania całych plików. Jest to przydatne, gdy trzeba pobrać dane z poprzednich miesięcy. Wystarczy umieścić plik dziennika w folderze, a zostanie on odczytany w całości.

Po zatrzymaniu usługi dane nie są już przesyłane dalej do magazynu.

Przykładowa konfiguracja wygląda następująco:

plikbeat.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: ~

logstash. Zbieracz dzienników

Ten komponent jest przeznaczony do odbierania wpisów dziennika z FileBeat (lub przez kolejkę RabbitMQ), analizowania i wstawiania wsadów do bazy danych ClickHouse.

Do wstawienia do ClickHouse używana jest wtyczka Logstash-output-clickhouse. Wtyczka Logstash ma mechanizm ponawiania żądania, ale przy regularnym wyłączaniu lepiej jest zatrzymać samą usługę. Po zatrzymaniu wiadomości będą gromadzone w kolejce RabbitMQ, więc jeśli zatrzymanie trwa przez długi czas, lepiej zatrzymać Filebeats na serwerach. W schemacie, w którym RabbitMQ nie jest używany (w sieci lokalnej Filebeat wysyła logi bezpośrednio do Logstash), Filebeats działają całkiem akceptowalnie i bezpiecznie, więc dla nich niedostępność wyjścia przechodzi bez konsekwencji.

Przykładowa konfiguracja wygląda następująco:

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"
        }
    }
 
}

rurociągi.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"

dom kliknięć. Przechowywanie dziennika

Logi dla wszystkich systemów są przechowywane w jednej tabeli (patrz na początku artykułu). Ma za zadanie przechowywać informacje o żądaniach: wszystkie parametry są podobne dla różnych formatów, takich jak logi IIS, logi apache i nginx. Dla logów aplikacji, w których zapisywane są np. błędy, komunikaty informacyjne, ostrzeżenia, zostanie udostępniona osobna tabela o odpowiedniej strukturze (obecnie na etapie projektowania).

Podczas projektowania tabeli bardzo ważne jest, aby zdecydować się na klucz podstawowy (po którym dane będą sortowane podczas przechowywania). Od tego zależy stopień kompresji danych i szybkość zapytań. W naszym przykładzie kluczem jest
ZAMÓW PRZEZ (nazwa_aplikacji fld, moduł_aplikacji fld, czas_logowania)
Czyli po nazwie systemu, nazwie komponentu systemu i dacie zdarzenia. Początkowo na pierwszym miejscu była data wydarzenia. Po przesunięciu go na ostatnie miejsce zapytania zaczęły działać około dwa razy szybciej. Zmiana klucza podstawowego będzie wymagała ponownego utworzenia tabeli i przeładowania danych, aby ClickHouse ponownie posortował dane na dysku. Jest to ciężka operacja, dlatego dobrze jest przemyśleć, co powinno znaleźć się w kluczu sortowania.

Należy również zauważyć, że typ danych LowCardinality pojawił się w stosunkowo nowych wersjach. Podczas korzystania z niego rozmiar skompresowanych danych jest drastycznie zmniejszany dla tych pól, które mają niską liczność (niewiele opcji).

Obecnie używana jest wersja 19.6 i planujemy spróbować zaktualizować ją do najnowszej wersji. Mają takie wspaniałe funkcje, jak na przykład Adaptive Granularity, Skipping indexs i kodek DoubleDelta.

Domyślnie podczas instalacji poziom rejestrowania jest ustawiony na śledzenie. Logi są rotowane i archiwizowane, ale jednocześnie rozrastają się do gigabajta. Jeśli nie ma takiej potrzeby, możesz ustawić poziom ostrzegawczy, wtedy rozmiar dziennika zostanie drastycznie zmniejszony. Ustawienie rejestrowania jest ustawione w pliku config.xml:

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

Kilka przydatnych poleceń

Поскольку оригинальные пакеты установки собираются по 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

logstash. Zaloguj router z FileBeat do kolejki RabbitMQ

Ten komponent służy do kierowania logów pochodzących z FileBeat do kolejki RabbitMQ. Są tutaj dwa punkty:

  1. Niestety, FileBeat nie ma wtyczki wyjściowej do bezpośredniego zapisu do RabbitMQ. A taka funkcjonalność, sądząc po problemie na ich githubie, nie jest planowana do wdrożenia. Istnieje wtyczka do Kafki, ale z jakiegoś powodu nie możemy jej używać w domu.
  2. Istnieją wymagania dotyczące zbierania logów w strefie DMZ. Na ich podstawie logi muszą najpierw zostać dodane do kolejki, a następnie LogStash odczytuje wpisy z kolejki z zewnątrz.

Dlatego właśnie w przypadku, gdy serwery znajdują się w strefie DMZ, trzeba zastosować taki nieco skomplikowany schemat. Przykładowa konfiguracja wygląda następująco:

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
    }
}

KrólikMQ. kolejka wiadomości

Ten komponent służy do buforowania wpisów dziennika w strefie DMZ. Nagrywanie odbywa się za pomocą kilku Filebeat → LogStash. Odczyt odbywa się spoza DMZ przez LogStash. Podczas działania przez RabboitMQ przetwarzanych jest około 4 tysięcy wiadomości na sekundę.

Routing wiadomości jest konfigurowany według nazwy systemu, tj. na podstawie danych konfiguracyjnych FileBeat. Wszystkie wiadomości trafiają do jednej kolejki. Jeśli z jakiegoś powodu usługa kolejkowania zostanie zatrzymana, nie doprowadzi to do utraty wiadomości: FileBeats otrzyma błędy połączenia i tymczasowo zawiesi wysyłanie. A LogStash, który czyta z kolejki, również otrzyma błędy sieciowe i zaczeka na przywrócenie połączenia. W takim przypadku dane oczywiście nie będą już zapisywane w bazie danych.

Poniższe instrukcje służą do tworzenia i konfigurowania kolejek:

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. Pulpity nawigacyjne

Ten komponent służy do wizualizacji danych monitoringu. W takim przypadku musisz zainstalować źródło danych ClickHouse dla wtyczki Grafana 4.6+. Musieliśmy go trochę zmodyfikować, aby poprawić efektywność przetwarzania filtrów SQL na dashboardzie.

Np. używamy zmiennych i jeśli nie są one ustawione w polu filtru, to chcielibyśmy, aby nie generował warunku w miejscu WHERE formularza ( uriStem = » AND uriStem != » ). W takim przypadku ClickHouse odczyta kolumnę uriStem. Generalnie próbowaliśmy różnych opcji i ostatecznie poprawiliśmy wtyczkę (makro $valueIfEmpty) tak, aby w przypadku pustej wartości zwracała 1, nie wspominając o samej kolumnie.

A teraz możesz użyć tego zapytania dla wykresu

$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')

co przekłada się na ten SQL (zwróć uwagę, że puste pola uriStem zostały przekonwertowane na tylko 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

wniosek

Pojawienie się bazy ClickHouse stało się przełomowym wydarzeniem na rynku. Trudno było sobie wyobrazić, że w jednej chwili, całkowicie za darmo, otrzymamy potężne i praktyczne narzędzie do pracy z big data. Oczywiście wraz ze wzrostem potrzeb (na przykład sharding i replikacja na wiele serwerów) schemat będzie się komplikował. Ale na pierwszy rzut oka praca z tą bazą danych jest bardzo przyjemna. Widać, że produkt jest stworzony „dla ludzi”.

Szacuje się, że w porównaniu z ElasticSearch koszt przechowywania i przetwarzania logów jest niższy od pięciu do dziesięciu razy. Innymi słowy, jeśli na obecną ilość danych musielibyśmy postawić klaster kilku maszyn, to korzystając z ClickHouse wystarczy nam jedna maszyna o niskim poborze mocy. Tak, oczywiście ElasticSearch ma również mechanizmy kompresji danych na dysku i inne funkcje, które mogą znacznie zmniejszyć zużycie zasobów, ale w porównaniu z ClickHouse będzie to droższe.

Bez żadnych specjalnych optymalizacji z naszej strony, na ustawieniach domyślnych ładowanie danych i wybieranie z bazy działa z niesamowitą szybkością. Nie mamy jeszcze zbyt wielu danych (około 200 milionów rekordów), ale sam serwer jest słaby. Możemy wykorzystywać to narzędzie w przyszłości do innych celów niezwiązanych z przechowywaniem logów. Na przykład do kompleksowej analityki, w dziedzinie bezpieczeństwa, uczenia maszynowego.

Na koniec trochę o zaletach i wadach.

Wady

  1. Ładowanie rekordów w dużych partiach. Z jednej strony jest to funkcja, ale nadal musisz użyć dodatkowych komponentów do buforowania rekordów. To zadanie nie zawsze jest łatwe, ale wciąż możliwe do rozwiązania. I chciałbym uprościć schemat.
  2. Niektóre egzotyczne funkcje lub nowe funkcje często psują się w nowych wersjach. Powoduje to niepokój, zmniejszając chęć aktualizacji do nowej wersji. Na przykład silnik tabel Kafki jest bardzo przydatną funkcją, która pozwala bezpośrednio odczytywać zdarzenia z Kafki, bez implementacji konsumentów. Ale sądząc po liczbie problemów na githubie, nadal uważamy, aby nie używać tego silnika w produkcji. Jeśli jednak nie wykonujesz gwałtownych gestów w bok i korzystasz z głównej funkcjonalności, to działa stabilnie.

Plusy

  1. Nie zwalnia.
  2. Niski próg wejścia.
  3. Otwarte źródło.
  4. Bezpłatny.
  5. Dobrze skaluje się (sharding/replikacja po wyjęciu z pudełka)
  6. Wpisany do rejestru rosyjskiego oprogramowania rekomendowanego przez Ministerstwo Łączności.
  7. Obecność oficjalnego wsparcia Yandex.

Źródło: www.habr.com

Dodaj komentarz