@EnableEurekaServer是个开关
实现原理:
eureka的实现原理是基于Jersey框架过滤器拦截,节点间的通信采用HTTP,服务器接受请求后,拦截request,获取对应的执行操作调用不同的处理逻辑。
核心源码
@POST
@Consumes({
"application/json", "application/xml"})
public Response addInstance(InstanceInfo info,
@HeaderParam(PeerEurekaNode.HEADER_REPLICATION) String isReplication) {
//isReplication参数判断当前请求是eureka多节点同步还是单节点注册,TRUE为集群,FALSE为单节点注册请求
logger.debug("Registering instance {} (replication={})", info.getId(), isReplication);
// 验证请求的必要字段(某个微服务的注册的必要条件)
if (isBlank(info.getId())) {
return Response.status(400).entity("Missing instanceId").build();
} else if (isBlank(info.getHostName())) {
return Response.status(400).entity("Missing hostname").build();
} else if (isBlank(info.getIPAddr())) {
return Response.status(400).entity("Missing ip address").build();
} else if (isBlank(info.getAppName())) {
return Response.status(400).entity("Missing appName").build();
} else if (!appName.equals(info.getAppName())) {
return Response.status(400).entity("Mismatched appName, expecting " + appName + " but was " + info.getAppName()).build();
} else if (info.getDataCenterInfo() == null) {
return Response.status(400).entity("Missing dataCenterInfo").build();
} else if (info.getDataCenterInfo().getName() == null) {
return Response.status(400).entity("Missing dataCenterInfo Name").build();
}
//核心代码
registry.register(info, "true".equals(isReplication));
return Response.status(204).build(); // 204 to be backwards compatible
}
/**
* @Description: 注册该节点信息,并将该信息同步到其他同等eureka节点 (职责单一原则)
* @Author: PABLO
* @Date: 2022/5/18 12:12
* @Params: [info, isReplication是否需要复制]
* @Return: void
**/
public void register(final InstanceInfo info, final boolean isReplication) {
//默认租约90秒(过期时间,eurekaServer 90秒未接受到微服务节点的心跳,将剔除)
int leaseDuration = Lease.DEFAULT_DURATION_IN_SECS;
//如果微服务节点配置了租约时间,则使用自定义的
if (info.getLeaseInfo() != null && info.getLeaseInfo().getDurationInSecs() > 0) {
leaseDuration = info.getLeaseInfo().getDurationInSecs();
}
//注册服务
super.register(info, leaseDuration, isReplication);
//集群信息同步
replicateToPeers(PeerAwareInstanceRegistryImpl.Action.Register, info.getAppName(), info.getId(), info, null, isReplication);
}
//注册表数据结构 map嵌套视为了满足集群的存储
//如user服务集群,外侧的String保存该user集群的名称AppName,
// 内侧String保存每个节点的id如DESKTOP-523JB40:eureka-client-user:9092,而后对应一个Lease<InstanceInfo>包装
private final ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>> registry
= new ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>>();
/**
* @Description: 执行注册
* @Author: PABLO
* @Date: 2022/5/18 12:34
* @Params: [registrant 注册节点信息, leaseDuration 租约,即续约时间, isReplication 是否同步]
* @Return: void
**/
public void register(InstanceInfo registrant, int leaseDuration, boolean isReplication) {
//读锁,虽然可保证代码块的读写顺序,但registry还是共享的
read.lock();
try {
//查询缓存(注册表)获取对应集群信息,AppName即我们设置的ymal中的spring.application.name=eureka-client-user
Map<String, Lease<InstanceInfo>> gMap = registry.get(registrant.getAppName());
REGISTER.increment(isReplication);
//不存在,如首次进入
if (gMap == null) {
//创建map
final ConcurrentHashMap<String, Lease<InstanceInfo>> gNewMap = new ConcurrentHashMap<String, Lease<InstanceInfo>>();
//这是concurrentHashMap操作,如对应位置有值,返回对应位置的值,如果没有,放入,返回null
gMap = registry.putIfAbsent(registrant.getAppName(), gNewMap);
//说明该位置是空的,说明该节点还未注册
if (gMap == null) {
//将新节点替换null,下面会完善gMap即当前节点的具体信息
gMap = gNewMap;
}
}
//根据节点id(DESKTOP-523JB40:eureka-client-user:9092)获取对应节点信息
//如首次进入应该是空的,如并发,可能会注册冲突
Lease<InstanceInfo> existingLease = gMap.get(registrant.getId());
if (existingLease != null && (existingLease.getHolder() != null)) {
//获取冲突的最后活跃操作时间
Long existingLastDirtyTimestamp = existingLease.getHolder().getLastDirtyTimestamp();
Long registrationLastDirtyTimestamp = registrant.getLastDirtyTimestamp();
logger.debug("Existing lease found (existing={}, provided={}", existingLastDirtyTimestamp, registrationLastDirtyTimestamp);
//如果已存在的节点更加活跃,即这边在注册时候,已注册的节点又一次发来了一次连接,那就使用已注册的
//那个节点活跃用那个
if (existingLastDirtyTimestamp > registrationLastDirtyTimestamp) {
logger.warn("There is an existing lease and the existing lease's dirty timestamp {} is greater" +
" than the one that is being registered {}", existingLastDirtyTimestamp, registrationLastDirtyTimestamp);
logger.warn("Using the existing instanceInfo instead of the new instanceInfo as the registrant");
registrant = existingLease.getHolder();
}
} else {
//没有租约信息,说明是新的注册节点
synchronized (lock) {
if (this.expectedNumberOfClientsSendingRenews > 0) {
this.expectedNumberOfClientsSendingRenews = this.expectedNumberOfClientsSendingRenews + 1;
updateRenewsPerMinThreshold();
}
}
logger.debug("No previous lease information found; it is new registration");
}
//创建新的续约对象
//InstanceInfo对象保存了该节点的所有信息,续约、剔除等相关节点的操作都是操作此对象
Lease<InstanceInfo> lease = new Lease<>(registrant, leaseDuration);
if (existingLease != null) {
lease.setServiceUpTimestamp(existingLease.getServiceUpTimestamp());
}
//将此节点放入该节点集群的map集合中(map只有一个节点就是单机)
gMap.put(registrant.getId(), lease);
//........
//.........
//清除缓存
invalidateCache(registrant.getAppName(), registrant.getVIPAddress(), registrant.getSecureVipAddress());
//........
} finally {
read.unlock();
}
}
/**
* @Description: eureka集群信息同步
* @Author: PABLO
* @Date: 2022/5/18 14:45
* @Params: [action, appName, id, info, newStatus, isReplication]
* @Return: void
**/
private void replicateToPeers(PeerAwareInstanceRegistryImpl.Action action, String appName, String id,
InstanceInfo info /* optional */,
InstanceInfo.InstanceStatus newStatus /* optional */, boolean isReplication) {
Stopwatch tracer = action.getTimer().start();
try {
if (isReplication) {
numberOfReplicationsLastMin.increment();
}
//判断重复复制
if (peerEurekaNodes == Collections.EMPTY_LIST || isReplication) {
return;
}
//复制
for (final PeerEurekaNode node : peerEurekaNodes.getPeerEurekaNodes()) {
//跳过自己
if (peerEurekaNodes.isThisMyUrl(node.getServiceUrl())) {
continue;
}
replicateInstanceActionsToPeers(action, appName, id, info, newStatus, node);
}
} finally {
tracer.stop();
}
}
/**
* @Description: 执行复制操作
* @Author: PABLO
* @Date: 2022/5/18 14:47
* @Params: [action, appName, id, info, newStatus, node]
* @Return: void
**/
private void replicateInstanceActionsToPeers(PeerAwareInstanceRegistryImpl.Action action, String appName,
String id, InstanceInfo info, InstanceInfo.InstanceStatus newStatus,
PeerEurekaNode node) {
try {
InstanceInfo infoFromRegistry;
CurrentRequestVersion.set(Version.V2);
//判断状态进行对应操作
switch (action) {
case Cancel:
node.cancel(appName, id);
break;
case Heartbeat:
InstanceInfo.InstanceStatus overriddenStatus = overriddenInstanceStatusMap.get(id);
infoFromRegistry = getInstanceByAppAndId(appName, id, false);
node.heartbeat(appName, id, infoFromRegistry, overriddenStatus, false);
break;
case Register:
node.register(info);
break;
case StatusUpdate:
infoFromRegistry = getInstanceByAppAndId(appName, id, false);
node.statusUpdate(appName, id, newStatus, infoFromRegistry);
break;
case DeleteStatusOverride:
infoFromRegistry = getInstanceByAppAndId(appName, id, false);
node.deleteStatusOverride(appName, id, infoFromRegistry);
break;
}
} catch (Throwable t) {
logger.error("Cannot replicate information to {} for action {}", node.getServiceUrl(), action.name(), t);
} finally {
CurrentRequestVersion.remove();
}
}
流程
服务端启动
节点启动发送http请求到服务端
服务端拦截后找到对应方法addInstance
调用register
查询注册表该节点信息
如不存在创建
判断注册冲突
创建该节点注册信息
保存到注册表
清缓存