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.AbstractInstanceRegistry
的register()
方法
/**
* 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客户端的配置主要分为以下两个方面:
- 服务注册相关的配置信息,包括服务注册中心的地址、服务获取的时间间隔、可用区域等
- 服务实例相关的配置信息,包括服务实例的名称、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端点路径做了一些特殊处理,请参考前文介绍端点配置时的方法进行配置,让服务注册中心可以正确访问到健康检测端点。
网友评论