微服務架構—鏈路追蹤(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。

相關文章
相關標籤/搜索