Golang Go语言中如何让长连接负载均衡呢?

发布于 1周前 作者 sinazl 来自 Go语言

假设我们有以下机器:

  • 客户端,10w 台
  • 负载均衡 CLB ,2 台
  • 服务器,5 台,使用 Gin web 框架

现客户端每隔 10s 向服务器上报心跳,流量路径客户端>CLB>服务器,使用的是 http 连接,keep-alive 设置的 30s 。按理来说连接经过 CLB ,服务器负责的连接是均衡的。

现在,假设故障了 4 台服务器,流量会偏移到一台服务器上,现在重启这 4 台机器,这些连接由于 keep-alive ,不会重新分布。

请问,这种问题怎么解决呢?


Golang Go语言中如何让长连接负载均衡呢?

更多关于Golang Go语言中如何让长连接负载均衡呢?的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html

33 回复

都用 http 了,http 一般都人为是无状态的,我感觉你是 http 请求了之后,不完成,一直挂着链接

更多关于Golang Go语言中如何让长连接负载均衡呢?的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


服务端通知客户端断线,让客户端重连,就均衡了

有两种方案啊,一种是客户端定时断开重连。另外还有一种是服务端主动断开,想问问,服务端主动断开连接的细节是咋样的,老哥有了解吗?我们用的是 go ,gin 框架

给个关键字就好,其他的我再去找找资料

这个通知客户端连接,咋个操作哦,那我还得记录这个连接的时间啊,比如 30s ,主动在 header 头里通知客户端断开连接吗?

我个人其实觉得不是什么大问题。大不了等那 4 台好了,把之前那 1 台重启一下,全部断开连接。问了下 GPT ,说是可以在服务内部根据情况给客户端发送关闭连接的 header 。

func main() {
router := gin.Default()
router.Use(func(c *gin.Context) {
// 可以根据具体情况决定是否关闭连接
c.Header(“Connection”, “close”)
c.Next()
})

router.GET("/heartbeat", func(c *gin.Context) {
c.JSON(200, gin.H{
“message”: “pong”,
})
})

router.Run() // 默认在 localhost:8080
}

话说应该怎么格式化代码呢?

不过你这种架构我觉得还是应该负载均衡 LB 自己处理这些连接问题,直接断掉连接等重连就行。

如果是 http1.1+keep-alive 做长连接,现有负载均衡应该是能正常工作的,keep-alive 超时 tcp 链接就会被关闭,你新发送的请求就会开启新的链接

