[开源]gnet:一个轻量级且高性能的golang网络库(代码片段)

_andy _andy     2022-12-23     691

关键词:

技术图片
技术图片 技术图片 技术图片
技术图片 技术图片 技术图片

Github 主页

https://github.com/panjf2000/gnet

欢迎大家围观~~,目前还在持续更新,感兴趣的话可以 star 一下暗中观察哦。

简介

gnet 是一个基于 Event-Loop 事件驱动的高性能和轻量级网络库。这个库直接使用 epollkqueue 系统调用而非标准 Golang 网络包:net 来构建网络应用,它的工作原理类似于两个开源的网络库:libuvlibevent

这个项目存在的价值是提供一个在网络包处理方面能和 RedisHaproxy 这两个项目具有相近性能的Go 语言网络服务器框架。

gnet 的亮点在于它是一个高性能、轻量级、非阻塞的纯 Go 实现的传输层(TCP/UDP/Unix-Socket)网络库,开发者可以使用 gnet 来实现自己的应用层网络协议,从而构建出自己的应用层网络应用:比如在 gnet 上实现 HTTP 协议就可以创建出一个 HTTP 服务器 或者 Web 开发框架,实现 Redis 协议就可以创建出自己的 Redis 服务器等等。

gnet 衍生自另一个项目:evio,但是性能更好。

功能

  • 高性能 的基于多线程模型的 Event-Loop 事件驱动
  • 内置 Round-Robin 轮询负载均衡算法
  • 简洁的 APIs
  • 基于 Ring-Buffer 的高效内存利用
  • 支持多种网络协议:TCP、UDP、Unix Sockets
  • 支持两种事件驱动机制:Linux 里的 epoll 以及 FreeBSD 里的 kqueue
  • 支持异步写操作
  • 允许多个网络监听地址绑定在一个 Event-Loop 上
  • 灵活的事件定时器
  • SO_REUSEPORT 端口重用

核心设计

多线程/Go程模型

主从多 Reactors 模型

gnet 重新设计开发了一个新内置的多线程/Go程模型:『主从多 Reactors』,这也是 netty 默认的线程模型,下面是这个模型的原理图:

技术图片

它的运行流程如下面的时序图:


技术图片

主从多 Reactors + 线程/Go程池

你可能会问一个问题:如果我的业务逻辑是阻塞的,那么在 Event.React() 注册方法里的逻辑也会阻塞,从而导致阻塞 event-loop 线程,这时候怎么办?

正如你所知,基于 gnet 编写你的网络服务器有一条最重要的原则:永远不能让你业务逻辑(一般写在 Event.React() 里)阻塞 event-loop 线程,否则的话将会极大地降低服务器的吞吐量,这也是 netty 的一条最重要的原则。

我的回答是,现在我正在为 gnet 开发一个新的多线程/Go程模型:『带线程/Go程池的主从多 Reactors』,这个新网络模型将通过引入一个 worker pool 来解决业务逻辑阻塞的问题:它会在启动的时候初始化一个 worker pool,然后在把 Event.React()里面的阻塞代码放到 worker pool 里执行,从而避免阻塞 event-loop 线程,

这个模型还在持续开发中并且很快就能完成,模型的架构图如下所示:

技术图片

它的运行流程如下面的时序图:


技术图片

不过,在这个新的网络模型开发完成之前,你依然可以通过一些其他的外部开源 goroutine pool 来处理你的阻塞业务逻辑,在这里我推荐个人开发的一个开源 goroutine pool:ants,它是一个基于 Go 开发的高性能的 goroutine pool ,实现了对大规模 goroutine 的调度管理、goroutine 复用。

你可以在开发 gnet 网络应用的时候集成 ants 库,然后把那些阻塞业务逻辑提交到 ants 池里去执行,从而避免阻塞 event-loop 线程。

通信机制

gnet 的『主从 Reactors 多线程』模型是基于 Golang 里的 Goroutines的,一个 Reactor 挂载在一个 Goroutine 上,所以在 gnet 的这个网络模型里主 Reactor/Goroutine 与从 Reactors/Goroutines 有海量通信的需求,因此 gnet 里必须要有一个能在 Goroutines 之间进行高效率的通信的机制,我没有选择 Golang 里的主流方案:基于 Channel 的 CSP 模型,而是选择了性能更好、基于 Ring-Buffer 的 Disruptor 方案。

