【干货分享超详细】due 基于Go语言开发的高性能分布式游戏服务器框架

1.介绍

due是一款基于Go语言开发的轻量级、高性能分布式游戏服务器框架。 其中,模块设计方面借鉴了kratos的模块设计思路,旨在为游戏服务器开发提供完善、高效、标准化的解决方案。 框架自创建至今已在多个企业级游戏项目中上线实践过,稳定性有充分的保障。

2.优势

  • :bulb: 简单性:架构简单,源码简洁易理解。

  • :mountain_cableway: 便捷性:仅暴露必要的调用接口,减轻开发者的心智负担。

  • :rocket: 高性能:框架原生实现集群通信方案,普通机器单线程也能轻松实现20W的TPS。

  • :ice_cube: 标准化:框架原生提供标准化的开发规范,无论多么复杂的项目也能轻松应对。

  • :airplane:

高效性:框架原生提供tcp、kcp、ws等协议的服务器,方便开发者快速构建各种类型的网关服务器。

  • :balance_scale: 稳定性:所有发布的正式版本均已通过内部真实业务的严格测试,具备较高的稳定性。

  • :tickets: 扩展性:采用良好的接口设计,方便开发者设计实现自有功能。

  • :key: 平滑性:引入■■量,通过控制服务注册中心来实现优雅地滚动更新。

  • :nut_and_bolt: 扩容性:通过优雅的路由分发机制,理论上可实现无限扩容。

  • :wrench: 易调试:框架原生提供了tcp、kcp、ws等协议的客户端,方便开发者进行独立的调试全流程调试。

  • :toolbox: 可管理:提供完善的后台管理接口,方便开发者快速实现自定义的后台管理功能。

3.功能

  • 网关:支持tcp、kcp、ws等协议的网关服务器。

  • 日志:支持std、zap、logrus、aliyun、tencent等多种日志组件。

  • 注册:支持consul、etcd、nacos等多种服务注册中心。

  • 协议:支持json、protobuf、msgpack等多种通信协议。

  • 配置:支持consul、etcd、nacos等多种配置中心;并支持json、yaml、toml、xml等多种文件格式。

  • 通信:支持grpc、rpcx等多种高性能通信方案。

  • 重启:支持服务器的平滑重启。

  • 事件:支持redis、nats、kafka、rabbitMQ等事件总线实现方案。

  • 加密:支持rsa、ecc等多种加密方案。

  • 服务:支持grpc、rpcx等多种微服务解决方案。

  • 灵活:支持单体、分布式等多种架构方案。

  • Web:提供http协议的fiber服务器及swagger文档解决方案。

  • 工具:提供due-cli脚手架工具箱,可快速构建集群项目。

  • 缓存:支持redis、memcache等多种常用的缓存方案。

  • Actor:提供完善actor模型解决方案。

  • 分布式锁:支持redis、memcache等多种分布式锁解决方案。

4.下一期新功能规划

  • 高性能日志库

5.特殊说明

在due交流群中经常有小伙伴提及到Gate、Node、Mesh之间到底是个什么关系,这里就做一个统一的解答

  • Gate:网关服,主要用于管理客户端连接,接收客户端的路由消息,并分发路由消息到不同的的Node节点服。

  • Node: 节点服,作为整个集群系统的核心组件,主要用于核心逻辑业务的编写。Node节点服务可以根据业务需要做成有状态或无状态的节点,当作为无状态的节点时,Node节点与Mesh微服务基本无异;但当Node节点作为有状态节点时,Node节点便不能随意更新进行重启操作。故而Node与Mesh分离的业务场景的价值就体现出来了。

  • Mesh:微服务,主要用于无状态的业务逻辑编写。Mesh能做的功能Node一样可以完成,如何选择完全取决于自身业务场景,开发者可以根据自身业务场景灵活搭配。

6.通信协议

在due框架中,通信协议统一采用size+header+route+seq+message的格式:

1.数据包

 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7+---------------------------------------------------------------+-+-------------+-------------------------------+-------------------------------+|                              size                             |h|   extcode   |             route             |              seq              |+---------------------------------------------------------------+-+-------------+-------------------------------+-------------------------------+|                                                                message data ...                                                               |+-----------------------------------------------------------------------------------------------------------------------------------------------+

2.心跳包

 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7+---------------------------------------------------------------+-+-------------+---------------------------------------------------------------+|                              size                             |h|   extcode   |                      heartbeat time (ns)                      |+---------------------------------------------------------------+-+-------------+---------------------------------------------------------------+

size: 4 bytes

  • 包长度位

  • 固定长度为4字节,且不可修改

header: 1 bytes

h: 1 bit

  • 心跳标识位

  • %x0 表示数据包

  • %x1 表示心跳包

