微服务注册中心 Eureka解析

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
云解析DNS,个人版 1个月
简介: 随着时代的发展,应用服务的架构也随之进行不停的变迁,从单一的架构到面向服务的SOA、以及正在流行的微服务架构,甚至正在火热的服务网格,无论何种架构,适合于技术的需要以及业务的发展才是最重要的。因此,无论是以Spring Cloud和Dubbo第一代的微服务框还是以Service Mesh下一代的微服务框架,对于服务的治理仍然是微服务架构中最核心、最重要的一部分,服务治理最基础的组件是注册中心。

      随着时代的发展,应用服务的架构也随之进行不停的变迁,从单一的架构到面向服务的SOA、以及正在流行的微服务架构,甚至正在火热的服务网格,无论何种架构,适合于技术的需要以及业务的发展才是最重要的。因此,无论是以Spring Cloud和Dubbo第一代微服务框还是以Service Mesh下一代的微服务框架,对于服务的治理仍然是微服务架构中最核心、最重要的一部分,服务治理最基础的组件是注册中心。

      针对注册中心的解决方案,目前以阿里系的Dubbo 和Spring系的Spring Cloud 2分天下,Dubbo目前支持Zookeeper、Redis、Multicast 和 Simple,其官方推荐 Zookeeper。而Spring Cloud 支持Zookeeper、Consul 和 Eureka,其官方推荐 Eureka。      

      本文将基于Spring家族的Spring Cloud生态,主要聚焦其核心组件Eureka 的内部实现原理,简要解析Eureka 的总体架构,然后剖析服务信息的存储结构,最后探究跟服务生命周期相关的典型机制。首先我们了解一下Eureka的总体架构图:

      从上述架构图中我们可以看到:整个Eureka体系中,主要包括2个Roles:

      1、Eureka Client:分为Applicaton Service(服务提供方)和Application Client(服务消费方)。每个区域有一个Eureka集群,并且每个区域至少有一个Eureka服务器可以处理区域故障,以防服务器瘫痪。

      2、Eureka Server:Eureka Client向Eureka Server注册,并将自己的一些客户端信息发送Eureka Server。然后,Eureka Client通过向Eureka Server发送心跳(每30秒)来续约服务的。如果客户端持续不能续约,那么,它将在大约90秒内从服务器注册表中删除。

      注册信息和续订被复制到集群中的Eureka Server所有节点。来自任何区域的Eureka Client都可以查找注册表信息(每30秒发生一次)。根据这些注册表信息,Application Client可以远程调用Applicaton Service来消费服务

       大家有没有想过一个问题:既然作为服务注册中心,必定会存储服务的相关信息,Eureka的数据是如何存储的呢?我们知道,ZooKeeper 是将服务信息保存在树形节点上。对Eureka而言,其数据存储分为两层:数据存储层和缓存层。

      Eureka Client 在拉取服务信息时,先从缓存层获取,如果获取不到,先把数据存储层的数据加载到缓存中,再从缓存中获取。值得注意的是,数据存储层的数据结构是服务信息,而缓存中保存的是经过处理加工过的、可以直接传输到 Eureka Client 的数据结构。

      为什么要这样设计呢?Eureka 数据结构设计初衷是为了把内部的数据存储结构与对外的数据结构隔离开,就像是我们平时在进行接口设计一样,对外输出的数据结构和数据库中的数据结构往往都是不一样的。

基本原理:

  • Client服务启动后向Eureka注册,Eureka Server会将注册信息借助“Replicate”向其他Eur

eka Server进行同步,当服务消费者要调用服务提供者,则向服务注册中心获取服务提供者地址,然后会将服务提供者地址缓存在本地,下次再调用时,则直接从本地缓存中取,完成一次调用。

  • 当服务注册中心Eureka Server检测到服务提供者因为宕机、网络原因不可用时,则在服

务注册中心将服务置为DOWN状态,并把当前服务提供者状态向订阅者发布,订阅过的服务消费者更新本地缓存。

  • 服务提供者在启动后,周期性(默认30秒)向Eureka Server发送心跳,以证明当前服务

是可用状态。Eureka Server在一定的时间(默认90秒)未收到客户端的心跳,则认为服务宕机,注销该实例。

      基于Eureka架构原理,此处针对其核心活动进行源码解析,此处主要解析在实际项目维护过程中存在的关键流程:服务注册与服务剔除,以便于大家能够再一次深入了解其内部具体实现:

       1、Register:服务注册

      当Eureka客户端向Eureka Server注册时,基于提供自身的元数据,比如IP地址、端口,运行状况指示符URL,主页等。若Eureka Client没有写service ID,则默认为 ${spring.application.name}。

      此功能由DiscoveryClient类开始:该类包含了Eureka Client向Eureka Server的相关方法。其中DiscoveryClient实现了EurekaClient接口,并且它是一个单例模式,而EurekaClient继承了LookupService接口。我们先看下DiscoveryClient类源码,在代码中我们看到服务注册的方法register(),该方法是通过Http请求向Eureka Client注册,具体详情如下所示:


