推荐两个网络复用相关的 Go pkg: cmux/smux

只写一下如何使用,不对实现进行大量描述,两个库的代码都比较精炼,花一会看一下就行。

  • cmux 对端口进行复用,单端口可以建立不同协议的连接(本质都是 TCP),如 TCP/TLS/HTTP/gRPC 或自定义协议

  • smux 对TCP连接复用,单TCP连接承载多条 smux stream

适用使用场景

  • cmux 一些对外只提供单端口的服务,比如一个端口同时提供 HTTP/HTTPS 功能,其实还能够更多

  • smux一些性能敏感的地方,比如大量TLS的短连接请求,对于频繁的握手非常消耗CPU,见性能压测反向连接,将 TCP 客户端抽象为服务端,方便如 HTTP/gRPC 的服务开发

cmux 的使用

借用一些官方示例,使用还是相对简单的,23456 端口同时提供了 gRPC/HTTP/tRPC 复用。

// Create the main listener.
l, err := net.Listen("tcp", ":23456")
if err != nil {log.Fatal(err)
}// Create a cmux.
m := cmux.New(l)// Match connections in order:
// First grpc, then HTTP, and otherwise Go RPC/TCP.
grpcL := m.Match(cmux.HTTP2HeaderField("content-type", "application/grpc"))
httpL := m.Match(cmux.HTTP1Fast())
trpcL := m.Match(cmux.Any()) // Any means anything that is not yet matched.// Create your protocol servers.
grpcS := grpc.NewServer()
grpchello.RegisterGreeterServer(grpcS, &server{})httpS := &http.Server{ Handler: &helloHTTP1Handler{} }trpcS := rpc.NewServer()
trpcS.Register(&ExampleRPCRcvr{})// Use the muxed listeners for your servers.
go grpcS.Serve(grpcL)
go httpS.Serve(httpL)
go trpcS.Accept(trpcL)// Start serving!
m.Serve()

自定义 Matcher

cmux 的实现上是对 payload 进行匹配,cmux.HTTP1Fast 是一个匹配函数,为内置的集中匹配函数中的一种,这类匹配函数可以同时设置多个。内部将 net.Conn 的数据读入至 buffer 内,依次调用各个匹配函数对这个 buffer 进行分析,如果匹配成功则 httpL 被返回,httpS 服务收到请求。

所以我们可以自定义一些 Matcher,比如一些携带 Magic/字符串 头的数据

const (PacketMagic = 0x00114514PacketToken = "xyz_token"
)func PacketMagicMatcher(r io.Reader) bool {buf := make([]byte, 4)n, err := io.ReadFull(r, buf)if err != nil {return false}return binary.BigEndian.Uint32(buf[:n]) == PacketMagic
}func PacketTokenMatcher(r io.Reader) bool {buf := make([]byte, len(PacketToken))n, err := io.ReadFull(r, buf)if err != nil {return false}return string(buf[:n]) == PacketToken
}

使用上和 cmux.HTTP1Fast 相同,需要注意的是,net.Conn 头部的 Magic/Token 是和连接相关的,和业务数据无关在使用这些数据之前,需要先将其读取出来

tcpMux := cmux.New(lis)
magicLis := tcpMux.Match(PacketMagicMatcher)go func() {conn, _ := magicLis.Accept()buf := make([]byte, 4)io.ReadAtLeast(conn, buf, len(buf)) // Read header magic length// Handle data ...
}()tcpMux.Serve()

多 mux 场景

需求:只开放一个端口 12345,需要支持

  • HTTP 协议的包下载

  • 基于 TLS 的 gRPC 服务

  • 基于 TLS 的自定义服务

分析:对于 HTTP 和 TLS 需要使用一个 mux 进行区分,TLS 中的 gRPC 和 自定义服务需要再通过一个 mux 区分

参考的实现(截取了一部分业务代码)

// TCP 分流 http/tls
tcpMux := cmux.New(lis)
installerL := tcpMux.Match(cmux.HTTP1Fast())
anyL := tcpMux.Match(cmux.Any())// tls.NewListener(anyL, ...)
mtlsL, err := mTLSListener(anyL, tlsEnable, tlsCertPath, tlsKeyPath, tlsCAPath)
if err != nil {return err
}
tlsMux := cmux.New(mtlsL)
grpcL := tlsMux.Match(cmux.HTTP2())
gwL := tlsMux.Match(gw.Matcher)

