一次鞭辟入里的 Log4j2 日志输出阻塞问题的定位

问题现象

线上某个应用的某个实例突然出现某些次请求服务响应极慢的情况,有几次请求超过 60s 才返回,并且通过日志发现,服务线程并没有做什么很重的操作。这种情况断断续续持续了半小时左右。

核心问题定位

由于半小时后,服务实例恢复正常,并且出现请求极慢的次数并不太多,没有能及时打印出现问题的时候线程堆栈采集相关信息。但是,我们有自己的法宝,JFR(关于 JFR,请参考我的另一系列JFR全解)。

JFR 非常适合用来事后复盘定位问题,并且配置得当的话,性能损耗极小,并且不像 APM 系统一样需要额外的采集以及需要统一的服务进程进行整合展现。我们随用随取即可,但是 JFR 面向的是单进程的问题定位,找到问题进程,定位跨进程业务问题链路追踪还是需要 APM 系统的。

我们通过如下命令采集对应时间段的 JFR:

jcmd 进程pid JFR.dump begin=2021-07-04T17:30:00.000Z end=2021-07-04T18:30:00.000Z

我们选择的时间跨度比出问题的时间跨度大一些,这样保证底层问题出现的时候也被采集写入这个 dump 文件。

对于历史某些请求响应慢,我一般按照如下流程去看:

  1. 是否有 STW(Stop-the-world,参考我的另一篇文章:JVM相关 - SafePoint 与 Stop The World 全解):
  2. 是否有 GC 导致的长时间 STW
  3. 是否有其他原因导致进程所有线程进入 safepoint 导致 STW
  4. 是否 IO 花了太长时间,例如调用其他微服务,访问各种存储(硬盘,数据库,缓存等等)
  5. 是否在某些锁上面阻塞太长时间?
  6. 是否 CPU 占用过高,哪些线程导致的?

接下来我们来详细分析,首先是 GC,从下图看,并没有能严重影响线程 STW 很长时间的:

然后查看 safepoint 相关,发现也没有什么影响:

接着,我们查看 IO 相关的事件,也并没有发现业务相关的阻塞读:

然后,我们查看是否在某些锁上面阻塞太长时间,终于发现异常:

通过堆栈,我们发现是** log4j2 打印日志卡住了**。

问题分析

1. Java Monitor Blocked 的 Monitor Address 对于同一个对象也是会改变的

首先,对于 Java Monitor Blocked 这个事件,我们可以通过 Monitor Address 来判断是否是同一个锁。例如这里我们发现这些线程都是阻塞在 FFFF 4C09 3188 这个地址的对象,这个地址是基于对象在 Java 堆中的相对地址得出的。

但是对于同一个对象,这个地址并不会是不变的,在每次 GC 扫描整理到这个对象对象的时候,如果有回收内存,一般这个地址就会改变。由于我们使用的是 G1,所以并不是每次 GC 都会扫描到这个对象,但是如果地址发生改变,一定是因为发生了 GC

2. log4j2 异步日志原理简介

在我们的应用配置中,采用的日志框架是 log4j2,采用的配置是异步日志配置。这里简单说一下 Log4j2 异步日志的原理:Log4j2 异步日志基于高性能数据结构 Disruptor,Disruptor 是一个环形 buffer,做了很多性能优化(具体原理可以参考我的另一系列:高并发数据结构(disruptor)),Log4j2 对此的应用如下所示:

简单来说,多线程通过 log4j2 的门面类 org.apache.logging.log4j.Logger 进行日志输出,被封装成为一个 org.apache.logging.log4j.core.LogEvent,放入到 Disruptor 的环形 buffer 中。在消费端有一个单线程消费这些 LogEvent 写入对应的 Appender,我们这里只有一个 Appender,其配置是:

<RollingFile name="file" append="true"
filePattern="./app.log-%d{yyyy.MM.dd.HH}">
<PatternLayout pattern="${logFormat}"/>
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
</Policies>
<DirectWriteRolloverStrategy maxFiles="72"/>
</RollingFile>