boolean register() throws Throwable {
        logger.info(PREFIX + appPathIdentifier + ": registering service...");
        EurekaHttpResponse<Void> httpResponse;
        try {
            httpResponse = eurekaTransport.registrationClient.register(instanceInfo);
        } catch (Exception e) {
            logger.warn("{} - registration failed {}", PREFIX + appPathIdentifier, e.getMessage(), e);
            throw e;
        }
        if (logger.isInfoEnabled()) {
            logger.info("{} - registration status: {}", PREFIX + appPathIdentifier, httpResponse.getStatusCode());
        }
        return httpResponse.getStatusCode() == 204;
    }

      继续阅读,我们发现:register()方法被InstanceInfoReplicator 类的run()方法调用,其中InstanceInfoReplicator实现了Runnable接口,run()方法具体源码如下所示:


public void run() {
        try {
            discoveryClient.refreshInstanceInfo();
            Long dirtyTimestamp = instanceInfo.isDirtyWithTime();
            if (dirtyTimestamp != null) {
                discoveryClient.register();
                instanceInfo.unsetIsDirty(dirtyTimestamp);
            }
        } catch (Throwable t) {
            logger.warn("There was a problem with the instance info replicator", t);
        } finally {
            Future next = scheduler.schedule(this, replicationIntervalSeconds, TimeUnit.SECONDS);
            scheduledPeriodicRef.set(next);
        }
    }

     沿着线索一路追踪,我们发现:在InstanceInfoReplicator类初始化过程中,有一个initScheduledTasks()方法,该方法开启了获取服务注册列表的信息,如果需要向Eureka Server注册,则开启,同时开启定时向Eureka Server服务续约的定时任务,具体源码参考如下所示:


