SpringBoot + Dubbo интегрированный бой ELK

Java

предисловие

Журналы всегда сопровождали наши процессы разработки и эксплуатации. Когда в системе есть ошибка, она часто подключается к серверу через Xshell, находит файл журнала и постепенно исследует источник проблемы.

С быстрым развитием Интернета наши системы становятся все больше и больше. Опора на визуальный анализ файлов журналов для устранения неполадок вышла на первый план:

  • В распределенной кластерной среде количество серверов может достигать сотен и тысяч, как их точно разместить?
  • Как в микросервисной архитектуре найти контекстную информацию других сервисов на основе информации об исключениях?
  • Поскольку файл журнала продолжает расти, он может столкнуться с тем, что его нельзя открыть непосредственно на сервере.
  • Текстовый поиск работает слишком медленно, не может выполнять запросы в нескольких измерениях и т. д.

Столкнувшись с этими проблемами, нам необходимо централизованное управление журналами для унифицированного сбора, управления и доступа к журналам на всех узлах сервера.

И сегодня наши средства - использоватьElastic Stackчтобы решить их.

1. Что такое эластичный стек?

Некоторые люди могут чувствовать себя немного незнакомыми с Elastic, поскольку его предшественником является ELK, а Elastic Stack является заменой продукта ELK Stack.

Elastic Stack соответствует четырем проектам с открытым исходным кодом.

  • Beats

Платформа Beats представляет собой набор одноцелевых сборщиков данных, которые собирают различные типы данных. Такие как файлы, системный мониторинг, журнал событий Windows и т. д.

  • Logstash

Logstash — это конвейер обработки данных на стороне сервера, способный одновременно принимать и преобразовывать данные из нескольких источников. Да, он может как собирать данные, так и преобразовывать их. Неструктурированные данные собираются и форматируются в удобные типы с помощью фильтров.

  • Elasticsearch

Elasticsearch — это распределенная система поиска и аналитики на основе JSON. Как ядро ​​Elastic Stack, он отвечает за централизованное хранение данных. Мы использовали Beats для сбора данных выше, и после преобразования через Logstash их можно сохранить в Elasticsearch.

  • Kibana

Наконец, вы можете визуализировать данные в своем собственном Elasticsearch через Kibana.

Примеры в этой статьеSpringBoot+Dubboмикросервисная архитектура в сочетании сElastic Stackинтегрировать журналы. Архитектура выглядит следующим образом:

注意,阅读本文需要了解ELK组件的基本概念和安装。本文不涉及安装和基本配置过程,重点是如何与项目集成,达成上面的需求。

2. Сбор и преобразование

1. ФайлБит

В проекте SpringBoot мы сначала настраиваем Logback, чтобы определить расположение файла журнала.

<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
	<file>${user.dir}/logs/order.log</file>
	<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
	    <fileNamePattern>${user.dir}/logs/order.%d{yyyy-MM-dd}.log</fileNamePattern>
	    <maxHistory>7</maxHistory>
	</rollingPolicy>
	<encoder>
	    <pattern></pattern>
	</encoder>
</appender>

FilebeatПредоставляет упрощенный метод пересылки и агрегирования журналов и файлов.

Итак, нам нужно рассказатьFileBeatРасположение файлов журнала и куда пересылать содержимое.

Как показано ниже, мы настроилиFileBeatчитатьusr/local/logsВсе файлы журнала по пути.

