eureka 服务治理

Eureka服务注册与发现

创建服务注册中心

  • 添加pom依赖
<dependencies>
      <dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
      </dependency>
</dependencies>
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${spring-cloud.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
  • 启动一个服务注册中心,只需要一个注解@EnableEurekaServer,这个注解需要在springboot工程的启动application类上加
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {

    public static void main(String[] args) {
        SpringApplication.run( EurekaServerApplication.class, args );
    }
}
  • 默认情况下erureka server也是一个eureka client ,必须要指定一个 server,eureka server的配置文件如下
server.port=8761
eureka.instance.hostname=localhost
#防止自己注册自己
eureka.client.register-with-eureka=false
#注册中心的职责就是维护服务实例,它并不需要去检索服务,所以设置成false
eureka.client.fetch-registry=false
eureka.client.service-url.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka/
  • 启动Eureka-server,查看UI界面


创建服务提供者

  • 添加pom依赖
<dependencies>
    <dependency>
       <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
<dependencies>
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${spring-cloud.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
  • 通过注解@EnableEurekaClient 表明自己是一个eurekaclient.
@SpringBootApplication
@EnableEurekaClient
@RestController
public class ServiceHiApplication {

    public static void main(String[] args) {
        SpringApplication.run( ServiceHiApplication.class, args );
    }
}
  • 配置文件配置
server.port=8762
#为服务命名
spring.application.name=eureka-client
#指定服务注册中心地址
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/
  • 启动服务
    启动服务,如果控制台输入如下的日志,说明服务注册成功


    并且此时再去查看服务注册中心,会发现会有一个服务实力已注册到注册中心


  • 源码分析
    使用@EnableEurekaClient注解或者@EnableDiscoveryClient注解都可以完成服务的注册,我们可以先看下@EnableEurekaClient的源码,
/**
 * Convenience annotation for clients to enable Eureka discovery configuration
 * (specifically). Use this (optionally) in case you want discovery and know for sure that
 * it is Eureka you want. All it does is turn on discovery and let the autoconfiguration
 * find the eureka classes if they are available (i.e. you need Eureka on the classpath as
 * well).
 *
 * @author Dave Syer
 * @author Spencer Gibb
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface EnableEurekaClient {

}

什么多余的注解都没有,只能看到这是一个简单的注解标识,我们可以看一下注释:

/**
 * Convenience annotation for clients to enable Eureka discovery configuration
 * (specifically). Use this (optionally) in case you want discovery and know for sure that
 * it is Eureka you want. All it does is turn on discovery and let the autoconfiguration
 * find the eureka classes if they are available (i.e. you need Eureka on the classpath as
 * well).
 *
 * @author Dave Syer
 * @author Spencer Gibb
 */

简单的翻译一下:

为客户端提供方便的注释,以启用Eureka发现配置(特别是)。使用这个(可选),如果你想要发现,
并确定它是你想要的Eureka。它所做的一切就是打开发现,并让自动配置在可用的情况下找到eureka类(即,您也需要类路径上的Eureka)。

阅读上面的注释,我们很清楚的知道,其实@EnableEurekaClient注解就是一种方便使用eureka的注解而已,可以说使用其他的注册中心后,都可以使用@EnableDiscoveryClient注解,但是使用@EnableEurekaClient的情景,就是在服务采用eureka作为注册中心的时候,使用场景较为单一。

我们在看一下EnableDiscoveryClient的源码:

/**
 * Annotation to enable a DiscoveryClient implementation.
* 注释以启用DiscoveryClient实现。
 * @author Spencer Gibb
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(EnableDiscoveryClientImportSelector.class)
public @interface EnableDiscoveryClient {

    /**
     * If true, the ServiceRegistry will automatically register the local server.
     */
    boolean autoRegister() default true; 
 
 
 
 
}

从该注解的注释中我们知道,它主要用来开启DiscoveryClient的实力。查看DiscoveryClient相关源码梳理得到如下图所示的关系:


右边的org.springframework.cloud.client.discovery.DiscoveryClient是SpringCloud的接口,它定义了用来发现服务的常用方法,通过该接口有效的屏蔽服务治理的实现细节,所以使用SpringCloud构建的微服务应用可以很方便的切换不同的服务治理框架,而不改动程序代码,只需要添加一些针对服务治理框架的配置即可。如,该接口的实现类有如下几种,我们使用了其中的一种。
org.springframework.cloud.netflix.eureka.EurekaDiscoveryClient是对该接口的实现,从命名来看,它实现的是对Eureka发现服务的封装,他们都是Netflix开源包中的内容,主要定义了针对Eureka的发现服务的抽象方法,而真正实现发现服务的则是Netflix包中的com.netflix.discovery.DiscoveryClient类。
我们看一看com.netflix.discovery.DiscoveryClient类的注解:

该类的核心功能就是完成向Eureka Server服务中心的注册,续约,当服务shutdown的时候取消租约,查询注册到Eureka Server的服务实例列表。

  • 服务注册
    我们可以看到带有@Inject注解的DiscoverClient的构造函数
@Inject
    DiscoveryClient(ApplicationInfoManager applicationInfoManager, EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs args,
                    Provider<BackupRegistry> backupRegistryProvider) {
      ......
      ......
       

        try {
            // default size of 2 - 1 each for heartbeat and cacheRefresh
            scheduler = Executors.newScheduledThreadPool(2,
                    new ThreadFactoryBuilder()
                            .setNameFormat("DiscoveryClient-%d")
                            .setDaemon(true)
                            .build());

            heartbeatExecutor = new ThreadPoolExecutor(
                    1, clientConfig.getHeartbeatExecutorThreadPoolSize(), 0, TimeUnit.SECONDS,
                    new SynchronousQueue<Runnable>(),
                    new ThreadFactoryBuilder()
                            .setNameFormat("DiscoveryClient-HeartbeatExecutor-%d")
                            .setDaemon(true)
                            .build()
            );  // use direct handoff

            cacheRefreshExecutor = new ThreadPoolExecutor(
                    1, clientConfig.getCacheRefreshExecutorThreadPoolSize(), 0, TimeUnit.SECONDS,
                    new SynchronousQueue<Runnable>(),
                    new ThreadFactoryBuilder()
                            .setNameFormat("DiscoveryClient-CacheRefreshExecutor-%d")
                            .setDaemon(true)
                            .build()
            );  // use direct handoff
        } catch (Throwable e) {
            throw new RuntimeException("Failed to initialize DiscoveryClient!", e);
        }

        if (clientConfig.shouldRegisterWithEureka() && clientConfig.shouldEnforceRegistrationAtInit()) {
            try {
                if (!register() ) {
                    throw new IllegalStateException("Registration error at startup. Invalid server response.");
                }
            } catch (Throwable th) {
                logger.error("Registration error at startup: {}", th.getMessage());
                throw new IllegalStateException(th);
            }
        }
    

        // finally, init the schedule tasks (e.g. cluster resolvers, heartbeat, instanceInfo replicator, fetch
        initScheduledTasks();
        ......
        ......
        initTimestampMs = System.currentTimeMillis();
        logger.info("Discovery Client initialized at timestamp {} with initial instances count: {}",
                initTimestampMs, this.getApplications().size());
    }

可以看到 initScheduledTasks()方法,我们在具体看initScheduledTasks()方法

 /**
     * Initializes all scheduled tasks.
     */
    private void initScheduledTasks() {
        ...

        if (clientConfig.shouldRegisterWithEureka()) {
            int renewalIntervalInSecs = instanceInfo.getLeaseInfo().getRenewalIntervalInSecs();
            int expBackOffBound = clientConfig.getHeartbeatExecutorExponentialBackOffBound();
            logger.info("Starting heartbeat executor: " + "renew interval is: {}", renewalIntervalInSecs);

            // 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
            ......
            ......
            instanceInfoReplicator.start(clientConfig.getInitialInstanceInfoReplicationIntervalSeconds());
        } else {
            logger.info("Not registering with Eureka server per configuration");
        }
    }

"服务续约","服务注册"在同一个if语句中,服务注册到Eureka Server后,需要有一个心跳去续约,表明自己还活着,可用,防止被剔除。对于服务续约相关的时间控制参数:

#指示eureka客户端需要向eureka服务器发送心跳的频率(以秒为单位),以指示该服务器仍处于活动状态
eureka.instance.lease-renewal-interval-in-seconds=30
#指示eureka服务器在接收到最后一个心跳之后等待的时间(以秒为单位),然后才可以从视图中删除该实例,
eureka.instance.lease-expiration-duration-in-seconds=90

可以继续查看InstanceInfoReplicator的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);
        }
    }

