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.Mutex
的 Lock
保护的代码,在同一时刻只能有一个协程可以执行。
// 推送消息的接口
func send(hub *Hub, w http.ResponseWriter, r *http.Request) {// ... 其他代码// 从 hub 中获取 clienthub.Lock()client, ok := hub.userClients[uid]hub.Unlock()// ... 其他代码
}
对于上面这种只读的操作,也就是没有对 map
进行写操作,我们依然使用了 sync.Mutex
的 Lock()
来锁定临界区。
这里存在的问题是,其实我们的 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
变成串行的,这样性能无疑会很差。
除此之外,我们在 Hub
的 run
方法中也使用了 sync.Mutex
:
case client := <-h.register:h.Lock()h.clients[client] = trueh.userClients[client.uid] = clienth.Unlock()
也就是说,我们将 Client
注册到 Hub
的操作也是串行的。
对于这种场景,其实也有一种解决方法就是分段 map
,
也就是将 clients
和 userClients
这两个 map
拆分为多个 map
,
然后对于每一个 map
都有一个对应的 sync.Mutex
互斥锁来保证其读写的安全。
但如果要这样做,单单分段还不够,我们的 register
和 unregister
还是只有一个,对于这个问题,
我们可能需要将 register
和 unregister
也分段,最后在 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 应用(七) - 性能、可用性
在前面的文章中,提到过非功能性需求决定了架构。 今天我们再来考虑一下另外两个非功能性需求:性能和可用性。 前言 关于性能,其实并不是只有我们这个消息推送系统独有的问题。 对于所有的开发者而言,都多多少少会处理过性能相关…...
Qt 状态机框架:The State Machine Framework (一)
传送门: Qt 状态机框架:The State Machine Framework (一) Qt 状态机框架:The State Machine Framework (二) 一、什么是状态机框架 状态机框架提供了用于创建和执行状态图/表[1]的类。这些概念和表示法基于Harel的Statecharts:一种复杂系统的可视化形式,也是UML状态图的基…...

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

Python爬虫时被封IP,该怎么解决?四大动态IP平台测评
在使用 Python 进行爬虫时,很有可能因为一些异常行为被封 IP,这主要是因为一些爬虫时产生的异常行为导致的。 在曾经的一次数据爬取的时候,我尝试去爬取Google地图上面的商家联系方式和地址信息做营销,可是很不幸,还只…...

积分梳状滤波器CIC原理与实现
CIC(Cascade Intergrator Comb):级联积分梳状滤波器,是由积分器和梳状滤波器级联而得。滤波器系数为1,无需对系数进行存储,只有加法器、积分器和寄存器,资源消耗少,运算速率高&#…...

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

【设计模式】文件目录管理是组合模式吗?
组合模式是什么? 组合模式是一种将对象组合成树形结构以表示"部分-整体"的层次结构的设计模式。它使得用户对单个对象和组合对象的使用具有一致性。 组合模式在什么情况下使用? 当你发现你需要在代码中实现树形数据结构,让整体-部…...

利用appium自动控制移动设备并提取数据
安装appium-python-client模块并启动已安装好的环境 安装appium-python-client模块 在window的虚拟环境下执行pip install appium-python-client 启动夜神模拟器,进入夜神模拟器所在的安装路径的bin目录下,进入cmd终端,使用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 打造软件行业首款集成工具,不管你是程序员,测试,运维等都可以使用该软件来提升自己的工作效率。 Easy-Manager-Tool 的诞生是为了解决软件行业众多参与者…...

Laravel7 + easyWeChat 实现微信公众号支付功能
注册服务号,需进行微信认证,此时需缴费 300 元/年,必须是认证成功的服务号才能开通微信支付。 注册微信支付商户号 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环境下,搭建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:运行 install.sh 脚本一键安装 ./install.s…...

Mermaid使用教程(绘制各种图)
Mermaid使用教程(绘制各种图) 文章目录 Mermaid使用教程(绘制各种图)简介饼状图简单的例子应用案例 序列图简单案例应用案例另一个应用案例 甘特图简单案例应用案例一个更为复杂的应用案例 Git图简单案例 总结 简介 本文将主要介…...
OpenAI/ChatGPT Plus 支持的虚拟卡有哪些
最近,有关 OpenAI/ChatGPT Plus 需要信用卡的讨论越来越多。在这篇文章中,我将分享一些我在绑定信用卡过程中得到的经验和教训,以及 OpenAI/ChatGPT Plus 支持的卡类型。 不支持的卡 根据 OpenAI 的地区限制,国内和香港的卡都不…...

