協(xié)議調(diào)用時(shí)長(zhǎng)監(jiān)控

罪惡潛伏在各個(gè)角落。 --VN

游戲中的業(yè)務(wù)邏輯鹿鳖,通常是不建議在網(wǎng)絡(luò)數(shù)據(jù)包接收線程池中處理的(如Netty Worker線程組)扁眯,最好在自己的業(yè)務(wù)線程池中處理。但是翅帜,不管在哪里處理姻檀,我們的業(yè)務(wù)邏輯最好能快速完成。因?yàn)榕核Γ螒驑I(yè)務(wù)線程池的線程數(shù)量通常都是有限的施敢,如果某個(gè)業(yè)務(wù)邏輯處理時(shí)間過(guò)長(zhǎng),那是可能阻塞其他玩家數(shù)據(jù)返回的狭莱,玩家體驗(yàn)起來(lái)會(huì)感覺(jué)游戲很“卡”僵娃。

比如業(yè)務(wù)線程池開(kāi)啟了8個(gè)線程,假如游戲的登錄邏輯處理要1s鐘腋妙,因?yàn)樵诘卿涍壿嬂锬梗赡芤啻蜗虻谌狡脚_(tái)發(fā)送賬號(hào)驗(yàn)證信息,這些就是額外的網(wǎng)絡(luò)開(kāi)銷骤素;還有的第一次登錄邏輯里匙睹,可能會(huì)從數(shù)據(jù)庫(kù)load很多功能模塊數(shù)據(jù),這些就是額外的磁盤讀取開(kāi)銷济竹,這些開(kāi)銷加起來(lái)就導(dǎo)致了登錄邏輯處理時(shí)間過(guò)長(zhǎng)痕檬,在高并發(fā)情況下,比如同時(shí)有9個(gè)玩家同時(shí)登錄送浊,每個(gè)業(yè)務(wù)線程分別處理一個(gè)玩家的登錄梦谜,那第9個(gè)玩家首先就得先等1s,等前面8個(gè)玩家處理完后,然后自己的登錄邏輯再處理1s唁桩,即需總計(jì)等待2s才能登錄到游戲中闭树。

有的游戲業(yè)務(wù)線程邏輯,還是按玩家id根據(jù)線程池大小求余綁定到指定業(yè)務(wù)線程處理的荒澡,在極端的情況下报辱,如果上述9個(gè)玩家被分配到了同一個(gè)業(yè)務(wù)線程去處理,那么第2個(gè)玩家需要等2s鐘才能登錄進(jìn)游戲单山,而第9個(gè)玩家需要等9s鐘才能登錄進(jìn)游戲碍现。這對(duì)玩家的游戲體驗(yàn)肯定是極其不好的。

因此饥侵,我們?cè)谟螒蜷_(kāi)發(fā)過(guò)程中鸵赫,可以寫一個(gè)工具,監(jiān)測(cè)每條協(xié)議的調(diào)用時(shí)長(zhǎng)躏升,通過(guò)長(zhǎng)時(shí)間的監(jiān)測(cè)統(tǒng)計(jì)辩棒,最終選出最耗時(shí)的那幾條協(xié)議處理,從而不斷優(yōu)化它們膨疏,使業(yè)務(wù)邏輯處理時(shí)間越來(lái)越短一睁,提高游戲服的消息吞吐量及玩家的游戲體驗(yàn)。

在這個(gè)工具里佃却,我們可以對(duì)上行協(xié)議(即發(fā)往服務(wù)端的協(xié)議)進(jìn)行調(diào)用時(shí)長(zhǎng)監(jiān)控者吁,從而優(yōu)化業(yè)務(wù)處理時(shí)長(zhǎng);對(duì)下行協(xié)議(即服務(wù)端返回給客戶端的協(xié)議)進(jìn)行包長(zhǎng)監(jiān)控饲帅,從而及早把控客戶端能否處理過(guò)來(lái)复凳。

