当前位置: 首页 > news >正文

Golang 搭建 WebSocket 应用(七) - 性能、可用性

在前面的文章中,提到过非功能性需求决定了架构。
今天我们再来考虑一下另外两个非功能性需求:性能和可用性。

前言

关于性能,其实并不是只有我们这个消息推送系统独有的问题。
对于所有的开发者而言,都多多少少会处理过性能相关的问题,比如后端为了减少数据库查询提高并发引入的缓存中间件,如 redis
又或者如前端一次性渲染大量数据的时候,如果让用户体验更加流畅等。

本文会针对 WebSocket 应用场景下去思考一些可能出现的性能问题以及可行的解决方案。

性能

对于性能,有几个可能导致性能问题的地方:

连接数

连接数过多会导致占用的内存过多,因为对于每一个连接,我们都有两个协程,一个读协程,一个写协程;
同时我们的 Client 结构体中的 send 是一个缓冲通道,它的缓冲区大小也直接影响最终占用的内存大小。

比如,我们目前的创建 Client 实例的代码是下面这样的:

client := &Client{hub: hub, conn: conn, send: make(chan Log, 256), uid: uid}

我们在这里直接为 send 分配了 256 的大小,如果 Log 结构体比较大的话,
它占用的内存就会比较大了(因为最终占用内存 = 连接数 * sizeof(Log) * 256)。

在实际中,我们一般没有那么多等待发送的消息,这个其实可以设置为一个非常小的值,比如 16;
设置为一个小的值的负面影响是,当 send 塞满了 16 条 Log 的时候,发送消息的接口会阻塞:

func send(hub *Hub, w http.ResponseWriter, r *http.Request) {// ... 其他代码// 如果 send 满了,下面这一行会阻塞client.send <- messageLoghub.pending.Add(int64(1))
}

所以这个数值可能需要根据实际场景来选择一个更加合适的值。

代码本身的问题

比如,我们的代码中其实有一个很常见的性能问题,就是 string[]byte 之间直接强转:

// writePump 方法里面将 string 转 []byte
if err := c.conn.WriteMessage(websocket.TextMessage, []byte(messageLog.Message)); err != nil {return
}

至于原因,可以去看看此前的一篇文章《深入理解 go unsafe》 的最后一小节,
简单来说,就是这个转换会产生内存分配,而内存分配会导致一定的性能损耗。而通过 unsafe 就可以实现无损的转换。

除了这个,其他地方也没啥太大的问题了,因为到目前为止,我们的代码还是非常的简单的。

互斥锁

为了保证程序的并发安全,我们在 Hub 中加了一个 sync.Mutex,也就是互斥锁。
在代码中,被 sync.MutexLock 保护的代码,在同一时刻只能有一个协程可以执行。

// 推送消息的接口
func send(hub *Hub, w http.ResponseWriter, r *http.Request) {// ... 其他代码// 从 hub 中获取 clienthub.Lock()client, ok := hub.userClients[uid]hub.Unlock()// ... 其他代码
}

对于上面这种只读的操作,也就是没有对 map 进行写操作,我们依然使用了 sync.MutexLock() 来锁定临界区。
这里存在的问题是,其实我们的 hub.userClients 是支持并发读的,只是不能同时读写而已。

所以我们可以考虑将 sync.Mutex 替换为 sync.RWMutex,这样就可以实现并发读了:

// 推送消息的接口
func send(hub *Hub, w http.ResponseWriter, r *http.Request) {// ... 其他代码// 从 hub 中获取 clienthub.RLock() // 读锁client, ok := hub.userClients[uid]hub.RUnlock() // 释放读锁// ... 其他代码
}

这样做的好处是,当有多个并发的 send 请求的时候,这些并发的 send 请求并不会相互阻塞;
而使用 sync.Mutex 的时候,并发的 send 请求是会相互阻塞的,也就是会导致 send 变成串行的,这样性能无疑会很差。

除此之外,我们在 Hubrun 方法中也使用了 sync.Mutex

case client := <-h.register:h.Lock()h.clients[client] = trueh.userClients[client.uid] = clienth.Unlock()

也就是说,我们将 Client 注册到 Hub 的操作也是串行的。
对于这种场景,其实也有一种解决方法就是分段 map
也就是将 clientsuserClients 这两个 map 拆分为多个 map
然后对于每一个 map 都有一个对应的 sync.Mutex 互斥锁来保证其读写的安全。

但如果要这样做,单单分段还不够,我们的 registerunregister 还是只有一个,对于这个问题,
我们可能需要将 registerunregister 也分段,最后在 run 方法里面起多个协程来进行处理。
这个实现起来就很复杂了。

其他

由于我们的 Hub 中还有 MessageLogger、错误处理、认证等功能,
在实际中,如果我们有将其替换为自己的实现,可能还得考虑自己的实现中可能存在的性能问题:

type Hub struct {messageLogger MessageLoggererrorHandler  Handlerauthenticator Authenticator
}

可用性

这里主要讨论的是集群部署的情况下,应用存在的一些的问题以及可行的解决方案。关于具体部署上的细节不讨论。

要实现高可用的话,我们就得加机器了,毕竟如果只有一台服务器的话,一旦它宕机了,服务就完全挂了。

由于我们的 WebSocket 应用维持着跟客户端的连接,在单机的时候,客户端连接、推送消息都是在一台机器上的。
这种情况下并没有什么问题,因为推送消息的时候,都可以根据 uid 来找到对应的 WebSocket 连接,从而给客户端推送消息。

而在多台机器的情况下,我们的客户端可能跟不同的服务器产生连接,这个时候一个比较关键的问题是:
如何根据 uid 找到对应的 WebSocket 连接所在的机器?
如果我们推送消息的请求到达的机器上并没有消息关联的 WebSocket 连接,那么我们的消息就无法推送给客户端了。

对于这个问题,一个可行的解决方案是,将 uid 和服务器建立起关联,比如,在用户登录的时候,
就给用户返回一个 WebSocket 服务器的地址,客户端拿到这个地址之后,跟这个服务器建立起 WebSocket 连接,
然后其他应用推送消息的时候,也根据同样的算法将推送消息的请求发送到这个 WebSocket 服务器即可。

总结

最后,再简单回顾一下本文的内容:

  • 具体来说,我们的系统中会有下面几个可能的地方会导致产生性能问题:
    • 连接数:一个连接会有两个协程,另外每一个 Client 结构体也会需要一定的缓冲区来缓冲发送给客户端的消息
    • 代码上的性能问题:如 string[]byte 之间转换带来的性能损耗
    • 互斥锁:某些地方可以使用读写锁来提高读的并发量,另外一个办法就是使用分段 map 配合互斥锁
    • 系统本身预留的扩展点中,用户自行实现的代码中可能会存在性能问题
  • 要实现高可用就得将系统部署到多台机器上,这个时候需要在 uid 和服务器之间建立起某种关联,以便推送消息的时候可以成功推送给客户端。

相关文章:

Golang 搭建 WebSocket 应用(七) - 性能、可用性

在前面的文章中&#xff0c;提到过非功能性需求决定了架构。 今天我们再来考虑一下另外两个非功能性需求&#xff1a;性能和可用性。 前言 关于性能&#xff0c;其实并不是只有我们这个消息推送系统独有的问题。 对于所有的开发者而言&#xff0c;都多多少少会处理过性能相关…...

Qt 状态机框架:The State Machine Framework (一)

传送门: Qt 状态机框架:The State Machine Framework (一) Qt 状态机框架:The State Machine Framework (二) 一、什么是状态机框架 状态机框架提供了用于创建和执行状态图/表[1]的类。这些概念和表示法基于Harel的Statecharts:一种复杂系统的可视化形式,也是UML状态图的基…...

高通平台学习一

