spring cloud中有几个重要的组件,深入理解它们之间的关系才能更好的使用它们:
上述ribbon和hystrix都是netflix贡献组件,目前它们都处于维护模式,不再增加新特性,将逐渐被spring cloud官方组件取代,例如从Hoxton.M2开始整合spring-cloud-loadbalancer用于替换ribbon,但目前还不成熟,还是老老实实用ribbon,而断路器方面spring cloud抽象了Spring Cloud Circuit Breaker,hystrix只是其中一个实现,还有其他实现可选,例如阿里贡献的sentinel。
三者关系
hystrix和ribbon并没有直接关系。
feign底层默认是通过ribbon进行服务定位和负载均衡,使用feign时你感知不到ribbon的存在,也可以不使用ribbon。如果你使用resttemplate,则需要通过@LoadBalanced使用ribbon。
hystrix有两种使用情况,一种是在controller的handler方法上增加@HystrixCommand注解,作用的是整个handler;另一种情况是调用其他app服务时,也就是@feignclient注解的http客户端,此时调用这个http客户端的handler可不需要hystrix。
总结一下:服务之间的http调用可通过feign实现,feign底层是通过ribbon实现服务发现和负载均衡,不管是否使用feign,ribbon都是必不可少的;feign客户端可选的可启用hystrix支持,hystrix也可以用在服务端整个handler上。
如上所示,app1.controll1.handler1调用app2.controller2.handler2的过程是:
如果feign.hystrix.enabled=true(默认为false),则feign通过jdk动态代理,将调用封装为HystrixCommand,在hystrix thread pool中执行,否则进入3和4;
hystrix thread pool中执行http调用,还是回调feign接口;
feign扩展了ribbon客户端,使用ribbon的服务定位和负载均衡获得可用服务;
feign扩展的ribbon客户端发起对app2.controller2.handler2的http请求,ribbon可以开启重试,如果请求超时则自动重试。
了解上述关系对于如何设置超时时间至关重要。如果hystrix的超时时间到达,则1就返回fallback了,不会等到4执行完。一般hystrix的超时时间要大于feign的超时时间。
另外上述服务端设置了断路器,实际上客户端可以不用设置。
超时相关配置
feign
官方参考
有两种配置方式:
属性配置,包括全局和实例;
代码配置,也包括全局和实例。
优先级:实例>全局,属性>代码
属性总是优先的,可以设置feign.client.default-to-properties为false,使得代码配置优先。
全局属性配置名默认是default,可以设置feign.client.default-config为其他名字。
属性配置
1 2 3 4 5 6 7 8 9 10 11 12 13
feign: hystrix: enabled: true client: config: # 全局配置 default: connectTimeout: 5000 readTimeout: 5000 # 实例配置,feignName即@feignclient中的value,也就是服务名 feignName: connectTimeout: 5000 readTimeout: 5000
代码配置
全局代码配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
package com.example.microservice2;import org.springframework.cloud.openfeign.support.SpringMvcContract;import org.springframework.context.annotation.Bean;import feign.Request;import feign.Retryer;public class FeignClientConfiguration { @Bean public Request.Options feignRequestOptions () { // 默认连接超时10秒,读取超时60秒 return new Request.Options(); } // 默认重试是关闭的 @Bean public Retryer feignRetry () { // 默认重试5次,首次间隔100毫秒,最大间隔1秒 return new Retryer.Default(); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
package com.example.microservice2;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cloud.client.discovery.EnableDiscoveryClient;import org.springframework.cloud.openfeign.EnableFeignClients;@SpringBootApplication @EnableDiscoveryClient //开启注册服务 @EnableFeignClients (defaultConfiguration = FeignClientConfiguration.class) //开启feign消费服务 public class DemoApplication { public static void main (String[] args) { SpringApplication.run(DemoApplication.class, args); } }
实例代码配置
1 2 3 4 5 6 7 8 9 10 11 12
package com.example.microservice2;import org.springframework.cloud.openfeign.FeignClient;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestMethod;import org.springframework.web.bind.annotation.RequestParam;@FeignClient (value = "microservice1" , fallback = HelloServiceHystric.class, configuration = FeignClientConfiguration.class) // 访问微服务1,指定断路器类 public interface HelloService { @RequestMapping (value = "/hello" , method = RequestMethod.GET) String hello (@RequestParam(value = "name" ) String name) ; }
hystrix
官方参考
hystrix有四种配置方式:
全局代码默认属性;
全局属性配置;
实例代码配置;
实例属性配置。
优先级:1<2<3<4
和feign一样,也是属性优先代码,实例优先全局。
属性配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
hystrix: command: #全局默认配置 default: #线程隔离相关 execution: timeout: #是否给方法执行设置超时时间,默认为true。一般我们不要改。 enabled: true isolation: #配置请求隔离的方式,这里是默认的线程池方式。还有一种信号量的方式semaphore。 strategy: THREAD thread: #方式执行的超时时间,默认为1000毫秒,在实际场景中需要根据情况设置 timeoutInMilliseconds: 10000 # 实例配置 HystrixCommandKey: execution: timeout: enabled: true isolation: strategy: THREAD thread: timeoutInMilliseconds: 10000
代码配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
@RefreshScope // 开启配置更新 @RestController @Slf 4jpublic class HelloController { @Value ("${serviceName}" ) // 读配置文件的这个属性 private String serviceName; @RequestMapping ("/hello" ) @HystrixCommand (fallbackMethod = "helloError" , commandProperties = { @HystrixProperty (name = "execution.isolation.thread.timeoutInMilliseconds" , value = "4000" ) }) // 指定断路器方法,断路器监控用 public String hello (HttpServletRequest request, @RequestParam(value = "name" , defaultValue = "Hugo" ) String name) throws InterruptedException { log.info("ServerName:{} time:{}" , request.getServerName(), DateFormatUtils.format(new Date(), "yyyyMMdd HH:mm:ss" )); TimeUnit.SECONDS.sleep(4 ); return "hello " + name + ", my name is " + serviceName; } public String helloError (HttpServletRequest request, String name) { return "microservice1 hystrix," + name + "!" ; } }
feign中的hystrix怎么配置
只能通过属性设置,那么commandkey是什么呢?
1 2 3 4 5 6 7
package com.example.microservice2;
@FeignClient (value = "microservice1" , fallback = HelloServiceHystric.class, configuration = FeignClientConfiguration.class) // 访问微服务1,指定断路器类 public interface HelloService { @RequestMapping (value = "/hello" , method = RequestMethod.GET) String hello (@RequestParam(value = "name" ) String name) ; }
上例中默认行为如下,groupkey为"microservice1",commandkey为"HelloService#hello(String)",threadpoolkey为null。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
public interface SetterFactory { /** * Returns a hystrix setter appropriate for the given target and method */ HystrixCommand.Setter create (Target<?> target, Method method) ; /** * Default behavior is to derive the group key from {@link Target#name()} and the command key from * {@link Feign#configKey(Class, Method)}. */ final class Default implements SetterFactory { @Override public HystrixCommand.Setter create (Target<?> target, Method method) { String groupKey = target.name(); String commandKey = Feign.configKey(target.type(), method); return HystrixCommand.Setter .withGroupKey(HystrixCommandGroupKey.Factory.asKey(groupKey)) .andCommandKey(HystrixCommandKey.Factory.asKey(commandKey)); } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
public abstract class Feign { public static String configKey (Class targetType, Method method) { StringBuilder builder = new StringBuilder(); builder.append(targetType.getSimpleName()); builder.append('#' ).append(method.getName()).append('(' ); for (Type param : method.getGenericParameterTypes()) { param = Types.resolve(targetType, targetType, param); builder.append(Types.getRawType(param).getSimpleName()).append(',' ); } if (method.getParameterTypes().length > 0 ) { builder.deleteCharAt(builder.length() - 1 ); } return builder.append(')' ).toString(); } }
所以这样设置:
1 2 3 4 5 6 7 8 9 10
hystrix: command: HelloService#hello(String): execution: timeout: enabled: true isolation: strategy: THREAD thread: timeoutInMilliseconds: 10000
也可以改变上述commandkey的默认行为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
package com.example.microservice2;import java.lang.reflect.Method;import com.netflix.hystrix.HystrixCommand;import com.netflix.hystrix.HystrixCommandGroupKey;import com.netflix.hystrix.HystrixCommandKey;import org.springframework.context.annotation.Bean;import feign.Target;import feign.hystrix.SetterFactory;public class FeignClientConfiguration { @Bean public SetterFactory feignHystrixSetter () { return new MySetterFactory(); } }class MySetterFactory implements SetterFactory { @Override public HystrixCommand.Setter create (Target<?> target, Method method) { String groupKey = target.name(); String commandKey = target.type().getName(); return HystrixCommand.Setter .withGroupKey(HystrixCommandGroupKey.Factory.asKey(groupKey)) .andCommandKey(HystrixCommandKey.Factory.asKey(commandKey)); } }
这样commandkey就会变成"com.example.microservice2.HelloService"。
ribbon
官方参考
ribbon只有属性配置,同样存在全局和实例配置,格式如下:
1
<clientName>.<nameSpace>.<propertyName>=<value>
nameSpace是可配置的,默认为ribbon。clientName可为远端服务名,即@feignclient的value,空表示全局配置。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
# 全局配置 ribbon: # 服务最大重试次数,不包含第一次请求,默认0 MaxAutoRetries: 5 # 负载均衡切换次数,如果服务注册列表小于 nextServer count 那么会循环请求 A > B > A,默认1 MaxAutoRetriesNextServer: 3 #是否所有操作都进行重试 OkToRetryOnAllOperations: false #连接超时时间,单位为毫秒,默认2秒 ConnectTimeout: 3000 #读取的超时时间,单位为毫秒,默认5秒 ReadTimeout: 3000 # 实例配置 clientName: ribbon: MaxAutoRetries: 5 MaxAutoRetriesNextServer: 3 OkToRetryOnAllOperations: false ConnectTimeout: 3000 ReadTimeout: 3000
超时时间关系
feign超时
feign可以设置自身超时,也可以设置ribbon超时,那么它们的关系是怎么样的?看feign代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
public class LoadBalancerFeignClient implements Client { static final Request.Options DEFAULT_OPTIONS = new Request.Options(); 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; } }
如果没有设置过feign超时,也就是等于默认值的时候,就会读取ribbon的配置,使用ribbon的超时时间和重试设置。否则使用feign自身的设置。两者是二选一的,且feign优先。
建议使用ribbon超时设置。
feign重试和ribbon重试
feign自身重试目前只有一个简单的实现Retryer.Default,包含三个属性:
maxAttempts:重试次数,包含第一次
period:重试初始间隔时间,单位毫秒
maxPeriod:重试最大间隔时间,单位毫秒
重试间隔算法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
public interface Retryer extends Cloneable { class Default implements Retryer { /** * Calculates the time interval to a retry attempt. <br> * The interval increases exponentially with each attempt, at a rate of nextInterval *= 1.5 * (where 1.5 is the backoff factor), to the maximum interval. * * @return time in nanoseconds from now until the next attempt. */ long nextMaxInterval () { long interval = (long ) (period * Math.pow(1.5 , attempt - 1 )); return interval > maxPeriod ? maxPeriod : interval; } } }
第一次重试间隔period,第二次period*1.5,第三次period*1.5*1.5,…,最大值不超过maxPeriod。
和ribbon的重试相比:
重试次数包含了首次;
不能设置多实例服务切换;
重试有一个延迟时间。
feign超时和hystrix超时
hystrix的超时时间要大于feign的,否则没有等到feign超时,hystrix就fallback了,特别是重试机制会无法起作用。