比如,對(duì)于protobuf協(xié)議可以這樣設(shè)計(jì)灶泵,因?yàn)槊織lprotobuf協(xié)議都是一個(gè)內(nèi)部類:

    private static final ConcurrentMap<Class<? extends Message>, Stats> STATS = new ConcurrentHashMap<>();//protobuf協(xié)議 -> 監(jiān)控統(tǒng)計(jì)

    public static void stats(Class<? extends Message> clazz, long time, int size) {
        Stats stats = STATS.get(clazz);
        if (stats == null) {//說(shuō)明是新的protobuf協(xié)議
            stats = new Stats(clazz);
            Stats old = STATS.putIfAbsent(clazz, stats);
            if (old != null)
                stats = old;
        }
        stats.stats(time, size);//協(xié)議處理時(shí)長(zhǎng)和包長(zhǎng)記錄
    }

統(tǒng)計(jì)類Stats設(shè)計(jì)如下:

    public static class Stats {
        private Class<? extends Message> clazz;
        private long count = 0;
        private long total = 0;
        private long min = Long.MAX_VALUE;
        private long max = Long.MIN_VALUE;
        private long minSize = 0;
        private long maxSize = 0;
        private long totalSize = 0;

        public Stats(Class<? extends Message> clazz) {
            this.clazz = clazz;
        }

        private synchronized void stats(long time, int size) {
            count++;
            total += time;
            min = Math.min(min, time);
            max = Math.max(max, time);
            totalSize += size;
            minSize = Math.min(minSize, size);
            maxSize = Math.max(maxSize, size);
        }

        @Override
        public synchronized String toString() {
            StringBuilder sb = new StringBuilder();
            sb.append("proto:").append(clazz.getSimpleName()).append(",")
                    .append("called:").append(count).append(",")
                    .append("avg:").append(count==0?0:total/count).append("ms,")
                    .append("min:").append(min==Long.MAX_VALUE?0:min).append("ms,")
                    .append("max:").append(max==Long.MIN_VALUE?0:max).append("ms,")
                    .append("avgSize:").append(count==0?0:totalSize/count).append("bytes,")
                    .append("minSize:").append(minSize==Long.MAX_VALUE?0:minSize).append("bytes,")
                    .append("maxSize:").append(maxSize==Long.MIN_VALUE?0:maxSize).append("bytes.");
            return sb.toString();
        }
    }

在業(yè)務(wù)線程中監(jiān)控協(xié)議處理如下:

        long time = System.currentTimeMillis();
        method.invoke(instance, session, msg);//游戲協(xié)議處理
        time = System.currentTimeMillis() - time;
        HandlerStatistic.stats(msg.getClass(), time, packet.getBytes().length);

返回協(xié)議監(jiān)控如下:

        conn.write(new Packet(Packet.HEAD_TCP, cmd, bytes));//返回客戶端協(xié)議
        HandlerStatistic.stats(message.getClass(), 0, bytes.length);

最后在關(guān)服時(shí)導(dǎo)出到文件中:

    public static void dump(File file) {
        FileWriter fileWriter = null;
        try {
            fileWriter = new FileWriter(file);
            fileWriter.append("==================statistic of handler begin(")
                    .append(new Date().toString())
                    .append(")==================\n");
            for (Map.Entry<Class<? extends Message>, Stats> entry : STATS.entrySet()) {
                Stats stats = entry.getValue();
                fileWriter.append(stats.toString()).append("\n");
            }
            fileWriter.append("==================statistic of handler end(")
                    .append(new Date().toString())
                    .append(")==================\n");
            fileWriter.flush();
        } catch (IOException e) {
            log.error("write file failed", e);
        } finally {
            if (fileWriter != null) {
                try {
                    fileWriter.close();
                } catch (IOException e) {
                    log.error("close file writer failed", e);
                }
            }
        }
    }

在導(dǎo)出邏輯里育八,我們還可以對(duì)最大時(shí)長(zhǎng),最大返回包長(zhǎng)做排序功能赦邻,提取前x名的請(qǐng)求協(xié)議或返回協(xié)議打印髓棋,從而優(yōu)化它們。

最終打印信息如下(下面的把處理時(shí)間過(guò)長(zhǎng)的篩選出來(lái)了):

