nodehub

package module
v0.0.0-...-9d91b79 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 3, 2024 License: BSD-3-Clause Imports: 17 Imported by: 4

README

Nodehub

Go Reference License Go report

Nodehub是为社交类游戏、棋牌类游戏设计的服务器端框架。

Nodehub的通讯方式建立于gRPC基础之上,在使用Nodehub开发之前,需要对gRPC开发方式有所了解。

特性

  • 网关支持websocket、tcp、quic三种连接方式
  • 服务注册与发现(使用etcd
  • 服务节点负载均衡(允许自定义)
  • 有状态服务节点路由
  • 集群内事件广播(允许注册自定义事件)
  • 所有的节点均内置gRPC管理服务

架构

  • 整体架构由客户端、网关节点、服务节点以及基础服务组成
  • 由etcd实现服务注册与发现,nats(推荐)或redis实现服务间消息总线
  • 客户端通过websocket/tcp/quic方式与网关连接,客户端只会通过网关与服务节点联系,不会直接请求服务节点
  • 内部服务节点通过gRPC方式提供接口
  • 网关把收到的客户端消息转换为gRPC请求转发到相应的内部节点,然后再把收到的gRPC响应结果返回给客户端

消息格式

由于客户端并非直接向内部节点发送gRPC请求而是通过网关转发,因此客户端的上下行消息需要经过一定的包装才能实现gRPC over websocket的效果。

所有的客户端请求一律使用nodehub.Request类型,服务器端返回的消息类型一律使用nodehub.Reply类型。

在采用raw tcp socket/quic方式与网关通讯时,采用了简单的length + data的方式实现数据包(0长度的包为心跳包)。

client.proto文件内包含了客户端上下行消息的protobuf定义。

client.go内提供了websocket client和tcp client实现供参考和测试。

服务配置

每个节点在启动之后,都会向etcd注册自身配置信息,配置信息结构如下:

{
	"id": "",	// ulid,每次启动后自动生成
	"name": "",	// 节点名称
	"state": "ok",	// 节点状态
	"entrace": "ws://host:port/grpc",	// 网关入口地址,非网关节点没有值
	"grpc": {
		"endpoint": "ip:port",	// grpc服务监听地址
		"services": [
			{
				"code": 0,	// grpc服务枚举值
				"path": "/helloworld.Greeter",	// 服务路径
				"public": true,	// 是否允许客户端访问的公开服务
				"balancer": "random",	// 负载均衡策略
				"stateful": false,	// 是否有状态服务
				"allocation": "auto",	// 有状态节点分配方式
			}
		]
	}
}

state(节点状态)有如下枚举值:

  • ok 表示节点正常提供服务
  • lazy 用于有状态节点服务,表示不再接受新的客户端请求,但之前建立了路由状态的请求仍然可以处理
  • down 表示节点下线,不再接受任何请求

每个节点允许注册多个gRPC服务,考虑到方便滚动更新,建议不要把有状态节点和无状态节点注册到一起。

集群内的每个gRPC服务,都要有单独的服务代码grpc.services.code,这样网关才能够根据服务代码把客户端请求转发到相应的服务节点上。

grpc.services.path 是gRPC服务的http2注册路径,会在调用gRPC注册函数时(rpc.GRPCServer.RegisterService()方法)自动填写

grpc.services.public声明此服务属于公开服务还是私有服务,客户端只能向公开服务发起请求。游戏节点之间的调用逻辑可以放到私有服务上,与客户端接口隔离开。

grpc.services.balancer控制此服务的负载均衡方式,目前内置了四种策略:

  • random 随机
  • roundRobin 加权轮询
  • ipHash 根据客户端IP地址哈希
  • idHash 根据账号ID哈希

除了内置的负载均衡策略外,也支持注册自定义的其它负载均衡策略。

grpc.services.stateful声明此服务属于有状态服务还是无状态服务,有状态服务需要建立了路由关系才能接受客户端请求。

grpc.services.allocation控制有状态节点的分配方式,对无状态节点没有影响,允许的配置方式有:

  • auto 网关根据负载均衡策略,在可用节点中自动选择一个节点建立路由关系
  • server 服务器端自行分配,网关不会参与分配
  • client 客户端请求时,通过nodehub.Request.node_id指定,网关会把客户端与指定node_id的节点关系记录到路由表内,后续即使不指定node_id,对同一个服务的请求也会继续路由到之前指定的节点,直到客户端指定其它的node_id

auto分配方式适用于玩家个人信息类型的服务,玩家登录后访问个人信息时自动分配,节点和客户端的路由关系在整个游戏过程中持续存在。

serverclient适用于房间服务类型的节点请求,在房间创建之后才建立路由关系,玩家在游戏过程中可能会访问多个不同的房间节点。无论使用server还是client类型的分配策略,都能达到类似的效果,具体开发时可根据实际情况酌情使用。

gRPC使用约束

面向客户端的gRPC方法的返回结果,只能是nodehub.Replygoogle.protobuf.Empty两种类型,否则会导致客户端无法解析。

凡是要下行到客户端解析的真正message类型,需要单独定义类型枚举值,这样客户端才能根据nodehub.Reply.message_type的值,使用正确的类型把nodehub.Reply.data内的数据解码使用。

内部服务的gRPC方法没有任何这种限制,因为内部节点间是通过正常的gRPC方式直接通讯。

示例 (echo server)

./example/echo/ 实现了把客户端发送的消息内容原封不动返回的简单服务。

./example/chat/ 实现了一个非常非常简单的聊天室。

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func NewNodeClient

func NewNodeClient(registry *cluster.Registry, nodeID ulid.ULID) (nh.NodeClient, error)

NewNodeClient 节点管理服务客户端

Types

type Component

type Component interface {
	// Name 组件名称,仅用于显示
	Name() string

	// Start方法注意不要阻塞程序执行
	Start(ctx context.Context) error
	Stop(ctx context.Context)
}

Component 组件

type GatewayConfig

type GatewayConfig struct {
	Options []gateway.Option

	GRPCListen       string
	GRPCListener     net.Listener
	GRPCServerOption []grpc.ServerOption
}

GatewayConfig 网关配置

type Node

type Node struct {
	// contains filtered or unexported fields
}

Node 节点,一个节点上可以运行多个网络服务

func NewGatewayNode

func NewGatewayNode(registry *cluster.Registry, config GatewayConfig) *Node

NewGatewayNode 构造一个网关节点

func NewNode

func NewNode(name string, registry *cluster.Registry, option ...NodeOption) *Node

NewNode 构造函数

func (*Node) AddComponent

func (n *Node) AddComponent(c ...Component)

AddComponent 添加组件

组件的启动顺序与添加顺序一致 组件的停止顺序与添加顺序相反

func (*Node) ChangeState

func (n *Node) ChangeState(state cluster.NodeState) (err error)

ChangeState 改变节点状态

func (*Node) Entry

func (n *Node) Entry() cluster.NodeEntry

Entry 获取服务发现条目

func (*Node) ID

func (n *Node) ID() ulid.ULID

ID 获取节点ID

func (*Node) Serve

func (n *Node) Serve(ctx context.Context) error

Serve 启动所有组件

func (*Node) Shutdown

func (n *Node) Shutdown()

Shutdown 关闭节点

func (*Node) State

func (n *Node) State() cluster.NodeState

State 获取节点状态

type NodeOption

type NodeOption func(*Node)

NodeOption 节点选项

func WithGitVersion

func WithGitVersion(version string) NodeOption

WithGitVersion 设置git版本

func WithState

func WithState(state cluster.NodeState) NodeOption

WithState 设置节点初始状态

Directories

Path Synopsis
component
rpc
example module
internal
mq
proto
nh

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL