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

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

1 背景介绍

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

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

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

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

2 Nginx

        在大部分的微服务架构中,Nginx基本是经常使用的接入层设施,因此咱们但愿请求ID从Nginx层进行校验填充,而且打印在Nginx的请求日志中。这里只提供三种方式来实现Nginx层的交易ID生产方式。dom

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

参数说明ui

  • $pid:nginx worker进程号lua

  • $connection:与upstream server连接id数spa

  • $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来使用便可。参考配置以下:

# Nnginx代理默认会把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。

相关文章
相关标签/搜索