==================statistic of handler begin(Thu Apr 30 15:49:38 HKT 2020)==================
proto:LoginReq_101001,called:29,avg:154ms,min:60ms,max:947ms,avgSize:167bytes,minSize:0bytes,maxSize:188bytes.
proto:GroupChatReq_109001,called:12,avg:132ms,min:16ms,max:819ms,avgSize:53bytes,minSize:0bytes,maxSize:58bytes.
proto:PvpReq_106101,called:11,avg:37ms,min:15ms,max:64ms,avgSize:2bytes,minSize:0bytes,maxSize:2bytes.
proto:FightReq_1203004,called:6,avg:24ms,min:7ms,max:62ms,avgSize:15bytes,minSize:0bytes,maxSize:15bytes.
proto:ThresholdGetHangUpRewardReq_50003,called:2,avg:51ms,min:49ms,max:54ms,avgSize:2bytes,minSize:0bytes,maxSize:2bytes.
proto:OnekeyMailReq_104005,called:2,avg:37ms,min:25ms,max:50ms,avgSize:20bytes,minSize:0bytes,maxSize:20bytes.
proto:FactionHallReq_128004,called:1,avg:18ms,min:18ms,max:18ms,avgSize:2bytes,minSize:0bytes,maxSize:2bytes.
......
==================statistic of handler end(Thu Apr 30 15:49:38 HKT 2020)==================

這樣惶洲,針對(duì)處理時(shí)間過(guò)長(zhǎng)和包長(zhǎng)返回過(guò)長(zhǎng)的按声,我們可以查閱源碼看是否有可優(yōu)化的地方。

相應(yīng)地恬吕,對(duì)于數(shù)據(jù)庫(kù)的增刪改查操作签则,我們也可做如此類似工具監(jiān)測(cè),以監(jiān)控業(yè)務(wù)中是否時(shí)間過(guò)長(zhǎng)的IO處理铐料。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末怀愧,一起剝皮案震驚了整個(gè)濱河市侨颈,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌芯义,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,482評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件妻柒,死亡現(xiàn)場(chǎng)離奇詭異扛拨,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)举塔,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門绑警,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人央渣,你說(shuō)我怎么就攤上這事计盒。” “怎么了芽丹?”我有些...
    開(kāi)封第一講書人閱讀 152,762評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵北启,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我拔第,道長(zhǎng)咕村,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 55,273評(píng)論 1 279
  • 正文 為了忘掉前任蚊俺,我火速辦了婚禮懈涛,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘泳猬。我一直安慰自己批钠,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評(píng)論 5 373
  • 文/花漫 我一把揭開(kāi)白布得封。 她就那樣靜靜地躺著埋心,像睡著了一般。 火紅的嫁衣襯著肌膚如雪呛每。 梳的紋絲不亂的頭發(fā)上踩窖,一...
    開(kāi)封第一講書人閱讀 49,046評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音晨横,去河邊找鬼洋腮。 笑死,一個(gè)胖子當(dāng)著我的面吹牛手形,可吹牛的內(nèi)容都是我干的啥供。 我是一名探鬼主播,決...
    沈念sama閱讀 38,351評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼库糠,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼伙狐!你這毒婦竟也來(lái)了涮毫?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 36,988評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤贷屎,失蹤者是張志新(化名)和其女友劉穎罢防,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體唉侄,經(jīng)...
    沈念sama閱讀 43,476評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡咒吐,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評(píng)論 2 324
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了属划。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片恬叹。...
    茶點(diǎn)故事閱讀 38,064評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖同眯,靈堂內(nèi)的尸體忽然破棺而出绽昼,到底是詐尸還是另有隱情,我是刑警寧澤须蜗,帶...
    沈念sama閱讀 33,712評(píng)論 4 323
  • 正文 年R本政府宣布硅确,位于F島的核電站,受9級(jí)特大地震影響唠粥,放射性物質(zhì)發(fā)生泄漏疏魏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評(píng)論 3 307
  • 文/蒙蒙 一晤愧、第九天 我趴在偏房一處隱蔽的房頂上張望大莫。 院中可真熱鬧,春花似錦官份、人聲如沸只厘。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,264評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)羔味。三九已至,卻和暖如春钠右,著一層夾襖步出監(jiān)牢的瞬間赋元,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,486評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工飒房, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留搁凸,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,511評(píng)論 2 354
  • 正文 我出身青樓狠毯,卻偏偏與公主長(zhǎng)得像护糖,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子嚼松,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評(píng)論 2 345