我们在看流程前看下下面的一个类图,其中client的流程基本都在DiscoveryClient的构造方法中,而eureka-server的逻辑大部分在PeerAwareInstanceRegistryImpl和PeerEurekaNodes中。

服务在eureka注册中心的名字设置 eureka服务端启动_应用实例


这里再介绍下InstanceInfo和EurekClient的区别, 主要用于标示这个应用的实例信息,如应用名称、consumer(provider)等,而EurekClient就是一个客户端的信息,如地址、注册信息等

那么eureka-client启动之前,我们先理一下它应该需要做那些事情。
1、告诉eureka-server(注册)。
2、拉取eureka-server上面的注册信息缓存到本地,实现高可用。
3、同步client的状态给eureka-server(心跳等)。
一、首先介绍下client向eureka-server注册的实现:
那么我们先看eureka-client的注册是怎么实现的,并不是直接注册的,而是通过后台线程是实现的,看下代码:

//DiscoveryClient.java
 @Inject
    DiscoveryClient(ApplicationInfoManager applicationInfoManager, EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs args,
                    Provider<BackupRegistry> backupRegistryProvider) {
            //代码省略...
            // default size of 2 - 1 each for heartbeat and cacheRefresh
            scheduler = Executors.newScheduledThreadPool(2,
                    new ThreadFactoryBuilder()
                            .setNameFormat("DiscoveryClient-%d")
                            .setDaemon(true)
                            .build());
        //代码省略...
        // finally, init the schedule tasks (e.g. cluster resolvers, heartbeat, instanceInfo replicator, fetch
        initScheduledTasks();
        //代码省略...
    }
private void initScheduledTasks() {
        if (clientConfig.shouldFetchRegistry()) {
            //省略代码...
            // InstanceInfo replicator创建 应用实例信息复制器
            instanceInfoReplicator = new InstanceInfoReplicator(
                    this,
                    instanceInfo,
                    clientConfig.getInstanceInfoReplicationIntervalSeconds(),
                    2); // burstSize

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

在看下instanceInfoReplicator是如何实现注册的,继续看它的start方法:

public void start(int initialDelayMs) {
        //
        if (started.compareAndSet(false, true)) {
            instanceInfo.setIsDirty();  // for initial register
            //定时任务
            Future next = scheduler.schedule(this, initialDelayMs, TimeUnit.SECONDS);
            //onDemandUpdate方法使用
            scheduledPeriodicRef.set(next);
        }
    }

看它的run方法:

public void run() {
        try {
            //刷新实例信息
            discoveryClient.refreshInstanceInfo();
            Long dirtyTimestamp = instanceInfo.isDirtyWithTime();
            //是否已经注册过,
            if (dirtyTimestamp != null) {
                discoveryClient.register();
                instanceInfo.unsetIsDirty(dirtyTimestamp);
            }
        } catch (Throwable t) {
            logger.warn("There was a problem with the instance info replicator", t);
        } finally {
            //schedule
            Future next = scheduler.schedule(this, replicationIntervalSeconds, TimeUnit.SECONDS);
            scheduledPeriodicRef.set(next);
        }
    }

到这里终于看到register方法了,不过在register之前有个refreshInstanceInfo方法,是用于刷新应用实例信息,跟进代码后我们发现了这些应用实例信息的 hostName 、 ipAddr等的属性的变化。因为IP变了后服务是无法知晓的,只能这个定时任务是随时变化。
下面我们看register方法,代码如下:

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

无非就是调用 AbstractJerseyEurekaHttpClient#register(...) 方法,请求 Eureka-Server 的 apps/${APP_NAME} 接口,参数为 InstanceInfo ,实现注册实例信息的注册。
补上一点,当refreshInstanceInfo方法刷到status变化时会被唤醒notify(),然后调用onDemandUpdate()告诉server修改对应的client信息。

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);
                    }
                    //状态变化,通过server
                    instanceInfoReplicator.onDemandUpdate();
                }
            };
public boolean onDemandUpdate() {
        if (rateLimiter.acquire(burstSize, allowedRatePerMinute)) {
            if (!scheduler.isShutdown()) {
                scheduler.submit(new Runnable() {
                    @Override
                    public void run() {
                        Future latestPeriodic = scheduledPeriodicRef.get();
                        if (latestPeriodic != null && !latestPeriodic.isDone()) {
                            //取消任务
                            latestPeriodic.cancel(false);
                        }
                        //再次拉起
                        InstanceInfoReplicator.this.run();
                    }
                });
                return true;
            //代码省略。。。
    }

二、下面我们看下server端是如何接受这个注册请求的,请求的路径是:"apps/" + info.getAppName(),也就是apps/${APP_NAME}。
是在com.netflix.eureka.resources.ApplicationResource中

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
        //省略验证的代码。。。
        registry.register(info, "true".equals(isReplication));
        return Response.status(204).build();  // 204 to be backwards compatible
    }
@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();
        }
        //注册isReplication用来区分是eureka-server节点同步还是直接注册的
        super.register(info, leaseDuration, isReplication);
        //同步到对应的节点
        replicateToPeers(Action.Register, info.getAppName(), info.getId(), info, null, isReplication);
    }

最终进入了AbstractInstanceRegistry中的registry的Map中,
private final ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>> registry = new ConcurrentHashMap<>();
不过其中使用了Lease(契约),非常nice(有兴趣可以自行了解下~),心跳、下线后续会补充上来。