天天看点

SpringBoot+Dubbo集成ELK实战

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

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

分布式集群环境下,服务器数量可能达到成百上千,如何准确定位?

微服务架构中,如何根据异常信息,定位其他各服务的上下文信息?

随着日志文件的不断增大,可能面临在服务器上不能直接打开的尴尬。

文本搜索太慢、无法多维度查询等

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

而今天,我们的手段的就是使用 <code>ElasticStack</code>来解决它们。

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

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

Beats

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

Logstash

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

Elasticsearch

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

Kibana

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

本文的实例是通过 <code>SpringBoot+Dubbo</code>的微服务架构,结合 <code>ElasticStack</code>来整合日志的。架构如下:

SpringBoot+Dubbo集成ELK实战

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

在SpringBoot项目中,我们首先配置Logback,确定日志文件的位置。

<code>Filebeat</code>提供了一种轻量型方法,用于转发和汇总日志与文件。

所以,我们需要告诉 <code>FileBeat</code>日志文件的位置、以及向何处转发内容。

如下所示,我们配置了 <code>FileBeat</code>读取 <code>usr/local/logs</code>路径下的所有日志文件。

然后,告诉 <code>FileBeat</code>将采集到的数据转发到 <code>Logstash</code>。

另外, <code>FileBeat</code>采集文件数据时,是一行一行进行读取的。但是 <code>FileBeat</code>收集的文件可能包含跨越多行文本的消息。

例如,在开源框架中有意的换行:

或者Java异常堆栈信息:

所以,我们还需要配置 <code>multiline</code>,以指定哪些行是单个事件的一部分。

<code>multiline.pattern</code> 指定要匹配的正则表达式模式。

<code>multiline.negate</code> 定义是否为否定模式。

<code>multiline.match</code> 如何将匹配的行组合到事件中,设置为after或before。

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

<code># The regexp Pattern that has to be matched. The example pattern matches all lines starting with [</code>

<code>multiline.pattern: '^\&lt;|^[[:space:]]|^[[:space:]]+(at|\.{3})\b|^java.'</code>

<code># Defines if the pattern set under pattern should be negated or not. Default is false.</code>

<code>multiline.negate: false</code>

<code># Match can be set to "after" or "before". It is used to define if lines should be append to a pattern</code>

<code># that was (not) matched before or after or as long as a pattern is not matched based on negate.</code>

<code># Note: After is the equivalent to previous and before is the equivalent to to next in Logstash</code>

<code>multiline.match: after</code>

上面配置文件说的是,如果文本内容是以 <code>&lt; 或 空格 或空格+at+包路径 或 java.</code>开头,那么就将此行内容当做上一行的后续,而不是当做新的行。

就上面的Java异常堆栈信息就符合这个正则。所以, <code>FileBeat</code>会将

这些内容当做 <code>开始获取数组内容...</code>的一部分。

在 <code>Logback</code>中,我们打印日志的时候,一般会带上日志等级、执行类路径、线程名称等信息。

有一个重要的信息是,我们在 <code>ELK</code>查看日志的时候,是否希望将以上条件单独拿出来做统计或者精确查询?

如果是,那么就需要用到 <code>Logstash</code>过滤器,它能够解析各个事件,识别已命名的字段以构建结构,并将它们转换成通用格式。

那么,这时候就要先看我们在项目中,配置了日志以何种格式输出。

比如,我们最熟悉的JSON格式。先来看 <code>Logback</code>配置:

没错, <code>Logstash</code>过滤器中正好也有一个JSON解析插件。我们可以这样配置它:

这么一段配置就是说利用JSON解析器格式化数据。我们输入这样一行内容:

<code>Logstash</code>将会返回格式化后的内容:

但是JSON解析器并不太适用,因为我们打印的日志中msg字段本身可能就是JSON数据格式。

比如:

这时候JSON解析器就会报错。那怎么办呢?

<code>Logstash</code>拥有丰富的过滤器插件库,或者你对正则有信心,也可以写表达式去匹配。

正如我们在 <code>Logback</code>中配置的那样,我们的日志内容格式是已经确定的,不管是JSON格式还是其他格式。

所以,笔者今天推荐另外一种:Dissect。

Dissect过滤器是一种拆分操作。与将一个定界符应用于整个字符串的常规拆分操作不同,此操作将一组定界符应用于字符串值。Dissect不使用正则表达式,并且速度非常快。

比如,笔者在这里以 <code>|</code> 当做定界符。

然后在 <code>Logback</code>中这样去配置日志格式:

最后同样可以得到正确的结果:

SpringBoot+Dubbo集成ELK实战

到此,关于数据采集和格式转换都已经完成。当然,上面的配置都是控制台输入、输出。

我们来看一个正儿八经的配置,它从 <code>FileBeat</code>中采集数据,经由 <code>dissect</code>转换格式,并将数据输出到 <code>elasticsearch</code>。

不出意外的话,打开浏览器我们在Kibana中就可以对日志进行查看。比如我们查看日志等级为 <code>DEBUG</code>的条目:

SpringBoot+Dubbo集成ELK实战

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

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

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

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

在 <code>SpringBoot</code>中,我们就可以先写一个 <code>HandlerInterceptor</code>,拦截所有的请求,来生成一个 <code>traceId</code>。

<code>@Component</code>

<code>public class TraceIdInterceptor implements HandlerInterceptor {</code>

<code>   Snowflake snowflake = new Snowflake(1,0);</code>

<code>   @Override</code>

<code>   public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler){</code>

<code>       MDC.put("traceId",snowflake.nextIdStr());</code>

<code>       return true;</code>

<code>   }</code>

<code>   public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView){</code>

<code>       MDC.remove("traceId");</code>

<code>   public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex){}</code>

<code>}</code>

然后在 <code>Logback</code>中配置一下,让这个 <code>traceId</code>出现在日志消息中。

另外还有一个问题,就是在微服务架构下我们怎么让这个 <code>traceId</code>来回透传。

熟悉 <code>Dubbo</code>的朋友可能就会想到隐式参数。是的,我们就是利用它来完成 <code>traceId</code>的传递。

<code>@Activate(group = {Constants.PROVIDER, Constants.CONSUMER}, order = 99)</code>

<code>public class TraceIdFilter implements Filter {</code>

<code>   public Result invoke(Invoker&lt;?&gt; invoker, Invocation invocation) throws RpcException {</code>

<code>       String tid = MDC.get("traceId");</code>

<code>       String rpcTid = RpcContext.getContext().getAttachment("traceId");</code>

<code>       boolean bind = false;</code>

<code>       if (tid != null) {</code>

<code>           RpcContext.getContext().setAttachment("traceId", tid);</code>

<code>       } else {</code>

<code>           if (rpcTid != null) {</code>

<code>               MDC.put("traceId",rpcTid);</code>

<code>               bind = true;</code>

<code>           }</code>

<code>       }</code>

<code>       try{</code>

<code>           return invoker.invoke(invocation);</code>

<code>       }finally {</code>

<code>           if (bind){</code>

<code>               MDC.remove("traceId");</code>

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

SpringBoot+Dubbo集成ELK实战

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

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

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

SpringBoot+Dubbo集成ELK实战