图片上传优化与7层负载均衡实践:解决高并发场景下的带宽瓶颈
写在前面
最近在做一个充电桩安装业务系统,安装师傅每天安装完充电桩后需要上传大量的现场图片(每单可能10-20张,每张2-5MB)。随着业务量增长,每天上传的图片量达到数万张,很快就遇到了两个严重问题:
- 服务器带宽占用过高:所有图片都先上传到应用服务器,再转发到OSS,导致服务器带宽被打满
- 请求超时:大文件上传占用连接时间长,导致其他正常业务请求超时
我们的技术栈是:阿里云OSS存储、ACK(容器服务Kubernetes)部署的微服务架构。这篇文章我会详细分析问题根源,介绍7层负载均衡的原理和应用场景,并给出完整的优化方案。
问题分析
当前架构的问题
让我们先看看当前的架构流程:
安装师傅手机端
↓ (上传图片,每张2-5MB)
应用服务器(ACK Pod)
↓ (占用服务器带宽,转发图片)
阿里云OSS
问题1:带宽瓶颈
假设每天有1000个安装师傅,每人上传15张图片,每张平均3MB:
- 每天总流量:1000 × 15 × 3MB = 45GB
- 如果集中在8小时内上传:45GB ÷ 8小时 = 5.625GB/小时 ≈ 12.5Mbps
- 但实际是并发上传,峰值可能达到100-200Mbps
如果应用服务器带宽只有100Mbps,很容易被打满,导致:
- 图片上传慢,用户体验差
- 其他业务请求被阻塞
- 服务器响应超时
问题2:成本浪费
当前架构下,图片流量走了两次:
- 手机端 → 应用服务器(占用服务器带宽,产生费用)
- 应用服务器 → OSS(占用服务器带宽,产生费用)
实际上,图片可以直接上传到OSS,不需要经过应用服务器。
问题3:连接占用
大文件上传通常需要较长时间(几秒到几十秒),在这期间会占用服务器的连接资源。如果并发上传量大,可能导致:
- 连接池耗尽
- 新请求无法建立连接
- 服务超时
解决方案:OSS直传 + CDN + 负载均衡
方案架构
优化后的架构应该是这样的:
安装师傅手机端
↓ (获取上传凭证)
应用服务器(只返回签名,不处理文件)
↓ (直接上传,不走服务器带宽)
阿里云OSS
↓ (CDN加速)
CDN节点(就近访问)
核心优化点
1. OSS直传(PostObject)
原理:客户端直接上传到OSS,不经过应用服务器。
实现步骤:
- 服务端生成签名(Java示例)
java
@Service
public class OssService {
@Value("${aliyun.oss.endpoint}")
private String endpoint;
@Value("${aliyun.oss.bucket}")
private String bucket;
@Value("${aliyun.oss.accessKeyId}")
private String accessKeyId;
@Value("${aliyun.oss.accessKeySecret}")
private String accessKeySecret;
/**
* 生成OSS直传签名
* @param fileName 文件名
* @param contentType 文件类型
* @return 上传凭证
*/
public OssUploadToken generateUploadToken(String fileName, String contentType) {
// 生成唯一文件名(避免覆盖)
String objectKey = "install-images/" +
LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd")) +
"/" + UUID.randomUUID().toString() +
"_" + fileName;
// 设置过期时间(1小时)
Date expiration = new Date(System.currentTimeMillis() + 3600 * 1000);
// 构建Policy
JSONObject policy = new JSONObject();
policy.put("expiration", new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
.format(expiration));
JSONArray conditions = new JSONArray();
// 限制bucket
JSONArray bucketCondition = new JSONArray();
bucketCondition.add("eq");
bucketCondition.add("$bucket");
bucketCondition.add(bucket);
conditions.add(bucketCondition);
// 限制文件大小(最大10MB)
JSONArray sizeCondition = new JSONArray();
sizeCondition.add("content-length-range");
sizeCondition.add(0);
sizeCondition.add(10485760);
conditions.add(sizeCondition);
// 限制文件类型
JSONArray contentTypeCondition = new JSONArray();
contentTypeCondition.add("starts-with");
contentTypeCondition.add("$content-type");
contentTypeCondition.add("image/");
conditions.add(contentTypeCondition);
policy.put("conditions", conditions);
// Base64编码Policy
String policyBase64 = Base64.getEncoder()
.encodeToString(policy.toJSONString().getBytes(StandardCharsets.UTF_8));
// 生成签名
Mac hmac = Mac.getInstance("HmacSHA1");
SecretKeySpec secretKeySpec = new SecretKeySpec(
accessKeySecret.getBytes(StandardCharsets.UTF_8), "HmacSHA1");
hmac.init(secretKeySpec);
byte[] signData = hmac.doFinal(policyBase64.getBytes(StandardCharsets.UTF_8));
String signature = Base64.getEncoder().encodeToString(signData);
OssUploadToken token = new OssUploadToken();
token.setPolicy(policyBase64);
token.setSignature(signature);
token.setAccessKeyId(accessKeyId);
token.setObjectKey(objectKey);
token.setHost("https://" + bucket + "." + endpoint);
token.setExpireTime(expiration.getTime());
return token;
}
}
- 客户端直接上传(前端示例)
javascript
// 1. 获取上传凭证
async function getUploadToken(fileName, fileType) {
const response = await fetch('/api/oss/upload-token', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
fileName: fileName,
contentType: fileType
})
});
return await response.json();
}
// 2. 直接上传到OSS
async function uploadToOss(file) {
// 获取上传凭证
const token = await getUploadToken(file.name, file.type);
// 构建FormData
const formData = new FormData();
formData.append('key', token.objectKey);
formData.append('policy', token.policy);
formData.append('OSSAccessKeyId', token.accessKeyId);
formData.append('signature', token.signature);
formData.append('Content-Type', file.type);
formData.append('file', file);
// 直接上传到OSS(不走应用服务器)
const response = await fetch(token.host, {
method: 'POST',
body: formData
});
if (response.ok) {
// 上传成功,返回OSS文件URL
return `${token.host}/${token.objectKey}`;
} else {
throw new Error('上传失败');
}
}
优势:
- 图片不经过应用服务器,节省服务器带宽
- 上传速度快(直接到OSS,减少一跳)
- 降低服务器负载
- 节省流量费用
2. CDN加速
原理:将OSS中的图片通过CDN分发,用户访问时从就近节点获取。
配置步骤:
-
OSS绑定CDN域名
- 在OSS控制台,为Bucket绑定CDN加速域名
- 配置HTTPS证书
- 开启CDN缓存(图片建议缓存7-30天)
-
CDN回源配置
- 回源Host:OSS的Bucket域名
- 回源协议:HTTPS
- 缓存规则:根据文件类型和路径设置
-
访问URL
- 原来:
https://bucket.oss-cn-hangzhou.aliyuncs.com/image.jpg - 现在:
https://cdn.example.com/image.jpg(通过CDN访问)
- 原来:
优势:
- 用户访问图片更快(就近节点)
- 减少OSS的访问压力
- CDN流量费用通常比OSS外网流量费用低
3. 图片压缩与格式优化
客户端压缩:
javascript
// 压缩图片(保持质量的同时减小体积)
function compressImage(file, maxWidth = 1920, quality = 0.8) {
return new Promise((resolve) => {
const reader = new FileReader();
reader.onload = (e) => {
const img = new Image();
img.onload = () => {
const canvas = document.createElement('canvas');
let width = img.width;
let height = img.height;
// 按比例缩放
if (width > maxWidth) {
height = (height * maxWidth) / width;
width = maxWidth;
}
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0, width, height);
// 转换为Blob
canvas.toBlob((blob) => {
resolve(blob);
}, 'image/jpeg', quality);
};
img.src = e.target.result;
};
reader.readAsDataURL(file);
});
}
// 使用
const compressedFile = await compressImage(originalFile);
await uploadToOss(compressedFile);
服务端处理(可选):
- 使用OSS的图片处理服务(IMG)
- 自动生成缩略图
- 格式转换(WebP等)
7层负载均衡详解
什么是7层负载均衡?
负载均衡分为4层(L4)和7层(L7):
- 4层负载均衡:基于IP和端口进行转发(TCP/UDP层)
- 7层负载均衡:基于HTTP/HTTPS协议内容进行转发(应用层)
OSI模型与负载均衡
OSI 7层模型:
┌─────────────────────────────────────┐
│ 7. 应用层 (Application) │ ← 7层负载均衡(HTTP/HTTPS)
│ 6. 表示层 (Presentation) │
│ 5. 会话层 (Session) │
├─────────────────────────────────────┤
│ 4. 传输层 (Transport) - TCP/UDP │ ← 4层负载均衡
├─────────────────────────────────────┤
│ 3. 网络层 (Network) - IP │
│ 2. 数据链路层 (Data Link) │
│ 1. 物理层 (Physical) │
└─────────────────────────────────────┘
7层负载均衡的工作原理
7层负载均衡器(如Nginx、ALB)会:
-
解析HTTP请求:读取HTTP头部信息(Host、URL、Cookie等)
-
根据规则路由:基于请求内容决定转发到哪个后端服务器
-
建立新连接:与后端服务器建立新的TCP连接
-
转发请求:将完整的HTTP请求转发给后端
-
返回响应:将后端响应返回给客户端
客户端请求
↓
7层负载均衡器(解析HTTP)
├─ 根据Host: api.example.com → 转发到API服务
├─ 根据URL: /static/ → 转发到静态资源服务
└─ 根据Cookie: session_id → 转发到特定服务器(会话保持)
↓
后端服务器
7层负载均衡的核心功能
1. 基于域名的路由(Virtual Host)
场景:多个域名指向同一个负载均衡器,需要路由到不同的后端服务。
请求:Host: api.example.com → 后端:API服务集群
请求:Host: admin.example.com → 后端:管理后台集群
请求:Host: static.example.com → 后端:静态资源服务
Nginx配置示例:
nginx
# API服务
server {
listen 80;
server_name api.example.com;
location / {
proxy_pass http://api_backend;
}
}
# 管理后台
server {
listen 80;
server_name admin.example.com;
location / {
proxy_pass http://admin_backend;
}
}
2. 基于URL路径的路由
场景:同一个域名下,不同路径路由到不同服务。
请求:/api/user → 用户服务
请求:/api/order → 订单服务
请求:/api/payment → 支付服务
请求:/static/ → 静态资源(CDN或Nginx直接返回)
Nginx配置示例:
nginx
server {
listen 80;
server_name example.com;
# 用户服务
location /api/user {
proxy_pass http://user_service;
}
# 订单服务
location /api/order {
proxy_pass http://order_service;
}
# 静态资源
location /static/ {
root /var/www/static;
expires 30d;
}
}
3. 基于HTTP头部的路由
场景:根据请求头(如User-Agent、Cookie)进行路由。
移动端请求(User-Agent包含Mobile) → 移动端专用服务
PC端请求 → PC端服务
Nginx配置示例:
nginx
server {
listen 80;
# 移动端
if ($http_user_agent ~* "mobile|android|iphone") {
proxy_pass http://mobile_backend;
break;
}
# PC端
proxy_pass http://pc_backend;
}
4. 会话保持(Session Affinity)
场景:确保同一用户的请求总是转发到同一台服务器(用于有状态服务)。
实现方式:
- Cookie方式:负载均衡器插入Cookie,记录后端服务器信息
- IP Hash:根据客户端IP的Hash值选择后端服务器
Nginx配置示例:
nginx
upstream backend {
# IP Hash方式
ip_hash;
server 192.168.1.10:8080;
server 192.168.1.11:8080;
server 192.168.1.12:8080;
}
5. 内容重写与重定向
场景:URL重写、HTTPS重定向等。
nginx
# HTTP重定向到HTTPS
server {
listen 80;
server_name example.com;
return 301 https://$server_name$request_uri;
}
# URL重写
location /old-path {
rewrite ^/old-path/(.*)$ /new-path/$1 permanent;
}
6. SSL终止(SSL Termination)
场景:在负载均衡器上处理SSL/TLS,减轻后端服务器压力。
客户端 → [HTTPS] → 负载均衡器(SSL终止) → [HTTP] → 后端服务器
优势:
- 后端服务器不需要处理SSL,性能更好
- 集中管理证书,更安全
- 可以统一做SSL优化
7层负载均衡的应用场景
场景1:微服务网关
需求:多个微服务需要统一入口,根据路径路由到不同服务。
客户端
↓
API网关(7层负载均衡)
├─ /user/* → 用户服务
├─ /order/* → 订单服务
├─ /payment/* → 支付服务
└─ /install/* → 安装服务(你的场景)
阿里云ALB配置:
- 创建应用型负载均衡(ALB)
- 配置监听规则:基于路径转发
- 后端服务器组:指向ACK的Service
场景2:动静分离
需求:静态资源(图片、CSS、JS)和动态API分开处理。
请求:/api/* → 应用服务器(动态内容)
请求:/static/* → CDN或Nginx(静态资源,直接返回,不走应用服务器)
优势:
- 静态资源不占用应用服务器资源
- 可以单独对静态资源做CDN加速
- 应用服务器专注处理业务逻辑
场景3:A/B测试与灰度发布
需求:根据用户特征(Cookie、Header)将流量分发到不同版本。
新用户 → 新版本服务(10%流量)
老用户 → 旧版本服务(90%流量)
Nginx配置示例:
nginx
# 根据Cookie中的version字段路由
map $cookie_version $backend {
default "v1_backend";
"v2" "v2_backend";
}
server {
location / {
proxy_pass http://$backend;
}
}
场景4:限流与防护
需求:在负载均衡层做限流,保护后端服务。
Nginx限流配置:
nginx
# 限制每个IP的请求速率
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
server {
location /api/ {
limit_req zone=api_limit burst=20;
proxy_pass http://backend;
}
}
场景5:请求日志与分析
需求:在负载均衡层记录访问日志,用于分析。
Nginx日志配置:
nginx
log_format detailed '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'rt=$request_time uct="$upstream_connect_time" '
'uht="$upstream_header_time" urt="$upstream_response_time"';
access_log /var/log/nginx/access.log detailed;
4层 vs 7层负载均衡对比
| 特性 | 4层负载均衡(L4) | 7层负载均衡(L7) |
|---|---|---|
| 工作层级 | TCP/UDP层 | HTTP/HTTPS层 |
| 转发依据 | IP + 端口 | HTTP头部、URL、Cookie等 |
| 性能 | 更高(不需要解析HTTP) | 相对较低(需要解析HTTP) |
| 功能 | 简单转发 | 丰富的路由规则、内容处理 |
| 适用场景 | 高并发、简单转发 | 微服务网关、复杂路由 |
| 典型产品 | 阿里云SLB(4层)、F5 | 阿里云ALB、Nginx、HAProxy |
阿里云负载均衡产品选择
1. 传统型负载均衡CLB(原SLB)
- 支持4层和7层
- 适用场景:传统应用、简单转发
- 特点:功能基础,性能稳定
2. 应用型负载均衡ALB
- 仅支持7层(HTTP/HTTPS)
- 适用场景:微服务、API网关、复杂路由
- 特点 :
- 基于内容的路由(域名、路径、Header)
- 支持Serverless后端(函数计算)
- 自动弹性伸缩
- 更丰富的监控指标
3. 网络型负载均衡NLB
- 仅支持4层(TCP/UDP)
- 适用场景:超高性能、低延迟场景
- 特点 :
- 超高性能(支持百万级并发)
- 低延迟
- 适用于游戏、金融等对性能要求极高的场景
针对我的场景的完整方案
架构设计
安装师傅手机端
↓
├─ 1. 获取上传凭证(小请求,走应用服务器)
│ /api/oss/upload-token
│
└─ 2. 直接上传图片到OSS(大文件,不走应用服务器)
https://bucket.oss-cn-hangzhou.aliyuncs.com
↓
OSS存储
↓
CDN加速(可选)
https://cdn.example.com/image.jpg
具体实施步骤
步骤1:实现OSS直传
后端服务(Spring Boot):
java
@RestController
@RequestMapping("/api/oss")
public class OssController {
@Autowired
private OssService ossService;
/**
* 获取上传凭证
*/
@PostMapping("/upload-token")
public Result<OssUploadToken> getUploadToken(
@RequestBody UploadTokenRequest request) {
OssUploadToken token = ossService.generateUploadToken(
request.getFileName(),
request.getContentType()
);
return Result.success(token);
}
/**
* 上传完成回调(可选,用于记录上传记录)
*/
@PostMapping("/upload-callback")
public Result<String> uploadCallback(@RequestBody OssCallback callback) {
// 验证OSS回调签名
if (!ossService.verifyCallback(callback)) {
return Result.error("签名验证失败");
}
// 记录上传信息到数据库
installImageService.recordUpload(
callback.getObjectKey(),
callback.getSize(),
callback.getUserId()
);
return Result.success("上传记录成功");
}
}
步骤2:配置CDN加速(可选但推荐)
-
在OSS控制台绑定CDN域名
- 进入OSS Bucket → 传输管理 → CDN加速
- 绑定自定义域名(如:cdn.example.com)
- 配置HTTPS证书
-
CDN缓存配置
- 缓存时间:图片文件缓存30天
- 回源协议:HTTPS
- 开启Gzip压缩
-
修改上传后的URL
java
@Service
public class OssService {
@Value("${aliyun.cdn.domain:}")
private String cdnDomain;
public String getImageUrl(String objectKey) {
if (StringUtils.isNotBlank(cdnDomain)) {
// 使用CDN域名
return "https://" + cdnDomain + "/" + objectKey;
} else {
// 使用OSS域名
return "https://" + bucket + "." + endpoint + "/" + objectKey;
}
}
}
步骤3:配置7层负载均衡(ALB)- 完整实施流程
场景:如果你的应用有多个服务,需要统一入口,通过7层负载均衡实现灵活的请求路由。
3.1 准备工作
在开始配置之前,需要确认以下信息:
-
ACK集群信息
- 集群ID
- VPC ID
- 可用区(至少2个)
- 子网ID
-
域名和证书
- API域名(如:api.example.com)
- SSL证书(可在阿里云SSL证书服务申请,或上传自有证书)
-
服务信息
- 各个微服务的Service名称
- 服务的健康检查路径(如:/health、/actuator/health)
3.2 创建ALB实例
步骤1:进入负载均衡控制台
- 登录阿里云控制台
- 进入「产品与服务」→「网络」→「负载均衡SLB」
- 点击「创建负载均衡」
步骤2:选择负载均衡类型
- 负载均衡类型:选择「应用型负载均衡(ALB)」
- 地域:选择与ACK集群相同的地域(如:华东1-杭州)
- 可用区:至少选择2个可用区(如:可用区H、可用区I),实现高可用
步骤3:配置基本信息
实例名称:alb-api-gateway
付费类型:按量付费(或包年包月)
规格:标准型(根据业务量选择,支持弹性扩容)
步骤4:配置网络
网络类型:专有网络(VPC)
VPC:选择ACK集群所在的VPC
交换机:在每个可用区选择至少一个交换机
- 可用区H:选择交换机1(如:vsw-xxx1)
- 可用区I:选择交换机2(如:vsw-xxx2)
重要提示:
- ALB必须与ACK集群在同一个VPC内
- 交换机必须有足够的IP地址(建议至少预留10个IP)
步骤5:创建并获取ALB地址
创建完成后,记录ALB的IP地址(如:47.xxx.xxx.xxx),后续配置DNS会用到。
3.3 配置ACK Service(后端服务)
在配置ALB之前,需要确保ACK中的服务已经正确配置。
步骤1:检查现有Service
bash
# 查看所有Service
kubectl get svc -n <namespace>
# 查看Service详情
kubectl describe svc <service-name> -n <namespace>
步骤2:创建或修改Service(如果需要)
假设你有以下微服务:
install-service:安装服务(处理安装相关API)oss-service:OSS服务(处理上传凭证等)user-service:用户服务
为每个服务创建Service:
yaml
# install-service.yaml
apiVersion: v1
kind: Service
metadata:
name: install-service
namespace: default
labels:
app: install-service
spec:
type: ClusterIP # 内部服务,不对外暴露
ports:
- port: 8080 # Service端口
targetPort: 8080 # Pod端口
protocol: TCP
selector:
app: install-service # 选择器,匹配Pod标签
---
# oss-service.yaml
apiVersion: v1
kind: Service
metadata:
name: oss-service
namespace: default
labels:
app: oss-service
spec:
type: ClusterIP
ports:
- port: 8080
targetPort: 8080
protocol: TCP
selector:
app: oss-service
应用配置:
bash
kubectl apply -f install-service.yaml
kubectl apply -f oss-service.yaml
步骤3:添加健康检查端点
确保每个服务都有健康检查接口:
java
// Spring Boot示例
@RestController
public class HealthController {
@GetMapping("/health")
public Map<String, String> health() {
Map<String, String> status = new HashMap<>();
status.put("status", "UP");
status.put("timestamp", Instant.now().toString());
return status;
}
// 或者使用Spring Boot Actuator
// 在application.yml中配置:
// management:
// endpoints:
// web:
// exposure:
// include: health,info
}
3.4 配置ALB监听(HTTPS)
步骤1:添加监听
- 在ALB实例详情页,点击「监听」标签
- 点击「添加监听」
- 选择协议和端口:
- 协议:HTTPS
- 端口:443
步骤2:配置SSL证书
-
证书来源:
- 如果已有证书:选择「上传证书」,上传证书文件(.pem格式)和私钥(.key格式)
- 如果没有证书:可以在「SSL证书」服务中申请免费证书(DV证书)
-
证书配置:
证书:选择或上传SSL证书 TLS版本:TLSv1.2、TLSv1.3(建议都勾选)
步骤3:配置监听高级设置
调度算法:加权轮询(WRR)或加权最小连接数(WLC)
- WRR:按权重轮询,适合请求处理时间相近的场景
- WLC:优先分配给连接数少的后端,适合请求处理时间差异大的场景
连接超时时间:60秒(根据实际情况调整)
请求超时时间:60秒
Gzip压缩:开启(可以压缩响应内容,节省带宽)
3.5 配置后端服务器组
步骤1:创建服务器组
- 在监听配置页面,点击「后端服务器组」
- 点击「创建服务器组」
步骤2:配置服务器组基本信息
服务器组名称:backend-install-service
服务器组类型:IP类型 或 服务器类型
- IP类型:直接指定Pod IP(不推荐,Pod重启IP会变)
- 服务器类型:选择ACK集群的节点(需要安装ALB Ingress Controller)
- 函数计算类型:如果使用Serverless后端
协议:HTTP(ALB到后端使用HTTP,SSL在ALB终止)
端口:8080(Service的端口)
重要 :对于ACK场景,推荐使用ALB Ingress Controller,这样可以直接关联Kubernetes Service。
步骤3:安装ALB Ingress Controller(推荐方式)
这是连接ALB和ACK的最佳实践:
bash
# 1. 添加ALB Ingress Controller的Helm仓库
helm repo add alibaba https://aliacs-app-catalog.oss-cn-hangzhou.aliyuncs.com/charts-incubator/
helm repo update
# 2. 安装ALB Ingress Controller
helm install alibaba/alb-ingress-controller \
--namespace kube-system \
--set regionId=cn-hangzhou \
--set clusterId=<你的ACK集群ID> \
--set vpcId=<你的VPC ID> \
--set albId=<你的ALB实例ID>
步骤4:使用Ingress配置路由(推荐)
创建Ingress资源,自动关联ALB:
yaml
# ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: api-ingress
namespace: default
annotations:
# 指定使用ALB
alb.ingress.kubernetes.io/load-balancer-id: <ALB实例ID>
# 监听配置
alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":443}]'
# SSL证书
alb.ingress.kubernetes.io/certificate-id: <证书ID>
# 健康检查
alb.ingress.kubernetes.io/healthcheck-path: /health
alb.ingress.kubernetes.io/healthcheck-protocol: HTTP
alb.ingress.kubernetes.io/healthcheck-interval-seconds: '5'
alb.ingress.kubernetes.io/healthcheck-timeout-seconds: '3'
alb.ingress.kubernetes.io/healthy-threshold-count: '2'
alb.ingress.kubernetes.io/unhealthy-threshold-count: '3'
spec:
ingressClassName: alb
rules:
# 安装服务路由
- host: api.example.com
http:
paths:
- path: /api/install
pathType: Prefix
backend:
service:
name: install-service
port:
number: 8080
# OSS服务路由
- path: /api/oss
pathType: Prefix
backend:
service:
name: oss-service
port:
number: 8080
# 其他API路由
- path: /api
pathType: Prefix
backend:
service:
name: api-gateway-service
port:
number: 8080
应用Ingress配置:
bash
kubectl apply -f ingress.yaml
步骤5:手动配置后端服务器组(如果不使用Ingress)
如果不想使用Ingress,可以手动配置:
-
创建服务器组
- 服务器组名称:
backend-install-service - 协议:HTTP
- 端口:8080
- 服务器组名称:
-
添加后端服务器
- 方式1:添加ECS实例(ACK节点)
- 选择ACK集群的节点ECS
- 端口:8080(Service的NodePort,如果使用NodePort类型)
- 方式2:添加IP地址(Pod IP,不推荐)
- 方式1:添加ECS实例(ACK节点)
-
配置健康检查
检查协议:HTTP 检查路径:/health 检查端口:8080 检查间隔:5秒 超时时间:3秒 健康阈值:2次(连续2次成功认为健康) 不健康阈值:3次(连续3次失败认为不健康) HTTP状态码:200(健康检查成功的状态码) -
配置权重
- 如果后端服务器性能不同,可以设置不同权重
- 权重越高,分配的流量越多
3.6 配置转发规则
步骤1:添加转发规则
在监听配置页面,添加转发规则:
规则1:安装服务路由
规则名称:rule-install-service
域名:api.example.com(或留空,匹配所有域名)
路径:/api/install/*
- 匹配方式:前缀匹配
- 说明:所有以 /api/install 开头的请求
转发至:backend-install-service(后端服务器组)
优先级:1(数字越小优先级越高)
规则2:OSS服务路由
规则名称:rule-oss-service
域名:api.example.com
路径:/api/oss/*
转发至:backend-oss-service
优先级:2
规则3:默认路由
规则名称:rule-default
域名:api.example.com
路径:/*
转发至:backend-default-service
优先级:100(最低优先级,作为默认规则)
规则匹配顺序:
- 先匹配域名(如果配置了)
- 再匹配路径(按优先级从高到低)
- 匹配到第一个规则后停止
步骤2:配置高级路由规则(可选)
如果需要更复杂的路由,可以使用基于Header的路由:
条件:HTTP Header
Header名称:X-Service-Version
Header值:v2
匹配方式:等于
转发至:backend-service-v2
3.7 配置DNS解析
步骤1:获取ALB的IP地址
在ALB实例详情页,记录「服务地址」(公网IP或内网IP)。
步骤2:配置DNS解析
-
进入「云解析DNS」控制台
-
找到你的域名(example.com)
-
添加解析记录:
记录类型:A记录
主机记录:api(完整域名为 api.example.com)
记录值:ALB的服务地址(IP)
TTL:600秒(10分钟)
步骤3:验证DNS解析
bash
# 使用dig命令验证
dig api.example.com
# 或使用nslookup
nslookup api.example.com
# 应该返回ALB的IP地址
3.8 配置会话保持(如需要)
如果你的服务是有状态的,需要配置会话保持:
步骤1:在服务器组中配置
会话保持:开启
会话保持方式:基于Cookie
- 植入Cookie:ALB自动插入Cookie
- 重写Cookie:重写应用返回的Cookie
Cookie名称:ALB_SESSION(可自定义)
Cookie超时时间:86400秒(24小时)
步骤2:在代码中处理(如果使用重写Cookie)
java
// Spring Boot示例
@RestController
public class ApiController {
@GetMapping("/api/test")
public Result test(HttpServletResponse response) {
// 如果使用重写Cookie方式,需要在响应中设置Cookie
Cookie cookie = new Cookie("ALB_SESSION", sessionId);
cookie.setPath("/");
cookie.setMaxAge(86400);
response.addCookie(cookie);
return Result.success();
}
}
3.9 配置访问控制(安全加固)
步骤1:配置访问控制列表(ACL)
-
在ALB实例中,进入「访问控制」
-
创建访问控制策略组
-
添加IP白名单或黑名单:
策略组名称:api-whitelist
类型:白名单(只允许列表中的IP访问)
IP地址:
- 192.168.1.0/24(内网段)
- 10.0.0.0/8(VPC网段) -
在监听中关联访问控制策略组
步骤2:配置WAF(Web应用防火墙)
如果需要更高级的安全防护:
- 在ALB监听中开启「WAF防护」
- 配置WAF规则:
- 防SQL注入
- 防XSS攻击
- 防CC攻击
- 自定义规则
3.10 测试验证
步骤1:测试健康检查
bash
# 直接访问后端服务(通过Service)
kubectl port-forward svc/install-service 8080:8080 -n default
# 在另一个终端测试
curl http://localhost:8080/health
# 应该返回:
# {"status":"UP","timestamp":"2024-01-01T12:00:00Z"}
步骤2:测试ALB路由
bash
# 测试安装服务路由
curl -H "Host: api.example.com" https://<ALB-IP>/api/install/health
# 测试OSS服务路由
curl -H "Host: api.example.com" https://<ALB-IP>/api/oss/upload-token
# 测试默认路由
curl -H "Host: api.example.com" https://<ALB-IP>/api/health
步骤3:测试HTTPS
bash
# 测试HTTPS(忽略证书验证,仅测试连通性)
curl -k https://api.example.com/api/health
# 正常测试(验证证书)
curl https://api.example.com/api/health
步骤4:测试负载均衡
bash
# 连续发送多个请求,观察是否分发到不同后端
for i in {1..10}; do
curl https://api.example.com/api/health
echo ""
done
# 查看后端服务器的访问日志,确认请求被分发
3.11 监控与告警配置
步骤1:查看监控指标
在ALB控制台,可以查看:
- 请求数:QPS、总请求数
- 流量:入流量、出流量
- 延迟:平均响应时间、P99延迟
- 错误率:4xx、5xx错误率
- 后端健康:健康检查成功率
步骤2:配置告警
-
进入「云监控」控制台
-
创建告警规则:
指标:ALB请求错误率
阈值:> 5%(错误率超过5%告警)
通知方式:邮件、短信、钉钉指标:ALB后端健康检查失败
阈值:连续失败3次
通知方式:邮件、短信
3.12 故障排查
问题1:502 Bad Gateway
可能原因:
- 后端服务不可用
- 健康检查失败
- 网络不通
排查步骤:
bash
# 1. 检查后端服务状态
kubectl get pods -n default
kubectl logs <pod-name> -n default
# 2. 检查Service
kubectl get svc -n default
kubectl describe svc <service-name> -n default
# 3. 检查健康检查端点
kubectl port-forward svc/<service-name> 8080:8080
curl http://localhost:8080/health
# 4. 检查ALB后端服务器组状态
# 在ALB控制台查看后端服务器健康状态
问题2:路由不生效
可能原因:
- 转发规则配置错误
- 路径匹配不正确
- 优先级设置错误
排查步骤:
bash
# 1. 检查Ingress配置
kubectl get ingress -n default
kubectl describe ingress <ingress-name> -n default
# 2. 检查ALB转发规则
# 在ALB控制台查看监听规则,确认路径和优先级
# 3. 测试不同路径
curl https://api.example.com/api/install/test
curl https://api.example.com/api/oss/test
问题3:SSL证书问题
可能原因:
- 证书过期
- 证书域名不匹配
- 证书格式错误
排查步骤:
bash
# 1. 检查证书有效期
openssl x509 -in certificate.pem -noout -dates
# 2. 检查证书域名
openssl x509 -in certificate.pem -noout -text | grep DNS
# 3. 测试SSL连接
openssl s_client -connect api.example.com:443 -servername api.example.com
问题4:DNS解析问题
排查步骤:
bash
# 1. 检查DNS解析
dig api.example.com
nslookup api.example.com
# 2. 检查本地DNS缓存
# Windows: ipconfig /flushdns
# Linux: systemd-resolve --flush-caches
# 3. 直接使用IP测试
curl -H "Host: api.example.com" https://<ALB-IP>/api/health
3.13 性能优化建议
1. 开启HTTP/2
在ALB监听中开启HTTP/2支持,可以提升性能:
协议版本:HTTP/2(自动协商,支持HTTP/1.1和HTTP/2)
2. 配置连接复用
在应用代码中配置HTTP客户端连接池:
java
// Spring Boot RestTemplate配置
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate() {
HttpComponentsClientHttpRequestFactory factory =
new HttpComponentsClientHttpRequestFactory();
factory.setConnectionRequestTimeout(5000);
factory.setConnectTimeout(5000);
factory.setReadTimeout(10000);
// 配置连接池
PoolingHttpClientConnectionManager connectionManager =
new PoolingHttpClientConnectionManager();
connectionManager.setMaxTotal(200);
connectionManager.setDefaultMaxPerRoute(50);
CloseableHttpClient httpClient = HttpClients.custom()
.setConnectionManager(connectionManager)
.build();
factory.setHttpClient(httpClient);
return new RestTemplate(factory);
}
}
3. 配置超时时间
根据业务需求合理设置超时:
连接超时:5秒(建立连接的最大时间)
请求超时:30秒(处理请求的最大时间,根据接口响应时间调整)
4. 开启Gzip压缩
在ALB监听中开启Gzip压缩,可以减少传输数据量:
Gzip压缩:开启
压缩类型:text/html, text/css, application/json, application/javascript
3.14 完整配置示例总结
架构图:
客户端
↓ HTTPS (api.example.com)
DNS解析
↓
ALB (47.xxx.xxx.xxx:443)
├─ 规则1: /api/install/* → backend-install-service
├─ 规则2: /api/oss/* → backend-oss-service
└─ 规则3: /api/* → backend-default-service
↓ HTTP (内网)
ACK Service (ClusterIP)
↓
Pod (应用服务)
配置清单:
- ✅ ALB实例创建(VPC、可用区、规格)
- ✅ SSL证书配置(HTTPS监听)
- ✅ ACK Service配置(ClusterIP类型)
- ✅ Ingress配置(自动关联ALB)或手动配置后端服务器组
- ✅ 转发规则配置(域名、路径、优先级)
- ✅ 健康检查配置(路径、间隔、阈值)
- ✅ DNS解析配置(A记录指向ALB)
- ✅ 监控告警配置(错误率、健康检查)
- ✅ 安全配置(ACL、WAF,可选)
验证清单:
- DNS解析正确(dig/nslookup)
- HTTPS访问正常(curl测试)
- 路由规则生效(不同路径访问不同服务)
- 健康检查正常(后端服务健康状态为正常)
- 负载均衡生效(请求分发到多个后端)
- 监控指标正常(请求数、错误率等)
步骤4:客户端优化
图片压缩与上传:
javascript
class ImageUploader {
constructor() {
this.maxWidth = 1920;
this.quality = 0.8;
}
// 压缩图片
async compressImage(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = (e) => {
const img = new Image();
img.onload = () => {
const canvas = document.createElement('canvas');
let { width, height } = img;
if (width > this.maxWidth) {
height = (height * this.maxWidth) / width;
width = this.maxWidth;
}
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0, width, height);
canvas.toBlob(
(blob) => resolve(blob),
'image/jpeg',
this.quality
);
};
img.onerror = reject;
img.src = e.target.result;
};
reader.onerror = reject;
reader.readAsDataURL(file);
});
}
// 上传到OSS
async upload(file) {
try {
// 1. 压缩图片
const compressedFile = await this.compressImage(file);
// 2. 获取上传凭证(小请求)
const token = await this.getUploadToken(
file.name,
compressedFile.type
);
// 3. 直接上传到OSS(大文件,不走服务器)
const formData = new FormData();
formData.append('key', token.objectKey);
formData.append('policy', token.policy);
formData.append('OSSAccessKeyId', token.accessKeyId);
formData.append('signature', token.signature);
formData.append('Content-Type', compressedFile.type);
formData.append('file', compressedFile);
const response = await fetch(token.host, {
method: 'POST',
body: formData
});
if (!response.ok) {
throw new Error('上传失败');
}
// 4. 返回CDN URL(如果配置了CDN)
return this.getImageUrl(token.objectKey);
} catch (error) {
console.error('上传失败:', error);
throw error;
}
}
async getUploadToken(fileName, contentType) {
const response = await fetch('/api/oss/upload-token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ fileName, contentType })
});
return await response.json();
}
getImageUrl(objectKey) {
// 如果配置了CDN,返回CDN URL
const cdnDomain = 'cdn.example.com';
return `https://${cdnDomain}/${objectKey}`;
}
}
// 使用
const uploader = new ImageUploader();
const imageUrl = await uploader.upload(file);
成本优化分析
优化前(当前方案)
每天流量:45GB
- 手机端 → 应用服务器:45GB(服务器带宽费用)
- 应用服务器 → OSS:45GB(服务器带宽费用)
- 用户访问图片:假设50GB(OSS外网流量费用)
总成本:服务器带宽 + OSS流量费用
优化后(OSS直传 + CDN)
每天流量:45GB
- 手机端 → OSS:45GB(OSS内网流量,如果同区域几乎免费)
- 用户访问图片:50GB(CDN流量,通常比OSS外网流量便宜30-50%)
节省:
1. 服务器带宽费用:45GB × 2 = 90GB(完全节省)
2. OSS外网流量费用:50GB(替换为更便宜的CDN流量)
预计节省成本:50-70%(取决于具体定价)
重要澄清:OSS流量费用详解
常见误解:通过SLB上传OSS,就不需要购买OSS流量资源包了?
答案 :❌ 不是的。SLB和OSS流量费用是两个不同的概念。
OSS流量费用构成
OSS的流量费用主要分为以下几种:
-
外网上行流量(上传) :通常是免费的
- 从客户端上传文件到OSS
- 无论是否经过SLB,上传流量都是免费的
- 例如:安装师傅上传图片到OSS,这部分流量免费
-
外网下行流量(下载) :需要付费
- 从OSS下载文件到客户端(用户查看图片)
- 这是产生OSS流量费用的主要来源
- 例如:用户访问、查看图片时产生的流量
-
内网流量 :几乎免费
- 同地域内(如都在华东1-杭州)的流量
- 如果OSS和应用服务器在同一地域,内网流量几乎免费
-
跨区域流量 :需要付费
- 不同地域之间的流量传输
SLB的作用
重要理解 :SLB(负载均衡)不会减少OSS的流量费用。
错误理解:
客户端 → SLB → OSS(以为这样就不收OSS流量费了)
实际情况:
客户端 → OSS(直接上传,不走SLB)
客户端 ← OSS(直接下载,不走SLB)
SLB的真正作用:
- SLB用于API请求的负载均衡(如获取上传凭证的接口)
- 不用于文件上传/下载的转发
- 文件上传/下载是客户端直接与OSS通信
正确的架构理解
场景1:获取上传凭证(小请求,走SLB)
客户端 → SLB → 应用服务器 → 返回签名
(这个请求很小,几KB,不涉及文件传输)
场景2:上传图片(大文件,直接到OSS)
客户端 → OSS(直接上传,不走SLB,不走应用服务器)
(上传流量免费)
场景3:查看图片(大文件,直接到OSS或CDN)
客户端 ← OSS/CDN(直接下载,不走SLB)
(下载流量收费,这是需要流量资源包的地方)
什么时候需要OSS流量资源包?
需要购买流量资源包的情况:
-
用户访问图片量大
- 每天有大量用户查看图片
- 例如:每天50GB的图片访问量
- 这种情况下,购买流量资源包比按量付费更便宜
-
外网下载流量
- 用户通过外网访问OSS中的图片
- 跨地域访问OSS
不需要购买流量资源包的情况:
-
只有上传,没有下载
- 如果图片只上传,用户不访问(很少见)
- 上传流量本身是免费的
-
使用CDN加速
- 如果图片通过CDN访问,流量走CDN,不走OSS外网
- CDN流量费用通常比OSS外网流量便宜30-50%
成本优化策略
策略1:使用CDN替代OSS外网流量
方案A(不推荐):
用户访问图片 → OSS外网 → 用户
流量费用:按OSS外网流量计费(较贵)
方案B(推荐):
用户访问图片 → CDN → OSS(回源)→ CDN → 用户
流量费用:按CDN流量计费(较便宜,通常便宜30-50%)
配置CDN后:
- 首次访问:CDN从OSS回源(产生OSS内网流量,几乎免费)
- 后续访问:CDN直接返回(不产生OSS流量)
- 用户流量:走CDN,按CDN流量计费
策略2:购买流量资源包(如果必须用OSS外网)
如果必须使用OSS外网流量(不使用CDN),可以:
-
预估流量:根据历史数据预估每月流量
-
购买资源包:在阿里云购买OSS流量资源包
-
成本对比 :
按量付费:0.5元/GB(示例价格,实际以阿里云为准) 资源包:100GB = 40元(0.4元/GB,更便宜)
策略3:内网访问(同地域)
如果应用服务器和OSS在同一地域:
应用服务器(华东1) → OSS(华东1)→ 内网流量(几乎免费)
实际成本对比示例
假设场景:
- 每天上传:45GB(免费)
- 每天用户访问:50GB
方案对比:
| 方案 | 上传流量费用 | 下载流量费用 | 总费用(月) |
|---|---|---|---|
| 方案1:直接OSS外网 | 免费 | 50GB/天 × 30天 × 0.5元/GB = 750元 | 750元 |
| 方案2:OSS + CDN | 免费 | 50GB/天 × 30天 × 0.3元/GB = 450元 | 450元 |
| 方案3:OSS流量资源包 | 免费 | 1500GB资源包 ≈ 600元 | 600元 |
推荐方案:方案2(OSS + CDN),既省钱又提升访问速度。
总结
-
SLB不减少OSS流量费用
- SLB用于API请求负载均衡
- 文件上传/下载不走SLB
-
上传流量免费
- 无论是否经过SLB,上传到OSS都是免费的
-
下载流量需要付费
- 用户访问图片产生的下载流量需要付费
- 这是需要流量资源包的地方
-
最佳实践
- 使用CDN加速,降低流量成本
- 如果必须用OSS外网,考虑购买流量资源包
- 尽量使用内网访问(同地域)
性能优化建议
1. 批量上传优化
如果一次需要上传多张图片,可以:
javascript
// 并发上传(但限制并发数,避免过载)
async function uploadMultiple(files, maxConcurrent = 3) {
const results = [];
const queue = [...files];
while (queue.length > 0) {
const batch = queue.splice(0, maxConcurrent);
const batchResults = await Promise.all(
batch.map(file => uploader.upload(file))
);
results.push(...batchResults);
}
return results;
}
2. 断点续传(大文件场景)
如果单张图片很大(>10MB),可以考虑断点续传:
- 使用OSS的分片上传(Multipart Upload)
- 前端记录上传进度
- 失败后可以续传
3. 图片预处理
- 服务端:使用OSS的图片处理服务自动生成缩略图
- 客户端:上传前压缩,减少上传时间
4. 监控与告警
java
// 添加上传监控
@Component
public class UploadMonitor {
@Autowired
private MeterRegistry meterRegistry;
public void recordUpload(String userId, long size, long duration) {
// 记录上传次数
meterRegistry.counter("upload.count", "user", userId).increment();
// 记录上传大小
meterRegistry.summary("upload.size", "user", userId).record(size);
// 记录上传耗时
meterRegistry.timer("upload.duration", "user", userId)
.record(duration, TimeUnit.MILLISECONDS);
}
}
总结
核心优化点
- OSS直传:图片直接上传到OSS,不经过应用服务器,节省服务器带宽
- CDN加速:图片通过CDN分发,提升访问速度,降低流量成本
- 7层负载均衡:用于API网关,统一入口,灵活路由
- 客户端压缩:上传前压缩图片,减少上传时间和流量
7层负载均衡适用场景
- ✅ 微服务网关(根据路径路由到不同服务)
- ✅ 动静分离(静态资源直接返回,不走应用服务器)
- ✅ 多域名统一入口(根据Host路由)
- ✅ A/B测试、灰度发布(根据Header/Cookie路由)
- ✅ SSL终止(在负载均衡层处理HTTPS)
不适用场景
- ❌ 纯TCP/UDP协议(需要用4层负载均衡)
- ❌ 超高性能、低延迟场景(4层性能更好)
- ❌ 简单的IP+端口转发(4层更简单高效)
最终效果
- ✅ 服务器带宽占用:从100-200Mbps降低到几乎为0(只有获取凭证的小请求)
- ✅ 上传速度:提升30-50%(减少一跳,直接到OSS)
- ✅ 成本:节省50-70%(服务器带宽费用 + CDN替代OSS外网流量)
- ✅ 用户体验:上传更快,访问图片更快(CDN加速)
- ✅ 系统稳定性:服务器不再被大文件上传阻塞,其他业务正常
希望这篇文章能帮你解决图片上传的带宽瓶颈问题。如果还有其他问题,欢迎继续讨论!