一、問題描述
使用quartz對作業進行調度時,報出下列異常:
2018-09-27 06:00:00,149 WARN [org.quartz.impl.jdbcjobstore.JobStoreTX] Failed to override connection auto commit/transaction isolation.
com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: The last packet successfully received from the server was 437823 seconds ago.The last packet sent successfully to the server was 437823 seconds ago, which is longer than the server configured value of 'wait_timeout'. You should consider either expiring and/or testing connection validity before use in your application, increasing the server configured values for client timeouts, or using the Connector/J connection property 'autoReconnect=true' to avoid this problem.
at sun.reflect.GeneratedConstructorAccessor246.newInstance(Unknown Source)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl. java:27)
at java.lang.reflect.Constructor.newInstance(Constructor.java:513)
at com.mysql.jdbc.Util.handleNewInstance(Util.java:406)
[Scheduler_QuartzSchedulerThread] WARN org.quartz.impl.jdbcjobstore.JobStoreTX - Failed to override connection auto commit/transaction isolation.
com.mysql.jdbc.CommunicationsException: Communications link failure due to underlying exception:
** BEGIN NESTED EXCEPTION **
java.net.SocketException
MESSAGE: Broken pipe
STACKTRACE:
java.net.SocketException: Broken pipe
at java.net.SocketOutputStream.socketWrite0(Native Method)
at java.net.SocketOutputStream.socketWrite(SocketOutputStream.java:92)
at java.net.SocketOutputStream.write(SocketOutputStream.java:136)
at java.io.BufferedOutputStream.flushBuffer(BufferedOutputStream.java:65)
at java.io.BufferedOutputStream.flush(BufferedOutputStream.java:123)
at com.mysql.jdbc.MysqlIO.send(MysqlIO.java:2744)
at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:1612)
at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:1723)
at com.mysql.jdbc.Connection.execSQL(Connection.java:3277)
at com.mysql.jdbc.Connection.setAutoCommit(Connection.java:5442)
at org.apache.commons.dbcp.DelegatingConnection.setAutoCommit(DelegatingConnection.java:237)
at org.quartz.impl.jdbcjobstore.AttributeRestoringConnectionInvocationHandler.setAutoCommit(AttributeRestoringConnectionInvocationHandler.java:91)
at org.quartz.impl.jdbcjobstore.AttributeRestoringConnectionInvocationHandler.invoke(AttributeRestoringConnectionInvocationHandler.java:65)
at $Proxy4.setAutoCommit(Unknown Source)
at org.quartz.impl.jdbcjobstore.JobStoreSupport.getConnection(JobStoreSupport.java:711)
at org.quartz.impl.jdbcjobstore.JobStoreTX.getNonManagedTXConnection(JobStoreTX.java:72)
at org.quartz.impl.jdbcjobstore.JobStoreSupport.executeInNonManagedTXLock(JobStoreSupport.java:3757)
at org.quartz.impl.jdbcjobstore.JobStoreSupport.acquireNextTrigger(JobStoreSupport.java:2729)
at org.quartz.core.QuartzSchedulerThread.run(QuartzSchedulerThread.java:266)
** END NESTED EXCEPTION **
使用的quartz版本為2.2.3,數據庫的配置如下:
org.quartz.jobStore.dataSource = myDS
org.quartz.dataSource.myDS.driver = com.mysql.jdbc.Driver
org.quartz.dataSource.myDS.URL = jdbc:mysql://localhost:3306/quartz?autoReconnect=true&characterEncoding=utf-8
org.quartz.dataSource.myDS.user = root
org.quartz.dataSource.myDS.password = root
org.quartz.dataSource.myDS.maxConnections =50
二、問題分析
-
數據庫連接池技術C3P0
MySql服務器默認的“wait_timeout”是8小時,也就是說一個connection空閑超過8個小時,MySql將自動斷開該connection。 這就是問題的所在,在C3P0 pools中的connections如果空閑超過8小時,MySql將其斷開,而C3P0並不知道該connection已經失效,如果這時有Client請求connection,C3P0將該失效的Connection提供給Client,將會造成上面的異常。
-
Quartz數據庫配置
quartz定時任務使用時數據庫連接默認是maxIdleTime=0,即永不放棄連接。在Spring Boot啟動服務,控制台打印的quartz數據庫配置信息如下:
2018-09-28 14:38:31.957 INFO 13932 --- [ main] com.mchange.v2.c3p0.C3P0Registry : Initializing c3p0-0.9.1.1 [built 15-March-2007 01:32:31; debug? true; trace: 10]
2018-09-28 14:38:31.988 INFO 13932 --- [ main] org.quartz.impl.StdSchedulerFactory : Using default implementation for ThreadExecutor
2018-09-28 14:38:32.018 INFO 13932 --- [ main] org.quartz.core.SchedulerSignalerImpl : Initialized Scheduler Signaller of type: class org.quartz.core.SchedulerSignalerImpl
2018-09-28 14:38:32.018 INFO 13932 --- [ main] org.quartz.core.QuartzScheduler : Quartz Scheduler v.2.2.3 created.
2018-09-28 14:38:32.019 INFO 13932 --- [ main] org.quartz.impl.jdbcjobstore.JobStoreTX : Using db table-based data access locking (synchronization).
2018-09-28 14:38:32.021 INFO 13932 --- [ main] org.quartz.impl.jdbcjobstore.JobStoreTX : JobStoreTX initialized.
2018-09-28 14:38:32.021 INFO 13932 --- [ main] org.quartz.core.QuartzScheduler : Scheduler meta-data: Quartz Scheduler (v2.2.3) 'MyScheduler' with instanceId 'MyScheduler'
Scheduler class: 'org.quartz.core.QuartzScheduler' - running locally.
NOT STARTED.
Currently in standby mode.
Number of jobs executed: 0
Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 50 threads.
Using job-store 'org.quartz.impl.jdbcjobstore.JobStoreTX' - which supports persistence. and is clustered.
2018-09-28 14:38:32.021 INFO 13932 --- [ main] org.quartz.impl.StdSchedulerFactory : Quartz scheduler 'CMDBScheduler' initialized from an externally provided properties instance.
2018-09-28 14:38:32.021 INFO 13932 --- [ main] org.quartz.impl.StdSchedulerFactory : Quartz scheduler version: 2.2.3
2018-09-28 14:38:32.021 INFO 13932 --- [ main] org.quartz.core.QuartzScheduler : JobFactory set to: com.cmb.cmdb.bean.JobFactory@26844abb
2018-09-28 14:38:32.083 INFO 13932 --- [ main] c.m.v.c.i.AbstractPoolBackedDataSource : Initializing c3p0 pool... com.mchange.v2.c3p0.ComboPooledDataSource [ acquireIncrement -> 3, acquireRetryAttempts -> 30, acquireRetryDelay -> 1000, autoCommitOnClose -> false, automaticTestTable -> null, breakAfterAcquireFailure -> false, checkoutTimeout -> 0, connectionCustomizerClassName -> null, connectionTesterClassName -> com.mchange.v2.c3p0.impl.DefaultConnectionTester, dataSourceName -> rhcsim9y8knjf2ph7q1r|51a6cc2a, debugUnreturnedConnectionStackTraces -> false, description -> null, driverClass -> com.mysql.jdbc.Driver, factoryClassLocation -> null, forceIgnoreUnresolvedTransactions -> false, identityToken -> rhcsim9y8knjf2ph7q1r|51a6cc2a, idleConnectionTestPeriod -> 50, initialPoolSize -> 3, jdbcUrl -> jdbc:mysql://localhost:3306/quartz?autoReconnect=true&characterEncoding=utf-8, lastAcquisitionFailureDefaultUser -> null, maxAdministrativeTaskTime -> 0, maxConnectionAge -> 0, maxIdleTime -> 0, maxIdleTimeExcessConnections -> 0, maxPoolSize -> 50, maxStatements -> 0, maxStatementsPerConnection -> 120, minPoolSize -> 1, numHelperThreads -> 3, numThreadsAwaitingCheckoutDefaultUser -> 0, preferredTestQuery -> select 1, properties -> {user=******, password=******}, propertyCycle -> 0, testConnectionOnCheckin -> false, testConnectionOnCheckout -> true, unreturnedConnectionTimeout -> 0, usesTraditionalReflectiveProxies -> false ]
注意到**maxIdleTime -> 0**,這是因為默認**org.quartz.dataSource.myDS.discardIdleConnectionsSeconds**沒有配置。該配置項表示數據庫連接在空閑之后放棄連接幾秒鍾。0表示禁用該功能。默認值為0。
三、Quartz數據源連接池
-
Quartz各版本數據庫連接池技術
Quartz 2.0 以前 DBCP
Quartz 2.0 以后 C3P0(包含2.0)
四、DBCP 和C3P0的使用和區別
-
DBCP 介紹
DBCP(DataBase connection pool),數據庫連接池。是 apache 上的一個 java 連接池項目,也是 tomcat 使用的連接池組件。單獨使用DBCP需要3個包:common-dbcp.jar,common-pool.jar,common-collections.jar。由於建立數據庫連接是一個非常耗時耗資源的行為,所以通過連接池預先同數據庫建立一些連接,放在內存中,應用程序需要建立數據庫連接時直接到連接池中申請一個就行,用完后再放回去。
-
C3P0介紹
C3P0是一個開源的JDBC連接池,它實現了數據源和JNDI綁定,支持JDBC3規范和JDBC2的標准擴展。目前使用它的開源項目有Hibernate,Spring等。
-
DBCP 和C3P0區別
DBCP沒有自動的去回收空閑連接的功能, C3P0有自動回收空閑連接功能。 兩者主要是對數據連接的處理方式不同,C3P0提供最大空閑時間,DBCP提供最大連接數。 前者當連接超過最大空閑連接時間時,當前連接就會被斷掉。DBCP當連接數超過最大連接數時,所有連接都會被斷開。DBCP的原理是維護多個連接對象Connection,在web項目要連接數據庫時直接使用它維護的對象進行連接,省去每次都要創建連接對象的麻煩。提高效率和減少內存使用。C3P0可以自動回收連接,DBCP需要自己手動釋放資源返回。不過DBCP效率比較高。
-
DBCP和C3P0在Spring的配置
<!-- 配置dbcp數據源 -->
<bean id="dataSource2" destroy-method="close" class="org.apache.commons.dbcp.BasicDataSource"> <property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<!-- 池啟動時創建的連接數量 -->
<property name="initialSize" value="5"/>
<!-- 同一時間可以從池分配的最多連接數量。設置為0時表示無限制。 -->
<property name="maxActive" value="30"/>
<!-- 池里不會被釋放的最多空閑連接數量。設置為0時表示無限制。 -->
<property name="maxIdle" value="20"/>
<!-- 在不新建連接的條件下,池中保持空閑的最少連接數。 -->
<property name="minIdle" value="3"/>
<!-- 設置自動回收超時連接 -->
<property name="removeAbandoned" value="true" />
<!-- 自動回收超時時間(以秒數為單位) -->
<property name="removeAbandonedTimeout" value="200"/>
<!-- 設置在自動回收超時連接的時候打印連接的超時錯誤 -->
<property name="logAbandoned" value="true"/>
<!-- 等待超時以毫秒為單位,在拋出異常之前,池等待連接被回收的最長時間(當沒有可用連接時)。設置為-1表示無限等待。 -->
<property name="maxWait" value="100"/>
</bean>
<!-- 配置c3p0數據源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close"> <property name="jdbcUrl" value="${jdbc.url}" />
<property name="driverClass" value="${jdbc.driverClassName}" />
<property name="user" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<!--連接池中保留的最大連接數。Default: 15 -->
<property name="maxPoolSize" value="100" />
<!--連接池中保留的最小連接數。-->
<property name="minPoolSize" value="1" />
<!--初始化時獲取的連接數,取值應在minPoolSize與maxPoolSize之間。Default: 3 -->
<property name="initialPoolSize" value="10" />
<!--最大空閑時間,60秒內未使用則連接被丟棄。若為0則永不丟棄。Default: 0 -->
<property name="maxIdleTime" value="30" />
<!--當連接池中的連接耗盡的時候c3p0一次同時獲取的連接數。Default: 3 -->
<property name="acquireIncrement" value="5" />
<!--JDBC的標准參數,用以控制數據源內加載的PreparedStatements數量。但由於預緩存的statements屬於單個 connection而不是整個連接池。所以設置這個參數需要考慮到多方面的因素。如果maxStatements與 maxStatementsPerConnection均為0,則緩存被關閉。Default: 0-->
<property name="maxStatements" value="0" />
<!--每60秒檢查所有連接池中的空閑連接。Default: 0 -->
<property name="idleConnectionTestPeriod" value="60" />
<!--定義在從數據庫獲取新連接失敗后重復嘗試的次數。Default: 30 -->
<property name="acquireRetryAttempts" value="30" />
<!--獲取連接失敗將會引起所有等待連接池來獲取連接的線程拋出異常。但是數據源仍有效保留,並在下次調用getConnection()的時候繼續嘗試獲取連接。如果設為true,那么在嘗試獲取連接失敗后該數據源將申明已斷開並永久關閉。Default: false-->
<property name="breakAfterAcquireFailure" value="true" />
<!--因性能消耗大請只在需要的時候使用它。如果設為true那么在每個connection提交的時候都將校驗其有效性。建議使用idleConnectionTestPeriod或automaticTestTable等方法來提升連接測試的性能。Default: false --> <property name="testConnectionOnCheckout" value="false" />
</bean>
五、Quartz配置DataSources
如果您使用JDBC-Jobstore,則需要使用DataSource(或使用兩個DataSource,如果您使用JobStoreCMT)。
DataSources可以通過三種方式進行配置:
-
在quartz.properties文件中指定的所有池屬性,以便Quartz可以自己創建DataSource。
-
可以指定應用程序服務器管理的Datasource的JNDI位置,以便Quartz可以使用它。
-
自定義的org.quartz.utils.ConnectionProvider實現。
建議您將Datasource max連接大小配置為至少線程池中的工作線程數量加上三個。如果您的應用程序也頻繁調用調度程序API,則可能需要其他連接。如果您使用JobStoreCMT,則“非受管理”數據源的最大連接大小應至少為4。
您定義的每個DataSource(通常為一個或兩個)必須為一個名稱,並且您為每個定義的屬性必須包含該名稱,如下所示。DataSource的“NAME”可以是任何您想要的,除了能夠在分配給JDBCJobStore之后能夠識別它之外,沒有什么意義。
Property Name | Required | Type | Default Value |
---|---|---|---|
org.quartz.dataSource.NAME.driver | yes | String | null |
org.quartz.dataSource.NAME.URL | yes | String | null |
org.quartz.dataSource.NAME.user | no | String | "" |
org.quartz.dataSource.NAME.password | no | String | "" |
org.quartz.dataSource.NAME.maxConnections | no | int | 10 |
org.quartz.dataSource.NAME.validationQuery | no | String | null |
org.quartz.dataSource.NAME.idleConnectionValidationSeconds | no | int | 50 |
org.quartz.dataSource.NAME.validateOnCheckout | no | boolean | false |
org.quartz.dataSource.NAME.discardIdleConnectionsSeconds | no | int | 0 (disabled) |
org.quartz.dataSource.NAME.driver
必須是數據庫的JDBC驅動程序的java類名稱。
org.quartz.dataSource.NAME.URL
連接到數據庫的連接URL(主機,端口等)。
org.quartz.dataSource.NAME.user
連接到數據庫時要使用的用戶名。
org.quartz.dataSource.NAME.password
連接到數據庫時使用的密碼。
org.quartz.dataSource.NAME.maxConnections
DataSource可以在其連接池中創建的最大連接數。
org.quartz.dataSource.NAME.validationQuery
是可選的SQL查詢字符串,DataSource可用於檢測和替換失敗/損壞的連接。例如,oracle用戶可能會選擇“從user_tables中選擇table_name” - 這是一個不應該失敗的查詢 - 除非連接實際上是壞的。
org.quartz.dataSource.NAME.idleConnectionValidationSeconds
空閑連接測試之間的秒數 - 僅在設置驗證查詢屬性時啟用。默認值為50秒。
org.quartz.dataSource.NAME.validateOnCheckout
每次從池中檢索連接時,是否應該執行數據庫sql查詢來驗證連接,以確保它仍然有效。如果為假,則在辦理登機手續時將進行驗證。默認值為false。
org.quartz.dataSource.NAME.discardIdleConnectionsSeconds
它們在空閑之后放棄連接幾秒鍾。0禁用該功能。默認值為0。
Quartz定義的DataSource示例
org.quartz.dataSource.myDS.driver = oracle.jdbc.driver.OracleDriver
org.quartz.dataSource.myDS.URL = jdbc:oracle:thin:@ 10.0.1.23:1521:demodb
org.quartz.dataSource.myDS.user = myUser
org.quartz.dataSource.myDS.password = myPassword
org.quartz.dataSource.myDS.maxConnections = 30
對Application Server DataSources的引用使用以下屬性定義:
Property Name | Required | Type | Default Value |
---|---|---|---|
org.quartz.dataSource.NAME.jndiURL | yes | String | null |
org.quartz.dataSource.NAME.java.naming.factory.initial | no | String | null |
org.quartz.dataSource.NAME.java.naming.provider.url | no | String | null |
org.quartz.dataSource.NAME.java.naming.security.principal | no | String | null |
org.quartz.dataSource.NAME.java.naming.security.credentials | no | String | null |
org.quartz.dataSource.NAME.jndiURL
由應用程序服務器管理的DataSource的JNDI URL。
org.quartz.dataSource.NAME.java.naming.factory.initial
要使用的JNDI InitialContextFactory的(可選)類名。
org.quartz.dataSource.NAME.java.naming.provider.url
用於連接到JNDI上下文的(可選)URL。
org.quartz.dataSource.NAME.java.naming.security.principal
用於連接到JNDI上下文的(可選)用戶主體。
org.quartz.dataSource.NAME.java.naming.security.credentials
用於連接到JNDI上下文的(可選)用戶憑據。
從應用程序服務器引用的數據源示例
org.quartz.dataSource.myOtherDS.jndiURL = JDBC / myDataSource
org.quartz.dataSource.myOtherDS.java.naming.factory.initial = com.evermind.server.rmi.RMIInitialContextFactory
org.quartz.dataSource.myOtherDS.java.naming.provider.url = ormi:// localhost
org.quartz.dataSource.myOtherDS.java.naming.security.principal = admin
org.quartz.dataSource.myOtherDS.java.naming.security.credentials = 123
自定義ConnectionProvider實現
Property Name | Required | Type | Default Value |
---|---|---|---|
org.quartz.dataSource.NAME.connectionProvider.class | yes | String (class name) | null |
org.quartz.dataSource.NAME.connectionProvider.class
要使用的ConnectionProvider的類名。實例化之后,Quartz可以自動設置實例上的配置屬性,bean樣式。
使用自定義ConnectionProvider實現的示例
org.quartz.dataSource.myCustomDS.connectionProvider.class = com.foo.FooConnectionProvider
org.quartz.dataSource.myCustomDS.someStringProperty = someValue
org.quartz.dataSource.myCustomDS.someIntProperty = 5
六、解決方案
-
方案一:
quzrtz的dataSource增加如下配置,加上最大空閑時間,設置為60s:
org.quartz.datasource.qzDS.validateOnCheckout=true
org.quartz.datasource.qzDS.validationQuery=select 1
org.quartz.dataSource.myDS.discardIdleConnectionsSeconds=60
-
方案二:
自定義Druid數據庫連接池,需要實現org.quartz.utils.ConnectionProvider接口,同時引入Druid相關的jar包,具體實現可以參考:https://blog.csdn.net/minicto/article/details/77897577或https://www.cnblogs.com/zouhao510/p/5313600.html
參考文章
- http://blog.sina.com.cn/s/blog_12cceab5a0102xav6.html
- https://blog.csdn.net/hehuanchun0311/article/details/80591929
- https://blog.csdn.net/minicto/article/details/77897577
- https://blog.csdn.net/retry000/article/details/79494299
- http://blog.51cto.com/13797478/2130872
- https://stackoverflow.com/questions/9159372/java-net-socketexception-broken-pipe-with-quartz-and-mysql-and-tomcat-tomcat-c
- https://stackoverflow.com/questions/9094578/quartz-failure-in-notifyjobstorejobcomplete-method
- http://www.cnblogs.com/huahua035/p/7839834.html
- https://www.w3cschool.cn/quartz_doc/quartz_doc-d8pn2do9.html