客户端请求头上的 keepalive 好像指定的是保持连接的最小时长(这个我不太确定
服务端可以配置 keepalive 的超时时间

这 keep-alive=30 ,表示 30s 内如果没有请求就会断开,现在心跳每 10s 一次,就比较尴尬了

gin 框架没有看到有这个参数,老哥有了解吗

clb 上可配置的参数太少了,用的腾讯云

这里有个问题,什么时候发送 close

可以参考 timeout 中间件,请求头上的 keepalive 不是个强制超时时间,而是给服务端的一个暗示,表示“需要保持 xx 秒的长连接”,具体什么时候 close 取决于双方的设置,如果库或框架没有自动 close ,你直接定时 close 就行

明白你的意思了,是通过中间件,判断建连的时间和当前时间的差值,超过设定的值就 close 。(我去找找,看看能不能获取建连的时间,这一块我去了解下 Thx )

  1. 首先客户端要支持无感重连
    2. 服务端长连接程序能够感知其他节点的启动时间和负载压力,然后根据自己的负载压力决定是否要进行主动连接断开(无感的关键是通过自定义消息通知客户端,而不是简单断连)
  1. 决定要进行断连时,选择较老且空闲的连接进行断开。

#10 应该是直接可以 set 在 headers 里面的,试一下?

不要用 clb , 用 alb ,alb 负责与客户端连接,客户端请求 alb 再转发到 ecs 时关闭长连接,这样每次都是新建一个 http 连接了。

把长连接 单独一个服务 然后 rpc 调用 其他服务可以不。

讲真,我之前也探索过类似的问题,但没有得到很好的答案。对于长链接,如果是 grpc 这种,通常要通过客户端负载均衡来做。如果加了 lb 后,客户端无法感知服务端服务器变化,因此客户端负载均衡肯定行不通。此时应该由 lb 来提供对应能力。如果通过连接经过 lb 已经建立,lb 如何重新调整是个问题,我也没有想到应该如何做。因此问了下 GPT ,以下可做参考:



在这种情况下,服务器故障后重启,由于 HTTP 连接的 keep-alive 特性,现有的连接可能不会立即重新分布到所有服务器上,这可能导致剩余的服务器负载过高。以下是一些可能的解决方案:

1. 重置连接:在服务器重启后,可以配置负载均衡器( CLB )发送一个重置信号给客户端,通知它们现有的连接已经无效,需要重新建立连接。这可以通过 HTTP 响应状态码如408 Request Timeout503 Service Unavailable来实现。

2. 调整 keep-alive 超时:如果可能,可以在服务器重启前调整 HTTP 连接的 keep-alive 超时时间,使其更短,这样在服务器重启后,客户端会更快地重新建立连接。

3. 使用更智能的负载均衡策略:一些负载均衡器支持基于服务器当前负载的动态调整流量分配的策略。如果 CLB 支持这种策略,可以在服务器重启后动态调整流量分配。

4. 客户端重连机制:客户端可以在检测到服务器故障或响应超时时,自动尝试重新连接到其他健康的服务器。

5. 使用会话持久性:如果 CLB 支持会话持久性( session persistence ),可以配置它在服务器重启后将流量重新分配给所有可用的服务器,而不是只连接到一台服务器。

6. 心跳机制调整:可以调整客户端的心跳上报机制,使其在检测到服务器故障时,立即断开连接并重新连接到其他服务器。

7. 使用更高级的负载均衡技术:例如使用基于 IP 的负载均衡,这样即使 TCP 连接保持打开状态,新的请求也可以被分配到不同的服务器。

8. 监控和自动化:实现监控系统来检测服务器状态和负载情况,一旦检测到故障或负载不均衡,自动化脚本可以介入,进行流量重新分配或触发重连机制。

9. 使用 WebSocket:如果适用,可以考虑使用 WebSocket 代替 HTTP 长连接,因为 WebSocket 提供了一个全双工通信渠道,可以在服务器端更容易地管理连接状态。

10. 服务发现:使用服务发现机制,让客户端能够动态地发现可用的服务实例,并在必要时重新连接。

选择哪种解决方案取决于具体的应用场景、现有的技术栈以及可接受的复杂度。在实际应用中,可能需要结合多种策略来确保系统的高可用性和负载均衡。

做代理的时候 一般都会禁用 keepalive 头

用四层 lb 不用要七层 lb,四层 lb 配置成客户端到 LB 保持长连接既可

定时踢掉重连

上一家公司就是这样做的

也就是说关闭 keep-alive 功能,使用 clb 的回话保持,对吧。

好的,这个我也去了解一下啦,禁用和启用的优缺点

很有参考意义,感谢感谢~

明白老哥的意思的,现在的问题是服务端如何感知连接的压力,还有如何感知连接的时长,这个是目前遇到的卡点

好奇长连接为什么不用 websocket ,全双工通讯比 http 更容易操控客户端

crc 算法可以根据多台服务器 ip 实现负载均衡

https://chatgpt.com/share/1a96aad4-ddb9-4b64-9e75-4674d050c9e7

多年以前网易开源游戏引擎 pomelo 就是这样的:

https://github.com/NetEase/pomelo

客户端都会默认自动重连,服务端 kickout 客户端就能恢复均衡,每台服务器连接数也是一目了然。

大结局啦,已经找到解决方案了,祝各位升职加薪,每天开开心心!!!!
https://tencentcloudcontainerteam.github.io/tke-handbook/best-practice/scale-keepalive-service.html

业务场景不同吧。我的理解边缘机器并没有那么高的请求。这个和游戏场景还不一样。

server := &http.Server{
Handler: r,
ConnContext: func(ctx context.Context, conn net.Conn) context.Context {
// 每个连接创建一个独立的计数器实例
return context.WithValue(ctx, “counter”, new(counter))
},
}

使用 connContext 也是可以的

在Golang中实现长连接的负载均衡,通常可以采取以下几种策略:

  1. 反向代理与负载均衡器:使用Nginx、HAProxy等成熟的反向代理和负载均衡器,它们支持TCP/UDP负载均衡,非常适合处理长连接。配置时需注意会话保持(Session Stickiness)设置,以确保同一用户的连接能持续被同一后端服务器处理。

  2. Go语言内置库:利用Go的net包和sync包,可以实现自定义的TCP连接池和负载均衡逻辑。通过维护一个活动连接列表,结合简单的轮询、随机或一致性哈希算法分配连接请求,实现基本的负载均衡。

  3. 第三方库:考虑使用如golang.org/x/net/proxy等库,它们提供了更高级的代理和负载均衡功能,有助于简化开发过程。

  4. 服务网格:对于微服务架构,引入Istio等服务网格技术,可以实现对长连接的智能路由、负载均衡和故障转移,同时提供丰富的监控和治理能力。

  5. 应用层负载均衡:在应用层实现负载均衡逻辑,比如通过Redis等分布式缓存记录连接状态,根据特定算法分配连接。这种方法灵活性高,但增加了开发和维护的复杂度。

选择哪种方案取决于具体的应用场景、性能要求和团队的技术栈。在实际部署前,建议进行充分的测试,确保负载均衡策略能满足系统的稳定性和响应速度要求。

回到顶部