美文网首页程序员IT@程序员猿媛
springcloud-eureka的重试策略

springcloud-eureka的重试策略

作者: braveheart075 | 来源:发表于2018-08-29 23:00 被阅读0次

    关于服务的注册和发现,其实中间件很多,之前用的比较多的是consul。后续有时间也会写一些关于consul的文章。好了,言归正传。

    eureka-client

    eureka-client配置
    eureka-client配置
    eureka-github

    eureka默认使用Ribbon做负载均衡

    • 实际在使用springcloud-feign的过程中,如果你有多个服务,如果服务在处理的过程中有超时,这个时候,你会发现一个坑:就是另外一个服务也会收到请求。如果第一个服务只是处理的很慢,而这个服务没有做过幂等控制,那么就会有重复的请求过来。
      在本地起两个节点的服务,在服务中增加sleep,让客户端错觉超时。
    @RequestMapping(value = "hello",method = RequestMethod.GET)
        public String hello() {
            log.info("you are into this method.....hello.......");
            try {
                TimeUnit.SECONDS.sleep(30);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "hello world.";
        }
    
    • 在客户端发器请求,发现两个服务端都会打印:
    you are into this method.....hello.......
    
    • 我们看feignclientsConfiguration中的代码发现:默认是不会retry的。但是为什么还会再发一次?
    @Bean
        @ConditionalOnMissingBean
        public Retryer feignRetryer() {
            return Retryer.NEVER_RETRY;
        }
    
        @Bean
        @Scope("prototype")
        @ConditionalOnMissingBean
        public Feign.Builder feignBuilder(Retryer retryer) {
            return Feign.builder().retryer(retryer);
        }
    
        @Bean
        @ConditionalOnMissingBean(FeignLoggerFactory.class)
        public FeignLoggerFactory feignLoggerFactory() {
            return new DefaultFeignLoggerFactory(logger);
        }
    
    • 通过日志发现,调用的是如下类:
    at org.springframework.cloud.netflix.feign.ribbon.LoadBalancerFeignClient.execute(LoadBalancerFeignClient.java:63) ~[spring-cloud-netflix-core-1.4.5.RELEASE.jar:1.4.5.RELEASE]
        at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:97) ~[feign-core-9.5.0.jar:na]
      
    

    feign调用的ribbon的负载均衡。

    AbstractLoadBalancerAwareClient.java类:
    protected LoadBalancerCommand<T> buildLoadBalancerCommand(final S request, final IClientConfig config) {
            RequestSpecificRetryHandler handler = getRequestSpecificRetryHandler(request, config);
            LoadBalancerCommand.Builder<T> builder = LoadBalancerCommand.<T>builder()
                    .withLoadBalancerContext(this)
                    .withRetryHandler(handler)
                    .withLoadBalancerURI(request.getUri());
            customizeLoadBalancerCommandBuilder(request, config, builder);
            return builder.build();
        }
    
    • 在handler类里,注入的是RequestSpecificRetryHandler类,通过debug,这个类的retryNextServer=1。在本地cache的队列里,如果有多个server,那么会默认会重试下一个节点。
    • 跟进getRequestSpecificRetryHandler这个方法,进入到ribbon这个package的FeignLoadBalancer类中:
    @Override
        public RequestSpecificRetryHandler getRequestSpecificRetryHandler(
                RibbonRequest request, IClientConfig requestConfig) {
            if (this.clientConfig.get(CommonClientConfigKey.OkToRetryOnAllOperations,
                    false)) {
                return new RequestSpecificRetryHandler(true, true, this.getRetryHandler(),
                        requestConfig);
            }
            if (!request.toRequest().method().equals("GET")) {
                return new RequestSpecificRetryHandler(true, false, this.getRetryHandler(),
                        requestConfig);
            }
            else {
                return new RequestSpecificRetryHandler(true, true, this.getRetryHandler(),
                        requestConfig);
            }
        }
    

    这里是实例化类RequestSpecificRetryHandler这个类。这个类我们看下他的构造函数:

    public RequestSpecificRetryHandler(boolean okToRetryOnConnectErrors, boolean okToRetryOnAllErrors, RetryHandler baseRetryHandler, @Nullable IClientConfig requestConfig) {
            Preconditions.checkNotNull(baseRetryHandler);
            this.okToRetryOnConnectErrors = okToRetryOnConnectErrors;
            this.okToRetryOnAllErrors = okToRetryOnAllErrors;
            this.fallback = baseRetryHandler;
            if (requestConfig != null) {
                if (requestConfig.containsProperty(CommonClientConfigKey.MaxAutoRetries)) {
                    retrySameServer = requestConfig.get(CommonClientConfigKey.MaxAutoRetries); 
                }
                if (requestConfig.containsProperty(CommonClientConfigKey.MaxAutoRetriesNextServer)) {
                    retryNextServer = requestConfig.get(CommonClientConfigKey.MaxAutoRetriesNextServer); 
                } 
            }
        }
    

    这里默认的okToRetryOnConnectErrors=true;okToRetryOnAllErrors=true。也就是说在链接错误的时候,会去重试;在所有error发生的时候回去重试。这里重试的handler是defaultLoadBalancerRetryHandler(我们稍后来看)。我们接着跟进去,发现这行代码:

    retryNextServer = requestConfig.get(CommonClientConfigKey.MaxAutoRetriesNextServer);
    

    也就是说这个值是从requestConfig里面来的。那么requestConfig是什么?我们返回之前的代码可以看到,他是在LoadBalancerFeignClient类中的方法构造出来的:

    IClientConfig getClientConfig(Request.Options options, String clientName) {
            IClientConfig requestConfig;
            if (options == DEFAULT_OPTIONS) {
                requestConfig = this.clientFactory.getClientConfig(clientName);
            } else {
                requestConfig = new FeignOptionsClientConfig(options);
            }
            return requestConfig;
        }
    

    这里我们没有自己配置,所以采用了默认的,也就是说走到了options == DEFAULT_OPTIONS这个分支里。那么如果走else分支,我们是否可以修改这个变量的值,我们看下FeignOptionsClientConfig这个类:

    static class FeignOptionsClientConfig extends DefaultClientConfigImpl {
    
            public FeignOptionsClientConfig(Request.Options options) {
                setProperty(CommonClientConfigKey.ConnectTimeout,
                        options.connectTimeoutMillis());
                setProperty(CommonClientConfigKey.ReadTimeout, options.readTimeoutMillis());
            }
    
            @Override
            public void loadProperties(String clientName) {
    
            }
    
            @Override
            public void loadDefaultValues() {
    
            }
    
        }
    

    这个构造函数顶多是重置了connectTimeout和readtimeout的值。Request.Options中也只有着两个值。问题到这里告一段落。我们继续看父类方法:DefaultClientConfigImpl.java

    protected Object getProperty(String key) {
            if (enableDynamicProperties) {
                String dynamicValue = null;
                DynamicStringProperty dynamicProperty = dynamicProperties.get(key);
                if (dynamicProperty != null) {
                    dynamicValue = dynamicProperty.get();
                }
                if (dynamicValue == null) {
                    dynamicValue = DynamicProperty.getInstance(getConfigKey(key)).getString();
                    if (dynamicValue == null) {
                        dynamicValue = DynamicProperty.getInstance(getDefaultPropName(key)).getString();
                    }
                }
                if (dynamicValue != null) {
                    return dynamicValue;
                }
            }
            return properties.get(key);
        }
    

    这个方法是关键。获取properties中的变量。{server_name}.ribbon.{property}。大功告成。

    在properties中添加:

    ribbon:
          MaxAutoRetriesNextServer: -1
    

    跟进代码,看到这个值已经改变,这个时候看了下代码,对于请求只会发生一次了。

    • 对于使用feign的应用,如果设置超时时间,可以这么来做
    @SpringBootApplication
    @EnableEurekaClient
    @EnableFeignClients
    public class EurekaClientApplication {
        public static void main(String[] args) {
            SpringApplication.run(EurekaClientApplication.class,args);
        }
    
        @Bean
        Request.Options options() {
            return new Request.Options(1000*10,40*1000);
        }
    }
    

    重写options类。

    相关文章

      网友评论

        本文标题:springcloud-eureka的重试策略

        本文链接:https://www.haomeiwen.com/subject/jbslwftx.html