Arthas实践--抽丝剥茧排查线上应用日志打满问题
现象
在应用的 service_stdout.log
里一直输出下面的日志,直接把磁盘打满了:
23:07:34.441 [TAIRCLIENT-1-thread-1] DEBUG io.netty.channel.nio.NioEventLoop - Selector.select() returned prematurely 14 times in a row.
23:07:34.460 [TAIRCLIENT-1-thread-3] DEBUG io.netty.channel.nio.NioEventLoop - Selector.select() returned prematurely 3 times in a row.
23:07:34.461 [TAIRCLIENT-1-thread-4] DEBUG io.netty.channel.nio.NioEventLoop - Selector.select() returned prematurely 3 times in a row.
23:07:34.462 [TAIRCLIENT-1-thread-5] DEBUG io.netty.channel.nio.NioEventLoop - Selector.select() returned prematurely 3 times in a row.
service_stdout.log
是进程标准输出的重定向,可以初步判定是tair插件把日志输出到了stdout里。
尽管有了初步的判断,但是具体logger为什么会打到stdout里,还需要进一步排查,常见的方法可能是本地debug。
下面介绍利用arthas直接在线上定位问题的过程,主要使用 sc
和 getstatic
命令。
https://alibaba.github.io/arthas/sc.html
https://alibaba.github.io/arthas/getstatic.html
定位logger的具体实现
日志是 io.netty.channel.nio.NioEventLoop
输出的,到netty的代码里查看,发现是DEBUG级别的输出:
https://github.com/netty/netty/blob/netty-4.0.35.Final/transport/src/main/java/io/netty/channel/nio/NioEventLoop.java#L673
然后用arthas的 sc
命令来查看具体的 io.netty.channel.nio.NioEventLoop
是从哪里加载的。
class-info io.netty.channel.nio.NioEventLoop
code-source file:/opt/app/plugins/tair-plugin/lib/netty-all-4.0.35.Final.jar!/
name io.netty.channel.nio.NioEventLoop
isInterface false
isAnnotation false
isEnum false
isAnonymousClass false
isArray false
isLocalClass false
isMemberClass false
isPrimitive false
isSynthetic false
simple-name NioEventLoop
modifier final,public
annotation
interfaces
super-class +-io.netty.channel.SingleThreadEventLoop
+-io.netty.util.concurrent.SingleThreadEventExecutor
+-io.netty.util.concurrent.AbstractScheduledEventExecutor
+-io.netty.util.concurrent.AbstractEventExecutor
+-java.util.concurrent.AbstractExecutorService
+-java.lang.Object
class-loader +-tair-plugin's ModuleClassLoader
classLoaderHash 73ad2d6
可见,的确是从tair插件里加载的。
查看NioEventLoop的代码,可以发现它有一个 logger
的field:
public final class NioEventLoop extends SingleThreadEventLoop {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(NioEventLoop.class);
使用arthas的 getstatic
命令来查看这个 logger
具体实现类是什么(使用 -c
参数指定classloader):
$ getstatic -c 73ad2d6 io.netty.channel.nio.NioEventLoop logger 'getClass().getName()'
field: logger
@String[io.netty.util.internal.logging.Slf4JLogger]
可以发现是 Slf4JLogger
。
再查看io.netty.util.internal.logging.Slf4JLogger的实现,发现它内部有一个logger的field:
package io.netty.util.internal.logging;
import org.slf4j.Logger;
/**
* <a href="http://www.slf4j.org/">SLF4J</a> logger.
*/
class Slf4JLogger extends AbstractInternalLogger {
private static final long serialVersionUID = 108038972685130825L;
private final transient Logger logger;
那么使用arthas的 getstatic
命令来查看这个 logger
属性的值:
$ getstatic -c 73ad2d6 io.netty.channel.nio.NioEventLoop logger 'logger'
field: logger
@Logger[
serialVersionUID=@Long[5454405123156820674],
FQCN=@String[ch.qos.logback.classic.Logger],
name=@String[io.netty.channel.nio.NioEventLoop],
level=null,
effectiveLevelInt=@Integer[10000],
parent=@Logger[Logger[io.netty.channel.nio]],
childrenList=null,
aai=null,
additive=@Boolean[true],
loggerContext=@LoggerContext[ch.qos.logback.classic.LoggerContext[default]],
]
可见,logger的最本质实现类是: ch.qos.logback.classic.Logger
。
再次用 getstatic
命令来确定jar包的location:
$ getstatic -c 73ad2d6 io.netty.channel.nio.NioEventLoop logger 'logger.getClass().getProtectionDomain().getCodeSource().getLocation()'
field: logger
@URL[
BUILTIN_HANDLERS_PREFIX=@String[sun.net.www.protocol],
serialVersionUID=@Long[-7627629688361524110],
protocolPathProp=@String[java.protocol.handler.pkgs],
protocol=@String[jar],
host=@String[],
port=@Integer[-1],
file=@String[file:/opt/app/plugins/tair-plugin/lib/logback-classic-1.2.3.jar!/],
query=null,
authority=@String[],
path=@String[file:/opt/app/plugins/tair-plugin/lib/logback-classic-1.2.3.jar!/],
userInfo=null,
ref=null,
hostAddress=null,
handler=@Handler[com.taobao.pandora.loader.jar.Handler@1a0c361e],
hashCode=@Integer[126346621],
tempState=null,
factory=@TomcatURLStreamHandlerFactory[org.apache.catalina.webresources.TomcatURLStreamHandlerFactory@3edd7b7],
handlers=@Hashtable[isEmpty=false;size=4],
streamHandlerLock=@Object[java.lang.Object@488ccac9],
serialPersistentFields=@ObjectStreamField[][isEmpty=false;size=7],
]
可见这个 ch.qos.logback.classic.Logger
的确是tair插件里加载的。
定位logger的level
上面已经定位logger的实现类是 ch.qos.logback.classic.Logger
,但是为什么它会输出 DEBUG
level的日志?
其实在上面的 getstatic-c73ad2d6io.netty.channel.nio.NioEventLooplogger'logger'
输出里,已经打印出它的level是null了。如果对logger有所了解的话,可以知道当child logger的level为null时,它的level取决于parent logger的level。
我们再来看下 ch.qos.logback.classic.Logger
的代码,它有一个parent logger的属性:
public final class Logger implements org.slf4j.Logger, LocationAwareLogger, AppenderAttachable<ILoggingEvent>, Serializable {
/**
* The parent of this category. All categories have at least one ancestor
* which is the root category.
*/
transient private Logger parent;
所以,我们可以通过 getstatic
来获取到这个parent属性的内容。然后通过多个parent操作,可以发现level都是null,最终发现ROOT level是DEBUG 。
$ getstatic -c 73ad2d6 io.netty.channel.nio.NioEventLoop logger 'logger.parent.parent.parent.parent.parent'
field: logger
@Logger[
serialVersionUID=@Long[5454405123156820674],
FQCN=@String[ch.qos.logback.classic.Logger],
name=@String[ROOT],
level=@Level[DEBUG],
effectiveLevelInt=@Integer[10000],
parent=null,
childrenList=@CopyOnWriteArrayList[isEmpty=false;size=2],
aai=@AppenderAttachableImpl[ch.qos.logback.core.spi.AppenderAttachableImpl@1ecf9bae],
additive=@Boolean[true],
loggerContext=@LoggerContext[ch.qos.logback.classic.LoggerContext[default]],
]
所以 io.netty.channel.nio.NioEventLoop
的logger的level取的是ROOT logger的配置,即默认值 DEBUG
。
具体实现可以查看 ch.qos.logback.classic.LoggerContext
public LoggerContext() {
super();
this.loggerCache = new ConcurrentHashMap<String, Logger>();
this.loggerContextRemoteView = new LoggerContextVO(this);
this.root = new Logger(Logger.ROOT_LOGGER_NAME, null, this);
this.root.setLevel(Level.DEBUG);
loggerCache.put(Logger.ROOT_LOGGER_NAME, root);
initEvaluatorMap();
size = 1;
this.frameworkPackages = new ArrayList<String>();
}
为什么logback输出到了stdout里
上面我们得到结论
tair插件里的logback默认的level是DEBUG,导致netty里的日志可以被打印出来
那么我们可以猜测:
tair里的logback没有特殊配置,或者只配置了tair自己的package,导致ROOT logger的日志直接输出到stdout里
那么实现上tair里是使用了 logger-api
,它通过 LoggerFactory.getLogger
函数获取到了自己package的logger,然后设置level为 INFO
,并设置了appender。
换而言之,tair插件里的logback没有设置ROOT logger,所以它的默认level是DEBUG,并且默认的appender会输出到stdout里。
所以tair插件可以增加设置ROOT logger level为 INFO
来修复这个问题。
private static com.taobao.middleware.logger.Logger logger
= com.taobao.middleware.logger.LoggerFactory.getLogger("com.taobao.tair");
public static com.taobao.middleware.logger.Logger infolog
= com.taobao.middleware.logger.LoggerFactory.getLogger("com.taobao.tair.custom-infolog");
public static int JM_LOG_RETAIN_COUNT = 3;
public static String JM_LOG_FILE_SIZE = "200MB";
static {
try {
String tmp = System.getProperty("JM.LOG.RETAIN.COUNT", "3");
JM_LOG_RETAIN_COUNT = Integer.parseInt(tmp);
} catch (NumberFormatException e) {
}
JM_LOG_FILE_SIZE = System.getProperty("JM.LOG.FILE.SIZE", "200MB");
logger.setLevel(Level.INFO);
logger.activateAppenderWithSizeRolling("tair-client", "tair-client.log", "UTF-8",
TairUtil.splitSize(JM_LOG_FILE_SIZE, 0.8 / (JM_LOG_RETAIN_COUNT + 1)), JM_LOG_RETAIN_COUNT);
logger.setAdditivity(false);
logger.activateAsync(500, 100);
logger.info("JM_LOG_RETAIN_COUNT " + JM_LOG_RETAIN_COUNT + " JM_LOG_FILE_SIZE " + JM_LOG_FILE_SIZE);
infolog.setLevel(Level.INFO);
infolog.activateAppenderWithSizeRolling("tair-client", "tair-client-info.log", "UTF-8", "10MB", 1);
infolog.setAdditivity(false);
infolog.activateAsync(500, 100);
总结
tair插件里直接以api的方式设置了自己package下的logger
tair插件里netty的logger的packge和tair并不一样,所以它最终取的是ROOT logger的配置
logback默认的ROOT logger level是
DEBUG
,输出是stdout利用arthas的
sc
命令定位具体的类利用arthas的
getstatic
获取static filed的值利用logger parent层联的特性,可以向上一层层获取到ROOT logger的配置
链接
Arthas开源:https://github.com/alibaba/arthas
往期精选
本号专注于后端技术、JVM问题排查和优化、Java面试题、个人成长和自我管理等主题,为读者提供一线开发者的工作和成长经验,期待你能在这里有所收获。
Arthas实践--抽丝剥茧排查线上应用日志打满问题的更多相关文章
- Linux命令排查线上问题常用的几个
排查线上问题常用的几个Linux命令 https://www.cnblogs.com/cjsblog/p/9562380.html top 相当于Windows任务管理器 可以看到,输出结果分两部分, ...
- 记一次linux通过jstack定位CPU使用过高问题或排查线上死锁问题
一.java定位进程 在服务器中终端输入命令:top 可以看到进程ID,为5421的cpu这列100多了. 记下这个数字:5421 二.定位问题进程对应的线程 然后在服务器中终端输入命令:top -H ...
- Arthas协助排查线上skywalking不可用问题
前言 首先描述下问题的背景,博主有个习惯,每天上下班的时候看下skywalking的trace页面的error情况.但是某天突然发现生产环境skywalking页面没有任何数据了,页面也没有显示任何的 ...
- 你要偷偷学会排查线上CPU飙高的问题,然后惊艳所有人!
GitHub 20k Star 的Java工程师成神之路,不来了解一下吗! GitHub 20k Star 的Java工程师成神之路,真的不来了解一下吗! GitHub 20k Star 的Java工 ...
- 轻松排查线上Node内存泄漏问题
I. 三种比较典型的内存泄漏 一. 闭包引用导致的泄漏 这段代码已经在很多讲解内存泄漏的地方引用了,非常经典,所以拿出来作为第一个例子,以下是泄漏代码: 'use strict'; const exp ...
- 推荐几个我近期排查线上http接口偶发415时用到的工具
导读:近期有一个业务部门的同学反馈说他负责的C工程在小概率情况下SpringMvc会返回415,通过输出的日志可以确定是SpringMvc找不到content-type这个头了,具体为什么找不到了呢? ...
- 利用JVM在线调试工具排查线上问题
在生产上我们经常会碰到一些不好排查的问题,例如线程安全问题,用最简单的threaddump或者heapdump不好查到问题原因.为了排查这些问题,有时我们会临时加一些日志,比如在一些关键的函数里打印出 ...
- 记一次排查线上MySQL死锁过程,不能只会curd,还要知道加锁原理
昨晚我正在床上睡得着着的,突然来了一条短信. 啥,线上MySQL死锁了,我赶紧登录线上系统,查看业务日志. 能清楚看到是这条insert语句发生了死锁. MySQL如果检测到两个事务发生了死锁,会回滚 ...
- JVM jmap dump 分析dump文件 / 如何使用Eclipse MemoryAnalyzer MAT 排查线上问题
jhat简介 jhat用来分析java堆的命令,可以将堆中的对象以html的形式显示出来,包括对象的数量,大小等等,并支持对象查询语言 这个工具并不是想用于应用系统中而是用于"离线" ...
随机推荐
- DataGuard搭建逻辑StandBy
DataGuard搭建逻辑StandBy 原创 作者:bayaim 时间:2016-03-31 17:23:48 272 0删除编辑 物理StandBy优点是效率高,缺点是只读模式不能恢复,恢复模 ...
- Linux--简单实现nfs的目录挂载,ntp时间同步
一.NFS (Network FileSystem) 网络文件系统 是FreeBSD支持的文件系统中的一种,它允许网络中的计算机之间通过TCP/IP网络共享资源. 在NFS的应用中,本地NFS的客户端 ...
- viscode 使用 格式的配置
viscode 现在也越来越适用于 python 开发使用的 IDEA ,慢慢不逊色于 pycharm .下面是关于使用的 格式[字体颜色,背景之类的配置] 1. 2. 如果是设置的 中文显示,在界 ...
- 远程控制服务(SSH)之Windows远程登陆Linux主机
本篇blog同样介绍两种方式进行. 首先进行准备工作: 1.所用到的工具如下: (1) 装有Linux系统的VMware虚拟机*1 (2) 终端连接工具Xshell 6 2.将Wind ...
- 数据库(update tab1 set tab1.name=tab1.name+(select t2.name from tab2 t2 where t2.id=tab1.id))
有t1 和 t2 两个表,表中的数据和字段如下: 执行 如下SQL语句: update tab1 set tab1.name=tab1.name+(select t2.name from tab2 t ...
- 工具资源系列之给 windows 虚拟机装个 centos
前面我们已经介绍了如何在 Windows 宿主机安装 VMware 虚拟机,这节我们将利用安装好的 VMware 软件安装 centos 系统. 前情回顾 由于大多数人使用的 Windows 电脑而工 ...
- 【CometOJ】Comet OJ - Contest #8 解题报告
点此进入比赛 \(A\):杀手皇后(点此看题面) 大致题意: 求字典序最小的字符串. 一场比赛总有送分题... #include<bits/stdc++.h> #define Tp tem ...
- A1063 Set Similarity (25 分)
一.技术总结 这个题目是属于set容器的内容,使用可以减少很多代码量 开始试过在同一个for循环中定义两个auto,结果编译通不过,有时候构思很重要,就比如这一题,开始我是一个一个去加,而代码中是,先 ...
- 介绍一个比较强大的网页剪藏工具——web-clipper
网址:https://clipper.website/ 在语雀的第三方工具处遇到的. 感觉可以代替印象笔记网页端的剪藏,还是很强大的.
- JVM-基本操作
1.我们为什么要对jvm做优化?在本地开发环境中我们很少会遇到需要对jvm进行优化的需求,但是到了生产环境,我们可能将有下面的需求: 运行的应用“卡住了”,日志不输出,程序没有反应服务器的CPU负载突 ...