Nginx Unique Tracing ID

背景

我们想要从Nginx接受请求开始,生成一个Unique Tracing ID,不仅记录在Nginx的日志中,也要贯穿到整个后台的服务,从而利用这个ID方便问题的排查。

方案一

利用Nginx丰富的内置变量,拼接出一个“unique enough id”。这里使用了五个变量:

  • $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

实现步骤

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的日志中将包含这个ID

log_format trace '... $req_id';

3.在后台服务中可以通过下面的方式获取$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内置的变量 $request_id
这是最直接的办法,使用Nginx自带的一个$request_id,一个16位比特的随机数,用32位的16进制数表示。

proxy_set_header X-Request-Id $request_id;

问题

这Nginx 1.11.0 版本新增加的feature,使用Nginx旧版本,或者依赖某些二次开发的Nginx版本,例如 Tengine 继承的是Nginx 1.8.1 版本,都面临着升级Nginx的问题。

方案三

使用 Lua 生成一个uuid.
利用Lua轻量小巧的特性,嵌入到Nginx的配置文件当中,然后生成一个uuid.

实现步骤

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的这个模块太长,担心性能问题,需要进行性能评估。

方案四

还是利用Lua脚本,使用时间戳加随机数的方式
关键步骤:

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

问题

os.time()的精确度在1秒,os.clock()的精确度在0.01秒,这样处理之后,总的精度在10毫秒,没有达到要求。
Lua有一个 Luasocket 模块,可以达到毫秒级别的精度,但是需要安装。

方案五

结合Nginx的 $msec 变量和 Lua 的随机数
关键配置

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;
    }
}

终记

最终确定方案五,简单,方便,影响最小。
在方案选择、测试过程中,还遇到了环境搭建相关的问题,将记录在下篇文章中,敬请期待!


参考

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,905评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,140评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,791评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,483评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,476评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,516评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,905评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,560评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,778评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,557评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,635评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,338评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,925评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,898评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,142评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,818评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,347评论 2 342

推荐阅读更多精彩内容