求生欲很強的數據庫


 

最近一個月來一直在做某局的項目,涉及到了微信支付相關的業務。業務本身也是一套完整的從下單到支付到退款的全流程,我司和三方聯調開發,個中滋味不表。

其中從最開始就遇到一個反復出現(偶發性)的問題,有時候數據庫無法快速且正常返回查詢結果。我先后從多個方面進行嘗試調試,尋求解決辦法。

我先還原一下場景。

服務器:2台a和b。

負載均衡:訪問任意到打到a或者b。

數據庫:前期沒有使用持續化存儲,用的熱數據緩存。此次使用mongo,最開始版本3.0.x,后升級到我司其他線上項目運行版本3.2.6。a和b公用同一台database service,且mongo安裝在b上。

 

現象:數據庫有時候能返回查詢結果,有時候好像卡死在那里,從接口調用到最終數據庫服務本身皆無錯誤日志。我查詢的那個集合只有50多條記錄,並且我增加了索引在我的查詢字段上,按照道理來說應該是毫秒級別的耗時。

 

首先我先從項目數據庫調用入手,寫了最小化的腳本,單獨去讀取某一條訂單記錄,能快速且正常返回。至此說明項目是用的mongo driver是能正常調用到mongo的。后我開啟mongoose debug模式,開啟方式為在我操作類里添加如下語句:

mongoose.set('debug', true);

開啟后,每當調用到數據庫操作會輸出類似mysql的sql語句,更方便調試跟蹤問題。然而我看到的現象是,有時候能正常執行查詢的時候就有該debug日志吞吐,而有時候沒有任何輸出,也無數據返回。

期間,我反復嘗試安裝並部署不同版本的mongo和對應版本的mongoose,甚至調整了項目框架版本,先排除了各模塊之間兼容性問題。

后我懷疑到是否是負載均衡到了a(mongo是安裝在b上),而a不能訪問到b上的mongo呢?

我查詢相關資料,修改了mongo的配置文件,將bind_ip從127.0.0.1改為0.0.0.0,且把maxConns改為2000。試探性地臨時關閉了iptables服務,然而沒有解決該問題。

在這個過程里面我有意識到,每當重啟服務之后短暫的十幾秒里面,可以正常進行查詢,最多能成功2次,到第三次的時候就會前端調用超時報錯。

后為了能順利進行后續開發(因為很多接口是用到數據庫存儲本地數據的),我暫時關閉了a上的項目服務,先排除因為網絡不通造成的問題。到這個時候數據庫查詢就開始正常了一段時間,我的開發也能順利進行下去。

我那個時候暫時把問題歸結於網絡,因為腳本本地調用是ok的,關閉了負載,b上本地部署mongo調用也是正常的。我和某科的老師一起排查問題,a和b兩台機子相互ping和telnet均成功,說明數據鏈路層是通的,問題應該是在應用層上的。通過嘗試添加output設置,將27017端口暴露出去。然后重啟a上的服務,再次進行負載均衡,驚喜地發現兩邊都可以正常使用mongo查詢了。到這個時候,我再次把問題歸結於網絡設置導致每次請求轉到a的時候無法連接到mongo,而有時候負載到b是正常的,所以才會偶發性地出現查詢結果無法返回導致超時的問題。

Xx大會如期上線了我們的項目,大會期間非常穩定,此后的一周我再也沒想起這段為了解決詭異bug而煩惱的時光。直到后面和某海聯調退費接口的時候,再次出現該問題。

說實話,到這次復現問題的時候,我是有點沒有思路。因為之前幾乎把所有可能性都嘗試過了。在同事探討和幫助的過程,我想到了會不會是有其他代碼引起mongo操作阻塞呢?

 

在網上也有人遇到過類似問題,討論回復如下:

有個比較常見的坑是mongodb使用數據庫級的鎖,在寫數據的時候要快,否則很容易把其他mongodb的請求阻塞。你可以嘗試在mongodb起來的時候是就開起mongostat,然后查看locked%和qr|qw列的值是否出現異常,飆升。 

還有如果collection中記錄很多的話,一定要建立索引。你可以直接在mongo shell下驗證你的查詢是否有用到索引,我記得mongo語句后跟explain()可以顯示索引是否起作用的。

簡單說就是有頻繁寫入的操作和頻繁未命中索引(包括無索造成引)查詢(被查詢的集合數據量較大)的邏輯代碼導致數據庫級別鎖,讓其他請求一直排隊等待。我想起了每次查看日志的時候總有調用人社接口的日志大量輸出,而在那個公共調用的代碼里面有一段讀寫mongo的操作代碼。

我仔細看了一下,這段歷史遺留代碼是為了緩存從人社接口拉取到的數據,其他的我不說了,緩存這段代碼毫無意義,首先從量級來看全cd人的數據有無必要存是其一,其二查詢沒有索引,業務場景上是存儲的數據是只增不減的,數據數量越來越大,查詢會非常慢。其三我全文搜索也沒有找到用到這個數據的其他地方。經過查詢,該集合數據量已經超過200萬條。

我先屏蔽了該段代碼,重啟服務后發現之前timeout的接口馬上毫秒級返回了數據。多次嘗試跑通了全支付流程,再無mongo查詢異常出現。之后和寫這段代碼的前同事溝通后確認該代碼是可以刪除的,至此該問題塵埃落地。

所以,之前時好時壞,只是因為數據庫頑強的求生欲而已。本質是因為那段歷史遺留代碼會非常頻繁地讀寫數據庫,導致其他請求排隊等待。通過在這次排查問題,我又一次對分析問題有了新的思考:

  1. 對一些臨時接手后有大量歷史遺留問題的項目,一定要深入細節,不能因為是繼承的代碼,默認它是正常無錯的,關於那段存儲人社數據的代碼我也曾經懷疑過,但是沒有去深究,默認了它一直使用就是不能輕易改動的。
  2. 結對編程是很重要的。這在一些其他公司已經很常見了,對於小的創業公司,我們考慮更多的是一個工程師去support一個或者多個完整的項目,有時候個人的思路是非常有限的,常見的問題是很容易通過自身努力解決,而一些思路有死角的疑難雜症可能需要群策。我這里說的結對編程特指對該項目負責且全程參與的人。因為往往一些google都很難找到特別接近的解決方案的問題,往往有項目自身特殊原因。

God is in details.God bless my code.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM