博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
深度解析SpringCloud之服务注册与发现机制(万字深度好文)
阅读量:4292 次
发布时间:2019-05-27

本文共 5716 字,大约阅读时间需要 19 分钟。

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

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类型。

转载地址:http://qxzws.baihongyu.com/

你可能感兴趣的文章
分布式架构系列: 负载均衡技术详解
查看>>
python3安装scrapy
查看>>
python正则表达式入门一
查看>>
python正则表达式入门二
查看>>
scrapy运行
查看>>
XPATH入门
查看>>
python爬虫 CSS选择器
查看>>
正常关闭java程序
查看>>
查看linux核心数
查看>>
数据结构与算法三: 数组
查看>>
Activiti工作流会签二 启动流程
查看>>
Activiti工作流会签三 撤销,审批,驳回
查看>>
Oauth2方式实现单点登录
查看>>
CountDownLatch源码解析加流程图详解--AQS类注释翻译
查看>>
ES相关度评分
查看>>
我们一起做一个可以商用的springboot脚手架
查看>>
idea在搭建ssm框架时mybatis整合问题 无法找到mapper
查看>>
java设计基本原则----单一职责原则
查看>>
HashMap的实现
查看>>
互斥锁 synchronized分析
查看>>