SpringCloud之Eureka的常见问题及配置优化
1.EurekaServer集群中节点均出现在unavailable-replicas下
1.问题描述
我在启动了3个EurekaServer服务后,在dashboard的General Info中发现,集群中的节点均出现在unavailable-replicas下,没有出现在available-replicas中,这样虽然我的集群是可用的,但是总感觉不踏实,最后通过源码发现,是自己的配置不对。
2.解决方式
那么需要怎么解决呢?通过多次试验,主要是以下几个配置:
1.
eureka:
client:
fetch-registry: true
register-with-eureka: true
eureka.client. service-url.defaultZone中的url要用hostname,要使用域名,DNS解析请自行配置,不能使用localhost或者ip,并且这个hostname要与eureka.instance.hostname的值是一样的;
spring.application.name的值要一样
基本上就是以上两三点。
3.原因
我访问的dashboard的页面主要是通过EurekaController下的这status的get请求得到的,如下:
@RequestMapping(
method = {RequestMethod.GET}
)
public String status(HttpServletRequest request, Map<String, Object> model) {
this.populateBase(request, model);
this.populateApps(model);
StatusInfo statusInfo;
try {
//获取集群中peer节点的状态信息
statusInfo = (new StatusResource()).getStatusInfo();
} catch (Exception var5) {
statusInfo = Builder.newBuilder().isHealthy(false).build();
}
model.put("statusInfo", statusInfo);
this.populateInstanceInfo(model, statusInfo);
this.filterReplicas(model, statusInfo);
return "eureka/status";
}
下面我就看下getStatusInfo()这个方法:
@GET
public StatusInfo getStatusInfo() {
return this.statusUtil.getStatusInfo();
}
再往下跟,我们看下statusUtil.getStatusInfo()这个方法:
public StatusInfo getStatusInfo() {
Builder builder = Builder.newBuilder();
int upReplicasCount = 0;
StringBuilder upReplicas = new StringBuilder();
StringBuilder downReplicas = new StringBuilder();
StringBuilder replicaHostNames = new StringBuilder();
Iterator var6 = this.peerEurekaNodes.getPeerEurekaNodes().iterator();
while(var6.hasNext()) {
PeerEurekaNode node = (PeerEurekaNode)var6.next();
if (replicaHostNames.length() > 0) {
replicaHostNames.append(", ");
}
replicaHostNames.append(node.getServiceUrl());
//判断是否能够加入到available-replicas中
if (this.isReplicaAvailable(node.getServiceUrl())) {
upReplicas.append(node.getServiceUrl()).append(',');
++upReplicasCount;
} else {
downReplicas.append(node.getServiceUrl()).append(',');
}
}
builder.add("registered-replicas", replicaHostNames.toString());
builder.add("available-replicas", upReplicas.toString());
builder.add("unavailable-replicas", downReplicas.toString());
if (this.peerEurekaNodes.getMinNumberOfAvailablePeers() > -1) {
builder.isHealthy(upReplicasCount >= this.peerEurekaNodes.getMinNumberOfAvailablePeers());
}
builder.withInstanceInfo(this.instanceInfo);
return builder.build();
}
下面我们再往下跟,主要看下isReplicaAvailable(node.getServiceUrl())这个判断的方法:
private boolean isReplicaAvailable(String url) {
try {
//从其他节点中获取当前节点的注册信息,即当前的服务端要注册到其他节点,如果没注册,这里的app就会返回null
//而控制能否注册的参数就是eureka.client.register-with-eureka
//然后就是还要能够拉取,如果不能及时拉取,就只能等其它节点向我同步了,会产生延迟或者数据不同步(一致)
//除此之外,还要注意一点,就是registry.getApplication(this.myAppName, false);这个方法中myAppName,这就
//说集群中的每个server的名字要一样,即spring.application.name的值要一样
if (app == null) {
Application app = this.registry.getApplication(this.myAppName, false);
if (app == null) {
return false;
}
Iterator var3 = app.getInstances().iterator();
//循环判断peerEurekaNodes中的hostname是否与注册到我这个服务的节点的hostname一致
while(var3.hasNext()) {
InstanceInfo info = (InstanceInfo)var3.next();
//这个函数比较重要,就是判断hostname是否一致,下面我们看下
if (this.peerEurekaNodes.isInstanceURL(url, info)) {
return true;
}
}
} catch (Throwable var5) {
logger.error("Could not determine if the replica is available ", var5);
}
return false;
}
下面我们看下isInstanceURL()这个方法:
public boolean isInstanceURL(String url, InstanceInfo instance) {
String hostName = hostFromUrl(url);
String myInfoComparator = instance.getHostName();
if (this.clientConfig.getTransportConfig().applicationsResolverUseIp()) {
myInfoComparator = instance.getIPAddr();
}
return hostName != null && hostName.equals(myInfoComparator);
}
其实这个方法很简答, 就是从url中抽取hostname与注册进来的节点的hostname尽心比较。
2. Eureka的Server端的参数配置优化
eureka:
client:
# 开启注册表的拉取
fetch-registry: true
register-with-eureka: true
service-url:
defaultZone: http://eureka-8700:8700/eureka/,http://eureka-8701:8700/eureka/,http://eureka-8702:8702/eureka/
server:
# 自我保护,看服务的多少,如果服务很多,就开启,服务较少,就关闭
enable-self-preservation: false
# 自我保护的阈值
renewal-percent-threshold: 0.85
# 剔除服务的间隔时间
eviction-interval-timer-in-ms: 1000
# 关闭从readOnly读注册表
use-read-only-response-cache: false
# readwrite与readOnly同步的时间间隔
response-cache-update-interval-ms: 1000
# 从其他peer拉取注册表重试的次数(最多拉取的次数)
registry-sync-retries: 2
这样做的目的主要有两个:
1.减少服务上下线的延迟;
2.自我保护的开启或者关闭,需要看网络和服务的多少
3.服务更新的时候,要先停止服务,再发送下线请求。因为如果先发送下线请求,再停止服务的话,由于服务有续约的定时任务,会导致下线之后,服务又自动续约了,那就只等到server来进行剔除服务了。这里的服务指的是注册到EurekaServer中的服务。
3.Eureka的Client端的参数配置优化
eureka:
client:
service-url:
defaultZone: http://localhost:8700/eureka,http://localhost:8701/eureka,http://localhost:8702/eureka,
prefer-same-zone-eureka:
## 开启注册表的拉取
fetch-registry: true
## 说明自己是一个客户端
enabled: true
## 拉取注册表的时间间隔
registry-fetch-interval-seconds: 5
## 开启想eurekaServer注册
register-with-eureka: true
instance:
## 续约的时间间隔
lease-renewal-interval-in-seconds: 10
## 缺少心跳的过期时间
lease-expiration-duration-in-seconds: 10
除此之外,还有一个小技巧,我们在生产中配置eureka.client.service-url.defaultZone的时候,各个client端的配置尽量要随机一下,即打乱一下defaultZone中url的顺序,这是因为在拉取注册表的时候,默认从第一个url开始拉取,拉取不到才从下一个拉取,并且最多只能拉取3个;同时,在注册的时候,只会注册到第一个url,不会注册到下面的url。所以我们打乱了顺序以后,就减少了对某一个server的依赖,也降低了对某一个server的请求次数。