Eureka 源码分析(一) 全流程

423 阅读3分钟

写这篇文章的初衷是为了给sentinel社区提交一个关于动态规则源的pull request,如下图

这里的动态规则源的意思是指,在sentinel添加限流规则后,能实时推送到我们指定的注册中心。使注册中心上的服务实例及时注册相应的规则,根据DisCoverClient进行服务发现并启用规则

这里就需要对eureka的启动流程展开说明一下:

这里的InstanceInfo就是每个服务的实例,也是动态规则源需要监听的对象,从注释中可以看出,这个对象就是暴露给其他服务的

 * The class that holds information required for registration with
 * <tt>Eureka Server</tt> and to be discovered by other components.
 * <p>
 * <code>@Auto</code> annotated fields are serialized as is; Other fields are
 * serialized as specified by the <code>@Serializer</code>.
 * </p>
 *

Eureka-Client 发起注册

  • 应用实例信息复制器
// DiscoveryClient.java
public class DiscoveryClient implements EurekaClient {

    /**
     * 应用实例状态变更监听器
     */
    private ApplicationInfoManager.StatusChangeListener statusChangeListener;
    /**
     * 应用实例信息副本
     */
    private InstanceInfoReplicator instanceInfoReplicator;

    private void initScheduledTasks() {
        // ... 省略无关代码
        
        if (clientConfig.shouldRegisterWithEureka()) {
        
            // ... 省略无关代码
            
            // 创建 应用实例信息复制器
            // InstanceInfo replicator
            instanceInfoReplicator = new InstanceInfoReplicator(
                    this,
                    instanceInfo,
                    clientConfig.getInstanceInfoReplicationIntervalSeconds(),
                    2); // burstSize

            // 创建 应用实例状态变更监听器
            statusChangeListener = new ApplicationInfoManager.StatusChangeListener() {
                @Override
                public String getId() {
                    return "statusChangeListener";
                }

                @Override
                public void notify(StatusChangeEvent statusChangeEvent) {
                    if (InstanceStatus.DOWN == statusChangeEvent.getStatus() ||
                            InstanceStatus.DOWN == statusChangeEvent.getPreviousStatus()) {
                        // log at warn level if DOWN was involved
                        logger.warn("Saw local status change event {}", statusChangeEvent);
                    } else {
                        logger.info("Saw local status change event {}", statusChangeEvent);
                    }
                    instanceInfoReplicator.onDemandUpdate();
                }
            };

            // 注册 应用实例状态变更监听器
            if (clientConfig.shouldOnDemandUpdateStatusChange()) {
                applicationInfoManager.registerStatusChangeListener(statusChangeListener);
            }

            // 开启 应用实例信息复制器
            instanceInfoReplicator.start(clientConfig.getInitialInstanceInfoReplicationIntervalSeconds());
        }
    
    }

}

其中InstanceInfoReplicator就是将这个实例复制一个副本。为了避免线程安全问题,用于在一个线程的run方法内复制执行

代码如下:

// InstanceInfoReplicator.java
class InstanceInfoReplicator implements Runnable {

    private static final Logger logger = LoggerFactory.getLogger(InstanceInfoReplicator.class);

    private final DiscoveryClient discoveryClient;
    /**
     * 应用实例信息
     */
    private final InstanceInfo instanceInfo;
    /**
     * 定时执行频率,单位:秒
     */
    private final int replicationIntervalSeconds;
    /**
     * 定时执行器
     */
    private final ScheduledExecutorService scheduler;
    /**
     * 定时执行任务的 Future
     */
    private final AtomicReference<Future> scheduledPeriodicRef;
    /**
     * 是否开启调度
     */
    private final AtomicBoolean started;

    private final RateLimiter rateLimiter; // 限流相关,跳过
    private final int burstSize; // 限流相关,跳过
    private final int allowedRatePerMinute; // 限流相关,跳过

    InstanceInfoReplicator(DiscoveryClient discoveryClient, InstanceInfo instanceInfo, int replicationIntervalSeconds, int burstSize) {
        this.discoveryClient = discoveryClient;
        this.instanceInfo = instanceInfo;
        this.scheduler = Executors.newScheduledThreadPool(1,
                new ThreadFactoryBuilder()
                        .setNameFormat("DiscoveryClient-InstanceInfoReplicator-%d")
                        .setDaemon(true)
                        .build());

        this.scheduledPeriodicRef = new AtomicReference<Future>();

        this.started = new AtomicBoolean(false);
        this.rateLimiter = new RateLimiter(TimeUnit.MINUTES);
        this.replicationIntervalSeconds = replicationIntervalSeconds;
        this.burstSize = burstSize;

        this.allowedRatePerMinute = 60 * this.burstSize / this.replicationIntervalSeconds;
        logger.info("InstanceInfoReplicator onDemand update allowed rate per min is {}", allowedRatePerMinute);
    }

    public void start(int initialDelayMs) {
        if (started.compareAndSet(false, true)) {
            // 设置 应用实例信息 数据不一致
            instanceInfo.setIsDirty();  // for initial register
            // 提交任务,并设置该任务的 Future
            Future next = scheduler.schedule(this, initialDelayMs, TimeUnit.SECONDS);
            scheduledPeriodicRef.set(next);
        }
    }
    
    // ... 省略无关方法
}

// InstanceInfo.java
private volatile boolean isInstanceInfoDirty = false;
private volatile Long lastDirtyTimestamp;

public synchronized void setIsDirty() {
   isInstanceInfoDirty = true;
   lastDirtyTimestamp = System.currentTimeMillis();
}

刷新应用实例信息

实现代码如下:

void refreshInstanceInfo() {
   // 刷新 数据中心信息
   applicationInfoManager.refreshDataCenterInfoIfRequired();
   // 刷新 租约信息
   applicationInfoManager.refreshLeaseInfoIfRequired();
   // 健康检查
   InstanceStatus status;
   try {
       status = getHealthCheckHandler().getStatus(instanceInfo.getStatus());
   } catch (Exception e) {
       logger.warn("Exception from healthcheckHandler.getStatus, setting status to DOWN", e);
       status = InstanceStatus.DOWN;
   }
   if (null != status) {
       applicationInfoManager.setInstanceStatus(status);
   }
}

这里的refreshxxx的方法,就是用来更新实例信息用的。到此为止,服务实例更新完了

发起注册应用实例 实现代码如下:

// DiscoveryClient.java
boolean register() throws Throwable {
   logger.info(PREFIX + appPathIdentifier + ": registering service...");
   EurekaHttpResponse<Void> httpResponse;
   try {
       httpResponse = eurekaTransport.registrationClient.register(instanceInfo);
   } catch (Exception e) {
       logger.warn("{} - registration failed {}", PREFIX + appPathIdentifier, e.getMessage(), e);
       throw e;
   }
   if (logger.isInfoEnabled()) {
       logger.info("{} - registration status: {}", PREFIX + appPathIdentifier, httpResponse.getStatusCode());
   }
   return httpResponse.getStatusCode() == 204;
}

// AbstractJerseyEurekaHttpClient.java
@Override
public EurekaHttpResponse<Void> register(InstanceInfo info) {
   String urlPath = "apps/" + info.getAppName();
   ClientResponse response = null;
   try {
       Builder resourceBuilder = jerseyClient.resource(serviceUrl).path(urlPath).getRequestBuilder();
       addExtraHeaders(resourceBuilder);
       response = resourceBuilder
               .header("Accept-Encoding", "gzip")
               .type(MediaType.APPLICATION_JSON_TYPE)
               .accept(MediaType.APPLICATION_JSON)
               .post(ClientResponse.class, info);
       return anEurekaHttpResponse(response.getStatus()).headers(headersOf(response)).build();
   } finally {
       if (logger.isDebugEnabled()) {
           logger.debug("Jersey HTTP POST {}/{} with instance {}; statusCode={}", serviceUrl, urlPath, info.getId(),
                   response == null ? "N/A" : response.getStatus());
       }
       if (response != null) {
           response.close();
       }
   }
}

可以看到作为注册信息的监听者是ApplicationManager,被监听的服务实例是InstanceConfig。这样就先确立了观察者与被观察者的关系