可以看到discoveryClient.register()完成了服务的注册功能。继续查看register()的方法

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

可以看到注册的时候传入了instanceInfo对象,我们可以看看instanceInfo对象

public class InstanceInfo {

    private static final String VERSION_UNKNOWN = "unknown";

    /**
     * {@link InstanceInfo} JSON and XML format for port information does not follow the usual conventions, which
     * makes its mapping complicated. This class represents the wire format for port information.
     */
    public static class PortWrapper {
        private final boolean enabled;
        private final int port;

        @JsonCreator
        public PortWrapper(@JsonProperty("@enabled") boolean enabled, @JsonProperty("$") int port) {
            this.enabled = enabled;
            this.port = port;
        }

        public boolean isEnabled() {
            return enabled;
        }

        public int getPort() {
            return port;
        }
    }

    private static final Logger logger = LoggerFactory.getLogger(InstanceInfo.class);

    public static final int DEFAULT_PORT = 7001;
    public static final int DEFAULT_SECURE_PORT = 7002;
    public static final int DEFAULT_COUNTRY_ID = 1; // US

    // The (fixed) instanceId for this instanceInfo. This should be unique within the scope of the appName.
    private volatile String instanceId;

    private volatile String appName;
    @Auto
    private volatile String appGroupName;

    private volatile String ipAddr;

