溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

僅378條數(shù)據(jù)居然導(dǎo)致合服失?。?!

發(fā)布時(shí)間:2020-08-07 06:49:34 來源:ITPUB博客 閱讀:162 作者:騰訊云數(shù)據(jù)庫 欄目:數(shù)據(jù)庫

作者:伍旭飛,騰訊云數(shù)據(jù)庫高級工程師,主要負(fù)責(zé)騰訊云Redis、MongoDB開發(fā)。

故事從一個MongoDB數(shù)據(jù)庫連接超時(shí)案例說起。該異常導(dǎo)致2次合服失敗,前面已在服務(wù)器上抓包并dump下來,下方是客戶端超時(shí)現(xiàn)場截圖:

僅378條數(shù)據(jù)居然導(dǎo)致合服失???!

從截圖不難看出,這是一個Nodejs服務(wù)出錯信息,推測DBA應(yīng)該是用的nodejs mongodb來實(shí)現(xiàn)連接數(shù)據(jù)庫并進(jìn)行操作,找到這個driver的官網(wǎng) https:// -mongodb-native ,clone了一份代碼下來。簡單看了下,再結(jié)合上圖,初步分析出錯連接是在第38個連接超時(shí)的。

1. 分析抓包內(nèi)容

由于前面已經(jīng)在出錯服務(wù)器上抓包了,因此,首先我用wireShark打開從服務(wù)器上dump下來的文件,wireShark很智能,能分析多種常用協(xié)議,很方便,但是也容易帶來誤判。比如我們的數(shù)據(jù)庫連接很多地方都被誤判為X11協(xié)議:

僅378條數(shù)據(jù)居然導(dǎo)致合服失???!

一開始很糾結(jié)這個錯誤,其實(shí)這當(dāng)然不是什么x11協(xié)議,只是碰巧模式匹配上了,到wireShark設(shè)置了下,取消了X11的分析,很容易從端口和連接看出,就是數(shù)據(jù)庫連接。

仔細(xì)檢查了抓包內(nèi)容,大致如下:

(1)開始有個連接從數(shù)據(jù)庫拉取了大概3M多的數(shù)據(jù)。

(2)后面陸續(xù)有常規(guī)的三次握手連接建立成功,但是都基本沒有實(shí)質(zhì)性的數(shù)據(jù)傳輸,就走了正常的tcp結(jié)束流程了。

(3)從抓包內(nèi)容來看,服務(wù)器不存在未回應(yīng)客戶端syn連接包的情況。

好了,到這里分析的內(nèi)容,似乎完全解釋不了為什么會超時(shí),那么下一步就是和用戶溝通,獲取更多的信息了。

(4)所有的tcp鏈接均為客戶端發(fā)起FIN主動關(guān)閉,不存在服務(wù)器主動關(guān)閉客戶端連接的情況。

2. 出錯代碼

通過溝通,拿到了出錯部分工具的代碼片段(最開始沒有完整的函數(shù),后面才拿到完整函數(shù)):

function merge_union_info(dbs) {
    var union_data = [];
    async.each(
        dbs,
        function(path, cb) {
            mongodb.connect(path, (err, db) => {
                if (err) {
                    cb(err);
                    return
                }
                db.collection('union').find().sort({level: -1, exp: -1}).toArray((err1, v) => {
                    if (err1) {
                        db.close();
                        console.log(err1);
                        return
                    }
                    let loop = v.length > 50 ? 50 : v.length;
                    let u_data = [];
                    for (let i = 0; i < loop; i++) {
                        v[i].merge_flag = 1;
                        u_data.push(v[i]);
                    }
                    union_data.push(u_data);
                    db.close();
                    cb();
                });
            })
        },
        function(err) {
            if (err) {
                console.log("[ERROR]merge union-data failed !!!");
                return
            }
            async.waterfall([
                function(cb1) {
                    mongodb.connect(dbs[0], (err1, db) => {
                        if (err1) {
                            cb1(`[ERROR]gen union-data [drop] failed for ${err1}`)
                            return
                        }
                        var col = db.collection('union');
                        db.collection('union').drop((err2, r2) => {
                            db.close();
                            cb1(err2);
                        });
                    }); 
                },
                function(cb1) {
                    async.each(
                        union_data, 
                        function(u_data, cb2) {
                            mongodb.connect(dbs[0], (err1, db) => {
                                if (err1) {
                                    cb2(`[ERROR]gen union-data [insert] failed for ${err1}`)
                                    return
                                }
                                var col = db.collection('union');
                                col.insertMany(u_data, (err2, r2) => {
                                    db.close();
                                    cb2(err2);
                                });
                            }); 
                        },
                        function(errN) {
                            cb1(errN);
                        }
                    );
                },
            ], function(errX, r) {
                if (errX) {
                    console.log("[ERROR]gen union-data failed for ", errX);
                }else {
                    console.log("4 - update union-data ok !!!");
                }
            });
        }
    );
}

熟悉nodejs的都知道,nodejs的優(yōu)點(diǎn)是無同步操作,所以性能相對高。所以通過代碼分析,第一反應(yīng)是數(shù)據(jù)量太大導(dǎo)致建立了太多的tcp連接,而mongodb是每用戶一個線程的處理模型,極有可能造成tcp連接達(dá)到max open file數(shù)量或者線程太多,導(dǎo)致整個系統(tǒng)性能下降,以至于無回應(yīng)。