异步日志 logger 配置为(配置 includeLocation 为 false,避免每次打印日志需要获取调用堆栈的性能损耗):

 <Asyncroot level="info" includeLocation="false">
<appender-ref ref="file"/>
</Asyncroot>

log4j component 额外配置为:

log4j2.component.properties:

# 当没有日志的时候,消费线程通过 Block 等待日志事件到来,这样 CPU 占用最少
AsyncLoggerConfig.WaitStrategy=Block

3. log4j2 disruptor 的 RingBuffer 的大小

既然是一个环形 Buffer,它的容量是有限的,我们这里没有修改它的大小,走的是默认配置,查看其源码:

AsyncLoggerConfigDisruptor.java

public synchronized void start() {
//省略其他代码
ringBufferSize = DisruptorUtil.calculateRingBufferSize("AsyncLoggerConfig.RingBufferSize");
disruptor = new Disruptor<>(factory, ringBufferSize, threadFactory, ProducerType.MULTI, waitStrategy);
}

DisruptorUtil.java

private static final int RINGBUFFER_MIN_SIZE = 128;
private static final int RINGBUFFER_DEFAULT_SIZE = 256 * 1024;
private static final int RINGBUFFER_NO_GC_DEFAULT_SIZE = 4 * 1024; static int calculateRingBufferSize(final String propertyName) {
//是否启用了 ThreadLocal,如果是则为 4 kB,不是则为 256 kB
int ringBufferSize = Constants.ENABLE_THREADLOCALS ? RINGBUFFER_NO_GC_DEFAULT_SIZE : RINGBUFFER_DEFAULT_SIZE;
//读取系统变量,以及 log4j2.component.properties 文件获取 propertyName(这里是 AsyncLoggerConfig.RingBufferSize) 这个配置
final String userPreferredRBSize = PropertiesUtil.getProperties().getStringProperty(propertyName,
String.valueOf(ringBufferSize));
try {
int size = Integer.parseInt(userPreferredRBSize);
//如果小于 128 字节则按照 128 字节设置
if (size < RINGBUFFER_MIN_SIZE) {
size = RINGBUFFER_MIN_SIZE;
LOGGER.warn("Invalid RingBufferSize {}, using minimum size {}.", userPreferredRBSize,
RINGBUFFER_MIN_SIZE);
}
ringBufferSize = size;
} catch (final Exception ex) {
LOGGER.warn("Invalid RingBufferSize {}, using default size {}.", userPreferredRBSize, ringBufferSize);
}
//取最近的 2 的 n 次方,因为对于 2 的 n 次方取余等于对于 2^n-1 取与运算,这样更快
return Integers.ceilingNextPowerOfTwo(ringBufferSize);
}

如果启用了 ThreadLocal 这种方式生成 LogEvent,每次不新生成的 LogEvent 用之前的,用 ThreadLocal 存储的,这样避免了创建新的 LogEvent。但是考虑下面这种情况:

logger.info("{}", someObj);

这样会造成强引用,导致如果线程没有新的日志,这个 someObj 一直不能回收。所以针对 Web 应用,log4j2 默认是不启用 ThreadLocal 的 方式生成 LogEvent

Constants.java

public static final boolean IS_WEB_APP = PropertiesUtil.getProperties().getBooleanProperty(
"log4j2.is.webapp", isClassAvailable("javax.servlet.Servlet"));
public static final boolean ENABLE_THREADLOCALS = !IS_WEB_APP && PropertiesUtil.getProperties().getBooleanProperty(
"log4j2.enable.threadlocals", true);

由此,可以看出,我们的 RingBuffer 的大小为 256 kB

4. RingBuffer 满了 log4j2 会发生什么?

当 RingBuffer 满了,如果在 log4j2.component.properties 配置了 AsyncLoggerConfig.SynchronizeEnqueueWhenQueueFull=false,则会 Wait(其实是 park) 在 Disruptor 的 produce 方法上,等待消费出下一个可以生产的环形 buffer 槽;默认这个配置为 true,即所有生产日志的线程尝试获取全局中的同一个锁(private final Object queueFullEnqueueLock = new Object();):