所以我最终选择了 go-disruptor:高性能消息分发队列 LMAX Disruptor 的 Golang 实现。

自动扩容的 Ring-Buffer

gnet 利用 Ring-Buffer 来缓存 TCP 流数据以及管理内存使用。

技术图片

开始使用

安装

$ go get -u github.com/panjf2000/gnet

使用示例

gnet 来构建网络服务器是非常简单的,只需要把你关心的事件注册到 gnet.Events 里面,然后把它和绑定的监听地址一起传递给 gnet.Serve 方法就完成了。在服务器开始工作之后,每一条到来的网络连接会在各个事件之间传递,如果你想在某个事件中关闭某条连接或者关掉整个服务器的话,直接把 gnet.Action 设置成 Cosed 或者 Shutdown就行了。

Echo 服务器是一种最简单网络服务器,把它作为 gnet 的入门例子在再合适不过了,下面是一个最简单的 echo server,它监听了 9000 端口:

不带阻塞逻辑的 echo 服务器

package main

import (
    "log"

    "github.com/panjf2000/gnet"
)

func main() 
    var events gnet.Events
    events.Multicore = true
    events.React = func(c gnet.Conn) (out []byte, action gnet.Action) 
        top, tail := c.ReadPair()
        out = append(top, tail...)
        c.ResetBuffer()
        if trace 
            log.Printf("%s", strings.TrimSpace(string(top)+string(tail)))
        
        return
    
    log.Fatal(gnet.Serve(events, "tcp://:9000"))

正如你所见,上面的例子里 gnet 实例只注册了一个 React 事件。一般来说,主要的业务逻辑代码会写在这个事件方法里,这个方法会在服务器接收到客户端写过来的数据之时被调用,然后处理输入数据(这里只是把数据 echo 回去)并且在处理完之后把需要输出的数据赋值给 out 变量然后返回,之后你就不用管了,gnet 会帮你把数据写回客户端的。

带阻塞逻辑的 echo 服务器

package main

import (
    "log"
    "time"

    "github.com/panjf2000/gnet"
    "github.com/panjf2000/ants"
)

func main() 
    var events gnet.Events
    events.Multicore = true
    
    poolSize := 256 * 1024
    pool, _ := ants.NewPool(poolSize, ants.WithNonblocking(true))
    defer pool.Release()
    
    events.React = func(c gnet.Conn) (out []byte, action gnet.Action) 
        data := c.ReadBytes()
        c.ResetBuffer()
        // Use ants pool to unblock the event-loop.
        _ = pool.Submit(func() 
            time.Sleep(1 * time.Second)
            c.AsyncWrite(data)
        )
        return
    
    log.Fatal(gnet.Serve(events, "tcp://:9000"))

正如我在『主从多 Reactors + 线程/Go程池』那一节所说的那样,如果你的业务逻辑里包含阻塞代码,那么你应该把这些阻塞代码变成非阻塞的,比如通过把这部分代码通过 goroutine 去运行,但是要注意一点,如果你的服务器处理的流量足够的大,那么这种做法将会导致创建大量的 goroutines 极大地消耗系统资源,所以我一般建议你用 goroutine pool 来做 goroutines 的复用和管理,以及节省系统资源。

I/O 事件

gnet 目前支持的 I/O 事件如下:

  • OnInitComplete 当 server 初始化完成之后调用。
  • OnOpened 当连接被打开的时候调用。
  • OnClosed 当连接被关闭的时候调用。
  • React 当 server 端接收到从 client 端发送来的数据的时候调用。(你的核心业务代码一般是写在这个方法里)
  • Tick 服务器启动的时候会调用一次,之后就以给定的时间间隔定时调用一次,是一个定时器方法。
  • PreWrite 预先写数据方法,在 server 端写数据回 client 端之前调用。

多地址绑定

// 在同一个 Server 上同时绑定 TCP 和 Unix-Socket 两个地址
gnet.Serve(events, "tcp://:9000", "unix://socket")

定时器

Tick 会每隔一段时间触发一次,间隔时间你可以自己控制,设定返回的 delay 变量就行。

定时器的第一次触发是在 gnet.Serving 事件之后。

