携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第21天,点击查看活动详情
前言
首先eureka server采用的架构模型是peer模型,意思是说所有节点地位相等不分主次,那么在每个server中就必须保持一致的可用的server的地址集合。而后再根据具体的地址将自身的变化同步过去。
保持地址一致可用
在EurekaBootstrap找了一大圈没看到,就顺手点到initialize()中,看看初始化话线程调用的时候有没有。结果确实在这里,这个方法中启动了一个peerEurekaNodes.start(),而在这个方法中又调用了一个updatePeerEurekaNodes的方法,这个名字一看就知道是更新server节点信息用的。
try {
//resolvePeerUrls()会解析出配置文件中的地址.并剔除掉自己本身的节点地址
updatePeerEurekaNodes(resolvePeerUrls());
Runnable peersUpdateTask = new Runnable() {
@Override
public void run() {
try {
updatePeerEurekaNodes(resolvePeerUrls());
} catch (Throwable e) {
logger.error("Cannot update the replica Nodes", e);
}
}
};
taskExecutor.scheduleWithFixedDelay(
peersUpdateTask,
serverConfig.getPeerEurekaNodesUpdateIntervalMs(),
serverConfig.getPeerEurekaNodesUpdateIntervalMs(),
TimeUnit.MILLISECONDS
);
}
protected void updatePeerEurekaNodes(List<String> newPeerUrls) {
if (newPeerUrls.isEmpty()) {
logger.warn("The replica size seems to be empty. Check the route 53 DNS Registry");
return;
}
/**
* 这里是将本身存在的地址和每次运行线程时计算的地址进行比对
* 如果线下list为并且者添加节点为空 说明没有新注册的节点和下线的节点,反之怎进行节点的维护
*/
Set<String> toShutdown = new HashSet<>(peerEurekaNodeUrls);
toShutdown.removeAll(newPeerUrls);
Set<String> toAdd = new HashSet<>(newPeerUrls);
toAdd.removeAll(peerEurekaNodeUrls);
if (toShutdown.isEmpty() && toAdd.isEmpty()) { // No change
return;
}
// Remove peers no long available
List<PeerEurekaNode> newNodeList = new ArrayList<>(peerEurekaNodes);
//删除已经下线的节点
if (!toShutdown.isEmpty()) {
logger.info("Removing no longer available peer nodes {}", toShutdown);
int i = 0;
while (i < newNodeList.size()) {
PeerEurekaNode eurekaNode = newNodeList.get(i);
if (toShutdown.contains(eurekaNode.getServiceUrl())) {
newNodeList.remove(i);
eurekaNode.shutDown();
} else {
i++;
}
}
}
// Add new peers
//添加新上线的节点
if (!toAdd.isEmpty()) {
logger.info("Adding new peer nodes {}", toAdd);
for (String peerUrl : toAdd) {
newNodeList.add(createPeerEurekaNode(peerUrl));
}
}
this.peerEurekaNodes = newNodeList;
this.peerEurekaNodeUrls = new HashSet<>(newPeerUrls);
}
这里的比较方式也很简单,首先他会获得在这一次计算的可用的节点集合,然后会把当前本地保存的集合放入两个集合中,分别为下线集合和新增集合,然后将这两个集合中的本次计算结果删除,如果均为空的话,那么就说明节点没有发生改变,如果不为空的话,那么具体逻辑走具体方法。有了节点之间的集合后那么就可以进行服务间数据的同步了。这个线程每10min调度一次。
同步注册表
同步注册表是在registry.syncUp中进行的
public int syncUp() {
// Copy entire entry from neighboring DS node
int count = 0;
for (int i = 0; ((i < serverConfig.getRegistrySyncRetries()) && (count == 0)); i++) {
if (i > 0) {
try {
Thread.sleep(serverConfig.getRegistrySyncRetryWaitMs());
} catch (InterruptedException e) {
logger.warn("Interrupted during registry transfer..");
break;
}
}
//从自己client的注册表中获取注册信息
//这里其实server端在启动时回去注册另一个server,并从另一个server中拉取对应的注册表信息
//放入本地的注册表中去,然后本机会先去判断这些实例是不是可以被注册,如果可以被注册
//那么就在本机中进行注册,如果第一次没有拉取到注册信息的话,会停30s中再次去尝试,最多尝试5次
Applications apps = eurekaClient.getApplications();
for (Application app : apps.getRegisteredApplications()) {
for (InstanceInfo instance : app.getInstances()) {
try {
if (isRegisterable(instance)) {
register(instance, instance.getLeaseInfo().getDurationInSecs(), true);
count++;
}
} catch (Throwable t) {
logger.error("During DS init copy", t);
}
}
}
}
return count;
}
实例间状态的同步
当client在某一台server的状态发生改变的时候,这台server也会把改变同步给其他的server,以保持数据的一致,同步的方法在PeerAwareInstanceRegisterImpl类中
这些地方调用,而这些地方分别对应着注册,下线等地方,并且对应着几种状态
public enum Action {
Heartbeat, Register, Cancel, StatusUpdate, DeleteStatusOverride;
}
而同步的方法即为replicationToPeers
private void replicateToPeers(Action action, String appName, String id,
InstanceInfo info /* optional */,
InstanceStatus newStatus /* optional */, boolean isReplication) {
Stopwatch tracer = action.getTimer().start();
try {
//isReplication这个属性是用来判断是否是复制过来的,如果为false则需要给其他server进行同步,反之不需要
if (isReplication) {
numberOfReplicationsLastMin.increment();
}
// If it is a replication already, do not replicate again as this will create a poison replication
//如果它已经是复制,请不要再次复制,因为这将创建有毒复制
if (peerEurekaNodes == Collections.EMPTY_LIST || isReplication) {
return;
}
//向其他节点开始进行同步
for (final PeerEurekaNode node : peerEurekaNodes.getPeerEurekaNodes()) {
// If the url represents this host, do not replicate to yourself.
//删除自己的节点
if (peerEurekaNodes.isThisMyUrl(node.getServiceUrl())) {
continue;
}
replicateInstanceActionsToPeers(action, appName, id, info, newStatus, node);
}
} finally {
tracer.stop();
}
}
在调用replicateInstanceActionsToPeers方法
private void replicateInstanceActionsToPeers(Action action, String appName,
String id, InstanceInfo info, InstanceStatus newStatus,
PeerEurekaNode node) {
try {
InstanceInfo infoFromRegistry;
CurrentRequestVersion.set(Version.V2);
switch (action) {
case Cancel:
node.cancel(appName, id);
break;
case Heartbeat:
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();
}
}
这里其实就是针对不同的操作进行不同的属性拼装
查看各个node.XX方法后,发现最后都会进入到一个batchingDispatcher
//cancel方法
public void cancel(final String appName, final String id) throws Exception {
....
batchingDispatcher.process(
....
expiryTime
);
}
//heartbeat方法
public void heartbeat(final String appName, final String id,
final InstanceInfo info, final InstanceStatus overriddenStatus,
boolean primeConnection) throws Throwable {
....
batchingDispatcher.process(taskId("heartbeat", info), replicationTask, expiryTime);
}
//register方法
public void register(final InstanceInfo info) throws Exception {
batchingDispatcher.process(
taskId("register", info),
....
,
expiryTime
);
}
//statusUpdate方法
public void statusUpdate(final String appName, final String id,
final InstanceStatus newStatus, final InstanceInfo info) {
batchingDispatcher.process(
....
},
expiryTime
);
}
//deleteStatusOverride方法
public void deleteStatusOverride(final String appName, final String id, final InstanceInfo info) {
batchingDispatcher.process(
....
expiryTime);
}
后面会走一个三层队列来进行批量的请求,后面太过于麻烦,在网上找了张画的差不多的图