【源码】Spring Cloud —— Eureka Server 2 集群相关

前言

上一章节,对 AbstractInstanceRegistry 实现 服务注册、服务续约、服务下线、服务剔除 的相关内容做了解读,本章节对 Eureka 集群相关的操作进行解读

版本

Spring Cloud Netflix 版本:2.2.3.RELEASE
对应 Netflix-Eureka 版本:1.9.21

集群初始化

Eureka Server 在启动时,首先要尝试从其他节点获取注册表信息,该逻辑由 PeerAwareInstanceRegistryImpl#syncUp 方法实现

	@Override
    public int syncUp() {
        int count = 0;

        // 尝试多次
        for (int i = 0; ((i < serverConfig.getRegistrySyncRetries()) && (count == 0)); i++) {
            if (i > 0) {
                try {
                    // 每次失败后会睡眠一会
                    Thread.sleep(serverConfig.getRegistrySyncRetryWaitMs());
                } catch (InterruptedException e) {
                    
                    // ...
                    
                    break;
                }
            }

            // 获取所有 InstanceInfo 并注册
            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++,打断循环条件
                            count++;
                        }
                    } catch (Throwable t) {
                        logger.error("During DS init copy", t);
                    }
                }
            }
        }
        return count;
    }

该方法的调用链路如下

  • spring-cloud-netflix-eureka-server 依赖通过 自动装配 引入类 EurekaServerAutoConfiguration
  • EurekaServerAutoConfiguration 通过注解 @Import 引入类 EurekaServerInitializerConfiguration
  • EurekaServerInitializerConfiguration 实现了 SmartLifecycle 接口,再其 start 生命周期中调用了 eurekaServerBootstrap#contextInitialized 方法
  • 该方法内部调用 syncUp
  • syncUp 之后,openForTraffic 方法会被调用,使得 Eureka Server 可以接受来自 Eureka Client 的流量信息

集群复制

Eureka Server服务注册、服务续约、服务下线、服务剔除 等操作也会同步到其他集群上

比如 服务注册 PeerAwareInstanceRegistryImpl#register

	@Override
    public void register(final InstanceInfo info, final boolean isReplication) {
        int leaseDuration = Lease.DEFAULT_DURATION_IN_SECS;
        if (info.getLeaseInfo() != null && info.getLeaseInfo().getDurationInSecs() > 0) {
            leaseDuration = info.getLeaseInfo().getDurationInSecs();
        }
        
        // AbstractInstanceRegistry#register
        super.register(info, leaseDuration, isReplication);
        
        // 集群同步
        replicateToPeers(Action.Register, info.getAppName(), info.getId(), info, null, isReplication);
    }

	---------------- replicateToPeers ----------------

	private void replicateToPeers(Action action, String appName, String id,
                                  InstanceInfo info,
                                  InstanceStatus newStatus, 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();
        }
    }

	---------- replicateInstanceActionsToPeers ----------

	private void replicateInstanceActionsToPeers(Action action, String appName,
                                                 String id, InstanceInfo info, InstanceStatus newStatus,
                                                 PeerEurekaNode node) {
        try {
            InstanceInfo infoFromRegistry;
            CurrentRequestVersion.set(Version.V2);
            
            // 根据对应的 Action 调用对应的方法
            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) {
            
            // ...
        
        } finally {
            CurrentRequestVersion.remove();
        }
    }

借由 集群初始化集群复制 的操作,Eureka 保证各集群间的数据同步,保证了 分区容忍性

实例获取

  • Application:封装 服务实例 列表,所有的 实例 都挂在相同的 appName
  • Applications:注册表中 所有服务 的集合

注册表以 Application 维度封装了 InstanceInfoAbstractInstanceRegistry 类提供了对应的获取方法

getApplication

	@Override
    public Application getApplication(String appName) {
        boolean disableTransparentFallback = serverConfig.disableTransparentFallbackToOtherRegion();
        return this.getApplication(appName, !disableTransparentFallback);
    }

    @Override
    public Application getApplication(String appName, boolean includeRemoteRegion) {
        Application app = null;

        // 所有租约
        Map<String, Lease<InstanceInfo>> leaseMap = registry.get(appName);

        if (leaseMap != null && leaseMap.size() > 0) {
            // 将所有租约的实例信息封装到 Application
            for (Entry<String, Lease<InstanceInfo>> entry : leaseMap.entrySet()) {
                if (app == null) {
                    app = new Application(appName);
                }
                app.addInstance(decorateInstanceInfo(entry.getValue()));
            }
        } else if (includeRemoteRegion) {
            // 尝试从远程 Region 获取
            for (RemoteRegionRegistry remoteRegistry : this.regionNameVSRemoteRegistry.values()) {
                Application application = remoteRegistry.getApplication(appName);
                if (application != null) {
                    return application;
                }
            }
        }
        return app;
    }

