Eureka是一个REST (Representational State Transfer)服务,用于定位服务,以实现中间层服务器的负载平衡和故障转移,我们称此服务为Eureka
服务器。Eureka
还有一个基于java
的客户端组件,Eureka客户端,这使得与服务的交互更加容易,同时客户端也有一个内置的负载平衡器,它执行基本的循环负载均衡。
Feign 是一种声明式服务调用组件,它在 RestTemplate
的基础上做了进一步的封装。通过 Feign,我们只需要声明一个接口并通过注解进行简单的配置(类似于 Dao
接口上面的 Mapper
注解一样)即可实现对 HTTP
接口的绑定。
通过 Feign
,我们可以像调用本地方法一样来调用远程服务,而完全感觉不到这是在进行远程调用。
eureka
解决了什么问题?为什么要用eureka
?
eureka
为分布式环境引入了服务注册与发现的能力,如果没有服务注册与发现,我们便需要手动去维护一大堆服务的地址信息,而当服务的状态变化发生变化,比如有新的服务加入或某些现有服务突然不可用时,现存的服务也无法感知到并及时切换到可用的服务上去
另外,feign
屏蔽了请求构建、发送、重试、负载均衡等相关细节,让我们能够从重复的工作中解放出来,更专注于业务本身。
引入依赖
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
依赖
org.springframework.boot spring-boot-starter-web
org.springframework.boot spring-boot-starter-actuator
org.springframework.cloud spring-cloud-starter-openfeign
org.springframework.cloud spring-cloud-starter-netflix-eureka-client
启动类
@SpringBootApplication
@EnableDiscoveryClient
@RestController
@EnableFeignClients
public class HelloClientApplication {@AutowiredHelloClient 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: HelloClientserver:port: 7211eureka:password: passwordclient:serviceUrl:defaultZone: http://user:${eureka.password}@localhost:8761/eureka/instance:leaseRenewalIntervalInSeconds: 10metadataMap:instanceId: ${vcap.application.instance_id:${spring.application.name}:${spring.application.instance_id:${server.port}}}endpoints:restart:enabled: true
依赖
org.springframework.boot spring-boot-starter-web
org.springframework.boot spring-boot-starter-actuator
org.springframework.cloud spring-cloud-starter-openfeign
org.springframework.cloud spring-cloud-starter-netflix-eureka-client
启动类
@SpringBootApplication
@EnableDiscoveryClient
@RestController
public class HelloServerApplication {@AutowiredDiscoveryClient client;@RequestMapping("/")public String hello() {List 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: HelloServerserver:port: 7111eureka:password: passwordclient:serviceUrl:defaultZone: http://user:${eureka.password}@localhost:8761/eureka/instance:leaseRenewalIntervalInSeconds: 10metadataMap: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请求。
service consumer
的HttpClient
接口很简单,只有两个注解和一个接口定义,没有任何Http相关的细节。
而访问HttpClient
的方式是通过容器里的bean
来实现的,自然而然可以想到这里一定用到了代理。而FeignClient
注解大概率就是代理的Pointcut
,代理内部就是网络访问的细节。
像这种开箱即用的组件,按之前文章的分析经验来看,Spring
的套路基本都是通过XXXAutoConfiguration
以及EnableXXX
注解来实现的。
可以看到在service consumer
的启动类上有EnableDiscoveryClient
、EnableFeignClients
两个注解。
然后搜一搜eurekaClientAutoConfiguration
。
先看整体流程图
public class EurekaClientAutoConfiguration {//默认启用这个配置类,除非主动将eureka.client.refresh.enable设置为falseprotected static class RefreshableEurekaClientConfiguration {@Autowiredprivate ApplicationContext context;@Autowiredprivate 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 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配置项的值,默认为trueif (clientConfig.shouldFetchRegistry()) {//任务执行间隔,默认30s。配置项:eureka.client.registryFetchIntervalSecondsint registryFetchIntervalSeconds = clientConfig.getRegistryFetchIntervalSeconds();int expBackOffBound = clientConfig.getCacheRefreshExecutorExponentialBackOffBound();//创建任务,内部会执行CacheRefreshThread#runcacheRefreshTask = 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 默认为trueif (clientConfig.shouldRegisterWithEureka()) {//间隔,默认10sint 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/heartbeatTaskscheduler.schedule(heartbeatTask,renewalIntervalInSecs, TimeUnit.SECONDS);// 周期性检查当前服务的状态信息,比如hostname变化,或者改变InstanceStatus或者renew失败。// 一旦状态发生变化,会重新将自己注册到eureka serverinstanceInfoReplicator = new InstanceInfoReplicator(this,instanceInfo,clientConfig.getInstanceInfoReplicationIntervalSeconds(),2); // burstSizestatusChangeListener = new ApplicationInfoManager.StatusChangeListener() {@Overridepublic String getId() {return "statusChangeListener";}@Overridepublic void notify(StatusChangeEvent statusChangeEvent) {logger.info("Saw local status change event {}", statusChangeEvent);instanceInfoReplicator.onDemandUpdate();}};//注册状态监听器,好处是状态发生变更时可能比周期性更早的感知到变化//参考:ApplicationInfoManager#setInstanceStatusif (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)) {//更新regionString[] 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 httpResponse;try {//将当前实例的Id等信息发至eurekahttpResponse = eurekaTransport.registrationClient.sendHeartBeat(instanceInfo.getAppName(), instanceInfo.getId(), instanceInfo, null);//如果status时404if (httpResponse.getStatusCode() == Status.NOT_FOUND.getStatusCode()) {//将dirty设置为truelong 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;}}
}
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
. 可以看到它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 candidateComponents = new LinkedHashSet<>();//指定注解过滤器,过滤的注解为FeignClientscanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));Set basePackages = getBasePackages(metadata);for (String basePackage : basePackages) {//扫描basePackage下带FeignClient注解的类并包装成beanDefinitioncandidateComponents.addAll(scanner.findCandidateComponents(basePackage));}for (BeanDefinition candidateComponent : candidateComponents) {if (candidateComponent instanceof AnnotatedBeanDefinition) {// verify annotated class is an interfaceAnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();Map attributes = annotationMetadata.getAnnotationAttributes(FeignClient.class.getCanonicalName());String name = getClientName(attributes);registerClientConfiguration(registry, name, attributes.get("configuration"));registerFeignClient(registry, annotationMetadata, attributes);}}}
beanDefinition
以及callbackfactoryBean.getObject();
生成代理对象。通过将SynchronousMethodHandler
包装成InvocationHandler
植入目标对象来完成。而SynchronousMethodHandler
里负责http调用的细节。
private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata,Map attributes) {String className = annotationMetadata.getClassName();Class clazz = ClassUtils.resolveClassName(className, null);FeignClientFactoryBean factoryBean = new FeignClientFactoryBean();factoryBean.setType(clazz);//注册一个回调来生成类型为clazz的bean//往beanDefinition里放入一个Supplier的实例,Spring会优先通过Supplier来创建目标beanBeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(clazz, () -> {//返回代理对象return factoryBean.getObject();});BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, qualifiers);//注册beanDefinitionBeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);}
创建SynchronousMethodHandler.Factory
public abstract class Feign {public T target(Target target) {return build().newInstance(target);}public T target(Target target) {return build().newInstance(target);}public Feign build() {//创建SynchronousMethodHandler的factorySynchronousMethodHandler.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 newInstance(Target target) {//通过SynchronousMethodHandler.Factory的create方法创建SynchronousMethodHandlerMap nameToHandler = targetToHandlersByName.apply(target);Map methodToHandler = new LinkedHashMap();methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));//通过SynchronousMethodHandler创建FeignInvocationHandlerInvocationHandler 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 dispatch;FeignInvocationHandler(Target target, Map dispatch) {//SynchronousMethodHandler是MethodHandler的子类//method是目标方法this.target = checkNotNull(target, "target");this.dispatch = checkNotNull(dispatch, "dispatch for %s", target);}@Overridepublic 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 {@Overridepublic Object invoke(Object[] argv) throws Throwable {RequestTemplate template = buildTemplateFromArgs.create(argv);Retryer retryer = this.retryer.clone();while (true) {try {//执行http requestreturn executeAndDecode(template, options);} catch (RetryableException e) {//IO异常//服务提供者返回服务端错误(status>300&status!=404),并且response header里有Retry-Aftertry {//检查是否重试,重试次数达到retryer.maxAttempts就会抛出RetryableException//重试间隔:如果是服务端错误,则使用Retry-After指定的值,但是不能超过maxPeriod//如果是IO异常,则每次的间隔延长1.5倍//如果符合重试要求,则不抛出异常//服务端异常参考:ErrorDecoder#decode//IO异常参考:SynchronousMethodHandler#executeAndDecoderetryer.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) {//抛出RetryableExceptionthrow errorExecuting(request, e);}...}}
获取服务提供者信息并执行http请求
public class FeignBlockingLoadBalancerClient implements Client {@Overridepublic 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 ServiceInstance choose(String serviceId, Request request) {//从容器中获取ReactorServiceInstanceLoadBalancer的实例//默认是RoundRobinLoadBalancer//参考LoadBalancerClientConfigurationReactiveLoadBalancer loadBalancer = loadBalancerClientFactory.getInstance(serviceId);if (loadBalancer == null) {return null;}//通过loadBanlancer获取地址信息Response loadBalancerResponse = Mono.from(loadBalancer.choose(request)).block();if (loadBalancerResponse == null) {return null;}return loadBalancerResponse.getServer();}
}
public class RoundRobinLoadBalancer implements ReactorServiceInstanceLoadBalancer { @Overridepublic Mono> choose(Request request) {//拿到ServiceInstanceListSupplier,持有eurekaClientServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider.getIfAvailable(NoopServiceInstanceListSupplier::new);//根据请求从eureka拿到所有的服务端实例后选出一个实例return supplier.get(request).next().map(serviceInstances -> //从serviceInstances选择一个实例processInstanceResponse(supplier, serviceInstances));}private Response processInstanceResponse(ServiceInstanceListSupplier supplier,List serviceInstances) {//轮询选出一个实例Response serviceInstanceResponse = getInstanceResponse(serviceInstances);if (supplier instanceof SelectedInstanceCallback && serviceInstanceResponse.hasServer()) {((SelectedInstanceCallback) supplier).selectedServiceInstance(serviceInstanceResponse.getServer());}return serviceInstanceResponse;}private Response getInstanceResponse(List instances) {//每次获取+1int pos = Math.abs(this.position.incrementAndGet());//取余实现轮询ServiceInstance instance = instances.get(pos % instances.size());return new DefaultResponse(instance);}
}
过eureka client
从eureka server
根据appName(FeignClient
注解的value
)获取服务提供者信息,并通过refresh保证信息及时性header
里设置了Retry-After
,则根据该header
指定的值来设置间隔时间,且不超过配置的最大间隔https://github.com/Netflix/eureka/wiki/Eureka-at-a-glance