Spring Cloud Eureka(二):Client源码

入口

上一篇搭建的 Spring Cloud Client 客户端项目中,只是在配置文件中加了一点配置,Eureka 客户端就成功启动了,那么客户端是如何加入 Spring 的呢?
我们知道如果想注册一些Bean到Spring中,但是这些Bean又不在Spring的扫描路径下,那就只剩下两种方案:

  1. 使用 @Configuration 或 @Import
  2. 使用SpringBoot SPI机制,配置到spring.factories文件中

找到 spring-cloud-netflix-eureka-client.jarMETA-INF/spring.factories 文件

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.netflix.eureka.config.EurekaClientConfigServerAutoConfiguration,\
org.springframework.cloud.netflix.eureka.config.DiscoveryClientOptionalArgsConfiguration,\
org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration,\
org.springframework.cloud.netflix.ribbon.eureka.RibbonEurekaAutoConfiguration,\
org.springframework.cloud.netflix.eureka.EurekaDiscoveryClientConfiguration,\
org.springframework.cloud.netflix.eureka.reactive.EurekaReactiveDiscoveryClientConfiguration,\
org.springframework.cloud.netflix.eureka.loadbalancer.LoadBalancerEurekaAutoConfiguration

org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.netflix.eureka.config.EurekaConfigServerBootstrapConfiguration

这些配置类注册了一系列 Eureka 需要的 bean,这也是我们寻找一些重要的类的入手处。

EurekaDiscoveryClient

客户端最重要的就是从服务端发现服务,Spring Cloud 定义了一个用于服务发现的顶级接口org.springframework.cloud.client.discovery.DiscoveryClient

// 通用服务发现的操作
public interface DiscoveryClient extends Ordered {

	// 省略 Ordered 接口方法...

	// 实现类的描述
	String description();

	// 通过服务id查询服务实例信息列表
	List<ServiceInstance> getInstances(String serviceId);

	// 获取所有服务实例id
	List<String> getServices();
}

Eureka 也有对应的实现类,在spring.factories文件中的配置类EurekaDiscoveryClientConfiguration里注册

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties
@ConditionalOnClass(EurekaClientConfig.class)
@ConditionalOnProperty(value = "eureka.client.enabled", matchIfMissing = true)
@ConditionalOnDiscoveryEnabled
@ConditionalOnBlockingDiscoveryEnabled
public class EurekaDiscoveryClientConfiguration {
	// 省略其他配置...
	
	@Bean
	@ConditionalOnMissingBean
	public EurekaDiscoveryClient discoveryClient(EurekaClient client,
			EurekaClientConfig clientConfig) {
		return new EurekaDiscoveryClient(client, clientConfig);
	}
}

注册该类需要另外两个 bean EurekaClientEurekaClientConfig

EurekaClientConfig

// 接口,指定了一个默认的实现
@ImplementedBy(DefaultEurekaClientConfig.class)
public interface EurekaClientConfig {...}

// Eureka 自己的实现类,将配置文件中的配置项保存在这个类
@ConfigurationProperties(EurekaClientConfigBean.PREFIX)
public class EurekaClientConfigBean implements EurekaClientConfig, Ordered {

	// 配置文件中配置项的前缀: eureka.client
	public static final String PREFIX = "eureka.client";

	// 省略其他...
}

该类在spring.factories文件中EurekaClientAutoConfiguration里注册

// 省略类上注解
public class EurekaClientAutoConfiguration {
	// 省略其他配置...
	
	@Bean
	@ConditionalOnMissingBean(value = EurekaClientConfig.class,
			search = SearchStrategy.CURRENT)
	public EurekaClientConfigBean eurekaClientConfigBean(ConfigurableEnvironment env) {
		return new EurekaClientConfigBean();
	}
}

EurekaClient

在这里插入图片描述
EurekaClient最底层的实现类是CloudEurekaClient,该类只是在方法onCacheRefreshed被调用时发送一个心跳事件,主要的工作还是com.netflix.discovery.DiscoveryClient完成。

public class DiscoveryClient implements EurekaClient {
	// 省略其他方法及方法体

	// 服务注册
	boolean register();

	// 服务续约
	boolean renew();
	
	// 服务下线,来自 EurekaClient 接口
	synchronized void shutdown();

