在大部分的微服務架構中,Nginx基本上是常用的存取層設施,所以我們希望請求ID從Nginx層進行校驗填充,並且列印在Nginx的請求日誌中。
閱讀提示:本文不提供鏈路追蹤的完整解決方案,只提供Nginx層對鏈路追蹤的支援方案!
微服務的誕生,解決了傳統單體應用的許多問題,如可維護性差、擴展性差和靈活性差等問題(粗粒比較)。微服務架構雖好,但同時也帶來了許多挑戰,其中 故障排查 就是其需要解決的挑戰之一。那麼,如何在很多個應用程式和實例中找到故障發生的根源呢?
基於以上需求,我們可以將每一筆交易在各個應用中產生的所有日誌,進行集中式收集與展示(但前提是你得有:日誌中心)。這樣很快就可以看出交易是在哪一步出來的故障。如果做得好,還可以直接進行二次開發與資料分析,將收集的日誌和出現的故障進行分析後,用圖形介面很直觀的進行展示。
例如,可以展示出微服務呼叫的拓樸圖,使用顏色進行區分故障(如常用紅:表示異常、綠:正常、黃:警告)。接著可以將常出現的故障或異常進行分類後做出友好型的展示(說白了就不用直接上堆疊),如:NullPointerException:則界面直接友好型的提示哪一行代碼拋了空指針,輸入參數是什麼……(這不是這篇的重點哈,廢話不多說了,後續有機會再詳細介紹)。
要做整個微服務架構的鏈路追踪,肯定是希望從交易進入微服務中心的第一個點就開始有一個全局的交易ID來關聯所有日誌(鏈路追踪,這麼一個ID肯定是不夠的,但這裡只介紹這個哈)。當然最理想的肯定是希望把前端的日誌(如操作日誌、資料流等)也規劃進行。
在大部分的微服務架構中,Nginx基本上是常用的存取層設施,所以我們希望請求ID從Nginx層進行校驗填充,並且印在Nginx的請求日誌中。這裡只提供三種方式來實現Nginx層的交易ID生產方式。
在1.11.0之前的版本,我們可以採用拼接的方式來組裝請求ID。參考配置如下:
<span class="hljs-section">server</span> { <span class="hljs-comment"># 定义$request_trace_id的值,在1.11.0之前,我们可以使用类似的方式声明</span> <span class="hljs-comment"># 只要能确保其值出现重复的可能性尽可能的小即可。 </span> <span class="hljs-attribute">set</span> <span class="hljs-variable">$request_trace_id</span> trace-id-<span class="hljs-variable">$pid</span>-<span class="hljs-variable">$connection</span>-<span class="hljs-variable">$bytes_sent</span>-<span class="hljs-variable">$msec</span>; <span class="hljs-attribute">location</span> / { <span class="hljs-comment"># ……</span> <span class="hljs-comment"># 将此trace_id传递给后端的server,通过header方式,此后我们既可以在环境中获取此header </span> <span class="hljs-attribute">proxy_set_header</span> X-Request-Id <span class="hljs-variable">$request_trace_id</span>; } }
參數說明:
利用系統/dev/urandom 產生的隨機 UUID 。參考腳本如下:
<span class="hljs-comment">---</span> <span class="hljs-comment">--- UUID</span> <span class="hljs-comment">--- Created by lry.</span> <span class="hljs-comment">--- DateTime: 2018/2/25 下午7:38</span> <span class="hljs-comment">--- Describe: 用系统/dev/urandom生成的随机uuid</span> <span class="hljs-comment">---</span> <span class="hljs-keyword">local</span> template =<span class="hljs-string">"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"</span> <span class="hljs-keyword">local</span> d = <span class="hljs-built_in">io</span>.open(<span class="hljs-string">"/dev/urandom"</span>, <span class="hljs-string">"r"</span>):read(<span class="hljs-number">4</span>) <span class="hljs-built_in">math</span>.randomseed(<span class="hljs-built_in">os</span>.time() + d:byte(<span class="hljs-number">1</span>) + (d:byte(<span class="hljs-number">2</span>) * <span class="hljs-number">256</span>) + (d:byte(<span class="hljs-number">3</span>) * <span class="hljs-number">65536</span>) + (d:byte(<span class="hljs-number">4</span>) * <span class="hljs-number">4294967296</span>)) <span class="hljs-keyword">local</span> uuid=<span class="hljs-built_in">string</span>.gsub(template, <span class="hljs-string">"x"</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-params">(c)</span></span> <span class="hljs-keyword">local</span> v = (c == <span class="hljs-string">"x"</span>) <span class="hljs-keyword">and</span> <span class="hljs-built_in">math</span>.random(<span class="hljs-number">0</span>, <span class="hljs-number">0xf</span>) <span class="hljs-keyword">or</span> <span class="hljs-built_in">math</span>.random(<span class="hljs-number">8</span>, <span class="hljs-number">0xb</span>) <span class="hljs-keyword">return</span> <span class="hljs-built_in">string</span>.format(<span class="hljs-string">"%x"</span>, v) <span class="hljs-keyword">end</span>) <span class="hljs-keyword">return</span> uuid
Nginx在 1.11.0版本中就提供了内置变量 $request_id ,其原理就是生成32位的随机字符串,虽不能比拟UUID的概率,但32位的随机字符串的重复概率也是微不足道了,所以一般可视为UUID来使用即可。参考配置如下:
<span class="hljs-comment"># Nnginx代理默认会把header中参数的 "_" 下划线去掉,所以后台服务器后就获取不到带"_"线的参数名</span> <span class="hljs-attribute">underscores_<span class="hljs-keyword">in</span>_headers</span> <span class="hljs-literal">on</span>; <span class="hljs-comment"># 设定日志格式</span> <span class="hljs-attribute"><span class="hljs-built_in">log</span>_format</span> main \<span class="hljs-string">'<span class="hljs-variable">$remote_addr</span> - <span class="hljs-variable">$remote_user</span> [<span class="hljs-variable">$time_local</span>] "<span class="hljs-variable">$request</span>" \' \'<span class="hljs-variable">$status</span> <span class="hljs-variable">$body_bytes_sent</span> "<span class="hljs-variable">$http_referer</span>" <span class="hljs-variable">$upstream_http_request_id</span> \' \'"<span class="hljs-variable">$http_user_agent</span>" "<span class="hljs-variable">$http_x_forwarded_for</span>"\'; server { location / { <span class="hljs-comment"># 如果请求头中已有该参数,则获取即可;如果没有,则使用</span><span class="hljs-variable"><span class="hljs-comment">$request_id</span></span><span class="hljs-comment">进行填充</span> <span class="hljs-built_in">set</span> <span class="hljs-variable">$temp_request_id</span> <span class="hljs-variable">$http_x_request_id</span>; <span class="hljs-keyword">if</span> (<span class="hljs-variable">$temp_request_id</span> = "") { <span class="hljs-built_in">set</span> <span class="hljs-variable">$temp_request_id</span> <span class="hljs-variable">$request_id</span>; } <span class="hljs-comment"># 屏蔽掉原来的请求头参数</span> proxy_<span class="hljs-built_in">set</span>_header x_request_id ""; <span class="hljs-comment"># 设置向后转发的请求头参数</span> proxy_<span class="hljs-built_in">set</span>_header X-Request-Id <span class="hljs-variable">$temp_request_id</span>; } } </span>
生成交易ID的方式有很多种,但希望使用者结合自身实际情况进行合理取舍,而不要盲目的追求ID的唯一性、可读性和时序性等等。
比如,ID具有时序性虽然有一定的好处,但实际的架构根本没有去使用该时序性,则没必要花大量的精力和做出大量的开发,去实现一个有时序性的交易ID。又比如,觉得UUID可读性太差,从而花了很多成本去开发一个具有一定含义的交易ID(如前几位表示什么意思,多少位到多少位又表示什么意思之类的),开发出来后,实际架构根本没有去解读该ID的地方,则浪费了成本。
但也不是所有人都直接使用UUID就能满足的,比如我需要考虑日志的容量,则可以考虑适当缩减ID的长度(每个ID缩减10个字符串,每笔交易就可能少几百或几千个字符串,再往上规划,还是可以减少一些日志容量的)。
最后,如果有考虑想收集前端的日志的童鞋,建议交易ID就不要使用Long型,因为前端可能会有损失精度的问题。同时也建议使用 $request_id 来填充交易ID。
以上是微服務架構之Nginx連結追蹤的詳細內容。更多資訊請關注PHP中文網其他相關文章!