记录一次springboot自动任务线上突然不执行问题排查过程

      其实问题排查过程不是很复杂,只是我们项目要远程,并且服务器是win,连jdk自带的一些工具都没有,arthas在windowns上兼容性也不好,浪费了很多时间。

1、简述

       首先,springboot的自动任务注解@EnableScheduling和@Scheduled底层原理是默认是单线程的,你也可以配置用自定义的线程池。所以,线上有自动任务突然不执行,肯定是有自动任务线程发生阻塞或者死锁,导致卡住了,其他自动任务都无法执行,对外呈现一个程序假死的情况。

       首先简单说下多线程的配置方式,不是本文重点,简单记录下:

import org.springframework.boot.autoconfigure.batch.BatchProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import java.lang.reflect.Method;
import java.util.concurrent.Executors;
@Configuration
public class ScheduleConfig implements SchedulingConfigurer {
  @Override
  public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
    Method[] methods = BatchProperties.Job.class.getMethods();
    int defaultPoolSize = 3;
    int corePoolSize = 0;
    if (methods != null && methods.length > 0) {
      for (Method method : methods) {
        Scheduled annotation = method.getAnnotation(Scheduled.class);
        if (annotation != null) {
          corePoolSize++;
        }
      }
      if (defaultPoolSize > corePoolSize)
        corePoolSize = defaultPoolSize;
    }
    taskRegistrar.setScheduler(Executors.newScheduledThreadPool(corePoolSize));
  }
}

           由于这个问题重启后就很难复现了,所以我没有立即重启,而是想办法通过jps jmap jstack这些好用的工具查看线程堆栈信息,看是不是发生死锁或者其他问题;可是坑爹的是,我们这个项目只有jre运行环境,所以我把相同版本的jdk下的jps jmap jhat jstack这些exe文件直接拷贝到jre的bin目录下,发现可以执行了。

2、jstack查看线程

首先查看下tomcat的进程号,然后

jstack pid

"scheduling-1" #287 prio=5 os_prio=0 tid=0x00000000259c3800 nid=0x138c8 runnable [0x000000004749d000]
   java.lang.Thread.State: RUNNABLE
	at java.net.SocketInputStream.socketRead0(Native Method)
	at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
	at java.net.SocketInputStream.read(SocketInputStream.java:171)
	at java.net.SocketInputStream.read(SocketInputStream.java:141)
	at oracle.net.ns.Packet.receive(Packet.java:300)
	at oracle.net.ns.DataPacket.receive(DataPacket.java:106)
	at oracle.net.ns.NetInputStream.getNextPacket(NetInputStream.java:315)
	at oracle.net.ns.NetInputStream.read(NetInputStream.java:260)
	at oracle.net.ns.NetInputStream.read(NetInputStream.java:185)
	at oracle.net.ns.NetInputStream.read(NetInputStream.java:102)
	at oracle.jdbc.driver.T4CSocketInputStreamWrapper.readNextPacket(T4CSocketInputStreamWrapper.java:124)
	at oracle.jdbc.driver.T4CSocketInputStreamWrapper.read(T4CSocketInputStreamWrapper.java:80)
	at oracle.jdbc.driver.T4CMAREngine.unmarshalUB1(T4CMAREngine.java:1137)
	at oracle.jdbc.driver.T4CTTIfun.receive(T4CTTIfun.java:290)
	at oracle.jdbc.driver.T4CTTIfun.doRPC(T4CTTIfun.java:192)
	at oracle.jdbc.driver.T4CTTIoauthenticate.doOAUTH(T4CTTIoauthenticate.java:380)
	at oracle.jdbc.driver.T4CTTIoauthenticate.doOAUTH(T4CTTIoauthenticate.java:760)
	at oracle.jdbc.driver.T4CConnection.logon(T4CConnection.java:401)
	at oracle.jdbc.driver.PhysicalConnection.<init>(PhysicalConnection.java:546)
	at oracle.jdbc.driver.T4CConnection.<init>(T4CConnection.java:236)
	at oracle.jdbc.driver.T4CDriverExtension.getConnection(T4CDriverExtension.java:32)
	at oracle.jdbc.driver.OracleDriver.connect(OracleDriver.java:521)
	at java.sql.DriverManager.getConnection(DriverManager.java:664)
	at java.sql.DriverManager.getConnection(DriverManager.java:247)
	at com.hikvision.ctm05cqfskdj.module.service.impl.SyncPersonInfoServiceImp.getNewConnection(SyncPersonInfoServiceImp.java:189)
	at com.hikvision.ctm05cqfskdj.module.service.impl.SyncPersonInfoServiceImp.syncPerson(SyncPersonInfoServiceImp.java:86)
	at com.hikvision.ctm05cqfskdj.module.task.AutoSyncPersonToIscTask.autoSyncPersonTask(AutoSyncPersonToIscTask.java:37)
	- locked <0x000000072a8cca68> (a com.hikvision.ctm05cqfskdj.module.task.AutoSyncPersonToIscTask$$EnhancerBySpringCGLIB$$b642cbea)
	at sun.reflect.GeneratedMethodAccessor3553.invoke(Unknown Source)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:343)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:198)
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:752)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
	at org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoinPoint.java:88)
	at org.springframework.cloud.sleuth.instrument.scheduling.TraceSchedulingAspect.traceBackgroundThread(TraceSchedulingAspect.java:70)
	at sun.reflect.GeneratedMethodAccessor2405.invoke(Unknown Source)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:644)
	at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:633)
	at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:70)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
	at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:93)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
	at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:688)
	at com.hikvision.ctm05cqfskdj.module.task.AutoSyncPersonToIscTask$$EnhancerBySpringCGLIB$$ad984c90.autoSyncPersonTask(<generated>)
	at sun.reflect.GeneratedMethodAccessor3553.invoke(Unknown Source)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.springframework.scheduling.support.ScheduledMethodRunnable.run(ScheduledMethodRunnable.java:84)
	at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:54)
	at org.springframework.scheduling.concurrent.ReschedulingRunnable.run(ReschedulingRunnable.java:93)
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
	at java.util.concurrent.FutureTask.run(FutureTask.java:266)
	at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:180)
	at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)

   Locked ownable synchronizers:
	- <0x0000000721613c10> (a java.util.concurrent.ThreadPoolExecutor$Worker)

