博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
[case1]记一次spring schedule异常
阅读量:6974 次
发布时间:2019-06-27

本文共 13501 字,大约阅读时间需要 45 分钟。

本文主要记录一下spring schedule调度异常。

相关背景

  • 调度配置
@Scheduled(cron = "0 0 22 * * ?")    public void doSomething() throws InterruptedException {        //remote http call    }复制代码
  • 问题

从某一天开始这个调度没有执行

排查

查看日志

查看了下日志,没有抛异常

查看系统状态

查了那个时间点的系统指标,没啥异常,附近有个young gc,不过也就80ms

jstack

jstack -l pid复制代码

初步看来线程堆栈,没看到有啥死锁信息

假设

一开始有点怀疑是不是spring schedule因为线程池线程不够,或者前面几个时间点的定时任务执行时间过长导致这个调度任务一直无法执行,或者导致了misfire的情况。

后来线下单独试图去复现,发现spring schedule只要有放入队列,即使misfire了,过了设定的调度时间点,还是会执行的。就单单这个定时任务有问题,其他的定时任务每天都执行好好的。

ScheduledThreadPoolExecutor

java/util/concurrent/ScheduledThreadPoolExecutor.java

public ScheduledThreadPoolExecutor(int corePoolSize,                                       ThreadFactory threadFactory,                                       RejectedExecutionHandler handler) {        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,              new DelayedWorkQueue(), threadFactory, handler);}public ThreadPoolExecutor(int corePoolSize,                              int maximumPoolSize,                              long keepAliveTime,                              TimeUnit unit,                              BlockingQueue
workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) { if (corePoolSize < 0 || maximumPoolSize <= 0 || maximumPoolSize < corePoolSize || keepAliveTime < 0) throw new IllegalArgumentException(); if (workQueue == null || threadFactory == null || handler == null) throw new NullPointerException(); this.corePoolSize = corePoolSize; this.maximumPoolSize = maximumPoolSize; this.workQueue = workQueue; this.keepAliveTime = unit.toNanos(keepAliveTime); this.threadFactory = threadFactory; this.handler = handler; }复制代码

这里创建的是DelayedWorkQueue

ThreadPoolExecutor#runWorker

java/util/concurrent/ThreadPoolExecutor.java

