Java项目(日志追踪系统)

329 阅读25分钟

Java项目(日志追踪系统)1 -

笔记中涉及资源:

链接:pan.baidu.com/s/18cgWBpiV…

提取码:Coke


一、环境准备及日志测试

①:配置并运行SpringBoot项目

1.可以直接创建SpringBoot项目

image.png

2.有时由于网络原因,也可以先创建Maven项目再改造成SpringBoot项目

  • 创建Maven工程后添加SpringBoot父类依赖
    • spring-boot-starter-parent这个POM比较好用,里面包含大部分常用的开发所需的jar包.
<parent>
    <artifactId>spring-boot-starter-parent</artifactId>
    <groupId>org.springframework.boot</groupId>
    <version>2.1.5.RELEASE</version>
    <relativePath/>
</parent>
  • 添加其他依赖
<!--springmvc-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

②:springboot中注解讲解:

1.@SpringBootApplication:

Sprnig Boot项目的核心注解,目的是开启自动配置.

里面又包含3个注解:

  1. @ComponentScan 注解 在应用程序所在的包上启用 @component扫描(参见最佳实践)

  2. @EnableAutoConfiguration 注解启用Spring Boot的自动配置机制

  3. @SpringBootConfiguration 注解允许在上下文中注册额外的bean或导入额外的配置类

2.创建启动类 com.build.log.ServerApplication

@SpringBootApplication
public class ServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ServerApplication.class, args);
    }
}

3.启动测试

image.png

③:测试logback打印日志

1. 使用Lombok并设置IDEA

  • 导入依赖
<!--lombok-->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.10</version>
    <scope>provided</scope>
</dependency>

检查 IntellijIDEA->Preferences..->Build,Exection,Deployment->Compiler->Annotation Processors中Enable annotation processing是勾选状态 使用Lombok要勾选这个选项

JAVA 注解处理的相关特性提供于包 javax.annotation.processing 中, 但其已经有一个部分实现的抽象类 AbstractProcessor, 所以最终我们只需要继承这个抽象类即可

image.png

2. logback的使用及配置

logback,一个“可靠、通用、快速而又灵活的Java日志框架”,是springboot默认的日志框架.

logback.xml参考:

