1-4 Eureka 服务端源码解析

发布于 2022年 02月 11日 21:41

服务端

  • Eureka Server同时也是Eureka Client,在不禁止Server的客户端行为时,他会向Server拉取注册表,服务注册,发送心跳等功能

一、相关类介绍

1.1、类结构

  • instanceRegistry(1):是Eureka Server注册表的最核心接口,主要是在内存中管理注册到Server中的服务实例信息
  • instanceRegistry(2):对PeerAwareInstanceRegistryImpl进行了扩展用于适配Spring cloud使用环境
  • LeaseManager:主要功能是对注册到Server中服务租约进行管理
  • LookupService:提供对服务实例进行检索功能

1.2、LeaseManager

  • 核心:对注册到Server的租约进行管理:服务注册,服务下线,服务租约更新,服务剔除
import com.netflix.eureka.registry.AbstractInstanceRegistry;

public interface LeaseManager<T> {

    void register(T r, int leaseDuration, boolean isReplication);

    boolean cancel(String appName, String id, boolean isReplication);

    boolean renew(String appName, String id, boolean isReplication);

    void evict();
}

管理对象
package com.netflix.eureka.lease;

public class Lease<T> {

    // 注册,下线,更新
    enum Action {
        Register, Cancel, Renew
    };

    public static final int DEFAULT_DURATION_IN_SECS = 90;

    private T holder;
    private long evictionTimestamp;
    private long registrationTimestamp;
    private long serviceUpTimestamp;
    // Make it volatile so that the expiration task would see this quicker
    private volatile long lastUpdateTimestamp;
    private long duration;

    public Lease(T r, int durationInSecs) {
        holder = r;
        registrationTimestamp = System.currentTimeMillis();
        lastUpdateTimestamp = registrationTimestamp;
        duration = (durationInSecs * 1000);

    }

    public void renew() {
        lastUpdateTimestamp = System.currentTimeMillis() + duration;

    }

    public void cancel() {
        if (evictionTimestamp <= 0) {
            evictionTimestamp = System.currentTimeMillis();
        }
    }

    public void serviceUp() {
        if (serviceUpTimestamp == 0) {
            serviceUpTimestamp = System.currentTimeMillis();
        }
    }

    public void setServiceUpTimestamp(long serviceUpTimestamp) {
        this.serviceUpTimestamp = serviceUpTimestamp;
    }

    public boolean isExpired() {
        return isExpired(0l);
    }

    public boolean isExpired(long additionalLeaseMs) {
        return (evictionTimestamp > 0 || System.currentTimeMillis() > (lastUpdateTimestamp + duration + additionalLeaseMs));
    }

    public long getRegistrationTimestamp() {
        return registrationTimestamp;
    }

    public long getLastRenewalTimestamp() {
        return lastUpdateTimestamp;
    }

    public long getEvictionTimestamp() {
        return evictionTimestamp;
    }

    public long getServiceUpTimestamp() {
        return serviceUpTimestamp;
    }

    public T getHolder() {
        return holder;
    }

}
  • 管理对象为泛型T:Lease
  • Lease代表Client实例信息的租约,提供了对其内持有的类的时间有效性操作
  • Lease持有的类是代表服务实例信息的InstanceInfo
  • Lease定义了租约操作类型:注册,下线,更新,以及租约中时间属性操作
  • 租约默认有效时间为90秒

二、instanceRegistry实现类解析

2.1、AbstractInstanceRegistry

2.1.1、服务注册
场景
  • Server收到Client元数据InstanceInfo,会将其放到本地注册表来提供其他Client进行服务发现
相关数据结构
  • InstanceInfo数据存储在Lease中
  • Lease在ConcurrentHashMap中存与内存(private final ConcurrentHashMap<String, Map<String, Lease>> registry)
注册流程
  • 1、在服务注册中会先读取一个锁,防止其他线程对内存中的数据进行操作导致数据不一致
  • 2、从内存中查询对应的InstanceInfo租约是否存在注册表中,根据AppName划分服务集群使用InstanceId作为标记服务实例
  • 2.1、如果租约存在比较租约中的更新时间,保留时间戳大的租约
  • 2.2、如果租约不存在则是一个全新的服务注册,将会继续自我保护的统计,创建新的InstanceInfo,并将租约放到registry中
  • 3、之后将一系列缓存操作并根据覆盖状态规则设置服务实例的状态,缓存操作:将InstanceInfo加入统计Client增量获取注册信息表的队列和实现中对应的缓存
  • 4、最后设置服务租约上线时间用于计算租约有效时间,释放锁完成注册
    public void register(InstanceInfo registrant, int leaseDuration, boolean isReplication) {
        try {
    
    //读取锁,通过AppName获取集群类
    //registry(ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>> registry)根据AppName对服务实例集群进行分类
    
            read.lock();
            Map<String, Lease<InstanceInfo>> gMap = registry.get(registrant.getAppName());
            REGISTER.increment(isReplication);
            
    //防止添加新的服务实例集群租约时把已有的其他线程添加的集群租约覆盖掉:
    //如果存在该键值直接返回已经存在的值;否则添加该键值返回null        
            
            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;
                }
            }
            
    //根据InstanceId获取租约实例
            
            Lease<InstanceInfo> existingLease = gMap.get(registrant.getId());
            // Retain the last dirty timestamp without overwriting it, if there is already a lease
            if (existingLease != null && (existingLease.getHolder() != null)) {
                Long existingLastDirtyTimestamp = existingLease.getHolder().getLastDirtyTimestamp();
                Long registrationLastDirtyTimestamp = registrant.getLastDirtyTimestamp();
                logger.debug("Existing lease found (existing={}, provided={}", existingLastDirtyTimestamp, registrationLastDirtyTimestamp);

                // this is a > instead of a >= because if the timestamps are equal, we still take the remote transmitted
                // InstanceInfo instead of the server local copy.
                
    //如果该租约存在,比较后更新时间戳,取最大的注册信息为有效            
                
                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 {
                // The lease does not exist and hence it is a new registration
                synchronized (lock) {
                
                //自我保护机制
                
                    if (this.expectedNumberOfRenewsPerMin > 0) {
                        // Since the client wants to cancel it, reduce the threshold
                        // (1
                        // for 30 seconds, 2 for a minute)
                        this.expectedNumberOfRenewsPerMin = this.expectedNumberOfRenewsPerMin + 2;
                        this.numberOfRenewsPerMinThreshold =
                                (int) (this.expectedNumberOfRenewsPerMin * serverConfig.getRenewalPercentThreshold());
                    }
                }
                logger.debug("No previous lease information found; it is new registration");
            }
            
    //创建新的租约 
            
            Lease<InstanceInfo> lease = new Lease<InstanceInfo>(registrant, leaseDuration);
            
    //如果租约存在,继续租约的服务上线初始时间
    
            if (existingLease != null) {
                lease.setServiceUpTimestamp(existingLease.getServiceUpTimestamp());
            }
            
    //保存租约,添加最近注册队列        
            
            gMap.put(registrant.getId(), lease);
            synchronized (recentRegisteredQueue) {
            
    //统计最近注册服务实例
                recentRegisteredQueue.add(new Pair<Long, String>(
                        System.currentTimeMillis(),
                        registrant.getAppName() + "(" + registrant.getId() + ")"));
            }
            // This is where the initial state transfer of overridden status happens
            if (!InstanceStatus.UNKNOWN.equals(registrant.getOverriddenStatus())) {
                logger.debug("Found overridden status {} for instance {}. Checking to see if needs to be add to the "
                                + "overrides", registrant.getOverriddenStatus(), registrant.getId());
                if (!overriddenInstanceStatusMap.containsKey(registrant.getId())) {
                    logger.info("Not found overridden id {} and hence adding it", registrant.getId());
                    overriddenInstanceStatusMap.put(registrant.getId(), registrant.getOverriddenStatus());
                }
            }
            
            InstanceStatus overriddenStatusFromMap = overriddenInstanceStatusMap.get(registrant.getId());
            if (overriddenStatusFromMap != null) {
                logger.info("Storing overridden status {} from map", overriddenStatusFromMap);
                registrant.setOverriddenStatus(overriddenStatusFromMap);
            }

    //根据覆盖状态规则得到服务实例的最终状态,并设置服务实例的当前状态   
    
            // Set the status based on the overridden status rules
            InstanceStatus overriddenInstanceStatus = getOverriddenInstanceStatus(registrant, existingLease, isReplication);
            registrant.setStatusWithoutDirty(overriddenInstanceStatus);

    //如果服务状态为up,设置租约的服务上线时间,只有一次设置有效
    
            // If the lease is registered with UP status, set lease service up timestamp
            if (InstanceStatus.UP.equals(registrant.getStatus())) {
                lease.serviceUp();
            }
            
    //添加最近租约变更记录队列,标识状态为ADD;设置服务的更新时间,设置response缓存过期时间用于Client全量获取注册表信息        
            
            registrant.setActionType(ActionType.ADDED);
            recentlyChangedQueue.add(new RecentlyChangedItem(lease));
            registrant.setLastUpdatedTimestamp();
            invalidateCache(registrant.getAppName(), registrant.getVIPAddress(), registrant.getSecureVipAddress());
            logger.info("Registered instance {}/{} with status {} (replication={})",
                    registrant.getAppName(), registrant.getId(), registrant.getStatus(), isReplication);
        } finally {
            read.unlock();
        }
    }
2.1.2、接受服务心跳
  • 场景:Client完成注册后会定时向Server发送心跳请求,维持自己在Server中租约的有效性
  • 参数:服务名称,服务id
流程
  • 1、通过AppName获取到内存中服务集群租约集合,并通过id获取到对应的租约对象
  • 2、排查租约不存在的状态
  • 3、通过覆盖状态获取到服务真实状态,并排查unknown状态的租约
  • 4、续约正常状态的租约
  • 5、统计续约次数用于自我保护,更新租约中有效时间
    public boolean renew(String appName, String id, boolean isReplication) {
        RENEW.increment(isReplication);
        
    //根据AppName获取服务集群租约集合
         
        Map<String, Lease<InstanceInfo>> gMap = registry.get(appName);
        Lease<InstanceInfo> leaseToRenew = null;
        if (gMap != null) {
            leaseToRenew = gMap.get(id);
        }
        
    //租约不存在直接返回心跳续约失败
    
        if (leaseToRenew == null) {
            RENEW_NOT_FOUND.increment(isReplication);
            logger.warn("DS: Registry: lease doesn't exist, registering resource: {} - {}", appName, id);
            return false;
        } else {
            InstanceInfo instanceInfo = leaseToRenew.getHolder();
            if (instanceInfo != null) {
                // touchASGCache(instanceInfo.getASGName());
                
    //获取服务的真实状态:通过覆盖状态
                
                InstanceStatus overriddenInstanceStatus = this.getOverriddenInstanceStatus(
                        instanceInfo, leaseToRenew, isReplication);
                        
    //取消异常状态的服务状态:unknown状态
                        
                if (overriddenInstanceStatus == InstanceStatus.UNKNOWN) {
                    logger.info("Instance status UNKNOWN possibly due to deleted override for instance {}"
                            + "; re-register required", instanceInfo.getId());
                    RENEW_NOT_FOUND.increment(isReplication);
                    return false;
                }
                
    //续约正常状态的服务            
                if (!instanceInfo.getStatus().equals(overriddenInstanceStatus)) {
                    logger.info(
                            "The instance status {} is different from overridden instance status {} for instance {}. "
                                    + "Hence setting the status to overridden status", instanceInfo.getStatus().name(),
                                    instanceInfo.getOverriddenStatus().name(),
                                    instanceInfo.getId());
                    instanceInfo.setStatusWithoutDirty(overriddenInstanceStatus);

                }
            }
            
    //统计续约次数用于自我保护,更新租约中有效时间
            
            renewsLastMin.increment();
            leaseToRenew.renew();
            return true;
        }
    }
服务状态
  • UP,DOWN,STARTING,OUT_OF_SERVICE,UNKNOWN
    public enum InstanceStatus {
        UP, // Ready to receive traffic
        DOWN, // Do not send traffic- healthcheck callback failed
        STARTING, // Just about starting- initializations to be done - do not
        // send traffic
        OUT_OF_SERVICE, // Intentionally shutdown for traffic
        UNKNOWN;

        public static InstanceStatus toEnum(String s) {
            if (s != null) {
                try {
                    return InstanceStatus.valueOf(s.toUpperCase());
                } catch (IllegalArgumentException e) {
                    // ignore and fall through to unknown
                    logger.debug("illegal argument supplied to InstanceStatus.valueOf: {}, defaulting to {}", s, UNKNOWN);
                }
            }
            return UNKNOWN;
        }
    }
2.1.3、服务剔除
场景
  • 当Client注册后没有续约也没有下线,这样服务的状态就处于不可知的状态,所以需要定时清理这次服务
流程
  • 1、判断是否在自我保护状态,当前状态不能剔除
  • 2、遍历注册表,获取到过期租约
  • 3、通过过期租约数量计算出续租百分百阈值和可剔除租约数量(租约总数-租约阈值)
  • 4、逐个剔除过期租约
注意
  • 服务剔除方法有很多限制是为了保证Server的可用性:自我保护模式,过期操作分批进行,服务随机逐个剔除(防止集群奔溃)
  • 定时任务执行服务剔除时间为60秒
自我保护机制
  • 背景:主要在Client与Server存在网络分区的情况下
  • 场景:当网络异常情况,Client与Server无法通讯,Client不能向Server续约,Server中存在大量过期租约,如果此时剔除租约是不合理的
  • 使用:在Server中如果出现大量Client过期被剔除,Server就会进去自我保护模式,保护注册表中的租约不被剔除,稳定以后退出该模式;
  • 使用:Client中如果向Server注册失败将快速超时并尝试与其他Server进行通信
    @Override
    public void evict() {
        evict(0l);
    }

    public void evict(long additionalLeaseMs) {
        logger.debug("Running the evict task");

    //自我保护状态的情况下不允许剔除
    
        if (!isLeaseExpirationEnabled()) {
            logger.debug("DS: lease expiration is currently disabled.");
            return;
        }

    //遍历注册表registry,获取所有过期租约
        // We collect first all expired items, to evict them in random order. For large eviction sets,
        // if we do not that, we might wipe out whole apps before self preservation kicks in. By randomizing it,
        // the impact should be evenly distributed across all applications.
        List<Lease<InstanceInfo>> expiredLeases = new ArrayList<>();
        for (Entry<String, Map<String, Lease<InstanceInfo>>> groupEntry : registry.entrySet()) {
            Map<String, Lease<InstanceInfo>> leaseMap = groupEntry.getValue();
            if (leaseMap != null) {
                for (Entry<String, Lease<InstanceInfo>> leaseEntry : leaseMap.entrySet()) {
                    Lease<InstanceInfo> lease = leaseEntry.getValue();
                    if (lease.isExpired(additionalLeaseMs) && lease.getHolder() != null) {
                        expiredLeases.add(lease);
                    }
                }
            }
        }

    //获取过期租约总数,计算出租约阈值,计算剔除租约的数量,与自我保护相关
    
        // To compensate for GC pauses or drifting local time, we need to use current registry size as a base for
        // triggering self-preservation. Without that we would wipe out full registry.
        int registrySize = (int) getLocalRegistrySize();
        int registrySizeThreshold = (int) (registrySize * serverConfig.getRenewalPercentThreshold());
        int evictionLimit = registrySize - registrySizeThreshold;

        int toEvict = Math.min(expiredLeases.size(), evictionLimit);
        if (toEvict > 0) {
            logger.info("Evicting {} items (expired={}, evictionLimit={})", toEvict, expiredLeases.size(), evictionLimit);

    //遍历过期租约集合,逐个剔除
    
            Random random = new Random(System.currentTimeMillis());
            for (int i = 0; i < toEvict; i++) {
                // Pick a random item (Knuth shuffle algorithm)
                int next = i + random.nextInt(expiredLeases.size() - i);
                Collections.swap(expiredLeases, i, next);
                Lease<InstanceInfo> lease = expiredLeases.get(i);

                String appName = lease.getHolder().getAppName();
                String id = lease.getHolder().getId();
                EXPIRED.increment();
                logger.warn("DS: Registry: expired lease for {}/{}", appName, id);
                internalCancel(appName, id, false);
            }
        }
    }
2.1.4、服务下线
场景
  • Client在应用销毁时,会向Server发送服务下线请求,清除应用中租约避免无效调用。
  • 服务剔除是通过服务下线的逻辑完成第单个服务实例过期租约的清除工作
  • 参数:服务名称,服务id(AppName,InstanceId)
流程
  • 核心:线程操作,将租约从注册表删除,并将租约进行下线及租约操作
  • 1、读取锁,防止其他线程操作
  • 2、通过AppName获取服务实例集群,并将实例从注册列表删除
  • 3、租约下线:设置下线时间,添加最近租约变更记录队列,标识类型为删除
    @Override
    public boolean cancel(String appName, String id, boolean isReplication) {
        return internalCancel(appName, id, isReplication);
    }

    protected boolean internalCancel(String appName, String id, boolean isReplication) {
        try {
        
    //读取锁,防止其他线程修改    
            read.lock();
            CANCEL.increment(isReplication);
            
    //通过appName获取到服务实例集群,并将实例从集合中移除
            
            Map<String, Lease<InstanceInfo>> gMap = registry.get(appName);
            Lease<InstanceInfo> leaseToCancel = null;
            if (gMap != null) {
                leaseToCancel = gMap.remove(id);
            }
            
    //将实例信息添加到下线服务实例统计队列
            
            synchronized (recentCanceledQueue) {
                recentCanceledQueue.add(new Pair<Long, String>(System.currentTimeMillis(), appName + "(" + id + ")"));
            }
            InstanceStatus instanceStatus = overriddenInstanceStatusMap.remove(id);
            if (instanceStatus != null) {
                logger.debug("Removed instance id {} from the overridden map which has value {}", id, instanceStatus.name());
            }
            
    //如果租约不存在返回为空
            
            if (leaseToCancel == null) {
                CANCEL_NOT_FOUND.increment(isReplication);
                logger.warn("DS: Registry: cancel failed because Lease is not registered for: {}/{}", appName, id);
                return false;
            } else {
    //租约下线:设置下线时间,添加最近租约变更记录队列,标识类型为删除        
                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);
                logger.info("Cancelled instance {}/{} (replication={})", appName, id, isReplication);
                return true;
            }
        } finally {
            read.unlock();
        }
    }
2.1.5、获取服务实例信息
全量获取注册信息
  • 核心:获取本地注册表信息,获取远程注册表信息
  • 1、从本地registry中获取注册表信息
  • 2、判断是否需要从远程获取注册表信息
  • 3、获取远程registry注册表信息
    public Applications getApplicationsFromMultipleRegions(String[] remoteRegions) {

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

        logger.debug("Fetching applications registry with remote regions: {}, Regions argument {}",
                includeRemoteRegion, remoteRegions);

        if (includeRemoteRegion) {
            GET_ALL_WITH_REMOTE_REGIONS_CACHE_MISS.increment();
        } else {
            GET_ALL_CACHE_MISS.increment();
        }
        Applications apps = new Applications();
        apps.setVersion(1L);
        
    //获取本地registry注册信息
        
        for (Entry<String, Map<String, Lease<InstanceInfo>>> entry : registry.entrySet()) {
            Application app = null;

            if (entry.getValue() != null) {
                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));
                }
            }
            if (app != null) {
                apps.addApplication(app);
            }
        }
        
    //获取远程registry注册表信息
    
        if (includeRemoteRegion) {
            for (String remoteRegion : remoteRegions) {
                RemoteRegionRegistry remoteRegistry = regionNameVSRemoteRegistry.get(remoteRegion);
                if (null != remoteRegistry) {
                    Applications remoteApps = remoteRegistry.getApplications();
                    for (Application application : remoteApps.getRegisteredApplications()) {
                        if (shouldFetchFromRemoteRegistry(application.getName(), remoteRegion)) {
                            logger.info("Application {}  fetched from the remote region {}",
                                    application.getName(), remoteRegion);

                            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 {
                            logger.debug("Application {} not fetched from the remote region {} as there exists a "
                                            + "whitelist and this app is not in the whitelist.",
                                    application.getName(), remoteRegion);
                        }
                    }
                } else {
                    logger.warn("No remote registry available for the remote region {}", remoteRegion);
                }
            }
        }
        apps.setAppsHashCode(apps.getReconcileHashCode());
        return apps;
    }