extcode: 7 bit

  • 扩展操作码

  • 暂未明确定义具体操作码

route: 1 bytes | 2 bytes | 4 bytes

  • 消息路由

  • 默认采用2字节,可通过打包器配置packet.routeBytes进行修改

  • 不同的路由对应不同的业务处理流程

  • 心跳包无消息路由位

  • 此参数由业务打包器打包,服务器开发者和客户端开发者均要关心此参数

seq: 0 bytes | 1 bytes | 2 bytes | 4 bytes

  • 消息序列号

  • 默认采用2字节,可通过打包器配置packet.seqBytes进行修改

  • 可通过将打包器配置packet.seqBytes设置为0来屏蔽使用序列号

  • 消息序列号常用于请求/响应模型的消息对儿的确认

  • 心跳包无消息序列号位

  • 此参数由业务打包器packet.Packer打包,服务器开发者和客户端开发者均要关心此参数

message data: n bytes

  • 消息数据

  • 心跳包无消息数据

  • 此参数由业务打包器packet.Packer打包,服务器开发者和客户端开发者均要关心此参数

heartbeat time: 8 bytes

  • 心跳数据

  • 数据包无心跳数据

  • 上行心跳包无需携带心跳数据,下行心跳包默认携带8 bytes的服务器时间(ns),可通过网络库配置进行设置是否携带下行包时间信息

  • 此参数由网络框架层自动打包,服务端开发者不关注此参数,客户端开发者需关注此参数

7.相关工具链

1.安装protobuf编译器(使用场景:开发mesh微服务)

  • Linux, using apt or apt-get, for example:

apt install -y protobuf-compiler protoc --version # Ensure compiler version is 3+

  • MacOS, using Homebrew:

brew install protobuf protoc --version # Ensure compiler version is 3+

  • Windows, download from Github:

2.安装protobuf go代码生成工具(使用场景:开发mesh微服务)

go install google.golang.org/protobuf/cmd/protoc-gen-go@latest

3.安装grpc代码生成工具(使用场景:使用GRPC组件开发mesh微服务)

go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest

4.安装rpcx代码生成工具(使用场景:使用RPCX组件开发mesh微服务)

go install github.com/rpcxio/protoc-gen-rpcx@latest

5.安装gorm dao代码生成工具(使用场景:使用GORM作为数据库orm)

go install github.com/dobyte/gorm-dao-generator@latest

6.安装mongo dao代码生成工具(使用场景:使用MongoDB作为数据库orm)

go install github.com/dobyte/mongo-dao-generator@latest

8.配置中心

1.功能介绍

配置中心主要定位于业务的配置管理,提供快捷灵活的配置方案。支持完善的读取、修改、删除、热更新等功能。

2.支持组件

  • file

  • etcd

  • consul

  • nacos

9.注册中心

1.功能介绍

注册中心用于集群实例的服务注册和发现。支撑整个集群的无感知停服、重启、动态扩容等功能。

2.支持组件

  • etcd

  • consul

  • nacos

10.网络模块

1.功能介绍

网络模块主要以组件的形式集成于网关模块,为网关提供灵活的网络通信支持。

2.支持组件

  • tcp

  • kcp

  • ws

11.快速开始

下面我们就通过两段简单的代码来体验一下due的魅力,Let’s go~~

1.启动组件

docker-compose up

docker-compose.yaml文件已在docker目录中备好,可以直接取用

2.获取框架

go get -u github.com/dobyte/due/v2@latest go get -u github.com/dobyte/due/locate/redis/v2
3.构建Gate服务器

