Skip to content

Provider-Linkd 架构

本文档描述 Zeze 分布式架构中 Linkd 前端、Provider 后端、ServiceManager 服务注册中心及 ProviderDirect 直连的角色职责、交互流程与配置方式,供架构理解与部署参考。

Zeze 采用「前端连接管理 + 后端业务承载」的分布式拆分架构。核心组件职责如下:

组件角色主要职责
Linkd前端管理客户端连接,按 Module 配置负载分发到合适 Provider,管理绑定、广播、踢人;不使用数据库,专注连接管理
Provider后端承载业务逻辑与事务,操作数据库,向 Linkd 报告负载,处理转发请求
ServiceManager服务注册中心通告服务上下线,承载负载上报
ProviderDirectProvider 间直连承载 Redirect(跨服调用)
┌───────────────────┐
│ ServiceManager │ 服务注册中心(通告上下线 / 负载上报)
└─────────┬─────────┘
注册/订阅/负载上报 │
┌───────────────────────────┼───────────────────────────┐
│ │ │
┌──────▼──────┐ Provider协议 ┌─▼──────────┐ Provider直连 ┌─────────────┐
│ Client │───TCP──────────▶ │ Linkd │◀──────────────▶│ Provider │
│ │ │ (前端) │ │ (后端) │
└─────────────┘ └────────────┘ └──────┬──────┘
│ 派发 │
└──────────────────────────────▶│
数据库操作
[Client] ──TCP──▶ [Linkd] ──Provider协议──▶ [Provider] + ServiceManager + ProviderDirect
服务类方向说明
LinkdServiceLinkd ↔ ClientHandshakeServer,接收客户端连接并握手
LinkdProviderServiceLinkd ↔ ProviderHandshakeServer,接收 Provider 连接并握手
ProviderServiceProvider → LinkdHandshakeClient,连接到 Linkd
ProviderDirectServiceProvider ↔ ProviderProvider 之间直连,承载 Redirect

Module 是最小的逻辑单位,类似微服务。

属性说明
全局唯一每个模块有全局唯一的 moduleId
同一协议同一 Module 使用同一套协议
Linkd 路由Linkd 把请求路由到实现该 Module 的同一组 Provider
多 Module 实现一个 Provider 可实现多个 Module
水平扩展同一 Module 可部署多个 Provider
单工程实现一个 Module 只能由一个代码工程实现

绑定模型(静态绑定 vs 动态绑定)

Section titled “绑定模型(静态绑定 vs 动态绑定)”
维度静态绑定(Static)动态绑定(Dynamic)
触发时机Provider 启动 Bind 注册,Linkd 收首个请求时选 ProviderBind 指定 linkSids,需 UnBind 解绑
绑定粒度把所有静态模块绑定到客户端会话(亲缘性,避免跨进程)每次请求不缓存绑定
适用场景常规登录后业务临时性、一次性访问

provider.module.binds.xml 配置示例:

<xml>
<defaultModule ChoiceType="HashAccount"/>
<ChoiceType name="Load"/>
<module name="Login" ChoiceType="HashAccount"/>
<module name="Bag" ChoiceType="HashRoleId" providers="0,1"/>
<module name="Chat" ChoiceType="Load" dynamic="true"/>
<ProviderNoDefaultModule/>
</xml>
ChoiceType说明
HashAccount对 account 做 Hash
HashRoleId对 roleId 做 Hash
HashSourceAddress对来源地址做 Hash
FeedFullOneByOne轮询「喂饱」(按容量逐个填满)
Load按负载权重随机
Request按请求计数反权重
Default默认策略

⚠️ XML 配置时取值要带 ChoiceType 前缀:写成 ChoiceTypeHashAccount / ChoiceTypeHashRoleId / ChoiceTypeLoad / ChoiceTypeDefault 等。上表省略前缀仅为阅读方便,直接写 HashAccountLoadRequest 是无法通过字符串解析的。

绑定加载:ProviderModuleBinds.load()

