爲了解決分佈式鏈路追蹤的問題,咱們引入了實現OpenTracing的Jaeger來實現。而後咱們爲SpringBoot框架寫了一個starter以讓用戶實現近零改造接入全鏈路。
因爲公司有一個封裝了SpringBoot的內部框架,而後咱們的starter就以最新框架所使用的SpringBoot版本爲基礎進行開發。因此業務系統在接入的時候須要先升級框架,而後再引入咱們的starter才行無縫接入全鏈路。java
而後有一個業務系統就按照步驟,升級框架,引入starter就接入了全鏈路系統,而且功能測試壓力測試都已經經過了。結果咱們滿懷信心地就上線了。結果,線上nginx報大量http 400錯誤。nginx
出現故障後,業務系統的研發人員查了全部的日誌,包括elk以及機器上的日誌,都沒有發現明顯的錯誤日誌。這個就。。。web
幾番掙扎後仍是沒有在線上的日誌中找到任何蛛絲馬跡。這個就比較絕望了。更奇怪的是在測試環境中是正常的,這個就比較詭異了。tomcat
而後咱們猜測是否是以前壓力測試作得不夠啊,咱們仍是在壓測環境中再壓測一下看看會不會復現。而後正好以前這個業務系統作過壓測,那就趕忙找運維搭建一個壓測環境。結果剛搭建完就很是給面子地復現了400錯誤。框架
而後運維同窗就各類折騰,而後神奇般地在nginx中的location下加了一行配置後就行了.運維
proxy_set_header HOST $host
而後就開始各類查這個配置是啥意思。dom
這個配置的主要是在nginx在轉發htp請求的時候會加上實際的Host請求頭。如http請求是 http://abc.com/hello,那麼nginx在轉發http請求的時候會原封不動的把host請求頭(Host:abc.com)轉發給後臺服務。對於nginx而言,若是沒有配置proxysetheader HOST $host的時候會默認修改Host爲upstream的名稱。分佈式
而後咱們又在壓測環境中試了一下修改以前的版本,發現是正常的。咱們nginx的配置大致以下ide
那總結一下如今的現象:post
在nginx沒有配置proxysetheader HOST $host的時候,修改以前的版本是正常的,修改以後的版本報400錯誤
那咱們到底修改了什麼呢?
升級SpringBoot的版本
而後咱們試了下去掉全鏈路starter的引用,發現仍是400錯誤。而後再回退SpringBoot版本,發現是正常的
綜上:是由於升級了SpringBoot版本致使了該問題,又由於是http的頭部變化致使的問題,故能夠大膽猜想是由於升級了Tomcat版本致使的該問題。
tomcat版本從8.5.11升級到8.5.31
由前面的分析可知,nginx在沒有配置proxysetheader HOST $host 的時候,在轉發http請求的時候會默認把upstream的名稱做爲Host頭部的內容。
也就是說新版的tomcat在接收Host爲sc_java(帶有下劃線)的http請求報了400錯誤
下面咱們來複現一下這個錯誤:以下,本地部署兩個使用新版本tomcat的後臺服務,端口分別爲8083和8084
nginx配置以下。重點是upstream是帶下劃線的
而後使用postman請求nginx,復現400錯誤
調整nginx配置,主要修改upstream爲沒有下劃線的
而後再請求,發現是正常的
回退tomcat版本。代價較大
根因分析
咱們雖然知道了故障的緣由,也知道了怎麼修復這個故障。可是就是不知道新版的tomcat爲何出現這個問題。帶着這個疑問,咱們組的同事在SpringBoot項目的issue中搜索了下400問題,發現確實有相關的issue
[tomcat] Spring boot web always return 400 when use a domain name
雖然看上去跟咱們的問題是同樣的,都是400問題,可是具體發生的緣由是不同的。這個issue是說,若是domain name .ext 包含數字,好比 "domain.sf1m",會出現400問題。這個問題也已經在tomcat的新版本中修復了。
可是即便我使用最新的8.5.x版本的tomcat,用帶有下劃線的Host的http去請求tomcat的時候依然會報400錯誤。
也就是說,帶有下劃線的Host的http請求,tomcat認爲是有問題的
那爲何以前版本的tomcat是正常的呢?帶着這個疑問咱們來分析一下tomcat的源代碼。
因爲以前沒有看過tomcat的源代碼,因此要分析出究竟是哪一行代碼有問題是很困難的,因此我查看了下tomcat的相關的bugImprove logging in AbstractProcessor.parseHost()
下面是bug中的錯誤stack
發現對應的代碼改動以下
到這裏咱們也就知道了處理Host頭部的類就是這個HttpParser類。
而後我在本次check了下tomcat8.5.31 和8.5.11的代碼,比對了一下HttpParser以及AbstractProcessor類。對比結果以下:
發現8.5.31版本的AbstractProcessor類中多了一個parseHost的方法,而後主要解析方法是Host.parse(valueMB);
到這裏咱們就已經知道了爲何8.5.11版本的tomcat是正常的,主要是由於8.5.11版本的tomcat沒有對Host頭部進行校驗,而在8.5.31版本的tomcat增長了該校驗。
咱們來看一下tomcat源代碼的提交記錄
咱們發如今 2018/4/6增長了對host/port的校驗。
那爲何tomcat增長了這個Host的校驗呢,並且不容許使用帶有下劃線的Host呢?實際上這個是有規範的,能夠訪問下面地址
https://www.ietf.org/rfc/rfc1034.txt
好了,到這裏咱們就知道了,其實對於帶有下劃線的Host,tomcat是遵循的RFC1-1034的規範的,因此tomcat的處理是正確的。可是tomcat在處理某些其餘合法的Host的時候歷史上出現過bug,可是對於下劃線的處理一直是正確的。
因此,之後nginx在配置upstream的時候不能使用帶有下劃線的名稱,還有最好在location位置上加上proxysetheader HOST $host。