V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
dampler
V2EX  ›  Go 编程语言

如何让长连接负责均衡呢?

  •  1
     
  •   dampler · 138 天前 · 2501 次点击
    这是一个创建于 138 天前的主题,其中的信息可能已经有所发展或是发生改变。

    假设我们有以下机器:

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

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

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

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

    32 条回复    2024-07-14 10:11:23 +08:00
    cz5424
        1
    cz5424  
       138 天前
    都用 http 了,http 一般都人为是无状态的,我感觉你是 http 请求了之后,不完成,一直挂着链接
    cz5424
        2
    cz5424  
       138 天前
    服务端通知客户端断线,让客户端重连,就均衡了
    dampler
        3
    dampler  
    OP
       138 天前
    @cz5424 有两种方案啊,一种是客户端定时断开重连。另外还有一种是服务端主动断开,想问问,服务端主动断开连接的细节是咋样的,老哥有了解吗?我们用的是 go ,gin 框架
    dampler
        4
    dampler  
    OP
       138 天前
    @cz5424 给个关键字就好,其他的我再去找找资料
    dampler
        5
    dampler  
    OP
       138 天前
    @cz5424 这个通知客户端连接,咋个操作哦,那我还得记录这个连接的时间啊,比如 30s ,主动在 header 头里通知客户端断开连接吗?
    fds
        6
    fds  
       138 天前
    我个人其实觉得不是什么大问题。大不了等那 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
    }

    话说应该怎么格式化代码呢?
    fds
        7
    fds  
       138 天前
    不过你这种架构我觉得还是应该负载均衡 LB 自己处理这些连接问题,直接断掉连接等重连就行。
    csys
        8
    csys  
       138 天前
    如果是 http1.1+keep-alive 做长连接,现有负载均衡应该是能正常工作的,keep-alive 超时 tcp 链接就会被关闭,你新发送的请求就会开启新的链接

    客户端请求头上的 keepalive 好像指定的是保持连接的最小时长(这个我不太确定
    服务端可以配置 keepalive 的超时时间
    dampler
        9
    dampler  
    OP
       138 天前
    @csys 这 keep-alive=30 ,表示 30s 内如果没有请求就会断开,现在心跳每 10s 一次,就比较尴尬了
    dampler
        10
    dampler  
    OP
       138 天前
    @csys gin 框架没有看到有这个参数,老哥有了解吗
    dampler
        11
    dampler  
    OP
       138 天前
    @fds clb 上可配置的参数太少了,用的腾讯云
    dampler
        12
    dampler  
    OP
       138 天前
    @fds 这里有个问题,什么时候发送 close
    csys
        13
    csys  
       138 天前 via Android
    @dampler 可以参考 timeout 中间件,请求头上的 keepalive 不是个强制超时时间,而是给服务端的一个暗示,表示“需要保持 xx 秒的长连接”,具体什么时候 close 取决于双方的设置,如果库或框架没有自动 close ,你直接定时 close 就行
    dampler
        14
    dampler  
    OP
       138 天前
    @csys 明白你的意思了,是通过中间件,判断建连的时间和当前时间的差值,超过设定的值就 close 。(我去找找,看看能不能获取建连的时间,这一块我去了解下 Thx )
    lasuar
        15
    lasuar  
       138 天前
    1. 首先客户端要支持无感重连
    2. 服务端长连接程序能够感知其他节点的启动时间和负载压力,然后根据自己的负载压力决定是否要进行主动连接断开(无感的关键是通过自定义消息通知客户端,而不是简单断连)
    lasuar
        16
    lasuar  
       138 天前
    3. 决定要进行断连时,选择较老且**空闲**的连接进行断开。
    gongguowei02
        17
    gongguowei02  
       138 天前
    @dampler #10 应该是直接可以 set 在 headers 里面的,试一下?
    coderxy
        18
    coderxy  
       138 天前
    不要用 clb , 用 alb ,alb 负责与客户端连接,客户端请求 alb 再转发到 ecs 时关闭长连接,这样每次都是新建一个 http 连接了。
    xxs55
        19
    xxs55  
       138 天前
    把长连接 单独一个服务 然后 rpc 调用 其他服务可以不。
    mingyuewandao
        20
    mingyuewandao  
       138 天前
    讲真,我之前也探索过类似的问题,但没有得到很好的答案。对于长链接,如果是 grpc 这种,通常要通过客户端负载均衡来做。如果加了 lb 后,客户端无法感知服务端服务器变化,因此客户端负载均衡肯定行不通。此时应该由 lb 来提供对应能力。如果通过连接经过 lb 已经建立,lb 如何重新调整是个问题,我也没有想到应该如何做。因此问了下 GPT ,以下可做参考:



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

    1. **重置连接**:在服务器重启后,可以配置负载均衡器( CLB )发送一个重置信号给客户端,通知它们现有的连接已经无效,需要重新建立连接。这可以通过 HTTP 响应状态码如`408 Request Timeout`或`503 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. **服务发现**:使用服务发现机制,让客户端能够动态地发现可用的服务实例,并在必要时重新连接。

    选择哪种解决方案取决于具体的应用场景、现有的技术栈以及可接受的复杂度。在实际应用中,可能需要结合多种策略来确保系统的高可用性和负载均衡。
    kxg3030
        21
    kxg3030  
       137 天前
    做代理的时候 一般都会禁用 keepalive 头
    qinze113
        22
    qinze113  
       137 天前
    用四层 lb 不用要七层 lb,四层 lb 配置成客户端到 LB 保持长连接既可
    zhoudaiyu
        23
    zhoudaiyu  
       137 天前
    定时踢掉重连
    dampler
        24
    dampler  
    OP
       137 天前
    @xxs55 上一家公司就是这样做的
    dampler
        25
    dampler  
    OP
       137 天前
    @qinze113 也就是说关闭 keep-alive 功能,使用 clb 的回话保持,对吧。
    dampler
        26
    dampler  
    OP
       137 天前
    @raviscioniemeche 好的,这个我也去了解一下啦,禁用和启用的优缺点
    dampler
        27
    dampler  
    OP
       137 天前
    @mingyuewandao 很有参考意义,感谢感谢~
    dampler
        28
    dampler  
    OP
       137 天前
    @lasuar 明白老哥的意思的,现在的问题是服务端如何感知连接的压力,还有如何感知连接的时长,这个是目前遇到的卡点
    photon006
        29
    photon006  
       137 天前
    好奇长连接为什么不用 websocket ,全双工通讯比 http 更容易操控客户端

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

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

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

    https://github.com/NetEase/pomelo

    客户端都会默认自动重连,服务端 kickout 客户端就能恢复均衡,每台服务器连接数也是一目了然。
    dampler
        30
    dampler  
    OP
       137 天前
    大结局啦,已经找到解决方案了,祝各位升职加薪,每天开开心心!!!!
    https://tencentcloudcontainerteam.github.io/tke-handbook/best-practice/scale-keepalive-service.html
    dampler
        31
    dampler  
    OP
       136 天前
    @photon006 业务场景不同吧。我的理解边缘机器并没有那么高的请求。这个和游戏场景还不一样。
    dampler
        32
    dampler  
    OP
       136 天前
    server := &http.Server{
    Handler: r,
    ConnContext: func(ctx context.Context, conn net.Conn) context.Context {
    // 每个连接创建一个独立的计数器实例
    return context.WithValue(ctx, "counter", new(counter))
    },
    }

    使用 connContext 也是可以的
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5475 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 33ms · UTC 07:21 · PVG 15:21 · LAX 23:21 · JFK 02:21
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.