Skip to content

Spring Cloud源码分析 - Eureka原理 #12

@TFdream

Description

@TFdream

服务注册与服务发现是微服务架构的核心功能,Eureka 则是 Spring Cloud 中负责服务注册与服务发现的组件,在学习 Eureka 之前,你需要了解什么是服务注册?什么是服务发现?只有了解了服务注册与服务发现的原理才能更好地理解 Eureka 的使用和设计原理。

服务注册指的是服务在启动时将自身的信息注册到注册中心中,方便信息进行统一管理。服务注册是客户端向注册中心提交信息的动作。

服务发现指的是从注册中心获取对应服务的信息。服务发现是客户端向注册中心获取信息的动作。

无论是服务提供者还是服务消费者,都会将信息注册到注册中心中进行统一管理。

服务消费者需要知道服务提供者的信息,比如 IP、 端口等信息,才能发起远程调用,所以需要通过拉取的动作从注册中心拉取对应服务的信息,然后发起调用。

当服务提供者出现故障后,这时是无法提供服务的,如果此时服务提供者留在注册中心的状态还是正常就会导致服务消费者调用服务失败。

那么注册中心如何知道其他服务是否健康呢,这时就需要有一个心跳的动作,心跳就是健康汇报,定时跟注册中心汇报服务健康状态。

当一定时间内无心跳产生,则证明服务可能出现故障,无法汇报健康状态,注册中心就会剔除无效的服务信息。

总结,如果需要实现完整的服务注册与服务发现的功能,我们需要有注册中心来统一存储和管理服务信息,应用程序需要将自身的信息注册到注册中心,也就是服务提供者和服务消费者的信息。整个过程包含的操作有注册、拉取、心跳、剔除等动作。

Eureka

Netflix Eureka 是一款由 Netflix 开源的基于 REST 服务的注册中心,用于提供服务发现功能。Spring Cloud Eureka 是 Spring Cloud Netflix 微服务套件的一部分,基于 Netflix Eureka 进行了二次封装,主要负责完成微服务架构中的服务治理功能。

Spring Cloud Eureka 是一个基于 REST 的服务,并提供了基于 Java 的客户端组件,能够非常方便的将服务注册到 Spring Cloud Eureka 中进行统一管理。 

Eureka架构剖析

如下图所示,Eureka 的架构主要分为 Eureka Server 和 Eureka Client 两部分,Eureka Client 又分为 Applicaton Service 和 Application Client,Applicaton Service 就是服务提供者,Application Client 就是服务消费者。

image

我们首先会在应用程序中依赖 Eureka Client,项目启动后 Eureka Client 会向 Eureka Server 发送请求,进行注册,并将自己的一些信息发送给 Eureka Server。

注册成功后,每隔一定的时间,Eureka Client 会向 Eureka Server 发送心跳来续约服务,也就是汇报健康状态。 如果客户端长时间没有续约,那么 Eureka Server 大约将在 90 秒内从服务器注册表中删除客户端的信息。

Eureka Client 还会定期从 Eureka Server 拉取注册表信息,然后根据负载均衡算法得到一个目标,并发起远程调用,关于负载均衡在后面的课时会详细介绍,也就是 Ribbon 组件。

应用停止时也会通知 Eureka Server 移除相关信息,信息成功移除后,对应的客户端会更新服务的信息,这样就不会调用已经下线的服务了,当然这个会有延迟,有可能会调用到已经失效的服务,所以在客户端会开启失败重试功能来避免这个问题。

Eureka Server 会有多个节点组成一个集群,保证高可用。Eureka Server 没有集成其他第三方存储,而是存储在内存中。所以 Eureka Server 之间会将注册信息复制到集群中的 Eureka Server 的所有节点。 这样数据才是共享状态,任何的 Eureka Client 都可以在任何一个 Eureka Server 节点查找注册表信息。

Eureka 部署

1、standalone模式

2、集群模式

Eureka 注册表

Eureka 作为注册中心,统一管理服务实例的信息。但我们在部署时并没有配置任何数据库相关的信息,也就是说 Eureka 管理的信息不是存储在数据库中的。

还有一点就是前面讲的集群模式,会将注册的信息复制给其他节点,更加验证了不可能采用数据库来存储注册信息,通过复制的模式我们可以推断,注册信息肯定会在每个节点都存储一份,信息的变更通过复制的形式解决,那么信息肯定是存储在 Eureka Server 的节点内部的,通过源码可以验证答案,Eureka 的注册信息是存储在 ConcurrentHashMap 中的。

注册表定义在 AbstractInstanceRegistry 类中,Map 的 key 是服务名称,也就是 MONKEY-ARTICLE-SERVICE 。value 是一个 Map。value 的 Map 的 key 是服务实例的 ID, 比如这里的monkey-article-service:192.168.31.244:2012 。value 的 Map 里的 value 是 Lease 类,Lease 中存储了实例的注册时间、上线时间等信息,还有具体的实例信息,比如 IP、端口、健康检查的地址等信息,对应的是 InstanceInfo。

