日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

当心Spring缓慢的事务回调

發布時間:2023/12/3 javascript 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 当心Spring缓慢的事务回调 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

TL; DR

如果您的應用程序無法獲得新的數據庫連接,則重新啟動ActiveMQ代理可能會有所幫助。 有興趣嗎

性能問題

幾個月前,我們經歷了生產中斷。 大家都很熟悉,許多請求都失敗了:

java.sql.SQLTransientConnectionException: HikariPool-1 - Connection is not available, request timed out after 30003ms.at com.zaxxer.hikari.pool.HikariPool.createTimeoutException(HikariPool.java:555) ~[HikariCP-2.4.7.jar:na]at com.zaxxer.hikari.pool.HikariPool.getConnection(HikariPool.java:188) ~[HikariCP-2.4.7.jar:na]at com.zaxxer.hikari.pool.HikariPool.getConnection(HikariPool.java:147) ~[HikariCP-2.4.7.jar:na]at com.zaxxer.hikari.HikariDataSource.getConnection(HikariDataSource.java:99) ~[HikariCP-2.4.7.jar:na]at org.springframework.jdbc.datasource.DataSourceTransactionManager.doBegin(DataSourceTransactionManager.java:211) ~[spring-jdbc-4.3.4.RELEASE.jar:4.3.4.RELEASE]at org.springframework.transaction.support.AbstractPlatformTransactionManager.getTransaction(AbstractPlatformTransactionManager.java:373) ~[spring-tx-4.3.4.RELEASE.jar:4.3.4.RELEASE]at org.springframework.transaction.interceptor.TransactionAspectSupport.createTransactionIfNecessary(TransactionAspectSupport.java:447) ~[spring-tx-4.3.4.RELEASE.jar:4.3.4.RELEASE]at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:277) ~[spring-tx-4.3.4.RELEASE.jar:4.3.4.RELEASE]at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96) ~[spring-tx-4.3.4.RELEASE.jar:4.3.4.RELEASE]

為了完全理解正在發生的事情,我們首先來看一下Spring和JDBC連接池在做什么。 Spring每次遇到@Transactional方法時,都會使用TransactionInterceptor對其進行包裝。 該攔截器將間接向TransactionManager詢問當前交易。 如果沒有,則AbstractPlatformTransactionManager嘗試創建新的事務。 如果是JDBC, DataSourceTransactionManager將通過首先獲取新的數據庫連接來啟動新事務。 最后,Spring向配置的DataSource (在我們的例子中為HikariPool )請求新的Connection 。 您可以從上述堆棧跟蹤中讀取所有內容,沒有新內容。

查詢速度很慢

那么出現異常的原因是什么呢? 我們以Hikari為例,但該說明對我所知道的所有池化DataSource實現均有效。 Hikari查看其內部連接池,并嘗試返回空閑的Connection對象。 如果沒有空閑連接且池尚未滿,則Hikari將無縫創建新的物理連接并返回。 但是,如果池已滿,但當前所有連接都在使用中,則Hikari將無能為力。 它必須等待,希望另一個線程在最近的將來返回一個Connection ,以便可以將其傳遞給另一個客戶端。 但是在30秒(可配置的超時)后,Hikari將超時并失敗。

導致此異常的根本原因是什么? 想象一下,您的服務器正在非常努力地處理數百個請求,每個請求都需要數據庫連接才能進行查詢。 如果所有查詢都很快,則它們應該相當快地將連接返回給池,以便其他請求可以重用它們。 即使在高負載下,等待時間也不會造成災難性的后果。 Hikari在30秒后失敗可能意味著實際上所有連接都被占用了至少半分鐘,這真是太糟糕了! 換句話說,我們擁有一個系統,該系統可以永久保存所有數據庫連接(好幾十秒),使所有其他客戶端線程都餓死。

顯然,我們遇到了數據庫查詢非常慢的情況,讓我們檢查一下數據庫引擎! 根據所使用的RDBMS,您將擁有不同的工具。 在我們的案例中,PostgreSQL報告確實我們的應用程序具有10個打開的連接-最大池大小。 但這并不意味著什么–我們正在池化連接,因此希望在中等負載下所有允許的連接都打開。 僅當應用程序非常空閑時,連接池才可以決定關閉某些連接。 但是應該非常保守地進行,因為打開物理連接的成本非常高。