	// 以下来自 LookupService 接口
	
	// 查询相同appName的服务实例信息
	Application getApplication(String appName);

	// 查找所有服务实例信息
	Applications getApplications();
	
	// 根据实例id获取实例信息
	List<InstanceInfo> getInstancesById(String id);
}

下面重点来看DiscoveryClient

DiscoveryClient

先看构造函数

DiscoveryClient(ApplicationInfoManager applicationInfoManager, EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs args,
                    Provider<BackupRegistry> backupRegistryProvider, EndpointRandomizer endpointRandomizer) {
        // 一些初始化工作,省略
       
		// 配置项 eureka.client.region
        logger.info("Initializing Eureka in region {}", clientConfig.getRegion());

		// 不拉取 && 不注册
        if (!config.shouldRegisterWithEureka() && !config.shouldFetchRegistry()) {
            // 不启动任何任务, DiscoveryClient 初始化完成
            // 省略代码...
            return;  
        }

        try {
            // 定时器线程池
            scheduler = Executors.newScheduledThreadPool(2,
                    new ThreadFactoryBuilder()
                            .setNameFormat("DiscoveryClient-%d")
                            .setDaemon(true)
                            .build());
			
			// 定时心跳
            heartbeatExecutor = new ThreadPoolExecutor(
                    1, clientConfig.getHeartbeatExecutorThreadPoolSize(), 0, TimeUnit.SECONDS,
                    new SynchronousQueue<Runnable>(),
                    new ThreadFactoryBuilder()
                            .setNameFormat("DiscoveryClient-HeartbeatExecutor-%d")
                            .setDaemon(true)
                            .build()
            );  // use direct handoff

			// 缓存刷新
            cacheRefreshExecutor = new ThreadPoolExecutor(
                    1, clientConfig.getCacheRefreshExecutorThreadPoolSize(), 0, TimeUnit.SECONDS,
                    new SynchronousQueue<Runnable>(),
                    new ThreadFactoryBuilder()
                            .setNameFormat("DiscoveryClient-CacheRefreshExecutor-%d")
                            .setDaemon(true)
                            .build()
            ); 

            // 省略部分代码...
		}
		
		// 拉取注册表
        if (clientConfig.shouldFetchRegistry()) {
            try {
            	// 主服务器拉取,false:增量拉取
                boolean primaryFetchRegistryResult = fetchRegistry(false);
                if (!primaryFetchRegistryResult) {
                    logger.info("Initial registry fetch from primary servers failed");
                }
                // 主服务器拉取失败,从备份服务器拉取
                boolean backupFetchRegistryResult = true;
                if (!primaryFetchRegistryResult && !fetchRegistryFromBackup()) {
                    backupFetchRegistryResult = false;
                    logger.info("Initial registry fetch from backup servers failed");
                }
                // 两次都失败 && 初始化时强制拉取注册表,则初始化失败
                // 配置项:eureka.cletn.should-enforce-registration-at-init
                if (!primaryFetchRegistryResult && !backupFetchRegistryResult && clientConfig.shouldEnforceFetchRegistryAtInit()) {
                    throw new IllegalStateException("Fetch registry error at startup. Initial fetch failed.");
                }
            } catch (Throwable th) {
                logger.error("Fetch registry error at startup: {}", th.getMessage());
                throw new IllegalStateException(th);
            }
        }

		// 注册到 Eureka Server && 初始化时强制注册
        if (clientConfig.shouldRegisterWithEureka() && clientConfig.shouldEnforceRegistrationAtInit()) {
            try {
	            // 向 Eureka Server 注册自己
                if (!register() ) {
                    throw new IllegalStateException("Registration error at startup. Invalid server response.");
                }
            } catch (Throwable th) {
                logger.error("Registration error at startup: {}", th.getMessage());
                throw new IllegalStateException(th);
            }
        }

        // 初始化定时任务:服务心跳,服务拉取,服务信息实例发送
        initScheduledTasks();

        // 省略部分代码...
    }

构造函数小结:

  1. 初始化部分信息
  2. 拉取注册表
  3. 注册自己
  4. 初始化定时器:服务心跳,服务拉取,服务实例信息发送

fentchRegistry:拉取注册表

