关于websocket,真实场景踩坑经验

我的项目架构和使用场景

  • 前端:我的在线简历(服务端渲染),在线音乐(nginx客户端渲染)
  • 管理端:vue3 elementuiplus echarts
  • 服务端:gin框架gorm+mysql+redis(个人不建议gin使用mysql,因为postgresql足够优秀,性能高使用简单)

我的简历和音乐都有浏览记录提交功能,有用户访问时会用websocket推送消息给管理端的首页数据可视化,让echarts图表实时变化,前段时间做了整体优化,之前博客提过,让nginx网关扛下所有攻击,由于没有日志系统,网站被攻击,删了大多数数据,凌晨时候还有很多莫名其妙的攻击,所以做了升级

之前的websocket是直连的服务端ip,也没有做wesocket断开自动重连,偶尔会断开,要手动刷新前端页面重新连接,毕竟是自己用,麻烦点也无所谓

升级后隐藏了服务端,ws链接改为nginx代理,这时候就出现了链接后很快就断了,

1,阶段1的解决方案

修改nginx配置,延长nginx连接时长

ini 复制代码
location /ws {
    proxy_pass http://backend;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_read_timeout 300s;
    proxy_send_timeout 300s;
}

修改后确实连接的时间长了,但是考虑到性能,也不能无限长

2,第2阶段给后端加断开自动重连功能
go 复制代码
func sedMessage(scene string) {
    l := len(conns)
    for i := 0; i < len(conns); i++ { // 这个循环有问题后续说
        if index := strings.Index(scene, "jianli"); index != -1 {
            e := conns[i].WriteMessage(websocket.TextMessage, []byte("jianli"))
            if e == nil {
                return
            }
            time.AfterFunc(time.Second*3, func() {
                e2 := conns[i].WriteMessage(websocket.TextMessage, []byte("jianli"))
                if e2 != nil {
                    conns = removeIndex(conns, i)
                    i--
                }
            })
    } else if index := strings.Index(scene, "music"); index != -1 {
        e := conns[i].WriteMessage(websocket.TextMessage, []byte("music"))
        if e == nil {
            return
        }
        time.AfterFunc(time.Second*3, func() {
            e2 := conns[i].WriteMessage(websocket.TextMessage, []byte("music"))
            if e2 != nil {
                conns = removeIndex(conns, i)
                i--
            }
        })
    } else {
        logger.Info("发心跳信息")
            e := conns[i].WriteMessage(websocket.TextMessage, []byte(""))
            if e != nil {
                conns = removeIndex(conns, i)
                i--
            }
        }
    }
}

加了自动重连后前端5分钟断开一次,3秒后自动重新连上,服务端推送消息的时候第一次推送失败的话,3秒钟后再推送一次,第二次再失败就认定这个链接挂了,删除这个链接,这个方案怎么说呢!你就盯着前端5分钟断一次,重连一次,焦虑症都看发作了,而且循环发消息那里,发送失败就删除该索引的链接,肯定有问题,因为删除指定索引的元素后,数组几变短了,后面删除的数据就不是指定索引的数据。

3,第3阶段方案,服务端发心跳数据,保持nginx链接不超时,使用携程和从后往前循环发

循环从后往前循环,即使删除断开的链接也不影响前边的数据索引变化

go 复制代码
func sedMessage(scene string) {
    l := len(conns)
    logger.Info("ws链接的数组长度", zap.Int("长度为:", l))

    // 确定要发送的消息内容
    var message string
    if strings.Contains(scene, "jianli") {
            message = "jianli"
    } else if strings.Contains(scene, "music") {
            message = "music"
    } else {
            message = ""
    }

    // 遍历所有连接,每个连接独立处理
    // 从后往前循环,避免删除元素后数组索引改变
    for i := len(conns) - 1; i >= 0; i-- {
        // 保存当前连接的副本
        currentConn := conns[i]
        currentIndex := i

        // 使用协程异步发送,不阻塞其他连接
        go func(conn *websocket.Conn, idx int, msg string) {
            // 第一次发送
            err := conn.WriteMessage(websocket.TextMessage, []byte(msg))
            if err != nil {
                logger.Warn("第一次发送失败,3秒后重试", zap.Int("索引", idx), zap.Error(err))

                // 3秒后重试
                time.Sleep(3 * time.Second)
                err2 := conn.WriteMessage(websocket.TextMessage, []byte(msg))
                if err2 != nil {
                    // 重试失败,移除连接
                    logger.Error("重试发送失败,移除连接", zap.Int("索引", idx), zap.Error(err2))
                    conns = removeIndex(conns, idx)
                } else {
                    logger.Info("重试发送成功", zap.Int("索引", idx))
                }
            } else {
                logger.Info("发送成功", zap.Int("索引", idx))
            }
        }(currentConn, currentIndex, message)
    }
}
func removeIndex(slice []*websocket.Conn, index int) []*websocket.Conn {
    if index < 0 || index >= len(slice) {
        return slice
    }
    return append(slice[:index], slice[index+1:]...)
}

目前的情况,从更新后到发稿几个小时过去了,没有再断过,我给前端加了显示断开次数的数据,一直显示断开0次,我使用websocket是使用的比较基础的版本,需要自己维护链接池数组,还要自己删除已经断开的数据,就是很简陋,建议小伙伴使用封装比较完善的第三方插件,喜欢折腾的就随意啦,最后附一张图展示一下我捡漏的页面

相关推荐
To_OC2 小时前
手写 AI 编程 Agent 的命令执行工具:我被 child_process 坑出来的实战经验
后端·node.js·agent
胡萝卜术2 小时前
从API调用到手写LRU:力扣146「LRU缓存」的哈希表+双向链表终极进化之路
前端·javascript·面试
科技道人2 小时前
记录 默认置灰/禁用 app ‘Search Engine Selector‘ 的disable按钮
开发语言·前端·javascript
天疆说2 小时前
Zotero Connector 在 Edge 里找不到桌面 Zotero(Linux / Zotero 9.0.6 / Edge 150)
linux·前端·edge
李伟_Li慢慢3 小时前
02-从硬件说起WebGL
前端·three.js
逝水无殇4 小时前
C# 异常处理详解
开发语言·后端·c#
kyriewen4 小时前
看了微软几万人用AI编程的数据——效率涨24%的代价没人提
前端·ai编程·claude
2601_963771374 小时前
How to Scale Your WordPress Store Traffic in 2026
前端·php·plugin
亿元程序员5 小时前
老板说我的3D箭头游戏用来做试玩太普通了,没人想玩,让我变点花样...
前端
李伟_Li慢慢5 小时前
01-threejs架构原理-课程简介
前端·three.js