events.Tick = func() (delay time.Duration, action Action)
    log.Printf("tick")
    delay = time.Second
    return

UDP 支持

gnet 支持 UDP 协议,在 gnet.Serve 里绑定 UDP 地址即可,gnet 的 UDP 支持有如下的特性:

  • 数据进入服务器之后立刻写回客户端,不做缓存。
  • OnOpenedOnClosed 这两个事件在 UDP 下不可用,唯一可用的事件是 React

使用多核

Events.Multicore 参数指定了 gnet 是否会使用多核来进行服务,如果是 true 的话就会使用多核,否则就是单核运行,利用的核心数一般是机器的 CPU 数量。

负载均衡

gnet 目前内置的负载均衡算法是轮询调度 Round-Robin,暂时不支持自定制。

SO_REUSEPORT 端口复用

服务器支持 SO_REUSEPORT 端口复用特性,允许多个 sockets 监听同一个端口,然后内核会帮你做好负载均衡,每次只唤醒一个 socket 来处理 accept 请求,避免惊群效应。

开启这个功能也很简单,在要绑定的监听地址后面设置 reuseport=true 即可:

gnet.Serve(events, "tcp://:9000?reuseport=true"))

性能测试

Linux (epoll)

系统参数

# Machine information
        OS : Ubuntu 18.04/x86_64
       CPU : 8 Virtual CPUs
    Memory : 16.0 GiB

# Go version and configurations
Go Version : go1.12.9 linux/amd64
GOMAXPROCS=8

同类型的网络库性能对比:

Echo Server

技术图片

HTTP Server

技术图片

FreeBSD (kqueue)

系统参数

# Machine information
        OS : macOS Mojave 10.14.6/x86_64
       CPU : 4 CPUs
    Memory : 8.0 GiB

# Go version and configurations
Go Version : go version go1.12.9 darwin/amd64
GOMAXPROCS=4

Echo Server

技术图片

HTTP Server

技术图片

证书

gnet 的源码允许用户在遵循 MIT 开源证书 规则的前提下使用。

相关文章

待做事项

gnet 还在持续开发的过程中,所以这个仓库的代码和文档会一直持续更新,如果你对 gnet 感兴趣的话,欢迎给这个开源库贡献你的代码,还有你要是喜欢 gnet 的话,可以给个星星鼓励一下哦 ~~

[c++][转载]一个轻量级高性能的c++web框架oat++(代码片段)

一个轻量级、高性能的C++Web框架开源前哨说起Web开发,大多数人会想到Java、Python、Golang... 因为它们的主流Web框架有很多,Java有非常知名的Spring全家桶,Python有大而全的Django、小而精的Flask、高性能的Tornado࿰... 查看详情

gnet框架

..., 这时候采取异步网络模型就会降低很多资源,相当于一个subreact管理了很多连接,这样就少开了很多协程,同时这样也存在一个问题,就是不让你的subreact阻塞,因为一旦阻塞,就让影响很多连接3:把阻塞的操作扔到工作池 查看详情

github开源推荐|一个轻量级高性能的c++web框架(代码片段)

星标/置顶 公众号👇,硬核文章第一时间送达! Github开源推荐专注分享GitHub上有趣、好玩的开源项目,以帮助大家提高编程技巧,找到编程乐趣。如果你对开源感兴趣,想和大家分享一些优质项目,... 查看详情

asp.netcore读写任意文件

参考技术AASP.NETCore是一个免费的,开源的,高性能,轻量级且跨平台的框架,用于构建基于云的应用程序,例如Web应用程序,IoT应用程序和移动后端。它旨在在云和本地环境中运行。与.NETCore一样,它以最小的开销进行模块化设... 查看详情

golang网络框架netpoll(multi-reactor模型)核心源码分析(代码片段)

...工程开发中绝大部分的情况都会优先考虑采用已有的一些开源网络框架来做功能的开发。网络框架不同的语言有不同的实现,例如java中大名鼎鼎的netty,再比如c++中的libevent、boost::asio、muduo等,golang中目前在开源社区比较有影响... 查看详情

开源|一个轻量级的前端低代码框架

