从0到1手写注册中心registry之集群选主

一、领域对象

  1. Cluster:描述集群信息
  • port描述当前服务端口;
  • host描述当前服务主机;
  • myself描述当前服务本身;
  • servers描述当前服务集群列表
  • registryConfigProperties配置信息;
  • executor定时任务,负责更新服务信息和选主;
  • timeout定时任务执行时间间隔。

图片

  1. Server:描述服务实例
  • url:服务地址
  • status:服务状态
  • leader:服务实例是否为主节点
  • version:服务最新版本号

图片

二、集群选主

  1. 初始化
  • 获取当前服务实例地址信息
  • 构建当前服务实例对象
  • 获取所有服务列表
  • 开启定时任务:更新集群服务信息;选主。

图片

所有服务列表有属性文件里进行配置,分别创建当前服务对象和其他服务对象。区别是当前服务实例状态为true,其他为false。

图片

  1. 更新集群服务信息

初始化的时候只是描述了服务简单信息,实际信息需要事实获取并更新。

通过http调用获取到其他服务实际的信息,包括真实转态、是否主节点、版号。

图片3. 选主

从所有服务列表中筛选出状态为true,是主节点的服务。

  • 如果是空列表,就进行选主;
  • 如果有多个服务,也需要选主
  • 如果只有一个这样的节点,就不需要选主,说明当前集群只有一个主,符合预期。

图片

选主算法有:

  • 各种节点自己选,算法保证大家选的是同一个
  • 外部有一个分布式锁,谁拿到锁,谁是主
  • 分布式一致性算法,比如paxos,raft,,很复杂

当前实现采用第一种,通过选择出最小的hash为主节点,这种算法可以保证大家选择的都是一样的。

图片

选主成功后,设置为主节点即可。

三、测试

  1. 三个节点

通过端口8484、8485、8486分别启动三个服务,模拟集群。

观察8484的日志:先更新三个服务信息,都是成功的。然后选择出8484为主节点。

图片

观察8485的日志:先更新三个服务信息,都是成功的。然后选择出8484为主节点。

图片

观察8486的日志:先更新三个服务信息,都是成功的。然后选择出8484为主节点。

图片

  1. 两个节点

停掉8484,只保留8485和8486两个服务。

观察8485的日志:更新服务状态8484是失败的,8485和8486是成功的。选择是8485为主节点。

图片

观察8486的日志:更新服务状态8484是失败的,8485和8486是成功的。选择是8485为主节点。

图片

源码地址:

https://github.com/midnight2104/midnight-registry/tree/v2

从0到1手写注册中心registry之核心接口设计

一. 数据模型

InstanceMeta用于描述服务实例的元信息:

  • schema:比如http
  • host,:比如127.0.0.1
  • port:比如8082
  • context:比如midnight-rpc
  • status:服务上下线,true/false
  • Parameters: 服务携带的参数,比如环境、tag等

图片

二. 注册中心服务接口定义

  • 注册:将服务和实例注册;
  • 取消注册:移除服务实例;
  • 获取所有实例;
  • 刷新服务版本:服务实例每变动一次,就更新时间戳;
  • 版本:获取服务指定版本;
  • 多个版本:获取多个服务对应的版本号;

图片

三. 注册中心服务实现

实现接口定义中的方法。使用controller对外暴露接口。

使用的字段包括

  • TIMESTAMPS:每个服务实例对应的时间戳;
  • REGISTRY:保存服务和实例,使用Map;
  • VERSIONS:每个服务对应的最新版本号;
  • VERSION:全局递增变量;

图片

  1. register()服务注册

根据服务从注册中心获取实例,如果实例已经存在,就把状态设置为“上线”,然后返回该实例。如果不存在就添加到注册中心,状态设置为true(上线),刷服务实例时间戳,设置服务版本号。

图片

  1. renew() 刷新服务实例

更新服务实例的时间戳

图片

  1. unregister() 取消注册

根据服务获取实例,不存在就直接返回。存在就从注册中心集合中移除该实例,实例状态设置为false(下线),刷新时间戳,更新最新版本号。

图片

  1. getAllInstances() 获取所有实例

直接从注册中心获服务对应的所有实例。

图片

  1. version() 服务版本号

根据服务获取对应的版本号。

图片

  1. versions() 多个服务版本号

根据传入的服务集合获取对应的版本号。

图片

四. 测试

  1. register注册服务

图片

  1. getAllInstances获取所有实例

图片

  1. unregiste取消服务实例注册

图片

  1. versions获取多个服务版本号

图片

从0到1实现rpc之限流

  1. 在服务提供者provider端添加限流逻辑

限流:指定时间内请求数超过指定阈值时就抛出异常。

在ProviderInvoker的调用过程中,添加限流逻辑:

  • 使用滑动窗口SlidingTimeWindow统计30s的请求数;
  • 每个服务service对应一个滑动窗口;
  • 对限流模块使用同步锁;
  • 判断请求数是否超过阈值,如果是则抛出异常;
  • 记录请求次数。

图片

  1. 添加测试案例

循环请求指定服务,每秒请求一次,限流配置参数为30s内超过20个请求就会被限流。

图片

观察日志,在30s内连续请求20次后第21次的请求就被限流了。

图片

源码:

https://github.com/midnight2104/midnight-rpc/tree/lesson12