日志是分析线上问题的重要手段,通常我们会把日志输出到控制台或者本地文件中,排查问题时通过根据关键字搜索本地日志,但越来越多的公司,项目开发中采用分布式的架构,日志会记录到多个服务器或者文件中,分析问题时可能需要查看多个日志文件才能定位问题,如果相关项目不是一个团队维护时沟通成本更是直线上升。把各个系统的日志聚合并通过关键字链接一个事务处理请求,是分析分布式系统问题的有效的方式。

ELK(elasticsearch+logstash+kibana)是目前比较常用的日志分析系统,包括日志收集(logstash),日志存储搜索(elasticsearch),展示查询(kibana),我们使用ELK作为日志的存储分析系统并通过为每个请求分配requestId链接相关日志。ELK具体结构如下图所示:

1、安装logstash
logstash需要依赖jdk,安装logstash之前先安装java环境。
下载JDK:
在oracle的官方网站下载,http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html
根据操作系统的版本下载对应的JDK安装包,本次实验下载的是jdk-8u101-linux-x64.tar.gz
上传文件到服务器并执行:
# mkdir /usr/local/java
# tar -zxf jdk-8u45-linux-x64.tar.gz -C /usr/local/java/
配置java环境

export JAVA_HOME=/usr/local/java/jdk1.8.0_45
export PATH=$PATH:$JAVA_HOME/bin
export CLASSPATH=.:$JAVA_HOME/lib/tools.jar:$JAVA_HOME/lib/dt.jar:$CLASSPATH

执行java -version命令,打印出java版本信息表示JDK配置成功。

下载logstash:
wget https://download.elastic.co/logstash/logstash/logstash-2.4.0.tar.gz
tar -xzvf logstash-2.4.0.tar.gz
进入安装目录: cd #{dir}/logstash-2.4.0
创建logstash测试配置文件:
vim test.conf
编辑内容如下:

input {
stdin { }
}
output {
stdout {
codec => rubydebug {}
}
}

运行logstash测试:
bin/logstash -f test.conf
显示

证明logstash已经启动了,
输入hello world

因为我们配置内容为,控制台输出日志内容,所以显示以上格式即为成功。
2、安装elasticsearch
下载安装包:
wget https://download.elastic.co/elasticsearch/release/org/elasticsearch/distribution/tar/elasticsearch/2.4.0/elasticsearch-2.4.0.tar.gz
解压并配置:
tar -xzvf elasticsearch-2.4.0.tar.gz
cd #{dir}/elasticsearch-2.4.0
vim config/elasticsearch.yml
修改:

path.data: /data/es #数据路径
path.logs: /data/logs/es #日志路径
network.host: 本机地址 #服务器地址
http.port: 9200 #端口

配置执行用户和目录:

groupadd elsearch
useradd elsearch -g elsearch -p elasticsearch
chown -R elsearch:elsearch elasticsearch-2.4.0
mkdir /data/es
mkdir /data/logs/es
chown -R elsearch:elsearch /data/es
chown -R elsearch:elsearch /data/logs/es

启动elasticsearch:
su elsearch
bin/elasticsearch
通过浏览器访问:

安装成功.
集成logstash和elasticsearch,修改Logstash配置为:

input {
stdin { }
}
output {
elasticsearch {
hosts => "elasticsearchIP:9200"
index => "logstash-test"
}
stdout {
codec => rubydebug {}
}
}

再次启动logstash,并输入任意文字:“hello elasticsearch”

通过elasticsearch搜索到了刚才输入的文字,集成成功。
但是通过elasticsearch的原生接口查询和展示都不够便捷直观,下面我们配置一下更方便的查询分析工具kibana。
3、安装kibana
下载安装包:
wget https://download.elastic.co/kibana/kibana/kibana-4.6.1-linux-x86_64.tar.gz
解压kibana,并进入解压后的目录
打开config/kibana.yml,修改如下内容
#启动端口 因为端口受限 所以变更了默认端口
server.port: 8601
#启动服务的ip
server.host: “本机ip”
#elasticsearch地址
elasticsearch.url: “http://elasticsearchIP:9200”
启动程序:
bin/kibana
访问配置的ip:port,在discover中搜索刚才输入的字符,内容非常美观的展示了出来。

