Flink 使用 Hutool 调用 Kubernetes Service 负载不均处理笔记

Flink 使用 Hutool 调用 Kubernetes Service 负载不均处理笔记

1. 处理背景与目标

1.1 处理背景

Flink 作业中通过 Hutool 调用后端接口:

post = HttpUtil.post(requstUrl, JSONObject.toJSONString(cbtMessages));

调用目标为 Kubernetes 集群内的 Service,后端由多个 Pod 副本提供服务。

运行过程中发现:

  • 后端 Pod 之间请求负载不均。
  • 部分 Pod 请求量明显偏高,其他 Pod 请求量偏低。
  • 前面增加 Nginx / Gateway 后,负载会更均衡。
  • 但代理层会增加一跳转发、资源消耗和运维复杂度。

因此,本次处理优先考虑在代码层调整连接行为,避免额外引入代理组件。

1.2 处理目标

本次处理目标如下:

  1. 核实 Hutool HttpUtil.post 默认连接行为。
  2. 分析 Flink 直连 Kubernetes Service 时后端 Pod 负载不均的原因。
  3. 通过最小化代码调整改善后端 Pod 请求分布。
  4. 验证短连接方案对成功率、响应耗时和后端资源消耗的影响。
  5. 明确异常观察项、回滚方式和后续优化方向。

2. 前置输入与依赖条件

2.1 前置输入

业务调用点:

post = HttpUtil.post(requstUrl, JSONObject.toJSONString(cbtMessages));

Hutool HttpRequest.keepAlive(boolean) 关键源码:

public HttpRequest keepAlive(boolean isKeepAlive) {
    header(Header.CONNECTION, isKeepAlive ? "Keep-Alive" : "Close");
    return this;
}

Hutool HttpRequest.isKeepAlive() 关键源码:

public boolean isKeepAlive() {
    String connection = header(Header.CONNECTION);
    if (connection == null) {
        return !httpVersion.equalsIgnoreCase(HTTP_1_0);
    }

    return false == "close".equalsIgnoreCase(connection);
}

2.2 依赖条件与关键假设

本次分析基于以下条件:

  • Flink 作业通过 Kubernetes Service 调用后端接口。
  • 后端服务存在多个 Pod 副本。
  • 当前请求未显式设置 Connection: Close
  • 调用场景为常见 HTTP/1.1 行为。
  • Kubernetes Service 通常在新 TCP 连接建立时选择后端 Pod。
  • 当前诉求是不新增 Nginx / Gateway 等代理层。

2.3 环境等级判断

当前按 dev/test 场景处理,不默认引入以下复杂方案:

  • Service Mesh。
  • 新增 Gateway。
  • 完整连接池改造。
  • 网络治理平台化改造。
  • 额外高可用或流量治理组件。

3. 详细处理步骤

3.1 核实 Hutool 默认连接行为

根据 Hutool HttpRequest.isKeepAlive() 源码:

if (connection == null) {
    return !httpVersion.equalsIgnoreCase(HTTP_1_0);
}

可以得出:

  • 如果未设置 Connection 请求头。
  • 且 HTTP 版本不是 HTTP/1.0
  • Hutool 默认认为该请求使用 Keep-Alive

结论:

HttpUtil.post(...) 在 HTTP/1.1 场景下默认倾向长连接。

3.2 分析 Kubernetes Service 负载不均原因

Kubernetes Service 通常在新 TCP 连接建立时选择后端 Pod。

简化链路:

Flink Task
  |
  v
Kubernetes Service
  |
  v
Backend Pod

当 Flink 客户端复用长连接时,后续多个 HTTP 请求会继续走同一个后端 Pod。

示例:

Flink SubTask 1 -> Service -> Pod A
Flink SubTask 2 -> Service -> Pod A
Flink SubTask 3 -> Service -> Pod B
Flink SubTask 4 -> Service -> Pod C

可能结果:

  • 少数 Pod 承担大量请求。
  • 其他 Pod 请求量偏低。
  • 后端副本未被充分利用。
  • 高负载 Pod 响应耗时升高。
  • P95 / P99 尾延迟变差。

3.3 评估代理层方案

增加 NginxGateway 后,请求链路变为:

Flink
  |
  v
Nginx / Gateway
  |
  v
Backend Pod

代理层可以在应用层重新分发请求,因此可以改善后端 Pod 负载不均。

但该方案存在额外代价:

  • 多一跳网络转发。
  • 多一层代理处理成本。
  • 增加部署组件。
  • 增加故障点。
  • 增加运维复杂度。

本次决策:

不优先增加代理层,先通过代码层最小化调整验证。

3.4 确定最小化代码调整方案

原始代码:

post = HttpUtil.post(requstUrl, JSONObject.toJSONString(cbtMessages));

建议调整为:

post = HttpRequest.post(requstUrl)
        .keepAlive(false)
        .setConnectionTimeout(3000)
        .setReadTimeout(30000)
        .body(JSONObject.toJSONString(cbtMessages))
        .execute()
        .body();

核心配置:

.keepAlive(false)

该配置会设置请求头:

Connection: Close