DisruptorUtil.java

static final boolean ASYNC_CONFIG_SYNCHRONIZE_ENQUEUE_WHEN_QUEUE_FULL = PropertiesUtil.getProperties()
.getBooleanProperty("AsyncLoggerConfig.SynchronizeEnqueueWhenQueueFull", true);
private boolean synchronizeEnqueueWhenQueueFull() {
return DisruptorUtil.ASYNC_CONFIG_SYNCHRONIZE_ENQUEUE_WHEN_QUEUE_FULL
// Background thread must never block
&& backgroundThreadId != Thread.currentThread().getId();
} private final Object queueFullEnqueueLock = new Object(); private void enqueue(final LogEvent logEvent, final AsyncLoggerConfig asyncLoggerConfig) {
//如果 AsyncLoggerConfig.SynchronizeEnqueueWhenQueueFull=true,默认就是 true
if (synchronizeEnqueueWhenQueueFull()) {
synchronized (queueFullEnqueueLock) {
disruptor.getRingBuffer().publishEvent(translator, logEvent, asyncLoggerConfig);
}
} else {
//如果 AsyncLoggerConfig.SynchronizeEnqueueWhenQueueFull=false
disruptor.getRingBuffer().publishEvent(translator, logEvent, asyncLoggerConfig);
}
}

默认配置的时候,异常堆栈和我们 JFR 中看到的一样,举个例子:

"Thread-0" #27 [13136] prio=5 os_prio=0 cpu=0.00ms elapsed=141.08s tid=0x0000022d6f2fbcc0 nid=0x3350 waiting for monitor entry  [0x000000399bcfe000]
java.lang.Thread.State: BLOCKED (on object monitor)
at org.apache.logging.log4j.core.async.AsyncLoggerConfigDisruptor.enqueue(AsyncLoggerConfigDisruptor.java:375)
- waiting to lock <merged>(a java.lang.Object)
at org.apache.logging.log4j.core.async.AsyncLoggerConfigDisruptor.enqueueEvent(AsyncLoggerConfigDisruptor.java:330)
at org.apache.logging.log4j.core.async.AsyncLoggerConfig.logInBackgroundThread(AsyncLoggerConfig.java:159)
at org.apache.logging.log4j.core.async.EventRoute$1.logMessage(EventRoute.java:46)
at org.apache.logging.log4j.core.async.AsyncLoggerConfig.handleQueueFull(AsyncLoggerConfig.java:149)
at org.apache.logging.log4j.core.async.AsyncLoggerConfig.logToAsyncDelegate(AsyncLoggerConfig.java:136)
at org.apache.logging.log4j.core.async.AsyncLoggerConfig.log(AsyncLoggerConfig.java:116)
at org.apache.logging.log4j.core.config.LoggerConfig.log(LoggerConfig.java:460)
at org.apache.logging.log4j.core.config.AwaitCompletionReliabilityStrategy.log(AwaitCompletionReliabilityStrategy.java:82)
at org.apache.logging.log4j.core.Logger.log(Logger.java:162)
at org.apache.logging.log4j.spi.AbstractLogger.tryLogMessage(AbstractLogger.java:2190)
at org.apache.logging.log4j.spi.AbstractLogger.logMessageTrackRecursion(AbstractLogger.java:2144)
at org.apache.logging.log4j.spi.AbstractLogger.logMessageSafely(AbstractLogger.java:2127)
at org.apache.logging.log4j.spi.AbstractLogger.logMessage(AbstractLogger.java:2003)
at org.apache.logging.log4j.spi.AbstractLogger.logIfEnabled(AbstractLogger.java:1975)
at org.apache.logging.log4j.spi.AbstractLogger.info(AbstractLogger.java:1312)
省略业务方法堆栈

配置为 false 的时候,堆栈是这个样子的:

