eureka 源码

  本文为eureka学习笔记,错误之处请指正。

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

1、服务生产者是怎么注册到配置中心的
  a、第一步,构造实例信息,用于接下来的注册;
  这个过程是通过EurekaClientConfiguration类中的方法eurekaApplicationInfoManager来实现的,其中的InstanceInfoFactory.create(config)返回了一个实例,这个实例就是创建后的实例信息对象。通过查看create方法的源码可以看到该方法就是将读取的eureka的配置信息放入一个EurekaInstanceConfig,然后用这些配置创建了一个InstanceInfo,注意,这时候是没有注册到服务端的。
  EurekaClientConfiguration的部分代码:

@Configuration
@EurekaClientAutoConfiguration.ConditionalOnMissingRefreshScope
protected static class EurekaClientConfiguration {
//省略部分代码
@Bean(destroyMethod = "shutdown")
@ConditionalOnMissingBean(value = {EurekaClient.class},search = SearchStrategy.CURRENT)
public EurekaClient eurekaClient(ApplicationInfoManager manager, EurekaClientConfig config) {//用于跟服务器交互的客户端实例,实际是DiscoveryClient的一个子类(spring cloud对eureka自己进行了一些封装)
    return new CloudEurekaClient(manager, config, this.optionalArgs, this.context);
}
@Bean
@ConditionalOnMissingBean( value = {ApplicationInfoManager.class}, search = SearchStrategy.CURRENT ) //在没有ApplicationInfoManager的实例的时候创建
public ApplicationInfoManager eurekaApplicationInfoManager(EurekaInstanceConfig config) {
    InstanceInfo instanceInfo = (new InstanceInfoFactory()).create(config); //在这里创建了instanceInfo
    return new ApplicationInfoManager(config, instanceInfo);
 }
}
  创建过程:
public InstanceInfo create(EurekaInstanceConfig config) {
    Builder leaseInfoBuilder = Builder.newBuilder().setRenewalIntervalInSecs(config.getLeaseRenewalIntervalInSeconds()).setDurationInSecs(config.getLeaseExpirationDurationInSeconds());
    com.netflix.appinfo.InstanceInfo.Builder builder = com.netflix.appinfo.InstanceInfo.Builder.newBuilder();
builder.setNamespace(namespace).setAppName(config.getAppname()) // 这里用config的信息进行了大量的设置
      .setInstanceId(config.getInstanceId())
      .setAppGroupName(config.getAppGroupName())
      .setDataCenterInfo(config.getDataCenterInfo())
      .setIPAddr(config.getIpAddress()).setHostName(config.getHostName(false))
      .setPort(config.getNonSecurePort())
      .enablePort(InstanceInfo.PortType.UNSECURE,
            config.isNonSecurePortEnabled())
      .setSecurePort(config.getSecurePort())
      .enablePort(InstanceInfo.PortType.SECURE, config.getSecurePortEnabled())
      .setVIPAddress(config.getVirtualHostName())
      .setSecureVIPAddress(config.getSecureVirtualHostName())
      .setHomePageUrl(config.getHomePageUrlPath(), config.getHomePageUrl())
      .setStatusPageUrl(config.getStatusPageUrlPath(),
            config.getStatusPageUrl())
      .setHealthCheckUrls(config.getHealthCheckUrlPath(),
            config.getHealthCheckUrl(), config.getSecureHealthCheckUrl())
      .setASGName(config.getASGName());
// 省略代码若干...
    return instanceInfo;
}

  其中,方法参数EurekaInstanceConfig的实际实现是EurekaInstanceConfigBean,类注解如下:

@ConfigurationProperties("eureka.instance")//读取配置文件中前缀为eureka.instance的内容
public class EurekaInstanceConfigBean implements CloudEurekaInstanceConfig, EnvironmentAware {
     private static final String UNKNOWN = "unknown";
     private HostInfo hostInfo;
     private InetUtils inetUtils;
......
}
  类似的配置信息类还有:EurekaClientConfigBean(客户端配置信息),EurekaServerConfigBean(服务端配置信息)等;
  b、利用初始化的discoveryClient,将实例信息注册到server端(如果配置文件中声明要注册的话)
  DiscoveryClient类在创建的时候会执行initScheduledTasks()方法进行初始化,显然这个方法是构建了一系列定时任务,其中一段如下:
if (clientConfig.shouldRegisterWithEureka()) {
    int renewalIntervalInSecs = instanceInfo.getLeaseInfo().getRenewalIntervalInSecs();
    int expBackOffBound = clientConfig.getHeartbeatExecutorExponentialBackOffBound();
    logger.info("Starting heartbeat executor: " + "renew interval is: " + renewalIntervalInSecs);
    // Heartbeat timer
    scheduler.schedule(  //心跳检测的代码
            new TimedSupervisorTask(
                    "heartbeat",
                    scheduler,
                    heartbeatExecutor,
                    renewalIntervalInSecs,
                    TimeUnit.SECONDS,
                    expBackOffBound,
                    new HeartbeatThread()
            ),
            renewalIntervalInSecs, TimeUnit.SECONDS);
   //省略代码若干......
    if (clientConfig.shouldOnDemandUpdateStatusChange()) {
        applicationInfoManager.registerStatusChangeListener(statusChangeListener);
    }
    instanceInfoReplicator.start(clientConfig.getInitialInstanceInfoReplicationIntervalSeconds());
}

  我们看到该方法启动了一个线程,new HeartbeatThread的run方法代码如下:

private class HeartbeatThread implements Runnable {
    public void run() {
        if (renew()) { //这个renew实际就是心跳检测的过程
            lastSuccessfulHeartbeatTimestamp = System.currentTimeMillis();
        }
    }
}

  renew方法代码:

boolean renew() {
    EurekaHttpResponse<InstanceInfo> httpResponse;
    try {
        httpResponse = eurekaTransport.registrationClient.sendHeartBeat(instanceInfo.getAppName(), instanceInfo.getId(), instanceInfo, null);//发送心跳请求
        logger.debug("{} - Heartbeat status: {}", PREFIX + appPathIdentifier, httpResponse.getStatusCode());
        if (httpResponse.getStatusCode() == 404) {//返回状态码400,不存在,说明服务端没有信息,要进行注册操作
            REREGISTER_COUNTER.increment();
            logger.info("{} - Re-registering apps/{}", PREFIX + appPathIdentifier, instanceInfo.getAppName());
            return register();//进行注册
        }
        return httpResponse.getStatusCode() == 200;//服务端正常返回,心跳检测成功
    } catch (Throwable e) {
        logger.error("{} - was unable to send heartbeat!", PREFIX + appPathIdentifier, e);
        return false;
    }
}
2、服务端注册过程

  spring cloud中eureka的client跟server端都使用jersey进行交互,在server端,EurekaServerAutoConfiguration中的jerseyFilterRegistration方法动态注册了一个Jersey filter,用来拦截所有请求。具体对请求的处理是由ApplicationResource类的addInstance方法来完成的。

@POST
@Consumes({"application/json", "application/xml"})
public Response addInstance(InstanceInfo info,  @HeaderParam(PeerEurekaNode.HEADER_REPLICATION) String isReplication) {
    logger.debug("Registering instance {} (replication={})", info.getId(), isReplication);
    // validate that the instanceinfo contains all the necessary required fields
    //参数校验代码

    // handle cases where clients may be registering with bad DataCenterInfo with missing data
   //高可用部分代码

    registry.register(info, "true".equals(isReplication));
    return Response.status(204).build();  // 204 to be backwards compatible
}

  父类InstanceRegistry的register是具体的执行代码,这个register方法干了两件事儿:1、发布了一个事件,2、进行了一个注册操作。再父类PeerAwareInstanceRegistryImpl的主要功能是将注册操作分发到eureka server集群的其它节点,以保持数据的一致性。继续一步步点击进入,最终的注册由AbstractInstanceRegistry类的register完成,部分代码如下:

private final ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>> registry  = new ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>>(); //这个是map里边套了个map,键为服务名称
public void register(InstanceInfo registrant, int leaseDuration, boolean isReplication) {
    try {
        read.lock();
        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>>();//这个是内部的map,键为示例名称,值为实例的具体信息
            gMap = registry.putIfAbsent(registrant.getAppName(), gNewMap);
            if (gMap == null) {
                gMap = gNewMap;
            }
        }
       //其它代码
}

  可以看到,最终服务端维护了一个ConcurrentHashMap,key为服务名值为该服务的实例组成的一个map,子map中key为实例名。

3、集群节点间信息同步

  eureka中没有主从的概念,所有节点都是平等的。其数据传播依赖于“对等机制”(peer2peer)。
  EurekaServerAutoConfiguration类中有一个上下文初始化的bean--eurekaServerContext,该bean的初始化方法如下:
@PostConstruct
@Override
public void initialize() throws Exception {
    logger.info("Initializing ...");
    peerEurekaNodes.start();  //服务启动,刷新peer集群列表并启动更新任务
    registry.init(peerEurekaNodes);  
    logger.info("Initialized");
}

  其中start方法主要是启动了一个线程,每隔一段时间执行一次updatePeerEurekaNodes(List<String> newPeerUrls)方法,该方法主要作用是更新集群节点信息,添加新节点,删除关闭的节点。

  在服务端完成注册之后,PeerAwareInstanceRegistryImpl的register方法中:
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();
    }
    super.register(info, leaseDuration, isReplication);
    replicateToPeers(Action.Register, info.getAppName(), info.getId(), info, null, isReplication); //复制注册信息到其它节点,isReplication用于防止复制自身,造成集群间节点相互复制
}

  当然,信息的同步过程,不止在注册发生,在服务的续约(renew),下架(cancle)均会进行同步。  

  续约:从服务提供者发起的定时任务,类似于心跳机制,隔一段时间调用一次服务端接口,告诉服务端它还活着,不要把它T了。
  下架:服务提供者自己shut down的时候,通知服务端把自己剔除,避免客户端拿到不可用的服务。
  剔除:服务端判断服务不可用,将服务从服务端删除。