- type: log
  # Change to true to enable this input configuration.
  enabled: true
  # Paths that should be crawled and fetched. Glob based paths.
  paths:
    - /usr/local/logs/*.log

Тогда расскажиFileBeatНаправить собранные данные вLogstash.

#----------------------------- Logstash output --------------------------------
output.logstash:
  # The Logstash hosts
  hosts: ["192.168.159.128:5044"]

Кроме того,FileBeatПри сборе файловых данных они считываются построчно. ноFileBeatСобранные файлы могут содержать сообщения, состоящие из нескольких строк текста.

Например, преднамеренные новые строки в фреймворках с открытым исходным кодом:

2019-10-29 20:36:04.427  INFO  org.apache.dubbo.spring.boot.context.event.WelcomeLogoApplicationListener 
 :: Dubbo Spring Boot (v2.7.1) : https://github.com/apache/incubator-dubbo-spring-boot-project
 :: Dubbo (v2.7.1) : https://github.com/apache/incubator-dubbo
 :: Discuss group : dev@dubbo.apache.org

Или информация о стеке исключений Java:

2019-10-29 21:30:59.849 INFO com.viewscenes.order.controller.OrderController http-nio-8011-exec-2 开始获取数组内容...
java.lang.IndexOutOfBoundsException: Index: 3, Size: 0
	at java.util.ArrayList.rangeCheck(ArrayList.java:657)
	at java.util.ArrayList.get(ArrayList.java:433)

Итак, нам также необходимо настроитьmultiline, чтобы указать, какие строки являются частью одного события.

multiline.patternУказывает шаблон регулярного выражения для сопоставления.

multiline.negateОпределяет, является ли это отрицательным режимом.

multiline.matchКак сгруппировать совпадающие строки в событие, установленное после или до.

Возможно, это прозвучит грубо, давайте рассмотрим набор конфигураций:

# The regexp Pattern that has to be matched. The example pattern matches all lines starting with [
multiline.pattern: '^\<|^[[:space:]]|^[[:space:]]+(at|\.{3})\b|^java.'

# Defines if the pattern set under pattern should be negated or not. Default is false.
multiline.negate: false

# Match can be set to "after" or "before". It is used to define if lines should be append to a pattern
# that was (not) matched before or after or as long as a pattern is not matched based on negate.
# Note: After is the equivalent to previous and before is the equivalent to to next in Logstash
multiline.match: after

В приведенном выше файле конфигурации сказано, что если текстовое содержимое< 或 空格 或空格+at+包路径 或 java.начинается, то рассматривайте эту строку как продолжение предыдущей строки, а не как новую строку.

Приведенная выше информация о стеке исключений Java соответствует этой закономерности. так,FileBeatбудет

java.lang.IndexOutOfBoundsException: Index: 3, Size: 0
	at java.util.ArrayList.rangeCheck(ArrayList.java:657)
	at java.util.ArrayList.get(ArrayList.java:433)

это содержание как开始获取数组内容...часть.

2. Логсташ

существуетLogbackКогда мы печатаем журнал, мы обычно приносим уровень журнала, путь к классам выполнения, имя потока и другую информацию.

Важным сообщением является то, что мы находимся вELKПри просмотре журналов вы хотите разделить вышеуказанные условия для статистики или точного запроса?

Если да, то вам нужно использоватьLogstashФильтр, который анализирует отдельные события, идентифицирует именованные поля для построения структур и преобразовывает их в общий формат.

Тогда в это время мы должны сначала посмотреть, в каком формате мы настраиваем вывод журнала в проекте.

Например, наиболее знакомый нам формат JSON. Первый взглядLogbackКонфигурация:

<pattern>
    {"log_time":"%d{yyyy-MM-dd HH:mm:ss.SSS}","level":"%level","logger":"%logger","thread":"%thread","msg":"%m"}
</pattern>

Верно,LogstashВ фильтре также есть плагин для разбора JSON. Мы можем настроить это так:

input{ 
   stdin{}
}
filter{
   json {
      source => "message"
   }
}
output {
  stdout {}
}

Такая конфигурация означает, что данные форматируются с помощью парсера JSON. Вводим эту строку:

{
    "log_time":"2019-10-29 21:45:12.821",
    "level":"INFO",
    "logger":"com.viewscenes.order.controller.OrderController",
    "thread":"http-nio-8011-exec-1",
    "msg":"接收到订单数据."
}

Logstashвернет отформатированное содержимое:

Но синтаксический анализатор JSON не очень подходит, потому что поле msg в журнале, который мы печатаем, может само по себе быть форматом данных JSON.

Например:

{
    "log_time":"2019-10-29 21:57:38.008",
    "level":"INFO",
    "logger":"com.viewscenes.order.controller.OrderController",
    "thread":"http-nio-8011-exec-1",
    "msg":"接收到订单数据.{"amount":1000.0,"commodityCode":"MK66923","count":5,"id":1,"orderNo":"1001"}"
}

В это время анализатор JSON сообщит об ошибке. тогда что нам делать?

LogstashИмея богатую библиотеку подключаемых модулей фильтров или если вы уверены в регулярных выражениях, вы также можете написать соответствующие выражения.

как и мыLogbackВ соответствии с настройками был определен наш формат содержимого журнала, будь то формат JSON или другие форматы.

Поэтому автор сегодня рекомендует еще один: Dissect.

Dissect filter — это операция разделения. В отличие от обычной операции разделения, которая применяет один разделитель ко всей строке, эта операция применяет набор разделителей к строковому значению. Dissect не использует регулярные выражения и работает очень быстро.

Вот, например, автор|как разделитель.

input{ 
   stdin{}
}
filter{  
   dissect {
      mapping => {
	  "message" => "%{log_time}|%{level}|%{logger}|%{thread}|%{msg}"
     }
   }   
}
output {
  stdout {}
}

затем вLogbackЧтобы настроить формат журнала таким образом:

<pattern>
    %d{yyyy-MM-dd HH:mm:ss.SSS}|%level|%logger|%thread|%m%n
</pattern>

Наконец, правильный результат также может быть получен:

На данный момент завершены сбор данных и преобразование формата. Конечно, все приведенные выше конфигурации являются консольным вводом и выводом.

Давайте рассмотрим серьезную конфигурацию, которая начинается сFileBeatданные собираются вdissectПреобразуйте формат и выведите данные вelasticsearch.

input {
  beats {
    port => 5044
  }
}
filter{
   dissect {
      mapping => {
        "message" => "%{log_time}|%{level}|%{logger}|%{thread}|%{msg}"
     }
   }
   date{
      match => ["log_time", "yyyy-MM-dd HH:mm:ss.SSS"]
      target => "@timestamp"
   }
}
output {
  elasticsearch {
    hosts => ["192.168.216.128:9200"]
    index => "logs-%{+YYYY.MM.dd}"
  }
}

Если ничего другого, откройте браузер, и мы сможем просмотреть журнал в Kibana. Например, мы рассматриваем уровень журнала какDEBUGзапись:

3. Отслеживание

Представьте, что мы отправляем запрос на заказ на фронтенде. Если серверная система представляет собой микросервисную архитектуру, она может использовать несколько сервисов, таких как система инвентаризации, система купонов, система учетных записей, система заказов и т. д. Как отследить цепочку вызовов этого запроса?

1. Механизм МДК

Во-первых, нам нужно понять механизм MDC.

MDC — сопоставленные диагностические контексты, которые по сути являются сопоставлениями, поддерживаемыми структурой ведения журналов. где код приложения предоставляет пары ключ-значение, которые затем могут быть вставлены в сообщения журнала платформой ведения журнала.

Короче говоря, мы использовалиMDC.PUT(key,value),ТакLogbackЭто значение может быть автоматически распечатано в журнале.

существуетSpringBoot, мы можем сначала написатьHandlerInterceptor, перехватывать все запросы для созданияtraceId.

@Component
public class TraceIdInterceptor implements HandlerInterceptor {

    Snowflake snowflake = new Snowflake(1,0);

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler){
        MDC.put("traceId",snowflake.nextIdStr());
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView){
        MDC.remove("traceId");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex){}
}

затем вLogbackнастроить его, пусть этоtraceIdпоявляется в сообщениях журнала.

<pattern>
    %d{yyyy-MM-dd HH:mm:ss.SSS}|%level|%logger|%thread|%X{traceId}|%m%n
</pattern>

2. Фильтр Даббо

Другой вопрос, как заставить это работать в микросервисной архитектуре.traceIdПройти вперед и назад.

Знаком сDubboДрузья, можете подумать о неявных параметрах. Да, мы просто используем это, чтобы сделать это.traceIdкоробка передач.

@Activate(group = {Constants.PROVIDER, Constants.CONSUMER}, order = 99)
public class TraceIdFilter implements Filter {
    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {

        String tid = MDC.get("traceId");
        String rpcTid = RpcContext.getContext().getAttachment("traceId");

        boolean bind = false;
        if (tid != null) {
            RpcContext.getContext().setAttachment("traceId", tid);
        } else {
            if (rpcTid != null) {
                MDC.put("traceId",rpcTid);
                bind = true;
            }
        }
        try{
            return invoker.invoke(invocation);
        }finally {
            if (bind){
                MDC.remove("traceId");
            }
        }
    }
}

После написания этого мы можем с радостью просмотреть всю информацию журнала запроса. Например, следующий запрос, журналы двух систем обслуживания заказов и службы инвентаризации.

4. Резюме

В этой статье описываетсяElastic Stackосновная концепция. и черезSpringBoot+DubboПроект демонстрирует, как добиться централизованного управления и отслеживания журналов.

По факту,KibanaС дополнительным анализом и статистическими функциями. Так что его роль не ограничивается логированием.

Кроме тогоElastic StackПроизводительность тоже очень хорошая. Автор записал на виртуальную машину 1 млн+ пользовательских данных, размер индекса 1.1G, скорость запросов и статистики не уступает.