"Thread-0" #27 [18152] prio=5 os_prio=0 cpu=0.00ms elapsed=5.68s tid=0x000002c1fa120e00 nid=0x46e8 runnable  [0x000000eda8efe000]
java.lang.Thread.State: TIMED_WAITING (parking)
at jdk.internal.misc.Unsafe.park(java.base@17-loom/Native Method)
at java.util.concurrent.locks.LockSupport.parkNanos(java.base@17-loom/LockSupport.java:410)
at com.lmax.disruptor.MultiProducerSequencer.next(MultiProducerSequencer.java:136)
at com.lmax.disruptor.MultiProducerSequencer.next(MultiProducerSequencer.java:105)
at com.lmax.disruptor.RingBuffer.publishEvent(RingBuffer.java:524)
at org.apache.logging.log4j.core.async.AsyncLoggerConfigDisruptor.enqueue(AsyncLoggerConfigDisruptor.java:379)
at org.apache.logging.log4j.core.async.AsyncLoggerConfigDisruptor.enqueueEvent(AsyncLoggerConfigDisruptor.java:330)
at org.apache.logging.log4j.core.async.AsyncLoggerConfig.logInBackgroundThread(AsyncLoggerConfig.java:159)
at org.apache.logging.log4j.core.async.EventRoute$1.logMessage(EventRoute.java:46)
at org.apache.logging.log4j.core.async.AsyncLoggerConfig.handleQueueFull(AsyncLoggerConfig.java:149)
at org.apache.logging.log4j.core.async.AsyncLoggerConfig.logToAsyncDelegate(AsyncLoggerConfig.java:136)
at org.apache.logging.log4j.core.async.AsyncLoggerConfig.log(AsyncLoggerConfig.java:116)
at org.apache.logging.log4j.core.config.LoggerConfig.log(LoggerConfig.java:460)
at org.apache.logging.log4j.core.config.AwaitCompletionReliabilityStrategy.log(AwaitCompletionReliabilityStrategy.java:82)
at org.apache.logging.log4j.core.Logger.log(Logger.java:162)
at org.apache.logging.log4j.spi.AbstractLogger.tryLogMessage(AbstractLogger.java:2190)
at org.apache.logging.log4j.spi.AbstractLogger.logMessageTrackRecursion(AbstractLogger.java:2144)
at org.apache.logging.log4j.spi.AbstractLogger.logMessageSafely(AbstractLogger.java:2127)
at org.apache.logging.log4j.spi.AbstractLogger.logMessage(AbstractLogger.java:2003)
at org.apache.logging.log4j.spi.AbstractLogger.logIfEnabled(AbstractLogger.java:1975)
at org.apache.logging.log4j.spi.AbstractLogger.info(AbstractLogger.java:1312)

5. 为何会满,我们的消费线程当时在干啥?

我们来看下当时的消费线程是否有异常,由于当时硬盘 io 看系统监控并没有异常所以这个线程很大可能是 Runnable 的,不断的在写入日志。同时,我们知道,写入文件 java 底层基于 native 调用,所以我们查看 JFR 的 native 方法采集。使用事件查看器中的 Method Profiling Sample Native,右键点击,并选择“使用所选类型事件创建新页”:

在创建出来的新页中,按照线程分组查看,查看 Log4j2 的 disruptor 消费线程,可以得出下图:

可以看出:在出问题的时间段,采集到的写入日志文件的事件数量明显增多很多,并且,通过堆栈可以看到:

"Log4j2-TF-2-AsyncLoggerConfig-1" #26 [23680] daemon prio=5 os_prio=0 cpu=406.25ms elapsed=2.89s tid=0x0000022d6f3d4080 nid=0x5c80 runnable  [0x000000399bbfe000]
java.lang.Thread.State: RUNNABLE
at java.io.FileOutputStream.writeBytes(java.base@17-loom/Native Method)
at java.io.FileOutputStream.write(java.base@17-loom/FileOutputStream.java:365)
at org.apache.logging.log4j.core.appender.OutputStreamManager.writeToDestination(OutputStreamManager.java:261)
- eliminated <0x000000070ee0af40> (a org.apache.logging.log4j.core.appender.rolling.RollingFileManager)
at org.apache.logging.log4j.core.appender.FileManager.writeToDestination(FileManager.java:272)
- eliminated <0x000000070ee0af40> (a org.apache.logging.log4j.core.appender.rolling.RollingFileManager)
at org.apache.logging.log4j.core.appender.rolling.RollingFileManager.writeToDestination(RollingFileManager.java:236)
- eliminated <0x000000070ee0af40> (a org.apache.logging.log4j.core.appender.rolling.RollingFileManager)
at org.apache.logging.log4j.core.appender.OutputStreamManager.flushBuffer(OutputStreamManager.java:293)
- locked <0x000000070ee0af40> (a org.apache.logging.log4j.core.appender.rolling.RollingFileManager)
at org.apache.logging.log4j.core.appender.OutputStreamManager.flush(OutputStreamManager.java:302)
- locked <0x000000070ee0af40> (a org.apache.logging.log4j.core.appender.rolling.RollingFileManager)
at org.apache.logging.log4j.core.appender.AbstractOutputStreamAppender.directEncodeEvent(AbstractOutputStreamAppender.java:199)
at org.apache.logging.log4j.core.appender.AbstractOutputStreamAppender.tryAppend(AbstractOutputStreamAppender.java:190)
at org.apache.logging.log4j.core.appender.AbstractOutputStreamAppender.append(AbstractOutputStreamAppender.java:181)
at org.apache.logging.log4j.core.appender.RollingFileAppender.append(RollingFileAppender.java:312)
at org.apache.logging.log4j.core.config.AppenderControl.tryCallAppender(AppenderControl.java:156)
at org.apache.logging.log4j.core.config.AppenderControl.callAppender0(AppenderControl.java:129)
at org.apache.logging.log4j.core.config.AppenderControl.callAppenderPreventRecursion(AppenderControl.java:120)
at org.apache.logging.log4j.core.config.AppenderControl.callAppender(AppenderControl.java:84)
at org.apache.logging.log4j.core.config.LoggerConfig.callAppenders(LoggerConfig.java:543)
at org.apache.logging.log4j.core.async.AsyncLoggerConfig.callAppenders(AsyncLoggerConfig.java:127)
at org.apache.logging.log4j.core.config.LoggerConfig.processLogEvent(LoggerConfig.java:502)
at org.apache.logging.log4j.core.config.LoggerConfig.log(LoggerConfig.java:485)
at org.apache.logging.log4j.core.async.AsyncLoggerConfig.log(AsyncLoggerConfig.java:121)
at org.apache.logging.log4j.core.async.AsyncLoggerConfig.logToAsyncLoggerConfigsOnCurrentThread(AsyncLoggerConfig.java:169)
at org.apache.logging.log4j.core.async.AsyncLoggerConfigDisruptor$Log4jEventWrapperHandler.onEvent(AsyncLoggerConfigDisruptor.java:111)
at org.apache.logging.log4j.core.async.AsyncLoggerConfigDisruptor$Log4jEventWrapperHandler.onEvent(AsyncLoggerConfigDisruptor.java:97)
at com.lmax.disruptor.BatchEventProcessor.processEvents(BatchEventProcessor.java:168)
at com.lmax.disruptor.BatchEventProcessor.run(BatchEventProcessor.java:125)
at java.lang.Thread.run(java.base@17-loom/Thread.java:1521)

log4j2 调用了很多次 flush, JFR 显示采集到的事件,每次都调用了 flush。每次调用 flush,都会造成文件真正写的 native 调用。而消费慢的原因,其实就是这种 native 调用太多,系统写入不过来了。

问题解决

我们可以通过以下四个方向解决这个问题:

  1. 减少日志输出,精简日志,这是比较基础的方式,也是比较简单的方式,但是身为一个技术人,我们不能满足于此
  2. 增加硬盘 io,这个也是比较基础简单的解决方式
  3. 我们是否可以减少这个 flush 呢?答案是可以的,我们可以配置 Appender 的 immediateFlush 为 false
  4. 增加监控,针对堆栈包含 org.apache.logging.log4j.core.async.AsyncLoggerConfigDisruptor.enqueue 的 java monitor block 事件进行监控,如果发现时间过长或者数量很多的事件则报警或者重建进程

1. 配置 Appender 的 immediateFlush 为 false

我们可以配置 Appender 的 immediateFlush 为 false,例如:

<RollingFile name="file" append="true"
filePattern="./app.log-%d{yyyy.MM.dd.HH}"
immediateFlush="false">
<PatternLayout pattern="${logFormat}"/>
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
</Policies>
<DirectWriteRolloverStrategy maxFiles="72"/>
</RollingFile>

这里的原理对应源码:

AbstractOutputStreamAppender.java

protected void directEncodeEvent(final LogEvent event) {
getLayout().encode(event, manager);
//如果配置了 immdiateFlush (默认为 true)或者当前事件是 EndOfBatch
if (this.immediateFlush || event.isEndOfBatch()) {
manager.flush();
}
}

那么对于 Log4j2 Disruptor 异步日志来说,什么时候 LogEventEndOfBatch 呢?是在消费到的 index 等于生产发布到的最大 index 的时候,这也是比较符合性能设计考虑,即在没有消费完的时候,尽可能地不 flush,消费完当前所有的时候再去 flush:

BatchEventProcessor.java

private void processEvents()
{
T event = null;
long nextSequence = sequence.get() + 1L; while (true)
{
try
{
final long availableSequence = sequenceBarrier.waitFor(nextSequence);
if (batchStartAware != null)
{
batchStartAware.onBatchStart(availableSequence - nextSequence + 1);
} while (nextSequence <= availableSequence)
{
event = dataProvider.get(nextSequence);
//这里 nextSequence == availableSequence 就是 EndOfBatch
eventHandler.onEvent(event, nextSequence, nextSequence == availableSequence);
nextSequence++;
} sequence.set(availableSequence);
}
catch (final TimeoutException e)
{
notifyTimeout(sequence.get());
}
catch (final AlertException ex)
{
if (running.get() != RUNNING)
{
break;
}
}
catch (final Throwable ex)
{
exceptionHandler.handleEventException(ex, nextSequence, event);
sequence.set(nextSequence);
nextSequence++;
}
}
}

2. 增加基于 JFR 事件监控

这个需要 Java 14 以上的版本

Configuration config = Configuration.getConfiguration("default");
//设置监控的锁 block 时间超过多少就会采集
config.getSettings().put("jdk.JavaMonitorEnter#threshold", "1s");
try (var es = new RecordingStream(config)) {
es.onEvent("jdk.JavaMonitorEnter", recordedEvent -> {
//如果堆栈包含我们关注的,则报警
if (recordedEvent.getStackTrace().getFrames().stream().anyMatch(recordedFrame -> recordedFrame.toString().contains("org.apache.logging.log4j.core.async.AsyncLoggerConfigDisruptor.enqueue"))) {
System.out.println("Alarm: " + recordedEvent);
}
});
es.start();
}

微信搜索“我的编程喵”关注公众号,每日一刷,轻松提升技术,斩获各种offer

一次鞭辟入里的 Log4j2 异步日志输出阻塞问题的定位的更多相关文章

  1. 如何监控 Log4j2 异步日志遇到写入瓶颈

    如何监控 Log4j2 异步日志遇到写入瓶颈 在之前的一篇文章中(一次鞭辟入里的 Log4j2 异步日志输出阻塞问题的定位),我们详细分析了一个经典的 Log4j2 异步日志阻塞问题的定位,主要原因还 ...

  2. log4j2异步日志解读(二)AsyncLogger

    前文已经讲了log4j2的AsyncAppender的实现[log4j2异步日志解读(一)AsyncAppender],今天我们看看AsyncLogger的实现. 看了这个图,应该很清楚AsyncLo ...

  3. 动态配置log4j2.xml日志输出文件的位置

    目标:根据启动jar时传进main()的参数动态修改日志位置 一.修改启动项 MainMapLookup.setMainArguments(args);注:不要在lookup设置之前初始化log(如: ...

  4. log4j2异步日志配置及官方文档的问题澄清

    配置及demo 方法一全部打开 加启动参数 -DLog4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextS ...

  5. log4j2异步日志解读(一)AsyncAppender

    log4j.logback.log4j2 历史和关系,我们就在这里不展开讲了.直接上干货,log4j2突出于其他日志的优势,异步日志实现. 看一个东西,首先看官网文档 ,因为前面文章已经讲解了disr ...

  6. 使用Log4j2实现日志输出

    一.说明 如果是使用slf4j规范的,请先引用: <!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-s ...

  7. SpringBoot 6.SpringBoot使用 Log4j2 实现日志输出

    一.添加 Log4j2 的依赖 <!-- 引入 log4j2 必须排除 logging --> <dependency> <groupId>org.springfr ...

  8. Log4j2中的同步日志与异步日志

    1.背景 Log4j 2中记录日志的方式有同步日志和异步日志两种方式,其中异步日志又可分为使用AsyncAppender和使用AsyncLogger两种方式. 2.Log4j2中的同步日志 所谓同步日 ...

  9. 近期业务大量突增微服务性能优化总结-3.针对 x86 云环境改进异步日志等待策略

    最近,业务增长的很迅猛,对于我们后台这块也是一个不小的挑战,这次遇到的核心业务接口的性能瓶颈,并不是单独的一个问题导致的,而是几个问题揉在一起:我们解决一个之后,发上线,之后发现还有另一个的性能瓶颈问 ...

随机推荐

  1. Spring Cloud Alibaba(12)---Gatway概述、简单示例

    Gatway概述.项目搭建 前言 有关网关的概念之前这里不在概述,因为之前在写zuul网关的时候有详细陈述过,地址如下: SpringCloud(7)---网关概念.Zuul项目搭建 SpringCl ...

  2. SwiftUI 简明教程之属性包装器

    本文为 Eul 样章,如果您喜欢,请移步 AppStore/Eul 查看更多内容. Eul 是一款 SwiftUI & Combine 教程 App(iOS.macOS),以文章(文字.图片. ...

  3. linux自动化交互脚本expect详解set timeout 5是 意思是在expect语句中,5s后超时,不再作出选择。

    linux自动化交互脚本expect详解  更新时间:2020年10月21日 10:13:20   作者:lendsomething     这篇文章主要介绍了linux自动化交互脚本expect的相 ...

  4. top,它们的意思分别是1分钟、5分钟、15分钟内系统的平均负荷。

    理解Linux系统负荷   作者: 阮一峰 日期: 2011年7月31日   一.查看系统负荷 如果你的电脑很慢,你或许想查看一下,它的工作量是否太大了. 在Linux系统中,我们一般使用uptime ...

  5. sosreport命令 然后diff 正常的操作系统例如centos

    nux学习笔记:有用的linux命令  发表于 2018-06-25 |  分类于 linux|  字数统计: 1,269 |  阅读时长 ≈ 6 写在前面 这着笔记,整理一些网上搜集到有用的linu ...

  6. lsblk         查看分区和磁盘

    lsblk                                                                             查看分区和磁盘 df -h     ...

  7. 二进制部署K8S-1基本概念

    二进制部署K8S-1基本概念 感谢老男孩教育王导的公开视频,文档整理自https://www.yuque.com/duduniao/k8s. 1.实验环境 1.1 虚拟机 因为在后期运行容器需要有大量 ...

  8. linux进阶之nmtui和nmcli配置网络

    CentOS7配置网络推荐使用NetworkManager服务(不推荐network服务). 图形化方式:nmtui或Applications->System Tools->Setting ...

  9. Lombok 插件安装和使用

    引言 以前的 Java 项目中,充斥着太多毫无技术含量的代码,比如类属性的 getter/setter/toString 方法,还有就是异常处理.I/O 流的关闭操作等.这些样板代码虽然可以通过 ID ...

  10. 【待写Java线程之线程终止 Interrupt 】

    参考:https://bbs.csdn.net/topics/280082639 interrupt()方法不会中断一个正在运行的线程.这一方法实际上完成的是,在线程受到阻塞时抛出一个中断信号,这样线 ...