从零实现一个注册中心 - 客户端

 
接上回, 咱们介绍了注册中心的概念, 以及如何实现注册中心的服务端, 那么, 一个完整的注册中心, 除了服务端以外, 客户端SDK也是不可或缺的
 

客户端SDK是什么?

SDK是为了简化客户端应用与服务端的对接的逻辑, 将大部分复杂的交互代码封装起来, 并提供较为简单的API来给客户端应用进行使用, 让开发人员可以将更多精力投入在业务逻辑上
对于注册中心的SDK, 由于是与SpringCloud框架进行集成和扩展, 所以甚至可以做到客户端应用无需改动任何代码, 引入依赖即可获得全新的注册发现能力
 

客户端SDK设计

通讯模块

这个模块定义了客户端与服务端之间的通讯协议, 以完成注册/发现过程中的数据交换, 并维护与服务端的链接
由于服务端采用了WebSocket协议, 所以客户端我们使用Netty实现WebSocket的Client与服务端建立链接, 并采用定时发送心跳消息的方式来维持长链接, 直到客户端应用下线
我们使用一个SunClientFactory来管理客户端的WebSocket通信模块, 它的核心作用就是维护客户端实例与服务端之间的长链接, 并能在链接异常或中断后进行重连等操作
SunClient就是真正的WebSocket客户端了, 它由SunClientFactory进行实例化并启动, SunClient将会与服务端建立连接并持有一个Channel, 同时, 在建立连接后, 它还会开启一个心跳线程HeartBeatWorker
HeartBeatWorker的职责有三:
  1. 在链接建立后, 向服务端发送注册消息, 将自身注册到注册中心
  2. 注册成功后, 定期发送心跳消息维持长链接, 服务端在收到心跳消息后也会回复一个心跳消息, 以此来防止长时间无数据交互(ReadTimeout/WriteTimeout)导致连接主动断开
  3. 如果设置的健康检查方式为主动上报, 那么在发送的心跳信息中, 还会携带自身的健康状态

注册消息中包含哪些信息?

serviceId: 服务名称, 注册中心中将同一个ServiceId的实例集合认为是一个服务集群
instanceId: 实例ID, 用来标记每一个客户端实例, 默认是 ip+port
scheme/ip/port: 自身的服务地址以及协议, 默认为http并自动获取本机内网ip和8080端口, 可以通过配置来进行覆盖
group/version: 实例所属的分组/版本信息, 这个可以在后续用来进行多版本分流与灰度发布等功能, 默认是default与-1
tags: 自定义标签, 我们可以通过配置给服务打上自定义的标签信息, 后续可以用来进行自定义筛选, 默认无
subscribeMode: 发现模式, 分为全量订阅与指定订阅, 它告诉注册中心我需要知道哪些服务的实例列表信息, 考虑到全量订阅的数据量很大, 只建议使用指定订阅, 即注册中心只会向它推送指定服务的实例列表信息
providers: 订阅的服务集合, 告诉注册中心我依赖这些服务
probe: 健康检查相关配置, 它告诉注册中心以什么方式对自己进行健康检查, 包含健康检查开关/方式/周期/容忍度等
 

消息处理模块

这个模块服务处理服务端推送过来的消息, 这里包含两个过程 1: 将消息路由给对应的Action; 2: 由Action完成对消息的逻辑处理
与服务端的请求处理模块一样, 客户端这边也使用component-dispatcher-action组件来对消息进行路由
这个组件已经开源在github上 - 传送门
有了分发组件的支持, 我们只需要专心编写我们的处理逻辑就可以了, 像下面这个样子
 

数据存储模块

这个模块负责将服务端推送过来的服务列表等数据保存在本地缓存中, 使得客户端应用在执行负载均衡时可以直接从内存中读取服务列表
我们直接采用ConcurrentHashMap来存储所有的服务实例数据, 数据分为两层
第一层为服务层, 意味着我们可以根据一个ServiceId来获取这个服务的所有实例集合
第二层为实例层, 这是为了我们可以根据ServiceId与InstanceId对一个实例进行更新/删除等操作
/**
* Map<serviceId, <instanceId, server>>
*/
private static Map<String, ConcurrentHashMap<String, RegistryServer>> SERVERS = new ConcurrentHashMap<>();

 

服务路由模块

这个模块负责将我们发现的服务列表数据应用在Ribbon框架中, 来真正完成服务发现的功能
我们基于Ribbon的IRule接口来作为自定义服务发现的切入点, 只需要实现IRule接口, 并声明为Bean即可覆盖默认的路由器, 我们这里自定义一个 SunLoadBalancerRule
服务路由模块就是由 SunLoadBalancerRule 来实现, 它里面包含了一个核心逻辑和两个扩展点, 两个扩展点分别是 前置路由器 和 后置路由器, 核心逻辑则是有标准路由器来实现
前置路由器: 如果前置路由器返回了一个Server, 那么将不会再执行注册发现逻辑, 而是直接采用前置路由器选择的Server, 一般用于直连调试时, 对特定的服务配置特定的地址
标准路由器: 这里就是根据当前请求的服务名信息, 从数据存储模块中找到对应的服务实例列表, 筛选出健康的实例, 并使用配置的负载均衡算法得出一个可用的实例地址
后置路由器: 当我们的前置路由器和标准路由器都没有得出一个可用的服务地址时, 就可以使用后置路由器来实现一个兜底, 比如我们可以为每一个服务配置一个兜底的访问地址, 假如注册中心挂了或者数据同步出现问题导致无法路由时, 就可以通过后置路由器将兜底的访问地址返回出去
 

小结

至此, 我们介绍了一个微服务注册中心从服务端到客户端SDK的完整实现方案与模块设计, 当然, 这是一个非常基础的方案说明, 仅仅实现了简单的从注册到发现的过程, 实际生产开发过程中, 我们的需求会更多且更复杂, 比如:
如何基于注册中心实现灰度发布?
如何基于注册中心实现可靠的RPC访问鉴权机制?
如何实现业务零感知的平滑发布?
等等...
想知道怎么基于现有的注册中心, 来实现上面几个常见的需求, 请听下回分解!
posted @ 2021-12-30 00:04  EEEEET  阅读(620)  评论(0编辑  收藏  举报