Nginx Unique Tracing ID

背景

我們想要從Nginx接受請求開始,生成一個(gè)Unique Tracing ID,不僅記錄在Nginx的日志中,也要貫穿到整個(gè)后臺的服務(wù),從而利用這個(gè)ID方便問題的排查。

方案一

利用Nginx豐富的內(nèi)置變量,拼接出一個(gè)“unique enough id”。這里使用了五個(gè)變量:

  • $pid: Nginx worker process id
  • $msec: timestamp in millisecond
  • $remote_addr: client address
  • $connection: TCP connection serial number
  • $connection_requests: current number of requests made through a connection

實(shí)現(xiàn)步驟

1.在nginx.conf的location模塊里:

location / {
    proxy_pass http://upstream;
    set $req_id $pid.$msec.$remote_addr.$connection.$connection_requests;
    proxy_set_header X-Request-Id $req_id;
}

2.在http模塊的 log_format 里加上 $req_id,至此Nginx的日志中將包含這個(gè)ID

log_format trace '... $req_id';

3.在后臺服務(wù)中可以通過下面的方式獲取$req_id

class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.write(self.request.headers["X-Request-Id"])

4.重啟Nginx

nginx -s reload

問題

格式混亂,信息冗余,生成的效果如下:

97372.1493211301.686.127.0.0.1.471.32

方案二

使用Nginx內(nèi)置的變量 $request_id
這是最直接的辦法,使用Nginx自帶的一個(gè)$request_id,一個(gè)16位比特的隨機(jī)數(shù),用32位的16進(jìn)制數(shù)表示。

proxy_set_header X-Request-Id $request_id;

問題

這Nginx 1.11.0 版本新增加的feature,使用Nginx舊版本,或者依賴某些二次開發(fā)的Nginx版本,例如 Tengine 繼承的是Nginx 1.8.1 版本,都面臨著升級Nginx的問題。

方案三

使用 Lua 生成一個(gè)uuid.
利用Lua輕量小巧的特性,嵌入到Nginx的配置文件當(dāng)中,然后生成一個(gè)uuid.

實(shí)現(xiàn)步驟

1.在 http 模塊里加入:

    map $host $uuid {
        default '';
    }
    lua_package_path '/path/to/uuid4.lua';
    init_by_lua '
        uuid4 = require "uuid4"
        math = require "math"
    ';

2.在server模塊里加入:

    set_by_lua $uuid '
        return uuid4.getUUID()
    ';

3.在location模塊里加入:

    proxy_set_header X-Request-Id $uuid;

4.uuid4.lua
引用自 第三方庫

--[[
The MIT License (MIT)
Copyright (c) 2012 Toby Jennings
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
associated documentation files (the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge, publish, distribute,
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial
portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--]]

local M = {}
-----
math.randomseed( os.time() )
math.random()
-----
local function num2bs(num)
    local _mod = math.fmod or math.mod
    local _floor = math.floor
    --
    local result = ""
    if(num == 0) then return "0" end
    while(num  > 0) do
         result = _mod(num,2) .. result
         num = _floor(num*0.5)
    end
    return result
end
--
local function bs2num(num)
    local _sub = string.sub
    local index, result = 0, 0
    if(num == "0") then return 0; end
    for p=#num,1,-1 do
        local this_val = _sub( num, p,p )
        if this_val == "1" then
            result = result + ( 2^index )
        end
        index=index+1
    end
    return result
end
--
local function padbits(num,bits)
    if #num == bits then return num end
    if #num > bits then print("too many bits") end
    local pad = bits - #num
    for i=1,pad do
        num = "0" .. num
    end
    return num
end
--
local function getUUID()
    local _rnd = math.random
    local _fmt = string.format
    --
    _rnd()
    --
    local time_low_a = _rnd(0, 65535)
    local time_low_b = _rnd(0, 65535)
    --
    local time_mid = _rnd(0, 65535)
    --
    local time_hi = _rnd(0, 4095 )
    time_hi = padbits( num2bs(time_hi), 12 )
    local time_hi_and_version = bs2num( "0100" .. time_hi )
    --
    local clock_seq_hi_res = _rnd(0,63)
    clock_seq_hi_res = padbits( num2bs(clock_seq_hi_res), 6 )
    clock_seq_hi_res = "10" .. clock_seq_hi_res
    --
    local clock_seq_low = _rnd(0,255)
    clock_seq_low = padbits( num2bs(clock_seq_low), 8 )
    --
    local clock_seq = bs2num(clock_seq_hi_res .. clock_seq_low)
    --
    local node = {}
    for i=1,6 do
        node[i] = _rnd(0,255)
    end
    --
    local guid = ""
    guid = guid .. padbits(_fmt("%X",time_low_a), 4)
    guid = guid .. padbits(_fmt("%X",time_low_b), 4)
    guid = guid .. padbits(_fmt("%X",time_mid), 4)
    guid = guid .. padbits(_fmt("%X",time_hi_and_version), 4)
    guid = guid .. padbits(_fmt("%X",clock_seq), 4)
    --
    for i=1,6 do
        guid = guid .. padbits(_fmt("%X",node[i]), 2)
    end
    --
    return guid
end
--
M.getUUID = getUUID
return M

問題

Lua的這個(gè)模塊太長,擔(dān)心性能問題,需要進(jìn)行性能評估。

方案四

還是利用Lua腳本,使用時(shí)間戳加隨機(jī)數(shù)的方式
關(guān)鍵步驟:

    set_by_lua $rdm_number '
        return os.time() .. os.clock()*100 .. math.random(1000000000, os.time())
    ';

問題

os.time()的精確度在1秒,os.clock()的精確度在0.01秒,這樣處理之后,總的精度在10毫秒,沒有達(dá)到要求。
Lua有一個(gè) Luasocket 模塊,可以達(dá)到毫秒級別的精度,但是需要安裝。

方案五

結(jié)合Nginx的 $msec 變量和 Lua 的隨機(jī)數(shù)
關(guān)鍵配置

server {
    ...
    set_by_lua $rdm_number '
        return math.random(1000000000, os.time())
    ';
    location / {
        ...
        set $req_id $msec$rdm_number;
        proxy_set_header X-Request-Id $req_id;
    }
}

終記

最終確定方案五,簡單,方便,影響最小。
在方案選擇、測試過程中,還遇到了環(huán)境搭建相關(guān)的問題,將記錄在下篇文章中,敬請期待!


參考

1.http://stackoverflow.com/questions/17748735/setting-a-trace-id-in-nginx-load-balancer
2.https://blog.ryandlane.com/2014/12/11/using-lua-in-nginx-for-unique-request-ids-and-millisecond-times-in-logs/
3.http://www.jb51.net/article/82167.htm
4.http://nginx.org/en/docs/http/ngx_http_core_module.html#.24args
5.http://nginx.org/en/docs/http/ngx_http_core_module.html#var_request_id

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容