背??景介绍为了解决分布式链路追踪的问题,我们引入了实现OpenTracing的Jaeger。然后我们写了一个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,如果不配置proxy_set_headerHOST$host,host会默认改为upstream名称。然后我们尝试在压测环境中修改之前的版本,发现是正常的。我们的nginx配置大致如下:总结一下目前的现象:当nginx没有配置proxy_set_headerHOST$host时,修改之前的版本正常,修改后的版本报400错误。nginx配置proxy_set_headerHOST$host后,两个版本都正常。那我们修改了什么?升级SpringBoot版本,引入全链路启动器。然后我们尝试去掉fulllinkstarter的引用,发现还是400错误。然后回滚SpringBoot版本,发现正常了。总结一下:问题是升级SpringBoot版本引起的,又因为问题是http头的变化引起的,所以可以大胆猜测是升级Tomcat版本引起的。tomcat版本从8.5.11升级到8.5.31,从前面的分析可以看出故障局部复现。当nginx没有配置proxy_set_headerHOST$host时,在转发HTTP请求时会默认以上游的名字作为Host头的内容。.也就是说,新版tomcat在收到Host为sc_java(带下划线)的http请求时,报400错误。我们重现这个错误:如下,本地部署了两个使用新版tomcat的后台服务,端口分别为8083和8084。nginx配置如下。关键是上游有下划线。然后使用postman请求nginx重现400错误。调整nginx配置,主要修改upstream没有下划线。然后再次请求,发现正常了。故障处理方案回滚tomcat版本。价格很高。在线修改nginx配置:添加proxy_set_headerHOST$host或者修改upstream为不带下划线的名称根本原因分析虽然我们知道故障原因,但也知道如何修复。但是就是不知道为什么新版的tomcat会出现这个问题。带着这个疑问,我们组的同事在SpringBoot项目的issues中搜索了400个issues,发现确实有相关的issues。https://github.com/spring-projects/spring-boot/issues/13236虽然好像和我们的问题一样,都是400问题,但是具体原因不一样。这个问题是指如果域名.ext中包含数字,比如“domain.sf1m”,就会出现400问题。这个问题在较新版本的tomcat中也已修复。但是即使我使用最新的8.5.x版本的tomcat,使用带下划线的Host的http请求tomcat,还是会报400错误。也就是tomcat认为带下划线的host的http请求有问题。那么为什么以前的tomcat版本正常呢?带着这个问题,我们来分析下tomcat的源码。由于之前没有看过tomcat的源码,所以很难分析是哪一行代码有问题,所以查看了tomcat的相关bughttps://bz.apache.org/bugzilla/show_bug.cgi?id=62371下面是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源码的提交记录:发现host/port的校验是在2018/4/6添加的。根本原因是tomcat为什么加了这个Host的校验,并且不允许使用带下划线的Hosts?其实这是有规定的。具体点这个链接https://www.ietf.org/rfc/rfc1034.txtLessonslearns,这里我们知道,其实对于带下划线的Host,tomcat遵循RFC1-1034规范,所以tomcat的处理是正确的.但是tomcat历史上在处理其他一些合法的Host时有bug,但是下划线的处理一直都是正确的。所以以后nginx在配置upstream的时候不能使用带下划线的名字,***在location位置加上proxy_set_headerHOST$host
