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 的话,可以给个星星鼓励一下哦 ~~

[开源] gnet: 一个轻量级且高性能的 Golang 网络库的更多相关文章

  1. gnet: 一个轻量级且高性能的 Go 网络框架 使用笔记

    一个偶然的机会接触到了golang,被它的高并发传说所吸引,就开始学这门语言,越学感觉越有意思^_^ 注册了博客园这么多年,第一次写东西,年纪大了,脑子不好使了,就得写下来,记下来,为了自己以后查阅, ...

  2. 专注于HTTP的高性能高易用性网络库:Fslib.network库

    博客列表页:http://blog.fishlee.net/tag/fslib-network/ 原创FSLib.Network库(目前专注于HTTP的高性能高易用性网络库) FSLib.Networ ...

  3. 公布一个基于 Reactor 模式的 C++ 网络库

    公布一个基于 Reactor 模式的 C++ 网络库 陈硕 (giantchen_AT_gmail) Blog.csdn.net/Solstice 2010 Aug 30 本文主要介绍 muduo 网 ...

  4. 基于c++11新标准开发一个支持多线程高并发的网络库

    背景 新的c++11标准出后,c++语法得到了非常多的扩展,比起以往不论什么时候都要灵活和高效,提高了程序编码的效率,为软件开发者节省了不少的时间. 之前我也写过基于ACE的网络server框架,但A ...

  5. 淘宝、天猫又开源了一个动态化、高性能的UI框架

    前言 淘宝.天猫一直致力于解决 页面动态化的问题 在2017年的4月发布了v1.0解决方案:Tangram模型 及其对应的 Android库 vlayout,该解决方案在手机淘宝.天猫 Android ...

  6. 高性能高并发网络库:StateThreads

    StateThreads是一个C的网络程序开发库,提供了编写高性能.高并发.高可读性的网络程序的开发库,轻量级网络应用框架 共也就3000行C代码 网络程序(Internet Application) ...

  7. Golang网络库中socket阻塞调度源码剖析

    本文分析了Golang的socket文件描述符和goroutine阻塞调度的原理.代码中大部分是Go代码,小部分是汇编代码.完整理解本文需要Go语言知识,并且用Golang写过网络程序.更重要的是,需 ...

  8. Viper--方便好用的Golang 配置库

    前言 本文主要是为读者介绍一个轻便好用的Golang配置库viper 正文 viper 的功能   viper 支持以下功能:   1. 支持Yaml.Json. TOML.HCL 等格式的配置   ...

  9. Parse发布Bolts,一个面向iOS和Android的底层库集合

    转载自:http://www.infoq.com/cn/news/2014/02/parse-announces-bolts 数月前,Parse被Facebook收购.最近,它开源了一个面向iOS和A ...

随机推荐

  1. BERT论文解读

    本文尽量贴合BERT的原论文,但考虑到要易于理解,所以并非逐句翻译,而是根据笔者的个人理解进行翻译,其中有一些论文没有解释清楚或者笔者未能深入理解的地方,都有放出原文,如有不当之处,请各位多多包含,并 ...

  2. mac下安装jmeter

    jmeter官网下载 双击解压 命令行进入/Users/yanguobin/apache-jmeter-5.1.1/bin目录下,输入sh jmeter即可启动 也可以 配置环境变量 vim ~/.b ...

  3. day 20

    目录 一.继承初体验 二.寻找继承关系 三.继承背景下对象属性查找顺序 四.派生 五.子类派生出新的属性,并重复父类的属性 六.新式类与经典类(了解) 一.继承初体验 父类: class Parent ...

  4. KEIL软件中编译时出现的Error L6200E: symbol multiply defined ...的解决方法

    原因:如LCD.C文件使用了bmp.h中的image[ ]变量,那么就不能将#include"bmp.h"放在LCD.H中,要将#include"bmp.h"放 ...

  5. UVA - 11400 Lighting System Design

    题文: You are given the task to design a lighting system for a huge conference hall. After doing a lot ...

  6. CSS定位机制:浮动 float及清除浮动的常用方法

    CSS的定位机制 1.普通流(标准流) 默认状态,元素自动从左往右,从上往下排列 块元素的特征: 独占一行 可以设置宽高 如果不设置宽度,宽度默认为容器的100% 常见的块元素:div p h1-h6 ...

  7. SD卡学习笔记

    最近调试了SD卡,遇到了一些小问题,记录一下,分享一下. 1.    SD卡 卡槽旁边的一个小开关 我们平时见过的SD卡都是小的SD卡,可以放在手机上的.现在大多数的开发板上也是小的SD卡的卡槽,插入 ...

  8. vue-electron 使用sqlite3数据库,执行npm run build 报错 .NET Framework 2.0 SDK,Microsoft Visual Studio 2005[C:\temp\wechat\node_modules\sqlite3\build\binding.sln]

    问题描述 vue-electron 使用sqlite3数据库,执行npm run build 报错如下: .NET Framework 2.0 SDK,Microsoft Visual Studio ...

  9. Nmon监控结果分析

    一:CPU信息 SYS_SUMM图表: 1.折线图中蓝线为cpu占有率变化情况:粉线为磁盘IO的变化情况: 2.下面表各种左边的位磁盘的总体数据,包括如下几个: Avg tps during an i ...

  10. DM7经常使用的命令汇总

    由于DM7兼容oracle ,所以当你不知道某个命令时,大抵就是可以参照oracle的命令及语法,当然有极少的情况会不一样.常用命令如下: 1.连接登录 disql SYSDBA/SYSDBA@223 ...