HttpServletRequest调用getHeader获取请求头参数长时间阻塞问题求助
根因排查方向
- 首先排查代码中
getIpAdrress(拼写存在错误,正确为Address)方法的实现逻辑:大部分场景下,获取IP的方法会迭代遍历所有请求头(比如X-Forwarded-For、Proxy-Client-IP等多个头),如果方法内部有不合理的同步锁、或者依赖了外部查询(比如IP库反查、DNS解析),会导致阻塞误判为getHeader方法本身的问题,建议先注释掉IP获取逻辑单独测试getHeader("language")的耗时确认根因。 - 检查Tomcat/Undertow容器的请求解析配置:Spring Boot 2.3.4默认内置Tomcat 9.0.38,如果开启了请求头的安全校验、或者配置了过大的
maxHeaderCount/maxHttpHeaderSize,容器在解析请求头时会加锁读取输入流,高并发场景下会出现阻塞。对应检查配置项:# Tomcat头配置检查 server.tomcat.max-http-header-size=8KB server.tomcat.max-header-count=100 server.tomcat.parse-body-methods=POST - 排查是否有自定义的ServletRequestWrapper实现:如果项目里有对请求做包装重写了
getHeader方法,比如统一解密请求头、或者从缓存/分布式存储中读取头信息,额外的IO操作会导致阻塞,建议打栈快照确认getHeader方法的实际调用链路:# 阻塞时执行jstack获取调用栈 jstack <进程ID> > stack.log # 搜索getHeader关键字看实际调用的实现类 - 检查JVM的JNI调用或本地方法阻塞:
getHeader底层依赖容器的本地实现,如果服务器的熵池不足,即使已做过SecureRandom优化,若容器的SSL握手/请求解析逻辑依赖/dev/random,还是会出现阻塞,验证方法:# 查看熵池大小 cat /proc/sys/kernel/random/entropy_avail # 低于200的话就是熵池不足,直接安装haveged解决 yum install haveged -y && systemctl start haveged && systemctl enable haveged # 同时确认JVM启动参数加了 -Djava.security.egd=file:/dev/./urandom
可行解决方案
- 先做根因定位:在
getIpAdrress执行前后、getHeader("language")执行前后分别加时间戳打印,确认具体阻塞的代码行,排除误判 - 如果确认是Tomcat原生
getHeader阻塞:升级Tomcat版本到9.0.60+,该版本修复了高并发下请求头解析的锁竞争问题,Spring Boot 2.3.x可以单独指定Tomcat版本:<!-- pom.xml里覆盖Tomcat版本 --> <properties> <tomcat.version>9.0.65</tomcat.version> </properties> - 替换内置容器为Undertow:Undertow的请求头解析是无锁实现,高并发场景下性能远高于Tomcat,替换方式:
<!-- 排除Tomcat依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> </exclusion> </exclusions> </dependency> <!-- 引入Undertow --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-undertow</artifactId> </dependency> - 对语言头做降级处理:如果业务允许,新增默认值逻辑,避免头解析阻塞影响所有请求:
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler){ logger.debug(".........................preHandle start ........................."); // 异步获取IP避免阻塞主链路 CompletableFuture.runAsync(() -> logger.info("================== REQUEST IP:{} ==================",this.getIpAdrress(request))); String languageCode = "zh"; // 默认值 try { // 加超时控制,超过10ms直接用默认值 languageCode = CompletableFuture.supplyAsync(() -> request.getHeader("language")) .get(10, TimeUnit.MILLISECONDS); } catch (Exception e) { logger.warn("get language header timeout, use default zh"); } LOGGER.info("================== request header languageCode:{} ================== ",languageCode); LocaleContextHolder.setLocale(new Locale(LanguageEnum.getName(Objects.requireNonNullElse(languageCode, "zh")))); return true; }
内容的提问来源于stack exchange,提问作者bob
相关产品推荐
相关产品推荐