private void initScheduledTasks() {
       ...//省略了任务调度获取注册列表的代码
        if (clientConfig.shouldRegisterWithEureka()) {
         ... 
            // Heartbeat timer
            scheduler.schedule(
                    new TimedSupervisorTask(
                            "heartbeat",
                            scheduler,
                            heartbeatExecutor,
                            renewalIntervalInSecs,
                            TimeUnit.SECONDS,
                            expBackOffBound,
                            new HeartbeatThread()
                    ),
                    renewalIntervalInSecs, TimeUnit.SECONDS);
            // InstanceInfo replicator
            instanceInfoReplicator = new InstanceInfoReplicator(
                    this,
                    instanceInfo,
                    clientConfig.getInstanceInfoReplicationIntervalSeconds(),
                    2); // burstSize
            statusChangeListener = new ApplicationInfoManager.StatusChangeListener() {
                @Override
                public String getId() {
                    return "statusChangeListener";
                }
                @Override
                public void notify(StatusChangeEvent statusChangeEvent) {
                    instanceInfoReplicator.onDemandUpdate();
                }
            };
          ...
    }

      基于相同的思路,我们可以在Eureka server端的源码,进行相关调用的探索,,有兴趣的可以自行去官网下载源码进行查阅。

       2、Eviction 服务剔除

      在默认的情况下,当Eureka客户端连续90秒没有向Eureka服务器发送服务续约,即心跳,Eureka服务器会将该服务实例从服务注册列表删除,即服务剔除。针对服务下线,目前的常用场景主要涉及以下:

  • 直接Kill -9 服务

      默认情况下,如果Eureka Server在90秒没有收到Eureka客户的续约,它会将实例从其注册表中删除。但这种做法的不好之处在于, 客户端已经停止了运行,但仍然在注册中心的列表中。 虽然通过一定的负载均衡策略或使用熔断器可以让服务正常进行,但不是一种很好的管控机制。

  • 执行Delete请求

       通过向eureka 注册中心发送Delete请求进行信号通知,以使得服务端能够及时知晓其操作含义。此种方式简单粗暴,立马见效,具体操作命令行为:


http://ip:port/eureka/apps/{application.name}/{instanst-id}

       例如,我们发起一个delete请求:具体可参考如下:


luga@OutOfMemory~% http://192.168.0.51:7001/eureka/apps/GASS-AUTH/localhost:gass-auth:8531

      备注: Eureka客户端每隔一段时间(默认30秒)会发送一次心跳到注册中心续约。如果通过这种方式下线了一个服务,而没有及时停掉的话,该服务很快又会回到服务列表中。

  • 客户端主动告知

      如果我们的Eureka客户端是是一个Spring Boot应用,可以通过调用以下代码通知注册中心下线。可以实现优雅关机放一个钩子,具体代码可参考如下:


@RestController
public class HelloController {
   @Autowired
   private DiscoveryClient client;
   @RequestMapping(value = "/hello", method = RequestMethod.GET)
   public String index() {
       java.util.List<ServiceInstance> instances = client.getInstances("hello-service");       
       return "Hello World";
   }
   @RequestMapping(value = "/offline", method = RequestMethod.GET)
   public void offLine(){
       DiscoveryManager.getInstance().shutdownComponent();
   }   
}

      其他几个活动暂不在这里进行源码跟踪解读,因为和服务注册和剔除类似,大家可以自己看下源码,深入理解。总之,借助源码,我们可以发现:整个源码基本上都是围绕架构进行。

      最后,回到前面,关于注册中心的选型问题,其本质在于生态组件的相关特性以及其所应用的场景。具体描述如下:

  • ZooKeeper 的设计原则是基于 CP,即强一致性和分区容错性。其能够保证数据的强一

致性,但舍弃了可用性,如果出现网络问题可能会影响 ZooKeeper的选举,导致 ZooKeeper注册中心的不可用。

  • Eureka 的设计原则是基于AP,即可用性和分区容错性。其能够保证注册中心的可用性

,但舍弃了数据一致性,各节点上的数据有可能是不一致的(会最终一致)。


相关文章
|
1天前
|
存储 负载均衡 监控
探索微服务架构中的服务发现与注册机制
【7月更文挑战第2天】在微服务架构的海洋中,服务发现与注册机制扮演着灯塔的角色,确保服务间的通信不因波涛汹涌而迷失方向。本文将深入探讨这一机制如何为微服务之间的交互提供动态、高效的路径,以及它对于整个系统稳定性和扩展性的重要性。我们将从基本原理出发,逐步剖析服务发现的实现方式,并讨论在设计服务注册中心时需要考虑的关键因素。
|
2天前
|
消息中间件 Java API
解析Java微服务架构:从零构建高性能系统
解析Java微服务架构:从零构建高性能系统
|
4天前
|
存储 负载均衡 云计算
微服务架构中的服务发现与注册机制
在分布式系统设计中,微服务架构因其灵活性和可伸缩性而受到青睐。本文深入探讨了微服务架构下的服务发现与注册机制,通过分析Eureka、Consul和Zookeeper等工具的原理与实践,揭示了这些机制如何优化服务间的通信和故障转移。文章结合最新研究和案例,提供了对微服务架构中关键组件的深刻见解,并讨论了其在不同场景下的应用效果。
|
18小时前
|
负载均衡 Java Maven
Java中的微服务治理与服务注册
Java中的微服务治理与服务注册
|
18小时前
|
负载均衡 Java 微服务
Java中的可扩展微服务架构设计案例解析
Java中的可扩展微服务架构设计案例解析
|
1天前
|
负载均衡 Java 微服务
Java中的可扩展微服务架构设计案例解析
Java中的可扩展微服务架构设计案例解析
|
2天前
|
负载均衡 Java Maven
Java中的微服务治理与服务注册
Java中的微服务治理与服务注册
|
5天前
|
JavaScript Java Go
Nacos vs. Eureka:微服务注册中心的对比
Nacos vs. Eureka:微服务注册中心的对比
12 0
|
21小时前
|
运维 监控 Devops
深入理解微服务架构:从理论到实践
随着数字化转型的加速,微服务架构已成为现代软件开发的重要趋势。本文将通过数据导向和科学严谨的分析方法,探讨微服务架构的核心概念、优势与挑战,并结合逻辑严密的案例研究,揭示如何在实际项目中有效实施微服务。我们将引用权威研究和统计数据,深入解读微服务对企业技术栈的影响,同时提供一套完整的微服务实施策略,旨在帮助读者构建更加灵活、可维护的软件系统。
|
21小时前
|
设计模式 弹性计算 监控
后端开发中的微服务架构:优势、挑战与实施策略
在现代软件开发中,微服务架构已成为一种流行的设计模式,特别是在后端开发领域。该架构风格通过将应用程序分解为一组小型、松耦合的服务,旨在提升可维护性、可扩展性和敏捷性。本文深入探讨了微服务架构的关键优势,面临的主要挑战,以及成功实施微服务的策略。通过引用业界案例和最新研究,文章提供了对微服务架构综合理解的视角,并讨论了如何在不断变化的技术环境中保持其有效性。

推荐镜像

更多