標籤:
恰當的JDBC逾時設定能夠有效地減少服務失效的時間。本文將對資料庫的各種逾時設定及其設定方法做介紹。
真實案例:應用伺服器在遭到DDos攻擊後無法響應
在遭到DDos攻擊後,整個服務都垮掉了。由於第四層交換器不堪重負,網路變得無法串連,從而導致業務系統也無法正常運轉。安全性群組很快屏蔽了所有的DDos攻擊,並恢複了網路,但業務系統卻還是無法工作。 通過分析系統的thread dump發現,業務系統停在了JDBC API的調用上。20分鐘後,系統仍處於WAITING狀態,無法響應。30分鐘後,系統拋出異常,服務恢複正常。
為什麼我們明明將query timeout設定成了3秒,系統卻持續了30分鐘的WAITING狀態?為什麼30分鐘後系統又恢複正常了? 當你對理解了JDBC的逾時設定後,就能找到問題的答案。
為什麼我們要瞭解JDBC?
當遇到效能問題或系統出錯時,業務系統和資料庫通常是我們最關心的兩個部分。在公司裡,這兩個部分是交由兩個不同的部門來負責的,因此各個部門都會集中精力地在自身領域內尋找問題,這樣的話,在業務系統和資料庫之間的部分就會成為一個盲區。對於Java應用而言,這個盲區就是DBCP資料庫連接池和JDBC,本文將集中介紹JDBC。
什麼是JDBC?
JDBC是Java應用中用來串連關係型資料庫的標準API。Sun公司一共定義了4種類型的JDBC,我們主要使用的是第4種,該類型的Driver完全由Java代碼實現,通過使用socket與資料庫進行通訊。
圖1 JDBC Type 4.
第4種類型的JDBC通過socket對位元組流進行處理,因此也會有一些基本網路操作,類似於HttpClient這種用於網路操作的程式碼程式庫。當在網路操作中遇到問題的時候,將會消耗大量的cpu資源,並且失去響應逾時。如果你之前用過HttpClient,那麼你一定遇到過未設定timeout造成的錯誤。同樣,第4種類型的JDBC,若沒有合理地設定socket timeout,也會有相同的錯誤——串連被阻塞。
接下來,就讓我們來學習一下如何正確地設定socket timeout,以及需要考慮的問題。
應用與資料庫間的timeout層級
圖2 Timeout Class.
展示了簡化後應用與資料庫間的timeout層級。(譯者註:WAS/BLOC是作者公司的具體應用程式名稱,無需深究)
進階別的timeout依賴於低層級的timeout,只有當低層級的timeout無誤時,進階別的timeout才能確保正常。例如,當socket timeout出現問題時,進階別的statement timeout和transaction timeout都將失效。
我們收到的很多評論中提到:
引用:
即使設定了statement timeout,當網路出錯時,應用也無法從錯誤中恢複。
statement timeout無法處理網路連接失敗時的逾時,它能做的僅僅是限制statement的操作時間。網路連接失敗時的timeout必須交由JDBC來處理。
JDBC的socket timeout會受到作業系統socket timeout設定的影響,這就解釋了為什麼在之前的案例中,JDBC串連會在網路出錯後阻塞30分鐘,然後又奇蹟般恢複,即使我們並沒有對JDBC的socket timeout進行設定。
DBCP串連池位於圖2的左側,你會發現timeout層級與DBCP是相互獨立的。DBCP負責的是資料庫連接的建立和管理,並不干涉timeout的處理。當串連在DBCP中建立,或是DBCP發送校正query檢查串連有效性的時候,socket timeout將會影響這些過程,但並不直接對應用造成影響。
當在應用中調用DBCP的getConnection()方法時,你可以設定擷取資料庫連接的逾時時間,但是這和JDBC的timeout毫不相關。
圖3 Timeout for Each Levels.
什麼是Transaction Timeout?
transaction timeout一般存在於架構(Spring, EJB)或應用級。transaction timeout或許是個相對陌生的概念,簡單地說,transaction timeout就是“statement Timeout * N(需要執行的statement數量) + @(記憶體回收等其他時間)”。transaction timeout用來限制執行statement的總時間長度。
例如,假設執行一個statement需要0.1秒,那麼執行少量statement不會有什麼問題,但若是要執行100,000個statement則需要10,000秒(約7個小時)。這時,transaction timeout就派上用場了。EJB CMT (Container Managed Transaction)就是一種典型的實現,它提供了多種方法供開發人員選擇。但我們並不使用EJB,Spring的transaction timeout設定會更常用一些。在Spring中,你可以使用下面展示的XML或是在源碼中使用@Transactional註解來進行設定。
xml代碼:
Spring提供的transaction timeout配置非常簡單,它會記錄每個事務的開始時間和消耗時間,當特定的事件發生時就會對消耗時間做校正,當超出timeout值時將拋出異常。
Spring中,資料庫連接被儲存在ThreadLocal裡,這被稱為事務同步(Transaction Synchronization),與此同時,事務的開始時間和消耗時間也被儲存下來。當使用這種代理串連建立statement時,就會校正事務的消耗時間。EJB CMT的實現方式與之類似,其結構本身也十分簡單。
當你選用的容器或架構並不支援transaction timeout這一特性,你可以考慮自己來實現。transaction timeout並沒有標準的API。Lucy架構的1.5和1.6版本都不支援transaction timeout,但是你可以通過使用Spring的Transaction Manager來達到與之同樣的效果。
假設某個事務中包含5個statement,每個statement的執行時間是200ms,其他商務邏輯的執行時間是100ms,那麼transaction timeout至少應該設定為1,100ms(200 * 5 + 100)。
什麼是Statement Timeout?
statement timeout用來限制statement的執行時間長度,timeout的值通過調用JDBC的java.sql.Statement.setQueryTimeout(int timeout) API進行設定。不過現在開發人員已經很少直接在代碼中設定,而多是通過架構來進行設定。
以iBatis為例,statement timeout的預設值可以通過sql-map-config.xml中的defaultStatementTimeout 屬性進行設定。同時,你還可以設定sqlmap中select,insert,update標籤的timeout屬性,從而對不同sql語句的逾時時間進行獨立的配置。
如果你使用的是Lucy1.5或1.6版本,通過設定queryTimeout屬性可以在datasource層面對statement timeout進行設定。
statement timeout的具體值需要依據應用本身的特性而定,並沒有可供推薦的配置。
JDBC的statement timeout處理過程
不同的關係型資料庫,以及不同的JDBC驅動,其statement timeout處理過程會有所不同。其中,Oracle和MS SQLServer的處理相類似,MySQL和CUBRID類似。
Oracle JDBC Statement的QueryTimeout處理過程
1. 通過調用Connection的createStatement()方法建立statement
2. 調用Statement的executeQuery()方法
3. statement通過自身connection將query發送給Oracle資料庫
4. statement在OracleTimeoutPollingThread(每個classloader一個)上進行註冊
5. 達到逾時時間
6. OracleTimeoutPollingThread調用OracleStatement的cancel()方法
7. 通過connection向正在執行的query發送cancel訊息
圖4 Query Timeout Execution Process for Oracle JDBC Statement.
JTDS (MS SQLServer) Statement的QueryTimeout處理過程
1. 通過調用Connection的createStatement()方法建立statement
2. 調用Statement的executeQuery()方法
3. statement通過自身connection將query發送給MS SqlServer資料庫
4. statement在TimerThread上進行註冊
5. 達到逾時時間
6. TimerThread調用JtdsStatement執行個體中的TsdCore.cancel()方法
7. 通過ConnectionJDBC向正在執行的query發送cancel訊息
圖5 QueryTimeout Execution Process for JTDS (MS SQLServer) Statement.
MySQL JDBC Statement的QueryTimeout處理過程
1. 通過調用Connection的createStatement()方法建立statement
2. 調用Statement的executeQuery()方法
3. statement通過自身connection將query發送給MySQL資料庫
4. statement建立一個新的timeout-execution線程用於逾時處理
5. 5.1版本後改為每個connection分配一個timeout-execution線程
6. 向timeout-execution線程進行註冊
7. 達到逾時時間
6. TimerThread調用JtdsStatement執行個體中的TsdCore.cancel()方法
7. timeout-execution線程建立一個和statement配置相同的connection
8. 使用新建立的connection向逾時query發送cancel query(KILL QUERY “connectionId”)
圖6 QueryTimeout Execution Process for MySQL JDBC Statement (5.0.8).
CUBRID JDBC Statement的QueryTimeout處理過程
1. 通過調用Connection的createStatement()方法建立statement
2. 調用Statement的executeQuery()方法
3. statement通過自身connection將query發送給CUBRID資料庫
4. statement建立一個新的timeout-execution線程用於逾時處理
5. 5.1版本後改為每個connection分配一個timeout-execution線程
6. 向timeout-execution線程進行註冊
7. 達到逾時時間
6. TimerThread調用JtdsStatement執行個體中的TsdCore.cancel()方法
7. timeout-execution線程建立一個和statement配置相同的connection
8. 使用新建立的connection向逾時query發送cancel訊息
圖7 QueryTimeout Execution Process for CUBRID JDBC Statement.
什麼是JDBC的socket timeout?
第4種類型的JDBC使用socket與資料庫連接,資料庫並不對應用與資料庫間的連線逾時進行處理。
JDBC的socket timeout在資料庫被突然停掉或是發生網路錯誤(由於裝置故障等原因)時十分重要。由於TCP/IP的結構原因,socket沒有辦法探測到網路錯誤,因此應用也無法主動探索資料庫串連斷開。如果沒有設定socket timeout的話,應用在資料庫返回結果前會無期限地等下去,這種串連被稱為dead connection。
為了避免dead connections,socket必須要有逾時配置。socket timeout可以通過JDBC設定,socket timeout能夠避免應用在發生網路錯誤時產生無休止等待的情況,縮短服務失效的時間。
不推薦使用socket timeout來限制statement的執行時間長度,因此socket timeout的值必須要高於statement timeout,否則,socket timeout將會先生效,這樣statement timeout就變得毫無意義,也無法生效。
下面展示了socket timeout的兩個設定項,不同的JDBC驅動其配置方式會有所不同。
socket串連時的timeout:通過Socket.connect(SocketAddress endpoint, int timeout)設定
socket讀寫時的timeout:通過Socket.setSoTimeout(int timeout)設定
通過查看CUBRID,MySQL,MS SQL Server (JTDS)和Oracle的JDBC驅動源碼,我們發現所有的驅動內部都是使用上面的2個API來設定socket timeout的。
下面是不同驅動的socket timeout配置方式。
connectTimeout和socketTimeout的預設值為0時,timeout不生效。
除了調用DBCP的API以外,還可以通過properties屬性進行配置。
通過properties屬性進行配置時,需要傳入key為“connectionProperties”的索引值對,value的格式為“[propertyName=property;]*”。下面是iBatis中的properties配置。
xml代碼:
作業系統的socket timeout配置
如果不設定socket timeout或connect timeout,應用多數情況下是無法發現網路錯誤的。因此,當網路錯誤發生後,在串連重新串連成功或成功接收到資料之前,應用會無限制地等下去。但是,通過本文開篇處的實際案例我們發現,30分鐘後應用的串連問題奇蹟般的解決了,這是因為作業系統同樣能夠對socket timeout進行配置。公司的Linux伺服器將socket timeout設定為了30分鐘,從而會在作業系統的層面對網路連接做校正,因此即使JDBC的socket timeout設定為0,由網路錯誤造成的資料庫連接問題的期間也不會超過30分鐘。
通常,應用會在調用Socket.read()時由於網路問題被阻塞住,而很少在調用Socket.write()時進入waiting狀態,這取決於網路構成和錯誤類型。當Socket.write()被調用時,資料被寫入到作業系統核心的緩衝區,控制權立即回到應用手上。因此,一旦資料被寫入核心緩衝區,Socket.write()調用就必然會成功。但是,如果系統核心緩衝區由於某種網路錯誤而滿了的話,Socket.write()也會進入waiting狀態。這種情況下,作業系統會嘗試重新發包,當達到重試的時間限制時,將產生系統錯誤。在我們公司,重新發包的逾時時間被設定為15分鐘。
至此,我已經對JDBC的內部操作做了講解,希望能夠讓大家學會如何正確的配置逾時時間,從而減少錯誤的發生。
最後,我將列出一些常見的問題。
FAQ
Q1. 我已經使用Statement.setQueryTimeout()方法設定了查詢逾時,但在網路出錯時並沒有產生作用。
? 查詢逾時僅在socket timeout生效的前提下才有效,它並不能用來解決外部的網路錯誤,要解決這種問題,必須設定JDBC的socket timeout。
Q2. transaction timeout,statement timeout和socket timeout和DBCP的配置有什麼關係?
? 當通過DBCP擷取資料庫連接時,除了DBCP擷取串連時的waitTimeout配置以外,其他配置對JDBC沒有什麼影響。
Q3. 如果設定了JDBC的socket timeout,那DBCP串連池中處於IDLE狀態的串連是否也會在達到逾時時間後被關閉?
? 不會。socket的設定只會在產生資料讀寫時生效,而不會對DBCP中的IDLE串連產生影響。當DBCP中發生新串連建立,老的IDLE串連被移除,或是串連有效性校正的時候,socket設定會對其產生一定的影響,但除非發生網路問題,否則影響很小。
Q4. socket timeout應該設定為多少?
? 就像我在本文中提的那樣,socket timeout必須高於statement timeout,但並沒有什麼推薦值。在發生網路錯誤的時候,socket timeout將會生效,但是再小心的配置也無法避免網路錯誤的發生,只是在網路錯誤發生後縮短服務失效的時間(如果網路恢複正常的話)。
原文地址:http://jingyan.baidu.com/article/fc07f98922615a12ffe519ce.html
JDBC逾時設定【轉】