private boolean fetchRegistry(boolean forceFullRegistryFetch) {
        Stopwatch tracer = FETCH_REGISTRY_TIMER.start();

        try {
            // 获取现有的注册表
            Applications applications = getApplications();

            if (clientConfig.shouldDisableDelta() // 增量获取禁用 eureka.clent.disable-delta,默认false
					// 配置了 eureka.client.registry-refresh-single-vip-address
                    || (!Strings.isNullOrEmpty(clientConfig.getRegistryRefreshSingleVipAddress()))
                    || forceFullRegistryFetch
                    // 现有注册表为null
                    || (applications == null)
                    // 现有注册表不为null但是为空
                    || (applications.getRegisteredApplications().size() == 0)
                    // 客户端不支持增量拉取
                    || (applications.getVersion() == -1))
            {
            	// 全量获取
                getAndStoreFullRegistry();
            } else {
            	// 增量获取
                getAndUpdateDelta(applications);
            }
            applications.setAppsHashCode(applications.getReconcileHashCode());
            logTotalInstances();
        } catch (Throwable e) {
            logger.info(PREFIX + "{} - was unable to refresh its cache! This periodic background refresh will be retried in {} seconds. status = {} stacktrace = {}",
                    appPathIdentifier, clientConfig.getRegistryFetchIntervalSeconds(), e.getMessage(), ExceptionUtils.getStackTrace(e));
            return false;
        } finally {
            if (tracer != null) {
                tracer.stop();
            }
        }

        // 发送注册表刷新事件
        onCacheRefreshed();

        // 更新此实例的状态到服务端
        updateInstanceRemoteStatus();

        // 拉取成功
        return true;
    }
    
    @Override
    public Applications getApplications() {
        return localRegionApps.get();
    }

getAndStoreFullRegistry:全量注册表

	// 注册表版本计数器,防止多线程环境下注册表更新为旧版本
	private final AtomicLong fetchRegistryGeneration;
	
    private void getAndStoreFullRegistry() throws Throwable {
    	// 注册表当前版本
        long currentUpdateGeneration = fetchRegistryGeneration.get();

        logger.info("Getting all instance registry info from the eureka server");

        Applications apps = null;
        // eurekaTransport.queryClient: AbstractJerseyEurekaHttpClient
        // 配置了 eureka.client.registry-refresh-single-vip-address,则从该配置地址取
		// remoteRegionsRef.get() 取的是 eureka.client.fetch-remote-regions-registry 配置项
        EurekaHttpResponse<Applications> httpResponse = clientConfig.getRegistryRefreshSingleVipAddress() == null
                ? eurekaTransport.queryClient.getApplications(remoteRegionsRef.get())
                : eurekaTransport.queryClient.getVip(clientConfig.getRegistryRefreshSingleVipAddress(), remoteRegionsRef.get());
        if (httpResponse.getStatusCode() == Status.OK.getStatusCode()) {
            apps = httpResponse.getEntity();
        }
        logger.info("The response status is {}", httpResponse.getStatusCode());

        if (apps == null) {
        	// 未拉取到注册表
            logger.error("The application is null for some reason. Not storing this information");
        } else if (fetchRegistryGeneration.compareAndSet(currentUpdateGeneration, currentUpdateGeneration + 1)) {
        	// 拉取到注册表,且当前版本一致,则版本+1
        	// 过滤(只保留状态为 UP 的)并 打乱注册表
            localRegionApps.set(this.filterAndShuffle(apps));
            logger.debug("Got full registry with apps hashcode {}", apps.getAppsHashCode());
        } else {
        	// 有其他线程已经更新了且版本更高
            logger.warn("Not updating applications as another thread is updating it already");
        }
    }

AbstractJerseyEurekaHttpClient

public abstract class AbstractJerseyEurekaHttpClient implements EurekaHttpClient {

	@Override
    public EurekaHttpResponse<Applications> getApplications(String... regions) {
        return getApplicationsInternal("apps/", regions);
    }
    
    @Override
    public EurekaHttpResponse<Applications> getVip(String vipAddress, String... regions) {
        return getApplicationsInternal("vips/" + vipAddress, regions);
    }
  
    @Override
    public EurekaHttpResponse<Applications> getDelta(String... regions) {
        return getApplicationsInternal("apps/delta", regions);
    }
    