smux 的使用

还是放一些官方的简单示例

func client() {// Get a TCP connectionconn, err := net.Dial(...)if err != nil {panic(err)}// Setup client side of smuxsession, err := smux.Client(conn, nil)if err != nil {panic(err)}// Open a new streamstream, err := session.OpenStream()if err != nil {panic(err)}// Stream implements io.ReadWriteCloserstream.Write([]byte("ping"))stream.Close()session.Close()
}func server() {// Accept a TCP connectionconn, err := listener.Accept()if err != nil {panic(err)}// Setup server side of smuxsession, err := smux.Server(conn, nil)if err != nil {panic(err)}// Accept a streamstream, err := session.AcceptStream()if err != nil {panic(err)}// Listen for a messagebuf := make([]byte, 4)stream.Read(buf)stream.Close()session.Close()
}

smux.Session 和 net.Conn 对应,smux.Stream 实现了 net.Conn 接口,所以使用起来和普通的连接无异。smux.Session 是双向的,Client/Server 的区分仅仅是内部的 Id 区别,这就为反向连接打下了基础

基于 smux 的反向连接

对于一个普通的 TCP 服务而言,A(client) -> B(server)。在 B 上 建立 gRPC/HTTP 服务是一件非常自然的事情。

在某些场景下,比如 A 在公网,B 在内网,不做公网映射的话,只能够 B(client) -> A(server)。但是这个情况下,B 上面的 gRPC/HTTP 的服务就不能直接建立了。

上面说过 smux.Session 再使用上时没有方向的,并且提供了和 net.Listener 相近的接口,如果将 smux.Session 封装实现 net.Listener,加上 smux.Stream 是 net.Conn,那么 B 连接 A 继续在 B 上建立 gRPC/HTTP 服务是可以的,内部感知不到具体的实现细节。

对 smux.Session 的封装如下

type SmuxSession struct{ *smux.Session }func (s *SmuxSession) Addr() net.Addr            { return s.Session.LocalAddr() }
func (s *SmuxSession) Accept() (net.Conn, error) { return s.Session.AcceptStream() }
func (s *SmuxSession) Close() error              { return s.Session.Close() }

将 B(tcp:client) -> A(tcp:server) 的场景改为 A(gRPC:client) -> B(gRPC:Server),关键实现如下:

// 忽略错误处理
// 在 A 上的实现如下,cc 为后续使用的 gRPC client
func handleConn(conn net.Conn) {sess, _ := smux.Client(conn, nil)cc, _ := grpc.Dial("",grpc.WithContextDialer(func(ctx context.Context, s string) (net.Conn, error) { return sess.OpenStream() }),grpc.WithTransportCredentials(insecure.NewCredentials()),)// do something.
}// 在 B 的实现如下
func dialAndServe() {conn, _ := net.Dial(...)sess, _ := smux.Server(conn, nil)return g.server.Serve(&SmuxSession{Session: sess})
}

cmux/smux 结合使用

其实 cmux/smux 是两个不同的维度:单端口/单连接,所以只要保证做好 net.Listener/net.Conn 的抽象,使用起来是感知不到的。

比如上面的反向连接中,handleConn 在上面的 gwL := tlsMux.Match(gw.Matcher) 中驱动的

// A 的实现
func handleConn(conn net.Conn) {// 增加的代码:读取 Headerio.CopyN(io.Discard, conn, int64(len(gw.MatcherToken)))sess, _ := smux.Client(conn, nil)cc, _ := grpc.Dial("",grpc.WithContextDialer(func(ctx context.Context, s string) (net.Conn, error) { return sess.OpenStream() }),grpc.WithTransportCredentials(insecure.NewCredentials()),)
}// B 的实现
func dialAndServe() {conn, _ := net.Dial(...)conn.Write([]byte(gw.MatcherToken))  // 增加的代码:发送一个头sess, _ := smux.Server(conn, nil)return g.server.Serve(&SmuxSession{Session: sess})
}

性能压测

性能压测代码见此.

长连接的读写

测试的连接的 case:

  • TCP 连接,作为一个参考基准

  • TLS

  • SmuxTCP,底层协议为 TCP 的情况TLS,底层协议为 TLS 的情况

  • CmuxTCP,底层协议为 TCP 的多个 MatcherTLS,底层协议为 TLS 的单个 Matcher,复合 mux