到这里我们的elk环境已经配置完成了,我们把已java web项目试验日志在elk中的使用。
4、创建web工程
一个普通的maven java web工程,为了测试分布式系统日志的连续性,我们让这个项目自调用n次,并部署2个项目,相互调用,关键代码如下:

@RequestMapping("http_client")
@Controller
public class HttpClientTestController { @Autowired
private HttpClientTestBo httpClientTestBo; @RequestMapping(method = RequestMethod.POST)
@ResponseBody
public BaseResult doPost(@RequestBody HttpClientTestResult result) {
HttpClientTestResult testPost = httpClientTestBo.testPost(result);
return testPost;
}
}
@Service
public class HttpClientTestBo { private static Logger logger = LoggerFactory.getLogger(HttpClientTestBo.class); @Value("${test_http_client_url}")
private String testHttpClientUrl; public HttpClientTestResult testPost(HttpClientTestResult result) {
logger.info(JSONObject.toJSONString(result));
result.setCount(result.getCount() + 1);
if (result.getCount() <= 3) {
Map<String, String> headerMap = new HashMap<String, String>();
String requestId = RequestIdUtil.requestIdThreadLocal.get();
headerMap.put(RequestIdUtil.REQUEST_ID_KEY, requestId);
Map<String, String> paramMap = new HashMap<String, String>();
paramMap.put("status", result.getStatus() + "");
paramMap.put("errorCode", result.getErrorCode());
paramMap.put("message", result.getMessage());
paramMap.put("count", result.getCount() + "");
String resultString = JsonHttpClientUtil.post(testHttpClientUrl, headerMap, paramMap, "UTF-8");
logger.info(resultString);
} logger.info(JSONObject.toJSONString(result));
return result;
}
}

为了表示调用的链接性我们在web.xml中配置requestId的filter,用于创建requestId:

<filter>
<filter-name>requestIdFilter</filter-name>
<filter-class>com.virxue.baseweb.utils.RequestIdFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>requestIdFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
public class RequestIdFilter implements Filter {
private static final Logger logger = LoggerFactory.getLogger(RequestIdFilter.class); /* (non-Javadoc)
* @see javax.servlet.Filter#init(javax.servlet.FilterConfig)
*/
public void init(FilterConfig filterConfig) throws ServletException {
logger.info("RequestIdFilter init");
} /* (non-Javadoc)
* @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain)
*/
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException,
ServletException {
String requestId = RequestIdUtil.getRequestId((HttpServletRequest) request);
MDC.put("requestId", requestId);
chain.doFilter(request, response);
RequestIdUtil.requestIdThreadLocal.remove();
MDC.remove("requestId");
} /* (non-Javadoc)
* @see javax.servlet.Filter#destroy()
*/
public void destroy() { }
}
public class RequestIdUtil {
public static final String REQUEST_ID_KEY = "requestId";
public static ThreadLocal&lt;String&gt; requestIdThreadLocal = new ThreadLocal&lt;String&gt;(); private static final Logger logger = LoggerFactory.getLogger(RequestIdUtil.class); /**
* 获取requestId
* @Title getRequestId
* @Description TODO
* @return
*
* @author sunhaojie 3113751575@qq.com
* @date 2016年8月31日 上午7:58:28
*/
public static String getRequestId(HttpServletRequest request) {
String requestId = null;
String parameterRequestId = request.getParameter(REQUEST_ID_KEY);
String headerRequestId = request.getHeader(REQUEST_ID_KEY); if (parameterRequestId == null &amp;&amp; headerRequestId == null) {
logger.info("request parameter 和header 都没有requestId入参");
requestId = UUID.randomUUID().toString();
} else {
requestId = parameterRequestId != null ? parameterRequestId : headerRequestId;
} requestIdThreadLocal.set(requestId); return requestId;
}
}