	private EurekaHttpResponse<Applications> getApplicationsInternal(String urlPath, String[] regions) {
		// 简化代码
		// 发送 GET 请求,Jersey HTTP GET serviceUrl/urlPath?regions=
    }
}

getAndUpdateDelta:增量注册表


private final Lock fetchRegistryUpdateLock = new ReentrantLock();

private void getAndUpdateDelta(Applications applications) throws Throwable {
		// 当前版本
        long currentUpdateGeneration = fetchRegistryGeneration.get();

		// 发送 HTTP 请求获取增量信息
        Applications delta = null;
        EurekaHttpResponse<Applications> httpResponse = eurekaTransport.queryClient.getDelta(remoteRegionsRef.get());
        if (httpResponse.getStatusCode() == Status.OK.getStatusCode()) {
            delta = httpResponse.getEntity();
        }

        if (delta == null) {
            // 服务端不允许增量拉取,则全量拉取
            getAndStoreFullRegistry();
        } else if (fetchRegistryGeneration.compareAndSet(currentUpdateGeneration, currentUpdateGeneration + 1)) {
	        // 拉取到注册表,且当前版本一致,则版本+1
            logger.debug("Got delta update with apps hashcode {}", delta.getAppsHashCode());
            String reconcileHashCode = "";
            if (fetchRegistryUpdateLock.tryLock()) {
            	// 拿到锁
                try {
                	// 更新增量
                    updateDelta(delta);
                    reconcileHashCode = getReconcileHashCode(applications);
                } finally {
                	// 释放锁
                    fetchRegistryUpdateLock.unlock();
                }
            } else {
            	// 没拿到锁则放弃此次更新
                logger.warn("Cannot acquire update lock, aborting getAndUpdateDelta");
            }

            if (!reconcileHashCode.equals(delta.getAppsHashCode()) || clientConfig.shouldLogDeltaDiff()) {
                // 由于一些原因导致客户端和服务端注册信息不一致,则再次全量拉取
                reconcileAndLogDifference(delta, reconcileHashCode);
            }
        } else {
	        // 有其他线程已经更新了且版本更高
            logger.warn("Not updating application delta as another thread is updating it already");
            logger.debug("Ignoring delta update with apps hashcode {}, as another thread is updating it already", delta.getAppsHashCode());
        }
    }

	private void updateDelta(Applications delta) {
        for (Application app : delta.getRegisteredApplications()) {
            for (InstanceInfo instance : app.getInstances()) {
            
                Applications applications = getApplications();
                
                // 省略部分代码...
   
                // 新增的服务实例
                if (ActionType.ADDED.equals(instance.getActionType())) {
                    Application existingApp = applications.getRegisteredApplications(instance.getAppName());
                    if (existingApp == null) {
                        applications.addApplication(app);
                    }
                    logger.debug("Added instance {} to the existing apps in region {}", instance.getId(), instanceRegion);
                    applications.getRegisteredApplications(instance.getAppName()).addInstance(instance);
                }
                
				// 变更的服务实例
				 else if (ActionType.MODIFIED.equals(instance.getActionType())) {
                	
                    Application existingApp = applications.getRegisteredApplications(instance.getAppName());
                    if (existingApp == null) {
                        applications.addApplication(app);
                    }
                    logger.debug("Modified instance {} to the existing apps ", instance.getId());
					applications.getRegisteredApplications(instance.getAppName()).addInstance(instance);
                }
                
                // 删除的服务实例
                else if (ActionType.DELETED.equals(instance.getActionType())) {
                    Application existingApp = applications.getRegisteredApplications(instance.getAppName());
                    if (existingApp != null) {
                        logger.debug("Deleted instance {} to the existing apps ", instance.getId());
                        existingApp.removeInstance(instance);
                         // 如果实例列表为空则删除该服务
                        if (existingApp.getInstancesAsIsFromEureka().isEmpty()) {
                            applications.removeApplication(existingApp);
                        }
                    }
                }
            }
        }
		// 省略代码,设置版本,打乱实例顺序
    }

register:注册自己

    boolean register() throws Throwable {
    	// 简化代码
    	// instanceInfo 当前实例信息
		eurekaTransport.registrationClient.register(instanceInfo);
    }
	
	
