Spring Cloud之服务注册与发现机制

一、复杂与简单并存 

1、背景:

到底是复杂好还是简单好,这是一个没有答案的问题,也是一个哲学问题。见仁见智啦。事物整体肯定是向复杂化方向发展,但是向人们呈现时应尽量简单化。用一句话来说就是:功能复杂化,使用简单化。
因为人们的要求越来越高,所以功能肯定越来越复杂。又因为要获得更好的用户体验,所以使用方式应该越来越简单。因此对用户隐藏复杂性是一个需要专门考虑的事情。这个事情一直在进行中,而且效果还不错。下面请看一些例子:

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

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

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

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

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

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

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

二、到底什么是服务?

 1、背景

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

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

2、dubbo眼中的服务 

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

å¨è¿éæå¥å¾çæè¿°
我们可以看到一个接口就是一个服务,所以dubbo发布服务是以接口为单位进行的。即dubbo中的服务指的就是接口。可以到注册中心zookeeper里查看注册信息,如图:

å¨è¿éæå¥å¾çæè¿°

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

当运行起来一个工程时,工程里可以定义多个接口,每一个接口就是一个dubbo服务,所以可以发布多个dubbo服务。那Spring Cloud里的服务和dubbo里的服务是一样的吗?答案肯定是不一样的。

3、spring cloud 眼中的服务

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

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

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

三、如何注册与发现? 

1、抽象

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

很少听到Spring去发明一个东西,或优化一个算法啥的。其实能整合好就已经足够了,就这已经估值几十亿美金了吧。其实要把这么多东西整合进来,还要保证不乱套,必须进行良好的接口抽象。就像电脑主板上要插很多东西,必须要进行合理的位置布局和插口设计。其实Spring Cloud现在已经是一块主板了,上面插满了各种组件,它用自己的“电源”和“总线”为大家“供电”和“传输数据”,保证整体的良好、平稳运行即可。

下面来解说下抽象过程,其实很容易理解。假如有一个和用户相关的工程叫langjitianya-account-service。把它运行起来,可以对外提供服务啦。但是任何东西如果只有一个的话,都存在单点问题。这很好解决,那就多运行几个呗。此时这个工程只有一个,就像是一个“类”(class),但它可以运行多份(IP和端口不同而已),就像是这个“类”new出来的多个实例(instance)。比如langjitianya-account-service运行如下:

192.168.10.1 : 8080
192.168.10.2 : 8080
192.168.10.3 : 8080
192.168.10.4 : 8080

类运行起来后通常称为对象。那工程运行起来后叫什么呢?上面刚刚说过,工程其实就是个服务,所以工程运行起来就叫服务实例。同一个工程同时运行多份,就表示同一个服务同时存在多个服务实例。所以服务是一个静态的概念,服务实例是一个动态的概念。因为只有运行起来后才能向外提供服务,否则代码再好,没有运行起来,就是一坨死代码,毛用都没有。因此Spring Cloud只关注运行起来的服务,于是就有了服务实例的抽象:

å¨è¿éæå¥å¾çæè¿°

接口名字就叫ServiceInstance。Host和Port就是ip和端口,ServiceId就是服务的标识,其实就是指的工程本身,一般默认的就是spring.application.name表示的值。InstanceId就是服务实例的标识,其实就是指的工程的一份运行,假如工程运行了四份,那就有四个InstanceId,可以分别用:
npfdev1 : langjitianya-account-service: 8080
npfdev2 : langjitianya-account-service: 8080
npfdev3 : langjitianya-account-service: 8080
npfdev4 : langjitianya-account-service: 8080
上面是根据默认的规则生成的,默认就是: 主机名:服务名:端口。

2、注册

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

å¨è¿éæå¥å¾çæè¿°

可以看到它只是单纯的继承服务实例接口,只是一个标记接口,就是为了概念上的统一。上面这个接口表示的是被注册的内容,是名词语义的。还应该有一个表示注册动作的动词语义接口,是的,那就是ServiceRegistry:

å¨è¿éæå¥å¾çæè¿°

可以看出服务注册接口可以注册一个Registration或取消注册一个Registration。整天讲的服务注册其实就是两个接口而已,使用ServiceRegistry接口来注册Registration接口。这就是Spring Cloud提供的服务注册的抽象,一般般吧,不过够用就行了。那么思考下,这些服务实例信息都注册到哪里了呢?答案自然是注册中心了。这个注册中心不是Spring Cloud里的内容,是第三方组件,常见的有Eureka, Consul,还有阿里的Nacos。

 

 

服务提供者、服务消费者、服务发现组件这三者之间的关系大致如下:

  • 各个微服务在启动时,将自己的网络地址等信息注册到服务发现组件中,服务发现组件会存储这些信息;

  • 服务消费者可从服务发现组件查询服务提供者的网络地址,并使用该地址调用服务提供者的接口;

  • 各个微服务与服务发现组件使用一定机制(例如心跳)通信。服务发现组件如长时间无法与某微服务实例通信,就会自动注销(即:删除)该实例;

  • 当微服务网络地址发生变更(例如实例增减或者IP端口发生变化等)时,会重新注册到服务发现组件;

  • 客户端缓存:各个微服务将需要调用服务的地址缓存在本地,并使用一定机制更新(例如定时任务更新、事件推送更新等)。这样既能降低服务发现组件的压力,同时,即使服务发现组件出问题,也不会影响到服务之间的调用。

综上,服务发现组件应具备以下功能。

  • 服务注册表:服务注册表是服务发现组件的核心,它用来记录各个微服务的信息,例如微服务的名称、IP、端口等。服务注册表提供查询API和管理API,查询API用于查询可用的微服务实例,管理API用于服务的注册和注销;

  • 服务注册与服务发现:服务注册是指微服务在启动时,将自己的信息注册到服务发现组件上的过程。服务发现是指查询可用微服务列表及其网络地址的机制;

  • 服务检查:服务发现组件使用一定机制定时检测已注册的服务,如发现某实例长时间无法访问,就会从服务注册表中移除该实例。

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

3、发现

 服务被注册上后,自然要有发现机制,要能找到它们。于是就又有了一个抽象,DiscoveryClient接口, 其实我觉得这个接口的名字起的不是很贴切,叫DiscoveryServiceInstanceClient可能会更好。

å¨è¿éæå¥å¾çæè¿°

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

四、自动注册机制

 就dubbo来说,工程启动好后,dubbo服务已经注册到了zookeeper中。Spring Cloud也可以实现这个功能,称为自动注册。下面就去源码中寻找自动注册机制的工作原理。首先发现有个自动服务注册AutoServiceRegistration接口,如图:

å¨è¿éæå¥å¾çæè¿°

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

å¨è¿éæå¥å¾çæè¿°

从注释中可以看到有这样一句话,“生命周期方法或许非常有用,通常用于服务注册的实现”。而且它包含了ServiceRegistry接口,它不就是用来注册服务实例的嘛。整体传达给我们的意思就是,在程序启动的时候,可以调用服务注册接口来注册服务实例。咦,套路是对的,有戏呀,继续看这个类吧。于是又找了start方法,显然是启动时调用的,如图:

å¨è¿éæå¥å¾çæè¿°

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

å¨è¿éæå¥å¾çæè¿°

调用服务注册接口注册服务实例,完全是正确的,只可惜获取服务实例的方法是抽象的。也就是说这是个抽象类,继续找它的子类,发现没有。这条线索断了。Spring里的所有功能几乎都是通过注解开启的。很快就找到了,@EnableDiscoveryClient,如图:

å¨è¿éæå¥å¾çæè¿°

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

å¨è¿éæå¥å¾çæè¿°

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

å¨è¿éæå¥å¾çæè¿°

它将spring.cloud.service-registry.auto-registration.enabled=true,并且它又注册了一个类,AutoServiceRegistrationProperties,是自动配置中用到的属性类,如下图:

可以看到默认是开启自动注册的,除此之外没有其它有价值信息。线索似乎又断了。至此,spring cloud的服务注册与发现的抽象层已分析完毕。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值