skynet——服务发现学习

skynet创建一个新服务,其他服务怎么知道他的地址,并和他通信呢?

单进程

本地服务管理

lua 复制代码
local service = {}  -- 存储所有注册的本地服务

function cmd.LAUNCH(service_name, subname, ...)
    local realname = read_name(service_name)
    return waitfor(service_name, skynet.newservice, realname, subname, ...)
end

function cmd.QUERY(service_name, subname)
    return waitfor(service_name)
end

这里service表维护了名字到句柄的映射。当服务启动时,它会被注册到这个表中,后续其他服务可以通过名字查询到对应的句柄。

服务注册

lua 复制代码
local function globalname(name, handle)
	local c = string.sub(name,1,1)
	assert(c ~= ':')
	if c == '.' then
		return false
	end

	assert(#name < 16)	-- GLOBALNAME_LENGTH is 16, defined in skynet_harbor.h
	assert(tonumber(name) == nil)	-- global name can't be number

	local harbor = require "skynet.harbor"

	harbor.globalname(name, handle)

	return true
end

function skynet.register(name)
    if not globalname(name) then
        c.command("REG", name)  -- 调用C层注册
    end
end

function skynet.name(name, handle)
    if not globalname(name, handle) then
        c.command("NAME", name .. " " .. skynet.address(handle))
    end
end

Skynet将服务名字分为两类------本地名字(以.开头)全局名字 (不以.和:开头)。本地名字通过C层的本地注册机制存储 ,适合单进程内使用;全局名字需要通过Harbor机制同步到其他节点。

集群

Cluster机制采用了去中心化的设计理念,每个节点只需要配置其他节点的地址即可直接通信。

名字服务注册

lua 复制代码
--cluster.lua
function cluster.register(name, addr)
    assert(type(name) == "string")
    assert(addr == nil or type(addr) == "number")
    return skynet.call(clusterd, "lua", "register", name, addr)
end

function cluster.unregister(name)
    assert(type(name) == "string")
    return skynet.call(clusterd, "lua", "unregister", name)
end

--clusterd.lua
local register_name = {}  -- 本节点的名字注册表
local node_address = {}   -- 节点地址配置

function command.register(source, name, addr)
    assert(register_name[name] == nil)
    addr = addr or source
    local old_name = register_name[addr]
    if old_name then
        register_name[old_name] = nil
    end
    register_name[addr] = name
    register_name[name] = addr
    skynet.error(string.format("Register [%s] :%08x", name, addr))
end

function command.queryname(source, name)
    skynet.ret(skynet.pack(register_name[name]))
end

跨节点地址发现

Cluster 采用静态配置方式解决"节点地址"问题。每个节点在启动时读取配置文件

lua 复制代码
-- clusterd.lua 第85-95行
local function loadconfig(tmp)
    if tmp == nil then
        tmp = {}
        if config_name then  -- 从环境变量读取配置文件路径
            local f = assert(io.open(config_name))
            local source = f:read "*a"
            f:close()
            assert(load(source, "@"..config_name, "t", tmp))()  -- 执行配置文件
        end
    end
    -- ...
    for name, address in pairs(tmp) do
        if name:sub(1,2) == "__" then  -- __开头的是特殊配置
            local opt = name:sub(3)
            config[opt] = address
        else
            node_address[name] = address  -- name -> IP:Port 映射
        end
    end
end

大致加载流程如下:

复制代码
┌─────────────────────────────────────────────────────────────────┐
│                      Node1 启动                                  │
├─────────────────────────────────────────────────────────────────┤
│  skynet.init()                                                   │
│      ↓                                                           │
│  clusterd 服务启动                                                │
│      ↓                                                           │
│  loadconfig("cluster.conf")                                      │
│      ↓                                                           │
│  加载配置:                                                        │
│    node_address = {                                              │
│        ["node1"] = "127.0.0.1:7001",                             │
│        ["node2"] = "127.0.0.1:7002",                             │
│        ["node3"] = "192.168.1.100:7003",                        │
│    }                                                             │
└─────────────────────────────────────────────────────────────────┘

完整服务调用流程

复制代码
┌─────────────────────────────────────────────────────────────────────────────┐
│                         Cluster 服务调用完整流程                              │
└─────────────────────────────────────────────────────────────────────────────┘

1. 配置阶段(启动时)
   ├── 所有节点加载 cluster.conf
   └── 每个节点知道所有其他节点的 IP:Port

2. 连接阶段(首次通信时)
   ├── Node1 调用 cluster.call("node2", "gate", "open", ...)
   ├── 发现 sender["node2"] == nil
   ├── 创建 clustersender("node2") 服务
   ├── clustersender 发起 TCP 连接到 127.0.0.1:7002
   └── 保存 sender["node2"] = clustersender

3. 服务查询阶段
   ├── 首次调用时,通过 sender 发送名字查询
   ├── Node2 的 clusterd 查询本地 register_name 表
   └── 返回服务的内部句柄(如 :00000005)

4. 消息发送阶段
   ├── 序列化请求(skynet.pack)
   ├── 通过 TCP 发送到 Node2
   ├── Node2 的 clustersender 接收
   ├── 转发给目标服务 :00000005 (gate)
   └── gate 处理请求并返回结果

5. 结果返回阶段
   ├── gate 返回结果给 clustersender
   ├── clustersender 通过 TCP 发回 Node1
   └── Node1 的调用协程被唤醒,获取结果

启动skynet时,如果是集群,他的流程是怎样的?

相关推荐
sunfdf5 小时前
知识学习场景下的智能应用实践大纲
学习
MartinYeung56 小时前
[论文学习]重新思考大型语言模型忘却目标:梯度视角与超越
人工智能·学习·语言模型
十月的皮皮6 小时前
C语言学习笔记20260615-有序升序序列合并
c语言·笔记·学习
JAVA面经实录9176 小时前
前端系统化学习计划表(含完整知识思维导图)
前端·学习
worilb7 小时前
Spring Cloud 学习与实践(9):Gateway + JWT 统一鉴权
学习·spring cloud·gateway
MartinYeung57 小时前
[论文学习]DP2Unlearning:高效且具保证的大型语言模型遗忘框架(基于差分隐私的 LLM Unlearning 方法)
学习·算法·语言模型
solicitous9 小时前
学习了解充电桩协议OCPP——J规范
学习
H__Rick10 小时前
C51单片机学习-DAY3
单片机·学习·mongodb
yoothey11 小时前
异常学习笔记:为什么自定义异常后还要 throw?
笔记·学习
WangN212 小时前
【通识】宇树G1_29DOF速度跟踪训练—逐章学习手册
人工智能·python·学习·机器人·具身智能