您好,登錄后才能下訂單哦!
這篇文章主要介紹了mongo的tickets被耗盡導致卡頓問題怎么解決的相關知識,內容詳細易懂,操作簡單快捷,具有一定借鑒價值,相信大家閱讀完這篇mongo的tickets被耗盡導致卡頓問題怎么解決文章都會有所收獲,下面我們一起來看看吧。
為了解決這個問題,我們首先要明白ticktes是什么,其實網上基本都說的一知半解,沒有一個能說明白的,但是有一個查詢tieckts消耗情況的mongo命令:
db.serverStatus().wiredTiger.concurrentTransactions
查詢結果:
{ "write" : { "out" : 0, "available" : 128, "totalTickets" : 128 }, "read" : { "out" : 1, "available" : 127, "totalTickets" : 128 } }
可以看到tickets分為讀寫兩種,那ticktets到底是什么呢,我們根據這個查詢命令,其實大致可以猜測認為是當前同時存在的事務數量。
也就是mongo限制了同時進行的事務數。
早期因為不知道tickets到底是什么意思,嘗試過很多思路錯誤的優化,所以解決問題,最好還是能弄明白問題本身,才能對癥下藥。
在眾多數據庫卡頓的經歷中,曾有一次因為rabbitmq導致的數據庫卡頓,原因是一小伙伴在請求的過濾層加了一個發送mq的邏輯,但是沒有進行限制,導致每次只有有接口被調,都會去發布一個mq消息,由于過高的并發導致rabbitmq不堪重負,倒是讓人想不明到的是mq卡的同時,數據庫也卡住了。
一開始以為是因為消息過多,導致消費者瘋狂消費,壓垮了數據庫,其實不存在這個問題,因為我們的mq配置單個消費者機器是串行的,也就是同一臺機器同一時間只會消費同一個消息隊列的一條消息,所以并不會因為消息的多給數據庫帶來壓力,只會堆積在mq集群里。所以這次其實沒有找到mq卡頓導致mongo卡頓的原因。
我們接入的幾家第三方服務,比如給我們提供IM消息服務的融云,每次他們出現問題的時候,我們也會出現數據庫卡頓,并且每次時間出奇的一直,但也始終找不到原因。
起初經過對他們調用我們接口情況進行分析,發現每次他們出問題時,我們收到的請求會倍增,以為是這個原因導致的數據庫壓力過大,并且我們基于redis和他們回調的流水號進行了攔截,攔截方式如下:
當請求過來時從redis中查詢該筆流水號狀態,如果狀態為已完結,則直接成功返回
如果查詢到狀態是進行中,則拋異常給第三方,從而讓他繼續重試
如果查詢不到狀態,則嘗試設置狀態為進行中并設置10秒左右的過期時間,如果設置成功,則放到數據庫層面進行數據處理;如果設置失敗,也拋異常給第三方,等待下次重試
等數據庫曾處理完成后,將redis中的流水號狀態改為已完結。
避免重復請求給我們帶來的數據庫的壓力。這其實也算是一部分原因但還是不算主要原因。
引起mongo卡頓的還有發布版本,有一段時間隔三差五發布版本,就會出現卡頓,但是查看更新的代碼也都是一些無關痛癢理論上不會引起問題的內容。
后來發現是發布版本時每次同時關閉和啟動的機器從原來的一臺改成了兩臺(一臺一臺發布太慢,所以運維改成了兩臺兩臺一起發),感覺原因應該就在這里,后來想到會不會和優雅關閉有關,當機器關閉時仍然有mq消費者以及內置循環腳本在執行,當進程殺死時,會產生大量需要立馬回滾的事務,從而導致mongo卡頓。
后來經過和運維小伙伴的溝通發現,在優雅關閉方面確實存在問題,他們關閉容器時會小容器內的主進程發一個容器即將關閉的信號,然后等待幾十秒后,如果主進程沒有自己關閉,則會直接殺死進程。
為此我們需要在程序中實現對關閉信號的監聽,并實現優雅關閉的邏輯,在spring中,我們可以通過spring的時間拿到外部即將關閉的信號:
@Volatile private var consumeSwitch = true /** * 銷毀邏輯 */ @EventListener fun close(event: ContextClosedEvent){ consumeSwitch = false logger.info("----------------------rabbitmq停止消費----------------------") }
可以通過如上方式,對系統中的mq消費者或者其他內置程序進行優雅關停控制,對優雅關閉問題優化后,服務器關閉重啟導致的數據庫卡頓確實得到了有效解決。
上面的融云問題優化過后,后來融云再次卡頓的時候,還是會出現mongo卡頓,由此可見,肯定和第三方有關,但上面說的問題肯定不是主要原因。
后來我看到我們調用第三方的邏輯很多都在@Transactional代碼塊中間,后來去看了第三方sdk里的邏輯,其實就是封裝了一個http請求,但是http請求的請求超時時間長達60秒,那就會有一個問題,如果這個時候第三方服務器卡頓了,這個請求就會不斷地等,知道60s超時,而由于這個操作是在事務塊中,意味著這個事務也不會commit掉,那等于這個事務所占用的tickets也一直不會放掉,至此根本原因似乎找到了,是因為事務本身被卡住了,導致tickets耗盡,從而后面新的事務全部都在等待狀態,全部都卡住了。
其實這次找的原因,同樣也可以解釋前面mq卡頓導致的數據庫卡頓,因為同樣有大量的發送mq的操作在事務塊中,因為短時間瘋狂發mq,導致mq服務端卡頓,從而導致發mq的操作出現卡頓,這就會出現整個事務被卡住,接著tickets被消耗殆盡,整個數據庫卡頓。
找到確定問題后就好對癥下藥了,第三方的問題由于我們不能保證第三方的穩定性,所以當第三方出現問題時的思路應該是進行服務降級,允許部分功能不可用,確定核心業務不受影響,我們基于java線程池進行了同步改異步處理,并且由于第三方的工作是給用戶推送im消息,所以配置的舍棄策略是當阻塞隊列堆積滿之后,將最老的進行丟棄。
而如果是mq導致的這種情況,我們這邊沒有進行額外的處理,因為這種情況是有自身的bug導致的,這需要做好整理分享工作,避免再次出現這樣的bug。
//自己實現的runnable abstract class RongCloudRunnable( private val taskDesc: String, private val params: Map<String, Any?> ) : Runnable { override fun toString(): String { return "任務名稱:${taskDesc};任務參數:${params}" } } //構建線程池 private val rongCloudThreadPool = ThreadPoolExecutor( externalProps.rongCloud.threadPoolCoreCnt, externalProps.rongCloud.threadPoolMaxCnt, 5, TimeUnit.MINUTES, LinkedBlockingQueue<Runnable>(externalProps.rongCloud.threadPoolQueueLength), RejectedExecutionHandler { r, executor -> if (!executor.isShutdown) { val item = executor.queue.poll() logger.warn("當前融云阻塞任務過多,舍棄最老的任務:${item}") executor.execute(r) } } ) //封裝線程池任務處理方法 fun taskExecute(taskDesc: String, params: Map<String,Any?>, handle: ()-> Unit){ rongCloudThreadPool.execute(object :RongCloudRunnable(taskDesc, params){ override fun run() { handle() } }) } //具體使用 taskExecute("發送消息", mapOf( "from_id" to fromId, "target_ids" to targetIds, "data" to data, "is_include_sender" to isIncludeSender )){ sendMessage(BatchSendData(fromId, targetIds, data, isIncludeSender)) }
關于“mongo的tickets被耗盡導致卡頓問題怎么解決”這篇文章的內容就介紹到這里,感謝各位的閱讀!相信大家對“mongo的tickets被耗盡導致卡頓問題怎么解決”知識都有一定的了解,大家如果還想學習更多知識,歡迎關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。