    private static final String SID_DEFAULT = "na";
    @Deprecated
    private volatile String sid = SID_DEFAULT;

    private volatile int port = DEFAULT_PORT;
    private volatile int securePort = DEFAULT_SECURE_PORT;

    @Auto
    private volatile String homePageUrl;
    @Auto
    private volatile String statusPageUrl;
    @Auto
    private volatile String healthCheckUrl;
    @Auto
    private volatile String secureHealthCheckUrl;
    @Auto
    private volatile String vipAddress;
    @Auto
    private volatile String secureVipAddress;
    @XStreamOmitField
    private String statusPageRelativeUrl;
    @XStreamOmitField
    private String statusPageExplicitUrl;
    @XStreamOmitField
    private String healthCheckRelativeUrl;
    @XStreamOmitField
    private String healthCheckSecureExplicitUrl;
    @XStreamOmitField
    private String vipAddressUnresolved;
    @XStreamOmitField
    private String secureVipAddressUnresolved;
    @XStreamOmitField
    private String healthCheckExplicitUrl;
    @Deprecated
    private volatile int countryId = DEFAULT_COUNTRY_ID; // Defaults to US
    private volatile boolean isSecurePortEnabled = false;
    private volatile boolean isUnsecurePortEnabled = true;
    private volatile DataCenterInfo dataCenterInfo;
    private volatile String hostName;
    private volatile InstanceStatus status = InstanceStatus.UP;
    private volatile InstanceStatus overriddenStatus = InstanceStatus.UNKNOWN;
    @XStreamOmitField
    private volatile boolean isInstanceInfoDirty = false;
    private volatile LeaseInfo leaseInfo;
    @Auto
    private volatile Boolean isCoordinatingDiscoveryServer = Boolean.FALSE;
    @XStreamAlias("metadata")
    private volatile Map<String, String> metadata;
    @Auto
    private volatile Long lastUpdatedTimestamp;
    @Auto
    private volatile Long lastDirtyTimestamp;
    @Auto
    private volatile ActionType actionType;
    @Auto
    private volatile String asgName;
    private String version = VERSION_UNKNOWN;

我们可以看到注册服务的信息包括port、instanceId、appName、ipAddr、homePageUrl、vipAddress......等等,该对象就是注册时客户端发送给服务端的元数据。

