【网关开发】10.Openresty 网关全链路的数据生成与数据上报

背景

全链路监控作为公司的重要的基础设施之一,可以帮助理解系统行为、用于分析性能问题,以便发生故障的时候,能够快速定位和解决问题。
网关作为流量的入口是非常重要的服务,也是全链路中的重要一环,需要讲信息传入全链路系统。

数据生成

全链路包含的信息越全面,查找问题的时候就越方便。
全链路中比较重要的信息包括trace_id,parent_id,child_id,还有其他的包括执行时间等

配置文件设置

apis.conf

# 在access阶段调用的lua块
access_by_lua_block { require "waf.trace".on_access() }

生成数据

从header中获取数据,如果没有数据,说明当前服务是初始服务,需要生成各种ID,放入header中
trace.lua

local function on_access()
    if disable_trace then
        return
    end
    local req = ngx.req
    local headers = req.get_headers()
    local traceid = headers["X-Trace-ID"]
    local parentid = headers["X-PARENT-ID"]
    local msgid = headers["X-CHILD-ID"]
    local newmsgid = generate_msg_id()  --生层traceid 可以有很多ID生成算法,比如SnowFlake,目的就是保证唯一性,下面有一个简单的实现
    if not newmsgid then
        return
    end
    if traceid and parentid and msgid then
        req.set_header("X-PARENT-ID", msgid)
        req.set_header("X-CHILD-ID", newmsgid)
    else
        -- treat as new transaction
        traceid = newmsgid
        parentid = newmsgid
        msgid = newmsgid
        newmsgid = generate_msg_id()
        req.set_header("X-Trace-ID", traceid)
        req.set_header("X-PARENT-ID", parentid)
        req.set_header("X-X-CHILD-ID", newmsgid)
    end

    local ctx = ngx.ctx
    ctx.trace_id = traceid
    ctx.parent_id = parentid
    ctx.msg_id = msgid
    ctx.child_id = newmsgid
    ctx.request_time = ngx.now() * 1000
end

-- 唯一ID的简单生成
local function generate_msg_id()
    local h = math_floor(ngx.time() / 3600)
    local key = "trace_sn_"..h
    local sn, err = shm:incr(key, 1, 0)
    if err then
        ngx.log(ngx.ERR, err)
        return
    end
    if sn == 1 then
        -- shm:expire(key, 3600 + 600) -- 1h10min to expire
        shm:set(key, 1, 3600 + 600)
    end
    return "openresty-"..get_my_ip().."-"..h.."-"..tostring(sn)
end

数据上报

一般服务的全链路数据上报都是SDK使用TCP/UDP将日志发送给本地插件,再由插件发送给采集服务。openresty的扩展都是lua写的,考虑到性能与接入sdk复杂性,采用将数据直接写入kafka
后面通过解析程序将数据上报平台的方式。
lua使用kafka采用的库:https://github.com/doujiang24/lua-resty-kafka

配置文件

apis.conf

# 在log阶段调用的代码块
log_by_lua_block { require "waf.trace".on_log() }

上报数据

local function _on_log()
    local ctx = ngx.ctx
    if not ctx.msg_id then
        return
    end

    local upaddr = tostring(ngx.var.upstream_addr)
    local child_id = ctx.child_id
    if not upaddr or upaddr == "nil" or upaddr == "" then
        child_id = nil -- no proxy, clear child id
    end

    local response_time = ngx.var.request_time 
    local response_time = tonumber(response_time)
    if not response_time then
        response_time = math_floor(ngx.now() * 1000)
    else
        response_time = ctx.request_time + math_floor(response_time * 1000)
    end
    local req = ngx.req
    local host=ngx.var.host
    local args=req.get_uri_args()
    local headers=req.get_headers()
    local ip = ngx.var.remote_addr

    local status=ngx.var.upstream_status
    local status_num = tonumber(status)
    if status_num == nil then
        status_num = 500
    end
    -- 拼接详细信息
    local data, err = require "cjson.safe".encode({
        url = tostring(ngx.var.uri),
        trace_id = ctx.trace_id,
        parent_id = ctx.parent_id,
        msg_id = ctx.msg_id,
        child_id = child_id,
        request_time = ctx.request_time,
        response_time = response_time,
        host = host,
        real_ip = ip,
        proxy_ip = tostring(ngx.var.server_addr),
        parms = args,
        status = status_num,
        headers = headers,
        upstream_addr = upaddr,
    })
    if err then
        ngx.log(ngx.ERR, "trace json encode err:", err)  
        return  
    end

    local ok, err = _send_msg(data)
    if not ok then
        ngx.log(ngx.ERR, "trace kafka send err:", err)  
        return
    end 
end

lua使用kafka producer

使用比较简单,不需要额外编译,直接将文件拷贝到resty目录下即可

local function _send_msg(data)
    ngx.log(ngx.INFO,"traceID msg"..data)

    local producer = require "resty.kafka.producer"
    local config = require "waf.config"
    local bp = producer:new(config['broker_list'], { producer_type = "async" })
    local ok, err = bp:send(config["topic"], nil, data)
    if not ok then
        ngx.log(ngx.ERR, "send msg to kafka err:", err)
        return
    end
end

总结

整体比较简单,理解全链路的的服务调用链(树)的概念比较重要。
本次提交代码:https://github.com/zhaoshoucheng/openresty/blob/main/pkg/lua_script/waf/zeus.lua

posted @ 2023-03-03 16:02  zscbest  阅读(204)  评论(0编辑  收藏  举报