曾量获取注册信息
  • 核心:从多个地区获取增量注册表信息,封装成Applications
  • 1、
    public Applications getApplicationDeltasFromMultipleRegions(String[] remoteRegions) {
        if (null == remoteRegions) {
            remoteRegions = allKnownRemoteRegions; // null means all remote regions.
        }

        boolean includeRemoteRegion = remoteRegions.length != 0;

        if (includeRemoteRegion) {
            GET_ALL_WITH_REMOTE_REGIONS_CACHE_MISS_DELTA.increment();
        } else {
            GET_ALL_CACHE_MISS_DELTA.increment();
        }

        Applications apps = new Applications();
        apps.setVersion(responseCache.getVersionDeltaWithRegions().get());
        Map<String, Application> applicationInstancesMap = new HashMap<String, Application>();
        try {
        
    //开启锁    
            write.lock();
            
    //遍历队列获取最近变化的服务实例信息
            
            Iterator<RecentlyChangedItem> iter = this.recentlyChangedQueue.iterator();
            logger.debug("The number of elements in the delta queue is :{}", this.recentlyChangedQueue.size());
            while (iter.hasNext()) {
                Lease<InstanceInfo> lease = iter.next().getLeaseInfo();
                InstanceInfo instanceInfo = lease.getHolder();
                logger.debug("The instance id {} is found with status {} and actiontype {}",
                        instanceInfo.getId(), instanceInfo.getStatus().name(), instanceInfo.getActionType().name());
                Application app = applicationInstancesMap.get(instanceInfo.getAppName());
                if (app == null) {
                    app = new Application(instanceInfo.getAppName());
                    applicationInstancesMap.put(instanceInfo.getAppName(), app);
                    apps.addApplication(app);
                }
                app.addInstance(decorateInstanceInfo(lease));
            }

    //读取远程Server中增量注册表信息
    
            if (includeRemoteRegion) {
                for (String remoteRegion : remoteRegions) {
                    RemoteRegionRegistry remoteRegistry = regionNameVSRemoteRegistry.get(remoteRegion);
                    if (null != remoteRegistry) {
                        Applications remoteAppsDelta = remoteRegistry.getApplicationDeltas();
                        if (null != remoteAppsDelta) {
                            for (Application application : remoteAppsDelta.getRegisteredApplications()) {
                                if (shouldFetchFromRemoteRegistry(application.getName(), remoteRegion)) {
                                    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);
                                    }
                                }
                            }
                        }
                    }
                }
            }
    
    //计算集合一致性哈希码,在Client拉取时对比
    
            Applications allApps = getApplicationsFromMultipleRegions(remoteRegions);
            apps.setAppsHashCode(allApps.getReconcileHashCode());
            return apps;
        } finally {
            write.unlock();
        }
    }

2.2、PeerAwareInstanceRegistry

2.2.1、集群同步
  • 背景:如果Server是集群部署,为了Server注册表一致性需要一个机制同步Server集群注册表数据
  • 集群同步包含两部分
  • 1、Server在启动过程中从peer节点中拉取注册表信息,并将这些数据注册到本地注册表中
  • 2、Server每次对注册表进行操作时,会将操作同步到他的peer节点,达到集群数据统一
2.2.2、初始化
初始化本地注册表信息
  • 启动过程会将peer节点中拉取注册表初始化本地注册表:syncUp()
    @Override
    public int syncUp() {
    
    //从邻近的peer中复制这个注册表
    
        // 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;
                }
            }
            
    //获取所有的服务实例
            
            Applications apps = eurekaClient.getApplications();
            for (Application app : apps.getRegisteredApplications()) {
                for (InstanceInfo instance : app.getInstances()) {
                    try {
                    
    //判断是否可注册(用于AWS环境),如果部署在其他环境直接返回true,并注册到自身的环境中
                   
                        if (isRegisterable(instance)) {
                            register(instance, instance.getLeaseInfo().getDurationInSecs(), true);
                            count++;
                        }
                    } catch (Throwable t) {
                        logger.error("During DS init copy", t);
                    }
                }
            }
        }
        return count;
    }

####### 服务接受请求原理

  • Server中有个过滤器StatusFilter检查Server状态
  • 正常:只有在UP情况下才可以接受Client的请求数据
  • 异常:如果Server在syncUp()没有获取到服务数据,会吧peerInstancesTransferEmptyOnStartUp改成true,这时过5分钟可以被Client获取到信息
  • 异常配置:eureka.server.wait-tim-in-ms-when-sync-empty默认5分钟