4、服务的剔除及下架
  服务剔除是指eureka server认为服务不可用,进行剔除;下架指客户端关闭服务时,向服务端发送关闭请求。下架主要是client端调用DiscoveryClient的shutdown方法以及unregister方法。以下是服务剔除内容:
  EurekaServerInitializerConfiguration的start方法中有一行eurekaServerBootstrap.contextInitialized,其中方法内的initEurekaServerContext有registry.openForTraffic,具体代码实现者peerAwareInstanceRegistryImpl的openForTraffic最后一行super.postInit(),实现中new EvictionTask中的run中的evict(),,,,,,,终于到了剔除服务的代码了!
public void evict(long additionalLeaseMs) {
//其它代码
    // 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);
                }
            }
        }
    }
//其它代码
}
  isExpired代码:
public boolean isExpired(long additionalLeaseMs) {
    return (evictionTimestamp > 0 || System.currentTimeMillis() > (lastUpdateTimestamp + duration + additionalLeaseMs)); //最后更新时间+lease的存活时间+补偿时间< 当前时间
}
5、自我保护机制
  自我保护机制不会剔除服务。
  三个数值:
  期望值:正常每分钟心跳数量,本服务的实例数*2(因为默认30秒心跳一次)
  保护值:进入自我保护机制的数值,心跳数少于该数值则进入保护机制。期望值*阈值百分比
  阈值:进入保护机制的一个设置值
  源码在PeerAwareInstanceRegistryImpl#isLeaseExpirationEnabled
6、消费者获取信息
  实际就是客户端有个定时任务去服务端获取信息并进行缓存的过程;
  DiscoveryClient类的initScheduledTasks的new CacheRefreshThread---->refreshRegistry---->EurekaHttpClient类的getApplications:
  其中,refreshRegistry中友拉取服务信息的操作fetchRegistry,该操作会根据客户端的配置(DisableDelta参数)决定是拉取全量信息还是增量信息
private boolean fetchRegistry(boolean forceFullRegistryFetch) {
    Stopwatch tracer = FETCH_REGISTRY_TIMER.start();
    try {
        // If the delta is disabled or if it is the first time, get all  applications
        Applications applications = getApplications();
        if (clientConfig.shouldDisableDelta()  || (!Strings.isNullOrEmpty(clientConfig.getRegistryRefreshSingleVipAddress())) || forceFullRegistryFetch || (applications == null) || (applications.getRegisteredApplications().size() == 0)   || (applications.getVersion() == -1))  {//Client application does not have latest library supporting delta
            logger.info("Disable delta property : {}", clientConfig.shouldDisableDelta());
            logger.info("Single vip registry refresh property : {}", clientConfig.getRegistryRefreshSingleVipAddress());
            logger.info("Force full registry fetch : {}", forceFullRegistryFetch);
            logger.info("Application is null : {}", (applications == null));
            logger.info("Registered Applications size is zero : {}", (applications.getRegisteredApplications().size() == 0));
            logger.info("Application version is -1: {}", (applications.getVersion() == -1));
            getAndStoreFullRegistry();//全量数据
        } else {
            getAndUpdateDelta(applications);//增量数据
        }
        applications.setAppsHashCode(applications.getReconcileHashCode());
        logTotalInstances();
    } catch (Throwable e) {
        logger.error(PREFIX + appPathIdentifier + " - was unable to refresh its cache! status = " + e.getMessage(), e);
        return false;
    } finally {
        if (tracer != null) {
            tracer.stop();
        }
    }
    // Notify about cache refresh before updating the instance remote status
    onCacheRefreshed();
    // Update remote status based on refreshed data held in the cache
    updateInstanceRemoteStatus();
    // registry was fetched successfully, so return true
    return true;
}
7、心跳机制
   DiscoveryClient 内部类HeartBeatThread:
private class HeartbeatThread implements Runnable {
    public void run() {
        if (renew()) {//可以看到,就是调用了renew方法
            lastSuccessfulHeartbeatTimestamp = System.currentTimeMillis();
        }
    }
}

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

 
 

猜你喜欢

转载自www.cnblogs.com/nevermorewang/p/9219851.html