skynet——服务发现学习
skynet创建一个新服务,其他服务怎么知道他的地址,并和他通信呢?
单进程
本地服务管理
localservice={}-- 存储所有注册的本地服务functioncmd.LAUNCH(service_name,subname,...)localrealname=read_name(service_name)returnwaitfor(service_name,skynet.newservice,realname,subname,...)endfunctioncmd.QUERY(service_name,subname)returnwaitfor(service_name)end这里service表维护了名字到句柄的映射。当服务启动时,它会被注册到这个表中,后续其他服务可以通过名字查询到对应的句柄。
服务注册
localfunctionglobalname(name,handle)localc=string.sub(name,1,1)assert(c~=':')ifc=='.'thenreturnfalseendassert(#name<16)-- GLOBALNAME_LENGTH is 16, defined in skynet_harbor.hassert(tonumber(name)==nil)-- global name can't be numberlocalharbor=require"skynet.harbor"harbor.globalname(name,handle)returntrueendfunctionskynet.register(name)ifnotglobalname(name)thenc.command("REG",name)-- 调用C层注册endendfunctionskynet.name(name,handle)ifnotglobalname(name,handle)thenc.command("NAME",name.." "..skynet.address(handle))endendSkynet将服务名字分为两类——本地名字(以.开头)和全局名字(不以.和:开头)。本地名字通过C层的本地注册机制存储,适合单进程内使用;全局名字需要通过Harbor机制同步到其他节点。
集群
Cluster机制采用了去中心化的设计理念,每个节点只需要配置其他节点的地址即可直接通信。
名字服务注册
--cluster.luafunctioncluster.register(name,addr)assert(type(name)=="string")assert(addr==nilortype(addr)=="number")returnskynet.call(clusterd,"lua","register",name,addr)endfunctioncluster.unregister(name)assert(type(name)=="string")returnskynet.call(clusterd,"lua","unregister",name)end--clusterd.lualocalregister_name={}-- 本节点的名字注册表localnode_address={}-- 节点地址配置functioncommand.register(source,name,addr)assert(register_name[name]==nil)addr=addrorsourcelocalold_name=register_name[addr]ifold_namethenregister_name[old_name]=nilendregister_name[addr]=name register_name[name]=addr skynet.error(string.format("Register [%s] :%08x",name,addr))endfunctioncommand.queryname(source,name)skynet.ret(skynet.pack(register_name[name]))end跨节点地址发现
Cluster 采用静态配置方式解决"节点地址"问题。每个节点在启动时读取配置文件
-- clusterd.lua 第85-95行localfunctionloadconfig(tmp)iftmp==nilthentmp={}ifconfig_namethen-- 从环境变量读取配置文件路径localf=assert(io.open(config_name))localsource=f:read"*a"f:close()assert(load(source,"@"..config_name,"t",tmp))()-- 执行配置文件endend-- ...forname,addressinpairs(tmp)doifname:sub(1,2)=="__"then-- __开头的是特殊配置localopt=name:sub(3)config[opt]=addresselsenode_address[name]=address-- name -> IP:Port 映射endendend大致加载流程如下:
┌─────────────────────────────────────────────────────────────────┐ │ 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 的调用协程被唤醒,获取结果如何动态发现新增的服务
结合cluster,新增一个中继集群,他负责维护全部服务的。
┌─────────────────────────────────────────────────────────────────────────────┐ │ 中继集群架构图 │ ├─────────────────────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │ Client A │ │ Client B │ │ Client C │ │ │ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │ │ │ │ │ │ │ │ cluster.call │ │ │ │ └────────────────────┼────────────────────┘ │ │ │ │ │ ▼ │ │ ┌──────────────────────────────────────────────────────────────────┐ │ │ │ 中继集群 (Relay Cluster) │ │ │ │ ┌────────────────────────────────────────────────────────────┐ │ │ │ │ │ 服务注册表 (Registry) │ │ │ │ │ │ ┌──────────────────────────────────────────────────────┐ │ │ │ │ │ │ │ gate │ node1:001234 │ node2:005678 │ 3个 │ │ │ │ │ │ │ │ worker │ node1:003456 │ node2:007890 │ 5个 │ │ │ │ │ │ │ │ db_mgr │ node3:009ABC │ │ 1个 │ │ │ │ │ │ │ └──────────────────────────────────────────────────────┘ │ │ │ │ │ │ │ │ │ │ │ │ 负载均衡器 (Load Balancer) │ │ │ │ │ │ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ │ │ │ │ │ │ │Round │ │Least │ │Hash │ │Random │ │ │ │ │ │ │ │Robin │ │Load │ │ │ │ │ │ │ │ │ │ │ └────────┘ └────────┘ └────────┘ └────────┘ │ │ │ │ │ └────────────────────────────────────────────────────────────┘ │ │ │ └──────────────────────────────────────────────────────────────────┘ │ │ │ │ │ │ cluster.call (转发) │ │ ┌────────────────────┼────────────────────┐ │ │ │ │ │ │ │ ▼ ▼ ▼ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │ Node 1 │ │ Node 2 │ │ Node 3 │ │ │ │ │ │ │ │ │ │ │ │ gate │ │ gate │ │ db_mgr │ │ │ │ worker x 3 │ │ worker x 2 │ │ │ │ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────────────┘设计方案
┌─────────────────────────────────────────────────────────────────────────────┐ │ 服务同步模式对比 │ ├─────────────────────────────────────────────────────────────────────────────┤ │ │ │ 模式 │ 示意图 │ 特点 │ │ ───────────────────────────────────────────────────────────────────────── │ │ │ │ │ │ 集中式 │ ┌────────┐ │ 简单,但中继节点压力大 │ │ (单中继) │ │ Master │ 所有注册 │ 单点风险 │ │ │ └───┬────┘ │ │ │ │ │ │ │ │ │ ┌────┴────┐ │ │ │ │ │ │ │ │ │ │ │ ▼ ▼ ▼ │ │ │ │ 节点1 节点2 节点3 │ │ │ │ │ │ │ ───────────────────────────────────────────────────────────────────────── │ │ │ │ │ │ 全复制 │ ┌────┐ ┌────┐ ┌────┐ │ 强一致性,每个节点完整副本 │ │ (每个节点 │ │ 1 │ │ 2 │ │ 3 │ │ 但同步开销大 │ │ 完整副本) │ │全量│ │全量│ │全量│ │ │ │ │ └────┘ └────┘ └────┘ │ │ │ │ 同步──→同步──→同步 │ │ │ │ │ │ │ ───────────────────────────────────────────────────────────────────────── │ │ │ │ │ │ 分区 │ ┌────┐ ┌────┐ ┌────┐ │ 扩展性好,负载分散 │ │ (按服务名 │ │ A │ │ B │ │ C │ │ 但查询复杂,可能需要多跳 │ │ 分片) │ │-G │ │-W │ │-M │ │ │ │ │ └────┘ └────┘ └────┘ │ │ │ │ │ │ │ │ 节点1 节点2 节点3 │ │ │ │ │ │ │ ───────────────────────────────────────────────────────────────────────── │ │ │ │ │ │ Gossip │ ┌────┐ ┌────┐ ┌────┐ │ 最终一致,适合大规模 │ │ (最终一致) │ │节点1│←→│节点2│←→│节点3│ │ 实现复杂,有延迟 │ │ │ │ ↕ │ ↕ │ ↕ │ │ │ │ │ └────┘ └────┘ └────┘ │ │ │ │ 随机同步,慢慢扩散 │ │ │ │ │ │ └─────────────────────────────────────────────────────────────────────────────┘