Skip to content

Cluster

Bruce edited this page Sep 21, 2025 · 1 revision

sidebar_position: 6

节点间通信

设置启动选项

使用默认启动选项创建的进程,适合用来编写一些工具脚本,对于游戏服务器进程,通常需要设置一些选项的:

  • thread: 工作线程数 默认是cpu核心数
  • enable_stdout: 是否打印标准输出 默认是true
  • logfile: 日志文件路径 默认不输出日志文件
  • loglevel: 日志等级, 默认 DEBUG. 可选 DEBUG,INFO,WARN,ERROR
  • path: lua模块搜索路径,默认会包含lualibservice路径

想要设置启动选项,必须在启动脚本第一行开始编写如下代码:

---__init__--- 这一行是固定格式, 用于标记启动脚本是否有设置启动选项
if _G["__init__"] then
    local arg = ... --- command line args
    return {
        -- 基础配置
        thread = 8,                -- 工作线程数
        enable_stdout = true,      -- 启用标准输出
        logfile = string.format("log/node-%s-%s.log", 
            arg[1], 
            os.date("%Y-%m-%d-%H-%M-%S")
        ),
        loglevel = "DEBUG",        -- 日志级别
        
        -- Lua搜索路径
        path = table.concat({
            "./?.lua",            -- 当前目录
            "./?/init.lua",       -- 当前目录的init文件
            "../lualib/?.lua",    -- moon库
            "../service/?.lua",   -- moon服务
            -- Append your lua module search path
        }, ";")
    }
end

这样做的好处是, 启动选项的配置文件和启动脚本在一起,方便维护。moon进程启动时会先检测代码的第一行是否包含---__init__---,如果包含就设置全局表的__init__,这样运行脚本时就拿到了启动选项。然后再次运行启动脚本,使用它创建第一个服务。

一个创建Node进程模板

---__init__---  初始化进程选项标识
if _G["__init__"] then
    local arg = ... ---这里可以获取命令行参数, string[] 类型
    return {
        thread = 8, ---启动8条线程
        enable_stdout = true,
        logfile = string.format("log/game-%s.log", os.date("%Y-%m-%d-%H-%M-%S")),
        loglevel = "DEBUG", ---默认日志等级
        path = table.concat({ --- 注意: 工作目录会切换到当前脚本所在的路径
            "./?.lua",
            "./?/init.lua",
            "../lualib/?.lua",   -- moon lualib 搜索路径
            "../service/?.lua",  -- moon 自带的服务搜索路径,需要用到redisd服务
            -- Append your lua module search path
        }, ";")
    }
end

--------开始编写第一个服务的逻辑代码----

local moon = require("moon")

local socket = require "moon.socket"

--初始化服务配置
local db_conf= {host = "127.0.0.1", port = 6379, timeout = 1000}

local gate_host = "0.0.0.0"
local gate_port = 8889
local client_timeout = 300

local services = {
    {
        unique = true,
        name = "db",
        file = "../service/redisd.lua",
        threadid = 2, ---独占线程
        poolsize = 5, ---连接池
        opts = db_conf
    },
    {
        unique = true,
        name = "center",
        file = "game/service_center.lua",
        threadid = 3,
    },
}

moon.async(function ()
    for _, one in ipairs(services) do
        local id = moon.new_service( one)
        if 0 == id then
            moon.exit(-1) ---如果唯一服务创建失败,立刻退出进程
            return
        end
    end

    local listenfd = socket.listen(gate_host, gate_port, moon.PTYPE_SOCKET_TCP)
    if 0 == listenfd then
        moon.exit(-1) ---监听端口失败,立刻退出进程
        return
    end

    print("server start", gate_host, gate_port)

    while true do
        local id = moon.new_service( {
            name = "user",
            file = "game/service_user.lua"
        })

        local fd, err = socket.accept(listenfd, id)
        if not fd then
            print("accept",err)
            moon.kill(id)
        else
            moon.send("lua", id,"start", fd, client_timeout)
        end
    end

end)

-- 注册进程退出回调
moon.shutdown(function ()
    moon.async(function ()
        -- 控制其它唯一服务的退出逻辑
        assert(moon.call("lua", moon.queryservice("center"), "shutdown"))
        moon.raw_send("system", moon.queryservice("db"), "wait_save")

        ---wait all service quit
        while true do
            local size = moon.server_stats("service.count")
            if size == 1 then
                break
            end
            moon.sleep(200)
            print("bootstrap wait all service quit, now count:", size)
        end

        moon.quit()
    end)
end)

节点(进程)间通信

对于搭建分布式游戏服务器,就需要节点间通信。moon对节点间通信做了简单的封装,能满足大部分需求。节点间通信主要用到 cluster 的两个API:

    ---向指定节点的唯一服务发送消息, 无返回值, 如果网络或其它原因造成消息不可达,消息会被丢弃
    cluster.send(receiver_node, receiver_sname, ...)
    ---向指定节点的唯一服务发起RPC调用, 不管成功和失败一定会得到返回值,可以检测返回值判断执行是否成功
    cluster.call(receiver_node, receiver_sname, ...)

并且需要以下条件:

  • Http服务提供通过 NODE ID 获得 节点的 host port
  • 每个节点注册 moon.env("NODE", node_id) 环境变量
  • 每个节点需要创建一个cluster 唯一服务,名且服务名字需要是cluster
  • 需要被访问的节点 需要在创建cluster服务之后,调用它的Listen函数

创建节点配置文件

node.json

[
    {
        "node": 1,
        "host":"127.0.0.1",
        "port":42345
    },
    {
        "node": 2,
        "host":"127.0.0.1",
        "port":42346
    }
]

创建配置中心节点

cluster_etc.lua, 提供配置中心http服务,cluster服务通过它获取其它节点的端口地址。这里用moon开启了一个简单的http-server实现,也可以使用其它方式。

代码链接

7.3.3 创建Node1-消息发送者

node1.lua 调用 node2 bootstrap 函数提供的函数

代码链接

创建Node2-消息接收者

node2.lua

代码链接

运行

按照如下顺序,开启三个终端运行

./moon cluster_etc.lua node.json
./moon node2.lua 2
./moon node1.lua 1

Clone this wiki locally