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

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环境

  1. export JAVA_HOME=/usr/local/java/jdk1.8.0_45
  2. export PATH=$PATH:$JAVA_HOME/bin
  3. 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
编辑内容如下:

  1. input {
  2. stdin { }
  3. }
  4. output {
  5. stdout {
  6. codec => rubydebug {}
  7. }
  8. }

运行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
修改:

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

配置执行用户和目录:

  1. groupadd elsearch
  2. useradd elsearch -g elsearch -p elasticsearch
  3. chown -R elsearch:elsearch elasticsearch-2.4.0
  4. mkdir /data/es
  5. mkdir /data/logs/es
  6. chown -R elsearch:elsearch /data/es
  7. chown -R elsearch:elsearch /data/logs/es

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

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

  1. input {
  2. stdin { }
  3. }
  4. output {
  5. elasticsearch {
  6. hosts => "elasticsearchIP:9200"
  7. index => "logstash-test"
  8. }
  9. stdout {
  10. codec => rubydebug {}
  11. }
  12. }

再次启动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个项目,相互调用,关键代码如下:

  1. @RequestMapping("http_client")
  2. @Controller
  3. public class HttpClientTestController {
  4.  
  5. @Autowired
  6. private HttpClientTestBo httpClientTestBo;
  7.  
  8. @RequestMapping(method = RequestMethod.POST)
  9. @ResponseBody
  10. public BaseResult doPost(@RequestBody HttpClientTestResult result) {
  11. HttpClientTestResult testPost = httpClientTestBo.testPost(result);
  12. return testPost;
  13. }
  14. }
  1. @Service
  2. public class HttpClientTestBo {
  3.  
  4. private static Logger logger = LoggerFactory.getLogger(HttpClientTestBo.class);
  5.  
  6. @Value("${test_http_client_url}")
  7. private String testHttpClientUrl;
  8.  
  9. public HttpClientTestResult testPost(HttpClientTestResult result) {
  10. logger.info(JSONObject.toJSONString(result));
  11. result.setCount(result.getCount() + 1);
  12. if (result.getCount() <= 3) {
  13. Map<String, String> headerMap = new HashMap<String, String>();
  14. String requestId = RequestIdUtil.requestIdThreadLocal.get();
  15. headerMap.put(RequestIdUtil.REQUEST_ID_KEY, requestId);
  16. Map<String, String> paramMap = new HashMap<String, String>();
  17. paramMap.put("status", result.getStatus() + "");
  18. paramMap.put("errorCode", result.getErrorCode());
  19. paramMap.put("message", result.getMessage());
  20. paramMap.put("count", result.getCount() + "");
  21. String resultString = JsonHttpClientUtil.post(testHttpClientUrl, headerMap, paramMap, "UTF-8");
  22. logger.info(resultString);
  23. }
  24.  
  25. logger.info(JSONObject.toJSONString(result));
  26. return result;
  27. }
  28. }

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

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

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

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

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

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

  1. input {
  2. file {
  3. path => ["/data/logs/java-base-web1/java-base-web.log", "/data/logs/java-base-web2/java-base-web.log"]
  4. type => "logs"
  5. start_position => "beginning"
  6. codec => multiline {
  7. pattern => "^\[\d{4}-\d{1,2}-\d{1,2}\s\d{1,2}:\d{1,2}:\d{1,2}"
  8. negate => true
  9. what => "next"
  10. }
  11. }
  12. }
  13. filter{
  14. mutate{
  15. split=>["message","^|^"]
  16. add_field => {
  17. "messageJson" => "{datetime:%{[message][0]}, requestId:%{[message][1]},level:%{[message][2]}, class:%{[message][3]}, content:%{[message][4]}}"
  18. }
  19. remove_field => ["message"]
  20. }
  21.  
  22. }
  23. output {
  24. elasticsearch {
  25. hosts => "10.160.110.48:9200"
  26. index => "logstash-${type}"
  27. }
  28. stdout {
  29. codec => rubydebug {}
  30. }
  31. }

其中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. Pitch,Yaw,Roll的概念

    在航空中,pitch, yaw, roll下图所示. pitch是围绕X轴旋转,也叫做俯仰角. yaw是围绕Y轴旋转,也叫偏航角. roll是围绕Z轴旋转,也叫翻滚角.     在3D系统中,假设视点 ...

  2. 双语:Interprocess Communication 进程通信

    when one process creates a new process, the identity of the newly created process is passed to the p ...

  3. IIS7.5配置Gzip压缩解决方案(转)

    开启配置HTTP压缩(GZip) 在IIS7中配置Gzip压缩相比IIS6来说实在容易了许多,而且默认情况下就是启用GZip压缩的.如果没有,则可以再功能视图下找到“压缩”项,进入之后就会看到“静态内 ...

  4. 【Scala】Scala-case-参考资料

    Scala-case-参考资料 scala case_百度搜索 Scala School - 基础知识(续) scala case匹配值 - CSDN博客 scala入门教程:scala中的match ...

  5. 【Spark】SparkStreaming-加载外部配置文件

    SparkStreaming-加载外部配置文件 spark加载配置文件_百度搜索 Spark加载外部配置文件 - CSDN博客 spark读取配置文件中的配置 - CSDN博客 spark加载prop ...

  6. O2O、C2C、B2B、B2C

    一.O2O.C2C.B2B.B2C的区别在哪里? O2O是Online to offline 分为四种运营模式 1.Online to offline 是线上交易到线下消费体验 2.Offline t ...

  7. SharePoint中Rating相关的字段。

      From: https://sharepoint.stackexchange.com/questions/194197/how-to-manipulate-likeby-nooflikes-rat ...

  8. Python标准库:内置函数type(object)

    type(object) type(name, bases, dict) 本函数是返回对象的类型对象.仅仅有一个參数object时,直接返回对象的类型对象.假设仅仅是想推断一个对象是否属于某一个类的对 ...

  9. android中LitePal的使用

    网上有一篇文章写的挺好的,推荐给大家:安卓项目实战之:数据库框架 LitePal 3.0 的使用详解 LitePal是对SQLite数据库操作进行了封装,采用对象映射的方式操作SQLite数据库,简化 ...

  10. android中使用SharedPreferences存储数据

    使用SharedPreferences存储数据还是比较简单的 1.添加或修改数据(没有数据就添加,有数据就是修改): SharedPreferences.Editor editor = getShar ...