我们使使用了Logback作为日志输出的插件,并且使用它的MDC类,可以无侵入的在任何地方输出requestId,具体的配置如下:

<configuration>
<appender name="logfile" class="ch.qos.logback.core.rolling.RollingFileAppender">
<Encoding>UTF-8</Encoding>
<File>${log_base}/java-base-web.log</File>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<FileNamePattern>${log_base}/java-base-web-%d{yyyy-MM-dd}-%i.log</FileNamePattern>
<MaxHistory>10</MaxHistory>
<TimeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<MaxFileSize>200MB</MaxFileSize>
</TimeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<layout class="ch.qos.logback.classic.PatternLayout">
<pattern>%d^|^%X{requestId}^|^%-5level^|^%logger{36}%M^|^%msg%n</pattern>
</layout>
</appender>
<root level="info">
<appender-ref ref="logfile" />
</root>
</configuration>

这里的日志格式使用了“^|^”做为分隔符,方便logstash进行切分。在测试服务器部署2个web项目,并且修改日志输出位置,并修改url调用链接使项目相互调用。

5、修改logstash读取项目输出日志:
新增stdin.conf,内容如下:

input {
file {
path => ["/data/logs/java-base-web1/java-base-web.log", "/data/logs/java-base-web2/java-base-web.log"]
type => "logs"
start_position => "beginning"
codec => multiline {
pattern => "^\[\d{4}-\d{1,2}-\d{1,2}\s\d{1,2}:\d{1,2}:\d{1,2}"
negate => true
what => "next"
}
}
}
filter{
mutate{
split=>["message","^|^"]
add_field => {
"messageJson" => "{datetime:%{[message][0]}, requestId:%{[message][1]},level:%{[message][2]}, class:%{[message][3]}, content:%{[message][4]}}"
}
remove_field => ["message"]
} }
output {
elasticsearch {
hosts => "10.160.110.48:9200"
index => "logstash-${type}"
}
stdout {
codec => rubydebug {}
}
}

其中path为日志文件地址;codec => multiline为处理Exception日志,使换行的异常内容和异常头分割在同一个日志中;filter为日志内容切分,把日志内容做为json格式,方便查询分析;

测试一下:

使用POSTMan模拟调用,提示服务器端异常:
通过界面搜索”调用接口异常”,共两条数据。

使用其中一条数据的requestId搜索,展示出了请求再系统中和系统间的执行过程,方便了我们排查错误。

到这里我们实验了使用elk配置日志分析,其中很多细节需要更好的处理,欢迎更多的同学交流学习。

ELK 实现 Java 分布式系统日志分析架构的更多相关文章

  1. ELK(ElasticSearch, Logstash, Kibana) 实现 Java 分布式系统日志分析架构

    一.首先理解为啥要使用ELK 日志主要分为三类:系统日志.应用程序日志和安全日志.系统运维和开发人员可以通过日志了解服务器软硬件信息.检查配置过程中的错误及错误发生的原因.通过分析日志可以了解服务器的 ...

  2. 学Java分布式和高架构,必懂的两大知识点!

    今天小编为你们分享阿里巴巴2018年招聘应届毕业生,Java工程师的面试考题,主要分为三种 Java中获取 mysql连接的方式: 第一部分:分布式   三步变成:分布式 1.将你的整个软件视为一个系 ...

  3. 万字长文:ELK(V7)部署与架构分析

    ELK(7版本)部署与架构分析 1.ELK的背景介绍与应用场景 在项目应用运行的过程中,往往会产生大量的日志,我们往往需要根据日志来定位分析我们的服务器项目运行情况与BUG产生位置.一般情况下直接在日 ...

  4. ELK(V7)部署与架构分析

    1.ELK的背景介绍与应用场景 在项目应用运行的过程中,往往会产生大量的日志,我们往往需要根据日志来定位分析我们的服务器项目运行情况与BUG产生位置.一般情况下直接在日志文件中tailf. grep. ...

  5. ELK、ELFK企业级日志分析系统

    ELK.ELFK企业级日志分析系统 目录 ELK.ELFK企业级日志分析系统 一.ELK日志分析系统 1. ELK简介 1.2 ElasticSearch 1.3 Logstash 1.4 Kiban ...

  6. ELK+redis搭建nginx日志分析平台

    ELK+redis搭建nginx日志分析平台发表于 2015-08-19   |   分类于 Linux/Unix   |  ELK简介ELKStack即Elasticsearch + Logstas ...

  7. centos7搭建ELK Cluster集群日志分析平台(一):Elasticsearch

    应用场景: ELK实际上是三个工具的集合,ElasticSearch + Logstash + Kibana,这三个工具组合形成了一套实用.易用的监控架构, 很多公司利用它来搭建可视化的海量日志分析平 ...

  8. centos7搭建ELK Cluster集群日志分析平台

    应用场景:ELK实际上是三个工具的集合,ElasticSearch + Logstash + Kibana,这三个工具组合形成了一套实用.易用的监控架构, 很多公司利用它来搭建可视化的海量日志分析平台 ...

  9. 使用elk+redis搭建nginx日志分析平台

    elk+redis 搭建nginx日志分析平台 logstash,elasticsearch,kibana 怎么进行nginx的日志分析呢?首先,架构方面,nginx是有日志文件的,它的每个请求的状态 ...

随机推荐

  1. 6.2 dubbo在spring中自定义xml标签源码解析

    在6.1 如何在spring中自定义xml标签中我们看到了在spring中自定义xml标签的方式.dubbo也是这样来实现的. 一 META_INF/dubbo.xsd 比较长,只列出<dubb ...

  2. Poly2Tri介绍[转]

    https://blog.csdn.net/xys206006/article/details/83002326 这是Poly2Tri介绍的翻译博文.原文链接:http://sites-final.u ...

  3. [leetcode]Word Break @ Python

    原题地址:https://oj.leetcode.com/problems/word-break/ 题意: Given a string s and a dictionary of words dic ...

  4. HTML5基础扩展——地理位置、本地存储、缓存

    HTML5扩展,继上两篇博客,我们来看一下HTML5的一些扩展的功能,由于HTML5更多是为了兼容电脑浏览器,安卓浏览器,苹果浏览器更多浏览器,或者说为这些浏览器提供一个统一的标准.因此目前在手机上的 ...

  5. SQLServer 数据库镜像+复制切换方案

    目标: 主机做了Mirror和Replication,当主机出现问题时,Replication和Mirror实现自动的故障转移(Mirror 和Replication都切换到备机,而当主机 重新启动后 ...

  6. vue里ref ($refs)用法

    ref 有三种用法: 1.ref 加在普通的元素上,用this.ref.name 获取到的是dom元素 2.ref 加在子组件上,用this.ref.name 获取到的是组件实例,可以使用组件的所有方 ...

  7. Android之ASD组件(一)

    Google在android5.0之后推出新设计标准Material Design,为了能在低版本上使用Material Design,google发布了Android Support Design支 ...

  8. Android -- 系统和自定义Notification

    Notification是一种让你的应用程序在不使用Activity的情况下警示用户,Notification是看不见的程序组件警示用户有需要注意的事件发生的最好途径. 作为UI部分,Notifica ...

  9. Get file extention in XSLT

      When working with data view web parts or data form web parts in SharePoint, you might want to use ...

  10. Andriod NDK编译的时候无法使用math.h的函数。

    编译NDK项目的时候,当用到sinf的时候,编译报错: 选中sinf函数,右键Go to declaration, 发现math.h的文件路径是: c:\ProgramData\Microsoft\A ...