日常Bug排查-偶發(fā)性讀數(shù)據(jù)不一致
日常Bug排查-偶發(fā)性讀數(shù)據(jù)不一致
前言
日常Bug排查系列都是一些簡單Bug的排查。筆者將在這里介紹一些排查Bug的簡單技巧,同時順便積累素材。
Bug現(xiàn)場
業(yè)務(wù)場景
先描述這個問題出現(xiàn)的業(yè)務(wù)場景。這是一個支付的場景,如果支付成功了,我們就把支付狀態(tài)置為success(主單據(jù)更新)同時寫入支付成功時間戳為t1(子單據(jù)更新)。支付成功之后,我們還需要做其它的動作,做這個動作的時候我們需要剛才的支付成功時間戳t1。那么,我們正常的請求順序即為:

Bug現(xiàn)場
奇怪的是,線上運(yùn)行時候,會有極小的概率(大概是幾億分之一)獲取的這個時間戳為0!也即在讀到主單為success的時候,看到的子單時間戳是0!由于時間戳為0,所以調(diào)用下游RPC傳參錯誤導(dǎo)致了調(diào)用失敗。
如下圖所示:

思路
因為在請求1中,我們是在事務(wù)內(nèi)更新的,數(shù)據(jù)應(yīng)該始終保持一致才對。那很直觀的第一個思考點(diǎn)就是:
思路1: 是不是事務(wù)沒生效?筆者看了下源代碼,使用沒有問題,也不存在類內(nèi)方法互相調(diào)用的情況。再者說,如果事務(wù)沒生效,概率不至于這么低。
思路2:稍加思索一下,好像這個是事務(wù)隔離級別的原因。在這個Case里面,看上去數(shù)據(jù)庫采用的RC隔離級別,也就是讀已提交。如下圖所示:

t1時刻,請求2查詢到的子單據(jù)時間戳為0
t2時刻,請求1提交,這時候?qū)⒆訂螕?jù)時間戳更新為t1,主單據(jù)狀態(tài)為success
t3時刻,請求2由于RC隔離級別,能看到請求1的提交,主單狀態(tài)為success,所以判定可以進(jìn)行下游RPC的調(diào)用,但是由于在t1時刻獲取到的時間戳為0,導(dǎo)致調(diào)用失敗
矛盾點(diǎn)
數(shù)據(jù)庫隔離級別是RC應(yīng)該能非常好的解釋出現(xiàn)Bug時的行為。于是筆者查了一下隔離級別,發(fā)現(xiàn)是RR,這就陷入了矛盾!但由于RC這個隔離級別解釋這個Bug非常的靠譜,所以筆者看了下業(yè)務(wù)的數(shù)據(jù)庫配置,發(fā)現(xiàn)它有100個庫。那么就自然有了下一步猜想:這100個庫中有的是RR的,有的是RC的。出問題的那個庫正好就是RC的。
指定庫查詢隔離級別
于是筆者就根據(jù)業(yè)務(wù)的shardKey到了指定的庫查詢隔離級別,發(fā)現(xiàn)它果然是RC級別的,真相大白!這100個庫中大概有1/3的庫是RC隔離級別。

后續(xù)修復(fù)
這個問題是由于DBA在換庫的過程中采用了默認(rèn)的配置,導(dǎo)致原來設(shè)置為RR級別的庫在換了大容量機(jī)器后被默認(rèn)改成了RC隔離級別。DBA找了個時間將隔離級別切換回RR后問題就消失了,并編寫了相應(yīng)的巡檢腳本防止此類問題再次發(fā)生。
總結(jié)
隔離級別是比較微妙的,相關(guān)問題大多只在高并發(fā)大流量下才會有偶發(fā)性的顯現(xiàn),分庫分表集群中不同DB的隔離級別由于種種原因?qū)е碌牟灰恢聲哟髥栴}的排查難度。有時候遇到無法解釋問題時可以考慮下底層組件的設(shè)置問題。


浙公網(wǎng)安備 33010602011771號