Golang 高性能 Websocket 库 gws 使用与设计(一)

前言

大家好这里是,白泽,这期分析一下 golang 开源高性能 websocket 库 gws。

视频讲解请关注📺B站:白泽talk

image-20240726234405804

介绍

  1. gws:https://github.com/lxzan/gws |GitHub 🌟 1.2k,高性能的 websocket 库,代码双语注释,适合有开发经验的同学进阶学习。
  2. gws 的两个特性
  • High IOPS Low Latency(高I/O,低延迟)

  • Low Memory Usage(低内存占用)

可以从下图看到: payload 越高,性能相比其他 websocket 库越是优越,如何做到?

image-20240723220947562

gws chatroom 架构图

这是 gws 的官方聊天室 demo 的架构图,绘制在这里帮助各位理解什么是全双工的通信模式。

image-20240723212541706

WebSocket 与 HTTP 一样是应用层的协议,只需要 TCP 完成三次握手之后,Golang 的 net/http 库提供了 Hijack() 方法,将 TCP 套接字(活跃的一个会话),从 HTTP 劫持,此后 tcp 的连接将由 WebSocket 管理,脱离了 HTTP 协议的范畴。

而只要获取了 TCP 的套接字,何时发送和接受数据,都是由应用层决定的,传输层的 TCP 套接字只是被编排的对象(单工/双工),自然可以实现服务端主动发送数据。

缓冲池

为什么 payload 越高,性能相比其他 websocket 库越是优越?

原因:gws 中的读写操作,全部使用了缓冲池。

image-20240726220546231

binaryPool    = internal.NewBufferPool(128, 256*1024) // 缓冲池

读缓冲:每次读取是一次系统调用,因此可以读取一段数据,且用一个 offset 定位消费的位置,减少读取次数。

写缓冲:每次写入是一次系统调用,因此可以多次写入 buffer,统一 flush。

缓冲池:为不同大小的 buffer 提供了缓冲池,大段 buffer 的创建次数减少,减少 GC 压力 & 创建对象和销毁对象时间。

// NewBufferPool Creating a memory pool
// Left, right indicate the interval range of the memory pool, they will be transformed into pow(2,n)。
// Below left, Get method will return at least left bytes; above right, Put method will not reclaim the buffer.
func NewBufferPool(left, right uint32) *BufferPool {var begin, end = int(binaryCeil(left)), int(binaryCeil(right))var p = &BufferPool{begin:  begin,end:    end,shards: map[int]*sync.Pool{},}for i := begin; i <= end; i *= 2 {capacity := ip.shards[i] = &sync.Pool{New: func() any { return bytes.NewBuffer(make([]byte, 0, capacity)) },}}return p
}

使用循环从 beginend,每次容量翻倍(乘以2),为每个容量创建一个 sync.Pool 实例。sync.Pool 是Go语言标准库中的一个类型,用于存储和回收临时对象。

使用缓冲池中的 bufferconn(网络连接)中读取和写入数据时,通常会执行以下步骤:

  1. 从缓冲池获取缓冲区:使用 Get 方法从缓冲池中获取一个 buffer
  2. 读取数据:如果需要从 conn 读取数据,可以将 buffer 用作读取操作的目的地。
  3. 处理数据:根据需要处理读取到的数据。
  4. 写入数据:如果需要写入数据,可以将数据写入从缓冲池获取的 buffer,然后从 buffer 写入 conn
  5. 释放缓冲区:使用完毕后,将 buffer 放回缓冲池,以便重用。

设计一个 WebScket 库

编写WebSocket库时,有几个关键点会影响其性能,尤其是在高并发场景下。

下面针对这些场景,部分给出一些 demo 写法(伪代码),可以从中提炼一些通用的项目设计方法:

  • 事件驱动模型: 使用非阻塞的事件驱动架构可以提高性能,因为它允许WebSocket库在单个线程内处理多个连接,而不会因等待I/O操作而阻塞。
