一句话
分布式系统中,服务往往是独立运行的,他们之间如何交流也是有很多可以挖掘的点。让服务之间自动“找到彼此”,并建立连接,且在服务增删变化时,连接关系能自动调整,这就是服务注册和发现要做的事情。
为什么需要服务发现
例子 1:网关找登录服
- 登录服可能多台,地址也可能变。
- 如果靠配置文件写死,运维一改地址就会出错。
- 服务发现就像“自动通讯录”,登录服上线就自动加入,掉线就自动移除。
服务发现要做到什么
简单理解就是三件事:
- 注册:服务实例将自身节点信息写入注册中心(例如 Zookeeper),也就是服务自己把“我是谁、我的网络地址是什么” 写进去
- 发现:订阅并获取自己关心的服务节点,即能拿到“我发现了哪些服务以及需要连接哪些服务”
- 更新:服务变化时自动通知,变更不仅有“来/走”,还有“节点属性更新”(比如权重/版本变化)
通俗地说,对于一个服务:
- 启动时先给我一张其他服务的完整名单(快照)
- 运行中有服务加入或离开就通知我(增量订阅)
这里其实有一个数据的一致性需求 :启动时需要“全量快照”,运行时需要“增量变更”,避免漏连或重复。
实现方案
1) 注册:服务自己告诉系统“我是谁”,我的地址是多少
每个服务启动后把信息写进注册中心,通常就是 key-value 的形式,比如:
login.tcp.3 => 10.1.2.3:8001
这里,login 是服务类型,tcp 是地址的类型(也可以是http),3 是服务的编号,这对于有状态服务的区分比较重要。
如果要更多信息,也可以带上权重、版本、元数据:
10.1.2.3|8001|20|v1|{"region":"cn"}
2) 发现:获得别的服务的地址,关注地址变更
服务启动后订阅自己关心的目标,例如:
- Gate 关心 Login、Router
- Game 关心 Battle、Router
当 Gate 启动时,需要先拿到 全量快照:
snapshot = [login-1, login-2, login-3, router-1, router-2]
这些服务的地址如果发生了变更,如权重从 20 变成 0,就需要关注。
3) 增量:有变化就通知
如果登录服新增了一台:
新增: login-tcp-4
订阅者收到事件后,只需要做一件事: 建立和 login-tcp-4 的连接
如果 login-tcp-2 下线:
删除: login-2
订阅者自动断开即可。
简化流程图(Mermaid)
我们把上面的几个过程串联起来,得到一个流程图:
更深入的点
- 在现代 kubernetes 云部署盛行的当下,服务注册和发现往往已经支持,但是如果需要自己来更细粒度更深度的定制和控制路由,自己实现一套服务注册和发现是很有必要的;
- 如果是 kubernetes 部署,服务注册的地址一般是 DNS 域名,因为服务本身的地址是会动态变化的;
- 在很多异构的系统中,往往由不同的语言来实现不同的服务,为了避免实现多套服务注册和发现,增加维护成本,可以将其抽象成agent 服务(以sidecar的形式部署),使用方用 gRPC 这种解耦的协议形式,和 agent 通信。
总结
- 服务注册和发现做的是一套“自动通讯录 + 实时变更提醒”
- 快照解决“启动时看到全量”
- 增量解决“运行中看到变化”
- 可以挖掘更适合k8s及异构系统的实现方式