ALB Target Optimizer 实战:精确控制后端并发请求数¶
Lab 信息
- 难度: ⭐⭐ 中级
- 预估时间: 45 分钟
- 预估费用: $0.50(含清理)
- Region: us-east-1
- 最后验证: 2026-03-28
背景¶
传统的 ALB 负载均衡(Round Robin、Least Outstanding Requests 等)在面对低并发高计算密集型工作负载时存在根本性问题:ALB 由多个独立节点组成,每个节点独立做路由决策且不共享 target 负载信息。对于 LLM 推理、图片生成等一次只能处理 1-2 个请求的应用,这意味着 target 可能被过载,导致 5XX 错误和高延迟。
2025 年 11 月,AWS 推出了 ALB Target Optimizer,通过在 target 上运行一个 agent,将负载分发从"推"模式(ALB 盲目转发)变为"拉"模式(target 主动请求流量),实现精确的并发请求控制。
本文将从零开始搭建完整测试环境,验证 Target Optimizer 的并发控制能力,并对比普通 Target Group 的行为差异。
前置条件¶
- AWS 账号(需要 EC2、ELB、VPC 相关权限)
- AWS CLI v2 已配置
- 一台可以 SSH 到 EC2 实例的客户端
核心概念¶
Push vs Pull 模式¶
| 传统 ALB(Push) | Target Optimizer(Pull) | |
|---|---|---|
| 决策者 | ALB 节点独立决策 | Target agent 主动请求 |
| 负载信息 | 每个 ALB 节点只有局部视图 | Agent 掌握精确的并发数 |
| 超载处理 | 照常转发,靠应用自己处理 | 立即 503 拒绝,fail-fast |
| 适用场景 | 通用 Web 应用 | 低并发高计算密集型(LLM 推理等) |
工作原理¶
Target Optimizer 在每个 target 上部署一个 AWS 提供的 agent(Docker 容器),作为 ALB 和应用之间的 inline proxy:
- Agent 监听两个端口:data port(接收应用流量)和 control port(与 ALB 交换管理信息)
- 你配置
MAX_CONCURRENCY(0-1000,默认 1)—— target 同时能处理的最大请求数 - 当 target 的并发请求数低于 MAX_CONCURRENCY,agent 向 ALB 发信号:"我准备好了"
- ALB 只在收到信号后才转发请求 —— 这就是 "pull" 模式
Client → ALB → Agent (data port:80) → Application (port:8080)
↕ (control port:3000)
ALB Control Plane
关键限制¶
- Target control port 只能在创建 Target Group 时指定,之后不可修改
- Agent 镜像:
public.ecr.aws/aws-elb/target-optimizer/target-control-agent:latest - 启用 Target Optimizer 的 Target Group 会产生更多 LCU 用量
动手实践¶
Step 1: 创建网络环境¶
# 变量定义
REGION="us-east-1"
PROFILE="your-aws-profile"
# 创建 VPC
VPC_ID=$(aws ec2 create-vpc \
--cidr-block 10.100.0.0/16 \
--tag-specifications 'ResourceType=vpc,Tags=[{Key=Name,Value=alb-to-test-vpc}]' \
--region $REGION --profile $PROFILE \
--query 'Vpc.VpcId' --output text)
aws ec2 modify-vpc-attribute --vpc-id $VPC_ID \
--enable-dns-hostnames '{"Value":true}' \
--region $REGION --profile $PROFILE
# 创建 Internet Gateway
IGW_ID=$(aws ec2 create-internet-gateway \
--tag-specifications 'ResourceType=internet-gateway,Tags=[{Key=Name,Value=alb-to-test-igw}]' \
--region $REGION --profile $PROFILE \
--query 'InternetGateway.InternetGatewayId' --output text)
aws ec2 attach-internet-gateway \
--internet-gateway-id $IGW_ID --vpc-id $VPC_ID \
--region $REGION --profile $PROFILE
# 创建两个公有子网(不同 AZ)
SUBNET_1=$(aws ec2 create-subnet --vpc-id $VPC_ID \
--cidr-block 10.100.1.0/24 --availability-zone ${REGION}a \
--tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=alb-to-public-1a}]' \
--region $REGION --profile $PROFILE \
--query 'Subnet.SubnetId' --output text)
SUBNET_2=$(aws ec2 create-subnet --vpc-id $VPC_ID \
--cidr-block 10.100.2.0/24 --availability-zone ${REGION}b \
--tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=alb-to-public-1b}]' \
--region $REGION --profile $PROFILE \
--query 'Subnet.SubnetId' --output text)
# 启用自动分配公有 IP
aws ec2 modify-subnet-attribute --subnet-id $SUBNET_1 --map-public-ip-on-launch --region $REGION --profile $PROFILE
aws ec2 modify-subnet-attribute --subnet-id $SUBNET_2 --map-public-ip-on-launch --region $REGION --profile $PROFILE
# 创建路由表并添加默认路由
RTB_ID=$(aws ec2 create-route-table --vpc-id $VPC_ID \
--tag-specifications 'ResourceType=route-table,Tags=[{Key=Name,Value=alb-to-test-rtb}]' \
--region $REGION --profile $PROFILE \
--query 'RouteTable.RouteTableId' --output text)
aws ec2 create-route --route-table-id $RTB_ID \
--destination-cidr-block 0.0.0.0/0 --gateway-id $IGW_ID \
--region $REGION --profile $PROFILE
aws ec2 associate-route-table --route-table-id $RTB_ID --subnet-id $SUBNET_1 --region $REGION --profile $PROFILE
aws ec2 associate-route-table --route-table-id $RTB_ID --subnet-id $SUBNET_2 --region $REGION --profile $PROFILE
Step 2: 创建安全组¶
# 获取你的客户端 IP
MY_IP=$(curl -s ifconfig.me)/32
# ALB 安全组
ALB_SG=$(aws ec2 create-security-group \
--group-name alb-to-sg-alb \
--description 'ALB SG for Target Optimizer test' \
--vpc-id $VPC_ID \
--region $REGION --profile $PROFILE \
--query 'GroupId' --output text)
aws ec2 authorize-security-group-ingress \
--group-id $ALB_SG --protocol tcp --port 80 --cidr $MY_IP \
--region $REGION --profile $PROFILE
# EC2 Target 安全组
EC2_SG=$(aws ec2 create-security-group \
--group-name alb-to-sg-ec2 \
--description 'EC2 Target SG' \
--vpc-id $VPC_ID \
--region $REGION --profile $PROFILE \
--query 'GroupId' --output text)
# 允许 ALB 访问 data port(80)、control port(3000)、app port(8080)
for PORT in 80 3000 8080; do
aws ec2 authorize-security-group-ingress \
--group-id $EC2_SG --protocol tcp --port $PORT --source-group $ALB_SG \
--region $REGION --profile $PROFILE
done
# 允许 SSH(用你的 IP)
aws ec2 authorize-security-group-ingress \
--group-id $EC2_SG --protocol tcp --port 22 --cidr $MY_IP \
--region $REGION --profile $PROFILE
Step 3: 启动 EC2 实例并部署 Agent¶
创建 user data 脚本,自动安装 Docker、拉取 Agent、部署模拟应用:
cat > /tmp/userdata.sh << 'EOF'
#!/bin/bash
set -ex
yum update -y && yum install -y docker python3
systemctl start docker && systemctl enable docker
# 拉取 Target Optimizer Agent
docker pull public.ecr.aws/aws-elb/target-optimizer/target-control-agent:latest
# 创建模拟慢应用(2 秒延迟,模拟推理场景)
cat > /home/ec2-user/slow_app.py << 'PYEOF'
from http.server import HTTPServer, BaseHTTPRequestHandler
from socketserver import ThreadingMixIn
import time, socket, threading
HOSTNAME = socket.gethostname()
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
active = threading.active_count()
time.sleep(2) # 模拟推理耗时
self.send_response(200)
self.send_header("Content-Type", "text/plain")
self.end_headers()
self.wfile.write(f"OK from {HOSTNAME} - threads:{active}\n".encode())
def log_message(self, format, *args): pass
class ThreadedHTTPServer(ThreadingMixIn, HTTPServer): pass
ThreadedHTTPServer(("0.0.0.0", 8080), Handler).serve_forever()
PYEOF
nohup python3 /home/ec2-user/slow_app.py &
# 启动 Agent: MAX_CONCURRENCY=1(严格一次一个请求)
docker run -d --name target-optimizer-agent \
--restart unless-stopped --network host \
-e TARGET_CONTROL_DATA_ADDRESS=0.0.0.0:80 \
-e TARGET_CONTROL_CONTROL_ADDRESS=0.0.0.0:3000 \
-e TARGET_CONTROL_DESTINATION_ADDRESS=127.0.0.1:8080 \
-e TARGET_CONTROL_MAX_CONCURRENCY=1 \
-e RUST_LOG=info \
public.ecr.aws/aws-elb/target-optimizer/target-control-agent:latest
EOF
# 获取最新 Amazon Linux 2023 AMI
AMI=$(aws ec2 describe-images --owners amazon \
--filters 'Name=name,Values=al2023-ami-2023*-x86_64' 'Name=state,Values=available' \
--query 'sort_by(Images, &CreationDate)[-1].ImageId' --output text \
--region $REGION --profile $PROFILE)
# 启动 2 个实例(不同 AZ)
INSTANCE_1=$(aws ec2 run-instances --image-id $AMI --instance-type t3.small \
--key-name your-keypair --security-group-ids $EC2_SG --subnet-id $SUBNET_1 \
--user-data file:///tmp/userdata.sh \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=alb-to-target-1}]' \
--region $REGION --profile $PROFILE \
--query 'Instances[0].InstanceId' --output text)
INSTANCE_2=$(aws ec2 run-instances --image-id $AMI --instance-type t3.small \
--key-name your-keypair --security-group-ids $EC2_SG --subnet-id $SUBNET_2 \
--user-data file:///tmp/userdata.sh \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=alb-to-target-2}]' \
--region $REGION --profile $PROFILE \
--query 'Instances[0].InstanceId' --output text)
Step 4: 创建 ALB 和 Target Group¶
# 创建 ALB
ALB_ARN=$(aws elbv2 create-load-balancer --name alb-to-test \
--subnets $SUBNET_1 $SUBNET_2 --security-groups $ALB_SG \
--scheme internet-facing --type application \
--region $REGION --profile $PROFILE \
--query 'LoadBalancers[0].LoadBalancerArn' --output text)
ALB_DNS=$(aws elbv2 describe-load-balancers --load-balancer-arns $ALB_ARN \
--region $REGION --profile $PROFILE \
--query 'LoadBalancers[0].DNSName' --output text)
# 创建普通 Target Group(对比用)
TG_NORMAL=$(aws elbv2 create-target-group --name alb-to-tg-normal \
--protocol HTTP --port 8080 --vpc-id $VPC_ID --target-type instance \
--health-check-path / --health-check-port 8080 \
--region $REGION --profile $PROFILE \
--query 'TargetGroups[0].TargetGroupArn' --output text)
# 创建 Target Optimizer Target Group(关键:指定 --target-control-port)
TG_OPTIMIZER=$(aws elbv2 create-target-group --name alb-to-tg-optimizer \
--protocol HTTP --port 80 --vpc-id $VPC_ID --target-type instance \
--target-control-port 3000 \
--health-check-path / --health-check-port 8080 \
--region $REGION --profile $PROFILE \
--query 'TargetGroups[0].TargetGroupArn' --output text)
关键参数
--target-control-port 3000 是启用 Target Optimizer 的唯一入口。此参数只能在创建时指定,之后不可修改。
# 注册 target
aws elbv2 register-targets --target-group-arn $TG_NORMAL \
--targets Id=$INSTANCE_1,Port=8080 Id=$INSTANCE_2,Port=8080 \
--region $REGION --profile $PROFILE
aws elbv2 register-targets --target-group-arn $TG_OPTIMIZER \
--targets Id=$INSTANCE_1,Port=80 Id=$INSTANCE_2,Port=80 \
--region $REGION --profile $PROFILE
# 创建 Listener(默认路由到 Optimizer TG)
LISTENER_ARN=$(aws elbv2 create-listener --load-balancer-arn $ALB_ARN \
--protocol HTTP --port 80 \
--default-actions Type=forward,TargetGroupArn=$TG_OPTIMIZER \
--region $REGION --profile $PROFILE \
--query 'Listeners[0].ListenerArn' --output text)
# 添加规则:/normal/* 路由到普通 TG(对比测试用)
aws elbv2 create-rule --listener-arn $LISTENER_ARN --priority 10 \
--conditions Field=path-pattern,Values='/normal/*' \
--actions Type=forward,TargetGroupArn=$TG_NORMAL \
--region $REGION --profile $PROFILE
Step 5: 验证部署¶
等待约 2 分钟,确认 Target 健康:
# 检查 Target 健康状态
aws elbv2 describe-target-health --target-group-arn $TG_OPTIMIZER \
--region $REGION --profile $PROFILE \
--query 'TargetHealthDescriptions[].{Id:Target.Id,State:TargetHealth.State}' \
--output table
# 发送测试请求
curl -s -w '\nHTTP: %{http_code} | Time: %{time_total}s\n' http://$ALB_DNS/
预期输出:
测试结果¶
测试 1:并发控制验证(MAX_CONCURRENCY=1,2 个 Target)¶
同时发送 10 个并发请求到 Target Optimizer TG:
| 请求 | HTTP 状态 | 响应时间 | 说明 |
|---|---|---|---|
| #1 | 200 | 2.42s | 正常处理 |
| #2-#10 | 503 | 0.42s | 立即拒绝 |
结果:总容量 = 2(1×2 targets),10 个并发请求中只有 2 个被接受,其余 8 个在 0.42 秒内被拒绝。严格执行并发限制,零延迟 fail-fast。
测试 2:普通 TG 对比¶
同样 10 个并发请求到普通 TG:
| 请求 | HTTP 状态 | 响应时间 | 说明 |
|---|---|---|---|
| #1-#2 | 200 | ~2.4s | 第一批处理 |
| #3-#4 | 200 | ~4.4s | 排队等待 |
| #5-#6 | 200 | ~6.4s | 继续排队 |
| #7-#8 | 200 | ~8.4s | 继续排队 |
| #9-#10 | 200 | ~10.8s | 最后处理 |
结果:全部成功,但尾部延迟高达 10.8 秒。普通 TG 把所有请求都推给 target,应用内部排队。
关键对比¶
| 指标 | 普通 TG | Target Optimizer TG |
|---|---|---|
| 成功率 | 10/10 (100%) | 2/10 (20%) |
| P50 延迟 | 6.4s | 0.43s |
| P99 延迟 | 10.8s | 2.45s |
| 行为 | 全部排队,尾部延迟爆炸 | 超容量立即 503 |
解读
Target Optimizer 的 503 不是缺点,而是设计意图。对于 LLM 推理等场景:
- 客户端收到 503 后可立即重试到其他服务或等待重试
- 不会让请求在 target 上无限排队
- 配合客户端重试策略,整体成功率和延迟都更优
测试 3:异构 Target(MAX_CONCURRENCY=1 + 3)¶
将 Target-2 的 MAX_CONCURRENCY 改为 3,发送 8 个并发请求:
| 结果 | 数量 | 来源 |
|---|---|---|
| 200 成功 | 4 | Target-1: 1 个, Target-2: 3 个 |
| 503 拒绝 | 4 | — |
精确按配置分配:总容量 1+3=4,恰好 4 个成功。适用于异构 GPU 集群(如 A10G 处理 1 个请求、A100 处理 3 个)。
测试 4:MAX_CONCURRENCY=0(边界条件)¶
将 Target-1 设为 MAX_CONCURRENCY=0 后,连续 6 个请求全部发往 Target-2。
实测发现(官方未记录)
MAX_CONCURRENCY=0 可用于零停机维护:target 不接收任何新请求,等同于 graceful drain。
测试 5:Agent 未运行¶
停止 Target-1 的 Agent 后:
- 健康检查仍显示 healthy(因为检查的是应用端口 8080,不经过 Agent)
- 路由到该 target 的请求返回 502 Bad Gateway
生产环境建议
应将 health check port 设为 Agent 的 data port(如 80),而非直接检查应用端口。这样 Agent 故障时 target 会被标记为 unhealthy。
CloudWatch 指标¶
Target Optimizer 提供专属 CloudWatch 指标:
| 指标 | 我们的观测值 | 说明 |
|---|---|---|
| TargetControlRequestCount | 2-6/min | 通过 Agent 处理的请求数 |
| TargetControlActiveChannelCount | 4 | 控制通道数(2 targets × 2 ALB AZ 节点) |
| TargetControlRequestRejectCount | 4-9/min | 被拒绝的请求数(对应 503) |
踩坑记录¶
踩坑 1:控制通道重建需要时间
Agent 重启后,需要 15-30 秒重新建立与 ALB 的控制通道。在此期间可能出现 403 ("Unexpected value for x-amzn-target-control-work-id") 或 503 错误。
建议:变更 Agent 配置时使用滚动更新,不要同时重启所有 target 的 Agent。
踩坑 2:Health Check 配置
默认 health check 走应用端口(8080),不经过 Agent。Agent 故障时 target 仍显示 healthy,但请求会失败。
建议:生产环境将 health check port 设为 Agent 的 data port(80)。已查文档确认:官方未明确此行为。
踩坑 3:直接 curl Agent 端口返回错误
直接 curl localhost:80(Agent data port)会返回 "missing x-amzn-target-control-work-id"。这是正常行为——Agent 需要 ALB 提供的特殊 header 才能处理请求。
费用明细¶
| 资源 | 单价 | 用量 | 费用 |
|---|---|---|---|
| ALB | $0.0225/hr | 1 hr | $0.02 |
| EC2 t3.small × 2 | $0.0208/hr | 1 hr × 2 | $0.04 |
| LCU(Target Optimizer 额外) | ~$0.008/LCU-hr | ~2 LCU | $0.02 |
| 合计 | ~$0.08 |
成本提示
启用 Target Optimizer 的 Target Group 会产生额外 LCU 用量。对于大规模部署,建议先评估 LCU 成本影响。
清理资源¶
# 1. 删除 Listener 和 ALB
aws elbv2 delete-listener --listener-arn $LISTENER_ARN --region $REGION --profile $PROFILE
aws elbv2 delete-load-balancer --load-balancer-arn $ALB_ARN --region $REGION --profile $PROFILE
# 2. 等待 ALB 删除完成
echo "等待 ALB 删除..."
sleep 30
# 3. 删除 Target Group
aws elbv2 delete-target-group --target-group-arn $TG_NORMAL --region $REGION --profile $PROFILE
aws elbv2 delete-target-group --target-group-arn $TG_OPTIMIZER --region $REGION --profile $PROFILE
# 4. 终止 EC2 实例
aws ec2 terminate-instances --instance-ids $INSTANCE_1 $INSTANCE_2 --region $REGION --profile $PROFILE
echo "等待实例终止..."
aws ec2 wait instance-terminated --instance-ids $INSTANCE_1 $INSTANCE_2 --region $REGION --profile $PROFILE
# 5. 删除安全组(先检查 ENI 残留)
aws ec2 describe-network-interfaces --filters "Name=group-id,Values=$EC2_SG" \
--region $REGION --profile $PROFILE --query 'NetworkInterfaces[].NetworkInterfaceId'
aws ec2 describe-network-interfaces --filters "Name=group-id,Values=$ALB_SG" \
--region $REGION --profile $PROFILE --query 'NetworkInterfaces[].NetworkInterfaceId'
# 确认无残留 ENI 后删除
aws ec2 delete-security-group --group-id $EC2_SG --region $REGION --profile $PROFILE
aws ec2 delete-security-group --group-id $ALB_SG --region $REGION --profile $PROFILE
# 6. 删除子网和路由表
aws ec2 delete-subnet --subnet-id $SUBNET_1 --region $REGION --profile $PROFILE
aws ec2 delete-subnet --subnet-id $SUBNET_2 --region $REGION --profile $PROFILE
aws ec2 delete-route-table --route-table-id $RTB_ID --region $REGION --profile $PROFILE
# 7. 删除 IGW 和 VPC
aws ec2 detach-internet-gateway --internet-gateway-id $IGW_ID --vpc-id $VPC_ID --region $REGION --profile $PROFILE
aws ec2 delete-internet-gateway --internet-gateway-id $IGW_ID --region $REGION --profile $PROFILE
aws ec2 delete-vpc --vpc-id $VPC_ID --region $REGION --profile $PROFILE
# 8. 删除 Key Pair
aws ec2 delete-key-pair --key-name your-keypair --region $REGION --profile $PROFILE
务必清理
Lab 完成后请执行清理步骤,避免产生意外费用。ALB 即使无流量也按小时计费。
结论与建议¶
适用场景¶
Target Optimizer 最适合以下场景:
- LLM 推理服务:每个 GPU 实例只能同时处理 1-5 个请求
- 图片/视频生成:计算密集型,并发能力有限
- 异构集群:不同规格的 target 需要不同的并发限制
- 需要精确资源利用率的场景:避免 hotspot,消除尾部延迟
不适合的场景¶
- 高并发轻量级 Web 应用(Round Robin 就够了)
- 不需要严格并发控制的服务
- 无法部署 Docker Agent 的环境
生产环境建议¶
- Health Check 配置:将 health check port 指向 Agent 的 data port(而非直接应用端口),确保 Agent 故障时 target 被及时标记为 unhealthy
- 客户端重试:配合指数退避重试策略,处理 503 响应
- 滚动更新:变更 Agent 配置时不要同时重启所有 target
- 监控指标:关注
TargetControlRequestRejectCount,高值意味着需要扩容或调高 MAX_CONCURRENCY - MAX_CONCURRENCY=0:可用于零停机维护,graceful drain 指定 target