Spring Cloud Feign是一套基于Netflix Feign实现的声明式服务调用客户端,目的是让Web Service调用更加简单,使用HTTP请求远程服务时能与调用本地方法一样的编码体验。
工作机制
提供了HTTP请求模板,通过编写简单的接口和插入注解,就可完成对Web服务接口的绑定。扩展了对Spring MVC注解的支持,整合了Ribbon和Hystrix。内部机制是使用RestTemplate来实现。
服务调用
配置 Feign+Ribbon+Hystrix,消费服务。因为 Spring Cloud 的封装,我们几乎不需要关心 Ribbon 和 Hystrix 的细节,只需要进行 Feign 的配置即可。
- pom依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
- 配置application.properties
spring.application.name=service-consumer
eureka.client.serviceUrl.defaultZone=http://localhost:8000/eureka
-
启动类添加 @EnableFeignClients 、@EnableDiscoveryClient注解,开启Eureka Client和Feign的相关功能
-
创建@FeignClient接口,统一声明依赖的服务
- 有url参数也要设置name
- configuration 参数可添加设定配置
- fallback 参数开启Hystrix 并定义Hystrix fallback
- 默认,@RequestParam注解的参数转换成字符串添加到URL中,将没有注解的参数通过Jackson转换成json放到请求体中。如果请求方式指定为POST,那么所有未标注解的参数将会被忽略。
- 在Spring Cloud环境下,Feign的Encoder只会用来编码没有添加注解的参数。
Client
//指定feign调用的服务和Hystrix Fallback
@FeignClient(name= "eureka-provider",fallback = ConsumerFallback.class,url = "http://localhost:8761/",configuration = EurekaConfiguration.class)
public interface ConsumerClient {
//对应服务方提供的接口
@RequestMapping(value = "/index", method = RequestMethod.GET)
String consumer();
}
Fallback
//Hystrix Fallback
@Component
public class ConsumerFallbackimplements Consumer{
@Override
public String index()
{
return "Feign客户端访问失败";
}
}
Configuration
@Configuration
public class EurekaConfiguration {
@Bean
public BasicAuthRequestInterceptor basicAuthRequestInterceptor() {
return new BasicAuthRequestInterceptor("zhihao.miao", "123456");
}
@Bean
public Encoder feignEncoder() {
HttpMessageConverter jacksonConverter = new MappingJackson2HttpMessageConverter(new ObjectMapper());
ObjectFactory<HttpMessageConverters> objectFactory = () -> new HttpMessageConverters(jacksonConverter);
return new SpringEncoder(objectFactory);
}
@Bean
public FeignErrorDecoder errorDecoder() {
return new FeignErrorDecoder();
}
}
public class FeignErrorDecoder implements ErrorDecoder {
@Override
public Exception decode(String methodKey, Response response) {
HttpStatus status = HttpStatus.valueOf(response.status());
byte[] responseBody;
try {
responseBody = IOUtils.toByteArray(response.body().asInputStream());
} catch (IOException e) {
throw new RuntimeException("Failed to process response body.", e);
}
if (status.is5xxServerError()) {
return new HttpServerErrorException(status, response.reason(), responseBody, null);
} else if (status.is4xxClientError()) {
return new HttpClientErrorException(status, response.reason(), responseBody, null);
}
return FeignException.errorStatus(methodKey, response);
}
}
- 通过接口调用服务
@RestController
public class DcController {
@Autowired
ConsumerClient consumerClient;
@GetMapping("/consumer")
public String index() {
return consumerClient .consumer();
}
}
第一次请求失败
Hystrix默认的超时时间是1秒,如果超过这个时间尚未响应,将会进入fallback代码。而首次请求往往会比较慢(Spring的懒加载机制)。解决方案有三种,以feign为例。
- 让Hystrix的超时时间改为5秒
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 5000
- 该配置,用于禁用Hystrix的超时时间
hystrix.command.default.execution.timeout.enabled: false
- 索性禁用feign的hystrix
feign.hystrix.enabled: false
处理异常
如果在FeignClient调用接口,接口服务出现异常的情况下需要提取异常信息,可以使用fallbackFactory。
@FeignClient(value = "bClient", url = "${interfase.user.management}", fallbackFactory = IBClientServiceFallBackFactory.class)
public interface IBClientService {
@RequestMapping(value = "/qual/company", method = RequestMethod.GET)
Result<BCompanyAuthInfo> getCompanyAuthInfo(@RequestHeader("cookie") String cookie);
}
@Slf4j
@Component
class IBClientServiceFallBackFactory implements FallbackFactory<IBClientService> {
@Override
public IBClientService create(Throwable cause) {
return new IBClientService() {
@Override
public Result<BCompanyAuthInfo> getCompanyAuthInfo(String cookie) {
log.error(cookie + cause.getMessage());
Result<BCompanyAuthInfo> res = new Result<>();
res.setCode(ResultCode.FAIL.getCode());
res.setMessage(cause.getMessage());
return res;
}
};
}
}
保留原始异常信息
当调用服务时,如果服务返回的状态码不是200,就会进入到Feign的ErrorDecoder中,因此如果我们要解析异常信息,就要重写ErrorDecoder
public class KeepErrMsgConfiguration {
@Bean
public ErrorDecoder errorDecoder() {
return new UserErrorDecoder();
}
/**
* 自定义错误解码器
*/
public class UserErrorDecoder implements ErrorDecoder {
private Logger logger = LoggerFactory.getLogger(getClass());
@Override
public Exception decode(String methodKey, Response response) {
Exception exception = null;
try {
// 获取原始的返回内容
String json = Util.toString(response.body().asReader());
exception = new RuntimeException(json);
// 将返回内容反序列化为Result,这里应根据自身项目作修改
Result result = JsonMapper.nonEmptyMapper().fromJson(json, Result.class);
// 业务异常抛出简单的 RuntimeException,保留原来错误信息
if (!result.isSuccess()) {
exception = new RuntimeException(result.getMessage());
}
} catch (IOException ex) {
logger.error(ex.getMessage(), ex);
}
return exception;
}
}
}
在@FeignClient注解中指定configuration
@FeignClient(name = "serviceId", fallbackFactory = TestServiceFallback.class, configuration = {KeepErrMsgConfiguration.class})
public interface TestService {
@RequestMapping(value = "/get/{id}", method = RequestMethod.GET)
String get(@PathVariable("id") Integer id);
}
超时设置
线程池设置
hystrix.threadpool.default.coreSize=100
hystrix.threadpool.default.maxQueueSize=1000
断路器超时设置和请求的超时
hystrix.command.default.execution.timeout.enabled=true
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=300000
ribbon.ConnectTimeout=300000
ribbon.ReadTimeout=300000
设置回退的最大线程数
hystrix.command.default.fallback.isolation.semaphore.maxConcurrentRequests=50
HTTP Client
Feign在默认情况下使用的是JDK原生的URLConnection发送HTTP请求,没有连接池,但是对每个地址会保持一个长连接,即利用HTTP的persistence connection 。
Apache的HTTP Client替换Feign原始的http client, 从而获取连接池、超时时间等与性能息息相关的控制能力。Spring Cloud从Brixtion.SR5版本开始支持这种替换,首先在项目中声明Apache HTTP Client和feign-httpclient依赖:
<!-- 使用Apache HttpClient替换Feign原生httpclient -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
<dependency>
<groupId>com.netflix.feign</groupId>
<artifactId>feign-httpclient</artifactId>
<version>${feign-httpclient}</version>
</dependency>
然后在application.properties中添加:
feign.httpclient.enabled=true
还可以替换OkHttpClient
feign.okhttp.enabled=true
日志
Feign的日志可以通过修改配置文件来实现
logging:
level:
project.user.UserClient: DEBUG
- NONE,无记录(DEFAULT)。
- BASIC,只记录请求方法和URL以及响应状态代码和执行时间。
- HEADERS,记录基本信息以及请求和响应标头。
- FULL,记录请求和响应的头文件,正文和元数据
Configuration里
@Bean
public Logger.Level feignLoggerLevel() {
//设置日志
return Logger.Level.FULL;
}