1.pom依赖和logback.xml\color{#00FF00}{1. pom依赖和logback.xml}

1.pom依赖

<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</dependency>
<!--
引入以上依赖,会自动引入以下jar
logback-classic.x.x.x.jar
logback-core.x.x.x.jar
slf4j-api-x.X.x.jar

注意spring-boot-starter-parent里已集成logback,可直接使用.

2.logback.xml

在工程resources目录下建立logback.xml

  • logback首先会试着查找logback.groovy文件

  • 当没有找到时,继续试着查找logback-test.xml文件

  • 当没有找到时,继续试着查找logback.xml文件

  • 如果仍然没有找到,则使用默认配置(打印到控制台)

3.Spring Boot推荐使用logback-spring.xml来替代logback.xml来配置logback日志

因为,logback.xml加载早于application.properties,所以如果你在logback.xml使用了变量时,而恰好这个变量是写在application.properties时,那么就会获取不到,只要改成logback-spring.xml就可以解决.

  • 创建 logback-spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>

    <appender name="consoleLog" class="ch.qos.logback.core.ConsoleAppender">
        <layout class="ch.qos.logback.classic.PatternLayout">
            <pattern>
                %date{yyyy-MM-dd HH:mm:ss.SSS} %-5level[%thread]%logger{56}.%method:%L -%msg%n
            </pattern>
        </layout>
    </appender>

    <appender name="fileInfoLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>DEBUG</level>
            <onMatch>DENY</onMatch>
            <onMismatch>ACCEPT</onMismatch>
        </filter>
        <encoder>
            <pattern>
                %date{yyyy-MM-dd HH:mm:ss.SSS} %-5level[%thread]%logger{56}.%method:%L -%msg%n
            </pattern>
        </encoder>
        <!--滚动策略-->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!--路径-->
            <fileNamePattern>logs/logbackInfo.%d.log</fileNamePattern>
        </rollingPolicy>
    </appender>

    <appender name="fileErrorLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>DEBUG</level>
        </filter>
        <encoder>
            <pattern>
                %date{yyyy-MM-dd HH:mm:ss.SSS} %-5level[%thread]%logger{56}.%method:%L -%msg%n
            </pattern>
        </encoder>
        <!--滚动策略-->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!--路径-->
            <fileNamePattern>logs/logbackError.%d.log</fileNamePattern>
        </rollingPolicy>
    </appender>

    <root level="info">
        <appender-ref ref="consoleLog"/>
        <appender-ref ref="fileInfoLog"/>
        <appender-ref ref="fileErrorLog"/>
    </root>
</configuration>

3. 测试日志打印

  • 项目结构

image.png

1.创建接口 com.build.log.service.api.TestService

public interface TestService {
    String test();
}

2.创建接口实现类 com.build.log.service.TestServiceImpl

@Slf4j
@Service
public class TestServiceImpl implements TestService {

    @Override
    public String test() {
        log.info("Service 打印日志");
        return "这是一个测试方法(日志已经打印请查看)~~";
    }
}

3.创建控制器 com.build.log.controller.TestController

@Slf4j
@RestController
public class TestController {

    @Autowired
    private TestService testService;

    @GetMapping("test")
    public String test() {
        log.info("Controller 打印日志");
        return testService.test();
    }
}

4.访问测试

image.png

  • 可以看到日志已经输出到文件中了

image.png

④:SpringBoot自定义starter

1. 简介:

SpringBoot最强大的功能就是把我们常用的场景抽取成了一个个starter(场景启动器),我们通过引入springboot为我提供的这些场景启动器,我们再进行少量的配置就能使用相应的功能,有时往往我们需要自定义starter,来简化我们对springboot的使用.

常见的starter:

image.png

2. 自定义stater

1.创建一个新项目\color{#00FF00}{1.创建一个新项目}

项目名:log-trace

groupId: com.log.trace

image.png

image.png

2.新建logtracespringbootstarter模块\color{#00FF00}{2.新建log-trace-spring-boot-starter模块}

SpringBoot官网有建议:For example, assume that you are creating a starter for "acme" and that you name the auto-configuremodule acme-spring-boot-autoconfigure and the starter acme-spring- boot-starter. If you only have one module that combines the two, name it acme-spring-boot-starter.

翻译: 例如,假设您正在为“acme”创建一个启动器,并将自动配置模块命名为acme-spring-boot-autoconfigure,将启动器命名为acme-spring- boot-starter。如果只有一个模块结合了这两个模块,则将其命名为acme-spring-boot-starter。

image.png

  • 创建工程后添加SpringBoot父类依赖
    • spring-boot-starter-parent这个POM比较好用,里面包含大部分常用的开发所需的jar包.
<parent>
    <artifactId>spring-boot-starter-parent</artifactId>
    <groupId>org.springframework.boot</groupId>
    <version>2.1.5.RELEASE</version>
    <relativePath/>
</parent>

3.引入springbootautoconfigure\color{#00FF00}{3.引入spring-boot-autoconfigure}

  • 在刚刚新创建的子模块中引入依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
  • 创建一个目录结构 com.log.trace.starter

4.创建一个子模块(logtraceserver\color{#00FF00}{4. 创建一个子模块(log-trace-server)}

image.png

⑤:logback结构介绍

1.根节点<configuration>包含的属性\color{#00FF00}{1. 根节点<configuration>包含的属性}

<configuration scan="true" scanPeriod="60 seconds" debug="false">
</configuration>
  • scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true
  • scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟
  • debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false

2.根节点<configuration>的子节点:\color{#00FF00}{2.根节点<configuration>的子节点:}

<configuration>下面一共有2个属性分别是:

属性1:设置上下文名称<contextName>:

每个logger都关联到logger上下文,默认上下文名称为"default"”。但可以使用设置成其他名字,用于区分不同应用程序的记录。一旦设置,不能修改,可以通过%contextName来打印日志上下文名称.

<contextName>logback</contextName>

属性2:设置变量<property>:

用来定义变量值的标签,有两个属性,name和value;其中name的值是变量的名称,value的值是变量定义的值.定义的值会被插入到logger上下文中.定义变量后,可以使"${}"来使用变量.

<property name="log.path" value="D:/logback.log"/>

3.configuration下面有3个子节点之appender\color{#00FF00}{3.configuration下面有3个子节点之appender}

1. 什么是Appender?

Logback将执行日志事件输出的组件称为Appender,是控制输出日志格式和地方的,实现的Appender必须继承ch.qos.logback.core.Appender接口

package ch.qos.logback.core;

import ch.qos.logback.core.spi.ContextAware;
import ch.qos.logback.core.spi.FilterAttachable;
import ch.qos.logback.core.spi.LifeCycle;

public interface Appender<E> extends LifeCycle, ContextAware, FilterAttachable<E> {
    String getName();

    void doAppend(E var1) throws LogbackException;

    void setName(String var1);
}

Appender接口里的大多数方法都是getter和setter,值得注意的是doAppend()方法,它唯一的参数是类型E的对象.类型E的实际类型视logback模块的不同而不同.在logback-classic模块里,E可能是"ILoggingEvent"类型,在logback-access模块里,E可能是"AccessEvent"类型.doAppend()方法是 logback框架里最重要的方法,它负责以适当的格式将记录事件输出到合适的设备.

appender用来格式化日志输出节点,有两个属性name和class,name指定appender名称,class用来指定哪种输出策略,常用就是控制台输出策略和文件输出策略.

2. <encoder>对日志进行格式化

表示对日志进行编码:

  • %d{HH:mm:ss.SSS}——日志输出时间

  • %thread——输出日志的进程名字,这在Web应用以及异步任务处理中很有用

  • %-5level——日志级别,并且使用5个字符靠左对齐

  • %logger{36}——日志输出者的名字

  • %msg——日志消息

  • %n——平台的换行符

3. ThresholdFilter

为系统定义的拦截器,例如我们用ThresholdFilter来过滤掉ERROR级别以下的日志不输出到文件中。如果不用记得注释掉,不然你控制台会发现没日志.

4. AppenderBase

类ch.qos.logback.core.AppenderBase是实现了Appender接口的抽象类,AppenderBase提供所有appender共享的基本功能,比如设置/获取名字的方法,其活动状态和过滤器

AppenderBase是logback里所有appender的超类,尽管是抽象类,AppenderBase实际上实现了Appender接口的doAppend()方法.

    public synchronized void doAppend(E eventObject) {
        // WARNING: The guard check MUST be the first statement in the
        // doAppend() method.

        // prevent re-entry.
        if (guard) {
            return;
        }

        try {
            guard = true;

            if (!this.started) {
                if (statusRepeatCount++ < ALLOWED_REPEATS) {
                    addStatus(new WarnStatus("Attempted to append to non started appender [" + name + "].", this));
                }
                return;
            }

            if (getFilterChainDecision(eventObject) == FilterReply.DENY) {
                return;
            }

            // ok, we now invoke derived class' implementation of append
            this.append(eventObject);

        } catch (Exception e) {
            if (exceptionCount++ < ALLOWED_REPEATS) {
                addError("Appender [" + name + "] failed to append.", e);
            }
        } finally {
            guard = false;
        }
    }    

这里的doAppend()方法的实现是同步的,确保不同线程对同一个appender的记录是线程安全的.

5. AppenderBase有哪些子类或实现类:

1.UnsynchronizedAppenderBase:

UnsynchronizedAppenderBase类实现AppenderBase这个接口,但是它本身是一个抽象类,需要继承它才能得到真正的实现类,他的doAppend方法是不同步的.

public void doAppend(E eventObject) {
    // WARNING: The guard check MUST be the first statement in the
    // doAppend() method.

    // prevent re-entry.
    if (Boolean.TRUE.equals(guard.get())) {
        return;
    }

    try {
        guard.set(Boolean.TRUE);

        if (!this.started) {
            if (statusRepeatCount++ < ALLOWED_REPEATS) {
                addStatus(new WarnStatus("Attempted to append to non started appender [" + name + "].", this));
            }
            return;
        }

        if (getFilterChainDecision(eventObject) == FilterReply.DENY) {
            return;
        }

        // ok, we now invoke derived class' implementation of append
        this.append(eventObject);

    } catch (Exception e) {
        if (exceptionCount++ < ALLOWED_REPEATS) {
            addError("Appender [" + name + "] failed to append.", e);
        }
    } finally {
        guard.set(Boolean.FALSE);
    }
}

2.OutputStreamAppender

OutputStreamAppender是继承自UnsynchronizedAppenderBase的Appender实现类,虽然它已经不是抽象类了,但是实际也是不能直接使用的,它的实现类就是最常见的ConsoleAppender和FileAppender.

3.ConsoleAppender

如同它的名字一样,这个Appender将日志输出到console,更准确的说是System.out 或者System.err.

4.FileAppender:

将日志输出到文件当中,目标文件取决于file属性。是否追加输出,取决于append属性

5.RollingFileAppender

RollingFileAppender继承自FileAppender,提供日志目标文件自动切换的功能。例如可以用日期作为日志分割的条件.

6.SocketAppender及SSLSocketAppender

SocketAppender是被设计用来输出日志到远程实例中的,SocketAppender输出日志采用明文方式,SSLSocketAppender则采用加密方式传输日志.

7.ServerSocketAppender及SSLSeverSocketAppender

SocketAppender作为与日志服务器建立连接的主动方,而ServerSocketAppender是被动的,它监听来自客户端的连接请求.

6. 自定义RedisAppender并测试

1.创建com.build.log.controller.RedisAppender类

public class RedisAppender extends UnsynchronizedAppenderBase<ILoggingEvent> {
    @Override
    protected void append(ILoggingEvent eventObject) {
        System.out.println("appender~~~~~~~~");
    }
}

2.src/main/resources/logback-spring.xml中添加以下代码

<appender name="redisAppender" class="com.build.log.controller.RedisAppender">

</appender>

<root level="info">
    <appender-ref ref="consoleLog"/>
    <appender-ref ref="fileInfoLog"/>
    <appender-ref ref="fileErrorLog"/>
    <appender-ref ref="redisAppender"/>
</root>

image.png

3.启动项目测试

image.png

⑥:blockingQueue阻塞队列

前言

BlockingQueue很好的解决了多线程中,如何高效安全"传输"数据的问题。通过这些高效并且线程安全的队列类,为我们快速搭建高质量的多线程程序带来极大的便利.

1.认识BlockingQueue

阻塞队列,顾名思义,首先它是一个队列,在数据结构中所起的作用大致如下图所示:

image.png

从上图我们可以很清楚看到,通过一个共享的队列,可以使得数据由队列的一端输入,从另外一端输出.

1.常用的队列主要有以下两种:

先进先出(FIFO):先插入的队列的元素也最先出队列,类似于排队的功能。从某种程度上来说这种队列也体现了一种公平性

后进先出(LIFO):后插入队列的元素最先出队列,这种队列优先处理最近发生的事件

多线程环境中,通过队列可以很容易实现数据共享,比如经典的"生产者”和"消费者”模型中,通过队列可以很便利地实现两者之间的数据共享。假设我们有若干生产者线程,另外又有若干个消费者线程。如果生产者线程需要把准备好的数据共享给消费者线程,利用队列的方式来传递数据,就可以很方便地解决他们之间的数据共享问题。但如果生产者和消费者在某个时间段内,万一发生数据处理速度不匹配的情况呢?理想情况下,如果生产者产出数据的速度大于消费者消费的速度,并且当生产出来的数据累积到—定程度的时候,那么生产者必须暂停等待一下(阻塞生产者线程),以便等待消费者线程把累积的数据处理完毕,反之亦然。然而,在concurrent包发布以前,在多线程环境下,我们每个程序员都必须去自己控制这些细节,尤其还要兼顾效率和线程安全,而这会给我们的程序带来不小的复杂度。好在此时,强大的concurrent包横空出世了,而他也给我们带来了强大的BlockingQueue。(在多线程领域:所谓阻塞,在某些情况下会挂起线程(即阻塞),一旦条件满足,被挂起的线程又会自动被唤醒),下面两幅图演示了BlockingQueue的两个常见阻塞场景:

2.当队列中没有数据的情况下,消费者端的所有线程都会被自动阻塞(挂起),直到有数据放入队列:

image.png

3.当队列中填满数据的情况下,生产者端的所有线程都会被自动阻塞(挂起),直到队列中有空的位置,线程被自动唤醒:

image.png

这也是我们在多线程环境下,为什么需要BlockingQueue的原因。作为BlockingQueue的使用者,我们再也不需要关心什么时候需要阻塞线程,什么时候需要唤醒线程,因为这—切BlockingQueue都给你 一手包办了.

2. BlockingQueue的核心方法

1.获取数据

1.take(): 取走BlockingQueue里排在首位的对象,若BlockingQueue为空,阻断进入等待状态直到BlockingQueue有新的数据被加入

2.drainTo(): —次性从BlockingQueue获取所有可用的数据对象(还可以指定获取数据的个数),通过该方法,可以提升获取数据效率;不需要多次分批加锁或释放锁

3.封装BlockingQueue工具类

1.创建 com.build.log.controller.SysLog 类

/**
 * 日志对象
 */
@Data
public class SysLog implements Serializable {

    // 应用名称
    private String appName;
    // 时间戳
    private Long timestamp;
    // 用于追踪
    private String traceId;
    // 内容
    private String content;
    // 日志级别
    private String logLevel;
    // 时间
    private LocalDateTime dateTime;
    // 类名
    private String className;
    // 方法名
    private String method;

    public static SysLog getSysLog(final ILoggingEvent event){
        SysLog sysLog = new SysLog();
        long timeStamp = event.getTimeStamp();
        sysLog.setLogLevel(event.getLevel().levelStr);
        sysLog.setAppName(event.getLoggerContextVO().getName());
        sysLog.setContent(event.getFormattedMessage());
        sysLog.setMethodName(event.getThreadName());
        sysLog.setTimeStamp(timeStamp);
        sysLog.setDataTime(LocalDateTime.ofInstant(Instant.ofEpochMilli(timeStamp), ZoneId.systemDefault()));
        sysLog.setClassName(event.getLoggerName());
        // todo 设置traceId
        return sysLog;
    }
}

2.创建 com.build.log.controller.UtilsQueue 工具类

/**
 * 队列操作工具类
 */
public class UtilsQueue {

    /**
     * 一次性从blockingQueue获取100条数据
     */
    private static  final  int MAX_ELEMENT = 100;

    /**
     * 阻塞队列容量2000
     */
    private static final BlockingQueue<SysLog> QUEUE_LIST = new LinkedBlockingQueue<>(2000);

    /**
     * 添加
     * @param sysLog 参数
     */
    public static void add(SysLog sysLog){
        try {
            QUEUE_LIST.add(sysLog);
        } catch (IllegalStateException e) {
            // 如果队列满了 清空队列
            QUEUE_LIST.clear();
            e.printStackTrace();
        }
    }

    /**
     * 从队列中获取数据
     * @return 获取数据的集合
     */
    public static List<SysLog> pop(){
        LinkedList<SysLog> listSysLog = new LinkedList<>();
        // 一次性获取100条数据
        QUEUE_LIST.drainTo(listSysLog,MAX_ELEMENT);
        return listSysLog;
    }
}

⑦:封装ThreadPoolExecutor工具类

1. 线程池的3大好处

  1. 降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁造成的消耗。

  2. 提高响应速度:当任务到达时,任务可以不需要等到线程创建就能立即执行。

  3. 提高线程的可管理性:线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。

2. 线程池的创建

1.ThreadPoolExecutor 有以下四种方法

public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) {
    super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler);
    this.prestartAllCoreThreads();
}

public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) {
    super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);
    this.prestartAllCoreThreads();
}

public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) {
    super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, new ThreadPoolExecutor.RejectHandler());
    this.prestartAllCoreThreads();
}

public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
    super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, new ThreadPoolExecutor.RejectHandler());
    this.prestartAllCoreThreads();
}

这里面需要几个参数

1、corePoolSize(线程池的基本大小):当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使其他空闲的基本线程能够执行新任务也会创建线程,等到需要执行的任务数大于线程池基本大小时就不再创建。如果调用了prestartAllCoreThreads()方法,线程池会提前创建并启动所有基本线程.

2、workQueue(任务队列):用于保存等待执行的任务的阻塞队列。可以选择以下几个阻塞队列:

  • ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,按FIFO原则进行排序

  • LinkedBlockingQueue:—个基于链表结构的阻塞队列,吞吐量高于ArrayBlockingQueue。静态工厂方法Excutors.newFixedThreadPool()使用了这个队列

  • SynchronousQueue: 一个不存储元素的阻塞队列。每个插入操作必须等到另—个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量高于LinkedBlockingQueue,静态工厂方法Excutors.newCachedThreadPool()使用了这个队列

  • PriorityBlockingQueue:—个具有优先级的无限阻塞队列

3、maximumPoolSize(线程池最大数量):线程池允许创建的最大线程数。如果队列满了,并且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务。值得注意的是,如果使用了无界的 任务队列这个参数就没用了。

4、threadFactory(线程工厂):可以通过线程工厂为每个创建出来的线程设置更有意义的名字,如开源框架guava

5、RejectedExecutionHandler(饱和策略):当队列和线程池都满了,说明线程池处于饱和状态,那么必须采取—种策略还处理新提交的任务。它可以有如下四个选项

  • AbortPolicy:直接抛出异常,默认情况下采用这种策略
  • CallerRunsPolicy:使用调用者所在线程来运行任务
  • DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务
  • DiscardPolicy:不处理,丢弃掉

更多的时候,我们应该通过实现RejectedExecutionHandler接口来自定义策略,比如记录日志或持久化存储等。

6、keepAliveTime(线程活动时间):线程池的工作线程空闲后,保持存活的时间。所以如果任务很多,并且每个任务执行的时间比较短,可以调大时间,提高线程利用率。

7、TimeUnit(线程活动时间的单位):可选的单位有天(Days)、小时(HOURS)、分钟(MINUTES)、毫秒(MILLISECONDS)、微秒(MICROSECONDS,千分之一毫秒)和纳秒 (NANOSECONDS,千分之一微秒)

2.创建 com.build.log.controller.UtilsThreadPool 类(自定义线程池)

/**
 * 线程池工具类
 */
public class UtilsThreadPool {

    /**
     * 线程池
     * @return ThreadPoolExecutor
     */
    public static ThreadPoolExecutor logTraceThreadPoolExecutor(){
        return new ThreadPoolExecutor(5,
                10,
                1L,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(1000),
                new ThreadPoolExecutor.CallerRunsPolicy()
        );
    }
}

二、Redis配置和封装

①:封装Redis缓存工具类

1.导入依赖

<!-- springboot种redis依赖-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-to-slf4j</artifactId>
        </exclusion>
    </exclusions>
</dependency>

2.需要特别注意pushAII操作:

public void leftPushAll(String key, Collection<?> value){
    redisTemplate.opsForList().leftPushAll(key,value);
)    

以下面的操作为例子:

List<string> list = UtilsQueue.pop();
leftPushAll("cba", list);

直接将list放进了队列中,那么在redis的队列中是如何存储的呢?

image.png

可以看到一个key,放了整个队列,而不是一个key—行数据.这种情况我们需要自己去遍历数据插入.

3.创建 com.build.log.controller.UtilsRedis 工具类

/**
 * Redis工具类
 */
public class UtilsRedis {

    /**
     * Redis模板
     */
    private RedisTemplate<String,Object> redisTemplate;

    /**
     * Redis Key
     */
    public static final String LOG_TRACE_QUQEUE_KEY = "log_trace_queue";

    /**
     * 获取Spring容器种的自身
     */
    private static UtilsRedis SELF_UTILS_REDIS;

    public UtilsRedis(RedisTemplate< String,Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    /**
     * 放入Redis缓存
     */
    public void action(){
        List<SysLog> list = UtilsQueue.pop();
        if (list.size() > BigDecimal.ZERO.intValue()){
            leftPushAll(LOG_TRACE_QUQEUE_KEY,list);
        }
    }

    /**
     * 单条队列添加
     * @param key
     * @param value
     */
    private void leftPush(String key, Object value){
        redisTemplate.opsForList().leftPush(key, value);
    }

    /**
     * 批量添加
     * @param key
     * @param value
     */
    private void leftPushAll(String key, Collection<SysLog> value){
        for (SysLog sysLog : value) {
            leftPush(key, sysLog);
        }
    }

    public static UtilsRedis getSelfUtilsRedis() {
        return SELF_UTILS_REDIS;
    }

    public static void setSelfUtilsRedis(UtilsRedis selfUtilsRedis) {
        SELF_UTILS_REDIS = selfUtilsRedis;
    }
}

②:配置Redis

1.添加 src/main/resources/application.yml 文件

# 开发环境配置
server:
  # 服务器端口
  port: 10000
  servlet:
    # 应用的访问路径
    context-path: /
# spring的配置
spring:
  application:
    name: 01_buildLog
  # redis配置
  redis:
    host: 127.0.0.1
    port: 6379
    # 连接超时时间
    timeout: 10s
    jedis:
      pool:
        # 连接池种的最小空闲链接
        min-idle: 0
        # 连接池中的最大空闲链接
        max-idle: 2
        # 连接池中最大数据库链接
        max-active: 8
        # 连接池最大阻塞等待时间
        max-wait: -1ms

2.导入依赖

<!-- redis池-->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
    <version>2.6.2</version>
</dependency>

fastjson依赖放在父模块中

<!-- fastjson-->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.55</version>
</dependency>

3.创建 com.build.log.controller.LogTraceAutoConfiguration 类

@Configuration
public class LogTraceAutoConfiguration {
    @Bean
    public RedisTemplate<String,Object> logTraceRedisTemplate(RedisConnectionFactory redisConnectionFactory){
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        // 使用fastJson序列化器
        GenericFastJsonRedisSerializer serializer = new GenericFastJsonRedisSerializer();
        // value值序列化 采用fastJson的GenericFastJsonRedisSerializer
        template.setValueSerializer(serializer);
        template.setHashValueSerializer(serializer);
        // key的序列化 StringRedisSerializer
        template.setKeySerializer(new StringRedisSerializer());
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setConnectionFactory(redisConnectionFactory);
        template.afterPropertiesSet();
        return template;
    }
    @Bean
    public UtilsRedis UtilsRedis(RedisTemplate< String,Object> template){
        UtilsRedis utilsRedis = new UtilsRedis(template);
        UtilsRedis.setSelfUtilsRedis(utilsRedis);
        return utilsRedis;
    }
}

③:将日志封装进Redis中

1.修改 com.build.log.controller.RedisAppender 中的方法

public class RedisAppender extends UnsynchronizedAppenderBase<ILoggingEvent> {

    /**
     * 自定义线程池
     */
    private static ThreadPoolExecutor EXECUTOR = UtilsThreadPool.logTraceThreadPoolExecutor();

    @Override
    protected void append(ILoggingEvent eventObject) {
        UtilsQueue.add(SysLog.getSysLog(eventObject));
        UtilsRedis selfUtilsRedis = UtilsRedis.getSelfUtilsRedis();
        if (Objects.nonNull(selfUtilsRedis)){
            EXECUTOR.execute(selfUtilsRedis::action);
        }
    }
}

2.测试

image.png

④:全链路跟踪TraceId

前言

数据库主键:标示唯┬一条数据,譬如唯一商品,唯一订单

全局事务ID:实现分布式事务一致性的必备良药

请求ID:requestld,seesionld,标示一个请求或者一次会话的生命周期

身份证ID:代表你在中国的唯一标示

学号:你在某个机构的特殊代号

1.全链路跟踪Traceld的作用:

标示一次调用的上下文ID,通过此ID可以获取所做事情的调用链,当请求来时生成—个traceld放在ThreadLocal里,然后打印时去取就行了.

1 Traceld 实现

  1. 负载均衡:譬如nginx,初始化 traceld 放入header

  2. web request:通过fliter获取 header的traceld,无则初始化 traceld

  3. rpc调用:通过扩展机制传递traceld ,无则初始化 traceld

  4. 定时任务@Schedule:通过 注解切面@Traceld, 初始化 traceld

  5. 消息消费:通过消息传递协议添加tracelD,无则使用注解切面@Traceld初始化traceld

  6. 线程池或者异步:封装runnable和callable初始化传递traceld或者封装线程池初始化传递traceld

2.封装ThreadLocal 创建 com.build.log.controller.TraceIdThreadLocal 类

/**
 * traceId线程对象
 */
public class TraceIdThreadLocal {

    /**
     * 线程trace对象
     */
    private static final ThreadLocal<String> TRACE_ID = new ThreadLocal<>();

    /**
     * 添加操作
     * @param value
     */
    public static void add(String value){
        TRACE_ID.set(value);
    }

    /**
     * 获取操作
     * @return
     */
    public static String get(){
        return  TRACE_ID.get();
    }

    /**
     * 删除操作
     */
    public static void remove(){
        TRACE_ID.remove();
    }

}

2.使用拦截器或过滤器实现web request traceId:

1.创建自己的拦截器:

定义一个Interceptor非常简单方式也有几种,我这里简单列举两种:

  1. 实现Spring的HandlerInterceptor接口:

  2. 继承实现了Handlerlnterceptor接口的类,例如已经提供的实现了HandlerInterceptor 接口的抽象类HandlerInterceptorAdapter

  3. HandlerInterceptor方法介绍

boolean preHandle(HttpServletRequest request, HttpservletResponse response, object handler)throws Exception;

void postHandle(HttpservletRequest request, HttpServletResponse response,object handler, ModelAndView modelAndView)throws Exception;

void afterCompletion(HttpServletRequest request, HttpservletResponse response, object handler, Exception ex)throws Exception;

preHandle: 在业务处理器处理请求之前被调用。预处理,可以进行编码、安全控制、权限校验等处理.

postHandle:在业务处理器处理请求执行完成后,生成视图之前执行。后处理

afterCompletion:在DispatcherServlet完全处理完请求后被调用,可用于清理资源等

  1. 同时需要根据拦截规则进行注册,创建一个Java类继承WebMvcConfigurationSupport,并重写 addInterceptors方法

在一个项目中WebMvcConfigurationSupport只能存在一个,多个的时候,只有一个会生效.

2创建过滤器:

  1. 创建一个Java类实现Filter接口,并使用@Component注解标注为组件自动注入bean
  • 引入生成全局唯—id jar包:
<!--生成唯一的uuid 生成的是排序的-->
<dependency>
    <groupId>com.fasterxml.uuid</groupId>
    <artifactId>java-uuid-generator</artifactId>
    <version>3.1.4</version>
</dependency>
/**
 * traceId拦截器
 */
public class TraceIdFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
        TraceIdThreadLocal.add(Generators.timeBasedGenerator().generate().toString());
        filterChain.doFilter(request, response);
    }

    @Override
    public void destroy() {
        TraceIdThreadLocal.remove();
    }
}

