30行代碼在skynet中實現預警機器人

1. 預警機器人的定義

預警機器人就是當線上有任何錯誤發生時,它會把錯誤信息以某種形式通知到某處。

2. 思路

2.1 報錯攔截

在 skynet 構建的系統中,報錯一般是 lua 引起的,比如 attemp index a nil value, 這些報錯的位置雖然分散在成百上千個文件裏,但入口其實非常有限,我們只要在入口處攔截掉這些報錯,就能把錯誤信息發送到出去。

以下列出不同業務類型其報錯的入口點:

函數類型 入口點 底層代碼位置
skynet.dispatch dispatch 函數自身
skynet.fork error skynet.lua Line 184, 607行resume後協程執行報錯,協程中斷,不會直接報錯,只是結束協程並讓出執行權,resume返回nil傳到suspend內,184行調用error
skynet.timeout assert skynet.lua Line 617, 這個跟普通消息一樣都是走 dispatch_message, pcall+ assert
snax 服務 assert snaxd.lua Line 46

可以看到,除了 dispatch 外,其他幾類都在 assert/error 裏,所以我們只需要改寫 assert/error 函數,並在 dispatch 裏使用 pcall 把真正的業務函數包起來,再把結果送到 assert 或 error 裏,整個錯誤攔截就完成了。

之所以這麼簡單,還是得益於雲風良好的編碼風格,幾乎底層在執行上層業務函數時,都是 pcall 包起來,再由 assert/error 拋出這麼個模式。

pcall+assert 示例代碼:

local ok, err = pcall(f, ...)
assert(ok, err)

2.2 服務設計

我們需要創建一個專門發錯誤信息的服務,在改寫 assert/error 時,把錯誤信息統一轉發到這個服務,再由這個服務處理。

給這個服務起名叫 error_monitor 吧,這裏面只做一件事,接收其他服務發來的報錯信息,整理一下(帶上集羣名和節點ID),發到目的地即可。

2.3 錯誤信息發送目的地

攔截到錯誤信息後,想發到哪都可以,我們不弄太複雜,直接用釘釘機器人。不熟悉的可以看看這裏

發送的方式也不打算用 libcurl, 而是直接用終端裏的 curl, os.execute(“curl xxxx”), 這裏只是爲了簡單,因爲假設我們的報錯信息不是很多,用這種方式也完全夠用了。如果你覺得你們的報錯很多,可以改用 libcurl, 當然這樣30行代碼搞不定了。

或者如果不想在遊戲進程內處理,也可以把報錯信息發到一個類似 rabbitmq 這樣的消息隊列裏,再在外面起一個進程去消息這些報錯。

3. 實現

3.1 實現 error_monitor 服務

local skynet = require "skynet"
skynet.start(function()
    local worldName = skynet.getenv("world_name") or "Unknown(Please add world_name to config)" -- worldName 也就是集羣名,用來判斷是哪個服務器,默認可以用 require("skynet.cluster.core").nodename()
    local url = "https://oapi.dingtalk.com/robot/send?access_token=xxxxx" -- 後面的xxx改成自己機器人的token
    local selfnodeid = 1 -- 自己的節點id
    skynet.dispatch("lua", function(_,_,...)
        os.execute(string.format([[curl '%s' -H 'Content-Type: application/json' -d '{"msgtype":"markdown","markdown":{"title":"ERROR","text":"* World: %s\n* Node: %d\n* Traceback:\n`%s`"}}']],
            url, worldName, selfnodeid, select("#",...)>1 and table.concat({...}," ") or tostring(...)))
    end)
end)

3.2 改寫 assert/error

function util.registerErrorMonitor()
    local addr = skynet.uniqueservice("error_monitor")
    sutil.registerServerLaunchCallback(function()
        local _error = error
        function error(...)
            skynet.send(addr, "lua", ...)
            _error(...)
        end
        local _assert = assert
        function assert(...)
            if not ... then
                skynet.send(addr, "lua", ...)
            end
            return _assert(...)
        end
    end)
end

改寫完後,在需要監控報錯的服務的初始化那裏,調一下util.registerErrorMonitor即可。

這裏不推薦把改寫函數放到 preload 裏去,因爲並不是所有服務都需要改寫的,一般我們只改幾個大的業務服務即可,再說也不能在 preload 裏調 uniqueservice。

最後不要忘記 skynet.dispatch 改成 pcall+assert 的模式,這樣纔可以把報錯拋出去,如果不想要重複寫這段代碼,可以簡單封裝一下:

function skynet_dispatch(typename, func)
    skynet.dispatch(typename, function(session, source, cmd, ...)
        assert(pcall, func, session, source, cmd, ...)
    end)
end
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章