关于堆栈查看,很简单,关注以下几个点,另外,这里给出一个堆栈分析网站,可视化的,很牛逼:https://fastthread.io/
 

      堆栈信息很多,忽略掉其他的,根据线程名称查scheduling-1,也没发现发现死锁,scheduling-1线程阻塞在了java.net.SocketInputStream.socketRead0(Native Method)这个本地方法,并且状态是RUNABLE,初步判断是这个地方阻塞,查看堆栈信息,发现代码行:

at com.hikvision.ctm05cqfskdj.module.service.impl.SyncPersonInfoServiceImp.getNewConnection(SyncPersonInfoServiceImp.java:189)
/**
     * 每次人员同步都去清空连接池中的连接(不管是否有效),拿最新连接去执行自动任务
     * */
    public void getNewConnection(){
        log.info("开始获取最新连接,放入连接池");
        poolClear();
        LinkedList<Connection> pool = JdbcHelper.getInstance().getPool();
        try {
            log.info("解密前username={},password={}",username,password);
            String decryptUserName = ConfigEncryptUtil.decrypt(username);
            String decryptPassword = ConfigEncryptUtil.decrypt(password);
            for(int i = 0; i < JDBC_POOL_SIZE; i++) {
                Connection conn = DriverManager.getConnection(url, decryptUserName, decryptPassword);
                if (null != conn && pool.size() < JDBC_POOL_SIZE){
                    pool.push(conn);
                }
            }
        } catch (Exception e) {
            log.error("获取连接异常.e={},size={}",e.getMessage(),pool.size());
        }
        log.info("获取连接结束。size={}",pool.size());
    }


通过日志发现,"开始获取最新连接,放入连接池"这行日志没有与之对应的“获取连接结束”,说明这个方法阻塞了。再进一步定位,很容易就发现:

Connection conn = DriverManager.getConnection(url, decryptUserName, decryptPassword);

这行代码可能有问题,点进去看:

private static volatile int loginTimeout = 0;

/**
     * Sets the maximum time in seconds that a driver will wait
     * while attempting to connect to a database once the driver has
     * been identified.
     *
     * @param seconds the login time limit in seconds; zero means there is no limit
     * @see #getLoginTimeout
     */
    public static void setLoginTimeout(int seconds) {
        loginTimeout = seconds;
    }

注意这个注释:登录时间限制以秒为单位;零表示没有限制 ,而默认值刚好是0;所以基本定位ok!

3、查看GC情况

gc情况正常,CMS垃圾回收算是比较高效的并发收集器,不过最新的ZGC是未来的趋势,或许以后都不需要gc调优了!

