Eureka series (07) registered and active service offline
[TOC]
Spring Cloud Series catalog - Eureka articles
In the previous Eureka series (05) message broadcast in the message broadcast on Eureka source code is analyzed, after several articles will be a detailed analysis of local service registration, the initiative off the assembly line, the heartbeat renew automatically expire and other implementation mechanisms.
PeerAwareInstanceRegistryImpl
Cluster is responsible for internal messaging.AbstractInstanceRegistry
Responsible for local service information management, which is the focus of attention after a few articles.
Resources | Features | url |
---|---|---|
ApplicationsResource | To capture all or incremental service instance information | GET /apps GET /apps/delta |
ApplicationResource | 1. Obtain a single application information 2. Examples of registration information |
GET /apps/{appName} POST /apps/{appName} |
InstanceResource | CURD service instance: 1. Examples of information acquisition 2. The modified meta information service instance delete instance information services offline 4 to send the heartbeat |
GET /apps/{appName}/{id} PUT /apps/{appName}/{id}/metadata DELETE /apps/{appName}/{id} PUT /apps/{appName}/{id} |
InstancesResource | Examples of information directly acquired according to example id | GET /instances/{id} |
PeerReplicationResource | Within a cluster bulk data synchronization | POST /peerreplication/batch |
ServerInfoResource | ??? | POST /serverinfo/statusoverrides |
StatusResource | ??? | GET /statusoverrides |
Note: represents the name of the application or service id, representing the instance id. EG: HTTP: // localhost: 8080 / Eureka / Apps
1. Service registration
1.1 service instance registration process
Summary: Eureka Web using the Jersey container service registration request is register entry method ApplicationResource, the path of the request is POST:/euraka/apps/{appName}
.
1.2 ApplicationResource
// ApplicationResource HTTP请求入口
@POST
@Consumes({"application/json", "application/xml"})
public Response addInstance(InstanceInfo info,
@HeaderParam(PeerEurekaNode.HEADER_REPLICATION) String isReplication) {
... // 数据检验
registry.register(info, "true".equals(isReplication));
return Response.status(204).build(); // 204 to be backwards compatible
}
Summary: ApplicationResource main entrance is parametric test, the main logic are delegated to the PeerAwareInstanceRegistryImpl completed.
Note: isReplication argument, if it is the client's request was false, expressed the need for the news broadcast to other servers. If the cluster message broadcast was true, that is no longer the need to continue broadcasting, which will cause circulation problems broadcast.
// PeerAwareInstanceRegistryImpl 默认注册器实现
@Override
public void register(final InstanceInfo info, final boolean isReplication) {
// 租约的过期时间,默认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(Action.Register, info.getAppName(), info.getId(), info, null, isReplication);
}
Summary: PeerAwareInstanceRegistryImpl this class should be very familiar, is responsible for internal communications between the cluster, its parent AbstractInstanceRegistry is responsible for local service information management, but also the research focus of this article.
1.3 AbstractInstanceRegistry
In Eureka, the service registration information stored in memory, the data structure ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>> registry
, Map nested layers, the outer layer is appName Key, Key memory is InstanceId.
private final ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>> registry
= new ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>>();
public void register(InstanceInfo registrant, int leaseDuration, boolean isReplication) {
try {
read.lock();
// 1. 获取该服务对应的所有服务实例,如果不存在就创建一个新的Map
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;
}
}
// 2. 两种情况:一是实例已经注册,二是实例没有注册
Lease<InstanceInfo> existingLease = gMap.get(registrant.getId());
// 2.1 实例已经注册,就需要PK,PK原则:谁最后一次更新就是谁赢
// 也就是说如果已经注册的实例最近更新了,就不用重新更新了
if (existingLease != null && (existingLease.getHolder() != null)) {
Long existingLastDirtyTimestamp = existingLease.getHolder().getLastDirtyTimestamp();
Long registrationLastDirtyTimestamp = registrant.getLastDirtyTimestamp();
// 已经注册的实例PK赢了
if (existingLastDirtyTimestamp > registrationLastDirtyTimestamp) {
registrant = existingLease.getHolder();
}
// 2.2 没有注册,很好处理。更新注册的实例个数
} else {
synchronized (lock) {
if (this.expectedNumberOfClientsSendingRenews > 0) {
this.expectedNumberOfClientsSendingRenews = this.expectedNumberOfClientsSendingRenews + 1;
updateRenewsPerMinThreshold();
}
}
}
// 3. 更新注册信息(核心步骤)
Lease<InstanceInfo> lease = new Lease<InstanceInfo>(registrant, leaseDuration);
if (existingLease != null) {
lease.setServiceUpTimestamp(existingLease.getServiceUpTimestamp());
}
// (核心步骤)
gMap.put(registrant.getId(), lease);
// 添加到最近的注册队列里面去,以时间戳作为Key,名称作为value,主要是为了运维界面的统计数据
synchronized (recentRegisteredQueue) {
recentRegisteredQueue.add(new Pair<Long, String>(
System.currentTimeMillis(),
registrant.getAppName() + "(" + registrant.getId() + ")"));
}
// 4. 更新实例状态 InstanceStatus
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())) {
lease.serviceUp();
}
// 5. 清理缓存等善后工作
registrant.setActionType(ActionType.ADDED);
// 租约变更记录队列,记录了实例的每次变化, 用于注册信息的增量获取
recentlyChangedQueue.add(new RecentlyChangedItem(lease));
registrant.setLastUpdatedTimestamp();
// 清理缓存 ,传入的参数为key
invalidateCache(registrant.getAppName(), registrant.getVIPAddress(), registrant.getSecureVipAddress());
} finally {
read.unlock();
}
}
Summary: The first three steps of logic is very clear, the purpose is to update the instance information in memory, just need to pay attention to the case of the example already exists about the need for PK, in principle, that is, who is who won the last update.
Examples of status update service fourth step, OverriddenStatus reference InstanceInfo role in OverriddenStatus
Every five steps to clear the cache and other rehabilitation work. For now, to gMap.put(registrant.getId(), lease)
this point enough.
2. initiative off the assembly line
OPEN API corresponding service is offline DELETE /apps/{appName}/{id}
protected boolean internalCancel(String appName, String id, boolean isReplication) {
try {
read.lock();
CANCEL.increment(isReplication);
// 1. 清空registry中注册的实例信息(核心步骤)
Map<String, Lease<InstanceInfo>> gMap = registry.get(appName);
Lease<InstanceInfo> leaseToCancel = null;
if (gMap != null) {
leaseToCancel = gMap.remove(id);
}
// 2. 添加到 recentCanceledQueue 队列中
synchronized (recentCanceledQueue) {
recentCanceledQueue.add(new Pair<Long, String>(System.currentTimeMillis(), appName + "(" + id + ")"));
}
InstanceStatus instanceStatus = overriddenInstanceStatusMap.remove(id);
if (leaseToCancel == null) {
CANCEL_NOT_FOUND.increment(isReplication);
return false;
} else {
// 3. 和注册时一样,也要做一下清除缓存等善后工作
leaseToCancel.cancel();
InstanceInfo instanceInfo = leaseToCancel.getHolder();
String vip = null;
String svip = null;
if (instanceInfo != null) {
instanceInfo.setActionType(ActionType.DELETED);
recentlyChangedQueue.add(new RecentlyChangedItem(leaseToCancel));
instanceInfo.setLastUpdatedTimestamp();
vip = instanceInfo.getVIPAddress();
svip = instanceInfo.getSecureVipAddress();
}
invalidateCache(appName, vip, svip);
return true;
}
} finally {
read.unlock();
}
}
Summary: registered and active downline logical service is still very clear. ** For now, to gMap.remove(id)
this point enough. ** As for the future details of the real use Eureka continue in-depth study.
The intentions of recording a little bit every day. Perhaps the content is not important, but the habit is very important!