3.修改 com.build.log.controller.LogTraceAutoConfiguration 类 添加以下内容

@Configuration
public class LogTraceAutoConfiguration {
    ......

    @Bean
    public FilterRegistrationBean<TraceIdFilter> traceIdFilterBean(){
        FilterRegistrationBean<TraceIdFilter> registrationBean = new FilterRegistrationBean<>();
        registrationBean.setFilter(new TraceIdFilter());
        registrationBean.setUrlPatterns(Collections.singletonList("/*"));
        registrationBean.setOrder(0);
        return registrationBean;
    }
}

4.修改 com.build.log.controller.SysLog 类 添加以下内容

image.png

3. 测试

1.启动项目后查看Redis

image.png

2.访问接口后

image.png

image.png

三、整合内容到自定义starter中

①:引入依赖及代码文件

1.log-trace 包中引入以下依赖

<parent>
  <artifactId>spring-boot-starter-parent</artifactId>
  <groupId>org.springframework.boot</groupId>
  <version>2.1.5.RELEASE</version>
  <relativePath/>
</parent>
<dependencies>
  <!-- redis池-->
  <dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
    <version>2.6.2</version>
  </dependency>

  <!-- springboot种redis依赖-->
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    <exclusions>
      <exclusion>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-to-slf4j</artifactId>
      </exclusion>
    </exclusions>
  </dependency>

  <!--lombok-->
  <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.10</version>
    <scope>provided</scope>
  </dependency>