package mainimport ( "github.com/dobyte/due/locate/redis/v2""github.com/dobyte/due/network/ws/v2""github.com/dobyte/due/registry/consul/v2""github.com/dobyte/due/v2"“github.com/dobyte/due/v2/cluster/gate”)funcmain() { // 创建容器container:=due.NewContainer() // 创建服务器server:=ws.NewServer() // 创建用户定位器locator:=redis.NewLocator() // 创建服务发现registry:=consul.NewRegistry() // 创建网关组件component:=gate.NewGate( gate.WithServer(server), gate.WithLocator(locator), gate.WithRegistry(registry), ) // 添加网关组件container.Add(component) // 启动容器container.Serve() }

4.启动Gate服务器

$ go run main.go ____ __ ________ / __ / / / / / / / / / / / / __/ / // / // / /_ //_/___/ ┌──────────────────────────────────────────────────────┐| [Website] https://github.com/dobyte/due || [Version] v2.1.0 |└──────────────────────────────────────────────────────┘ ┌────────────────────────Global────────────────────────┐| PID: 27159 || Mode: debug |└──────────────────────────────────────────────────────┘ ┌─────────────────────────Gate─────────────────────────┐| Name: gate || Link: 172.22.243.151:46545 || Server: [ws] 0.0.0.0:3553 || Locator: redis || Registry: consul |└──────────────────────────────────────────────────────┘

5.构建Node服务器

package mainimport ( “fmt”"github.com/dobyte/due/locate/redis/v2""github.com/dobyte/due/registry/consul/v2""github.com/dobyte/due/v2"“github.com/dobyte/due/v2/cluster/node”“github.com/dobyte/due/v2/codes”“github.com/dobyte/due/v2/log”“github.com/dobyte/due/v2/utils/xtime”)constgreet=1funcmain() { // 创建容器container:=due.NewContainer() // 创建用户定位器locator:=redis.NewLocator() // 创建服务发现registry:=consul.NewRegistry() // 创建节点组件component:=node.NewNode( node.WithLocator(locator), node.WithRegistry(registry), ) // 初始化监听initListen(component.Proxy()) // 添加节点组件container.Add(component) // 启动容器container.Serve() }// 初始化监听funcinitListen(proxy*node.Proxy) { proxy.Router().AddRouteHandler(greet, false, greetHandler) }typegreetReqstruct { Messagestringjson:"message"}typegreetResstruct { Codeintjson:"code"Messagestringjson:"message"}funcgreetHandler(ctx node.Context) { req:=&greetReq{} res:=&greetRes{} deferfunc() { iferr:=ctx.Response(res); err!=nil { log.Errorf(“response message failed: %v”, err) } }() iferr:=ctx.Parse(req); err!=nil { log.Errorf(“parse request message failed: %v”, err) res.Code=codes.InternalError.Code() return } log.Info(req.Message) res.Code=codes.OK.Code() res.Message=fmt.Sprintf(“I’m server, and the current time is: %s”, xtime.Now().Format(xtime.DateTime)) }

6.启动Node服务器

$ go run main.go ____ __ ________ / __ / / / / / / / / / / / / __/ / // / // / /_ //_/___/ ┌──────────────────────────────────────────────────────┐| [Website] https://github.com/dobyte/due || [Version] v2.1.0 |└──────────────────────────────────────────────────────┘ ┌────────────────────────Global────────────────────────┐| PID: 27390 || Mode: debug |└──────────────────────────────────────────────────────┘ ┌─────────────────────────Node─────────────────────────┐| Name: node || Link: 172.22.243.151:37901 || Codec: json || Locator: redis || Registry: consul || Encryptor: - || Transporter: - |└──────────────────────────────────────────────────────┘

7.构建测试客户端

package mainimport ( “fmt”"github.com/dobyte/due/eventbus/nats/v2""github.com/dobyte/due/network/ws/v2""github.com/dobyte/due/v2"“github.com/dobyte/due/v2/cluster”“github.com/dobyte/due/v2/cluster/client”“github.com/dobyte/due/v2/eventbus”“github.com/dobyte/due/v2/log”“github.com/dobyte/due/v2/utils/xtime”“time”)constgreet=1funcmain() { // 初始化事件总线eventbus.SetEventbus(nats.NewEventbus()) // 创建容器container:=due.NewContainer() // 创建客户端组件component:=client.NewClient( client.WithClient(ws.NewClient()), ) // 初始化监听initListen(component.Proxy()) // 添加客户端组件container.Add(component) // 启动容器container.Serve() }// 初始化监听funcinitListen(proxyclient.Proxy) { // 监听组件启动proxy.AddHookListener(cluster.Start, startHandler) // 监听连接建立proxy.AddEventListener(cluster.Connect, connectHandler) // 监听消息回复proxy.AddRouteHandler(greet, greetHandler) }// 组件启动处理器funcstartHandler(proxyclient.Proxy) { if_, err:=proxy.Dial(); err!=nil { log.Errorf(“gate connect failed: %v”, err) return } }// 连接建立处理器funcconnectHandler(connclient.Conn) { doPushMessage(conn) }// 消息回复处理器funcgreetHandler(ctxclient.Context) { res:=&greetRes{} iferr:=ctx.Parse(res); err!=nil { log.Errorf(“invalid response message, err: %v”, err) return } ifres.Code!=0 { log.Errorf(“node response failed, code: %d”, res.Code) return } log.Info(res.Message) time.AfterFunc(time.Second, func() { doPushMessage(ctx.Conn()) }) }// 推送消息funcdoPushMessage(conn*client.Conn) { err:=conn.Push(&cluster.Message{ Route: 1, Data: &greetReq{ Message: fmt.Sprintf(“I’m client, and the current time is: %s”, xtime.Now().Format(xtime.DateTime)), }, }) iferr!=nil { log.Errorf(“push message failed: %v”, err) } }typegreetReqstruct { Messagestringjson:"message"}typegreetResstruct { Codeintjson:"code"Messagestringjson:"message"}

8.启动客户端

$ go run main.go ____ __ ________ / __ / / / / / / / / / / / / __/ / // / // / /_ //_/___/ ┌──────────────────────────────────────────────────────┐| [Website] https://github.com/dobyte/due || [Version] v2.1.0 |└──────────────────────────────────────────────────────┘ ┌────────────────────────Global────────────────────────┐| PID: 27801 || Mode: debug |└──────────────────────────────────────────────────────┘ ┌────────────────────────Client────────────────────────┐| Name: client || Codec: json || Protocol: ws || Encryptor: - |└──────────────────────────────────────────────────────┘ INFO[2024/07/03 14:53:08.969845] main.go:72 [I’m server, and the current time is: 2024-07-03 14:53:08]INFO[2024/07/03 14:53:09.983827] main.go:72 [I’m server, and the current time is: 2024-07-03 14:53:09] INFO[2024/07/03 14:53:10.986592] main.go:72 [I’m server, and the current time is: 2024-07-03 14:53:10]INFO[2024/07/03 14:53:11.988530] main.go:72 [I’m server, and the current time is: 2024-07-03 14:53:11] INFO[2024/07/03 14:53:12.991217] main.go:72 [I’m server, and the current time is: 2024-07-03 14:53:12]INFO[2024/07/03 14:53:13.995049] main.go:72 [I’m server, and the current time is: 2024-07-03 14:53:13]

12.压力测试

1.压测机器

Ubuntu 20.04.6 LTS 13th Gen Intel(R) Core(TM) i5-13400F 16GB

2.压测结果

                ____  __  ________                    / __ \/ / / / ____/                   / / / / / / / __/                  / /_/ / /_/ / /___                 /_____/\____/_____/ ┌──────────────────────────────────────────────────────┐| [Website] https://github.com/dobyte/due              || [Version] v2.1.0                                     |└──────────────────────────────────────────────────────┘ ┌────────────────────────Global────────────────────────┐| PID: 28660                                           || Mode: debug                                          |└──────────────────────────────────────────────────────┘ ┌────────────────────────Client────────────────────────┐| Name: client                                         || Codec: json                                          || Protocol: tcp                                        || Encryptor: -                                         |└──────────────────────────────────────────────────────┘ server               : tcp concurrency          : 50 latency              : 4.741343s data size            : 1.00KB sent requests        : 1000000 received requests    : 1000000 throughput (TPS)     : 210910 -------------------------------- server               : tcp concurrency          : 100 latency              : 4.697039s data size            : 1.00KB sent requests        : 1000000 received requests    : 1000000 throughput (TPS)     : 212900 -------------------------------- server               : tcp concurrency          : 200 latency              : 4.447127s data size            : 1.00KB sent requests        : 1000000 received requests    : 1000000 throughput (TPS)     : 224864 -------------------------------- server               : tcp concurrency          : 300 latency              : 5.616742s data size            : 1.00KB sent requests        : 1000000 received requests    : 1000000 throughput (TPS)     : 178039 -------------------------------- server               : tcp concurrency          : 400 latency              : 4.726411s data size            : 1.00KB sent requests        : 1000000 received requests    : 1000000 throughput (TPS)     : 211577 -------------------------------- server               : tcp concurrency          : 500 latency              : 5.054949s data size            : 1.00KB sent requests        : 1000000 received requests    : 1000000 throughput (TPS)     : 197825 -------------------------------- server               : tcp concurrency          : 1000 latency              : 5.486149s data size            : 1.00KB sent requests        : 1000000 received requests    : 1000000 throughput (TPS)     : 182277 -------------------------------- server               : tcp concurrency          : 1000 latency              : 7.753779s data size            : 2.00KB sent requests        : 1000000 received requests    : 1000000 throughput (TPS)     : 128969 --------------------------------

本测试结果仅供参考,详细测试用例代码请查看due-benchmark

13.其他组件

  1. 日志组件
  1. 网络组件
  1. 注册发现
  1. 传输组件
  1. 定位组件
  1. 事件总线
  1. Web组件
  1. 配置中心
  1. 缓存组件
  1. 分布式锁组件

14.其他客户端

  • due-client-ts

  • due-client-shape

15.详细示例

  • due-examples

  • due-chat

  • due-doudizhu-server 高性能分布式游戏服务器商业实战案例-斗地主服务器

开发交流反馈
攻击防御技术交流群1 (1)

4赞

这么好用的框架怎么没人顶一下啊 :rofl:

顶顶顶 ,刚看来gyalin框架也还不错 我整理整理

插眼,后期学习传送