Feign 微服务间的声明式调用
Feign是一个声明式的web service客户端,它使得编写web service客户端更为容易。创建接口,为接口添加注解,即可使用Feign,简而言之,它可以帮助我们封装http请求,使得我们可以像调用本地方法一样地请求其他服务的接口。
引包: compile group: 'org.springframework.cloud', name: 'spring-cloud-starter-openfeign'
启用:@EnableFeignClients
示例:
@FeignClient(name = "${feign.name}", url = "${feign.url}")
public interface StoreClient {
//..
}
contextId:
用于一个服务下多个接口可拆分到不同类中,将同名bean(如下为store)注册到不同的context下,否则就会报bean名称冲突
@FeignClient(name = "store", contextId="store/goods", url = "${feign.url}")
public interface StoreClient1 {
//..
}
@FeignClient(name = store", contextId="store/cash", url = "${feign.url}")
public interface StoreClient2 {
//..
}
服务间调用时FeignClient的两种设计思路:
-
由服务提供方将自己要暴露给其他方的方法封装进FeignClient,打成jar包,以api的方式让服务使用方引入jar包去调用,这样的好处是:
-
jar包内的接口做为服务提供者的一种能力暴露给外界,使用者可以明确知道自己有哪些方法可用;
-
只要服务提供方编写一次,多个使用者可用同一个jar包,省去了每个使用者封装feignclient的工作量
-
-
服务使用者在自己的服务内封装FeginClient, 这样的好处是使用者可以通过feignclient的fallback相关属性,配置方法降级后的返回
服务降级fallback的配置
两种方式: 1.Fallback.class
@FeignClient(name = "test", url = "http://localhost:${server.port}/", fallback = Fallback.class)
protected interface TestClient {
@RequestMapping(method = RequestMethod.GET, value = "/hello")
Hello getHello();
@RequestMapping(method = RequestMethod.GET, value = "/hellonotfound")
String getException();
}
@Component
static class Fallback implements TestClient {
@Override
public Hello getHello() {
throw new NoFallbackAvailableException("Boom!", new RuntimeException());
}
@Override
public String getException() {
return "Fixed response";
}
}
}
2.FallbackFactory.class, 如果需要捕获异常的话,需要使用这种方式
@FeignClient(name = "testClientWithFactory", url = "http://localhost:${server.port}/",
fallbackFactory = TestFallbackFactory.class)
protected interface TestClientWithFactory {
@RequestMapping(method = RequestMethod.GET, value = "/hello")
Hello getHello();
@RequestMapping(method = RequestMethod.GET, value = "/hellonotfound")
String getException();
}
@Component
static class TestFallbackFactory implements FallbackFactory<FallbackWithFactory> {
@Override
public FallbackWithFactory create(Throwable cause) {
return new FallbackWithFactory();
}
}
static class FallbackWithFactory implements TestClientWithFactory {
@Override
public Hello getHello() {
throw new NoFallbackAvailableException("Boom!", new RuntimeException());
}
@Override
public String getException() {
return "Fixed response";
}
}
}
Hystrix 服务的降级和熔断
引包: compile group: 'org.springframework.cloud', name: 'spring-cloud-starter-netflix-hystrix
当服务的提供方出现问题时,通过hystrix可以让服务的使用方进行容错处理,避免错误在整个服务链条中蔓延。
如果要使用Hystrix,需要在feign配置中启用它:feign.hystrix.enabled = true
降级: 调用服务失败时, 可以使用fallback方法返回一个托底数据
熔断: 当调用失败达到某种设定好的阈值时,如一分钟内有70%的请求失败,便不再请求服务提供者的接口,直接返回托底数据,处在熔断状态时,则每次请求都直接返回托底数据,不再向服务提供者发请求
半熔断: 半熔断是一种恢复机制,在熔断了一定的时间后,会再将请求发往服务提供方,如果收到成功响应,则关闭熔断
隔离策略: 当我们使用了Hystrix时,Hystrix将所有的外部调用都封装成一个HystrixCommand或者HystrixObservableCommand对象,这些外部调用将会在一个独立的线程中运行。我们可以将出现问题的服务通过熔断、降级等手段隔离开来,这样不影响整个系统的主业务。
Hystrix的处理的流程图如下:
常用配置:
hystrix:
command:
#全局默认配置
default:
#线程隔离相关
execution:
timeout:
#是否给方法执行设置超时时间(降级相关),默认为true
enabled: true
isolation:
#配置请求隔离的方式,这里是默认的线程池方式。还有一种信号量的方式semaphore
strategy: threadPool
thread:
#方式执行的超时时间,默认为1000毫秒,在实际场景中需要根据情况设置
timeoutInMilliseconds: 10000
circuitBreaker: #熔断器相关配置
enabled: true #是否启动熔断器
requestVolumeThreshold: 20 #该属性设置滚动窗口中熔断的最小失败请求数量,如果此属性值为20,则在窗口时间内(如10s内),如果只收到19个请求且都失败了,则断路器也不会开启,默认值:20
sleepWindowInMilliseconds: 5000 #熔断后,在此值的时间的内,hystrix会拒绝新的请求,只有过了这个时间断路器才会重新超时发送请求
errorThresholdPercentage: 50 #设置失败百分比的阈值。如果失败比率超过这个值,则熔断
Ribbon 客户端负载均衡
引包: compile group: 'org.springframework.cloud', name: 'spring-cloud-starter-netflix-ribbon'
负载均衡可分为客户端负载均衡和服务端负载均衡。
如nginx就是服务端负载均衡的代表,nginx收到某个请求时,根据配置的负载均衡策略,从该请求域名对应的机器中选择一个,然后把请求发往这个机器。
而客户端负载均衡,则是各个微服务都注册到一个注册中心,如consul,而服务A本地维护着注册中心各个服务对应各个机器的列表,当服务A要请求服务B的服务时,它要根据负载均衡的策略,从服务B对应的机器中选择一个,发起请求。Ribbon要解决的问题就是客户端的负载均衡。
常用配置:
ribbon:
# 当前实例上重试次数,不包含第一次请求,默认0
MaxAutoRetries: 5
# 切换实例的个数,不包含第一次请求的实例,如果服务注册列表小于配置值,那么会循环请求 A > B > A,默认1
MaxAutoRetriesNextServer: 3
#是否所有操作都进行重试
OkToRetryOnAllOperations: false
#连接超时时间,单位为毫秒
ConnectTimeout: 3000
#读取的超时时间,单位为毫秒
ReadTimeout: 3000
# 实例配置
clientName:
ribbon:
MaxAutoRetries: 5
MaxAutoRetriesNextServer: 3
OkToRetryOnAllOperations: false
ConnectTimeout: 3000
ReadTimeout: 3000
OkToRetryOnAllOperations: 当该值为false时,只有Get请求会重试,当该值为true时,所有的请求都会重试,包括post,put等。 所以该值的默认值设定是false,就是为了防止post,put方法重试时,多次修改数据,而接口没有实现幂等性,导致数据出错。
我们在写接口时也要注意,不要在get请求的逻辑里去修改数据,否则重试机制可能导致接口多次调用,数据多次被修改造成不一致。
这些组件的超时时间之间有什么关系,该如何设置?
单次http请求超时时间: Ribbon 和 Feign的超时时间(ConnectTimeout,ReadTimeout)都是控制单次Http请求超时的,如果同时配置了Feign和Ribbon的超时时间,Feign的超时配置会覆盖Ribbon的配置。
实际测试中feign和配置如下图:
在RetryableFeignLoadBalancer中会进行配置的覆盖,如果有feign的配置,就覆盖ribbon的,如下:
一次请求的总耗时限制为:
http请求总次数: 请求的总次数是根据ribbon的MaxAutoRetries(当前实例上重试次数,重试次数是不算第一次的)和MaxAutoRetriesNextServer(切换实例的个数,当然也是不算第一次请求的实例)来计算的,如果给单独的feignClient配置了,单独配置的优先级是高于全局的配置的。请求总次数为:
Ribbon重试总时间限制: 但根据Ribbon的配置,每个请求耗时ReqTime,共请求TotalNum次,那么请求的总时间限制应该为:
实际重试总耗时: 正常的请求ActualConnectTime和ActualReadTime应该是小于我们配置的超时时间的(如果不是,那配置就有问题,需要调整),可以重试的次数仍需要遵循ribbon配置的限制,所以实际的耗时应该为:
Hystrix超时时间: Hystrix的超时配置execution.isolation.thread.timeoutInMilliseconds(以下简称HystrixTimeout),是用来控制何时降级的,超过这个时间就返回fallback数据了。
那么到底什么时候会降级呢?
在请求超时的条件下,每一次请求都会把Ribbon的超时时间配置花光,所以降级时间为:
在请求没超时,但是请求返回码非200的情况下,降级时间为:
综合来看,降级时间应为:
所以在实际的项目配置中,HystrixTimeout 应该稍微大于 TotalRibbonLimitTime, 如果HystrixTimeout < TotalRibbonLimitTime, 那么重试还没结束就降级返回托底数据了,但重试依然会进行,后面的重试即使成功也没有用了,但HystrixTimeout设置的远大于TotalRibbonLimitTime也没有用,因为实际的fallback时间为三个时间重较小的那一个,Min(ActualToTalTime,TotalRibbonLimitTime, HystrixTimeout)。
另外,如果服务方在feign超时时间限制内返回了结果,但返回状态码非200(400,500等),也不会进行重试,因为Ribbon的重试机制会判断非200返回的状态码是否在可重试状态码列表中,这个列表通过retryableStatusCodes来配置,retryableStatusCodes默认值是空。