3. 嘗試重現(xiàn)

通過代碼分析,我簡單寫了個函數(shù),希望能重現(xiàn),mongodb用的是虛擬機(jī)上自己搭建,代碼如下:

function doLoopInsertTest(mongourl:string){
    for(var i = 0; i < maxInsertCount/500; i++){
        
        mongodb.MongoClient.connect(mongourl, function(err, client) {
            if (err != null){
                console.log("error:", err, "\n")
                return
            }
                    
            console.log("Connected successfully to server");
            const db = client.db("testdb");
            db.collection("testfei").insertMany(getNewDoc(i*500), (err, result:mongodb.InsertWriteOpResult)=>{
                if (err != null){
                    console.log("write error:", err)
                    return
                }
            })
            
        })
    }
}

這里模擬了用戶是每500個合并插入的代碼,在自建的mongodb上,很快就超過了1024的max fd限制。unlimit修改后,重啟mongodb進(jìn)程,再次測試,很快客戶端這邊無響應(yīng),但是出錯信息和用戶不完全相同,嘗試了好幾次,其中有一次出現(xiàn)了timeout的錯誤信息。

4. 用戶反饋

似乎大功告成,于是第二天把相關(guān)信息和用戶溝通,用戶反饋出錯的部分沒有那么多數(shù)據(jù),而且總共就插入了100條數(shù)據(jù),并提供了要合并的2個表的數(shù)據(jù)。

我在測試環(huán)境mongodbrestore看了下,一個表示257條,一個表示121條,總共加起來378條數(shù)據(jù)!!這根本不可能出錯,就算是每個插入建立一個連接,也不會出錯。

出于謹(jǐn)慎的考慮,我還是寫了個小代碼片段,在本地的虛擬機(jī)環(huán)境下測試了下,當(dāng)然是完全沒問題。萬一是真的云服務(wù)器獨(dú)有的問題呢,我申請了一個測試的mongodb,把數(shù)據(jù)導(dǎo)入,然后用nodejs代碼測試了下,依然沒有任何問題!

于是,與用戶進(jìn)一步溝通,是否是mongodb driver代碼版本不夠新?

得到反饋是:mongodb driver代碼確實(shí)不夠新,但是,他們前幾次合服也是這個代碼,都能成功。用戶不認(rèn)為代碼有問題,也不認(rèn)可是庫的版本問題。

用戶也不適合用我提供的代碼直接測試,因?yàn)槊看螠y試都要發(fā)公告停服合服,合服失敗后,部分?jǐn)?shù)據(jù)要手工回檔,風(fēng)險(xiǎn)太大。

5. 柳暗花明

似乎陷入了死胡同,我們不相信300多條數(shù)據(jù)插入會有問題,用戶不認(rèn)同我們的結(jié)論,我甚至詢問用戶,有沒有可能真的ip和端口寫錯了(其實(shí)也說不同,錯誤日志里的端口是對的)。

這個時(shí)候,突然想起來,合服不可能只合并工會(前面的代碼是工會合并的部分),前面應(yīng)該還有角色這些合并吧。用戶確認(rèn)了,發(fā)了代碼截圖:

僅378條數(shù)據(jù)居然導(dǎo)致合服失???!

并且標(biāo)注了出錯代碼是update_union_info。

這里就比較明顯能看出問題了: 數(shù)據(jù)量最大的是update_user_info,而基于nodejs的特點(diǎn),update_user_info應(yīng)該也是異步操作的,也就是說,執(zhí)行到update_union_info的時(shí)候,update_user_info一定是沒有執(zhí)行完畢的!而nodejs是單線程!!! 單線程!!! 單線程!!!重要的事情3遍。

所以,假如update_user_info的運(yùn)算量非常大,那么,即使網(wǎng)絡(luò)層tcp連接成功,也極有可能得不到運(yùn)行機(jī)會,等到cpu釋放出來,很可能已經(jīng)超時(shí)了。

6. 再次嘗試重現(xiàn)

通過前面的分析,寫了簡單粗暴代碼來重現(xiàn):

僅378條數(shù)據(jù)居然導(dǎo)致合服失???!

這個代碼很粗暴,setTimeout模擬的是用戶合并角色數(shù)據(jù)的過程,假設(shè)運(yùn)行了50S。

很快我們的超時(shí)斷點(diǎn)命中了:

僅378條數(shù)據(jù)居然導(dǎo)致合服失?。?!

把這個分析結(jié)果和用戶反饋后,用戶同意改代碼,但是想提前測試下。

7. 問題解決

正好我們mongodb回檔功能會提供一個臨時(shí)實(shí)例,回檔過程對線上完全無影響,是不是很神奇~

創(chuàng)建出來的臨時(shí)實(shí)例可以選擇替換線上的實(shí)例,也可以選擇不替換,轉(zhuǎn)正為一個臨時(shí)實(shí)例(保存2天)。2天足夠用戶測試了,用戶修改代碼為全部串行后,將20幾個服全部回檔測試了一把,最終測試成功!

往期推薦

云MongoDB優(yōu)化使LBS服務(wù)性能提升十倍

僅378條數(shù)據(jù)居然導(dǎo)致合服失敗?!

開年大禮包

僅378條數(shù)據(jù)居然導(dǎo)致合服失?。浚?></p>            </div>
            <div   id=向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI