是什么
Eureka是一个REST (Representational State Transfer)服务,用于定位服务,以实现中间层服务器的负载平衡和故障转移,我们称此服务为Eureka
服务器。Eureka
还有一个基于java
的客户端组件,Eureka客户端,这使得与服务的交互更加容易,同时客户端也有一个内置的负载平衡器,它执行基本的循环负载均衡。
Feign 是一种声明式服务调用组件,它在 RestTemplate
的基础上做了进一步的封装。通过 Feign,我们只需要声明一个接口并通过注解进行简单的配置(类似于 Dao
接口上面的 Mapper
注解一样)即可实现对 HTTP
接口的绑定。
通过 Feign
,我们可以像调用本地方法一样来调用远程服务,而完全感觉不到这是在进行远程调用。
为什么
eureka
解决了什么问题?为什么要用eureka
?
eureka
为分布式环境引入了服务注册与发现的能力,如果没有服务注册与发现,我们便需要手动去维护一大堆服务的地址信息,而当服务的状态变化发生变化,比如有新的服务加入或某些现有服务突然不可用时,现存的服务也无法感知到并及时切换到可用的服务上去。
相对其他提供服务注册与发现的组件(比如说zk
),Eureka
更偏向于AP。如果分布式系统对可用性的要求更高,那么,Eureka
会是一个不错的选择。
另外,feign
屏蔽了请求构建、发送、重试、负载均衡等相关细节,让我们能够从重复的工作中解放出来,更专注于业务本身。
架构图
案例
eureka server
引入依赖
implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-server'
启动类
@EnableEurekaServer
@SpringBootApplication
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
配置文件
server.port=8761
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false
service consumer
依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
启动类
@SpringBootApplication
@EnableDiscoveryClient
@RestController
@EnableFeignClients
public class HelloClientApplication {
@Autowired
HelloClient client;
@RequestMapping("/")
public String hello() {
return client.hello();
}
public static void main(String[] args) {
SpringApplication.run(HelloClientApplication.class, args);
}
@FeignClient("HelloServer")
interface HelloClient {
@RequestMapping(value = "/", method = GET)
String hello();
}
}
配置文件
spring:
application:
name: HelloClient
server:
port: 7211
eureka:
password: password
client:
serviceUrl:
defaultZone: http://user:${eureka.password}@localhost:8761/eureka/
instance:
leaseRenewalIntervalInSeconds: 10
metadataMap:
instanceId: ${vcap.application.instance_id:${spring.application.name}:${spring.application.instance_id:${server.port}}}
endpoints:
restart:
enabled: true
service provider
依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
启动类
@SpringBootApplication
@EnableDiscoveryClient
@RestController
public class HelloServerApplication {
@Autowired
DiscoveryClient client;
@RequestMapping("/")
public String hello() {
List<ServiceInstance> instances = client.getInstances("HelloServer");
ServiceInstance selectedInstance = instances
.get(new Random().nextInt(instances.size()));
return "Hello World: " + selectedInstance.getServiceId() + ":" + selectedInstance
.getHost() + ":" + selectedInstance.getPort();
}
public static void main(String[] args) {
SpringApplication.run(HelloServerApplication.class, args);
}
}
配置文件
spring:
application:
name: HelloServer
server:
port: 7111
eureka:
password: password
client:
serviceUrl:
defaultZone: http://user:${eureka.password}@localhost:8761/eureka/
instance:
leaseRenewalIntervalInSeconds: 10
metadataMap:
instanceId: ${vcap.application.instance_id:${spring.application.name}:${spring.application.instance_id:${server.port}}}
运行
依次启动eureka server、service provider、service consumer。访问http://localhost:7211/
如果在service provider的hello方法里打上断点,就可以发现请求由从service consumer的hello方法进到了service provider的hello方法。
而且,这里并没有在service consumer里配置service provider的地址信息,只是在service consumer里调用了HelloClient接口里的hello方法,便发出了一个到service provider的http请求。
提出问题
- 服务消费者怎么调用服务提供者
- 服务消费者如何拿到服务提供者的注册信息
- 有多个服务提供者的情况下,服务消费者如何判断访问哪一个
- 什么时候往eureka server注册
- eureka server怎么判断服务是否可用
- 有新服务上线或者服务地址发生变化怎么办
- 网络超时怎么办
初步分析
service consumer
的HttpClient
接口很简单,只有两个注解和一个接口定义,没有任何Http相关的细节。
而访问HttpClient
的方式是通过容器里的bean
来实现的,自然而然可以想到这里一定用到了代理。而FeignClient
注解大概率就是代理的Pointcut
,代理内部就是网络访问的细节。
像这种开箱即用的组件,按之前文章的分析经验来看,Spring
的套路基本都是通过XXXAutoConfiguration
以及EnableXXX
注解来实现的。
可以看到在service consumer
的启动类上有EnableDiscoveryClient
、EnableFeignClients
两个注解。
然后搜一搜eurekaClientAutoConfiguration
。
源码分析
Eureka
先看整体流程图
EurekaClient初始化
public class EurekaClientAutoConfiguration {
//默认启用这个配置类,除非主动将eureka.client.refresh.enable设置为false
protected static class RefreshableEurekaClientConfiguration {
@Autowired
private ApplicationContext context;
@Autowired
private AbstractDiscoveryClientOptionalArgs<?> optionalArgs;
public EurekaClient eurekaClient(ApplicationInfoManager manager, EurekaClientConfig config,
EurekaInstanceConfig instance, @Autowired(required = false) HealthCheckHandler healthCheckHandler) {
//创建eurekaclient,注册register,renew,refresh任务到线程池
CloudEurekaClient cloudEurekaClient = new CloudEurekaClient(appManager, config, this.optionalArgs,
this.context);
cloudEurekaClient.registerHealthCheck(healthCheckHandler);
return cloudEurekaClient;
}
}
}
获取注册信息并注册定时任务
public class DiscoveryClient implements EurekaClient {
DiscoveryClient(ApplicationInfoManager applicationInfoManager, EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs args,
Provider<BackupRegistry> backupRegistryProvider, EndpointRandomizer endpointRandomizer) {
//获取注册信息并保存
boolean primaryFetchRegistryResult = fetchRegistry(false);
if (clientConfig.shouldRegisterWithEureka() && clientConfig.shouldEnforceRegistrationAtInit()) {
try {
//注册
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);
}
}
//初始化用于refresh跟renew的线程池
//线程数为2,保证了refresh跟renew的单线程运行
scheduler = Executors.newScheduledThreadPool(2,
new ThreadFactoryBuilder()
.setNameFormat("DiscoveryClient-%d")
.setDaemon(true)
.build());
//注册定时任务
initScheduledTasks();
}
private void initScheduledTasks() {
//检查eureka.client.fetch-registry配置项的值,默认为true
if (clientConfig.shouldFetchRegistry()) {
//任务执行间隔,默认30s。配置项:eureka.client.registryFetchIntervalSeconds
int registryFetchIntervalSeconds = clientConfig.getRegistryFetchIntervalSeconds();
int expBackOffBound = clientConfig.getCacheRefreshExecutorExponentialBackOffBound();
//创建任务,内部会执行CacheRefreshThread#run
cacheRefreshTask = new TimedSupervisorTask(
"cacheRefresh",
scheduler,
cacheRefreshExecutor,
registryFetchIntervalSeconds,
TimeUnit.SECONDS,
expBackOffBound,
new CacheRefreshThread()
);
// 注册refresh任务,负责刷新从eureka server获取的注册信息
scheduler.schedule(
cacheRefreshTask,
registryFetchIntervalSeconds, TimeUnit.SECONDS);
}
//eureka.client.register-with-eurek 默认为true
if (clientConfig.shouldRegisterWithEureka()) {
//间隔,默认10s
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()
);
//注册renew/heartbeatTask
scheduler.schedule(
heartbeatTask,
renewalIntervalInSecs, TimeUnit.SECONDS);
// 周期性检查当前服务的状态信息,比如hostname变化,或者改变InstanceStatus或者renew失败。
// 一旦状态发生变化,会重新将自己注册到eureka server
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) {
logger.info("Saw local status change event {}", statusChangeEvent);
instanceInfoReplicator.onDemandUpdate();
}
};
//注册状态监听器,好处是状态发生变更时可能比周期性更早的感知到变化
//参考:ApplicationInfoManager#setInstanceStatus
if (clientConfig.shouldOnDemandUpdateStatusChange()) {
applicationInfoManager.registerStatusChangeListener(statusChangeListener);
}
instanceInfoReplicator.start(clientConfig.getInitialInstanceInfoReplicationIntervalSeconds());
}
}
}
refresh
任务: CacheRefreshThread
class CacheRefreshThread implements Runnable {
void refreshRegistry() {
try {
boolean isFetchingRemoteRegionRegistries = isFetchingRemoteRegionRegistries();
boolean remoteRegionsModified = false;
// 判断region是否发生变化
// 比如突然某个机房的服务出问题了,需要切到另一个机房。修改fetchRemoteRegionsRegistry配置项
String latestRemoteRegions = clientConfig.fetchRegistryForRemoteRegions();
if (null != latestRemoteRegions) {
String currentRemoteRegions = remoteRegionsToFetch.get();
if (!latestRemoteRegions.equals(currentRemoteRegions)) {
synchronized (instanceRegionChecker.getAzToRegionMapper()) {
if (remoteRegionsToFetch.compareAndSet(currentRemoteRegions, latestRemoteRegions)) {
//更新region
String[] remoteRegions = latestRemoteRegions.split(",");
remoteRegionsRef.set(remoteRegions);
instanceRegionChecker.getAzToRegionMapper().setRegionsToFetch(remoteRegions);
//保存region变化状态
remoteRegionsModified = true;
} else {
logger.info("Remote regions to fetch modified concurrently," +
" ignoring change from {} to {}", currentRemoteRegions, latestRemoteRegions);
}
}
}
}
//根据配置的url从eureka server拉取信息
//如果region发生变化,则拉取注册信息后进行全量覆盖。否则根据applicationName更新
//参考:DiscoveryClient#updateDelta -> applications.getRegisteredApplications(instance.getAppName()).addInstance(instance);
boolean success = fetchRegistry(remoteRegionsModified);
} catch (Throwable e) {
logger.error("Cannot fetch registry from server", e);
}
}
}
renew
public class DiscoveryClient implements EurekaClient {
boolean renew() {
EurekaHttpResponse<InstanceInfo> httpResponse;
try {
//将当前实例的Id等信息发至eureka
httpResponse = eurekaTransport.registrationClient.sendHeartBeat(instanceInfo.getAppName(), instanceInfo.getId(), instanceInfo, null);
//如果status时404
if (httpResponse.getStatusCode() == Status.NOT_FOUND.getStatusCode()) {
//将dirty设置为true
long timestamp = instanceInfo.setIsDirtyWithTime();
boolean success = register();
if (success) {
//如果成功,则将dirty设置为false。
//如果失败,InstanceInfoReplicator在扫描到dirty为false后会重新注册
instanceInfo.unsetIsDirty(timestamp);
}
return success;
}
return httpResponse.getStatusCode() == Status.OK.getStatusCode();
} catch (Throwable e) {
logger.error(PREFIX + "{} - was unable to send heartbeat!", appPathIdentifier, e);
return false;
}
}
}
Feign
核心组件关系概览
LoadBalancerAutoConfiguration
引入LoadBalancerAutoConfiguration
LoadBalancerAutoConfiguration
引入LoadBalancerClientConfiguration
LoadBalancerClientConfiguration
引入ServiceInstanceListSupplier
ServiceInstanceListSupplier
从BeanFactory
获取DiscoveryClient
DiscoveryClient
持有注册到eureka server
的服务的信息
----
LoadBalancerClientConfiguration
引入RoundRobinLoadBalancer
RoundRobinLoadBalancer
从BeanFactory
获取ServiceInstanceListSupplier
----
DefaultFeignLoadBalancerConfiguration
引入FeignBlockingLoadBalancerClient
FeignBlockingLoadBalancerClient
从容器中获取RoundRobinLoadBalancer
----
EnableFeignClients
引入FeignClientsRegistrar
FeignClientsRegistrar
为所有标注了FeignClient
的类生成BeanDefinition
FeignClientsRegistrar
为生成的BeanDefinition
注册callback
用于生成代理对象
----
FeignClientsRegistrar
注册的callback
:创建SynchronousMethodHandler
,在其中注入FeignBlockingLoadBalancerClient
,然后将SynchronousMethodHandler
包装成InvocationHandler
并注入代理对象
调用关系概览
访问代理对象->SynchronousMethodHandler
->FeignBlockingLoadBalancerClient
->RoundRobinLoadBalancer
->DiscoveryClient
获取服务提供者信息→RoundRobinLoadBalancer
轮询选择服务提供者→调用目标服务
流程图
初始化
远程调用
EnableFeignClients
点进EnableFeignClients
. 可以看到它import了FeignClientsRegistrar
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
...
}
扫描FeignClient
FeignClientsRegistrar
主要做了两件事
- 扫描
basePackage
下带FeignClient
注解的类 - 注册工厂方法,用于为第一步扫描出来的类生成代理对象
public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>();
//指定注解过滤器,过滤的注解为FeignClient
scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
Set<String> basePackages = getBasePackages(metadata);
for (String basePackage : basePackages) {
//扫描basePackage下带FeignClient注解的类并包装成beanDefinition
candidateComponents.addAll(scanner.findCandidateComponents(basePackage));
}
for (BeanDefinition candidateComponent : candidateComponents) {
if (candidateComponent instanceof AnnotatedBeanDefinition) {
// verify annotated class is an interface
AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
Map<String, Object> attributes = annotationMetadata
.getAnnotationAttributes(FeignClient.class.getCanonicalName());
String name = getClientName(attributes);
registerClientConfiguration(registry, name, attributes.get("configuration"));
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
注册beanDefinition
以及callback
factoryBean.getObject();
生成代理对象。通过将SynchronousMethodHandler
包装成InvocationHandler
植入目标对象来完成。而SynchronousMethodHandler
里负责http调用的细节。
private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata,
Map<String, Object> attributes) {
String className = annotationMetadata.getClassName();
Class clazz = ClassUtils.resolveClassName(className, null);
FeignClientFactoryBean factoryBean = new FeignClientFactoryBean();
factoryBean.setType(clazz);
//注册一个回调来生成类型为clazz的bean
//往beanDefinition里放入一个Supplier的实例,Spring会优先通过Supplier来创建目标bean
BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(clazz, () -> {
//返回代理对象
return factoryBean.getObject();
});
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, qualifiers);
//注册beanDefinition
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}
拦截器
创建SynchronousMethodHandler.Factory
public abstract class Feign {
public <T> T target(Target<T> target) {
return build().newInstance(target);
}
public <T> T target(Target<T> target) {
return build().newInstance(target);
}
public Feign build() {
//创建SynchronousMethodHandler的factory
SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,
logLevel, decode404, closeAfterDecode, propagationPolicy, forceDecoding);
ParseHandlersByName handlersByName =
new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder,
errorDecoder, synchronousMethodHandlerFactory);
return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
}
}
生成代理对象
public class ReflectiveFeign extends Feign {
public <T> T newInstance(Target<T> target) {
//通过SynchronousMethodHandler.Factory的create方法创建SynchronousMethodHandler
Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
//通过SynchronousMethodHandler创建FeignInvocationHandler
InvocationHandler handler = factory.create(target, methodToHandler);
//生成代理对象
T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
new Class<?>[] {target.type()}, handler);
return proxy;
}
}
InvocationHandler
InvocationHandler
的生成与执行
static class FeignInvocationHandler implements InvocationHandler {
private final Target target;
private final Map<Method, MethodHandler> dispatch;
FeignInvocationHandler(Target target, Map<Method, MethodHandler> dispatch) {
//SynchronousMethodHandler是MethodHandler的子类
//method是目标方法
this.target = checkNotNull(target, "target");
this.dispatch = checkNotNull(dispatch, "dispatch for %s", target);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//根据目标方法拿到MethodHandler并调用其invoke方法
return dispatch.get(method).invoke(args);
}
}
重试策略
SynchronousMethodHandler
,http调用入口以及重试策略
final class SynchronousMethodHandler implements MethodHandler {
@Override
public Object invoke(Object[] argv) throws Throwable {
RequestTemplate template = buildTemplateFromArgs.create(argv);
Retryer retryer = this.retryer.clone();
while (true) {
try {
//执行http request
return executeAndDecode(template, options);
} catch (RetryableException e) {
//IO异常
//服务提供者返回服务端错误(status>300&status!=404),并且response header里有Retry-After
try {
//检查是否重试,重试次数达到retryer.maxAttempts就会抛出RetryableException
//重试间隔:如果是服务端错误,则使用Retry-After指定的值,但是不能超过maxPeriod
//如果是IO异常,则每次的间隔延长1.5倍
//如果符合重试要求,则不抛出异常
//服务端异常参考:ErrorDecoder#decode
//IO异常参考:SynchronousMethodHandler#executeAndDecode
retryer.continueOrPropagate(e);
} catch (RetryableException th) {
Throwable cause = th.getCause();
if (propagationPolicy == UNWRAP && cause != null) {
throw cause;
} else {
throw th;
}
}
continue;
}
}
}
Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
...
Request request = targetRequest(template);
try {
response = client.execute(request, options);
} catch (IOException e) {
//抛出RetryableException
throw errorExecuting(request, e);
}
...
}
}
调用LoadBalancer
获取服务提供者信息并执行http请求
public class FeignBlockingLoadBalancerClient implements Client {
@Override
public Response execute(Request request, Request.Options options) throws IOException {
//从eureka client中根据serviceId获取服务提供方的地址信息
//serviceId是FeignClient注解的value
//通过eurekaClient从eurekaServer获取服务提供方的地址信息
ServiceInstance instance = loadBalancerClient.choose(serviceId, lbRequest);
...
//生成并执行http请求
return executeWithLoadBalancerLifecycleProcessing(delegate, options, newRequest, lbRequest, lbResponse,
supportedLifecycleProcessors);
}
}
获取服务提供者信息
根据FeignClient里设置的value获取服务提供者信息
public class BlockingLoadBalancerClient implements LoadBalancerClient {
public <T> ServiceInstance choose(String serviceId, Request<T> request) {
//从容器中获取ReactorServiceInstanceLoadBalancer的实例
//默认是RoundRobinLoadBalancer
//参考LoadBalancerClientConfiguration
ReactiveLoadBalancer<ServiceInstance> loadBalancer = loadBalancerClientFactory.getInstance(serviceId);
if (loadBalancer == null) {
return null;
}
//通过loadBanlancer获取地址信息
Response<ServiceInstance> loadBalancerResponse = Mono.from(loadBalancer.choose(request)).block();
if (loadBalancerResponse == null) {
return null;
}
return loadBalancerResponse.getServer();
}
}
负载均衡处理
public class RoundRobinLoadBalancer implements ReactorServiceInstanceLoadBalancer {
@Override
public Mono<Response<ServiceInstance>> choose(Request request) {
//拿到ServiceInstanceListSupplier,持有eurekaClient
ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider
.getIfAvailable(NoopServiceInstanceListSupplier::new);
//根据请求从eureka拿到所有的服务端实例后选出一个实例
return supplier.get(request).next()
.map(serviceInstances ->
//从serviceInstances选择一个实例
processInstanceResponse(supplier, serviceInstances)
);
}
private Response<ServiceInstance> processInstanceResponse(ServiceInstanceListSupplier supplier,
List<ServiceInstance> serviceInstances) {
//轮询选出一个实例
Response<ServiceInstance> serviceInstanceResponse = getInstanceResponse(serviceInstances);
if (supplier instanceof SelectedInstanceCallback && serviceInstanceResponse.hasServer()) {
((SelectedInstanceCallback) supplier).selectedServiceInstance(serviceInstanceResponse.getServer());
}
return serviceInstanceResponse;
}
private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances) {
//每次获取+1
int pos = Math.abs(this.position.incrementAndGet());
//取余实现轮询
ServiceInstance instance = instances.get(pos % instances.size());
return new DefaultResponse(instance);
}
}
解决问题
- 通过代理生成http进行远程调用
- 通
过eureka client
从eureka server
根据appName(FeignClient
注解的value
)获取服务提供者信息,并通过refresh保证信息及时性 - 默认通过轮询的方式
- 启动时,并通过周期性任务以及状态监听器保证数据变更时能够及时通知到eureka server
- 通过定时(默认10s)向eureka server发送心跳消息(renew)
- 通过定时(默认30s)从eureka server获取服务信息,然后根据appName更新本地缓存(refresh)
- 如果没超过最大重试次数,则定时重试,间隔时间每次增加1.5倍,但不超过最大重试时间(默认5s)。如果服务端返回的
header
里设置了Retry-After
,则根据该header
指定的值来设置间隔时间,且不超过配置的最大间隔