我的博客:兰陵笑笑生,欢迎浏览博客!
上一章 SpringCloud基础教程(六)-负载均衡Ribbon当中,我们介绍了Ribbon在微服务中是如何做到负载均衡的,本期我们将在此基础上使用Fegin更加简化的实现服务间的调用。
前言
什么是Fegin,在解释之前,我们先梳理一下我们之前学习到的,在微服模式下,解决服务间的调用可以通过Grpc、HttpClient、(Spring中的resttemplate是对HttpClient的封装)等开源框架,这种调用我们称之为远程过程的调用,即RPC,那么进行RPC调用需要解决几个重要的问题,一个是序列化/反序列化,比如Json/xml等怎样序列化和反序列化等,再一个就是以什么样的协议实现这样的调用。这两个问题在开源社区都有了很好的技术方案。那么Spring Cloud Fegin主要是为了更简单的实现开发,封装了Http的调用流程,更适合面向接口化编程的习惯。我们虽然能通过Ribbon和RestTemplate通过URL进行远程调用,但是这样拼接参数,并不是特别的优雅,为此,我们可以通过使用Feign让远程调用变的更简洁。
一、快速使用Feign
我们在上几章的服务调用方server-consumer示例的pom文件中添加Feign的依赖 (我使用的spring Cloud版本是Greenwich.SR3版本,不同的Feign依赖可能不太一样):
org.springframework.cloud
spring-cloud-starter-openfeign
接着我们在调用方主类上添加@EnableFeignClients 表示客户端允许开启使用Feign调用。
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
public class ServerConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ServerConsumerApplication.class, args);
}
}
然后定义Feign的接口,FeignClient(name = "server-provider") name 指的是在Eureka注册的服务名称,也是可以直接使用ip的,如果指定了url ,那么name就是只是一个标识。
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(name = "server-provider")
public interface HelloApi {
@RequestMapping("/sayHello")
public String sayHello(@RequestParam("name") String name);
}
编写控制器注入接口,调用:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class FeignController {
@Autowired
HelloApi helloApi;
@GetMapping("/sayHello")
public String sayHello(String name) {
String res = helloApi.sayHello(name);
return res;
}
}
服务的提供方server-provider代码不变:
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class RibbonController {
@Value("${server.port}")
private String port;
@RequestMapping("/sayHello")
public String sayHello(String name) {
return "from:"+port+ ",hello!,"+name;
}
}
之后,我们启动Eureka注册中心,启动服务提供者,启动修改的服务调用者,查看Eureka注册中心:有一个服务调用方,2个名称为server-provider的服务提供方:
通过HTTP方式http://localhost:5168/sayHello?name=test 我们可以正确的返回信息,并且通过多次的调用,默认的负载均衡方式是轮询,并且集成了Ribbon,如果我们想要修改负载均衡的策略,可以参考上一章
SpringCloud基础教程(六)-负载均衡Ribbon 实现。
通过以上的方式,我们可以很方便的把一个HTTP的请求转换为非常友好的、接口的方式。
二、Fegin的进阶
在引入了spring-cloud-starter-openfeign之后,我们可以点击进入配置文件查看到底依赖了那些项目,它主要是集成了 io.github.OpenFeign:
org.springframework.cloud
spring-cloud-starter
org.springframework.cloud
spring-cloud-openfeign-core
org.springframework
spring-web
org.springframework.cloud
spring-cloud-commons
io.github.openfeign
feign-core
io.github.openfeign
feign-slf4j
io.github.openfeign
feign-hystrix
io.github.openfeign
feign-java8
org.springframework.cloud
spring-cloud-starter-netflix-ribbon
true
org.springframework.cloud
spring-cloud-starter-netflix-archaius
true
在spring-cloud-openfeign-core依赖中,org.springframework.cloud.openfeign.FeignClientsConfiguration是Feign的默认配置类:
我们到底可以配置那些内容呢?我们先看看具体的源代码:
/**
* @author Dave Syer
* @author Venil Noronha
*/
@Configuration
public class FeignClientsConfiguration {
@Autowired
private ObjectFactory messageConverters;
@Autowired(required = false)
private List parameterProcessors = new ArrayList<>();
@Autowired(required = false)
private List feignFormatterRegistrars = new ArrayList<>();
@Autowired(required = false)
private Logger logger;
//反序列化
@Bean
@ConditionalOnMissingBean
public Decoder feignDecoder() {
return new OptionalDecoder(
new ResponseEntityDecoder(new SpringDecoder(this.messageConverters)));
}
//序列化
@Bean
@ConditionalOnMissingBean
@ConditionalOnMissingClass("org.springframework.data.domain.Pageable")
public Encoder feignEncoder() {
return new SpringEncoder(this.messageConverters);
}
@Bean
@ConditionalOnClass(name = "org.springframework.data.domain.Pageable")
@ConditionalOnMissingBean
public Encoder feignEncoderPageable() {
return new PageableSpringEncoder(new SpringEncoder(this.messageConverters));
}
//接口的验证等
@Bean
@ConditionalOnMissingBean
public Contract feignContract(ConversionService feignConversionService) {
return new SpringMvcContract(this.parameterProcessors, feignConversionService);
}
@Bean
public FormattingConversionService feignConversionService() {
FormattingConversionService conversionService = new DefaultFormattingConversionService();
for (FeignFormatterRegistrar feignFormatterRegistrar : this.feignFormatterRegistrars) {
feignFormatterRegistrar.registerFormatters(conversionService);
}
return conversionService;
}
@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(this.logger);
}
@Bean
@ConditionalOnClass(name = "org.springframework.data.domain.Page")
public Module pageJacksonModule() {
return new PageJacksonModule();
}
//这里我们可以配置集成 Hystrix
@Configuration
@ConditionalOnClass({ HystrixCommand.class, HystrixFeign.class })
protected static class HystrixFeignConfiguration {
@Bean
@Scope("prototype")
@ConditionalOnMissingBean
@ConditionalOnProperty(name = "feign.hystrix.enabled")
public Feign.Builder feignHystrixBuilder() {
return HystrixFeign.builder();
}
}
}
从源代码当中,我们提取以下几个配置,这些我们都可以去自定义的配置的:
- Decoder :反序列化,默认是ResponseEntityDecoder
- Encoder:序列化,默认是SpringEncoder
- Logger:日志的实现,默认是 io.github.OpenFeign中feign.Logger接口,实现是Slf4jLogger
- Contract:验证那些接口和值允许定义在接口上
- Client:具体发起HTTP请求的客户端,默认的事使用LoanBalancerFeignClient,通过上文我们可以判断,Feign默认是支持负载均衡的,当然我们也可以自己配置,支持Ribbon
2.1 自定义配置Feign,修改日志级别
服务的调用方主类,如果我们修改负载均衡的策略,添加@RibbonClient开启,参考上一章,其他的不变。
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableEurekaClient
//@RibbonClient(name = "server-provider",configuration = RibbonConfig.class)
@EnableFeignClients
public class ServerConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ServerConsumerApplication.class, args);
}
}
Fegin接口的@FeignClient 配置MyFeignConfig.class
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(name = "server-provider",configuration = MyFeignConfig.class)
public interface HelloApi {
@RequestMapping("/sayHello")
public String sayHello(@RequestParam("name") String name);
}
新建配置类MyFeignConfig.class,这里我们可以为每个客户端配置一个Logger.Level对象,级别有以下的级别:
- NONE: 无日志(默认)
- BASIC:金输出请求的方法
- HEADERS :加上request和response的header的信息
- FULL:包括request和response的header、body和元数据。
import feign.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MyFeignConfig {
@Bean
Logger.Level feignLoggerLevel(){
return Logger.Level.FULL;
}
}
配置 application.yml,logging.level 添加具体Fegin接口的全类名,值为DUBUG,
logging:
level:
com.microservice.consumer.test.HelloApi : DEBUG
启动项目,通过HTTP我们调用接口:看到调用的日志包含了请求的request,返回的response,包括编码使用了gzip,以及请求的元数据:
2.2 数据压缩
配置applicaiton.yml
feign:
compression:
request:
enabled: true
response:
enabled: true
2.3 整合Hystrix
Hystrix集成Feign可以实现熔断的功能,首先配置applicaiton.yml,开启熔断功能
feign:
hystrix:
enabled: true
重构Feign接口,添加接口调用失败、出现异常时候,使用本地的服务降级的自定义结果,并注入应用中:
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(name = "server-provider",configuration = MyFeignConfig.class,
fallback =HelloApi.HelloApiFallBack.class)
public interface HelloApi {
@RequestMapping("/sayHello")
public String sayHello(@RequestParam("name") String name);
@Component
class HelloApiFallBack implements HelloApi{
@Override
public String sayHello(String name) {
return "服务调用失败";
}
}
}
当服务调用失败的时候,会返回我们自定义的结果:
关于更多的服务熔断组件Hystrix我们将用单独的一章去介绍,这里简单的整合了Hystrix,实现了熔断的功能。
三、总结
Feign组件的使用,不仅能够大大简化HTTP调用的简化,提供编码的可读性和友好性。而且能够完美的支持Ribbon和Hystrix也是具有很大的优势。下一章我们将介绍微服务中的熔断组件Hystrix。
以就是本期的分享,你还可以关注公众号: 程序员笑笑生,关注更多精彩内容!
SpringCloud基础教程(一)-微服务与SpringCloud
SpringCloud基础教程(二)-服务发现 Eureka
SpringCloud基础教程(五)-配置中心热生效和高可用
SpringCloud 基础教程(六)-负载均衡Ribbon
更多精彩内容,请期待...
本文由博客一文多发平台 OpenWrite 发布!
我的博客[兰陵笑笑生] ( http://www.hao127.com.cn/),欢...!