<!-- logback-->
<dependency>
  <groupId>ch.qos.logback</groupId>
  <artifactId>logback-classic</artifactId>
</dependency>
<dependency>
  <groupId>org.apache.logging.log4j</groupId>
  <artifactId>log4j-to-slf4j</artifactId>
  <version>2.11.0</version>
</dependency>

2.log-trace-spring-boot-starter 包中引入以下依赖

<!-- fastjson-->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.55</version>
</dependency>
<!--springmvc-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--生成唯一的uuid 生成的是排序的-->
<dependency>
    <groupId>com.fasterxml.uuid</groupId>
    <artifactId>java-uuid-generator</artifactId>
    <version>3.1.4</version>
</dependency>

3.复制代码

image.png

②:配置类进行注册

在resources文件夹下面新建一个META-INF文件,并在下面创建spring.factories文件:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=/
com.XXXX. XXXXXXX

image.png

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.log.trace.starter.configuration.LogTraceAutoConfiguration

③:测试

1.删除原本无用的类文件

image.png

2.将自定义的starter包安装到本地仓库

image.png

3.在 01_buildLog 中引入刚刚安装的jar包

<dependency>
    <groupId>com.log.trace</groupId>
    <artifactId>log-trace-spring-boot-starter</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>

4.修改 logback-spring.xml 文件