因此,根據PostgreSQL,我們已經打開了所有這些連接,它們正在運行哪種查詢? 好吧,令人尷尬的是,所有連接都處于空閑狀態,最后一個命令是…… COMMIT 。 從數據庫的角度來看,我們有一堆開放的連接,所有連接都是空閑的,可以為事務提供服務。 從Spring的角度來看,所有連接都已被占用,我們無法獲得更多連接。 這是怎么回事? 在這一點上,我們很確定SQL并不是問題。

模擬故障

我們查看了服務器的堆棧轉儲,并Swift發現了問題。 在分析堆棧轉儲之后,讓我們看一下簡化后的代碼片段。 我編寫了一個在GitHub上可用的示例應用程序,它暴露了相同的問題:

@RestController open class Sample(private val jms: JmsOperations,private val jdbc: JdbcOperations) {@Transactional@RequestMapping(method = arrayOf(GET, POST), value = "/")open fun test(): String {TransactionSynchronizationManager.registerSynchronization(sendMessageAfterCommit())val result = jdbc.queryForObject("SELECT 2 + 2", Int::class.java)return "OK " + result}private fun sendMessageAfterCommit(): TransactionSynchronizationAdapter {return object : TransactionSynchronizationAdapter() {override fun afterCommit() {val result = "Hello " + Instant.now()jms.send("queue", { it.createTextMessage(result) })}}}}

就在Kotlin中,只是為了學習它。 該示例應用程序執行兩件事:*非常非常簡單的數據庫查詢,只是為了證明這不是問題*發送Commit鉤子發送JMS消息

JMS?

現在很明顯,這個提交后的鉤子一定是問題所在,但是為什么呢? 讓我們從頭開始。 通常,我們要執行數據庫事務并僅在事務成功時才發送JMS消息。 由于jms.send()原因,我們不能簡單地將jms.send()作為事務方法中的最后一條語句:

  • @Transactional可以是圍繞我們方法的較大事務的一部分,但是我們希望在整個事務完成后發送一條消息
  • 更重要的是,事務可能會在提交時失敗,而我們已經發送了JMS消息

這些說明適用于所有不參與事務的副作用,您要在提交后執行。 當然,可能會發生事務提交但未執行提交后掛接的情況,因此afterCommit()回調的語義最多為一次。 但是,至少可以保證,如果數據尚未持久存儲到數據庫,也不會發生副作用。 當不能選擇分布式交易時,這是一個合理的權衡,而很少這樣做。

這種習語可以在許多應用程序中找到,并且通常很好。 想象一下,您正在接收一個請求,將某些內容持久保存到數據庫中,然后向客戶端發送一條SMS,以確認請求已得到處理。 如果沒有后提交鉤子,最終將發送SMS,但是如果發生回滾,則不會將任何數據寫入數據庫。 甚至更有趣 ,如果您自動重試失敗的事務,則可能會發送多個SMS,而不會保留任何數據。 因此,提交后的鉤子很重要1 。 那怎么了 在查看堆棧轉儲之前,讓我們檢查一下Hikari公開的指標:

在中等高負載下(用ab模擬了25個并發請求),我們可以清楚地看到10個連接的池已被充分利用。 但是,有15個線程(請求)被阻止以等待數據庫連接。 他們可能最終會在30秒后獲得連接或超時。 看起來問題仍然出在某些長期運行的SQL查詢上,但是說真的, 2 + 2 ? 沒有。

ActiveMQ的問題

現在該揭示堆棧轉儲了。 大多數連接都停留在Hikari上,等待連接。 這些對我們來說沒有興趣,這只是一種癥狀,而不是原因。 讓我們看一下實際保持連接的10個線程,它們的作用是什么?

"http-nio-9099-exec-2@6415" daemon prio=5 tid=0x28 nid=NA waitingjava.lang.Thread.State: WAITING[...4 frames omitted...]at org.apache.activemq.transport.FutureResponse.getResultat o.a.a.transport.ResponseCorrelator.requestat o.a.a.ActiveMQConnection.syncSendPacketat o.a.a.ActiveMQConnection.syncSendPacketat o.a.a.ActiveMQSession.syncSendPacketat o.a.a.ActiveMQMessageProducer.at o.a.a.ActiveMQSession.createProducer[...5 frames omitted...]at org.springframework.jms.core.JmsTemplate.sendat com.nurkiewicz.Sample$sendMessageAfterCommit$1.afterCommitat org.springframework.transaction.support.TransactionSynchronizationUtils.invokeAfterCommitat o.s.t.s.TransactionSynchronizationUtils.triggerAfterCommitat o.s.t.s.AbstractPlatformTransactionManager.triggerAfterCommitat o.s.t.s.AbstractPlatformTransactionManager.processCommitat o.s.t.s.AbstractPlatformTransactionManager.commit[...73 frames omitted...]

所有這些連接都停留在ActiveMQ客戶端代碼上。 它本身并不常見,難道發送的JMS消息不是快速且異步的嗎? 好吧,不是真的。 JMS規范定義了某些保證,我們可以控制其中的一些。 在很多情況下,“一勞永逸”的語義是不夠的。 您真正需要的是來自代理的確認,確認郵件已收到并持續存在。 這意味著我們必須:*創建與ActiveMQ的物理連接(希望它像JDBC連接一樣被池化)*執行握手,授權等(如上所述,池化有很大幫助)*通過網絡發送JMS消息*等待來自經紀人,通常涉及經紀人方面的持久性

到目前為止,所有這些步驟都是同步的,并非免費。 而且ActiveMQ有幾種機制可以進一步減慢生產者(發送者)的速度: 性能調整 , 異步發送 , 快速生產者和緩慢的消費者會發生什么 。

提交后鉤子,真的嗎?

因此,我們確定生產商方面ActiveMQ性能不合格正在使我們放慢速度。 但是,這對數據庫連接池有何影響? 此時,我們重新啟動了ActiveMQ代理,情況恢復正常。 那天生產者如此緩慢的原因是什么? –這超出了本文的范圍。 我們花了一些時間檢查Spring框架的代碼。 提交后掛鉤如何執行? 這是寶貴的堆棧跟蹤的相關部分,已清理(自下而上閱讀):

c.n.Sample$sendMessageAfterCommit$1.afterCommit() o.s.t.s.TransactionSynchronizationUtils.invokeAfterCommit() o.s.t.s.TransactionSynchronizationUtils.triggerAfterCommit() o.s.t.s.AbstractPlatformTransactionManager.triggerAfterCommit() o.s.t.s.AbstractPlatformTransactionManager.processCommit() o.s.t.s.AbstractPlatformTransactionManager.commit() o.s.t.i.TransactionAspectSupport.commitTransactionAfterReturning()

這是大大簡化的AbstractPlatformTransactionManager.processCommit()樣子:

private void processCommit(DefaultTransactionStatus status) throws TransactionException {try {prepareForCommit(status);triggerBeforeCommit(status);triggerBeforeCompletion(status);doCommit(status);triggerAfterCommit(status);triggerAfterCompletion(status);} finally {cleanupAfterCompletion(status); //release connection here} }

我刪除了大多數錯誤處理代碼,以可視化核心問題。 JDBC Connection關閉(實際上是釋放回池中)在cleanupAfterCompletion()很晚cleanupAfterCompletion()發生。 因此在實踐中,在調用doCommit() (物理上提交事務)與釋放連接之間存在間隙。 如果提交后和完成后掛鉤不存在或便宜,則此時間間隔可以忽略不計。 但是在我們的例子中,鉤子正在與ActiveMQ交互,并且在這一天ActiveMQ生產者異常緩慢。 當連接空閑時,所有工作都已完成,但是在沒有明顯原因的情況下,我們仍然保持連接,這會造成非常不尋常的情況。 這基本上是暫時的連接泄漏。

解決方案和摘要

我并不是聲稱這是Spring框架中的錯誤(已通過spring-tx 4.3.7.RELEASE測試),但我很高興聽到此實現背后的原因。 提交后提交鉤子無法以任何方式更改事務或連接,因此在這一點上它是無用的,但我們仍然堅持。 有什么解決方案? 顯然,避免在提交后或完成后掛鉤中長時間運行或不可預測/不安全的代碼是一個好的開始。 但是,如果您真的需要發送JMS消息,進行RESTful調用或產生其他副作用,該怎么辦? 我建議將副作用卸載到線程池并異步執行。 當然,這意味著如果機器出現故障,您的副作用甚至更有可能消失。 但是至少您不會威脅到系統的整體穩定性。

如果您絕對需要確保在事務提交時發生副作用,則需要重新設計整個解決方案。 例如,與其立即發送消息,不如將未決請求存儲在同一事務內的數據庫中,然后稍后重試以處理此類請求。 但是,這可能意味著至少一次語義。

翻譯自: https://www.javacodegeeks.com/2017/03/beware-slow-transaction-callbacks-spring.html

總結

以上是生活随笔為你收集整理的当心Spring缓慢的事务回调的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。