微服务架构--链路追踪(Nginx篇)

阅读提示:本文不提供链路追踪的完整解决方案,只提供Nginx层对链路追踪的支持方案!

1 背景介绍

    微服务的诞生,解决了传统单体应用的很多问题,如可维护性差、扩展性差和灵活性差等问题(粗粒比较)。微服务架构虽好,但同时也带来了很多挑战,其中故障排查就是其需要解决的挑战之一。那么,如何在很多个应用和实例中找到故障发生的根源呢?

    基于以上需求,我们可以将每一笔交易在各个应用中产生的所有日志,进行集中式收集与展示(但前提是你得有:日志中心)。这样就可以很快看出交易是在哪一步出的故障。如果做得好,还可以直接进行二次开发与数据分析,将收集的日志和出现的故障进行分析后,用图形界面很直观的进行展示。

    比如,可以展示出微服务调用的拓扑图,使用颜色进行区分故障(如常用红:表示异常、绿:正常、黄:警告)。接着可以将常出现的故障或异常进行分类后做出友好型的展示(说白了就不用直接上堆栈),如:NullPointerException:则界面直接友好型的提示哪一行代码抛了空指针,输入参数是什么……(这不是该篇的重点哈,废话不多说了,后续有机会再详细介绍)。

    要做整个微服务架构的链路追踪,肯定是希望从交易进入微服务中心的第一个点就开始有一个全局的交易ID来关联所有日志(链路追踪,这么一个ID肯定是不够的,但这里只介绍这个哈)。当然最理想的肯定是希望把前端的日志(如操作日志、数据流等)也规划进行。

2 Nginx

    在大部分的微服务架构中,Nginx基本是常用的接入层设施,所以我们希望请求ID从Nginx层进行校验填充,并且打印在Nginx的请求日志中。这里只提供三种方式来实现Nginx层的交易ID生产方式。

  2.1 方案二:基于内置变量拼接

        在1.11.0之前的版本,我们可以采用拼接的方式来组装请求ID。参考配置如下:

server {
    # 定义$request_trace_id的值,在1.11.0之前,我们可以使用类似的方式声明
    # 只要能确保其值出现重复的可能性尽可能的小即可。
    set $request_trace_id trace-id-$pid-$connection-$bytes_sent-$msec;
    location / {
        # ......
        # 将此trace_id传递给后端的server,通过header方式,此后我们既可以在环境中获取此header
        proxy_set_header X-Request-Id $request_trace_id;
    }
}

参数说明

  • $pid:nginx worker进程号

  • $connection:与upstream server链接id数

  • $bytes_sent:发送字节数

  • $msec:当前时间,即此变量获取的时间,包含秒、毫秒数(中间以.分割)

  2.2 方案三:基于LUA脚本实现

      利用系统/dev/urandom生成的随机UUID。参考脚本如下:

---

--- UUID

--- Created by lry.

--- DateTime: 2018/2/25 下午7.38

--- Describe: 用系统/dev/urandom生成的随机uuid

---

local template = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"

local d = io.open("/dev/urandom", "r"):read(4)

math.randomseed(os.time() + d:byte(1) + (d:byte(2) * 256) + (d:byte(3) * 65536) + (d:byte(4) * 4294967296))

local uuid = string.gsub(template, "x",

    function (c) 

        local v = (c == "x") and math.random(0, 0xf) or math.random(8, 0xb)

        return string.format("%x", v)

    end)

return uuid

  2.3 方案一:基于 $request_id 实现

      Nginx在1.11.0版本中就提供了内置变量$request_id,其原理就是生成32位的随机字符串,虽不能比拟UUID的概率,但32位的随机字符串的重复概率也是微不足道了,所以一般可视为UUID来使用即可。参考配置如下:

# Nginx代理默认会把header中参数的"_"下划线去掉,所以后台服务器后就获取不到带"_"线的参数名
underscores_in_headers on;

# 设定日志格式
log_format main \'$remote_addr - $remote_user [$time_local] "$request" \'
                \'$status $body_bytes_sent "$http_referer" $upstream_http_request_id \'
                \'"$http_user_agent" "$http_x_forwarded_for"\';

server {
    location / {
        # 如果请求头中已有该参数,则获取即可;如果没有,则使用$request_id进行填充
        set $temp_request_id $http_x_request_id;
        if ($temp_request_id = "") {
            set $temp_request_id $request_id;
        }
        # 屏蔽掉原来的请求体参数
        proxy_set_header x_request_id "";
        # 设置向后转发的请求头参数
        proxy_set_header X-Request-Id   $temp_request_id;
    }
}

3 最佳实践

    生成交易ID的方式有很多种,但希望使用者结合自身实际情况进行合理取舍,而不要盲目的追求ID的唯一性、可读性和时序性等等。

    比如,ID具有时序性虽然有一定的好处,但实际的架构根本没有去使用该时序性,则没必要花大量的精力和做出大量的开发,去实现一个有时序性的交易ID。又比如,觉得UUID可读性太差,从而花了很多成本去开发一个具有一定含义的交易ID(如前几位表示什么意思,多少位到多少位又表示什么意思之类的),开发出来后,实际架构根本没有去解读该ID的地方,则浪费了成本。

   但也不是所有人都直接使用UUID就能满足的,比如我需要考虑日志的容量,则可以考虑适当缩减ID的长度(每个ID缩减10个字符串,每笔交易就可能少几百或几千个字符串,再往上规划,还是可以减少一些日志容量的)。

    最后,如果有考虑想收集前端的日志的童鞋,建议交易ID就不要使用Long型,因为前端可能会有损失精度的问题。同时也建议使用$request_id来填充交易ID。

 

 

转自:https://my.oschina.net/yu120/blog/1790419

你可能感兴趣的:(NGINX)