image.png

5.访问接口然后查看Redis缓存

image.png

经过测试发现我们已经将starter集成到了项目中

四、通过npm运行前端项目

①:安装npm

1. 什么是npm及安装

npm是javascript的包管理工具,是前端模块化下的一个标志性产物,简单地地说,就是通过npm下载模块,复用已有的代码,提高工作效率.

下载地址:nodejs.org/dist/

1.需要设置环境变量:

image.png

  1. 从社区的角度:把针对某一特定问题的模块发布到npm的服务器上,供社区里的其他人下载和使用,同时自己也可以在社区里寻找特定的模块的资源,解决问题

  2. 从团队的角度:有了npm这个包管理工具,复用团队既有的代码也变的更加地方便 2.这里我已经安装过别的版本了(测试一下)

image.png

2.利用npm安装包

npm安装的方式——本地安装和全局安装

什么时候用本地/全局安装?

1.当你试图安装命令行工具的时候,例如gruntCLI的时候,使用全局安装

  • 全局安装的方式:npm install-g模块名称

2.当你试图通过npm install某个模块,并通过require(XXX')的方式引入的时候,使用本地安装

  • 本地安装的方式:npm install 模块名称

本地安装的时候,将依赖包信息写入package.json中

注意一个问题,在团队协作中,一个常见的情景是他人从github上clone你的项目,然后通过npminstall安装必要的依赖,(刚从github上clone下来是没有node__modules的,需要安装)那么根据什么信息安装依赖呢?就是你的package.json中的dependencies和devDepencies。所以,在本地安装的同时,将依赖包的信息(要求的名称和版本)写入package.json中是很重要的!

npm install 模块:安装好后不写入package.json中
npm install 模块 --save 安装好后写入package.json的dependencies中(生产环境依赖)
npm install 模块 --save-dev 安装好后写入package.json的devDepencies中(开发环境依赖)

3.利用npm删除包

1.删除全局模块

npm uninstall -g 包名

2.删除本地模块

npm uninstall 模块

3.删除本地模块时你应该思考的问题:是否将在package.json上的相应依赖信息也消除?

npm uninstall 模块:删除模块,但不删除模块留在package.json中的对应信息
npm uninstall 模块 --save 删除模块,同时删除模块留在package.json中dependencies下的对应信息
npm uninstall 模块 --save-dev 删除模块,同时删除模块留在package.json中devDependencies下的对应信息

4.npm的缺点和安装cnpm

由于npm的源在国外,所以国内用户使用起来各种不方便,于是就有了cnpm

安装cnpm,输入以下命令:

npm install -g cnpm --registry=registry.npm.taobao.org

安装指定版本的cnpm

npm install cnpm@7.1.0 -g --registry=registry.npm.taobao.org

image.png

  • 安装成功

image.png

②:使用cnpm下载前端模块

image.png

image.png

③:使用node运行前端项目并访问

命令:node app

image.png

访问:http://localhost:8989

image.png

五、安装Elasticsearch和Kibana

安装笔记链接:juejin.cn/post/717775…

六、集成Elasticsearch

①:集成引入依赖

下面两个包一个不能少,否则会在创建文档的时候报错

引入到 log-trace-server 包中

image.png

<!-- https://mvnrepository.com/artifact/org.elasticsearch/elasticsearch -->
<dependency>
    <groupId>org.elasticsearch</groupId>
    <artifactId>elasticsearch</artifactId>
    <version>6.8.0</version>
</dependency>
<!--elasticsearch-->
<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-high-level-client</artifactId>
    <version>6.8.0</version>
    <exclusions>
        <exclusion>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
        </exclusion>
    </exclusions>
</dependency>

客户端使用:

从Elasticsearch7开始,High-level REST Client(HLRC)API的所有功能已经基本完成。对Java客户端而言,官方建议使用这种方式来访问集群,而原来的Transport Client测会被逐步废弃. Elasticsearch支持很多不同的类型的客户端,以Java客户端为例,早期的版本推荐使用TransportClient,默认使用 9300端口,以TCP的方式与集群进行交互。

在Elasticsearch 7中这种客户端将被废弃,转为推荐使用REST Client,包括Low Level REST Client与High Level REST Client两种客户端,均使用HTTP方式访问集群,更加轻量级,兼容性也更好.

Elasticsearch(ES)提供了两种连接方式:

1.transport:通过TCP方式访问ES

对应的库是 org.elasticsearch.client.transport

2.rest:通过HTTP API方式访问 ES

对应的库是:
elasticsearch-rest-client + org.elasticsearch.client.rest,提供 low-level rest API
elasticsearch-rest-high-level-client ,提供 high-level rest API 。从 Elasticsearch 6.0.0-beta1 开始提供

虽然说,ES提供了2 种方式,官方目前建议使用rest 方式,而不是transport方式。并且,transport 在未来的计划中,准备废弃.

②:创建启动类

在log-trace-server包中创建 com.log.trace.server.ServerApplication 启动类

@SpringBootApplication
public class ServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ServerApplication.class, args);
    }
}

