【RPC 专题】深入理解 RPC 之服务注册与发现篇

本人花费半年的时间总结的《Java面试指南》已拿腾讯等大厂offer,已开源在github ,欢迎star!

本文GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了6个月总结的一线大厂Java面试总结,本人已拿大厂offer,欢迎star

原文链接:blog.ouyangsihai.cn >> 【RPC 专题】深入理解 RPC 之服务注册与发现篇

点击上方“芋道源码”,选择“置顶公众号”

技术文章第一时间送达!

源码精品专栏

  • [**中文详细注释的开源项目**](http://mp.weixin.qq.com/s?__biz=MzUzMTA2NTU2Ng==&mid=2247484404&idx=1&sn=109f263e51b81ca9f270846dd16f6b3a&chksm=fa497c45cd3ef55358b09beb6e18ba04737799d3c0bc32baaa0796dc707b1275c0c555a249ba&scene=21#wechat_redirect)

  • **[Java 并发源码合集](http://mp.weixin.qq.com/s?__biz=MzUzMTA2NTU2Ng==&mid=2247484341&idx=1&sn=91d6fc7e8841a0f6046e1c2f4693a537&chksm=fa497c04cd3ef512f9249a5deb305a28b68d3ba44467f13fa8c6068711540b2f3e0a6f622ae3&scene=21#wechat_redirect)**
  • [**RocketMQ 源码合集**](http://mp.weixin.qq.com/s?__biz=MzUzMTA2NTU2Ng==&mid=2247484334&idx=1&sn=761e2659f474f06e7db935eae26e2b03&chksm=fa497c1fcd3ef509a02890b8e9f6bddb02e714f9c7e70cfbc37cd5bd75be64855225497fd3de&scene=21#wechat_redirect)
  • [**Sharding-JDBC 源码解析合集**](http://mp.weixin.qq.com/s?__biz=MzUzMTA2NTU2Ng==&mid=2247484360&idx=1&sn=0dae84944d2c388fdc1bbed868ac5b99&chksm=fa497c79cd3ef56f8487dda6d53e3772e0aa9812ee66376993c3445bc94920c01a03dd4a4b8f&scene=21#wechat_redirect)
  • [**Spring MVC 和 Security 源码合集**](http://mp.weixin.qq.com/s?__biz=MzUzMTA2NTU2Ng==&mid=2247484380&idx=1&sn=b4e0da1a314d77dcd170a25ed1ebb4c5&chksm=fa497c6dcd3ef57bcfb69a52c594bcb72e35d9bbe89fa87601b2a6c9f266d656b1ad2a5d4da4&scene=21#wechat_redirect)

  • [**MyCAT 源码解析合集**](http://mp.weixin.qq.com/s?__biz=MzUzMTA2NTU2Ng==&mid=2247484377&idx=3&sn=1323ac1a4099fac49c96686e58d1960d&chksm=fa497c68cd3ef57e5c3b683f9ead89f06ea5d01947672bfff8341cff2ab0c39c03274723c49a&scene=21#wechat_redirect)
  • 摘要: 原创出处 https://www.cnkirito.moe/rpc-registry/ 「老徐」欢迎转载,保留摘要,谢谢!

  • 注册中心的抽象
  • 注册信息概览
  • 注册信息详解
  • 感知服务的下线
  • 注册中心对比
  • 总结
  • 在我们之前 RPC 原理的分析中,主要将笔墨集中在 Client 和 Server 端。而成熟的服务治理框架中不止存在这两个角色,一般还会有一个 Registry(注册中心)的角色。一张图就可以解释注册中心的主要职责。

    注册中心的地位

  • 注册中心,用于服务端注册远程服务以及客户端发现服务
  • 服务端,对外提供后台服务,将自己的服务信息注册到注册中心
  • 客户端,从注册中心获取远程服务的注册信息,然后进行远程过程调用
  • 目前主要的注册中心可以借由 zookeeper,eureka,consul,etcd 等开源框架实现。互联网公司也会因为自身业务的特性自研,如美团点评自研的 MNS,新浪微博自研的 vintage。

    本文定位是对注册中心有一定了解的读者,所以不过多阐述注册中心的基础概念。

    注册中心的抽象

    借用开源框架中的核心接口,可以帮助我们从一个较为抽象的高度去理解注册中心。例如 motan 中的相关接口:

    服务注册接口

    
    public interface RegistryService {
        //1. 向注册中心注册服务
        void register(URL url);
        //2. 从注册中心摘除服务
        void unregister(URL url);
        //3. 将服务设置为可用,供客户端调用
        void available(URL url);
        //4. 禁用服务,客户端无法发现该服务
        void unavailable(URL url);
          //5. 获取已注册服务的集合
        CollectionURL getRegisteredServiceUrls();
    }
    

    服务发现接口

    
    public interface DiscoveryService {
        //1. 订阅服务
        void subscribe(URL url, NotifyListener listener);
        //2. 取消订阅
        void unsubscribe(URL url, NotifyListener listener);
        //3. 发现服务列表
        ListURL discover(URL url);
    }
    

    主要使用的方法是 RegistryService#register(URL) 和 DiscoveryService#discover(URL)。其中这个 URL 参数被传递,显然也是很重要的一个类。

    
    public class URL {
        private String protocol;//协议名称
        private String host;
        private int port;
        // interfaceName,也代表着路径
        private String path;
        private MapString, String parameters;
        private volatile transient MapString, Number numbers;
    }
    

    注册中心也没那么玄乎,其实可以简单理解为:提供一个存储介质,供服务提供者和服务消费者共同连接,而存储的主要信息就是这里的 URL。但是具体 URL 都包含了什么实际信息,我们还没有一个直观的感受。

    注册信息概览

    以元老级别的注册中心 zookeeper 为例,看看它实际都存储了什么信息以及它是如何持久化上一节的 URL。

    为了测试,我创建了一个 RPC 服务接口  com.sinosoft.student.api.DemoApi ,并且在 6666 端口暴露了这个服务的实现类,将其作为服务提供者。在 6667 端口远程调用这个服务,作为服务消费者。两者都连接本地的 zookeeper,本机 ip 为 192.168.150.1。

    使用 zkClient.bash 或者 zkClient.sh 作为客户端连接到本地的 zookeeper,执行如下的命令:

    
    [zk: localhost:2181(CONNECTED) 1] ls /motan/demo_group/com.sinosoft.student.api.DemoApi
     [client, server, unavailableServer]
    

    zookeeper 有着和 linux 类似的命令和结构,其中 motan,demo_group,com.sinosoft.student.api.DemoApi,client, server, unavailableServer 都是一个个节点。可以从上述命令看出他们的父子关系。

    /motan/demo_group/com.sinosoft.student.api.DemoApi 的结构为 /框架标识/分组名/接口名,其中的分组是 motan 为了隔离不同组的服务而设置的。这样,接口名称相同,分组不同的服务无法互相发现。如果此时有一个分组名为 demo_group2 的服务,接口名称为 DemoApi2,则 motan 会为其创建一个新的节点  /motan/demo_group2/com.sinosoft.student.api.DemoApi2

    而 client,server,unavailableServer 则就是服务注册与发现的核心节点了。我们先看看这些节点都存储了什么信息。

    server 节点:

    
    [zk: localhost:2181(CONNECTED) 2] ls /motan/demo_group/com.sinosoft.student.api.DemoApi/server
     [192.168.150.1:6666]
    
    [zk: localhost:2181(CONNECTED) 3] get /motan/demo_group/com.sinosoft.student.api.DemoApi/server/192.168.150.1:6666
     motan://192.168.150.1:6666/com.sinosoft.student.api.DemoApi?serialization=hessian2&protocol=motan&isDefault=true&maxContentLength=1548576&shareChannel=true&refreshTimestamp=1515122649835&id=motanServerBasicConfig&nodeType=service&export=motan:6666&requestTimeout=9000000&accessLog=false&group=demo_group&
    

    client 节点:

    
    [zk: localhost:2181(CONNECTED) 4] ls /motan/demo_group/com.sinosoft.student.api.DemoApi/client
     [192.168.150.1]
    [zk: localhost:2181(CONNECTED) 5] get /motan/demo_group/com.sinosoft.student.api.DemoApi/client/192.168.150.1
     motan://192.168.150.1:0/com.sinosoft.student.api.DemoApi?singleton=true&maxContentLength=1548576&check=false&nodeType=service&version=1.0&throwException=true&accessLog=false&serialization=hessian2&retries=0&protocol=motan&isDefault=true&refreshTimestamp=1515122631758&id=motanClientBasicConfig&requestTimeout=9000&group=demo_group&
    

    unavailableServer 节点是一个过渡节点,所以在一切正常的情况下不会存在信息,它的具体作用在下面会介绍。

    从这些输出数据可以发现,注册中心承担的一个职责就是存储服务调用中相关的信息,server 向 zookeeper 注册信息,保存在 server 节点,而 client 实际和 server 共享同一个接口,接口名称就是路径名,所以也到达了同样的 server 节点去获取信息。并且同时注册到了 client 节点下(为什么需要这么做在下面介绍)。

    注册信息详解

    Server 节点

    server 节点承担着最重要的职责,它由服务提供者创建,以供服务消费者获取节点中的信息,从而定位到服务提供者真正网络拓扑位置以及得知如何调用。demo 中我只在本机 [192.168.150.1:6666] 启动了一个实例,所以在server 节点之下,只存在这么一个节点,继续 get 这个节点,可以获取更详细的信息

    
    motan://192.168.150.1:6666/com.sinosoft.student.api.DemoApi?serialization=hessian2&protocol=motan&isDefault=true&maxContentLength=1548576&shareChannel=true&refreshTimestamp=1515122649835&id=motanServerBasicConfig&nodeType=service&export=motan:6666&requestTimeout=9000000&accessLog=false&group=demo_group&
    

    作为一个 value 值,它和 http 协议的请求十分相似,不过是以 motan:// 开头,表达的意图也很明确,这是 motan 协议和相关的路径及参数,关于 RPC 中的协议,可以翻看我的上一篇文章《深入理解RPC之协议篇》。

    serialization 对应序列化方式,protocol 对应协议名称,maxContentLength 对应 RPC 传输中数据报文的最大长度,shareChannel 是传输层用到的参数,netty channel 中的一个属性,group 对应分组名称。

    上述的 value 包含了 RPC 调用中所需要的全部信息。

    Client 节点

    在 motan 中使用 zookeeper 作为注册中心时,客户端订阅服务时会向 zookeeper 注册自身,主要是方便对调用方进行统计、管理。但订阅时是否注册 client 不是必要行为,和不同的注册中心实现有关,例如使用 consul 时便没有注册。

    由于我们使用 zookeeper,也可以分析下 zookeeper 中都注册了什么信息。

    
    motan://192.168.150.1:0/com.sinosoft.student.api.DemoApi?singleton=true&maxContentLength=1548576&check=false&nodeType=service&version=1.0&throwException=true&accessLog=false&serialization=hessian2&retries=0&protocol=motan&isDefault=true&refreshTimestamp=1515122631758&id=motanClientBasicConfig&requestTimeout=9000&group=demo_group
    

    和 Server 节点的值类似,但也有客户独有的一些属性,如 singleton 代表服务是否单例,check 检查服务提供者是否存在,retries 代表重试次数,这也是 RPC 中特别需要注意的一点。

    UnavailableServer 节点

    unavailableServer 节点也不是必须存在的一个节点,它主要用来做 server 端的延迟上线,优雅关机。

    延迟上线:一般推荐的服务端启动流程为:server 向注册中心的 unavailableServer 注册,状态为 unavailable,此时整个服务处于启动状态,但不对外提供服务,在服务验证通过,预热完毕,此时打开心跳开关,此时正式提供服务。

    优雅关机:当需要对 server 方进行维护升级时,如果直接关闭,则会影响到客户端的请求。所以理想的情况应当是首先切断流量,再进行 server 的下线。具体的做法便是:先关闭心跳开关,客户端感知停止调用后,再关闭服务进程。

    感知服务的下线

    服务上线时自然要注册到注册中心,但下线时也得从注册中心中摘除。注册是一个主动的行为,这没有特别要注意的地方,但服务下线却是一个值得思考的问题。服务下线包含了主动下线和系统宕机等异常方式的下线。

    临时节点+长连接

    在 zookeeper 中存在持久化节点和临时节点的概念。持久化节点一经创建,只要不主动删除,便会一直持久化存在;临时节点的生命周期则是和客户端的连接同生共死的,应用连接到 zookeeper 时创建一个临时节点,使用长连接维持会话,这样无论何种方式服务发生下线,zookeeper 都可以感知到,进而删除临时节点。zookeeper 的这一特性和服务下线的需求契合的比较好,所以临时节点被广泛应用。

    主动下线+心跳检测

    并不是所有注册中心都有临时节点的概念,另外一种感知服务下线的方式是主动下线。例如在 eureka 中,会有 eureka-server 和 eureka-client 两个角色,其中 eureka-server 保存注册信息,地位等同于 zookeeper。当 eureka-client 需要关闭时,会发送一个通知给 eureka-server,从而让 eureka-server 摘除自己这个节点。但这么做最大的一个问题是,如果仅仅只有主动下线这么一个手段,一旦 eureka-client 非正常下线(如断电,断网),eureka-server 便会一直存在一个已经下线的服务节点,一旦被其他服务发现进而调用,便会带来问题。为了避免出现这样的情况,需要给 eureka-server 增加一个心跳检测功能,它会对服务提供者进行探测,比如每隔30s发送一个心跳,如果三次心跳结果都没有返回值,就认为该服务已下线。

    注册中心对比

    FeatureConsulzookeeperetcdeuerka|------

    一般而言注册中心的特性决定了其使用的场景,例如很多框架支持 zookeeper,在我自己看来是因为其老牌,易用,但业界也有很多人认为 zookeeper 不适合做注册中心,它本身是一个分布式协调组件,并不是为注册服务而生,server 端注册一个服务节点,client 端并不需要在同一时刻拿到完全一致的服务列表,只要最终一致性即可。在跨IDC,多数据中心等场景下 consul 发挥了很大的优势,这也是很多互联网公司选择使用 consul 的原因。 eureka 是 ap 注册中心,并且是 spring cloud 默认使用的组件,spring cloud eureka 较为贴近 spring cloud 生态。

    总结

    注册中心主要用于解耦服务调用中的定位问题,是分布式系统必须面对的一个问题。更多专业性的对比,可以期待 spring4all.com 的注册中心专题讨论,相信会有更为细致地对比。

    666. 彩蛋

    如果你对 RPC 感兴趣,欢迎加入我的知识一起交流。

    【RPC 专题】深入理解 RPC 之服务注册与发现篇

    目前在知识星球(https://t.zsxq.com/2VbiaEu)更新了如下 Dubbo 源码解析如下:

    01. 调试环境搭建
    02. 项目结构一览
    03. API 配置(一)之应用
    04. API 配置(二)之服务提供者
    05. API 配置(三)之服务消费者
    06. 属性配置
    07. XML 配置
    08. 核心流程一览

    09. 拓展机制 SPI

    1. 线程池

    11. 服务暴露(一)之远程暴露(Injvm)

    12. 服务暴露(二)之远程暴露(Dubbo)


    一共 60 篇++

    本人花费半年的时间总结的《Java面试指南》已拿腾讯等大厂offer,已开源在github ,欢迎star!

    本文GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了6个月总结的一线大厂Java面试总结,本人已拿大厂offer,欢迎star

    原文链接:blog.ouyangsihai.cn >> 【RPC 专题】深入理解 RPC 之服务注册与发现篇


     上一篇
    【RPC 专栏】深入理解 RPC 之集群篇 【RPC 专栏】深入理解 RPC 之集群篇
    点击上方“芋道源码”,选择“置顶公众号” 技术文章第一时间送达! 源码精品专栏 [精尽 Dubbo 原理与源码专栏( 已经完成 69+ 篇,预计总共 75+ 篇 )](http://mp.weixin.qq.com/s?__biz=MzU
    2021-04-05
    下一篇 
    【RPC 专栏】Motan 中使用异步 RPC 接口 【RPC 专栏】Motan 中使用异步 RPC 接口
    点击上方“芋道源码”,选择“置顶公众号” 技术文章第一时间送达! 源码精品专栏 [**中文详细注释的开源项目**](http://mp.weixin.qq.com/s?__biz=MzUzMTA2NTU2Ng==&mid=22474844
    2021-04-05