$ go test -v -benchtime=10s  -benchmem -run=^$ -bench ^BenchmarkConn .
goos: linux
goarch: amd64
pkg: benchmark/connection
cpu: 12th Gen Intel(R) Core(TM) i7-12700
BenchmarkConnCmux
BenchmarkConnCmux/MagicMatcher
BenchmarkConnCmux/MagicMatcher-20                     997550             11862 ns/op        11049.73 MB/s          0 B/op          0 allocs/op
BenchmarkConnCmux/TokenMatcher
BenchmarkConnCmux/TokenMatcher-20                     958461             11714 ns/op        11188.94 MB/s          0 B/op          0 allocs/op
BenchmarkConnCmux/TLSMatcher
BenchmarkConnCmux/TLSMatcher/TLS
BenchmarkConnCmux/TLSMatcher/TLS-20                   295111             40471 ns/op        3238.68 MB/s         192 B/op          7 allocs/op
BenchmarkConnCmux/TLSMatcher/MagicMatcher
BenchmarkConnCmux/TLSMatcher/MagicMatcher-20          296203             39566 ns/op        3312.75 MB/s         192 B/op          7 allocs/op
BenchmarkConnCmux/AnyMatcher
BenchmarkConnCmux/AnyMatcher-20                       932871             11870 ns/op        11041.90 MB/s          0 B/op          0 allocs/op
BenchmarkConnSmux
BenchmarkConnSmux/OverTCP
BenchmarkConnSmux/OverTCP-20                          438889             24703 ns/op        5305.97 MB/s        1380 B/op         26 allocs/op
BenchmarkConnSmux/OverTLS
BenchmarkConnSmux/OverTLS-20                          210336             57345 ns/op        2285.69 MB/s        1596 B/op         36 allocs/op
BenchmarkConnTCP
BenchmarkConnTCP-20                                   917894             12120 ns/op        10814.60 MB/s          0 B/op          0 allocs/op
BenchmarkConnTLS
BenchmarkConnTLS-20                                   292843             40310 ns/op        3251.57 MB/s         192 B/op          7 allocs/op
PASS
ok      benchmark/connection    106.287s

短连接的读写

较长连接的 case 变化,减少 Cmux 为一个Matcher,额外引入了 net.HTTP 和 fasthttp 参与 PK。

短连接的测试,包含了连接的建立和关闭的场景。

$ go test -v -benchtime=10s  -benchmem -run=^$ -bench ^BenchmarkEcho .
goos: linux
goarch: amd64
pkg: benchmark/connection
cpu: 12th Gen Intel(R) Core(TM) i7-12700
BenchmarkEchoCmux
BenchmarkEchoCmux-20                       83162            164356 ns/op         797.49 MB/s       34005 B/op         26 allocs/op
BenchmarkEchoFastHTTP
BenchmarkEchoFastHTTP-20                  144302             95231 ns/op        1376.36 MB/s       12941 B/op         41 allocs/op
BenchmarkEchoNetHTTP
BenchmarkEchoNetHTTP-20                    65124            239187 ns/op         547.99 MB/s      370816 B/op         59 allocs/op
BenchmarkEchoSmux
BenchmarkEchoSmux/OverTCP
BenchmarkEchoSmux/OverTCP-20              153706             70494 ns/op        1859.34 MB/s       79824 B/op         85 allocs/op
BenchmarkEchoSmux/OverTLS
BenchmarkEchoSmux/OverTLS-20              106585            112120 ns/op        1169.04 MB/s       81776 B/op        102 allocs/op
BenchmarkEchoTCP
BenchmarkEchoTCP-20                       308125             39266 ns/op        3338.05 MB/s        1078 B/op         23 allocs/op
BenchmarkEchoTLS
BenchmarkEchoTLS-20                        10000           1988704 ns/op          65.91 MB/s      241188 B/op       1112 allocs/op
PASS
ok      benchmark/connection    112.673s

性能压测的结论

对照 TCP 为基准

  • cmu长连接下对性能的影响很小,接近 TCP,测试有的时候还会比 TCP 高一些短链接下,性能比较低;应该是在 Accept 返回 cmux.MuxConn 之前慢的,多了一次内存拷贝,函数匹配,chan 传递

  • smux底层协议为 TCP,性能相对 TCP 50% 左右,长连接和短连接表现差不多底层协议为 TLS,性能相对 TCP 25-30% 左右,长连接和短连接表现也接近这个比例

  • TLS 正常的 Read/Write 性能大概在 50% 左右,在短连接的情况下,性能非常差(TLS 握手攻击原理)

  • fasthttp 速度非常快

从性能的角度看,smux 适用于频繁建立 TLS 短连接的场景,将短连接变成了一般的 TLS 长连接,参考 BenchmarkConnSmux/OverTLS-20 和 BenchmarkConnTLS-20 性能只下降了 30% 左右,还是比较能接受的。

文章转载自:小胖西瓜

原文链接:https://www.cnblogs.com/shuqin/p/18027908

体验地址:引迈 - JNPF快速开发平台_低代码开发平台_零代码开发平台_流程设计器_表单引擎_工作流引擎_软件架构

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

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

相关文章

Puppeteer 使用实战:如何将自己的 CSDN 专栏文章导出并用于 Hexo 博客(二)

文章目录 上一篇效果演示Puppeteer 修改浏览器的默认下载位置控制并发数错误重试并发控制 错误重试源码 上一篇 Puppeteer 使用实战:如何将自己的 CSDN 专栏文章导出并用于 Hexo 博客(一) 效果演示 上一篇实现了一些基本功能,…

数据结构---链表的基本操作

头插法遍历链表尾插法头删法尾删法按位置插入数据按位置删除数据直接插入排序 链表翻转快慢指针 linklist.c #include <stdio.h> #include <stdlib.h> #include "./linklist.h"linklist* create_linklist(void) {linklist* head (linklist*)malloc(siz…

应用回归分析:非参数回归

非参数回归是一种统计方法&#xff0c;它在建模和分析数据时不假设固定的模型形式。与传统的参数回归模型不同&#xff0c;如线性回归和多项式回归&#xff0c;非参数回归不需要预先定义模型的结构&#xff08;例如&#xff0c;模型是否为线性或多项式&#xff09;。这使得非参…

[面试] 如何保证Redis和MySQL数据一致性?

为什么要在Redis存数据 Redis 用来实现应用和数据库之间读操作的缓存层&#xff0c;主要目的是减少数据 库 IO&#xff0c;还可以提升数据的 IO 性能。 因为Redis基于内存, 查询效率比MySQL快很多, 所以有限查询Redis中的数据,如果Redis没有就查询数据库然后同步到Redis 出…

低代码与大语言模型的探索实践

低代码系列文章&#xff1a; 可视化拖拽组件库一些技术要点原理分析可视化拖拽组件库一些技术要点原理分析&#xff08;二&#xff09;可视化拖拽组件库一些技术要点原理分析&#xff08;三&#xff09;可视化拖拽组件库一些技术要点原理分析&#xff08;四&#xff09;低代码…

1978-2021年全国及31省市农业机械总动力(万千瓦)

1978-2021年全国及31省市农业机械总动力&#xff08;万千瓦&#xff09; 1、时间&#xff1a;1978-2020年 2、范围&#xff1a;31省 3、来源&#xff1a;各省NJ 农业统计NJ 4、缺失情况&#xff1a;无缺失 5、指标&#xff1a;农业机械总动力 6、指标解释&#xff1a; 农…

每日五道java面试题之spring篇(二)

目录&#xff1a; 第一题 Spring事务传播机制第二题 Spring事务什么时候会失效?第三题 什么是bean的⾃动装配&#xff0c;有哪些⽅式&#xff1f;第四题 Spring中的Bean创建的⽣命周期有哪些步骤&#xff1f;第五题 Spring中Bean是线程安全的吗&#xff1f; 第一题 Spring事务…

LeetCode 448.找到所有数组中消失的数字

目录 1.题目 2.代码及思路 3.进阶 3.1题目 3.2代码及思路 1.题目 给你一个含 n 个整数的数组 nums &#xff0c;其中 nums[i] 在区间 [1, n] 内。请你找出所有在 [1, n] 范围内但没有出现在 nums 中的数字&#xff0c;并以数组的形式返回结果。 示例 1&#xff1a; 输入&am…

Redis中的rdb和aof

Redis中的rdb和aof 持久化流程RDB机制redis.conf中rdb的配置save bgsave 自动化 rdb触发的三种方式save 命令bgsave命令自动触发 rdb的优势劣势 AOFaof原理aof配置文件重写原理aof的三种触发机制 appendfsyncaof fix工具 redis-check-aof练习aofaof的优缺点 redis是一个内存数据…

try catch执行return的区别