final void runWorker(Worker w) {        Thread wt = Thread.currentThread();        Runnable task = w.firstTask;        w.firstTask = null;        w.unlock(); // allow interrupts        boolean completedAbruptly = true;        try {            while (task != null || (task = getTask()) != null) {                w.lock();                // If pool is stopping, ensure thread is interrupted;                // if not, ensure thread is not interrupted.  This                // requires a recheck in second case to deal with                // shutdownNow race while clearing interrupt                if ((runStateAtLeast(ctl.get(), STOP) ||                     (Thread.interrupted() &&                      runStateAtLeast(ctl.get(), STOP))) &&                    !wt.isInterrupted())                    wt.interrupt();                try {                    beforeExecute(wt, task);                    Throwable thrown = null;                    try {                        task.run();                    } catch (RuntimeException x) {                        thrown = x; throw x;                    } catch (Error x) {                        thrown = x; throw x;                    } catch (Throwable x) {                        thrown = x; throw new Error(x);                    } finally {                        afterExecute(task, thrown);                    }                } finally {                    task = null;                    w.completedTasks++;                    w.unlock();                }            }            completedAbruptly = false;        } finally {            processWorkerExit(w, completedAbruptly);        }    }复制代码

这里调用了task.run();

ScheduledThreadPoolExecutor.ScheduledFutureTask#run

/**         * Overrides FutureTask version so as to reset/requeue if periodic.         */        public void run() {            boolean periodic = isPeriodic();            if (!canRunInCurrentRunState(periodic))                cancel(false);            else if (!periodic)                ScheduledFutureTask.super.run();            else if (ScheduledFutureTask.super.runAndReset()) {                setNextRunTime();                reExecutePeriodic(outerTask);            }        }            /**     * Requeues a periodic task unless current run state precludes it.     * Same idea as delayedExecute except drops task rather than rejecting.     *     * @param task the task     */    void reExecutePeriodic(RunnableScheduledFuture
task) { if (canRunInCurrentRunState(true)) { super.getQueue().add(task); if (!canRunInCurrentRunState(true) && remove(task)) task.cancel(false); else ensurePrestart(); } }复制代码

这里每当执行完之后判断下一次的执行时间,然后重新放入队列,这样完成周期性调度。

看到这里再去仔细看来下日志,发现有一天的调度任务执行了没有打印结束日志,那么开始怀疑是不是这个定时任务一直没有执行完,导致接下来的几天的调度都一直没法放入队列执行。

线程堆栈

"job-async-5" #44 prio=5 os_prio=0 tid=0x00007fb9aa7a4000 nid=0x42 runnable [0x00007fb98dfee000]   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:170)	at java.net.SocketInputStream.read(SocketInputStream.java:141)	at java.io.BufferedInputStream.fill(BufferedInputStream.java:246)	at java.io.BufferedInputStream.read1(BufferedInputStream.java:286)	at java.io.BufferedInputStream.read(BufferedInputStream.java:345)	- locked <0x00000006c6167e60> (a java.io.BufferedInputStream)	at sun.net.www.http.HttpClient.parseHTTPHeader(HttpClient.java:704)	at sun.net.www.http.HttpClient.parseHTTP(HttpClient.java:647)	at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1536)	- locked <0x00000006c6167eb8> (a sun.net.www.protocol.http.HttpURLConnection)	at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1441)	- locked <0x00000006c6167eb8> (a sun.net.www.protocol.http.HttpURLConnection)	at java.net.HttpURLConnection.getResponseCode(HttpURLConnection.java:480)	at org.springframework.http.client.SimpleClientHttpResponse.getRawStatusCode(SimpleClientHttpResponse.java:52)	at org.springframework.cloud.netflix.metrics.DefaultMetricsTagProvider.clientHttpRequestTags(DefaultMetricsTagProvider.java:44)	at org.springframework.cloud.netflix.metrics.MetricsClientHttpRequestInterceptor.intercept(MetricsClientHttpRequestInterceptor.java:75)	at org.springframework.http.client.InterceptingClientHttpRequest$InterceptingRequestExecution.execute(InterceptingClientHttpRequest.java:86)	at org.springframework.http.client.InterceptingClientHttpRequest.executeInternal(InterceptingClientHttpRequest.java:70)	at org.springframework.http.client.AbstractBufferingClientHttpRequest.executeInternal(AbstractBufferingClientHttpRequest.java:48)	at org.springframework.http.client.AbstractClientHttpRequest.execute(AbstractClientHttpRequest.java:53)	at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:652)	at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:613)	at org.springframework.web.client.RestTemplate.postForEntity(RestTemplate.java:407)	at org.springframework.web.client.RestTemplate$$FastClassBySpringCGLIB$$aa4e9ed0.invoke(
) at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204) at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:721) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157) at org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoinPoint.java:85) at org.springframework.cloud.netflix.metrics.RestTemplateUrlTemplateCapturingAspect.captureUrlTemplate(RestTemplateUrlTemplateCapturingAspect.java:33) at sun.reflect.GeneratedMethodAccessor235.invoke(Unknown Source) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:497) at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:629) at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:618) at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:70) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:656) at org.springframework.web.client.RestTemplate$$EnhancerBySpringCGLIB$$68afae5e.postForEntity(
)复制代码

又在查看了没有结束的定时任务的线程堆栈,发现确实是卡住了,不过这里线程状态一直是RUNNABLE.

SocketInputStream.socketRead0

罪魁祸首就在这个SocketInputStream.socketRead0。不过有个疑问,默认RestTemplate应该是有设置超时时间才对的。

spring-web-4.3.7.RELEASE-sources.jar!/org/springframework/web/client/RestTemplate.java

protected 
T doExecute(URI url, HttpMethod method, RequestCallback requestCallback, ResponseExtractor
responseExtractor) throws RestClientException { Assert.notNull(url, "'url' must not be null"); Assert.notNull(method, "'method' must not be null"); ClientHttpResponse response = null; try { ClientHttpRequest request = createRequest(url, method); if (requestCallback != null) { requestCallback.doWithRequest(request); } response = request.execute(); handleResponse(url, method, response); if (responseExtractor != null) { return responseExtractor.extractData(response); } else { return null; } } catch (IOException ex) { String resource = url.toString(); String query = url.getRawQuery(); resource = (query != null ? resource.substring(0, resource.indexOf(query) - 1) : resource); throw new ResourceAccessException("I/O error on " + method.name() + " request for \"" + resource + "\": " + ex.getMessage(), ex); } finally { if (response != null) { response.close(); } } }复制代码

这里调用了createRequest

spring-web-4.3.7.RELEASE-sources.jar!/org/springframework/http/client/support/HttpAccessor.java

private ClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();protected ClientHttpRequest createRequest(URI url, HttpMethod method) throws IOException {		ClientHttpRequest request = getRequestFactory().createRequest(url, method);		if (logger.isDebugEnabled()) {			logger.debug("Created " + method.name() + " request for \"" + url + "\"");		}		return request;	}复制代码

