本文完成Spring Websocket Chatroom遗留的问题,即使用网关做负载均衡。
使用Ingress Nginx完成基本的负载均衡
Nginx Ingress Controller 在较新版本(本文用的v1.12.1)中,默认已经内置了对 WebSocket 的基本支持:
# Pass the extracted client certificate to the backend
# Allow websocket connections
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
因此之前的一些烦人的注解不再需要了:
nginx.ingress.kubernetes.io/configuration-snippet: |
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
以下注解是推荐配置的:
| Annotation | 作用 | 为什么推荐设置? | 示例 |
|---|---|---|---|
nginx.ingress.kubernetes.io/upstream-keepalive-timeout: "3600" | 控制 Nginx 与后端保持长连接的超时时间 | 避免过早断开空闲连接 | "3600"(s) |
nginx.ingress.kubernetes.io/upstream-keepalive-connections: "100" | 控制 Nginx 与后端保持的最大空闲连接数 | 避免频繁重建连接 | "100" |
nginx.ingress.kubernetes.io/proxy-read-timeout: "86400" | Nginx 等待后端数据的超时(长连接必备) | 避免 WebSocket 因长时间无数据被断开 | "1200"(s) |
nginx.ingress.kubernetes.io/proxy-send-timeout: "86400" | Nginx 等待转发客户端数据的超时 | 避免客户端发数据后后端未及时接收导致断开 | "1200" |
最终的Ingress:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
kubernets.io/ingress.class: nginx
nginx.ingress.kubernetes.io/upstream-keepalive-timeout: "1200"
nginx.ingress.kubernetes.io/upstream-keepalive-connections: "100"
nginx.ingress.kubernetes.io/proxy-read-timeout: "1200"
nginx.ingress.kubernetes.io/proxy-send-timeout: "1200"
name: chat
namespace: chat
spec:
ingressClassName: nginx
rules:
- host: "homedev"
http:
paths:
- path: /
pathType: ImplementationSpecific
backend:
service:
name: chat
port:
number: 8000
Nginx Ingress Controller 是怎么工作的
Kubernetes 用Ingress来定义从集群外部进来的流量(主要是HTTP 和 HTTPS 流量)该怎么转发给后端的服务。它允许通过声明式的方式,配置域名(Host)、路径(Path)、后端服务(Service)、TLS 证书重定向、重写等规则。但Ingress 本身并不提供任何网络功能,它仅仅是一个规则定义。Ingress Controller负责监听、解析Ingress,并将规则应用到实际的代理软件(如 Nginx、Envoy 等)。
Nginx Ingress Controller 是对 nginx 的一次包装。它的底层能力是在以 nginx 为基础的。它的工作过程:
- 通过 Kubernetes 的
Informer机制实时监听集群中 Ingress、Service、Endpoints 的创建 / 更新 / 删除事件(如用户新增 Ingress 规则、后端 Pod 扩容 / 缩容) - Controller 读取 Ingress 资源中的核心配置,包括:Host 域名:如 example.com(匹配外部请求的 Host 头),Path 路径:如 /api(匹配请求的 URL 路径);Backend 后端:关联的 Service 名称和端口(如 service-user:8080)
- Controller 根据 Ingress 关联的 Service,查询对应的 Endpoints 资源,拿到健康的后端 Pod IP:Port 列表
- Controller 将上述规则转化为
nginx.conf配置。比如将Pod IP:Port列表写入upstream(最新版本的不会直接写在nginx.conf文件中,而是用了lua) - Nginx 对 upstream 集群中的 Pod 执行预设的负载均衡策略,默认策略为 轮询(round-robin)
WebSocket 负载均衡问题
WebSocket 的长连接特性可能导致 nginx 负载均衡失效:
-
连接绑定:一旦 WebSocket 握手完成,客户端会与特定后端服务器保持长连接,所有后续数据都通过该连接传输,Nginx 无法将同一客户端的 WebSocket 流量分配到其他服务器。
-
负载倾斜:如果部分客户端的 WebSocket 连接持续时间很长(如在线用户),而新客户端不断连接,会导致早期服务器累积大量长连接,新服务器连接数较少,出现 “老服务器负载高、新服务器空闲” 的不均衡状态。
-
会话粘性的副作用:若为了解决 WebSocket 连接稳定性启用ip_hash(强制同一 IP 的请求到同一服务器),可能加剧不均衡(如某一 IP 段用户集中)。
如何避免 WebSocket 的负载不均衡?
解决核心是让长连接在后端服务器间合理分布,同时保证连接稳定性,可选的方案如下:
- 合理设置连接超时,自动释放闲置连接
nginx.ingress.kubernetes.io/proxy-read-timeout: "300"
nginx.ingress.kubernetes.io/proxy-send-timeout: "300"
- 采用 “最少连接数” 负载均衡策略
nginx.ingress.kubernetes.io/load-balance: "least_conn"
- 动态扩缩容
- 动态扩缩容:自动扩容新节点分担负载,新客户端的连接会分配到新节点。可以采取的方式(1)根据CPU动态扩缩,配置HPA (2)提高响应速度,通过KEDA采集QPS、连接数进行扩缩。需要接入Prometheus或OT的客户端暴露Metrics。
- 拆分 HTTP 服务器和 WebSocket 服务器
HTTP 服务器处理普通的 HTTP 请求,提供短连接api。WebSocket 服务器独立出来处理长连接实时会话,方便进一步监控、优化。二者暴露不同的Metrics、应用不同的扩缩容策略:
- 对于 HTTP:暴露QPS,根据QPS扩缩容,对于Spring应用有Spring Boot Actuator开箱即用。
- 对于 WebSocket:Spring Boot Actuator不区分 HTTP 请求和 WebSocket 连接,不直接告诉你“当前有多少个 WebSocket 活跃连接”,更重要的是它没法解码、不会统计消息速率、错误、心跳等应用层指标。最好的方式是自己接Prometheus或OT的客户端自己暴露。
- 拆分长连接与短连接的负载均衡
将 WebSocket 长连接和普通 HTTP 短连接的代理分开,针对不同类型的流量配置独立的负载均衡策略:
- 普通 HTTP 请求:使用
round_robin或least_conn,保持灵活均衡 - WebSocket 连接:使用
least_conn+ 超时释放,专门优化长连接分布
接下来
nginx ingress controller的使用不便性和架构局限性