为了解决分布式链路跟踪的问题,我们引入了Jaeger,它实现了OpenTracing。然后我们写了一个SpringBoot框架的starter,让用户实现近乎零的改造,接入全链路。由于公司内部有封装SpringBoot的框架,所以我们的starter是基于最新框架使用的SpringBoot版本开发的。所以,业务系统对接时,需要先升级框架,再引入我们的starter,无缝接入全链路。故障描述然后如果有业务系统,按照步骤升级框架,引入starter连接全链路系统,功能测试压力测试通过。于是,我们就放心上线了。结果网上的nginx报了大量的http400错误。故障排除后,业务系统研发人员检查了所有的日志,包括elk和机器上的日志,没有发现明显的错误日志。就是这个。..几经挣扎,还是没有在在线日志中找到任何蛛丝马迹。这更没希望了。更奇怪的是,在测试环境下是正常的,这个就比较诡异了。那么我们就在想,是不是之前的压力测试做的还不够。我们在压测环境再做一次压测,看看会不会重现。然后正好这个业务系统之前做过压测,所以赶紧让运维搭建压测环境。结果刚build完就重现了400错误,非常有面子。然后运维同学各种折腾,然后奇迹般的在nginx的location下加了一行配置。proxy_set_headerHOST$host然后开始查看这个配置是什么意思。这个配置的主要目的是nginx在转发http请求的时候会加上实际的Host请求头。如果http请求是http://abc.com/hello,那么nginx在转发http请求时会将host请求头(Host:abc.com)原封不动的转发给后台服务。对于nginx,如果不配置proxysetheaderHOST$host,host会默认改为upstream名称。然后我们尝试在压测环境中修改之前的版本,发现是正常的。我们的nginx配置大致如下,总结一下目前的现象:当nginx没有配置proxysetheaderHOST$host时,修改之前的版本正常,修改后的版本报400错误。nginx配置proxysetheaderHOST$host后,两个版本都正常,那么我们修改了什么?升级SpringBoot的版本引入全链接启动器,然后我们尝试去掉全链接启动器的引用,发现还是400错误。然后回滚SpringBoot版本,发现正常了。总结:问题是升级SpringBoot版本引起的,又因为问题是http头的变化引起的,所以可以大胆猜测问题是升级Tomcat版本引起的。问题。tomcat版本从8.5.11升级到8.5.31,本地复现故障。从前面的分析我们可以看出,当nginx没有配置proxysetheaderHOST$host时,在转发HTTP请求时会默认将upstreamname作为Host头的内容。.也就是说,新版本的tomcat在接收到主机为sc_java(下划线)的http请求时,报400错误。我们重现这个错误:如下,本地部署两个使用新版tomcat的后台服务,端口为8083和8084nginx配置如下。重点是upstream有下划线,然后用postman请求nginx,重现400错误并调整nginx配置,主要修改upstream为无下划线再请求,发现是正常的故障修复解决方案,roll返回tomcat版本。在线修改nginx配置开销很大:添加proxysetheaderHOST$host或者修改upstream为没有下划线的名字根本原因分析虽然我们知道故障原因,但也知道如何修复。但是就是不知道为什么新版的tomcat会出现这个问题。带着这个问题,我们组的同事在SpringBoot项目的issues中搜索了400个issue,发现确实有相关issue[tomcat]springbootwebalwaysreturn400whenuseadomainname虽然看起来我们的问题是一样的,都是400的问题,只是具体原因不一样。这个问题是指如果域名.ext中包含数字,比如“domain.sf1m”,就会出现400问题。这个问题在较新版本的tomcat中也已修复。但是即使我使用最新的8.5.x版本的tomcat,使用带下划线的Host的http请求tomcat,还是会报400错误。也就是说,tomcat认为下划线的Host的http请求有问题,那么为什么之前的tomcat版本是正常的呢?带着这个疑问,我们来分析一下tomcat的源码。由于之前没有看过tomcat的源码,所以很难分析是哪一行代码有问题,所以查看了tomcat的AbstractProcessor.parseHost()中的相关bugImprovelogging。下面是bug中的错误栈,发现对应的代码改动如下。这里我们也知道处理Host头的类是HttpParser类。然后这次查看了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的校验,并且不允许使用带下划线的Hosts呢?其实这个是有规定的,可以访问以下地址https://www.ietf.org/rfc/rfc1034.txt-1034规范,所以tomcat的处理是正确的。但是tomcat历史上在处理其他一些合法的Host时有bug,但是下划线的处理一直都是正确的。所以以后nginx在配置upstream的时候不能使用带下划线的名字,最好在location中加上proxysetheaderHOST$host。