  • 服务获取与服务续约
    我们可以继续查看DiscoveryClient类的initScheduledTasks()方法
 private void initScheduledTasks() {
        if (clientConfig.shouldFetchRegistry()) {
            // registry cache refresh timer
            int registryFetchIntervalSeconds = clientConfig.getRegistryFetchIntervalSeconds();
            int expBackOffBound = clientConfig.getCacheRefreshExecutorExponentialBackOffBound();
            scheduler.schedule(
                    new TimedSupervisorTask(
                            "cacheRefresh",
                            scheduler,
                            cacheRefreshExecutor,
                            registryFetchIntervalSeconds,
                            TimeUnit.SECONDS,
                            expBackOffBound,
                            new CacheRefreshThread()
                    ),
                    registryFetchIntervalSeconds, TimeUnit.SECONDS);
        }
        ......
        ......
}

默认情况下,eureka.client.fetch-registry=true,大部分情况下,我们不需要关心。为了定期更新服务端的服务清单,以保证客户端能够能够访问确实健康的服务实例,服务获取的请求不会只限于服务启动,而是一个定时执行的任务,eureka.client.registry-fetch-interval-seconds=30,默认值是30s,意思是从eureka获取注册表信息的频率(秒)是30秒。我们继续查看源码如下:


服务续约
/**
     * The heartbeat task that renews the lease in the given intervals.
     */
    private class HeartbeatThread implements Runnable {

        public void run() {
            if (renew()) {
                lastSuccessfulHeartbeatTimestamp = System.currentTimeMillis();
            }
        }
    }

继续查看renew()方法,可以看到续约方式是 通过合适的rest方式调用完成与eureka service的续约。
发送的信息也很简单,只需要三个参数:
instanceInfo.getAppName():name of the application registering with discovery.
instanceInfo.getId() :the unique id of the instance
instanceInfo:The class that holds information required for registration with Eureka Server and to be discovered by other components.

 /**
     * Renew with the eureka service by making the appropriate REST call
     */
    boolean renew() {
        EurekaHttpResponse<InstanceInfo> httpResponse;
        try {
            httpResponse = eurekaTransport.registrationClient.sendHeartBeat(instanceInfo.getAppName(), instanceInfo.getId(), instanceInfo, null);
            logger.debug(PREFIX + "{} - Heartbeat status: {}", appPathIdentifier, httpResponse.getStatusCode());
            if (httpResponse.getStatusCode() == 404) {
                REREGISTER_COUNTER.increment();
                logger.info(PREFIX + "{} - Re-registering apps/{}", appPathIdentifier, instanceInfo.getAppName());
                long timestamp = instanceInfo.setIsDirtyWithTime();
                boolean success = register();
                if (success) {
                    instanceInfo.unsetIsDirty(timestamp);
                }
                return success;
            }
            return httpResponse.getStatusCode() == 200;
        } catch (Throwable e) {
            logger.error(PREFIX + "{} - was unable to send heartbeat!", appPathIdentifier, e);
            return false;
        }
    }
  • 服务注册中心
    下面我们来看服务注册中心如何处理各类客户端的Rest请求的。Eureka Server对于各类REST请求的定义都位于com.netflix.eureka.resources包下,我们查看一下com.netflix.eureka.registry.AbstractInstanceRegistryregister()方法
/**
     * Registers a new instance with a given duration.
     *
     * @see com.netflix.eureka.lease.LeaseManager#register(java.lang.Object, int, boolean)
     */
    public void register(InstanceInfo registrant, int leaseDuration, boolean isReplication) {
            ...
            read.lock();
            Map<String, Lease<InstanceInfo>> gMap = registry.get(registrant.getAppName());
            REGISTER.increment(isReplication);
            if (gMap == null) {
                final ConcurrentHashMap<String, Lease<InstanceInfo>> gNewMap = new ConcurrentHashMap<String, Lease<InstanceInfo>>();
                gMap = registry.putIfAbsent(registrant.getAppName(), gNewMap);
                if (gMap == null) {
                    gMap = gNewMap;
                }
            }
           ......
     
    }

我们可以看到服务注册的信息被存储在了registry对象中,该对象是ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>>,注册中心存储了两层Map结构,第一层的key存储服务名:InstanceInfo中的appName属性,第二层的key存储实例名:InstanceInfo中的instanceId属性。

配置详解

在Eureka的服务治理体系中,主要分为客户端和服务端两个不同的角色,服务端为服务注册中心,而客户端为各个提供接口的微服务应用。

Eureka客户端的配置主要分为以下两个方面:

