Eureka使用
Eureka客户端实现原理分析
Eureka服务端实现原理分析
本文主要内容:
- 服务端如何存储应用服务信息
- 服务端如何处理client请求
- 服务端如何处理服务注册请求
- 服务端如何处理heartbeat请求
- 服务端如何处理服务列表拉取请求
- 服务端如何实现自我保护机制
spring-cloud-netflix-eureka-client不同版本之间,代码会不一样,本文分析的是spring-cloud-netflix-eureka-server-2.2.2.RELEASE.jar版
一、Eureka如何存储应用信息
首先,我们先了解Eureka Server是如何保存实例信息的,Eureka Server通过三级缓存的机制保存应用实例信息。即:在Eureka Server存在三个地方存储实例信息
:
- registry注册缓存: 用于处理客户端服务更新实例信息,类型ConcurrentHashMap<String, Map<String, Lease>>
- readWriteCacheMap读写缓存: 定时同步registry中的应用信息,如果禁用只读缓存readOnlyCacheMap,也可以对客户端提供服务列表查询,类型LoadingCache<Key, Value>
- readOnlyCacheMap只读缓存: 定时同步readWriteCacheMap中的应用信息,同时用于提供客户端读取应用列表,ConcurrentMap<Key, Value>
采用三级缓存的意义:当存在大量的服务注册和更新时,如果只是修改一个ConcurrentHashMap数据,会因为锁的竞争,影响性能。通过多级缓存实现读写分离,提供服务端性能
- 默认情况下定时任务每30s将readWriteCacheMap同步至readOnlyCacheMap,每60s清理超过90s未续约的节点,
- responseCacheUpdateIntervalMs参数值 : readOnlyCacheMap 缓存更新的定时器时间间隔,默认为30秒
- responseCacheAutoExpirationInSeconds : readWriteCacheMap 缓存过期时间,默认为 180 秒
- Eureka Client每30s从readOnlyCacheMap更新服务注册信息
三级缓存示意图:
二、服务端如何处理client请求
Eureka服务端对外提供了很多服务地址:在eureka-core-1.9.17.jar包的com.netflix.eureka.resources中
2.1、Eureka服务处理注册请求
2.1.1、ApplicationResource.addInstance():用于处理服务注册请求
@POST
@Consumes({
"application/json", "application/xml"})
public Response addInstance(InstanceInfo info,@HeaderParam(PeerEurekaNode.HEADER_REPLICATION) String isReplication) {
logger.debug("Registering instance {} (replication={})", info.getId(), isReplication);
// 参数验证:validate that the instanceinfo contains all the necessary required fields
......
// 省略
// handle cases where clients may be registering with bad DataCenterInfo with missing data
// 不能提供服务返回400
DataCenterInfo dataCenterInfo = info.getDataCenterInfo();
if (dataCenterInfo instanceof UniqueIdentifier) {
String dataCenterInfoId = ((UniqueIdentifier) dataCenterInfo).getId();
if (isBlank(dataCenterInfoId)) {
boolean experimental = "true".equalsIgnoreCase(serverConfig.getExperimental("registration.validation.dataCenterInfoId"));
if (experimental) {
String entity = "DataCenterInfo of type " + dataCenterInfo.getClass() + " must contain a valid id";
return Response.status(400).entity(entity).build();
} else if (dataCenterInfo instanceof AmazonInfo) {
AmazonInfo amazonInfo = (AmazonInfo) dataCenterInfo;
String effectiveId = amazonInfo.get(AmazonInfo.MetaDataKey.instanceId);
if (effectiveId == null) {
amazonInfo.getMetadata().put(AmazonInfo.MetaDataKey.instanceId.getName(), info.getId());
}
} else {
logger.warn("Registering DataCenterInfo of type {} without an appropriate id", dataCenterInfo.getClass());
}
}
}
// 进行服务注册
// 最终调用PeerAwareInstanceRegistry.register(info, "true".equals(isReplication));
registry.register(info, "true".equals(isReplication));
return Response.status(204).build(); // 204 to be backwards compatible
}
2.1.2、PeerAwareInstanceRegistry.register()
注册方法会调用到父类AbstractInstanceRegistry.register()方法
@Override
public void register(final InstanceInfo info, final boolean isReplication) {
// 租约过期时间,默认90s,如果超过90s没有收到客户端的心跳,则剔除该节点
int leaseDuration = Lease.DEFAULT_DURATION_IN_SECS;
if (info.getLeaseInfo() != null && info.getLeaseInfo().getDurationInSecs() > 0) {
leaseDuration = info.getLeaseInfo().getDurationInSecs();
}
// 调用super.register发起节点注册
super.register(info, leaseDuration, isReplication);
// 复制信息到Eureka Server集群中其他机器上,循环集群中的所有节点,逐一发起注册
replicateToPeers(Action.Register, info.getAppName(), info.getId(), info, null, isReplication);
}
PeerAwareInstanceRegistry类图:
2.1.3、AbstractInstanceRegistry.register完成服务注册
Eureka Server端,会把客户端的地址信息保存到一个ConcurrentHashMap中,即:registry注册缓存。并通过心跳检测机制进行服务续约。
public void register(InstanceInfo registrant, int leaseDuration, boolean isReplication) {
try {
// 获取读锁
read.lock();
// 根据appName,从registry中获得当前实例信息
Map<String, Lease<InstanceInfo>> gMap = registry.get(registrant.getAppName());
// 增加注册次数到监控信息中
REGISTER.increment(isReplication);
// 如果当前appName是第一次注册,则初始化一个ConcurrentHashMap
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;
}
}
// 从gMap中查询已经存在的Lease信息,(Lease信息,包括:服务提供者实例信息,以及对服务实例的租约管理)
Lease<InstanceInfo> existingLease = gMap.get(registrant.getId());
// Retain the last dirty timestamp without overwriting it, if there is already a lease
// 当instance已经存在,则和客户端的instance进行比较,时间最新的为有效instance信息
if (existingLease != null