Server对Client处理
  • 背景:Server也是一个Client,启动时会从对应的Server中拉取全量注册表信息,在Server集群时Server会从peer节点拉取信息后遍历Applications将所有服务注册到自身
  • 处理:初始化本地时,Server不会接受来自Client的请求,在同步注册接受后允许Server接受请求
  • 流程:
  • 1、初始化自我保护统计参数
  • 2、如果同步的应用实例数量为0,在当前状态下拒绝Client通讯
  • 3、排除在AWS环境运行的情况
  • 4、修改服务实例为健康状态可以接受Client通讯:StatusFilter过滤器,检查Server状态,只有up才可以接受请求
    @Override
    public void openForTraffic(ApplicationInfoManager applicationInfoManager, int count) {
    
    //初始化自我保护统计参数
    
        // Renewals happen every 30 seconds and for a minute it should be a factor of 2.
        this.expectedNumberOfRenewsPerMin = count * 2;
        this.numberOfRenewsPerMinThreshold =
                (int) (this.expectedNumberOfRenewsPerMin * serverConfig.getRenewalPercentThreshold());
        logger.info("Got {} instances from neighboring DS node", count);
        logger.info("Renew threshold is: {}", numberOfRenewsPerMinThreshold);
        this.startupTime = System.currentTimeMillis();
        
    //如果同步的应用实例数量为0,在当前状态下拒绝Client通讯
        
        if (count > 0) {
            this.peerInstancesTransferEmptyOnStartup = false;
        }
        DataCenterInfo.Name selfName = applicationInfoManager.getInfo().getDataCenterInfo().getName();
        boolean isAws = Name.Amazon == selfName;
        
    //排除在AWS环境运行的情况
        
        if (isAws && serverConfig.shouldPrimeAwsReplicaConnections()) {
            logger.info("Priming AWS connections for all replicas..");
            primeAwsReplicas(applicationInfoManager);
        }
        logger.info("Changing status to UP");
        
    //修改服务实例为健康状态可以接受Client通讯:StatusFilter过滤器,检查Server状态,只有up才可以接受请求
    
        applicationInfoManager.setInstanceStatus(InstanceStatus.UP);
        super.postInit();
    }
2.2.3、初始化Server之间注册表信息同步复制
  • 背景:为了保证Server集群运行时注册表信息一致Server在对本地注册表操作是会将相应的操作同步到peer节点
    @Override
    public boolean cancel(final String appName, final String id,
                          final boolean isReplication) {
                          
    //同步下线状态
    
        if (super.cancel(appName, id, isReplication)) {
            replicateToPeers(Action.Cancel, appName, id, null, null, isReplication);
            synchronized (lock) {
                if (this.expectedNumberOfRenewsPerMin > 0) {
                    // Since the client wants to cancel it, reduce the threshold (1 for 30 seconds, 2 for a minute)
                    this.expectedNumberOfRenewsPerMin = this.expectedNumberOfRenewsPerMin - 2;
                    this.numberOfRenewsPerMinThreshold =
                            (int) (this.expectedNumberOfRenewsPerMin * serverConfig.getRenewalPercentThreshold());
                }
            }
            return true;
        }
        return false;
    }
    //同步续约状态
  public boolean renew(final String appName, final String id, final boolean isReplication) {
        if (super.renew(appName, id, isReplication)) {
            replicateToPeers(Action.Heartbeat, appName, id, null, null, isReplication);
            return true;
        }
        return false;
    }
    
    //同步操作状态:Heartbeat, Register, Cancel, StatusUpdate, DeleteStatusOverride
    public enum Action {
        Heartbeat, Register, Cancel, StatusUpdate, DeleteStatusOverride;

        private com.netflix.servo.monitor.Timer timer = Monitors.newTimer(this.name());

        public com.netflix.servo.monitor.Timer getTimer() {
            return this.timer;
        }
    }
    //遍历Server中peer节点,向每个peer节点发送同步请求
    
    private void replicateToPeers(Action action, String appName, String id,
                                  InstanceInfo info /* optional */,
                                  InstanceStatus newStatus /* optional */, boolean isReplication) {
        Stopwatch tracer = action.getTimer().start();
        try {
            if (isReplication) {
                numberOfReplicationsLastMin.increment();
            }
            
    //排除peer为空的集群
            // If it is a replication already, do not replicate again as this will create a poison replication
            if (peerEurekaNodes == Collections.EMPTY_LIST || isReplication) {
                return;
            }
    
    //向peer集群的每个peer节点进行同步
    //如果peer节点是自身则不同步
    //同步是根据action状态进行请求        

            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();
        }
    }

  • 同步操作
  • 每一个同步复制都是通过批任务流方式操作,同一时间内相同服务实例相同操作使用相同的任务编号
  • 在同步复制时通过任务编号合并,减少同步操作的数量和我聊的消耗,同步复制造成了延时,不满足强一致性
    //根据不同状态进行同步:下线,心跳,注册,更新,删除
    
    private void replicateInstanceActionsToPeers(Action action, String appName,
                                                 String id, InstanceInfo info, InstanceStatus newStatus,
                                                 PeerEurekaNode node) {
        try {
            InstanceInfo infoFromRegistry = null;
            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);
        }
    }

推荐文章