预期效果:

  • 每次请求完成后关闭连接。
  • 下次请求重新建立 TCP 连接。
  • 新连接重新经过 Kubernetes Service 选择后端 Pod。
  • 后端 Pod 请求分布更均衡。

3.5 验证修改效果

修改后通过监控观察:

  • 后端 Pod 请求负载明显更平均。
  • 服务实例负载分布改善。
  • 接口成功率保持稳定。
  • 响应耗时未明显恶化。
  • 趋势图中短连接后平均响应时间及尾延迟下降。

对比结论:

9 点:未改短连接
11 点:已改短连接

观察结果:

短连接后 Pod 负载更均衡
Success Rate 保持 100%
Avg Response Time 下降
P95 / P99 尾延迟下降

4. 关键决策点与异常处理机制

4.1 关键决策点

决策点一:是否增加代理层

决策:

不增加 Nginx / Gateway,优先代码层最小化调整。

原因:

  • 代理层虽然能改善负载均衡。
  • 但会增加链路复杂度和额外消耗。
  • 当前问题可通过调整连接行为解决。

决策点二:是否关闭 Keep-Alive

决策:

显式关闭 Hutool HTTP Keep-Alive。

原因:

  • 当前负载不均与长连接复用高度相关。
  • Kubernetes Service 对长连接不会按每个请求重新分发。
  • 关闭 Keep-Alive 后,Pod 负载分布明显改善。

决策点三:短连接成本是否可接受

短连接可能增加:

  • TCP 建连成本。
  • 服务端 accept 压力。
  • 系统态 CPU。
  • TIME_WAIT 数量。
  • HTTPS 场景下 TLS 握手成本。

当前观察结果:

短连接成本未造成明显负面影响,负载均衡收益大于连接开销。

4.2 异常处理机制

4.2.1 响应耗时异常

观察指标:

  • Avg Response Time
  • P95
  • P99
  • 最大响应时间

异常表现:

P95 / P99 明显升高
接口超时增加

处理方式:

回滚 keepAlive(false),恢复原长连接逻辑。

4.2.2 成功率下降

观察指标:

  • Success Rate
  • 5xx
  • timeout

异常表现:

Success Rate 下降
5xx 增加
请求超时增加

处理方式:

优先回滚代码,再检查后端线程池、连接队列和服务端日志。

4.2.3 连接状态异常

检查命令:

ss -ant | awk '{print $1}' | sort | uniq -c

重点关注:

ESTAB
TIME-WAIT
CLOSE-WAIT
SYN-RECV

异常判断:

  • TIME-WAIT 可以增加,但不应持续异常堆积。
  • CLOSE-WAIT 不应持续增加。
  • SYN-RECV 不应持续增加。

4.2.4 Flink 反压异常

观察指标:

  • Flink Backpressure。
  • Checkpoint 时长。
  • Task 处理速率。

异常表现:

Backpressure 增加
Checkpoint 变慢
Task 吞吐下降

处理方式:

回滚短连接配置,检查后端响应耗时及 Flink 同步调用阻塞情况。

5. 最终输出结果及后续优化建议

5.1 最终输出结果

本次处理结果如下:

Hutool HttpUtil.post 在 HTTP/1.1 下默认倾向 Keep-Alive。
Kubernetes Service 通常在建连时选择后端 Pod。
长连接复用会导致请求持续绑定到部分 Pod。
显式设置 keepAlive(false) 后,后端 Pod 负载明显更均衡。
短连接后成功率保持稳定,平均响应时间和尾延迟未恶化。

当前推荐保留方案:

post = HttpRequest.post(requstUrl)
        .keepAlive(false)
        .setConnectionTimeout(3000)
        .setReadTimeout(30000)
        .body(JSONObject.toJSONString(cbtMessages))
        .execute()
        .body();

5.2 验证清单

后续上线或压测时,建议持续观察:

  • 后端各 Pod 请求分布。
  • 接口 Success Rate
  • Avg Response Time
  • P95 / P99
  • 后端 Pod CPU。
  • TIME-WAIT / CLOSE-WAIT / SYN-RECV
  • Flink Backpressure。
  • Checkpoint 时长。

5.3 回滚方案

如果短连接导致明显负面影响,可快速回滚为原始写法:

post = HttpUtil.post(requstUrl, JSONObject.toJSONString(cbtMessages));

或移除:

.keepAlive(false)

回滚后重新发布 Flink 作业即可。

5.4 后续优化建议

如果完全短连接在更高 QPS 下带来明显连接成本,可考虑折中方案:

短 Keep-Alive + 连接 TTL

示例策略:

keepAliveMs = 3000
connectionTtlMs = 30000

优化目标:

  • 允许连接短时间复用,降低建连成本。
  • 避免连接长期绑定到固定 Pod。
  • 定期重建连接,让 Kubernetes Service 有机会重新选择后端 Pod。
  • 在连接成本和负载均衡之间取得平衡。

可选实现方向:

  • Apache HttpClient。
  • OkHttp。

当前建议:

先保留 keepAlive(false) 的最小化方案。
只有在短连接成本被监控明确验证为瓶颈后,再考虑引入连接池与连接 TTL。
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