...周刊VIP会员」圈子,每日更新,精彩不断。Foxpage是一个轻量级的前端低代码框架,借助Foxpage可以让前端项目用低代码的方式进行迭代。Foxpage重点在前端,关注前端页面的整个生命周期,希望成为一个易用,灵活,开放且百搭的... 查看详情

fastdfs开源的轻量级分布式文件系统部署(代码片段)

1.技术背景介绍;FastDFS是一个开源的轻量级分布式文件系统,它对文件进行管理,功能包括:文件存储、文件同步、文件访问(文件上传、文件下载)等,解决了大容量存储和负载均衡的问题。特别适合以文件为载体的在线服务... 查看详情

字节跳动开源的一个golang微服务http框架

...f0c;并结合字节跳动内部的需求,使其具有高易用性、高性能、高扩展性等特点,目前在字节跳动内部已广泛使用。如今越来越多的微服务选择使用Golang,如果对微服务性能有要求,又希望框架能够充分满足内部的... 查看详情

你用过哪些好用的开源软件?

...:ApacheDubbo(incubating)|ˈdʌbəʊ|是一款高性能、轻量级的开源JavaRPC框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。3、经典模块化前端框架Layui官网:https://www... 查看详情

golang之单元测试

基本介绍:  Go语言中自带的一个轻量级的测试框架testing和自带的gotest命令来实现单元测试和性能测试,testing框架和其他语言中的测试框架类似,可以基于这个框架写针对相应函数的测试用例,也可以基于该框架写相应的压... 查看详情

开源.net分享一个前后端分离的轻量级内容管理框架

开发框架要考虑的面太多了:安全、稳定、性能、效率、扩展、整洁,还要经得起实践的考验,从零开发一个可用的框架,是很耗时费神的工作。网上很多开源的框架,为何还要自己开发?我是基于以下两点:没找到合适的:安... 查看详情

一款针对efcore轻量级分表分库读写分离的开源项目

...我们零成本的接入。项目简介这是一个针对EFCode高性能、轻量级分表分库、读写分离开源项目,可以让我们方便快速接入、或者改造原有项目,几乎零成本接入。项目特性1、分表:时间分表、自定义分表、多表查询... 查看详情

10个让你爱不释手的python开源框架

...及全功能的管理后台。相关推荐:《Python教程》1.Cubes:轻量级PythonOLAP框架Cubes是一个轻量级Python框架,包含OLAP、多维数据分析和浏览聚合数据(aggregateddata)等工具。2.Kartograph.py:创造矢量地图的轻量级Python框架Kartograph是一个P... 查看详情

分享一个简单且轻量级的日志库:log.c

关注+星标公众号,不错过精彩内容转自|老吴嵌入式对于嵌入式底层应用开发,基本离不开日志功能,今天一个开源的log.c代码,简单到了开箱即用的级别。log.c是什么?https://github.com/rxi/log.c简单地说,l... 查看详情

分享一个简单且轻量级的日志库:log.c

关注+星标公众号,不错过精彩内容转自|老吴嵌入式对于嵌入式底层应用开发,基本离不开日志功能,今天一个开源的log.c代码,简单到了开箱即用的级别。log.c是什么?https://github.com/rxi/log.c简单地说,l... 查看详情

go开源宝藏基于golang语法的性能调优技巧(字符串拼接)(代码片段)

文章目录字符串拼接参考链接字符串拼接我们一般使用字符串拼接方式有三种直接拼接str+="sum"fmt.Sprintf(“%s”,xxxxx)使用string.Builder使用bytes.Builder使用byte进行拼接我们先来写一个benchmark去测试每一种字符串拼接的情况... 查看详情

最常用python开源框架都有哪些

...客户端和服务器。支持TCP和UDP。Flask:一个用Python编写的轻量级Web应用框架Flask是一个使用Python编写的轻量级Web应用框架。基于WerkzeugWSGI工具箱和Jinja2模板引擎。Flask也被称为“microframework”,因为它使用简单的核心,用extension增... 查看详情

golang怎么实现横向扩展

...项目中使用同时做开源的.说到Skynet,这是一个极好的开源轻量级游戏服务器框架.使用lua的coroutine模拟goroutine,同步+多线程逻辑,用C底层帮你处理了复杂的Actor模型.留给上层只是发发消息,管理下id,很是惬意.再加上lua天生动态语言, 查看详情