  1. 服务注册相关的配置信息,包括服务注册中心的地址、服务获取的时间间隔、可用区域等
  2. 服务实例相关的配置信息,包括服务实例的名称、IP地址、端口号、健康检查路径等。
    Eureka Server的配置均已eureka.server作为前缀。

服务注册类配置

服务注册类的配置信息,可用查看org.springframework.cloud.netflix.eureka.EurekaClientConfigBean。这些配置信息都已eureka.client为前缀。我们针对一些常用的配置信息做一些进一步的介绍和说明。

指定注册中心

eureka.client.service-url.defaultZone=http://localhost:8761/eureka/

当构建高可用的服务注册中心集群时,可以为value的值配置多个注册中心地址,以逗号分隔,如:eureka.client.service-url.defaultZone=http://localhost:8761/eureka/,eureka.client.service-url.defaultZone=http://localhost:8762/eureka/
为了服务注册中心的安全考虑,可以为注册中心加上安全校验,在配置serviceUrl的时候,value的值格式如下:http://<username>:<password>@localhost:8761/eureka/。其中,<username>为安全校验信息的用户名,<password>为该用户的密码。

其他配置

下面整理了org.springframework.cloud.netflix.eureka.EurekaClientConfigBean中定义的常用配置参数以及对应的说明和默认值,这些参数均已erureka.client为前缀,我们可以看下EurekaClientConfigBean的定义

public static final String PREFIX = "eureka.client";
参数名 说明 默认值
eureka.client.enabled 标志,指示启用了Eureka客户端。 true
eureka.client.encoder-name 这是一个临时配置,一旦最新的编解码器稳定下来,就可以删除(因为只有一个)  
eureka.client.register-with-eureka 此实例是否应向eureka服务器注册其信息以供其他人发现。在某些情况下,您不希望您的实例被发现,而您只是希望发现其他实例。 true
eureka.client.registry-fetch-interval-seconds 指示从eureka服务器获取注册表信息的频率(秒)。 30
eureka.client.instance-info-replication-interval-seconds 更新实例信息的变化到Eureka服务端的间隔时间,单位为秒 30
eureka.client.eureka-server-read-timeout-seconds 读取Eureka Server的超时时间,单位秒 8
eureka.client.heartbeat-executor-exponential-back-off-bound 心跳超时重试延迟时间的最大乘数值 10
eureka.client.heartbeat-executor-thread-pool-size 心跳连接池的初始化线程数 2
eureka.client.fetch-registry 客户端是否应从eureka服务器获取eureka注册表信息。 true

服务实例类配置

关于服务实例类的信息我们可以看org.springframework.cloud.netflix.eureka.EurekaInstanceConfigBean源码,这些信息以eureka.instance为前缀。针对常用配置做一些讲解

元数据

EurekaInstanceConfigBean的配置中,有一大部分内容都是针对服务实力元数据(用来描述自身服信息的对象,包括服务名称,实例名称,ip,端口等,以及一些用于负载均衡策略或是其他特殊用途的自定义元数据信息)的配置。我们可以通过eureka.instance.<properties>=<value>的格式对标准化的元数据直接进行配置。自定义元数据:eureka.instance.metaMap.<key>=<value>。如eureka.instance.metaMap.zone=shanghai

实例名配置

InstanceInfo中的instanceId属性。区分同一服务中不同实例的唯一标识。在Spring Cloud Eureka的配置中,针对同一主机上启动多个实例的情况,对实例名的默认命名组了更为合理的扩展,它采用了如下规则:

${spring.cloud.client.hostname}:${spring.application.name}:${spring.application.instance_id:${server.port}}

对于实例名的命名规则,我们可以通过eureka.instance.instanceId参数配置
如:eureka.instance.instanceId=${spring.application.name}:${random.int},这样子可以解决同一主机上,不指定端口就能轻松启动多个实例的效果。

端点配置

为了服务的正常运行,我们必须确保Eureka客户端的/health端点在发送元数据的时候,是一个能被注册中心访问的地址,否则服务注册中心不会根据应用的健康检查来更改状态(仅当开启healthcheck功能时,以该端点信息作为健康检查标准)有时候为了安全考虑,也有可能修改/info和/heath端点的原始路径。这个时候,我们需要做一些特殊的配置

#endpoints.info.path=/appInfo弃用
#endpoints.health.path==/checkHealth 弃用,可以使用如下配置
management.endpoints.web.path-mapping.info=/appInfo
management.endpoints.web.path-mapping.health=/checkHealth

eureka.instance.status-page-url-path=${management.endpoints.web.path-mapping.info}
eureka.instance.health-check-url-path=${management.endpoints.web.path-mapping.health}

当客户端服务以https方式暴露服务和监控端点时,相对路径的配置方式就无法满足需求了。所以Spring Cloud Eureka还提供了绝对路径的配置参数,具体示例如下:

eureka.instance.home-page-url=https://${eureka.instance.hostname}/home
eureka.instance.health-check-url=https://${eureka.instance.hostname}/health
eureka.instance.status-page-url=https://${eureka.instance.hostname}/info

健康检查

