内容概览本文从实战的场景触发结合了配置说明从源码的角度剖析了微服务场景下接口调用的流程涉及了spring 的多个特性阅读本文相信会给你带来不少的收获。准备参数配置依赖parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.14/version relativePath/ /parent dependencies dependency groupIdorg.springframework.cloud/groupId artifactIdspring-cloud-starter-openfeign/artifactId /dependency dependency groupIdorg.springframework.cloud/groupId artifactIdspring-cloud-starter-netflix-hystrix/artifactId version2.2.10.RELEASE/version /dependency dependency groupIdorg.springframework.cloud/groupId artifactIdspring-cl dependencieshystrixfeign: client: config: default: connectTimeout: 3000 readTimeout: 30000 report-business-client: connectTimeout: 3000 readTimeout: 60000 autoservice-business-client: connectTimeout: 5000 readTimeout: 60000 circuitbreaker: enabled: true spring: cloud: loadbalancer: retry: enabled: true # 启用重试 initial-interval: 1000 # 重试的初始间隔时间毫秒 max-interval: 1000 # 最大重试间隔时间毫秒 multiplier: 1.0 # 间隔增加的乘数 max-retries-on-next-service-instance: 1 hystrix: command: default: execution: isolation: thread: timeoutInMilliseconds: 80000 timeout: enabled: true启动类配置EnableFeignClients(basePackages com.example.*)源码分析当我们在启动类上添加EnableFeignClients注解后Spring Boot 在项目启动时会自动扫描该注解。Spring 会解析注解上的Import元数据加载其中指定的FeignClientsRegistrar.class。由于FeignClientsRegistrar实现了ImportBeanDefinitionRegistrar接口在启动过程中ConfigurationClassPostProcessor的postProcessBeanDefinitionRegistry方法会被调用进而执行processConfigBeanDefinitions方法最终触发FeignClientsRegistrar的registerFeignClients方法。在这个方法中Spring 会扫描所有被FeignClient注解修饰的接口并将其注册为 Spring Bean。EnableFeignClientsImport(FeignClientsRegistrar.class) public interface EnableFeignClients {...}ConfigurationClassPostProcessorpublic class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor, PriorityOrdered, ResourceLoaderAware, ApplicationStartupAware, BeanClassLoaderAware, EnvironmentAware { Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) { int registryId System.identityHashCode(registry); if (this.registriesPostProcessed.contains(registryId)) { throw new IllegalStateException( postProcessBeanDefinitionRegistry already called on this post-processor against registry); } if (this.factoriesPostProcessed.contains(registryId)) { throw new IllegalStateException( postProcessBeanFactory already called on this post-processor against registry); } this.registriesPostProcessed.add(registryId); processConfigBeanDefinitions(registry); }FeignClientRegistrar注解扫描public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { LinkedHashSetBeanDefinition candidateComponents new LinkedHashSet(); MapString, Object attrs metadata.getAnnotationAttributes(EnableFeignClients.class.getName()); final Class?[] clients attrs null ? null : (Class?[]) attrs.get(clients); if (clients null || clients.length 0) { ClassPathScanningCandidateComponentProvider scanner getScanner(); scanner.setResourceLoader(this.resourceLoader); scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class)); SetString basePackages getBasePackages(metadata); for (String basePackage : basePackages) { 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(); Assert.isTrue(annotationMetadata.isInterface(), FeignClient can only be specified on an interface); MapString, Object attributes annotationMetadata .getAnnotationAttributes(FeignClient.class.getCanonicalName()); String name getClientName(attributes); registerClientConfiguration(registry, name, attributes.get(configuration)); registerFeignClient(registry, annotationMetadata, attributes); } } }getClientName生成Bean 的名称这里的取值跟后面按服务配置超时参数有关联。private String getClientName(MapString, Object client) { if (client null) { return null; } String value (String) client.get(contextId); if (!StringUtils.hasText(value)) { value (String) client.get(value); } if (!StringUtils.hasText(value)) { value (String) client.get(name); } if (!StringUtils.hasText(value)) { value (String) client.get(serviceId); } if (StringUtils.hasText(value)) { return value; } throw new IllegalStateException( Either name or value must be provided in FeignClient.class.getSimpleName()); }FeignClientFactoryBean初始化Feign 对象的以及配置protected Feign.Builder feign(FeignContext context) { FeignLoggerFactory loggerFactory get(context, FeignLoggerFactory.class); Logger logger loggerFactory.create(type); // formatter:off Feign.Builder builder get(context, Feign.Builder.class) // required values .logger(logger) .encoder(get(context, Encoder.class)) .decoder(get(context, Decoder.class)) .contract(get(context, Contract.class)); // formatter:on configureFeign(context, builder); return builder; } Override public Object getObject() { return getTarget(); }在 configureFeign 方法中加载用户的配置并初始化protected void configureFeign(FeignContext context, Feign.Builder builder) { if (properties.isDefaultToProperties()) { configureUsingConfiguration(context, builder); configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()), builder); configureUsingProperties(properties.getConfig().get(contextId), builder); } else { configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()), builder); configureUsingProperties(properties.getConfig().get(contextId), builder); configureUsingConfiguration(context, builder); } }加载 yaml 中的配置protected void configureUsingProperties(FeignClientProperties.FeignClientConfiguration config, Feign.Builder builder) { ... if (!refreshableClient) { connectTimeoutMillis config.getConnectTimeout() ! null ? config.getConnectTimeout() : connectTimeoutMillis; readTimeoutMillis config.getReadTimeout() ! null ? config.getReadTimeout() : readTimeoutMillis; followRedirects config.isFollowRedirects() ! null ? config.isFollowRedirects() : followRedirects; builder.options(new Request.Options(connectTimeoutMillis, TimeUnit.MILLISECONDS, readTimeoutMillis, TimeUnit.MILLISECONDS, followRedirects)); } }ReflectiveFeign负责实例化 Feign 对象并指定了代理对象public T T newInstance(TargetT target) { .... T proxy (T) Proxy.newProxyInstance(target.type().getClassLoader(), new Class?[] {target.type()}, handler); ... }Feign 动态代理、超时、重试业务代码调用 Feign 定义的接口方法时通过FeignCircuitBreakerInvocationHandler拦截由底层SynchronousMethodHandler生成 RequestTemplate核心逻辑如下SynchronousMethodHandlerfinal class SynchronousMethodHandler implements MethodHandler { public Object invoke(Object[] argv) throws Throwable { RequestTemplate template buildTemplateFromArgs.create(argv); Options options findOptions(argv); Retryer retryer this.retryer.clone(); while (true) { try { return executeAndDecode(template, options); } catch (RetryableException e) { retryer.continueOrPropagate(e); ... } } }SynchronousMethodHandler重试校验重试次数时间的处理public void continueOrPropagate(RetryableException e) { if (attempt maxAttempts) { throw e; } long interval; if (e.retryAfter() ! null) { interval e.retryAfter().getTime() - currentTimeMillis(); if (interval maxPeriod) { interval maxPeriod; } if (interval 0) { return; } } else { interval nextMaxInterval(); } try { Thread.sleep(interval); } catch (InterruptedException ignored) { Thread.currentThread().interrupt(); throw e; } sleptForMillis interval; }使用 LoadBalancer 选择服务实例OpenFeign 调用时若 URL 是服务名如 http://service-name通过 LoadBalancerFeignClient 使用负载均衡器选择服务实例SynchronousMethodHandler中的 executeAndDecode 完成接口调用并返回RetryableFeignBlockingLoadBalancerClientexecute 中loadBalancerClient.choose(serviceId, lbRequest); 选择实例execute 调用了 retryTemplate 发起请求return retryTemplate.execute(context - {} excute 内部 String reconstructedUrl loadBalancerClient.reconstructURI(retrievedServiceInstance, originalUri).toString(); 将服务名替换为目标实例的具体地址如 http://192.168.0.1:8080。追溯源码在InterceptorRetryPolicy中定义了重试时实例选择的相关策略Override public boolean canRetry(RetryContext context) { LoadBalancedRetryContext lbContext (LoadBalancedRetryContext) context; if (lbContext.getRetryCount() 0 lbContext.getServiceInstance() null) { // We havent even tried to make the request yet so return true so we do lbContext.setServiceInstance(null); return true; } return policy.canRetryNextServer(lbContext); }BlockingLoadBalancedRetryPolicy切换实例的重试配置properties.getRetry().getMaxRetriesOnNextServiceInstance()通过参数配置Override public boolean canRetryNextServer(LoadBalancedRetryContext context) { // After the failure, we increment first and then check, hence the equality check return nextServerCount properties.getRetry().getMaxRetriesOnNextServiceInstance() canRetry(context); } //默认只允许Get 请求的重试可以配置允许全部请求都重试 public boolean canRetry(LoadBalancedRetryContext context) { HttpMethod method context.getRequest().getMethod(); return HttpMethod.GET.equals(method) || properties.getRetry().isRetryOnAllOperations(); }Hystrix 熔断此处没有源码分析待后续完善通过 Hystrix 将 Feign 调用逻辑封装为命令执行熔断和降级逻辑。1. 熔断检测请求失败率或超时时间达到阈值时触发熔断执行降级。2. 隔离每个服务调用运行在独立线程池中避免过载影响其他服务。3. 降级调用失败或熔断时执行预设的降级方法如返回默认数据。最终经过负载均衡和熔断处理的请求由底层 HTTP 客户端如 OkHttp 或 Apache HttpClient执行。默认客户端是 Feign 在没有引入任何额外依赖时的兜底选HttpURLConnection而 OkHttp 是生产环境的推荐标准。客户端优化修改配置如下dependency groupIdio.github.openfeign/groupId artifactIdfeign-okhttp/artifactId /dependencyfeign: okhttp: enabled: true看下源码Configuration(proxyBeanMethods false) ConditionalOnClass(OkHttpClient.class) ConditionalOnProperty(feign.okhttp.enabled) ConditionalOnBean({ LoadBalancerClient.class, LoadBalancerClientFactory.class }) Import(OkHttpFeignConfiguration.class) EnableConfigurationProperties(LoadBalancerClientsProperties.class) class OkHttpFeignLoadBalancerConfiguration { ... Bean ConditionalOnMissingBean ConditionalOnClass(name org.springframework.retry.support.RetryTemplate) ConditionalOnBean(LoadBalancedRetryFactory.class) ConditionalOnProperty(value spring.cloud.loadbalancer.retry.enabled, havingValue true, matchIfMissing true) public Client feignRetryClient(LoadBalancerClient loadBalancerClient, okhttp3.OkHttpClient okHttpClient, LoadBalancedRetryFactory loadBalancedRetryFactory, LoadBalancerClientFactory loadBalancerClientF初始化RetryableFeignBlockingLoadBalancerClient在用户触发调用时, 执行excute 方法delegate 就是 初始化好的OkHttpClient代替了默认的 Clientpublic class RetryableFeignBlockingLoadBalancerClient implements Client { public Response execute(Request request, Request.Options options) throws IOException { ... Response response LoadBalancerUtils.executeWithLoadBalancerLifecycleProcessing(delegate, options, feignRequest, lbRequest, lbResponse, supportedLifecycleProcessors, retrievedServiceInstance ! null, loadBalancerProperties.isUseRawStatusCodeInResponseData()); ... } }
网站建设
高端定制
企业官网