ARM多核调度器DSU
1. 背景 从A75开始,ARM提出了一个新的多核心管理系统单元,叫做DSU(DynamIQ Shared Unit)。DSU的核心功能是控制CPU内核,使其成簇Cluster使用,簇内每一个核心可以单独开关、调整频率/电压,能效表现更佳,甚至…...
vue解决部署文件缓存方式
问题:系统上线后,除了bug。紧急修复后,发现安卓正常,ios上海市有问题。通过debug后发现,ios上缓存严重。于是想到了打包文件加时间戳的方式来去除缓存。 vue2 配置打包输出文件名方式: const baseUrl &qu…...
游戏开发中的噪声算法
一、噪声 噪声是游戏编程的常见技术,广泛应用于地形生成,图形学等多方面。 那么为什么要引入噪声这个概念呢?在程序中,我们经常使用直接使用最简单的rand()生成随机值,但它的问题在于生成的随机值太“随机”了…...

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

UE5 C++ Slate独立程序的打包方法
在源码版安装目录内找到已编译通过的xxx.exe,(\Engine\Binaries\Win64\xxx.exe),在需要的位置新建文件夹,拷贝源码版Engine内的Binaries、Content、Shaders文件夹到目标文件夹内,将xxx.exe放入对应位置,删除…...
[2025CVPR]DeepVideo-R1:基于难度感知回归GRPO的视频强化微调框架详解
突破视频大语言模型推理瓶颈,在多个视频基准上实现SOTA性能 一、核心问题与创新亮点 1.1 GRPO在视频任务中的两大挑战 安全措施依赖问题 GRPO使用min和clip函数限制策略更新幅度,导致: 梯度抑制:当新旧策略差异过大时梯度消失收敛困难:策略无法充分优化# 传统GRPO的梯…...

地震勘探——干扰波识别、井中地震时距曲线特点
目录 干扰波识别反射波地震勘探的干扰波 井中地震时距曲线特点 干扰波识别 有效波:可以用来解决所提出的地质任务的波;干扰波:所有妨碍辨认、追踪有效波的其他波。 地震勘探中,有效波和干扰波是相对的。例如,在反射波…...

大话软工笔记—需求分析概述
需求分析,就是要对需求调研收集到的资料信息逐个地进行拆分、研究,从大量的不确定“需求”中确定出哪些需求最终要转换为确定的“功能需求”。 需求分析的作用非常重要,后续设计的依据主要来自于需求分析的成果,包括: 项目的目的…...

376. Wiggle Subsequence
376. Wiggle Subsequence 代码 class Solution { public:int wiggleMaxLength(vector<int>& nums) {int n nums.size();int res 1;int prediff 0;int curdiff 0;for(int i 0;i < n-1;i){curdiff nums[i1] - nums[i];if( (prediff > 0 && curdif…...
2023赣州旅游投资集团
单选题 1.“不登高山,不知天之高也;不临深溪,不知地之厚也。”这句话说明_____。 A、人的意识具有创造性 B、人的认识是独立于实践之外的 C、实践在认识过程中具有决定作用 D、人的一切知识都是从直接经验中获得的 参考答案: C 本题解…...

20个超级好用的 CSS 动画库
分享 20 个最佳 CSS 动画库。 它们中的大多数将生成纯 CSS 代码,而不需要任何外部库。 1.Animate.css 一个开箱即用型的跨浏览器动画库,可供你在项目中使用。 2.Magic Animations CSS3 一组简单的动画,可以包含在你的网页或应用项目中。 3.An…...
CSS | transition 和 transform的用处和区别
省流总结: transform用于变换/变形,transition是动画控制器 transform 用来对元素进行变形,常见的操作如下,它是立即生效的样式变形属性。 旋转 rotate(角度deg)、平移 translateX(像素px)、缩放 scale(倍数)、倾斜 skewX(角度…...
NPOI Excel用OLE对象的形式插入文件附件以及插入图片
static void Main(string[] args) {XlsWithObjData();Console.WriteLine("输出完成"); }static void XlsWithObjData() {// 创建工作簿和单元格,只有HSSFWorkbook,XSSFWorkbook不可以HSSFWorkbook workbook new HSSFWorkbook();HSSFSheet sheet (HSSFSheet)workboo…...

解析奥地利 XARION激光超声检测系统:无膜光学麦克风 + 无耦合剂的技术协同优势及多元应用
在工业制造领域,无损检测(NDT)的精度与效率直接影响产品质量与生产安全。奥地利 XARION开发的激光超声精密检测系统,以非接触式光学麦克风技术为核心,打破传统检测瓶颈,为半导体、航空航天、汽车制造等行业提供了高灵敏…...

认识CMake并使用CMake构建自己的第一个项目
1.CMake的作用和优势 跨平台支持:CMake支持多种操作系统和编译器,使用同一份构建配置可以在不同的环境中使用 简化配置:通过CMakeLists.txt文件,用户可以定义项目结构、依赖项、编译选项等,无需手动编写复杂的构建脚本…...