package mainimport ("fmt""time"
)func main() {eventChan := make(chan string)readyChan := make(chan bool)// 模拟WebSocket连接go func() {time.Sleep(2 * time.Second)eventChan <- "connected"readyChan <- true}()// 事件处理循环for {select {case event := <-eventChan:fmt.Println("Event received:", event)case <-readyChan:fmt.Println("WebSocket is ready to use")return}}
}
  • 并发处理: 库如何处理并发连接和消息是影响性能的重要因素。使用goroutines或线程池可以提高并发处理能力。

  • 消息压缩: 支持消息压缩(如permessage-deflate扩展)可以减少传输数据量,但同时也会增加CPU的使用率,需要找到合适的平衡点。

  • 内存管理: 优化内存使用,比如通过减少内存分配和重用缓冲区,可以提高性能并减少垃圾回收的压力。

var buffer = make([]byte, 0, 1024)func readMessage(conn *websocket.Conn) {_, buffer, err := conn.ReadMessage()if err != nil {// 处理错误}// 使用buffer中的数据
}
  • 连接池管理: 有效的连接池管理可以减少连接建立和关闭的开销,特别是在长连接和频繁通信的场景下。
type WebSocketPool struct {pool map[*websocket.Conn]struct{}
}func (p *WebSocketPool) Add(conn *websocket.Conn) {p.pool[conn] = struct{}{}
}func (p *WebSocketPool) Remove(conn *websocket.Conn) {delete(p.pool, conn)
}func (p *WebSocketPool) Broadcast(message []byte) {for conn := range p.pool {conn.WriteMessage(websocket.TextMessage, message)}
}
  • 锁和同步机制: 在多线程或goroutine环境中,合理的锁和同步机制是必要的,以避免竞态条件和死锁,但过多的锁竞争会降低性能。
import "sync"var pool = &WebSocketPool{pool: make(map[*websocket.Conn]struct{}),
}
var mu sync.Mutexfunc broadcast(message []byte) {mu.Lock()defer mu.Unlock()for conn := range pool.pool {conn.WriteMessage(websocket.TextMessage, message)}
}
  • I/O模型: 使用非阻塞I/O或异步I/O模型可以提高性能,因为它们允许在等待网络数据时执行其他任务。
func handleConnection(conn *websocket.Conn) {go func() {for {_, message, err := conn.ReadMessage()if err != nil {return // 处理错误}// 处理接收到的消息}}()
}
  • 协议实现: 精确且高效的WebSocket协议实现,包括帧的处理、掩码的添加和去除、以及控制帧的管理,都是影响性能的因素。
func (c *Conn) genFrame(opcode Opcode, payload internal.Payload, isBroadcast bool) (*bytes.Buffer, error) {if opcode == OpcodeText && !payload.CheckEncoding(c.config.CheckUtf8Enabled, uint8(opcode)) {return nil, internal.NewError(internal.CloseUnsupportedData, ErrTextEncoding)}var n = payload.Len()if n > c.config.WriteMaxPayloadSize {return nil, internal.CloseMessageTooLarge}var buf = binaryPool.Get(n + frameHeaderSize)buf.Write(framePadding[0:])if c.pd.Enabled && opcode.isDataFrame() && n >= c.pd.Threshold {return c.compressData(buf, opcode, payload, isBroadcast)}var header = frameHeader{}headerLength, maskBytes := header.GenerateHeader(c.isServer, true, false, opcode, n)_, _ = payload.WriteTo(buf)var contents = buf.Bytes()if !c.isServer {internal.MaskXOR(contents[frameHeaderSize:], maskBytes)}var m = frameHeaderSize - headerLengthcopy(contents[m:], header[:headerLength])buf.Next(m)return buf, nil
}
  • 错误处理和恢复: 健壮的错误处理和异常恢复机制可以防止个别连接的问题影响整个服务的性能。

  • 测试和基准: 通过广泛的测试和基准测试来识别性能瓶颈,并根据测试结果进行优化。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/51018.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

0724,select +tcp 聊天室喵

目录 TCP协议喵 723__01&#xff1a;使用select实现一个基于UDP的一对一即时聊天程序。 001: 002: TIMEWAI OR BUG 721作业&#xff1a; 01&#xff1a;在一对一聊天的基础上&#xff0c;使用select实现一对多的回显服务。&#xff08;回显服务即接收到客户端发送的数…

Pyppeteer 的使用

puppeteer 是基于Node.js 开发的一个工具, 有了它&#xff0c;我们可以利用 JavaScript 控制 Chrome 浏览器的一些操作。当然&#xff0c; puppeteer 也可以用于网络爬虫&#xff0c;其 API 及其完善&#xff0c;功能非常强大。 Pyppeteer 其实是 puppeteer 的 python 实现&…

.net 连接达梦数据库开发环境部署

.net 开发环境部署 1. 环境准备 测试工具 Visual Studio2022 数据库版本 dm8 2. 搭建过程 1 &#xff09;创建新项目 2 &#xff09;选择创建空项目 3 &#xff09;配置新项目 4 &#xff09;右键 DM1 新建一个项 5 &#xff09;加 载 驱 动 &#xff0c; 新 建 …

0722_驱动3 地址映射驱动点灯

一、为什么需要地址映射 在芯片手册上查看到的地址属于物理地址&#xff0c;在硬件层 在内核空间地址属于虚拟地址&#xff0c;在内核层 在驱动中&#xff0c;操作的是虚拟地址 需要将物理地址《--mmu内存管理单元--》虚拟地址映射 二、映射API接口 void *ioremap(unsigned lon…

backtrace

介绍 arm平台的调用栈与x86平台的调用栈大致相同&#xff0c;稍微有些区别&#xff0c;主要在于栈帧的压栈内容和传参方式不同。在arm平台的不同程序&#xff0c;采用的编译选项不同&#xff0c;程序运行期间的栈帧也会不同。有些工具在对arm的调用栈回溯时&#xff0c;可能会…

电商项目之如何判断线程池是否执行完所有任务

文章目录 1 问题背景2 前言3 4种常用的方法4 代码4.1 isTerminated()4.2 线程池的任务总数是否等于已执行的任务数4.3 CountDownLatch计数器4.4 CyclicBarrier计数器 1 问题背景 真实生产环境的电商项目&#xff0c;常使用线程池应用于执行大批量操作达到高性能的效果。应用场景…

Dify中语音和文字间转换问题的一种暂时注释方式

本文主要解释了Dify中语音和文字间转换可能会遇到的问题&#xff0c;并给出了一种暂时注释的解决方案。 一.文本转语音可能问题 本地部署文本转语音时&#xff0c;如果遇到如下问题&#xff0c;安装ffmpeg即可。但是如果安装后&#xff0c;重启系统还是遇到这个问题该如何办&…

分库分表——从理论到最佳实践

目录 1、为什么要分库分表&#xff1f;2、切分方案有哪些&#xff1f;2.1 分库2.1.1 垂直分库2.1.2 水平分库 2.2 分表2.2.1 垂直分表2.2.2 水平分表 2.3 分库分表 3、数据水平分片方法3.1 Hash分片3.2 一致性Hash分片3.3 Range分片 4、分库分表的挑战4.1 分布式id4.2 分布式事…

LLM:归一化 总结

一、Batch Normalization 原理 Batch Normalization 是一种用于加速神经网络训练并提高稳定性的技术。它通过在每一层网络的激活值上进行归一化处理&#xff0c;使得每一层的输入分布更加稳定&#xff0c;从而加速训练过程&#xff0c;并且减轻了对参数初始化的依赖。 公式 …

分类模型——逻辑回归和Fisher线性判别分析

个人学习笔记&#xff0c;课程为数学建模清风付费课程 目录 一、引例 二、逻辑回归 2.1线性概率模型 2.2Fisher线性判别分析 2.3两点分布&#xff08;伯努利分布&#xff09; 2.4连接函数的取法 2.5如何求解 2.6如何用于分类 三、SPSS 3.1二元分类 3.1.1逻辑回…

MySQL内如何改变编码格式

查找数据库的编码格式&#xff1a; show variables like character%;具体内容时这些 在创建表时设定编码格式&#xff1a; create database <要创建的数据库的名字> charset utf8; 修改数据库默认编码&#xff1a; set character_set_databaseutf8mb4; character_…

eclipse ui bug

eclipse ui bug界面缺陷&#xff0c;可能项目过多&#xff0c;特别maven项目过多&#xff0c;下载&#xff0c;自动编译&#xff0c;加载更新界面异常 所有窗口死活Restore不回去了 1&#xff09;尝试创建项目&#xff0c;还原界面&#xff0c;失败 2&#xff09;关闭所有窗口&…

将TP5链接导入笔影个人博客代码

首先第一步&#xff0c;打开界面 第二步&#xff0c;这里卡住了&#xff0c;无法看到源代码&#xff0c;我们使用其他软件看看源代码 调试乱码&#xff0c;因为没有找到相应的笔影个人博客源码。源码在桌面上。询问百度&#xff0c;说了有的没的一大堆。 尝试的结果就是失败…

时间复杂度与O(n)

文章目录 1 复杂度分析1.1 时间复杂度1.1.1 循环执行次数1.1.2 大O(n)表示法 1.2 空间复杂度 1 复杂度分析 1.1 时间复杂度 ​ 时间复杂度用来表示算法运行时间的长短&#xff0c;用来定性的描述程序的运行时间。要了解时间复杂度&#xff0c;我们需要先了解程序执行的次数。…

机器学习(二十二):精度和召回率

一、倾斜数据集 倾斜数据集&#xff1a;一个数据集中的正面和负面例子的比例非常不平衡&#xff0c;比如数据集中&#xff0c;结果为1的占比20%&#xff0c;结果为0的占比80% 例子&#xff1a;如果数据集的结果中只有0.5%是1&#xff0c;其余结果是0。有一个模型的预测准确度…

【信创】udisk2服务异常导致U盘使用中自动移除问题解决

原文链接&#xff1a;【信创】udisk2服务异常导致U盘使用中自动移除问题解决 Hello&#xff0c;大家好啊&#xff01;今天给大家带来一篇关于在信创终端操作系统上由于udisk2服务异常导致U盘等移动设备在使用中自动移除问题的排查文章。udisk2是一个管理存储设备的服务&#xf…

【计算机网络】OSPF单区域实验

一&#xff1a;实验目的 1&#xff1a;掌握在路由器上配置OSPF单区域。 2&#xff1a;学习OSPF协议的原理&#xff0c;及其网络拓扑结构改变后的变化。 二&#xff1a;实验仪器设备及软件 硬件&#xff1a;RCMS交换机、网线、内网网卡接口、Windows 2019操作系统的计算机等。…

Vue 3 实现左侧列表点击跳转滚动到右侧对应区域的功能

使用 Vue 3 实现左侧列表点击跳转到右侧对应区域的功能 1. 引言 在这篇博客中&#xff0c;我们将展示如何使用 Vue 3 实现一个简单的页面布局&#xff0c;其中左侧是一个列表&#xff0c;点击列表项时&#xff0c;右侧会平滑滚动到对应的内容区域。这种布局在很多应用场景中都…

Llama 3.1 405B 详解

2024 年 7 月 23 日星期二&#xff0c;Meta 宣布推出 Llama 3.1&#xff0c;这是其Llama 系列大型语言模型 (LLM)的最新版本。虽然只是对 Llama 3 模型进行小幅更新&#xff0c;但它特别引入了Llama 3.1 405B——一个 4050 亿参数的模型&#xff0c;这是迄今为止世界上最大的开…

运行ruoyi

nacos 数据库配置 修改nacos/conf/application.properties 单机版运行 startup.cmd -m standalone redis 运行后端 运行gateway,auth,modules/system模块 可能遇到的问题&#xff1a;端口正在使用 解决 netstat -ano | findstr 9200 taskkill -pid 18284 -f