③:配置Elasticsearch及索引创建规范

1. 配置Elasticsearch

在 log-trace-server 包中进行以下操作

1.创建配置类 com.log.trace.server.config.RestHighLevelConfig

@Configuration
public class RestHighLevelConfig {

    @Bean
    public RestHighLevelClient elasticsearchClient(){
        return new RestHighLevelClient(RestClient.builder(
                new HttpHost("127.0.0.1",9200,"http")));
    }
}

2.添加yml配置文件 src/main/resources/application.yml

#开发环境配置
server:
  #服务器端口
  port: 8806
  servlet:
    #应用的访问路径
    context-path: /
#spring配置
spring:
  application:
    name: buidLog
  #redis配置
  redis:
    #地址
    host: 127.0.0.1
    #端口
    port: 6379
    #连接超时时间
    timeout: 10s
    lettuce:
      pool:
        #连接池中的最小空闲连接
        min-idle: 0
        #连接池中的最大空闲连接
        max-idle: 2
        #连接池的最大数据库连接数
        max-active: 8
        #连接池最大阻塞等待时间
        max-wait: -1ms

3.创建 com.log.trace.server.utils.UtilsRedis

/**
 * Redis工具类
 */
public class UtilsRedis {

    /**
     * Redis模板
     */
    private RedisTemplate<String,Object> redisTemplate;

