【网关开发】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