public abstract class AbstractJerseyEurekaHttpClient implements EurekaHttpClient {

	@Override
    public EurekaHttpResponse<Void> register(InstanceInfo info) {
		// 简化代码
        String urlPath = "apps/" + info.getAppName();
        // 发送请求 Jersey HTTP POST 
        // url: serviceUrl/urlPath
        // body: info, contentType: application/json
	}
}

initScheduledTasks:初始化定时器

private void initScheduledTasks() {
        if (clientConfig.shouldFetchRegistry()) {
        
            // 注册表拉取定时器
            
            // 拉取注册表的频率, eureka.client.registry-fetch-interval-seconds,默认30s
            int registryFetchIntervalSeconds = clientConfig.getRegistryFetchIntervalSeconds();
            int expBackOffBound = clientConfig.getCacheRefreshExecutorExponentialBackOffBound();
            cacheRefreshTask = new TimedSupervisorTask(
                    "cacheRefresh",
                    scheduler,
                    cacheRefreshExecutor,
                    registryFetchIntervalSeconds,
                    TimeUnit.SECONDS,
                    expBackOffBound,
                    // 拉取注册表线程
                    new CacheRefreshThread()
            );
            scheduler.schedule(cacheRefreshTask, registryFetchIntervalSeconds, TimeUnit.SECONDS);
        }

        if (clientConfig.shouldRegisterWithEureka()) {
			
			// 心跳定时器
			
			// 心跳发送间隔,eureka.instance.lease-renewal-interval-in-seconds: 默认30s
            int renewalIntervalInSecs = instanceInfo.getLeaseInfo().getRenewalIntervalInSecs();
            int expBackOffBound = clientConfig.getHeartbeatExecutorExponentialBackOffBound();
            logger.info("Starting heartbeat executor: " + "renew interval is: {}", renewalIntervalInSecs);

            heartbeatTask = new TimedSupervisorTask(
                    "heartbeat",
                    scheduler,
                    heartbeatExecutor,
                    renewalIntervalInSecs,
                    TimeUnit.SECONDS,
                    expBackOffBound,
                    // 心跳线程
                    new HeartbeatThread()
            );
            scheduler.schedule(heartbeatTask,renewalIntervalInSecs, TimeUnit.SECONDS);

            // 服务实例信息刷新,有变化则向 server 注册
            instanceInfoReplicator = new InstanceInfoReplicator(
                    this,
                    instanceInfo,
                    // eureka.client.instance-info-replication-interval-seconds,默认30s
                    clientConfig.getInstanceInfoReplicationIntervalSeconds(),
                    2);
                    
			// 状态监听,状态改变则触发 instanceInfoReplicator 执行
            statusChangeListener = new ApplicationInfoManager.StatusChangeListener() {
                @Override
                public String getId() {
                    return "statusChangeListener";
                }

                @Override
                public void notify(StatusChangeEvent statusChangeEvent) {
                    if (statusChangeEvent.getStatus() == InstanceStatus.DOWN) {
                        logger.error("Saw local status change event {}", statusChangeEvent);
                    } else {
                        logger.info("Saw local status change event {}", statusChangeEvent);
                    }
                    instanceInfoReplicator.onDemandUpdate();
                }
            };
			// eureka.client.on-demand-update-status-change,默认true
            if (clientConfig.shouldOnDemandUpdateStatusChange()) {
                applicationInfoManager.registerStatusChangeListener(statusChangeListener);
            }

            instanceInfoReplicator.start(clientConfig.getInitialInstanceInfoReplicationIntervalSeconds());
        } else {
            logger.info("Not registering with Eureka server per configuration");
        }
    }

CacheRefreshThread:拉取注册表线程

// DiscoveryClient 内部类
class CacheRefreshThread implements Runnable {
        public void run() {
            refreshRegistry();
        }
}

    @VisibleForTesting
    void refreshRegistry() {
        try {
            // 省略代码,region处理
            // 拉取注册表
            boolean success = fetchRegistry(remoteRegionsModified);
        } catch (Throwable e) {
            logger.error("Cannot fetch registry from server", e);
        }
    }

