# 5.2 节点服务器

# 5.2.1 基础介绍

节点服(node)作为游戏业务开发的核心模块,通常具备以下功能:

  • 处理经由网关服(gate)转发的来自客户端(client)的路由消息
  • 通过网关服(gate)下发消息到指定客户端(client)
  • 为其他后端服务器提供完善的调用接口

due (opens new window) 框架中,节点服(node)采用模块化的架构思路,开发者可以根据自身的业务情况任意搭配模块化组件。

# 5.2.2 调度模型

due (opens new window) 框架中,节点服(node)处理路由消息统一采用单线程调度。开发者可以根据自身业务需要对调度模型进行动态改变。

  • 单线程:在AddRouteHandler()注册的路由处理器下直接处理消息,此时调度模型就是单线程。
  • 多协程:在AddRouteHandler()注册的路由处理器下调用ctx.Task()将消息投递到协程池(goroutine pool)中进行处理,此时调度模型就从单线程转变为多协程。
  • Actor: 在AddRouteHandler()注册的路由处理器下调用ctx.Next()或ctx.Actor()将消息投递到actor中进行处理,此时调度模型就从单线程转变为Actor模型。

# 5.2.3 示例代码

以下完整示例详见:node-example (opens new window)

  1. 创建项目
$ mkdir node-example
1
  1. 安装依赖
$ cd node-example
$ go mod init node-example
$ go get github.com/dobyte/due/v2@v2.4.2
$ go get github.com/dobyte/due/locate/redis/v2@e5cd009
$ go get github.com/dobyte/due/registry/consul/v2@e5cd009
1
2
3
4
5
  1. 启动配置

文件位置:node-example/etc/etc.toml (opens new window)

# 进程号
pid = "./run/due.pid"
# 开发模式。支持模式:debug、test、release(设置优先级:配置文件 < 环境变量 < 运行参数 < mode.SetMode())
mode = "debug"
# 统一时区设置。项目中的时间获取请使用xtime.Now()
timezone = "Local"
# 容器关闭最大等待时间。支持单位:纳秒(ns)、微秒(us | µs)、毫秒(ms)、秒(s)、分(m)、小时(h)、天(d)。默认为0
shutdownMaxWaitTime = "0s"

[cluster.node]
    # 实例ID,集群中唯一。不填写默认自动生成唯一的实例ID
    id = ""
    # 实例名称
    name = "node"
    # 内建RPC服务器监听地址。不填写默认随机监听
    addr = ":0"
    # 是否将内部通信地址暴露到公网。默认为false
    expose = false
    # 编解码器。可选:json | proto。默认为proto
    codec = "json"
    # RPC调用超时时间,支持单位:纳秒(ns)、微秒(us | µs)、毫秒(ms)、秒(s)、分(m)、小时(h)、天(d)。默认为3s
    timeout = "3s"
    # 节点权重,用于节点无状态路由消息的加权轮询策略,权重值必需大于0才生效。默认为1
    weight = 1
    # 实例元数据
    [cluster.node.metadata]
        # 键值对,且均为字符串类型。由于注册中心的元数据参数限制,建议将键值对的数量控制在20个以内,键的字符长度控制在127个字符内,值得字符长度控制在512个字符内。
        key = "value"

[locate.redis]
    # 客户端连接地址
    addrs = ["127.0.0.1:6379"]
    # 数据库号
    db = 0
    # 用户名
    username = ""
    # 密码
    password = ""
    # 私钥文件
    keyFile = ""
    # 证书文件
    certFile = ""
    # CA证书文件
    caFile = ""
    # 最大重试次数
    maxRetries = 3
    # key前缀
    prefix = "due:locate"

[registry.consul]
    # 客户端连接地址,默认为127.0.0.1:8500
    addr = "127.0.0.1:8500"
    # 是否启用健康检查,默认为true
    healthCheck = true
    # 健康检查时间间隔(秒),仅在启用健康检查后生效,默认为10
    healthCheckInterval = 10
    # 健康检查超时时间(秒),仅在启用健康检查后生效,默认为5
    healthCheckTimeout = 5
    # 是否启用心跳检查,默认为true
    heartbeatCheck = true
    # 心跳检查时间间隔(秒),仅在启用心跳检查后生效,默认为10
    heartbeatCheckInterval = 10
    # 健康检测失败后自动注销服务时间(秒),默认为30
    deregisterCriticalServiceAfter = 30