什么是QMI? Qualcom Message Interface 高通信息接口 高通平台目前都是非对称多核心&#xff0c;最主要的是AP和Modem。两个处理器怎么进行通信呢&#xff0c;我们把AP和Modem当作两个主机&#xff0c;问题就变得了很简单&#xff0c;TCP/IP协议不是一种非常成功的进程间跨主…...

Python爬虫时被封IP,该怎么解决?四大动态IP平台测评

在使用 Python 进行爬虫时&#xff0c;很有可能因为一些异常行为被封 IP&#xff0c;这主要是因为一些爬虫时产生的异常行为导致的。 在曾经的一次数据爬取的时候&#xff0c;我尝试去爬取Google地图上面的商家联系方式和地址信息做营销&#xff0c;可是很不幸&#xff0c;还只…...

积分梳状滤波器CIC原理与实现

CIC&#xff08;Cascade Intergrator Comb&#xff09;&#xff1a;级联积分梳状滤波器&#xff0c;是由积分器和梳状滤波器级联而得。滤波器系数为1&#xff0c;无需对系数进行存储&#xff0c;只有加法器、积分器和寄存器&#xff0c;资源消耗少&#xff0c;运算速率高&#…...

【项目管理】CMMI-原因分析与解决过程(CAR)

概述&#xff1a; “原因分析与解决”通过预防缺陷或者问题的引入以及识别并适当纳入优秀过程性能的原因&#xff0c;改进质量与生产率。 目录 1、文档结构 2、原因分析与解决过程域包括如下活动 3、选择需要加以分析的结果(启动条件) 4、过程活动与实践对照表 5、实例 1、…...

【设计模式】文件目录管理是组合模式吗?

组合模式是什么&#xff1f; 组合模式是一种将对象组合成树形结构以表示"部分-整体"的层次结构的设计模式。它使得用户对单个对象和组合对象的使用具有一致性。 组合模式在什么情况下使用&#xff1f; 当你发现你需要在代码中实现树形数据结构&#xff0c;让整体-部…...

利用appium自动控制移动设备并提取数据

安装appium-python-client模块并启动已安装好的环境 安装appium-python-client模块 在window的虚拟环境下执行pip install appium-python-client 启动夜神模拟器&#xff0c;进入夜神模拟器所在的安装路径的bin目录下&#xff0c;进入cmd终端&#xff0c;使用adb命令建立adb…...

day22_236二叉树最近公共祖先_235二叉搜索树(最近公共祖先_701插入一个节点_450删除一个节点)

文章目录 [236 二叉树的最近公共祖先](https://programmercarl.com/0236.%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E6%9C%80%E8%BF%91%E5%85%AC%E5%85%B1%E7%A5%96%E5%85%88.html#%E7%AE%97%E6%B3%95%E5%85%AC%E5%BC%80%E8%AF%BE)[235 二叉搜索树的最近公共祖先](https://program…...

OpenSource - 工具管理器easy-manager-tool

文章目录 功能说明运行配置环境配置启动docker部署 项目安全UI展示 Easy-Manager-Tool 打造软件行业首款集成工具&#xff0c;不管你是程序员&#xff0c;测试&#xff0c;运维等都可以使用该软件来提升自己的工作效率。 Easy-Manager-Tool 的诞生是为了解决软件行业众多参与者…...

Laravel7 + easyWeChat 实现微信公众号支付功能

注册服务号&#xff0c;需进行微信认证&#xff0c;此时需缴费 300 元/年&#xff0c;必须是认证成功的服务号才能开通微信支付。 注册微信支付商户号 1、登录 https://pay.weixin.qq.com/index.php/core/home/login?return_urlhttps%3A%2F%2Fpay.weixin.qq.com%2Findex.php%…...

Linux环境下,针对QT软件工程搭建C++Test单元测试环境的操作指南

文章目录 前言一、安装QT二、安装CTest三、使用QT生成.bdf文件四、创建CTest工程注意事项 前言 CTest是Parasoft公司出品的一款可以针对C/C源代码进行静态分析、单元测试、集成测试的测试工具。本文主要讲解如何在Linux环境下&#xff0c;搭建QT插件版的CTest测试环境。 一、…...

16k+ start 一个开源的的监控系统部署教程

安装条件 Linux或macOS系统 4GB内存 开放 33014、33174、3183端口 1.安装 1、下载源码 首先使用 git 克隆源码到本地 git clone -b main https://github.com/SigNoz/signoz.git && cd signoz/deploy/ 方式1&#xff1a;运行 install.sh 脚本一键安装 ./install.s…...

Mermaid使用教程(绘制各种图)

Mermaid使用教程&#xff08;绘制各种图&#xff09; 文章目录 Mermaid使用教程&#xff08;绘制各种图&#xff09;简介饼状图简单的例子应用案例 序列图简单案例应用案例另一个应用案例 甘特图简单案例应用案例一个更为复杂的应用案例 Git图简单案例 总结 简介 本文将主要介…...

OpenAI/ChatGPT Plus 支持的虚拟卡有哪些

最近&#xff0c;有关 OpenAI/ChatGPT Plus 需要信用卡的讨论越来越多。在这篇文章中&#xff0c;我将分享一些我在绑定信用卡过程中得到的经验和教训&#xff0c;以及 OpenAI/ChatGPT Plus 支持的卡类型。 不支持的卡 根据 OpenAI 的地区限制&#xff0c;国内和香港的卡都不…...

ARM多核调度器DSU

1. 背景 从A75开始&#xff0c;ARM提出了一个新的多核心管理系统单元&#xff0c;叫做DSU(DynamIQ Shared Unit)。DSU的核心功能是控制CPU内核&#xff0c;使其成簇Cluster使用&#xff0c;簇内每一个核心可以单独开关、调整频率/电压&#xff0c;能效表现更佳&#xff0c;甚至…...

vue解决部署文件缓存方式

问题&#xff1a;系统上线后&#xff0c;除了bug。紧急修复后&#xff0c;发现安卓正常&#xff0c;ios上海市有问题。通过debug后发现&#xff0c;ios上缓存严重。于是想到了打包文件加时间戳的方式来去除缓存。 vue2 配置打包输出文件名方式&#xff1a; const baseUrl &qu…...

游戏开发中的噪声算法

一、噪声 噪声是游戏编程的常见技术&#xff0c;广泛应用于地形生成&#xff0c;图形学等多方面。 那么为什么要引入噪声这个概念呢&#xff1f;在程序中&#xff0c;我们经常使用直接使用最简单的rand()生成随机值&#xff0c;但它的问题在于生成的随机值太“随机”了&#xf…...

CodeReview 小工具

大家开发中有没有遇到一个版本开发的非常杂&#xff0c;开发很多个项目&#xff0c;改动几周后甚至已经忘了自己改了些什么&#xff0c;领导要对代码review的时候&#xff0c;理不清楚自己改过的代码&#xff0c;只能将主要改动的大功能过一遍。这样就很容易造成review遗漏&…...

UE5 C++ Slate独立程序的打包方法

在源码版安装目录内找到已编译通过的xxx.exe&#xff0c;&#xff08;\Engine\Binaries\Win64\xxx.exe)&#xff0c;在需要的位置新建文件夹&#xff0c;拷贝源码版Engine内的Binaries、Content、Shaders文件夹到目标文件夹内&#xff0c;将xxx.exe放入对应位置&#xff0c;删除…...

VB.net复制Ntag213卡写入UID

本示例使用的发卡器&#xff1a;https://item.taobao.com/item.htm?ftt&id615391857885 一、读取旧Ntag卡的UID和数据 Private Sub Button15_Click(sender As Object, e As EventArgs) Handles Button15.Click轻松读卡技术支持:网站:Dim i, j As IntegerDim cardidhex, …...

C# 类和继承(抽象类)

抽象类 抽象类是指设计为被继承的类。抽象类只能被用作其他类的基类。 不能创建抽象类的实例。抽象类使用abstract修饰符声明。 抽象类可以包含抽象成员或普通的非抽象成员。抽象类的成员可以是抽象成员和普通带 实现的成员的任意组合。抽象类自己可以派生自另一个抽象类。例…...

【JavaSE】绘图与事件入门学习笔记

-Java绘图坐标体系 坐标体系-介绍 坐标原点位于左上角&#xff0c;以像素为单位。 在Java坐标系中,第一个是x坐标,表示当前位置为水平方向&#xff0c;距离坐标原点x个像素;第二个是y坐标&#xff0c;表示当前位置为垂直方向&#xff0c;距离坐标原点y个像素。 坐标体系-像素 …...

Caliper 配置文件解析:config.yaml

Caliper 是一个区块链性能基准测试工具,用于评估不同区块链平台的性能。下面我将详细解释你提供的 fisco-bcos.json 文件结构,并说明它与 config.yaml 文件的关系。 fisco-bcos.json 文件解析 这个文件是针对 FISCO-BCOS 区块链网络的 Caliper 配置文件,主要包含以下几个部…...

【碎碎念】宝可梦 Mesh GO : 基于MESH网络的口袋妖怪 宝可梦GO游戏自组网系统

目录 游戏说明《宝可梦 Mesh GO》 —— 局域宝可梦探索Pokmon GO 类游戏核心理念应用场景Mesh 特性 宝可梦玩法融合设计游戏构想要素1. 地图探索&#xff08;基于物理空间 广播范围&#xff09;2. 野生宝可梦生成与广播3. 对战系统4. 道具与通信5. 延伸玩法 安全性设计 技术选…...

python报错No module named ‘tensorflow.keras‘

是由于不同版本的tensorflow下的keras所在的路径不同&#xff0c;结合所安装的tensorflow的目录结构修改from语句即可。 原语句&#xff1a; from tensorflow.keras.layers import Conv1D, MaxPooling1D, LSTM, Dense 修改后&#xff1a; from tensorflow.python.keras.lay…...

Xen Server服务器释放磁盘空间

disk.sh #!/bin/bashcd /run/sr-mount/e54f0646-ae11-0457-b64f-eba4673b824c # 全部虚拟机物理磁盘文件存储 a$(ls -l | awk {print $NF} | cut -d. -f1) # 使用中的虚拟机物理磁盘文件 b$(xe vm-disk-list --multiple | grep uuid | awk {print $NF})printf "%s\n"…...

人工智能(大型语言模型 LLMs)对不同学科的影响以及由此产生的新学习方式

今天是关于AI如何在教学中增强学生的学习体验&#xff0c;我把重要信息标红了。人文学科的价值被低估了 ⬇️ 转型与必要性 人工智能正在深刻地改变教育&#xff0c;这并非炒作&#xff0c;而是已经发生的巨大变革。教育机构和教育者不能忽视它&#xff0c;试图简单地禁止学生使…...

【笔记】WSL 中 Rust 安装与测试完整记录

#工作记录 WSL 中 Rust 安装与测试完整记录 1. 运行环境 系统&#xff1a;Ubuntu 24.04 LTS (WSL2)架构&#xff1a;x86_64 (GNU/Linux)Rust 版本&#xff1a;rustc 1.87.0 (2025-05-09)Cargo 版本&#xff1a;cargo 1.87.0 (2025-05-06) 2. 安装 Rust 2.1 使用 Rust 官方安…...

怎么让Comfyui导出的图像不包含工作流信息,

为了数据安全&#xff0c;让Comfyui导出的图像不包含工作流信息&#xff0c;导出的图像就不会拖到comfyui中加载出来工作流。 ComfyUI的目录下node.py 直接移除 pnginfo&#xff08;推荐&#xff09;​​ 在 save_images 方法中&#xff0c;​​删除或注释掉所有与 metadata …...