前言

现在流行的微服务体系结构正在改变我们构建应用程序的方式,从单一的单体服务转变为越来越小的可单独部署的服务(称为微服务),共同构成了我们的应用程序。当进行一个业务时不可避免就会存在多个服务之间调用,假如一个服务 A 要访问在另一台服务器部署的服务 B,那么前提是服务 A 要知道服务 B 所在机器的 IP 地址和服务对应的端口,最简单的方式就是让服务 A 自己去维护一份服务 B 的配置(包含 IP 地址和端口等信息),但是这种方式有几个明显的缺点:随着我们调用服务数量的增加,配置文件该如何维护;缺乏灵活性,如果服务 B 改变 IP 地址或者端口,服务 A 也要修改相应的文件配置;还有一个就是进行服务的动态扩容或缩小不方便。
一个比较好的解决方案就是 服务发现(Service Discovery)。它抽象出来了一个注册中心,当一个新的服务上线时,它会将自己的 IP 和端口注册到注册中心去,会对注册的服务进行定期的心跳检测,当发现服务状态异常时将其从注册中心剔除下线。服务 A 只要从注册中心中获取服务 B 的信息即可,即使当服务 B 的 IP 或者端口变更了,服务 A 也无需修改,从一定程度上解耦了服务。服务发现目前业界有很多开源的实现,比如 apachezookeeperNetflixeurekahashicorpconsulCoreOSetcd

Eureka 是什么

Eurekagithub 上对其的定义为

Eureka is a REST (Representational State Transfer) based service that is primarily used in the AWS cloud for locating services for the purpose of load balancing and failover of middle-tier servers.
At Netflix, Eureka is used for the following purposes apart from playing a critical part in mid-tier load balancing.

Eureka 是由 Netflix 公司开源,采用的是 Client / Server 模式进行设计,基于 http 协议和使用 Restful Api 开发的服务注册与发现组件,提供了完整的服务注册和服务发现,可以和 Spring Cloud 无缝集成。其中 Server 端扮演着服务注册中心的角色,主要是为 Client 端提供服务注册和发现等功能,维护着 Client 端的服务注册信息,同时定期心跳检测已注册的服务当不可用时将服务剔除下线,Client 端可以通过 Server 端获取自身所依赖服务的注册信息,从而完成服务间的调用。遗憾的是从其官方的 github wiki 可以发现,2.0 版本已经不再开源。但是不影响我们对其进行深入了解,毕竟服务注册、服务发现相对来说还是比较基础和通用的,其它开源实现框架的思想也是想通的。

服务注册中心(Eureka Server)

我们在项目中引入 Eureka Server 的相关依赖,然后在启动类加上注解 @EnableEurekaServer,就可以将其作为注册中心,启动服务后访问页面如下:

我们继续添加两个模块 service-providerservice-consumer,然后在启动类加上注解 @EnableEurekaClient 并指定注册中心地址为我们刚刚启动的 Eureka Server,再次访问可以看到两个服务都已经注册进来了。

Demo 仓库地址:https://github.com/mghio/depth-in-springcloud

可以看到 Eureka 的使用非常简单,只需要添加几个注解和配置就实现了服务注册和服务发现,接下来我们看看它是如何实现这些功能的。

服务注册(Register)

注册中心提供了服务注册接口,用于当有新的服务启动后进行调用来实现服务注册,或者心跳检测到服务状态异常时,变更对应服务的状态。服务注册就是发送一个 POST 请求带上当前实例信息到类 ApplicationResourceaddInstance 方法进行服务注册。

可以看到方法调用了类 PeerAwareInstanceRegistryImplregister 方法,该方法主要分为两步:

  1. 调用父类 AbstractInstanceRegistryregister 方法把当前服务注册到注册中心
  2. 调用 replicateToPeers 方法使用异步的方式向其它的 Eureka Server 节点同步服务注册信息

服务注册信息保存在一个嵌套的 map 中,它的结构如下:

第一层 mapkey 是应用名称(对应 Demo 里的 SERVICE-PROVIDER),第二层 mapkey 是应用对应的实例名称(对应 Demo 里的 mghio-mbp:service-provider:9999),一个应用可以有多个实例,主要调用流程如下图所示:

服务续约(Renew)

服务续约会由服务提供者(比如 Demo 中的 service-provider)定期调用,类似于心跳,用来告知注册中心 Eureka Server 自己的状态,避免被 Eureka Server 认为服务时效将其剔除下线。服务续约就是发送一个 PUT 请求带上当前实例信息到类 InstanceResourcerenewLease 方法进行服务续约操作。

进入到 PeerAwareInstanceRegistryImplrenew 方法可以看到,服务续约步骤大体上和服务注册一致,先更新当前 Eureka Server 节点的状态,服务续约成功后再用异步的方式同步状态到其它 Eureka Server 节上,主要调用流程如下图所示:

服务下线(Cancel)

当服务提供者(比如 Demo 中的 service-provider)停止服务时,会发送请求告知注册中心 Eureka Server 进行服务剔除下线操作,防止服务消费者从注册中心调用到不存在的服务。服务下线就是发送一个 DELETE 请求带上当前实例信息到类 InstanceResourcecancelLease 方法进行服务剔除下线操作。

进入到 PeerAwareInstanceRegistryImplcancel 方法可以看到,服务续约步骤大体上和服务注册一致,先在当前 Eureka Server 节点剔除下线该服务,服务下线成功后再用异步的方式同步状态到其它 Eureka Server 节上,主要调用流程如下图所示:

服务剔除(Eviction)

服务剔除是注册中心 Eureka Server 在启动时就启动一个守护线程 evictionTimer 来定期(默认为 60 秒)执行检测服务的,判断标准就是超过一定时间没有进行 Renew 的服务,默认的失效时间是 90 秒,也就是说当一个已注册的服务在 90 秒内没有向注册中心 Eureka Server 进行服务续约(Renew),就会被从注册中心剔除下线。失效时间可以通过配置 eureka.instance.leaseExpirationDurationInSeconds 进行修改,定期执行检测服务可以通过配置 eureka.server.evictionIntervalTimerInMs 进行修改,主要调用流程如下图所示:

服务提供者(Service Provider)

对于服务提供方(比如 Demo 中的 service-provider 服务)来说,主要有三大类操作,分别为 服务注册(Register)服务续约(Renew)服务下线(Cancel),接下来看看这三个操作是如何实现的。

服务注册(Register)

一个服务要对外提供服务,首先要在注册中心 Eureka Server 进行服务相关信息注册,能进行这一步的前提是你要配置 eureka.client.register-with-eureka=true,这个默认值为 true,注册中心不需要把自己注册到注册中心去,把这个配置设为 false,这个调用比较简单,主要调用流程如下图所示:

服务续约(Renew)

服务续约是由服务提供者方定期(默认为 30 秒)发起心跳的,主要是用来告知注册中心 Eureka Server 自己状态是正常的还活着,可以通过配置 eureka.instance.lease-renewal-interval-in-seconds 来修改,当然服务续约的前提是要配置 eureka.client.register-with-eureka=true,将该服务注册到注册中心中去,主要调用流程如下图所示:

服务下线(Cancel)

当服务提供者方服务停止时,要发送 DELETE 请求告知注册中心 Eureka Server 自己已经下线,好让注册中心将自己剔除下线,防止服务消费方从注册中心获取到不可用的服务。这个过程实现比较简单,在类 DiscoveryClientshutdown 方法加上注解 @PreDestroy,当服务停止时会自动触发服务剔除下线,执行服务下线逻辑,主要调用流程如下图所示:

服务消费者(Service Consumer)

这里的服务消费者如果不需要被其它服务调用的话,其实只会涉及到两个操作,分别是从注册中心 获取服务列表(Fetch)更新服务列表(Update)。如果同时也需要注册到注册中心对外提供服务的话,那么剩下的过程和上文提到的服务提供者是一致的,这里不再阐述,接下来看看这两个操作是如何实现的。

获取服务列表(Fetch)

服务消费者方启动之后首先肯定是要先从注册中心 Eureka Server 获取到可用的服务列表同时本地也会缓存一份。这个获取服务列表的操作是在服务启动后 DiscoverClient 类实例化的时候执行的。

可以看出,能发生这个获取服务列表的操作前提是要保证配置了 eureka.client.fetch-registry=true,该配置的默认值为 true,主要调用流程如下图所示:

更新服务列表(Update)

由上面的 获取服务列表(Fetch) 操作过程可知,本地也会缓存一份,所以这里需要定期的去到注册中心 Eureka Server 获取服务的最新配置,然后比较更新本地缓存,这个更新的间隔时间可以通过配置 eureka.client.registry-fetch-interval-seconds 修改,默认为 30 秒,能进行这一步更新服务列表的前提是你要配置 eureka.client.register-with-eureka=true,这个默认值为 true。主要调用流程如下图所示:

总结

工作中项目使用的是 Spring Cloud 技术栈,它有一套非常完善的开源代码来整合 Eureka,使用起来非常方便。之前都是直接加注解和修改几个配置属性一气呵成的,没有深入了解过源码实现,本文主要是阐述了服务注册、服务发现等相关过程和实现方式,对 Eureka 服务发现组件有了更近一步的了解。


参考文章
Netflix Eureka
Service Discovery in a Microservices Architecture

服务发现组件之 — Eureka的更多相关文章

  1. 浅谈SpringCloud (二) Eureka服务发现组件

    上面学习到了如何由一个程序访问另一个程序,那么如果使用SpringCloud来进行访问,该如何访问呐? 可以借助Eureka服务发现组件进行访问. 可以借助官方文档:https://spring.io ...

  2. 阿里开源服务发现组件 Nacos快速入门

    最近几年随着云计算和微服务不断的发展,各大云厂商也都看好了微服务解决方案这个市场,纷纷推出了自己针对微服务上云架构的解决方案,并且诞生了云原生,Cloud Native的概念. 云原生是一种专门针对云 ...

  3. Alibaba Nacos 服务发现组件集群部署

    前面学习了单机模式下的启动,生产环境中部署nacos肯定是使用集群模式cluster保证高可用. 官方文档的集群部署推荐使用VIP+域名模式,把所有服务列表放到一个vip下面,然后挂到一个域名下面. ...

  4. 服务发现与注册-Eureka

    1.搭建 创建一个Springboot项目,添加依赖 <dependencies> <!--添加Eureka服务器端依赖--> <dependency> <g ...

  5. 第二章 SpringCloud之Eureka-Server服务发现组件

    1.Eureka简介 文档:https://cloud.spring.io/spring-cloud-netflix/spring-cloud-netflix.html ############### ...

  6. springcloud实践(一)服务发现:Eureka

    Eureka 入门 是什么? Eureka 是 Netflix 开源的一个 RESTful服务,主要用于服务注册与发现. 它由Eureka server 和Eureka client组成. Eurek ...

  7. 微服务的发现与注册--Eureka

    目录 服务提供者.服务消费者.服务发现组件三者之间的关系 Eureka 简介 Eureka Server Eureka Client 编写Eureka Server 将微服务注册到Eureka Ser ...

  8. 分享知识-快乐自己:微服务的注册与发现(基于Eureka)

    1):微服务架构 服务提供者.服务消费者.服务发现组件这三者之间的关系: 各个微服务在启动时,将自己的网络地址等信息注册到服务发现组件中,服务发现组件会存储这些信息. 服务消费者可从服务发现组件查询服 ...

  9. Spring Cloud Eureka 服务发现 4.2

      在微服务架构中,服务发现可以说是最为核心和基础的模块,该模块主要用于实现各个微服务实例的自动化注册与发现.在Spring Cloud的子项目中,Spring Cloud Netflix提供了Eur ...

随机推荐

  1. linux下的时区修改

    Centos 7时区问题: 通常使用tzselect命令选择时区,今天在修改centos7的时区的时候,修改完以后时区还是没有发生变化,重启也是没有用的:通过网络的帮助了解到,在Centos和ubun ...

  2. CDC与HDC的区别以及相互转换

    CDC是MFC的DC的一个类  HDC是DC的句柄,API中的一个类似指针的数据类型.  MFC类的前缀都是C开头的  H开头的大多数是句柄  这是为了助记,是编程读\写代码的好的习惯.  CDC中所 ...

  3. 吴裕雄--天生自然 HADOOP大数据分布式处理:修改CenterOS 7系统时间为北京时间

  4. Numpy入门(三):Numpy概率模块和线性代数模块

    Numpy中经常使用到的两个模块是概率模块和线性代数模块,random 和 linalg 两个模块. 概率模块 产生二项分布的随机数:np.random.binomial(n,p,size=-),其中 ...

  5. Proto3:Techniques

    本文描述处理Protocol Buffer常用到的一些设计模式.你也可以给Protocol Buffers discussion group发送设计或使用问题. 流式多条消息 如果你想将多个消息写入到 ...

  6. phaser2->3:来个打地鼠试水

    本文中phaser具体版本 phaser2:2.8.1 phaser3:3.17.0 一.实现效果二.实现细节三.项目总结四.参考文档 一.实现效果 源码地址(phaser2&phaser3) ...

  7. 机器CPU load过高问题排查

    load average的概念 系统平均负载定义:在特定时间间隔内运行队列中(在CPU上运行或者等待运行多少进程)的平均进程数.如果一个进程满足以下条件则其就会位于运行队列中: 它没有在等待I/O操作 ...

  8. Hihocoder1456 Rikka with Lattice

    众所周知,萌萌哒六花不擅长数学,所以勇太给了她一些数学问题做练习,其中有一道是这样的:勇太有一个$n times m$的点阵,他想要从这$n times m$个点中选出三个点 ${A,B,C}$,满足 ...

  9. Redis(1)——5种基本数据结构

    一.Redis 简介 "Redis is an open source (BSD licensed), in-memory data structure store, used as a d ...

  10. 关于.net MVC中主视图和分部视图的数据共享遇到的问题

    今天在开发web时因为调用到的分部视图需要有个隐藏域.然后因为当我们第一次调用分部视图时,是用 @Html.Partial("DetailDataPart")在主视图里把它嵌进去主 ...