深度解析SpringCloud之服务注册与发现机制(万字深度好文)

原创: 编程新说李新杰 编程新说 今天

PS:文章很长,耐心阅读,收获爆满。

 

 

复杂与简单并存

 


到底是复杂好还是简单好,这是一个没有答案的问题,也是一个哲学问题。见仁见智啦。

事物整体肯定是向复杂化方向发展,但是向人们呈现时应尽量简单化。用一句话来说就是:功能复杂化,使用简单化。

因为人们的要求越来越高,所以功能肯定越来越复杂。又因为要获得更好的用户体验,所以使用方式应该越来越简单。

因此对用户隐藏复杂性是一个需要专门考虑的事情。这个事情一直在进行中,而且效果还不错。下面请看一些例子:

IP很难记忆,于是引入了域名。网上资源很难查找,于是有了搜索引擎。手动挡汽车操作繁琐,于是引入了自动变速箱。为了进一步简化,又引入了自动驾驶技术(即无人车)。

为了解决找零的麻烦,引入了刷卡消费。为了进一步简化,引入了小额免密。为了简化刷卡的繁琐流程,引入了二维码支付。为了进一步简化,又引入了刷脸支付。

为了简单快速的使用Spring进行开发,SpringBoot产生了,它帮我们管理依赖的版本号,通过引入starter来实现自动配置,让我们使用main方法一键启动。是不是很爽啊。

如果说SpringBoot改变了单个工程的构建和运行方式,那么SpringCloud将是改变了整个大项目的构建和运行方式。它刻意强调微服务,就是为了让我们去进行功能模块的拆分。将相关的功能聚合起来,又以服务的形式向外提供。

微服务不是一个技术,而是一种不错的理念。但是会引入非常大的复杂性,SpringCloud就是来隐藏复杂性的一个技术手段。所以SpringCloud只是微服务的一套解决方案而已,又因为它提供的功能涵盖各个方面、非常完善,所以大家习惯称它为“全家桶”。

现在回过头来看看,SpringBoot和SpringCloud是不是都符合“功能复杂化,使用简单化”这十字方针。因此它们非常火,又因为它们符合了正确的进化方向,所以短时内不会消亡,除非有更加简单的形式出现,而且要足以击垮整个Spring生态系统。

抛开技术上能不能实现这个问题不谈,那么隐藏复杂性的唯一方法就是抽象,看谁的思想够天马行空。几百年前肯定没人相信人能在天上飞,但是在1903年实现了。十年前刷脸吃饭还是一句玩笑话,现在也已经成为现实。

 

到底什么是服务?



先讲一则小笑话,说记者采访企鹅,每天都干什么,企鹅们都回答:“吃饭、睡觉、打豆豆”,问到第100只企鹅的时候,它回答:“吃饭,睡觉”,记者问它怎么不打豆豆,这只企鹅说,自己就是豆豆。

一开始大家都以为打豆豆是一种游戏,到最后才发现原来豆豆是只企鹅。现在大家都在讲服务注册与发现,却没有人来明确解释一下这里的“服务”到底指的是什么。这难道不就是另一种的打豆豆吗。

先拿Dubbo说事,因为大家对它都非常熟悉了。我们经常说把XX功能发布成Dubbo服务,供其它用户调用。这里的Dubbo服务指的是什么呢?那就来看看Dubbo服务是如何发布的吧。

下面是@Service注解的源码,还有Dubbo服务的典型发布方式,如图:


我们可以看到注解只能使用在类型上,而且还需要有一个接口名字。所以Dubbo发布服务是以接口为单位进行的。即Dubbo中的服务指的就是接口。可以到注册中心zookeeper里查看注册信息,如图:

 

可以看到根节点下有个dubbo节点,dubbo节点下的节点名称就是一个接口的全名,它就是一个服务。再看它的子节点,有consumers和providers,它们就是服务的消费者和提供者。再往下看就是非常多的数据信息了。

当运行起来一个工程时,工程里可以定义多个接口,所以可以发布多个Dubbo服务。那SpringCloud里的服务和Dubbo里的服务是一样的吗?答案肯定是不一样的。下面通过一个比喻来介绍。

如果把Dubbo里的服务比作是一节车厢的话,那SpringCloud里的服务就是整个列车。现实中是Dubbo里的服务就是一个接口,那SpringCloud里的服务就是整个工程。

没错,你现在运行起来的这个工程本身,就是SpringCloud服务注册与发现里的一个服务。与接口级别的Dubbo服务相比,这个工程级别的算是非常粗粒度的服务了。

只要明白了SpringCloud里的服务就是一个工程,后面的事情就很容易了。当一个工程运行起来后,怎么定位到它呢,其实就是IP和端口了。是不是发现事情渐渐明朗起来了。

 

如何注册与发现?



接触Spring久了,就会发现Spring最擅长的事情就是抽象和封装。所以我们听到最多的就是今天整合这个功能、明天整合那个中间件,把流行的好用的全部都整合进来。

很少听到Spring去发明一个东西,或优化一个算法啥的。其实能整合好就已经足够了,就这已经估值几十亿美金了吧。

其实要把这么多东西整合进来,还要保证不乱套,必须进行良好的接口抽象。就像电脑主板上要插很多东西,必须要进行合理的位置布局和插口设计。

其实SpringCloud现在已经是一块主板了,上面插满了各种组件,它用自己的“电源”和“总线”为大家“供电”和“传输数据”,保证整体的良好、平稳运行即可。

下面来解说下抽象过程,其实很容易理解。假如有一个和用户相关的工程叫user-manager吧。把它运行起来,可以对外提供服务啦。但是任何东西如果只有一个的话,都存在单点问题。

这很好解决,那就多运行几个呗。此时这个工程只有一个,就像是一个“类”(class),但它可以运行多份(IP和端口不同而已),就像是这个“类”new出来的多个实例(instance)。

类运行起来后通常称为对象。那工程运行起来后叫什么呢?上面刚刚说过,工程其实就是个服务,所以工程运行起来就叫服务实例。同一个工程同时运行多份,就表示同一个服务同时存在多个服务实例。

所以服务是一个静态的概念,服务实例是一个运行时的概念。因为只有运行起来后才能提供服务,否则代码再好,不让运行,毛用都没有。因此SpringCloud只关注运行起来的服务,于是就有了服务实例的抽象:

 


接口名字就叫ServiceInstance。Host和Port就是IP和端口,ServiceId就是服务的标识,其实就是指的工程本身,可以用工程名字user-manager来表示。InstanceId就是服务实例的标识,其实就是指的工程的一份运行,假如工程运行了四份,那就有四个InstanceId,可以分别用user-manager-01,user-manager-02,user-manager-03,user-manager-04来表示,当然这个Id一般是运行时按照某种规则生成的。

在不严格的情况下,可以把服务与服务实例当作是一回事儿,只要根据语境能分开就行。所以服务注册与发现里的服务就是服务实例。其实只需把服务实例注册上就可以啦,但是为了概念统一,SpringCloud还是抽象出了一个注册(Registration):


可以看到它只是单纯的继承服务实例接口,只是一个标记接口,就是为了概念上的统一。

上面这个接口表示的是被注册的内容,是名词语义的。还应该有一个表示注册动作的动词语义接口,是的,那就是ServiceRegistry:


可以看出服务注册接口可以注册一个Registration或取消注册一个Registration。

整天讲的服务注册其实就是两个接口而已,使用ServiceRegistry接口来注册Registration接口。这就是SpringCloud提供的服务注册的抽象,一般般吧,不过够用就行了。

那么思考下,这些服务实例信息都注册到哪里了呢?答案自然是注册中心了。这个注册中心不是SpringCloud里的内容,是第三方组件,常见的有Eureka, Consul,还有阿里的Nacos。

所以SpringCloud既不管注册中心是谁家的,也不管服务是怎么被注册上的,它只有一个要求,那就是只要实现我提供的这两个接口就行了。这样就可以被我管理了。

服务被注册上后,自然要有发现机制,要能找到它们。于是就又有了一个抽象,DiscoveryClient接口:


这个接口比较核心的功能是获取所有的serviceId,即都注册上了哪些工程。还有就是获取某个serviceId对应的所有服务实例,即某个工程的多份运行实例。SpringCloud还是不管具体实现细节,只要实现了DiscoveryClient这个接口就行了。

 

PS:以下是源码,要看的话需更加耐心,自然收获更大。

 

 

寻找自动注册机制



拿Dubbo来说,工程启动好后,Dubbo服务已经注册到了zookeeper中。SpringCloud也可以实现这个功能,称为自动注册。下面就去源码中寻找自动注册机制的工作原理。

首先发现有个自动服务注册(AutoServiceRegistration)接口,如图:


发现它是个空的标记接口,连注释都没有。看看它的实现类吧,AbstractAutoServiceRegistration,如图:

 


从注释中可以看到有这样一句话,“生命周期方法或许非常有用,通常用于服务注册的实现”。而且它包含了ServiceRegistry接口,它不就是用来注册服务实例的嘛。

整体传达给我们的意思就是,在程序启动的时候,可以调用服务注册接口来注册服务实例。咦,套路是对的,有戏呀,继续看这个类吧。

于是又找了start方法,显然是启动时调用的,如图:

 


看这个if语句,大意是,如果当前没有正在运行的话,先发布一个开始注册事件,然后注册服务实例,接着再发布一个注册完成事件,最后设置为已经正在运行。

套路方向还是对的,再看注册register方法,如图:

 


调用服务注册接口注册服务实例,完全是正确的,只可惜获取服务实例的方法是抽象的。也就是说这是个抽象类,继续找它的子类,发现没有。这条线索断了。

Spring里的所有功能几乎都是通过注解开启的。很快就找到了,@EnableDiscoveryClient,如图:

 


autoRegister属性默认为true,就是可以自动注册。这个注解引入了一个类EnableDiscoveryClientImportSelector,如下图:

 


可以看到它读取了注解中autoRegister属性的值,当为true时,又额外注册了一个类,AutoServiceRegistrationConfiguration,如下图:

 


它又注册了一个类,AutoServiceRegistrationProperties,是自动配置中用到的属性类,如下图:

 


可以看到默认是开启自动注册的,除此之外没有其它有价值信息。线索似乎又断了。对了,还要看看spring.factories文件,如下图:

 


发现了个类ServiceRegistryAutoConfiguration,赶紧看看它,如图:

 


发现没有有价值的信息,至此线索又断了。也许SpringCloud本身就是个半成品,需要找一个具体的整合示例才能发现。

 

Nacos整合SpringCloud的部分源码



我们选择从Nacos的整合源码里来发现规律,先看个整体的图吧:

 


可以看到NacosRegistration是对SpringCloud的注册接口Registration的实现,如图:


第一是它没有重新实现getInstanceId()方法,而是采用接口中的默认实现。第二是服务Id、IP和端口都是从属性类中获取。那就来看看这个属性类:

 


service其实就是serviceId了,如果不特别指定的话就是获取spring.application.name的值,其实就是application.yml中配置的应用名称了,和前面文章中指的工程名称user-manager是等价的。

IP和端口,不用设置,可以自动检测。instanceId是注册时由注册中心生成的,不用我们设置。

可以看到NacosServiceRegistry是对SpringCloud的注册接口ServiceRegistry的实现,如图:

 


可以看到对注册方法的实现,Instance和namingService都是Nacos里的类,最后调用namingService.registerInstance(serviceId, instance)方法把服务实例注册到Nacos里了。

可以看到NacosAutoServiceRegistration继承了SpringCloud的抽象类AbstractAutoServiceRegistration,就是上一小节提到的那个抽象类:

 


可以看到实现了抽象方法,返回了NacosRegistration作为注册信息。重写了注册方法,前面只是做一些检测,最后调用父类的方法完成向注册中心的注册过程。

此时这个类已经功能完整了。大家应该还记得这个注册方法是在父类的start方法里调用的,接下来就应该寻找start方法是在那里调用呢。

终于找到了NacosDiscoveryAutoConfiguration这个自动配置类,先看图吧:

 

 

 

前面四个方法把我们讲到的四个类全部注册到容器中了。最后一个方法通过lambda表达式向容器中注册了一个ApplicationRunner类型的bean。

在应用启动时,会调用这个ApplicationRunner,继而运行这个lambda表达式,就是它调用了上面提到的start方法。

终于找到根源了。那么这个自动配置类是在spring.factories文件中被指定的,如图:

 


最后,NacosDiscoveryClient类实现了SpringCloud的DiscoveryClient接口,实现了服务发现,如图:

 


因为namingService是Nacos的类,所以实际都是从Nacos注册中心获取出来的。从Nacos获取的服务实例是Instance类型,这是Nacos里的类,所以要转换为SpringCloud里的服务实例ServiceInstance类型。

  • 3
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值