这是 Lease 类中的字段信息,第一个 holder 是实例信息 InstanceInfo。然后是取消注册时间,也就是实例下线的时间。接着是服务注册的时间和服务上线的时间,以及最后更新的时间。需要注意的是最后更新的时间,也就是 lastUpdateTimestamp,从字面上理解就是最后更新的时间,实际上它是最后一次续约的时间加上租约的时长。最后一个字段是租约的时长。

那么 Eureka 将注册的服务信息存储在内存中原因是什么呢?

通过我个人的分析,存在内存中的优势在于性能高。然后就是对使用者来说,部署简单,不需要依赖于第三方存储。

有优势那么肯定也有劣势,内存存储的劣势在于对存储容量的扩容难度高,每个 Eureka Server 都是全量的存储一份注册表,假如存储空间不够了,需要扩容,那么所有的 Eureka Server 节点都必须扩容,必须采用相同的内存配置。

Eureka 核心操作主要有注册、续约、下线、移除,接口是com.netflix.eureka.lease.LeaseManager,说简单点这些操作都是针对注册表的操作,也就是对 Map 的操作,听上去好像很简单,实际上在每个操作背后,都有它自己的业务逻辑,不是简单的增、删、改、查。

打开 LeaseManager 接口,找到 register 的实现,进入 AbstractInstanceRegistry 这个类的 register 方法。看下具体的代码实现,刚开始会获取一把读锁,然后通过服务名称从注册表中获取对应的信息,如果不存在则创建一个,然后添加进去。

再获取 Lease 信息,如果存在则用现有的 InstanceInfo,如果不存在,则认为是新的注册,会计算跟续约相关的值,该值在自我保护的逻辑中会用到。

后面就是往一些变更队列里添加数据,会有对应的消费者去消费,最后将注册表的缓存进行清空,Eureka Client 在获取服务信息时,Eureka Server 为了提高读取性能,增加了缓存操作。所以当实例信息发生变化时需要将之前的缓存移除掉,最后释放锁。

Eureka集群各节点的数据同步

image

如上图所示,Eureka 集群采用相互注册的方式实现高可用集群,任何一台注册中心故障都不会影响服务的注册与发现。前面也介绍了 Eureka 的注册表是存储在内存中的,当服务 A 注册到 Eureka Server 2 的节点上后,会去 Eureka Server 1 的节点拉取信息,正常情况下是拉取不到信息的,为了能够正常的拉取信息,Eureka Server 内部采用了复制的方式向各个节点进行数据同步操作。

我们简单的来看下当服务注册后,信息是如何同步的,代码在com.netflix.eureka.registry.PeerAwareInstanceRegistryImpl.register(InstanceInfo, boolean)中,复制的方法是 replicateToPeers,主要参数是 Action,Action 表示操作的类型,有心跳、注册、取消等操作。还有服务名称和实例 ID。

通过 peerEurekaNodes.getPeerEurekaNodes() 得到 Eureka Server 的所有节点信息,在当前节点中循环进行复制操作,需要排除自己,不需要将信息同步给自己。复制操作会根据 Action 来进行对应的操作,通过 node 对象的方法构建复制的任务,任务本质还是通过调用 Eureka 的 Rest API 来进行操作的。

Eureka自我保护机制

自我保护机制是为了避免因网络分区故障而导致服务不可用的问题。具体现象为当网络故障后,所有的服务与 Eureka Server 之间无法进行正常通信,一定时间后,Eureka Server 没有收到续约的信息,将会移除没有续约的实例,这个时候正常的服务也会被移除掉,所以需要引入自我保护机制来解决这种问题。

Eureka健康检查

在前面我们讲过 Eureka 的心跳机制,Eureka Client 会定时发送心跳给 Eureka Server 来证明自己处于健康的状态,如下图所示。

image

但在某些场景下,服务仍处于存活状态,却已经不能对外提供服务了,比如数据库出问题了,这时,Eureka Client 还是会定时发送心跳,由于心跳正常,客户端在请求时还是会请求到这个出了问题的服务实例。

在第一课时我们已经讲过 Spring Boot 应用的健康状态监控,通过 Actuator 来管理健康状态,同时支持使用者扩展 /health 端点,常用的框架中都扩展了 /health 端点,比如 Mongodb、ElasticSearch 等。

我们只要在项目中集成 Actuator,就可以统一管理应用的健康状态,那么我们可以将这个状态反馈给 Eureka Server,这样当应用处于不健康的状态,Eureka Server 就能知道这个应用不健康了,然后将其进行下线操作,这样客户端就不会调用这个不健康的服务实例了,这就是 Eureka 的健康检查。

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions