Eureka——Someone might say “eureka” when they suddenly find or realize something, or when they solve a problem.
需要准备三个SpringBoot服务:eureka-server服务注册中心,hello-service服务提供者,service-consumer服务消费者。
pom.xml:
<project >
<modelVersion>4.0.0modelVersion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.1.6.RELEASEversion>
<relativePath/>
parent>
<groupId>com.springcloudactiongroupId>
<artifactId>eureka-serverartifactId>
<properties>
<java.version>1.8java.version>
<spring-cloud.version>Greenwich.SR2spring-cloud.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-serverartifactId>
dependency>
dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>${spring-cloud.version}version>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
project>
EurekaServerApplication:
@EnableEurekaServer
@SpringBootApplication
public class EurekaServerApplication {
psvm(String[] args){ SpringApplication.run(EurekaServerApplication.class, args); }
}
application.properties:
spring.application.name=eureka-server
eureka.client.fetch-registery=false
application-peer1.properties:
server.port=1111
eureka.client.service-url.defaultZone=http://127.0.0.1:1112/eureka
application-peer2.properties:
server.port=1112
eureka.client.service-url.defaultZone=http://127.0.0.1:1111/eureka
pom.xml:
<project >
<modelVersion>4.0.0modelVersion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.1.6.RELEASEversion>
<relativePath/>
parent>
<groupId>com.springcloudactiongroupId>
<artifactId>hello-serviceartifactId>
<version>0.0.1-SNAPSHOTversion>
<properties>
<java.version>1.8java.version>
<spring-cloud.version>Greenwich.SR2spring-cloud.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>${spring-cloud.version}version>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
project>
HelloServiceApplication.java:
@SpringBootApplication
@EnableDiscoveryClient
// 如果不加@EnableDiscoveryClient注解,该服务仍能在注册中心注册成功,管理页面的Instances currently registered with Eureka中仍有该服务,但其他消费者调用该服务会失败
// 不加@EnableDiscoveryClient注解,client.getInstances("hello-service")得到的为空结果
public class HelloServiceApplication {
psvm(String[] args) { SpringApplication.run(HelloServiceApplication.class, args); }
}
HelloController.java:
@RestController
@Slf4j
public class HelloController {
@Autowired
private DiscoveryClient client;
@RequestMapping("/hello")
public String hello(){
List<String> services = client.getServices();
List<ServiceInstance> serviceInstances = client.getInstances("hello-service");
String description = client.description();
return "HelloWorld!";
}
}
application.properties
server.port=8080
spring.application.name=hello-service
eureka.client.service-url.defaultZone=http://127.0.0.1:1111/eureka,http://127.0.0.1:1112/eureka
application-peer1.properties
server.port=8081
pom.xml
<project >
<modelVersion>4.0.0modelVersion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.1.6.RELEASEversion>
<relativePath/>
parent>
<groupId>com.springcloudactiongroupId>
<artifactId>service-consumerartifactId>
<version>0.0.1-SNAPSHOTversion>
<properties>
<java.version>1.8java.version>
<spring-cloud.version>Greenwich.SR2spring-cloud.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>${spring-cloud.version}version>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
project>
ServiceConsumerApplication.java
@SpringBootApplication
// @EnableDiscoveryClient
// 只在pom中添加了spring-cloud-netflix-eureka-client的依赖即可向注册中心注册,消费其他服务
// 如果想要作为服务提供方,需要加上@EnableDiscoveryClient注解
public class ServiceConsumerApplication {
psvm(String[] args) { SpringApplication.run(ServiceConsumerApplication.class, args); }
@Bean
@LoadBalanced
// 如果不加@LoadBalanced注解,将会报unknownhost异常,无法识别hello-service
RestTemplate restTemplate() { return new RestTemplate(); }
}
ConsumerController.java
@RestController
public class ConsumerController {
@Autowired
private RestTemplate restTemplate;
@RequestMapping("/consumer")
public String helloConsumer() {
return restTemplate.getForEntity("http://hello-service/hello", String.class).getBody();
}
}
application.properties
server.port=9000
spring.application.name=service-consumer
eureka.client.service-url.defaultZone=http://127.0.0.1:1111/eureka
eureka-server
,执行命令java -jar eureka-server.jar
--server.port=1111或1112
,分别部署到端口1111和1112上,也可以添加参数--spring.profiles.active=peer1或peer2
指定不同的配置文件,来指定不同的端口。hello-service
service-consumer
Instances currently registered with Eureka
下出现了三个服务:
eureka-server
,因为没有加eureka.client.register-with-eureka=false
,默认为true,所以服务中心也会相互注册,实现高可用hello-service
,在配置文件里加了eureka.client.service-url.defaultZone=http://127.0.0.1:1111/eureka,http:127.0.0.1:1112/eureka
,hello-service
被注册到了两个注册中心service-consumer
,配置文件中只有eureka.client.service-url.defaultZone=http://127.0.0.1:1111/eureka
,除1111端口之外,1112端口也有service-consumer
服务,即服务同步Eureka的服务治理体系中有三个主要角色:服务注册中心,服务提供者,服务消费者。
服务注册中心之间:服务同步
服务提供者与服务注册中心:服务注册、服务续约、服务下线
服务消费者与服务注册中心:获取服务清单
服务消费者与服务提供者:服务调用
服务需要添加@EnableDiscoveryClient
注解,标明是一个eureka的客户端,代码如下:
package org.springframework.cloud.client.discovery;
/** Annotation to enable a DiscoveryClient implementation. */
public @interface EnableDiscoveryClient {
boolean autoRegister() default true;
}
注释里说该注解用来开启DiscoveryClient
的实例,那么org.springframework.cloud.client.discovery.DiscoveryClient
接口代码如下:
package org.springframework.cloud.client.discovery;
/** Represents read operations commonly available to discovery services such as Netflix Eureka or consul.io. */
public interface DiscoveryClient extends Ordered {
// A human-readable desceription of the implementation, used in HealthIndicator.
String description();
// Gets all ServiceInstances associated with a particular serviceId.
List<ServiceInstance> getInstances(String serviceId);
// return all known service IDs.
List<String> getServices();
// Default implementation for getting order of discovery clients.
@Override
default int getOrder() {return DEFAULT_ORDER; }
int DEFAULT_ORDER = 0;
}
DiscoveryClient
接口有四个实现类:EurekaDiscoveryClient
,CompositeDiscoveryClient
,NoopDiscoveryClient
,SimpleDiscoveryClient
package org.springframework.cloud.client.discovery.composite;
/** A DiscoveryClient that is composed of other discovery clients and delegates calls to each of them in order. */
public class CompositeDiscoveryClient implements DiscoveryClient {
private final List<DiscoveryClient> dcs;
String description() { return "Composite Discovery Client"; }
List<ServiceInstance> getInstances(String serviceId) {
for (DiscoveryClient dc: this.dcs) {
List<ServiceInstances> instances = dc.getInstances(serviceId);
if (instances != null && !instances.isEmpty()) { return instances; }
}
return Collections.emptyList();
}
List<String> getServices() {
Set<String> services = new LinkedHashSet<>();
for (DiscoveryClient dc: this.dcs) {
List<String> s = dc.getServices();
if (s != null) { services.addAll(s); }
}
return new ArrayList<>(services);
}
}
package org.springframework.cloud.netflix.eureka;
public class EurekaDiscoveryClient implements DiscoveryClient {
psfs DESCRIPTION = "Spring Cloud Eureka Discovery Client";
final EurekaClient eurekaClient;
final EurekaClientConfig clientConfig;
List<ServiceInstance> getInstances(String serviceId) {
List<InstanceInfo> infos = this.eurekaClient.getInstancesByVipAddress(serviceId, false);
List<ServiceInstance> instances = new ArrayList<>();
for (InstanceInfo info : infos) { instances.add(new EurekaServiceInstance(info)); }
return instances;
}
List<String> getServices() {
Applications applications = this.eurekaClient.getApplications();
List<Application> registered = applications.getRegisteredApplications();
List<String> names = new ArrayList<>();
for (Application app : registered) {
if (app.getInstances().isEmpty()) { continue; }
names.add(app.getName().toLowerCase());
}
return names;
}
}
DiscoveryClient
的getInstances(serviceId)
返回对应serviceId
的ServiceInstance列表,ServiceInstance
接口代码如下:
package org.springframework.cloud.client;
/** Represents an instance of a service in a discovery system. */
public interface ServiceInstance {
// 获取instanceId,serviceId,host,port,是否https,uri,元数据metadata等
}
EurekaDiscoveryClient
持有一个EurekaClient
的实例,EurekaClient
位于com.netflix.discovery
包下,不是springframework.cloud
的一部分,代码如下:
package com.netflix.discovery;
@ImplementedBy(DiscoveryClient.class) // 这个DiscoveryClient是com.netflix.discovery下的,不是SpringCloud的
public interface EurekaClient extends LookupService {
// EurekaClient定义了几类方法
// 一、用于获取InstanceInfo,以getApplications开头或getInstances开头
// 二、用于获取元数据,如获取region,实例的status,serviceUrls
// 三、与健康检查相关,注册HealthCheckHandler、EurekaEventListener等
// 四、其他方法,shutdown,获取EurekaClientConfig
// 第二类中获取serviceUrl的几个方法标记了@Deprecated,替代类是com.netflix.discovery.endpoint.EndpointUtils
}
EurekaClient
有一个实现类com.netflix.discovery.DiscoveryCilent
,该实现类的类注释:
/**
* The class that is instrumental for interactions with Eureka Server.
* Eureka Client is responsible for
* a) Registering the instance with Eureka Server
* b) Renewal of the lease with Eureka Server
* c) Cancellation of the lease from Eureka Server during shutdown
* d) Querying the list of services/instances registered with Eureka Server
*
* Eureka Client needs a configured list of Eureka Server java.net.URLs to talk to.These java.net.URLs are
* typically amazon elastic eips which do not change. All of the functions defined above fail-over to other
* java.net.URLs specified in the list in the case of failure.
*
*/
即EurekaClient
负责1.向EurekaServer
注册,2.向EurekaServer
续约,3.服务关闭期间取消租约,4.查询注册到EurekaServer
的服务/实例列表。最后,EurekaClient
需要配置EurekaServer
的url列表。
我们的serviceUrl是在properties配置文件中配置的,据此找到EndpointUtils
里的getServiceUrlsMapFromConfig
方法:
/** 从properties文件获取eureka service urls列表,用于eurekaclient talk to
*/
public static List<String> getServiceUrlsFromConfig(EurekaClientConfig clientConfig, String instanceZone, boolean preferSameZone) {
String region = getRegion(clientConfig); // 获取clientConfig.getRegion(),默认为DEFAULT_REGION="default"
String[] availZones = clientConfig.getAvailabilityZones(clientConfig.getRegion()); // 从EurekaClientConfigBean里的availabilityZones这个map里获取region对应的zone,用逗号分隔,默认为defaultZone
int myZoneOffset = getZoneOffset(instanceZone, preferSameZone, availZones); // 获取instanceZone在availZones中的索引
String zone = availZones[myZoneOffset]; // 由于preferSameZone选项,instanceZone不一定等于availZones[myZoneOffset]
List<String> serviceUrls = clientConfig.getEurekaServerServiceUrls(zone); // 从zone中获取serviceUrl列表
List<String> orderedUrls;
orderedUrls.addAll(serviceUrls);
return orderedUrls;
}
getServiceUrlsFromConfig
做了三件事:加载region,加载zone,加载serviceUrl。
EurekaClientConfig
有两个实现类:Netflix的DefaultEurekaClientConfig
和Spring的EurekaClientConfigBean
。
查看EurekaClientConfigBean
,Zone来自于Map:availabilityZones根据Region得到的数组,即一个region对应一个zone[],serviceUrls来自于Map:serviceUrl根据选择的Zone得到的url数组。serviceUrl默认存储了defaultZone和defaultUrl。
psf String DEFAULT_ZONE = "defaultZone";
psf String DEFAULT_URL = "http://localhost:8761" + DEFAULT_PREFIX + "/";
psf String DEFAULT_PREFIX = "/eureka";
{
this.serviceUrl.put(DEFAULT_ZONE, DEFAULT_URL);
}
@Override
public String[] getAvailabilityZones(String region) {
String value = this.availabilityZones.get(region);
value = value == null ? DEFAULT_ZONE : value;
return value.split(",");
}
@Override
public List<String> getEurekaServerServiceUrls(String myZone) {
String serviceUrls = this.serviceUrl.get(myZone);
serviceUrls = serviceUrls == null ? this.serviceUrl.get(DEFAULT_ZONE) : serviceUrls;
String[] serviceUrlsSplit = StringUtils.commaDelimitedListToStringArray(serviceUrls);
return Arrays.asList(serviceUrlsSplit);
}
在EurekaClient
的实现类com.netflix.discovery.DiscoveryClient
中,有两个方法:register() 和 renew():
/** Register with eureka service by making the appropriate REST call. */
boolean register() throws Throwable {
EurekaHttpResponse<Void> httpResponse;
try {
httpResponse = eurekaTransport.registrationClient.register(instanceInfo);
} catch (Exp){}
return httpResponse.getStatusCode() == Status.NO_CONTENT; // NO_CONTENT=204
}
/** Renew with eureka service by making the appropriate REST call. */
boolean renew() {
EurekaHttpResponse<InstanceInfo> httpResponse;
try {
httpResponse = eurekaTransport.registrationClient.sendHeartBeat(instanceInfo.getAppName(), instanceInfo.getId(), instanceInfo, null);
if (httpResponse.getStatusCode == Status.NOT_FOUND) { // NOT_FOUND=404
REREGISTER_COUNTER.increment();
long timestamp = instanceInfo.setIsDirtyWithTime();
boolean success = register();
if (success) { instanceInfo.unsetIsDirty(timestamp); }
return success;
}
return httpResponse.getStatusCode() == Status.OK; // OK=200
} catch (Thr) { return false; }
}
register()
方法中的httpResponse = eurekaTransport.registrationClient.register(instanceInfo);
负责实施注册行为。
renew()
方法中的httpResponse = eurekaTransport.registrationClient.sendHeartBeat(appName, id, instanceInfo, null)
,负责实施续约行为。
registrationClient是EurekaHttpClient
的实例,EurekaHttpClient的继承结构如下:
EurekaHttpClient
EurekeHttpClientDecorator
MetricsCollectiongEurekaHttpClient
SessionedEurekaHttpClient
RedirectingEurekaHttpClient
RetryableEurekaHttpClient
AbstractJerseyEurekaHttpClient
JerseyApplicationClient
只有AbstractJerseyEurekaHttpClient
真正实现了EurekaHttpClient
中定义的register(InstanceInfo)
、sendHeartBeat(appName, id, info, overriddenStatus)
方法,其他类的register()
、sendHeartBeat()
方法采用了委托的方式。由AbstractJerseyEurekaHttpClient
的实现可知,register
操作通过发送post请求的方式,向serviceUrl发送自身的InstanceInfo数据,sendHeartBeat
通过发送put请求的方式,向serviceUrl发送心跳包。
/** AbstractJerseyEurekaHttpClient、JerseyApplicationClient
*/
@Override
public EurekaHttpResponse<Void> register(InstanceInfo info) {
String urlPath = "apps/" + info.getAppName(); // urlPath为apps/SERVICE-CONSUMER
ClientResponse response = null;
try {
Builder resourceBuilder = jerseyClient.resource(serviceUrl).path(urlPath).getRequestBuilder();
// serviceUrl= http://127.0.0.1/eureka/ 即在eureka.client.service-url.defaultZone里配置的serviceUrl地址
addExtraHeaders(resourceBuilder);
response = resourceBuilder.header(xx).type(xx).accept(x)
.post(ClientResponse.class, info);
return anEurekaHttpResponse(response.getStatus()).headers(headersOf(response)).build();
} finally {
if (response != null) { response.close(); }
}
}
@Override
public EurekaHttpResponse<InstanceInfo> sendHeartBeat(String appName, String id, InstanceInfo info, InstanceStatus overriddenStatus) {
String urlPath = "apps/" + appName + "/" + id; // appName为SERVICE-CONSUMER, id为172.11.22.100:service-consumer:9000
ClientResponse response = null;
try {
// serviceUrl为http://127.0.0.1/eureka/
WebResource webResource = jerseyClient.resource(serviceUrl).path(urlPath)
.queryParam("status", info.getStatus().toString())
.queryParam("lastDirtyTimestamp", info.getLastDirtyTimestamp().toString());
Builder requestBuilder = webResource.getRequestBuilder();
addExtraHeaders(requestBuilder);
response = requestBuilder.put(ClientResponse.class);
EurekaHttpResponseBuilder<InstanceInfo> eurekaResponseBuilder = anEurekaHttpResponse(response.getStatus(), InstanceInfo.class).headers(headersOf(response));
if (response.hasEntity()) {
eurekaResponseBuilder.entity(response.getEntity(InstanceInfo.class));
}
return eurekaResponseBuilder.build();
} finally {
if (response != null) { response.close(); }
}
}
查找调用DiscoveryClient
的register()
方法的地方,有三处:
DiscoveryClient
的构造函数里,if (clientConfig.shouldRegisterWithEureka() && clientConfig.shouldEnforceRegistrationAtInit()) { register(); }
,即初始化时注册renew()
里404时的注册InstanceInfoReplicator
的run()
方法里的注册,该类实现了Runnable
接口。// com.netflix.discovery.InstanceInfoReplicator
public void run() {
try {
discoveryClient.refreshInstanceInfo();
Long dirtyTimestamp = instanceInfo.isDirtyWithTime();
if (dirtyTimestamp != null) {
discovery.register();
instance.unsetIsDirty(dirtyTimestamp);
}
}
}
public void start(int initialDelayMs) {
if (started.compareAndSet(false, true)) {
instanceInfo.setIsDirty();
Future next = scheduler.schedule(this, initialDelayMs, SECONDS);
scheduledPeriodicRef.set(next);
}
}
DiscoveryClient
持有InstanceInfoReplicator
的实例,但是没有通过线程池去调度它,而是在initScheduledTasks()
方法里调用InstanceInfoReplicator
的start()
方法去调度,InstanceInfoReplicator
持有一个ScheduledExecutorService
实例,调度自身scheduler.schedule(this, initialDelayMs, SECONDS)
。
initScheduledTasks()
在DiscoveryClient
的构造函数中被调用,查看DiscoveryClient
的initScheduledTasks()
:
private void initScheduledTasks() {
if (clientConfig.shouldFetchRegistry()) {
// 服务获取
scheduler.schedule(new TimedSupervisorTask(..., new CacheRefreshThread()), registryFetchIntervalSeconds, TimeUnit.SECONDS);
}
if (clientConfig.shouldRegisterWithEureka()) {
// 服务续约
scheduler.schedule(new TimedSupervisorTask(..., new HeartbeatThread()), renewalIntervalInSecs, TimeUnit.SECONDS);
// 服务注册
instanceInfoReplicator.start(clientConfig.getInitialInstanceInfoReplicationIntervalSeconds());
}
}
// 定时服务续约任务
private class HeartbeatThread implements Runnable {
public void run() { if (renew()) { lastSuccessfulHeartbeatTimestamp = System.currentTimeMillis(); }}
}
// 定时服务获取任务
class CacheRefreshThread implements Runnable {
public void run() { refreshRegistry(); }
}
可以看到,DiscoveryClient
在构造函数中,根据配置项register-with-eureka,判断是否需要注册。
续约的处理流程与注册类似。
服务注册的请求在com.netflix.eureka.resources.ApplicationResource
中进行处理,
@POST
@Consume({"application/json", "application/xml"})
public Response addInstance(InstanceInfo info, @HeaderParam(PeerEurekaNode.HEADER_REPLICATION) String isReplication) {
// 校验instanceInfo参数
// 处理info.getDataCenterInfo()的DataCenterInfo的一些特殊情况
// 服务注册
registry.register(info, "true".equals(isReplication)); // registry:PeerAwareInstanceRegistry
return Response.status(204).build();
}
会调用InstanceRegistry
的register()
方法进行注册
// org.springframework.cloud.netflix.eureka.server.InstanceRegistry
@Override
public void register(final InstanceInfo info, final boolean isReplication) {
handleRegistration(info, resolveInstanceLeaseDuration(info), isReplication);
super.register(info, isReplication);
}
private void handleRegistration(InstanceInfo info, int leaseDuration, boolean isReplication) {
publishEvent(new EurekaInstanceRegisteredEvent(this, info, leaseDuration, isReplication));
}
private void publishEvent(ApplicationEvent applicationEvent) {
this.ctxt.publishEvent(applicationEvent); // ctxt:AbstractApplicationContext,调用其publishEvent方法,将服务注册事件广播
}
InstanceRegistry
的继承关系:
InstanceRegistry // com.netflix.eureka.registry
PeerAwareInstanceRegistry // com.netflix.eureka.registry
PeerAwareInstanceRegistryImpl // com.netflix.eureka.registry
InstanceRegistry // org.springframework.cloud.netflix.eureka.server
AwsInstanceRegistry // com.netflix.eureka.registry
AbstractInstanceRegistry // com.netflix.eureka.registry
PeerAwareInstanceRegistryImpl
InstanceRegistry
AwsInstanceRegistry
InstanceRegistry
的super.register()
最终调用到AbstractInstanceRegistry
的register()
方法。
在AbstractInstanceRegistry
中有一个ConcurrentHashMap
:private final ConcurrentHashMap
,可以看到这是一个双层Map,调用该Map的put方法的地方只有一处,即register()
方法:
public void register(InstanceInfo registrant, int leaseDuration, boolean isReplication) {
try {
read.lock();
Map<String, Lease<InstanceInfo>> gMap = registry.get(registrant.getAppName()); // gMap是根据appName从双层map——registry获取的内层map,即外层map的key是服务名
REGISTER.increment(isReplication);
if (gMap == null) { // 如果没有appName对应的Map,则为其关联一个new Map
final ConcurrentHashMap<String, Lease<InstanceInfo>> gNewMap = new ConcurrentHashMap<>();
gMap = registry.putIfAbsent(registrant.getAppName(), gNewMap);
if (gMap == null) { gMap = gNewMap; }
}
Lease<InstanceInfo> existingLease = gMap.get(registrant.getId()); // 根据id从内层map获取Lease,即内层map的key是id
// Lease翻译为租约,里面有几个时间戳和一个泛型对象holder,一个duration(默认90秒)
if (existingLease != null && (existingLease.getHolder() != null)) { // getHolder()返回id对应InstanceInfo对象
Long existingLastDirtyTimestamp = existingLease.getHolder().getLastDirtyTimestamp();
Long registrationLastDirtyTimestamp = registrant.getLastDirtyTimestamp();
// 比较已有租约的时间戳和传入的时间戳
if (existingLastDirtyTimestamp > registrationLastDirtyTimestamp) {
registrant = existingLease.getHolder(); // 已有的租约的时间戳大于传入的,则仍然使用已有的
}
} else {
// 不存在已有租约,是一个新注册
synchronized (lock) {
if (this.expectedNumberOfClientsSendingRenews > 0) {
this.expectedNumberOfClientsSendingRenews = this.expectedNumberOfClientsSendingRenews + 1;
updateRenewsPerMinThreshold();
}
}
}
Lease<InstanceInfo> lease = new Lease<>(registrant, leaseDuration);
if (existingLease != null) {
// serviceUpTimestamp仍沿用已有租约的时间戳
lease.setServiceUpTimestamp(existingLease.getServiceUpTimestamp());
}
gMap.put(registrant.getId(), lease); // 内层map使用id作为key,新的租约作为value
synchronized (recentRegisteredQueue) {
recentRegisteredQueue.add(new Pair<Long, String>(System.currentTimeMillis(), registrant.getAppName() + "(" + registrant.getId() + ")"));
}
// 以下是对overridden status的处理,如果overriddenInstanceStatusMap的key中不存在传入id,则保存传入的id和overriddenStatus,用已有的overriddenStatus更新传入的registrant。相当于如果有已有值,则用已有值更新传入的对象,类似的处理方式很多。
if (!InstanceStatus.UNKNOWN.equals(registrant.getOverriddenStatus())) {
if (!overriddenInstanceStatusMap.containsKey(registrant.getId())) {
overriddenInstanceStatusMap.put(registrant.getId(), registrant.getOverriddenStatus());
}
}
InstanceStatus overriddenStatusFromMap = overriddenInstanceStatusMap.get(registrant.getId());
if (overriddenStatusFromMap != null) { registrant.setOverriddenStatus(overriddenStatusFromMap); }
InstanceStatus overriddenInstanceStatus = getOverriddenInstanceStatus(registrant, existingLease, isReplication);
registrant.setStatusWithoutDirty(overriddenInstanceStatus);
if (InstanceStatus.UP.equals(registrant.getStatus())) {
// 传入的状态是UP,则更新serviceUpTimestamp,结合前处可知,如果是其他状态,则沿用已有租约的serviceUpTimestamp
lease.serviceUp(); // serviceUp()里更新了Lease的serviceUpTimestamp
}
registrant.setActionType(ActionType.ADDED);
recentlyChangeQueue.add(new RecentlyChangedItem(lease));
registrant.setLastUpdatedTimestamp();
invalidateCache(registrant.getAppName(), registrant.getVIPAddress(), registrant.getSecureVipAddress());
} finally {
read.unlock();
}
}
在这个实验中,registry
存储的数据为:
{
"EUREKA-SERVER":{
"172.22.33.44:eureka-server:1111":Lease<InstanceInfo1>,
"172.22.33.44:eureka-server:1112":Lease<InstanceInfo2>
},
"SERVICE-CONSUMER":{
"172.22.33.44:service-consumer:9000":Lease<InstanceInfo3>
}
}
在eureka-client
和eureka-core
中,可以找到几个与配置相关的接口:
在搭建了高可用的注册中心之后,注册中心、服务提供者、消费者、配置中心、网关等服务都可以看做是Eureka服务治理体系中的一个微服务,所以重点关注Eureka客户端的配置,即EurekaClientConfig。
查看实现类EurekaClientConfigBean,配置项的前缀PREFIX="eureka.client"
将服务注册,需要指定注册中心地址:
# 单节点注册中心
eureka.client.service-url.defaultZone=http://localhost:1111/eureka/
# 双节点高可用注册中心
eureka.client.service-url.defaultZone=http://localhost:1111/eureka/,http://localhost:1112/eureka/
# 带安全校验的注册中心,加入用户名/密码
eureka.client.service-url.defaultZone=http://<username>:<password>@localhost:1111/eureka/
其他参数:
参数名 | 默认值 | 说明 |
---|---|---|
enabled | true | 是否启用eureka客户端 |
registry-fetch-interval-seconds | 30 | 获取注册信息的间隔时间 |
instance-info-replication-interval-seconds | 30 | 反馈实例信息到eureka-server的间隔时间 |
initial-instance-info-replication-interval-seconds | 40 | 初始化实例信息到eureka-server的间隔时间 |
eureka-service-url-poll-interval-seconds | 300 | 轮询eureka-server更改的间隔时间 |
eureka-server-read-timeout-seconds | 8 | 读取eureka-server的超时时间 |
eureka-server-connect-timeout-seconds | 5 | 连接eureka-server的超时时间 |
eureka-server-total-connections | 200 | 从eureka-client到所有eureka-server的最大连接数 |
eureka-server-total-connections-per-host | 50 | 从eureka-client到一个eureka-server的最大连接数 |
eureka-connection-idle-timeout-seconds | 30 | eureka-server连接保持空闲的时间,直到关闭 |
heartbeat-executor-thread-pool-size | 2 | 心跳线程池的初始化大小 |
heartbeat-executor-exponential-back-off-bound | 10 | 心跳超时重试时,延迟时间的最大乘数值(multiplier) |
cache-refresh-executor-thread-pool-size | 2 | 缓存刷新线程池的初始化大小 |
cache-refresh-executor-exponential-back-off-bound | 10 | 缓存刷新重试时,延迟时间的最大乘数值(multiplier) |
use-dns-for-fetching-service-urls | false | eureka-client是否使用dns来获取eureka-server列表 |
register-with-eureka | true | 是否注册到eureka-server,从而被其他服务发现 |
prefer-same-zone-eureka | true | 是否优先使用相同zone中的eureka-server |
filter-only-up-instances | true | 只保留UP状态的实例 |
fetch-registry | true | 是否从eureka-server获取注册信息 |
backup-registry-impl
proxy-host
proxy-port
proxy-user-name
proxy-password
g-zip-content
eureka-server-url-context
eureka-server-port
eureka-server-dns-name
unregister-on-shutdown
allow-redirects
log-delta-diff
disable-delta
fetch-registry-for-remote-regions
region
fetch-remote-regions-registry
availability-zones
eureka-server-service-urls
registry-refresh-single-vip-address
dollar-replacement
escape-char-replacement
on-demand-update-status-change
enforce-registration-at-init
encoder-name
decoder-name
client-data-accept
experimental
transport-config
查看EurekaInstanceConfig的实现类EurekaInstanceConfigBean
,配置项的前缀eureka.server
参数列表:
instance-id
appname
app-group-name
instance-enable-onit
non-secure-port
secure-port
non-secure-port-enabled
secure-port-enabled
lease-renewal-interval-in-seconds
lease-expiration-duration-in-seconds
virtual-host-name
secure-virtual-host-name
a-s-g-name
host-name
metadata-map
data-center-info
ip-address
status-page-url-path
status-page-url
home-page-url-path
home-page-url
health-check-url-path
health-check-url
secure-health-check-url
default-address-resolution-order
namespace