默认是SimpleClientHttpRequestFactory spring-web-4.3.7.RELEASE-sources.jar!/org/springframework/http/client/SimpleClientHttpRequestFactory.java

private int connectTimeout = -1;	private int readTimeout = -1;@Override	public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException {		HttpURLConnection connection = openConnection(uri.toURL(), this.proxy);		prepareConnection(connection, httpMethod.name());		if (this.bufferRequestBody) {			return new SimpleBufferingClientHttpRequest(connection, this.outputStreaming);		}		else {			return new SimpleStreamingClientHttpRequest(connection, this.chunkSize, this.outputStreaming);		}	}	/**	 * Template method for preparing the given {@link HttpURLConnection}.	 * 

The default implementation prepares the connection for input and output, and sets the HTTP method. * @param connection the connection to prepare * @param httpMethod the HTTP request method ({@code GET}, {@code POST}, etc.) * @throws IOException in case of I/O errors */ protected void prepareConnection(HttpURLConnection connection, String httpMethod) throws IOException { if (this.connectTimeout >= 0) { connection.setConnectTimeout(this.connectTimeout); } if (this.readTimeout >= 0) { connection.setReadTimeout(this.readTimeout); } connection.setDoInput(true); if ("GET".equals(httpMethod)) { connection.setInstanceFollowRedirects(true); } else { connection.setInstanceFollowRedirects(false); } if ("POST".equals(httpMethod) || "PUT".equals(httpMethod) || "PATCH".equals(httpMethod) || "DELETE".equals(httpMethod)) { connection.setDoOutput(true); } else { connection.setDoOutput(false); } connection.setRequestMethod(httpMethod); }复制代码

这里prepareConnection进行了超时设置,不过connectTimeout和readTimeout默认都是-1,相当于永远阻塞。这就是出问题的地方。

修复问题

设置timeout

创建resttemplate的时候,指定设置好超时时间的ClientHttpRequestFactory

  • SimpleClientHttpRequestFactory 如果没有依赖http client,则默认使用的是SimpleClientHttpRequestFactory
@Bean    public RestTemplate restTemplate() {        SimpleClientHttpRequestFactory clientHttpRequestFactory = new SimpleClientHttpRequestFactory();        clientHttpRequestFactory.setConnectTimeout(30000);        clientHttpRequestFactory.setReadTimeout(30000);        RestTemplate restTemplate = new RestTemplate(clientHttpRequestFactory);        return restTemplate;    }复制代码

注意这里单位是毫秒

  • 如果有http client的依赖,底层使用HttpComponentsClientHttpRequestFactory
@Bean    public RestTemplate customRestTemplate(){        HttpComponentsClientHttpRequestFactory httpRequestFactory = new HttpComponentsClientHttpRequestFactory();        httpRequestFactory.setConnectionRequestTimeout(3000);        httpRequestFactory.setConnectTimeout(3000);        httpRequestFactory.setReadTimeout(3000);        return new RestTemplate(httpRequestFactory);    }复制代码

设置了timeout还不行

如果你设置了timeout,依然还阻塞在SocketInputStream.socketRead0,那么请升级JDK,JDK有个bug: 。

doc

转载地址:http://dfrsl.baihongyu.com/

你可能感兴趣的文章
你未必知道 十大服务器虚拟化优化窍门
查看>>
POTN——新时代网络融合的必经之路
查看>>
浅谈各地降低能耗技术 关注可持续发展
查看>>
如何写出漂亮的React组件
查看>>
Windows管理员不可错过的那些卓越DevOps工具(下)
查看>>
物联网兴起 我国工业发展需关注四大关键技术
查看>>
面向未来就绪,企业级存储如何重新定义?
查看>>
智慧医疗在张家界的应用 首家智慧医疗医院投用
查看>>
Web 开发中 20 个很有用的 CSS 库
查看>>
Android系统中的进程管理:进程的创建
查看>>
看好大工业数据市场 GE将向软件业务14亿美元
查看>>
《JavaScript和jQuery实战手册(原书第2版)》——1.1节编程简介
查看>>
牛津大学量化金融创始人:如何获取并应用互联网大数据?
查看>>
快讯:特斯拉宣布26亿美元收购太阳能公司SolarCity
查看>>
你要知道的4个机房除尘小技巧
查看>>
百度金融布局金交所,账户和场景成掣肘
查看>>
仙童半导体拒绝华润等收购 担忧难获监管批准
查看>>
阿里视频云最强转码技术揭秘:窄带高清原理解析+用户接入指南
查看>>
茶道长:为什么加粉对于你的微商团队来说这么难?
查看>>
《安娜卡列尼娜》文本生成——利用 TensorFlow 构建 LSTM 模型
查看>>