        默认情况下, Eureka中各个服务实例的健康检测并不是通过 spring-boot- actuator模块的/ health端点来实现的,而是依靠客户端心跳的方式来保持服务实例的存活。在Eureka的服务续约与剔除机制下,客户端的健康状态从注册到注册中心开始都会处于UP状态,除非心跳终止一段时间之后,服务注册中心将其剔除。默认的心跳实现方式可以有效检查客户端进程是否正常运作,但却无法保证客户端应用能够正常提供服务。由于大多数微服务应用都会有一些其他的外部资源依赖,比如数据库、缓存、消息代理等,如果我的应用与这些外部资源无法联通的时候,实际上已经不能提供正常的对外服务了,但是因为客户端心跳依然在运行,所以它还是会被服务消费者调用,而这样的调用实际上并不能获得预期的结果。
        在 Spring Cloud Eureka中,我们可以通过简单的配置,把 Eureka客户端的健康检测交给 spring-boot- actuator模块的/ health端点,以实现更加全面的健康状态维护。详细的配置步骤如下所示:

  • 在pom,xm1中引入 spring-boot- starter- actuator模块的依赖。
  • 在 application, properties中增加参数配置 eureka.client.healthcheck.enabled=true。
  • 如果客户端的/ health端点路径做了一些特殊处理,请参考前文介绍端点配置时的方法进行配置,让服务注册中心可以正确访问到健康检测端点。

AWS的区域和可用区概念解释

eureka官网

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,667评论 5 472
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,361评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,700评论 0 333
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,027评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,988评论 5 361
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,230评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,705评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,366评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,496评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,405评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,453评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,126评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,725评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,803评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,015评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,514评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,111评论 2 341

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,566评论 18 139
  • Eureka是什么,SpringCloud用Eureka来干什么等等相关概念就不细说了,网上有大把大把的解释,甚至...
    liuyiyou阅读 1,117评论 0 50
  • 1、服务治理三个核心点、服务提供者、服务消费者、服务注册中心 结合Ribbon 服务治理图 服务治理时序图 服务提...
    高山之水阅读 353评论 0 0
  • 并不是每个女人天生都会化妆,化妆时有很多禁忌,不少都是日常生活中你不经意的化妆习惯,别小看这些小习惯,也许会毁你容...
    美体美颜美好生活阅读 169评论 0 0
  • 成熟是种心境,豁达乐观。 不去抱怨,不去怪罪,不怨天尤人,不消沉感怀。 明得今之境,皆缘我命,即以注定,便又为何玻...
    若小言阅读 507评论 5 11