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 的调用协程被唤醒,获取结果