组件职责
LinkdApp组装、初始化、服务发现回调
LinkdProvider处理 Provider 协议:Bind / UnBind / Send / Broadcast,负载选择
LinkdProviderServiceLinkd 端与 Provider 的握手服务
LinkdServicedispatchUnknownProtocol 转发总入口,可重载实现特殊转发(如群组按 hash(GroupId) 固定转发,可偷 Decode 部分公共参数)
1. Linkd 启动 → registerService(),以 linkdServiceName 注册
2. Provider 启动 → registerModulesAndSubscribeLinkd()
· 每 Module 一条 serviceName = Prefix + moduleId
· 订阅 Linkd
3. ServiceManager → 广播 Linkd 上线
4. Provider 收到 → applyPut 建立 TCP 握手
发 AnnounceProviderInfo(携带 ServiceIdentity、directIpPort、appVersion、DisableChoice 等字段)
发 Bind(静态)+ Subscribe(动态)
5. Provider 间直连 → 订阅其他 Module 服务
协议说明
Dispatch转发客户端请求,携带 linkSid / account / protocolType / protocolData / userState,Provider 通过 ProcessDispatch 解码执行
LinkBroken通知链路断开
协议说明
AnnounceProviderInfo声明 Provider 信息
Bind静态绑定模块
UnBind解绑
Subscribe订阅动态模块
Send指定 linkSids 转发给客户端
Broadcast广播(支持 onlySameVersion
SetUserState设置用户状态
Kick踢人(eControlClose
SetDisableChoice设置是否禁止选择

当用户分多台 Server、查询量大且改动频繁时,异机查询会导致 Cache 失效。Transmit 查找目标用户所在 Server,转去执行,结果直接发 Sender 不返回;Sender 所在服务器修改/查询都在 Target 所在服务器完成,Cache 命中率高,仅多一个 Rpc 转发。

方法说明
Transmit(account, clientId, actionName, target, parameter)基础转发
TransmitWhileCommit事务提交时转发
TransmitWhileRollback事务回滚时转发

choiceProviderAndBind() 流程:

1. 选择版本分发器
2. 查询模块的 Provider 列表
3. 按 ChoiceType 选择
4. 检查版本、过载、disableChoice
5. 不满足则遍历剩余 Provider
6. 绑定静态模块

LoadBase 采集并上报 BLoad

字段说明
online当前在线数
onlineNew新增在线
proposeMaxOnline建议最大在线
maxOnlineNew最大新增
overload过载状态:eWorkFine / eThreshold / eOverload

上报频率自适应:过载加速,空闲降速,由 LoadConfig 配置。

Provider 过载检测(ProviderOverload)

Section titled “Provider 过载检测(ProviderOverload)”
指标默认值行为
< providerThreshold2000正常
< providerOverload4000阈值告警,丢 eSheddable
>= providerOverload4000过载,仅留 eCriticalPlus,Linkd 停止派发;ProcessDispatch 为 RPC 时自动回 Procedure.Busy
输出占比行为
< 70%不丢
70% ~ 100%自定义 DiscardAction
> 100%熔断,丢所有非关键
开关 / 机制说明
setDisableChoiceFromLinks优雅停服:providerService.setDisableChoiceFromLinks(true),等处理完安全关闭;与之配合,ProviderAppstartLast() 中调 setUserDisableChoice(false) 开启选择(详见下方 Java 示例)
maxAppVersion版本过滤:主版本号一致才派发,(serverAppVersion >>> 48) == (clientAppVersion >>> 48),用于滚动更新
启动时控制服务可见initDisableChoice = true,在 startService 前设置
  • 推荐按「不可靠服务」使用。
  • startLast 才开启模块注册,推荐 Redirect 实现在 startLast 前准备好。
<protocolref ref="Linkd.Auth"/>

通过引入共享模块(ProviderService)实现互相调用。

启动顺序:

ServiceManager → GlobalCacheManager → 任意顺序 Linkd 和 Provider

停止顺序:

ServiceManager 保持最后
→ Linkd 关闭 Acceptor
→ Provider 广播用户下线
→ Provider 等待关闭
→ Linkd 关闭
→ GCM 关闭

linkd.xml

<ServiceConf>
<Acceptor Ip="@internal" Port="5555"/>
<Acceptor Ip="@external" Port="5556"/>
</ServiceConf>

server.xml

<ServiceConf>
<Connector Ip="127.0.0.1" Port="5555" AutoReconnect="true"/>
</ServiceConf>

⚠️ 重要ProviderApp 不是 extends Application,而是 extends ReentrantLock(它本身不是 Zeze Application,持有的是 Application zeze 字段)。构造方法有 8 个参数,没有 (int serverId, String servicePrefix) 这样的简化构造。模块注册与服务订阅发生在 startLast() 内部调用的 ProviderImplement.registerModulesAndSubscribeLinkd(),最终通过 ServiceManager.editService(BEditService) 批量注册,而不是 providerService.start()

// ProviderApp 真实构造签名(8 参数)
public class ProviderApp extends ReentrantLock {
public ProviderApp(
@NotNull Application zeze,
@NotNull ProviderImplement server,
@NotNull ProviderService toLinkdService,
@NotNull String providerModulePrefixNameOnServiceManager,
@NotNull ProviderDirect direct,
@NotNull ProviderDirectService toOtherProviderService,
@NotNull String linkdNameOnServiceManager,
@NotNull LoadConfig loadConfig) {
// ...
}
@Override
public void startLast() throws Exception {
// 模块注册与订阅 Linkd 在这里完成:
// providerImplement.registerModulesAndSubscribeLinkd()
// 内部用 sm.editService(BEditService) 批量注册服务
// 同时 setUserDisableChoice(false) 开启选择
}
}

LinkdApp 负责前端连接管理,服务注册通过 registerService(@Nullable BLinkInfo.Data extra) 完成(内部用 editService + BServiceInfo):

public class LinkdApp extends Application {
// Linkd 的服务发现注册走 registerService(BLinkInfo.Data)
// LinkdApp 和 LinkdProvider 上都没有 setUserDisableChoice 方法
}

setUserDisableChoice(boolean)ProviderApp 的方法(包级可见),不在 LinkdApp/LinkdProvider。它在 startLast() 中被调用(setUserDisableChoice(false) 开启选择),也会被 ProviderService 内部使用。不要在 Linkd 端调用它。

开关归属说明
setUserDisableChoice(false)ProviderAppstartLast() 内调用,开启选择
setDisableChoiceFromLinks(true)ProviderService优雅停服:拒绝 Linkd 派发新用户,等存量处理完再关
initDisableChoice = trueProviderApp启动时控制服务可见性,在 startService 前设置