    /**
     * Redis Key
     */
    public static final String LOG_TRACE_QUQEUE_KEY = "log_trace_queue";


    /**
     * 有参构造函数
     * @param redisTemplate
     */
    public UtilsRedis(RedisTemplate< String,Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    /**
     * 获取队列中的一个元素
     * @param key
     * @return
     */
    public Object rightPop(String key){
        return  redisTemplate.opsForList().rightPop(key);
    }

    /**
     * 获取指定区间的元素
     * @param key
     * @param count
     * @return
     */
    public List<Object> rightPopRange(String key, long count){
        List<Object> list = new ArrayList<>();
        for (long i = 0; i < count; i++) {
            Object item = rightPop(key);
            if (Objects.nonNull(item)){
                list.add(item);
            }
        }
        return list;
    }
}

4.创建 com.log.trace.server.config.RedisConfig

@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<String,Object> logTraceRedisTemplate(RedisConnectionFactory redisConnectionFactory){
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        // 使用fastJson序列化器
        GenericFastJsonRedisSerializer serializer = new GenericFastJsonRedisSerializer();
        // value值序列化 采用fastJson的GenericFastJsonRedisSerializer
        template.setValueSerializer(serializer);
        template.setHashValueSerializer(serializer);
        // key的序列化 StringRedisSerializer
        template.setKeySerializer(new StringRedisSerializer());
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setConnectionFactory(redisConnectionFactory);
        template.afterPropertiesSet();
        return template;
    }
    @Bean
    public UtilsRedis UtilsRedis(RedisTemplate< String,Object> template){
        return new UtilsRedis(template);
    }
}

