Quartz Task在Tomcat中重複運行問題解決

問題描述

Spring Quartz是很常用的定時任務框架。把一個Quartz的工程部署到Tomcat中啓動,意外地發現,每個Task都在同一時間跑了兩次,而本地在開發的過程中卻沒有問題。

問題排查

爲了防止多線程問題,有部分Task上是加了鎖的,類似如下方式:

@Component
public class ExampleTask{
	private ReentrantLock lock = new ReentrantLock();
    protected void executeInternal(){
    	if (lock.tryLock()) {
            try {
                // task main logic
            } finally {
                lock.unlock();
            }
        }
    }
}

按理說,SpringBean默認是單例的,加了鎖之後,同一時間,只會有一個線程能拿到鎖,然後執行Task的邏輯纔對。難道鎖不生效?於是我們又新增了類似如下日誌,把ReentrantLock對象和this都打印出來:

logger.info("lock: " + lock + ", this: " + this);

得到:

2020-05-12 06:26:40 INFO  ExampleTask:30 - 7db46a61-e1e6-4d26-a038-d2f6721f70ac|lock: java.util.concurrent.locks.ReentrantLock@1cd8d32a[Unlocked], this: cn.com.nightfield.ExampleTask@121f2ec1
2020-05-12 06:26:40 INFO  ExampleTask:30 - 51afa06a-7d61-493c-943d-6e1f8c2ecc79|lock: java.util.concurrent.locks.ReentrantLock@7e7aab34[Unlocked], this: cn.com.nightfield.ExampleTask@70bd5a8b

表示震驚:ReentrantLockthis竟然都不是同一個實例!
於是我們大致可以有一個結論:應該是工程跑了兩遍導致的。果然,在log中看到,QuartzScheduler被初始化了兩次:

......
2020-05-12 06:26:23 INFO  QuartzScheduler:240 - Quartz Scheduler v.2.2.1 created.
2020-05-12 06:26:23 INFO  RAMJobStore:155 - RAMJobStore initialized.
......
2020-05-12 06:26:28 INFO  QuartzScheduler:240 - Quartz Scheduler v.2.2.1 created.
2020-05-12 06:26:28 INFO  RAMJobStore:155 - RAMJobStore initialized.
......

自然的,把目標放到了Tomcat身上。

檢查了一下server.xml文件:

<Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true">
	<Context path="nightfield" docBase="/usr/local/tomcat/webapps/nightfield" debug="0" reloadable="false"/>
    <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
            prefix="localhost_access_log" suffix=".txt"
            pattern="%h %l %u %t &quot;%r&quot; %s %b" />
</Host>

問題就出在這裏:我們把工程放到了Tomcatwebapps下面,而且把autoDeploy設成了true
根據Tomcat官網對Automatic Application Deployment的介紹,當autoDeploytrue的時候,Tomcat會起線程監控appBase下的文件變化,當檢測到有文件變化的時候,工程會被重新加載(reload)或被重新部署(redeploy)。所以在autoDeploy模式下,工程目錄(docBase)需要指定在appBase目錄之外:

When using automatic deployment, the docBase defined by an XML Context file should be outside of the appBase directory. If this is not the case, difficulties may be experienced deploying the web application or the application may be deployed twice. The deployIgnore attribute can be used to avoid this situation.

Note that if you are defining contexts explicitly in server.xml, you should probably turn off automatic application deployment or specify deployIgnore carefully. Otherwise, the web applications will each be deployed twice, and that may cause problems for the applications.

3. 問題解決

有了官網的指導,問題解決也就很簡單了,有三種方法:

  1. 把工程放到webapps外面:
<Context path="nightfield" docBase="/usr/local/nightfield" debug="0" reloadable="false"/>
  1. appBase設置成空:
<Host name="localhost" appBase="" unpackWARs="true" autoDeploy="true">
  1. autoDeploy設成false,順便把deployOnStartup也設置成false
<Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="false" deployOnStartup="false">

總結

一般情況下,TomcatautoDeploy功能在開發過程中很有用,能節省調試過程中重啓服務的時間;但是在服務器環境上,推薦關閉此功能。不當的使用,可能會使服務多次部署,導致無法預料的bug。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章