在Java&#xff08;或类似的语言&#xff09;中&#xff0c;try-catch 块是用于异常处理的结构&#xff0c;而 return 语句用于从方法中返回值。当它们结合使用时&#xff0c;会有一些行为上的差异。 try-catch 块&#xff1a; try-catch 块用于捕获可能会在 try 代码块中抛出的…

一年级英语单元测试

起因 小孩读一年级的&#xff0c;期末想复习下之前的英语听力单元测试。在网上找了一圈没有找到资源。干脆自己用扫描王&#xff0c;把自己手上纸质的扫描后&#xff0c;擦除答题痕迹。 硬货 下述链接是整理后的资源&#xff0c;包括试卷和听力录音。是上海教育出版社的版本。需…

java——IO流基础

目录 IO流IO流的四大分类&#xff1a;IO流的体系&#xff1a;FileinputStream&#xff08;文件字节输入流&#xff09;FileOutputStream(文件字节输出流&#xff09;文件复制资源释放FileReader&#xff08;文件字符输入流&#xff09;FileWriter(文件字符输出流&#xff09;缓…

Python实战: 获取 后缀名(扩展名) 或 文件名

Python实战: 获取 后缀名(扩展名) 或 文件名 &#x1f308; 个人主页&#xff1a;高斯小哥 &#x1f525; 高质量专栏&#xff1a;Matplotlib之旅&#xff1a;零基础精通数据可视化、Python基础【高质量合集】、PyTorch零基础入门教程 &#x1f448; 希望得到您的订阅和支持~ &…

mysql 事务详解一

前言 提到事务&#xff0c;大家肯定不陌生。在我们现实生活中也是存在的&#xff0c;比如我们去超市购物&#xff0c;然后去支付。虽然是两个步骤&#xff0c;必须保证同时成功&#xff0c;这个交易才可以完成。 如果这个场景&#xff0c;拿到我们购物系统&#xff0c;就是几…

VirtualBox+Vagrant安装linux

一、VirtualBox安装 VirtualBox官网&#xff1a;Oracle VM VirtualBox 这里采用VirtualBox--7.0.0 版本 二、Vagrant安装 Vagrant官网&#xff1a;Vagrant by HashiCorp Vagrant镜像仓库&#xff1a;Discover Vagrant Boxes - Vagrant Cloud 这里采用Vagrant--2.4.1版本 在…

神经网络系列---权重初始化方法

文章目录 权重初始化方法Xavier初始化&#xff08;Xavier initialization&#xff09;Kaiming初始化&#xff0c;也称为He初始化LeCun 初始化正态分布与均匀分布Orthogonal InitializationSparse Initializationn_in和n_out代码实现 权重初始化方法 Xavier初始化&#xff08;X…

关于uniapp H5应用无法在触摸屏正常显示的处理办法

关于uniapp H5应用无法在触摸屏正常显示的处理办法 1、问题2、处理3、建议 1、问题 前几天&#xff0c; 客户反馈在安卓触摸大屏上无法正确打开web系统&#xff08;uni-app vue3开发的h5 应用&#xff09;&#xff0c;有些页面显示不出内容。该应用在 pc 端和手机端都可以正常…

【递归版】归并排序算法(1)

目录 MergeSort归并排序 整体思想 图解分析 代码实现 时间复杂度 递归&归并排序VS快速排序 MergeSort归并排序 归并排序&#xff08;MERGE-SORT&#xff09;是建立在归并操作上的一种有效的排序算法&#xff0c;该算法是采用分治法&#xff08;Divide and Conquer&a…

元宇宙与大模型的关系

元宇宙与大模型之间存在密切的关系&#xff0c;它们可以相辅相成&#xff0c;共同构建一个更加复杂、真实和全面的虚拟世界。 元宇宙的概念&#xff1a;元宇宙是一个包容性的虚拟世界&#xff0c;涵盖了虚拟现实、增强现实和混合现实等多种技术&#xff0c;以及人工智能、区块…

Golang性能分析神器:pprof与火焰图实战揭秘

文章目录 性能分析的重要性性能分析的维度 pprof简介pprof的作用pprof的工作原理 使用pprof进行性能分析采样方式数据分析实战案例火焰图 深入理解pprof的采样机制CPU采样如何启动CPU采样 Goroutine采样如何启动Goroutine采样 内存采样如何启动内存采样 阻塞和锁竞争采样如何启…