HeartbeatThread:心跳线程

	// DiscoveryClient 内部类
    private class HeartbeatThread implements Runnable {

        public void run() {
            if (renew()) {
                lastSuccessfulHeartbeatTimestamp = System.currentTimeMillis();
            }
        }
    }
    
    // 续约
    boolean renew() {
    	// 简化部分代码
        EurekaHttpResponse<InstanceInfo> httpResponse;
        try {
            httpResponse = eurekaTransport.registrationClient.sendHeartBeat(instanceInfo.getAppName(), instanceInfo.getId(), instanceInfo, null);
            // 服务端返回404,表示没有此实例,则重新发起注册
            if (httpResponse.getStatusCode() == Status.NOT_FOUND.getStatusCode()) {
                long timestamp = instanceInfo.setIsDirtyWithTime();
                // 重新注册自身
                boolean success = register();
                return success;
            }
            return httpResponse.getStatusCode() == Status.OK.getStatusCode();
        } catch (Throwable e) {
            logger.error(PREFIX + "{} - was unable to send heartbeat!", appPathIdentifier, e);
            return false;
        }
    }

	// AbstractJerseyEurekaHttpClient#sendHeartBeat
	public EurekaHttpResponse<InstanceInfo> sendHeartBeat(String appName, String id, InstanceInfo info, InstanceStatus overriddenStatus) {
		// 简化代码
        String urlPath = "apps/" + appName + '/' + id;
       	// 发送请求 Jersey HTTP PUT
        // url: serviceUrl/urlPath
        // query param: status = info.getStatus()
        // query param: lastDirtyTimestamp = info.getLastDirtyTimestamp()
    }

InstanceInfoReplicator:服务实例信息刷新线程

class InstanceInfoReplicator implements Runnable {
    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 {
        	// 休眠 replicationIntervalSeconds 秒 再次执行
            Future next = scheduler.schedule(this, replicationIntervalSeconds, TimeUnit.SECONDS);
            scheduledPeriodicRef.set(next);
        }
    }
}

定时器小结

  1. 拉取注册表,频率由 eureka.client.registry-fetch-interval-seconds 决定,默认30s
  2. 心跳续约,频率由 eureka.instance.lease-renewal-interval-in-seconds 决定,默认30s
  3. 服务实例信息刷新,频率由 eureka.client.instance-info-replication-interval-seconds 决定,默认30s
  4. 状态监听器,监听状态变化的事件,触发服务实例信息刷新执行,属于服务实例信息刷新的被动表现,是否启用由 eureka.client.on-demand-update-status-change 决定,默认true

shutdown:服务下线

    @PreDestroy
    @Override
    public synchronized void shutdown() {
        if (isShutdown.compareAndSet(false, true)) {
            logger.info("Shutting down DiscoveryClient ...");

			// 删除状态监听
            if (statusChangeListener != null && applicationInfoManager != null) {
                applicationInfoManager.unregisterStatusChangeListener(statusChangeListener.getId());
            }

			// 关闭定时任务
            cancelScheduledTasks();

            if (applicationInfoManager != null
                    && clientConfig.shouldRegisterWithEureka()
                    && clientConfig.shouldUnregisterOnShutdown()) {
                    
                // 给服务端发送下线状态
                applicationInfoManager.setInstanceStatus(InstanceStatus.DOWN);
                unregister();
            }

            // 省略代码,关闭一些东西

            logger.info("Completed shut down of DiscoveryClient");
        }
    }

    void unregister() {
    	// 简化代码
    	eurekaTransport.registrationClient.cancel(instanceInfo.getAppName(), instanceInfo.getId());
    }

public abstract class AbstractJerseyEurekaHttpClient implements EurekaHttpClient {

    @Override
    public EurekaHttpResponse<Void> cancel(String appName, String id) {
    	// 简化代码
        String urlPath = "apps/" + appName + '/' + id;
        // 发送请求 Jersey HTTP DELETE
        // url: serviceUrl/urlPath
    }
}

总结

这一篇从DiscoveryClient的构造函数开始,浏览了一遍 Eureka 客户端的工作流程,包括拉取注册表,注册自身,心跳续约等任务。也看到了一些配置项生效的源码,以及和服务端通信的 HTTP 接口路径。看完了这些源码,对 Eureka 客户端的实现原理也有了一定的认识。如果有兴趣,完全可以自己去实现一个其他语言的客户端。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值