2. 索引创建规范

1.索引设计:

1)建立订单索引时用“log_trace_yyyyMM”命名。采用日志创建时间,按月建立新的索引

2)为这些订单索引建立别名order

3)写数据时需要根据日志创建时间,把日志数据写到对应的月份索引;读取日志数据时根据别名读取,就可以查询到所有日志数据

2.索引模板:

索引模板,就是创建索引的模板,模板中包含公共的配置(settings)和映射(mappings),并包含一个简单触发条件,及条件满足时使用该模板创建一个新的索引。

模板只在创建索引时应用,更改模板不会对现有索引产生影响。当使用create index API时,作为create index调用的一部分定义的设置/映射将优先于模板中定义的任何匹配设置/映射.

设置好模板后,每次创建order索引,就会自动关联别名,查询数据时就可以通过别名一下把所有数据都获取到.

3.封装一个工具类 Elasticsearch 索引工具 com.log.trace.server.utils.UtilsElasticsearch

@Component
public class UtilsElasticsearch {

    @Autowired
    protected RestHighLevelClient restHighLevelClient;

    /**
     * 判断索引是否已经存在
     * @param indexName
     * @return
     */
    public boolean exists(String indexName) throws IOException {
        GetIndexRequest getIndexRequest = new GetIndexRequest(indexName);
        getIndexRequest.humanReadable(true);
        return restHighLevelClient.indices().exists(getIndexRequest, RequestOptions.DEFAULT);
    }

    /**
     * 创建索引
     * @param indexName 索引名称
     * @param shards  索引主分片
     * @param replicas 索引主分片的副本数
     * @return
     */
    public boolean createIndex(String indexName, int shards, int replicas) throws IOException {
        CreateIndexRequest createIndexRequest = new CreateIndexRequest(indexName);
        createIndexRequest.settings(Settings.builder().
                put("index.number_of_shards",shards).
                put("index.number_of_replicas",replicas));
        // 设置别名
        createIndexRequest.alias(new Alias("log_trace"));
        CreateIndexResponse createIndexResponse =
                restHighLevelClient.indices().create(createIndexRequest, RequestOptions.DEFAULT);
        return createIndexResponse.isAcknowledged();
    }


    /**
     * 批量添加
     * @param indexName
     * @param documents
     * @return
     */
    public boolean batchAddDocument(String indexName, List<Object> documents) throws IOException {
        BulkRequest bulkRequest = new BulkRequest();
        documents.forEach(item -> {
            bulkRequest.add(new IndexRequest(indexName, "_doc")
                    .source(JSON.toJSONString(item), XContentType.JSON));
        });
        BulkResponse bulk = restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
        return !bulk.hasFailures();
    }
}

4.创建 com.log.trace.server.init.StartService 类

注意while循环慎用

1.while循环使用不当会阻塞当前线程:

Java线程一般在执行完run方法就可以正常结束,不过有一类线程叫做伺服线程,不间断地执行,往往在run方法中有一个死循环,监视着某些条件,只有当这些条件满足时才能结束。例:

public void run(){
    while(true){
        somework();
        if(finished){
            break;
            try {
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                    e.printStackTrace(); 
        }
    }
}

有些执行伺服任务的线程,在while(true)这样的死循环内部,是一个阻塞中的方法,当该方法没有返回时,该线程一直处于阻塞当中,根本无法执行其他语句.

@Component
public class StartService {

    public static final String 方法 = "方法";
    @Autowired
    private UtilsRedis utilsRedis;

    @Autowired
    private UtilsElasticsearch utilsElasticsearch;

    @PostConstruct
    public void start() {
        while (true) {
            List<Object> list = utilsRedis.rightPopRange(UtilsRedis.LOG_TRACE_QUQEUE_KEY, 1000);
            try {
                if (CollectionUtils.isEmpty(list)){
                    Thread.sleep(5000);
                    continue;
                }
                String indexName = "log_trace" + LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMM"));
                if (!utilsElasticsearch.exists(indexName)){
                    utilsElasticsearch.createIndex(indexName,1,0);
                }
                utilsElasticsearch.batchAddDocument(indexName,list);
            } catch (InterruptedException | IOException e) {
                e.printStackTrace();
            }
        }
    }
}

七、测试并运行

①:准备

1.运行Elasticsearch Kibana Redis

image.png

image.png

2.访问:http://localhost:5601

image.png

②:测试(这里es和Kibana换成了6.8.0)

1.运行log-trace-server 服务(确保服务正常运行)

2.将 log-trace 项目重新安装到本地仓库

image.png

3.运行01_buildLog 服务

image.png

4.查看Redis缓存

image.png

5.查看Elasticsearch 视图管理工具

image.png

6.查看日志追踪系统

image.png