详解SpringBoot+Dubbo集成ELK实战

前言

一直以来,日志始终伴随着我们的开发和运维过程。当系统出现了Bug,往往就是通过Xshell连接到服务器,定位到日志文件,一点点排查问题来源。

随着互联网的快速发展,我们的系统越来越庞大。依赖肉眼分析日志文件来排查问题的方式渐渐凸显出一些问题:

  • 分布式集群环境下,服务器数量可能达到成百上千,如何准确定位?
  • 微服务架构中,如何根据异常信息,定位其他各服务的上下文信息?
  • 随着日志文件的不断增大,可能面临在服务器上不能直接打开的尴尬。
  • 文本搜索太慢、无法多维度查询等

面临这些问题,我们需要集中化的日志管理,将所有服务器节点上的日志统一收集,管理,访问。

而今天,我们的手段的就是使用 Elastic Stack 来解决它们。

一、什么是Elastic Stack ?

或许有人对Elastic感觉有一点点陌生,它的前生正是ELK ,Elastic Stack 是ELK Stack的更新换代产品。

Elastic Stack分别对应了四个开源项目。

Beats

Beats 平台集合了多种单一用途数据采集器,它负责采集各种类型的数据。比如文件、系统监控、Windows事件日志等。

Logstash

Logstash 是服务器端数据处理管道,能够同时从多个来源采集数据,转换数据。没错,它既可以采集数据,也可以转换数据。采集到了非结构化的数据,通过过滤器把他格式化成友好的类型。

Elasticsearch

Elasticsearch 是一个基于 JSON 的分布式搜索和分析引擎。作为 Elastic Stack 的核心,它负责集中存储数据。我们上面利用Beats采集数据,通过Logstash转换之后,就可以存储到Elasticsearch。

Kibana

最后,就可以通过 Kibana,对自己的 Elasticsearch 中的数据进行可视化。

本文的实例是通过 SpringBoot+Dubbo 的微服务架构,结合 Elastic Stack 来整合日志的。架构如下:

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

二、采集、转换

1、FileBeat

在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 如何将匹配的行组合到事件中,设置为after或before。

听起来可能比较饶口,我们来看一组配置:

# 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、Logstash

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过滤器是一种拆分操作。与将一个定界符应用于整个字符串的常规拆分操作不同,此操作将一组定界符应用于字符串值。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 的条目:

三、追踪

试想一下,我们在前端发送了一个订单请求。如果后端系统是微服务架构,可能会经由库存系统、优惠券系统、账户系统、订单系统等多个服务。如何追踪这一个请求的调用链路呢?

1、MDC机制

首先,我们要了解一下MDC机制。

MDC - Mapped Diagnostic Contexts ,实质上是由日志记录框架维护的映射。其中应用程序代码提供键值对,然后可以由日志记录框架将其插入到日志消息中。

简而言之,我们使用了 MDC.PUT(key,value) ,那么 Logback 就可以在日志中自动打印这个value。

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、Dubbo Filter

另外还有一个问题,就是在微服务架构下我们怎么让这个 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");
   }
  }
 }
}

这样写完,我们就可以愉快的查看某一次请求所有的日志信息啦。比如下面的请求,订单服务和库存服务两个系统的日志。

四、总结

本文介绍了 Elastic Stack 的基本概念。并通过一个 SpringBoot+Dubbo 项目,演示如何做到日志的集中化管理、追踪。

事实上, Kibana 具有更多的分析和统计功能。所以它的作用不仅限于记录日志。

