You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot+Jetty应用添加equalsIgnoreCase后CPU占用飙升问题

Spring Boot + Jetty 应用请求头过滤导致CPU飙升问题排查与解决

问题场景

基于Jetty容器的大型Spring Boot Web应用,原有慢响应检测过滤器运行多年稳定。近期为避免记录敏感数据,添加了忽略大小写校验以过滤authorization请求头,仅1-2个线程处理该逻辑,但CPU占用从约10%飙升至98%。性能分析显示耗时集中在java.lang.StringLatin1.regionMatchesCI和java.lang.Character.toLowerCase方法。

无问题的旧代码

Enumeration<String> headers = request.getHeaderNames();
while (headers.hasMoreElements()) {
    String name = headers.nextElement();

    Enumeration<String> headerValues = request.getHeaders(name);
    while (headerValues.hasMoreElements()) {
        r.append(name);
        r.append(": ");
        r.append(headerValues.nextElement());
        r.append(System.lineSeparator());
    }
}

存在问题的新代码

Enumeration<String> headers = request.getHeaderNames();
while (headers.hasMoreElements()) {
    String name = headers.nextElement();

    Enumeration<String> headerValues = request.getHeaders(name);
    while (headerValues.hasMoreElements()) {
        if (!name.equalsIgnoreCase("authorization")) {
            r.append(name);
            r.append(": ");
            r.append(headerValues.nextElement());
            r.append(System.lineSeparator());
        }
    }
}

原因分析

  1. 循环层级错误导致重复校验:将请求头名称的校验放在请求头值的循环内部,同一个请求头的每个值都会重复执行一次equalsIgnoreCase校验。例如某个请求头有100个值,就会重复100次相同的名称校验,完全无意义。
  2. 无前置长度过滤的低效匹配:equalsIgnoreCase会逐字符进行大小写转换和比较,即使请求头名称长度和authorization(13个字符)不符,也会执行完整的字符遍历。对于大量非目标请求头,这会产生大量无意义的计算。
  3. 逐字符转换的累积开销:equalsIgnoreCase底层依赖StringLatin1.regionMatchesCI实现,该方法会对每个字符调用Character.toLowerCase,多次重复调用时累积的性能开销非常可观。

解决方法

优化方案1:调整校验层级+前置长度过滤

将请求头名称的校验移到请求头值循环外部,同一个请求头仅校验一次;同时先判断长度,快速排除非目标请求头,避免不必要的字符比较。

Enumeration<String> headers = request.getHeaderNames();
// 缓存目标字符串和长度,避免重复创建
final String TARGET_HEADER = "authorization";
final int TARGET_LENGTH = TARGET_HEADER.length();

while (headers.hasMoreElements()) {
    String name = headers.nextElement();
    // 先判断长度,快速跳过非目标请求头
    boolean isAuthorization = false;
    if (name.length() == TARGET_LENGTH) {
        // 用regionMatches指定大小写不敏感,性能优于equalsIgnoreCase
        isAuthorization = name.regionMatches(true, 0, TARGET_HEADER, 0, TARGET_LENGTH);
    }

    Enumeration<String> headerValues = request.getHeaders(name);
    while (headerValues.hasMoreElements()) {
        if (!isAuthorization) {
            r.append(name);
            r.append(": ");
            r.append(headerValues.nextElement());
            r.append(System.lineSeparator());
        }
    }
}

优化方案2:预转小写比较(可选)

如果请求头名称的大小写差异不大,也可以将请求头名称转一次小写后用普通equals比较,性能略高于regionMatches:

Enumeration<String> headers = request.getHeaderNames();
final String TARGET_LOWER = "authorization";
final int TARGET_LENGTH = TARGET_LOWER.length();

while (headers.hasMoreElements()) {
    String name = headers.nextElement();
    boolean isAuthorization = false;
    if (name.length() == TARGET_LENGTH) {
        isAuthorization = TARGET_LOWER.equals(name.toLowerCase());
    }

    Enumeration<String> headerValues = request.getHeaders(name);
    while (headerValues.hasMoreElements()) {
        if (!isAuthorization) {
            r.append(name);
            r.append(": ");
            r.append(headerValues.nextElement());
            r.append(System.lineSeparator());
        }
    }
}

效果说明

优化后,请求头名称的校验次数从「请求头值的总数量」降低到「请求头的数量」,同时大部分非目标请求头会通过长度判断直接跳过字符比较,CPU开销会大幅回落至原有水平。

内容的提问来源于stack exchange,提问作者mjaggard

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 13:55:17