Attaching to process ID 61008, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.181-b13

using parallel threads in the new generation.
using thread-local object allocation.
Concurrent Mark-Sweep GC

Heap Configuration:
   MinHeapFreeRatio         = 40
   MaxHeapFreeRatio         = 70
   MaxHeapSize              = 3221225472 (3072.0MB)
   NewSize                  = 536870912 (512.0MB)
   MaxNewSize               = 536870912 (512.0MB)
   OldSize                  = 536870912 (512.0MB)
   NewRatio                 = 2
   SurvivorRatio            = 8
   MetaspaceSize            = 21807104 (20.796875MB)
   CompressedClassSpaceSize = 1073741824 (1024.0MB)
   MaxMetaspaceSize         = 17592186044415 MB
   G1HeapRegionSize         = 0 (0.0MB)

Heap Usage:
New Generation (Eden + 1 Survivor Space):
   capacity = 483196928 (460.8125MB)
   used     = 423850536 (404.21537017822266MB)
   free     = 59346392 (56.597129821777344MB)
   87.71796992881544% used
Eden Space:
   capacity = 429522944 (409.625MB)
   used     = 417381920 (398.0464172363281MB)
   free     = 12141024 (11.578582763671875MB)
   97.17337009126106% used
From Space:
   capacity = 53673984 (51.1875MB)
   used     = 6468616 (6.168952941894531MB)
   free     = 47205368 (45.01854705810547MB)
   12.051678518963675% used
To Space:
   capacity = 53673984 (51.1875MB)
   used     = 0 (0.0MB)
   free     = 53673984 (51.1875MB)
   0.0% used
concurrent mark-sweep generation:
   capacity = 1133961216 (1081.4296875MB)
   used     = 372137864 (354.89832305908203MB)
   free     = 761823352 (726.531364440918MB)
   32.817512517112405% used

90956 interned Strings occupying 9060464 bytes.

4、原因:socketRead0阻塞

       现在的Java EE 系统通常都依赖于远程服务,需要通过网络请求获取远程服务。不管是使用什么协议(HTTP,HTTPS,JDBC 等)请求远程服务,Java VM最终都将委托给Socket去实现。首先会通过connect方法与远程服务建立连接,连接建立成功后就可以 write/read 与远程服务实现数据通信。Socket.connect()、Socket.write()、Socket.read() 方法调用都被称为阻塞IO调用。这些调用由于网络延时、服务器响应缓慢等问题往往会导致线程挂起。

5、何时出现阻塞

通过日志发现,阻塞的时机发生在连接oracle的时候如果报错:

ORA-00257: 存檔器錯誤. 連線僅限內部, 直到釋出為止

出现这个错误,才发现了自动任务不执行的情况,表明这个错误会导致获取连接被阻塞;这个错误查阅资料发现是:

 

5、解决方案

//单位s:设置10分钟
DriverManager.setLoginTimeout(10 * 60);
Connection conn = DriverManager.getConnection(url, decryptUserName, decryptPassword);

6、验证是否生效

只能改了之后,看后面是否还出现自动任务不执行的情况了。一个月后评论给出验证结果!

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
线上内存飙升是一个比较严重的问题,如果不及时处理,可能会导致系统崩溃。以下是一般的排查问题过程: 1. 监控告警分析:系统应该有监控告警机制,如果内存使用率过高,应该及时发出告警并记录监控数据。首先,需要分析告警信息和监控数据,确认内存使用率确实出现了异常。 2. 定位问题代码:通过监控工具、日志和代码调试等方法,确定哪些代码出现了内存使用率飙升的问题。可以使用 JVM 的内存分析工具,例如 jmap、jhat、jstack、VisualVM 等来分析内存使用情况,并找出内存泄漏的代码位置。 3. 解决问题代码:根据定位出的代码问题,进行修复。对于内存泄漏问题,需要分析代码中的对象生命周期,释放不再使用的对象,并检查是否存在对象引用未释放的情况。对于频繁创建和销毁对象、大对象等问题,需要优化代码逻辑,减少内存占用。 4. 测试验证:完成修复后,需要进行测试验证,确保问题得到了解决,并且没有引入新的问题。 5. 监控预警优化:针对该问题,需要优化监控预警机制,避免类似问题再次出现。可以增加更加详细的监控数据、设置更加敏感的告警阈值等等。 总之,线上内存飙升是一个比较严重的问题,需要及时发现、定位、修复和验证。同时,也需要优化监控预警机制,避免类似问题再次出现。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值