另外 Elastic Stack 性能也很不错。笔者在一台虚拟机上,记录了100+万条用户数据,index大小为1.1G,查询和统计速度也不逊色。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Spring boot 集成Dubbox的方法示例

    前言 因为工作原因,需要在项目中集成dubbo,所以去查询dubbo相关文档,发现dubbo目前已经不更新了,所以把目光投向了dubbox,dubbox是当当网基于dubbo二次开发的一个项目,dubbox,因为公司项目中一个是基于spring mvc 3.0的,一个是基于spring boot的,而spring boot相对来说文档少一点,所以此文记录下spring boot下如何继承dubbox 一.安装zookeeper 1.zookeeper简介 ZooKeeper是一个分布式的,开放源

  • Spring Boot 集成Dubbo框架实例

    使用Spring Boot 与Dubbo集成,这里我之前尝试了使用注解的方式,简单的使用注解注册服务其实是没有问题的,但是当你涉及到使用注解的时候在服务里面引用事务,注入其他对象的时候,会有一些问题.于是我就果断放弃了注解了,使用的是XML,这里可能介绍的是Dubbo,但是如果使用Dubbox的话,基本上是兼容的.接下来,将说说使用XML的方式与Spring Boot在一起开发. 1.创建工程在pom.xml中加入依赖 创建工程名为: (1)springboot-dubbo-provide (2

  • springboot集成dubbo注解版的示例代码

    工作中用springboot搭建项目,用dubbo做远程调用.springboot提倡注解配置和java配置,本文是基于dubbo最新版本2.6.3,使用注解方式的示例. 本文假定你已经有springboot和dubbo的使用经验. dubbo简介 dubbo是阿里巴巴开源的分布式服务框架,一般使用dubbo的RPC调用.但2016年停止维护,现在使用的2.8.4版本其实是当当维护的dubbox.2017年8月阿里又重启维护dubbo,并从2.5.7版本开始支持注解配置. 准备 此示例使用gra

  • 详解Springboot整合Dubbo之代码集成和发布

    本文介绍了Springboot整合Dubbo之代码集成和发布,分享给大家,具体如下: 1. boot-dubbo-api相关 打开boot-dubbo-api项目,正在src/main/java下创建一个包,并创建你需要dubbo暴露的接口TestService.java,并创建一个实体类用于测试User.java.如下图所示: 创建文件和包结构 User.java package com.boot.domain; import lombok.Data; import java.io.Seria

  • 详解SpringBoot+Dubbo集成ELK实战

    前言 一直以来,日志始终伴随着我们的开发和运维过程.当系统出现了Bug,往往就是通过Xshell连接到服务器,定位到日志文件,一点点排查问题来源. 随着互联网的快速发展,我们的系统越来越庞大.依赖肉眼分析日志文件来排查问题的方式渐渐凸显出一些问题: 分布式集群环境下,服务器数量可能达到成百上千,如何准确定位? 微服务架构中,如何根据异常信息,定位其他各服务的上下文信息? 随着日志文件的不断增大,可能面临在服务器上不能直接打开的尴尬. 文本搜索太慢.无法多维度查询等 面临这些问题,我们需要集中化的

  • 详解SpringBoot结合策略模式实战套路

    1.1. 前言 我们都知道设计模式好,可以让我们的代码更具可读性,扩展性,易于维护,但大部分程序猿一开始都学过至少一遍设计模式吧,实战中不知用到了几成.接下来让我介绍一个结合SpringBoot的策略模式套路,让你的代码少些if-else 1.2. 开撸 废话不多说,直接告诉你今天的核心是@autowired,看到这个是不是很熟悉,你每天都在用,不就是自动注入Spring管理的Bean吗?但我们对它的用法很多时候就局限在全局变量的注入了,忘记了,它其实还可以构造器注入,类型注入或命名注入,那么结

  • 详解SpringBoot之集成Spring AOP

    在开始之前,我们先把需要的jar包添加到工程里.新增Maven依赖如下: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> 接下来,我们进入正题.这里的涉及的通知类型有:前置通知.后置最终通知.后置返回通知.后置异常通知.环绕通知,下面我们就具体的

  • 详解SpringBoot基于Dubbo和Seata的分布式事务解决方案

    1. 分布式事务初探 一般来说,目前市面上的数据库都支持本地事务,也就是在你的应用程序中,在一个数据库连接下的操作,可以很容易的实现事务的操作. 但是目前,基于SOA的思想,大部分项目都采用微服务架构后,就会出现了跨服务间的事务需求,这就称为分布式事务. 本文假设你已经了解了事务的运行机制,如果你不了解事务,那么我建议先去看下事务相关的文章,再来阅读本文. 1.1 什么是分布式事务 对于传统的单体应用而言,实现本地事务可以依赖Spring的@Transactional注解标识方法,实现事务非常简

  • 详解Springboot集成sentinel实现接口限流入门

    Sentinel是阿里巴巴开源的限流器熔断器,并且带有可视化操作界面. 在日常开发中,限流功能时常被使用,用于对某些接口进行限流熔断,譬如限制单位时间内接口访问次数:或者按照某种规则进行限流,如限制ip的单位时间访问次数等. 之前我们已经讲过接口限流的工具类ratelimter可以实现令牌桶的限流,很明显sentinel的功能更为全面和完善.来看一下sentinel的简介: https://github.com/spring-cloud-incubator/spring-cloud-alibab

  • 详解SpringBoot中的参数校验(项目实战)

    Java后端发工作中经常会对前端传递过来的参数做一些校验,在业务中还要抛出异常或者不断的返回异常时的校验信息,充满了if-else这种校验代码,在代码中相当冗长.例如说,用户注册时,会校验手机格式的正确性,用户名的长度等等.虽说前端也可以做参数校验,但是为了保证我们API接口的可靠性,以保证最终数据入库的正确性,后端进行参数校验不可忽视. Hibernate Validator 提供了一种统一方便的方式,让我们快速的实现参数校验. Hibernate Validator 使用注解,实现声明式校验

  • 详解SpringBoot集成消息队列的案例应用

    目录 背景 方案规划 统一设计 集成Redis消息队列 集成ActiveMQ消息队列 使用示例 背景 最近在对公司开发框架进行优化,框架内涉及到多处入库的日志记录,例如登录日志/操作日志/访问日志/业务执行日志,集成在业务代码中耦合度较高且占用业务操作执行时间,所以准备集成相关消息队列进行代码解耦 方案规划 现有的成熟消息队列组件非常多,例如RabbitMQ,ActiveMQ,Kafka等,考虑到业务并发量不高且框架已经应用于多个项目平稳运行,准备提供基于Redis的消息队列和集成ActiveM

  • 详解springboot整合ehcache实现缓存机制

    EhCache 是一个纯Java的进程内缓存框架,具有快速.精干等特点,是Hibernate中默认的CacheProvider. ehcache提供了多种缓存策略,主要分为内存和磁盘两级,所以无需担心容量问题. spring-boot是一个快速的集成框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程.该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置. 由于spring-boot无需任何样板化的配置文件,所以spring-boot集成一些其他框架时会有略微的

  • 详解SpringBoot 应用如何提高服务吞吐量

    意外和明天不知道哪个先来.没有危机是最大的危机,满足现状是最大的陷阱. 背景 生产环境偶尔会有一些慢请求导致系统性能下降,吞吐量下降,下面介绍几种优化建议. 方案 1.undertow替换tomcat 电子商务类型网站大多都是短请求,一般响应时间都在100ms,这时可以将web容器从tomcat替换为undertow,下面介绍下步骤: 1.增加pom配置 <dependency> <groupid> org.springframework.boot </groupid>

  • 详解@ConfigurationProperties实现原理与实战

    在SpringBoot中,当需要获取到配置文件数据时,除了可以用Spring自带的@Value注解外,SpringBoot提供了一种更加方便的方式:@ConfigurationProperties.只要在bean上添加上这个注解,指定好配置文件的前缀,那么对应的配置文件数据就会自动填充到bean中.举个栗子,现在有如下配置: myconfig.name=test myconfig.age=22 myconfig.desc=这是我的测试描述 添加对应的配置类,并添加上注解@Configuratio

随机推荐