参数 includeRemoteRegion 决定是否从远程 Region 获取

getApplications

	public Applications getApplications() {
        boolean disableTransparentFallback = serverConfig.disableTransparentFallbackToOtherRegion();
        if (disableTransparentFallback) {
            // 仅从本地
            return getApplicationsFromLocalRegionOnly();
        } else {
            // 包含其他 Region
            return getApplicationsFromAllRemoteRegions();
        }
    }

	---------------------------------------------------

    public Applications getApplicationsFromAllRemoteRegions() {
        return getApplicationsFromMultipleRegions(allKnownRemoteRegions);
    }

    @Override
    public Applications getApplicationsFromLocalRegionOnly() {
        return getApplicationsFromMultipleRegions(EMPTY_STR_ARRAY);
    }

	-------- getApplicationsFromMultipleRegions --------

	public Applications getApplicationsFromMultipleRegions(String[] remoteRegions) {

        boolean includeRemoteRegion = null != remoteRegions && remoteRegions.length != 0;

        // ...

        if (includeRemoteRegion) {
            GET_ALL_WITH_REMOTE_REGIONS_CACHE_MISS.increment();
        } else {
            GET_ALL_CACHE_MISS.increment();
        }
        Applications apps = new Applications();
        apps.setVersion(1L);

        // 遍历所有租约信息
        for (Entry<String, Map<String, Lease<InstanceInfo>>> entry : registry.entrySet()) {
            Application app = null;

            if (entry.getValue() != null) {

                // 遍历所有示例信息封装到 Application
                for (Entry<String, Lease<InstanceInfo>> stringLeaseEntry : entry.getValue().entrySet()) {
                    Lease<InstanceInfo> lease = stringLeaseEntry.getValue();
                    if (app == null) {
                        app = new Application(lease.getHolder().getAppName());
                    }
                    app.addInstance(decorateInstanceInfo(lease));
                }
            }

            // 将 Application 封装到 Applications
            if (app != null) {
                apps.addApplication(app);
            }
        }
        if (includeRemoteRegion) {
            // 遍历所有远程注册表
            for (String remoteRegion : remoteRegions) {
                RemoteRegionRegistry remoteRegistry = regionNameVSRemoteRegistry.get(remoteRegion);
                if (null != remoteRegistry) {

                    // 获取对应的 Applications
                    Applications remoteApps = remoteRegistry.getApplications();

                    // 遍历 Applications 中的所有 Application
                    for (Application application : remoteApps.getRegisteredApplications()) {
                        if (shouldFetchFromRemoteRegistry(application.getName(), remoteRegion)) {
                            
                            // ...

                            // 从 apps 中获取对应的 Application
                            Application appInstanceTillNow = apps.getRegisteredApplications(application.getName());

                            // 获取不到则将其加入
                            if (appInstanceTillNow == null) {
                                appInstanceTillNow = new Application(application.getName());
                                apps.addApplication(appInstanceTillNow);
                            }

                            // 添加对应的实例信息
                            for (InstanceInfo instanceInfo : application.getInstances()) {
                                appInstanceTillNow.addInstance(instanceInfo);
                            }
                        } else {
                            
                            // ...
                            
                        }
                    }
                } else {
                    
                    // ...
                    
                }
            }
        }
        apps.setAppsHashCode(apps.getReconcileHashCode());
        return apps;
    }

提供对所有 服务实例 的获取

总结

PeerAwareInstanceRegistryImplAbstractInstanceRegistry 提供相关的操作保证了 Eureka Server 各集群间 服务实例 的同步,因此保证 Eureka Server 作为 注册中心分区容忍性

关于 Eureka 和 CAP

关于 分布式系统CAP 理论,可以看这篇文章

[分布式]:分布式系统的CAP理论

Eureka 便是典型的 AP 系统

  • A可用性(Availability)Eureka 通过 自我保护 机制,在由于 Eureka ClientEureka Server 之间通讯中断时引起大面积 服务心跳 丢失时,便会启动 自我保护 机制,避免大批健康的 服务实例 被剔除,这大大提升了 Eureka可用性
  • P分区容忍性(Partition Tolerance)Eureka 并不依赖单一的 ServerEureka ClientEureka Server 直接可以互相注册,形成 集群,通过 集群 之间 服务实例 信息的 复制与同步,单一 Eureka Server 的宕机并不会对 服务 的访问产生影响,这大大提升了 Eureka分区容忍性
  • C一致性(Consistency),由于 集群 之间的 同步复制 是通过 HTTP 的方式进行,基于网络的不可靠性,集群 中的 Eureka Server 之间的注册表信息难免存在不同步的时间节点,因此不满足 一致性

上一篇:【源码】Spring Cloud —— Eureka Server 1 服务注册、服务续约、服务下线、服务剔除

参考

《Spring Cloud 微服务架构进阶》 —— 朱荣鑫 张天 黄迪璇

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值