[packet]
    # 字节序,默认为big。可选:little | big
    byteOrder = "big"
    # 路由字节数,默认为2字节
    routeBytes = 2
    # 序列号字节数,默认为2字节
    seqBytes = 2
    # 消息字节数,默认为5000字节
    bufferBytes = 5000
    # 是否携带服务器心跳时间
    heartbeatTime = false

[log]
    # 日志输出级别,可选:debug | info | warn | error | fatal | panic
    level = "info"
    # 堆栈的最低输出级别,可选:debug | info | warn | error | fatal | panic
    stackLevel = "error"
    # 时间格式,标准库时间格式
    timeFormat = "2006/01/02 15:04:05.000000"
    # 输出栈的跳过深度
    callSkip = 2
    # 是否启用调用文件全路径
    callFullPath = true
    # 日志输出终端
    terminals = ["console", "file"]
    # 控制台同步器配置
    [log.console]
        # 日志输出格式,可选:text | json
        format = "text"
    # 文件同步器配置
    [log.file]
        # 输出文件路径
        path = "./log/due.log"
        # 日志输出格式,可选:text | json
        format = "text"
        # 文件最大留存时间,d:天、h:时、m:分、s:秒
        maxAge = "7d"
        # 文件最大尺寸限制,支持单位: B | K | KB | M | MB | G | GB | T | TB | P | PB | E | EB | Z | ZB,默认为100M
        maxSize = "100M"
        # 文件翻转方式,可选:none | year | month | week | day | hour,默认为none
        rotate = "none"
        # 文件翻转时是否对文件进行压缩
        compress = false
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
  1. 编写示例

文件位置:node-example/main.go (opens new window)

package main

import (
	"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"
)

// 路由号
const greet = 1

func main() {
	// 创建容器
	container := due.NewContainer()
	// 创建用户定位器
	locator := redis.NewLocator()
	// 创建服务发现
	registry := consul.NewRegistry()
	// 创建节点组件
	component := node.NewNode(
		node.WithLocator(locator),
		node.WithRegistry(registry),
	)
	// 初始化应用
	initApp(component.Proxy())
	// 添加节点组件
	container.Add(component)
	// 启动容器
	container.Serve()
}

// 初始化应用
func initApp(proxy *node.Proxy) {
	proxy.Router().AddRouteHandler(greet, greetHandler)
}

// 请求
type greetReq struct {
	Message string `json:"message"`
}

// 响应
type greetRes struct {
	Code    int    `json:"code"`
	Message string `json:"message"`
}

// 路由处理器
func greetHandler(ctx node.Context) {
	req := &greetReq{}
	res := &greetRes{}
	ctx.Defer(func() {
		if err := ctx.Response(res); err != nil {
			log.Errorf("response message failed: %v", err)
		}
	})

	if err := 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 tcp server, and the current time is: %s", xtime.Now().Format(xtime.DateTime))
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
  1. 启动服务
$ cd node-example
$ go run main.go
                    ____  __  ________
                   / __ \/ / / / ____/
                  / / / / / / / __/
                 / /_/ / /_/ / /___
                /_____/\____/_____/
┌──────────────────────────────────────────────────────┐
| [Website] https://github.com/dobyte/due              |
| [Version] v2.4.2                                     |
└──────────────────────────────────────────────────────┘
┌────────────────────────Global────────────────────────┐
| PID: 31428                                           |
| Mode: debug                                          |
| Time: 2025-10-30 11:48:19.4392339 +0800 CST          |
└──────────────────────────────────────────────────────┘
┌─────────────────────────Node─────────────────────────┐
| ID: 463cfcba-b543-11f0-b2d9-f4f19e1f0070             |
| Name: node                                           |
| Link: 192.168.2.202:55252                            |
| Codec: json                                          |
| Locator: redis                                       |
| Registry: consul                                     |
| Encryptor: -                                         |
| Transporter: -                                       |
└──────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26