1. 简介
1.1 了解Feign
Feign 是 Netflix 开发的声明式、模板化的 HTTP 客户端,让编写 web 服务客户端变得非常容易。Feign 集成了 Ribbon、RestTemplate 实现了负载均衡的执行 Http 调用,只不过对原有的方式(Ribbon+RestTemplate)进行了封装,开发者不必手动使用 RestTemplate 调服务,而是使用 Feign 的注解定义接口,调用这个接口,就可以调用服务注册中心的服务,这样更加符合面向接口编程的宗旨,简化了开发。但遗憾的是 Feign 现在停止迭代了。
1.2 了解Open Feign
OpenFeign 是 springcloud 在 Feign 的基础上支持了 SpringMVC 的注解,如 @RequestMapping 等等。OpenFeign的 @FeignClient 可以解析 SpringMVC 的@RequestMapping 注解下的接口,并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务。
2. 快速上手
2.1 引入依赖
<!-- openfeign 远程调用 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2.2 服务创建
1、创建两个服务 openfeign-provider8082(服务提供者)、openFeign-consumer8083(服务消费者)。
2、在 openFeign-consumer8083 启动类添加 @EnableFeignClients
开启 openfeign 功能。
3、修改两个服务配置文件,指定服务名称和端口号、nacos 地址等。
4、在 openfeign-provider8082 服务中定义控制器方法。
@RestController
@RequestMapping("/openfeign/provider")
public class OpenFeignProviderController {
@PostMapping("/order1")
public Order createOrder1(Order order){
System.out.println("表单传输 order");
return order;
}
@PostMapping("/order2")
public Order createOrder2(@RequestBody Order order){
System.out.println("请求体传输:order");
return order;
}
@GetMapping("/test/{id}")
public String test(@PathVariable("id")Integer id){
System.out.println("请求url带参数:" + id);
return "accept one msg id="+id;
}
@PostMapping("/test2")
public String test2(String id, String name){
System.out.println("请求参数:" + id + name);
return MessageFormat.format("accept on msg id={0},name={1}",id,name);
}
}
2.3 服务调用
1、服务消费者想要调用服务生产者中的控制器方法,需要定义对应的接口,指明访问路径。
@Service
@FeignClient(value = "openFeign-provider")
public interface OpenFeignService {
/**
* 参数默认是@RequestBody标注的,如果通过POJO表单传参的,使用@SpringQueryMap标注
*/
@PostMapping("/openfeign/provider/order1")
Order createOrder1(@SpringQueryMap Order order);
/**
* 参数默认是@RequestBody标注的,这里的@RequestBody可以不填
* 方法名称任意
*/
@PostMapping("/openfeign/provider/order2")
Order createOrder2(@RequestBody Order order);
@GetMapping("/openfeign/provider/test/{id}")
String test1(@PathVariable("id")Integer id);
/**
* 必须要@RequestParam注解标注,且value属性必须填上参数名
* 方法参数名可以任意,但是@RequestParam注解中的value属性必须和provider中的参数名相同
*/
@PostMapping("/openfeign/provider/test2")
String test2(@RequestParam("id") String arg1, @RequestParam("name") String arg2);
}
2、在 openFeign-consumer8083 中的控制器中引入 OpenFeignService,就可以实现像调用本地方法一样调用远程服务方法。
@RestController
@RequestMapping("/openfeign")
public class OpenFeignConsumerController {
@Autowired
private OpenFeignService openFeignService;
@PostMapping("/order1")
public Order createOrder1(Order order){
return openFeignService.createOrder1(order);
}
@PostMapping("/order2")
public Order createOrder2(@RequestBody Order order){
return openFeignService.createOrder2(order);
}
@GetMapping("/test/{id}")
public String test(@PathVariable("id")Integer id){
return openFeignService.test1(id);
}
@PostMapping("/test2")
public String test2(@RequestParam String id, @RequestParam String name){
return openFeignService.test2(id, name);
}
}
3. 自定义配置
3.1 配置超时时间
open feign 其实是有默认的超时时间的,默认分别是连接超时时间10秒、读超时时间60秒,源码在 feign.Request.Options#Options()
这个方法中:
public Options() {
this(10L, TimeUnit.SECONDS, 60L, TimeUnit.SECONDS, true);
}
但是又因为 open feign 集成了 ribbon,Ribbon的默认超时连接时间、读超时时间都是是1秒,源码在org.springframework.cloud.openfeign.ribbon.FeignLoadBalancer#execute()
方法中:
// 如果openFeign没有设置对应得超时时间,那么将会采用Ribbon的默认超时时间。
public FeignLoadBalancer.RibbonResponse execute(FeignLoadBalancer.RibbonRequest request, IClientConfig configOverride) throws IOException {
Options options;
if (configOverride != null) {
RibbonProperties override = RibbonProperties.from(configOverride);
options = new Options((long)override.connectTimeout(this.connectTimeout), TimeUnit.MILLISECONDS, (long)override.readTimeout(this.readTimeout), TimeUnit.MILLISECONDS, override.isFollowRedirects(this.followRedirects));
} else {
options = new Options((long)this.connectTimeout, TimeUnit.MILLISECONDS, (long)this.readTimeout, TimeUnit.MILLISECONDS, this.followRedirects);
}
Response response = request.client().execute(request.toRequest(), options);
return new FeignLoadBalancer.RibbonResponse(request.getUri(), response);
}
这里不建议配置 ribbon 的超时时间,可以修改 open feign 的超时时间(谁调用服务就配超时时间),有两种办法:
1、yaml 方式修改。
feign:
client:
config:
## default 设置的全局超时时间,指定服务名称可以设置单个服务的超时时间
default:
connectTimeout: 5000
readTimeout: 5000
## 如果一个服务调用时间超过给定的默认时间,可以专门定义调用该服务的超时时间。
serviceC:
connectTimeout: 30000
readTimeout: 30000
2、配置类修改。
@Configuration
public class FeignConfig {
@Bean
public Request.Options options() {
return new Request.Options(5000, 10000);
}
}
3.2 配置日志增强
openFeign 提供了日志增强功能,但是默认是不显示任何日志的,不过开发者在调试阶段可以自己配置日志的级别。
openFeign 的日志级别如下:
- NONE:默认的,不显示任何日志;
- BASIC:仅记录请求方法、URL、响应状态码及执行时间;
- HEADERS:除了BASIC中定义的信息之外,还有请求和响应的头信息;
- FULL:除了HEADERS中定义的信息之外,还有请求和响应的正文及元数据。
1、全局配置,在配置类中设置日志级别:
// 注意:此处配置@Configuration注解就会全局生效,如果想指定对应微服务生效,就不能配置
public class FeignConfig {
/**
* 日志级别
* @return
*/
@Bean
public Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
}
2、局部配置,在 yaml 中配置:
feign:
client:
config:
mall-order: #对应微服务名称
loggerLevel: FULL
3、设置接口日志级别。
logging:
level:
com.xuwei.openfeignconsumer8083.service: debug
3.3 配置GZIP通讯压缩
gzip 是一种数据格式,采用用 deflate 算法压缩数据;gzip 是一种流行的数据压缩算法,应用十分广泛,尤其是在 Linux 平台。当 GZIP 压缩到一个纯文本数据时,效果是非常明显的,大约可以减少70%以上的数据大小。网络数据经过压缩后实际上降低了网络传输的字节数,最明显的好处就是可以加快网页加载的速度。网页加载速度加快的好处不言而喻,除了节省流量,改善用户的浏览体验外,另一个潜在的好处是 GZIP 与搜索引擎的抓取工具有着更好的关系。例如 Google 就可以通过直接读取 GZIP 文件来比普通手工抓取更快地检索网页。
压缩流程:
- 客户端向服务器请求头中带有:Accept-Encoding:gzip,deflate 字段,向服务器表示,客户端支持的压缩格式(gzip 或者 deflate),如果不发送该消息头,服务器是不会压缩的。
- 服务端在收到请求之后,如果发现请求头中含有Accept-Encoding字段,并且支持该类型的压缩,就对响应报文压缩之后返回给客户端,并且携带
Content-Encoding:gzip
消息头,表示响应报文是根据该格式压缩过的。 - 客户端接收到响应之后,先判断是否有 Content-Encoding 消息头,如果有,按该格式解压报文。否则按正常报文处理。
注意:openFeign 支持的 GZIP 仅仅是在 openFeign 接口的请求和响应,即是 openFeign 消费者调用服务提供者的接口。
openFeign开启GZIP步骤也是很简单,只需要在配置文件中开启如下配置:
feign:
# 配置 GZIP 来压缩数据
compression:
request:
enabled: true
# 配置压缩的类型
mime-types: text/xml,application/xml,application/json
# 最小压缩值
min-request-size: 2048
response:
enabled: true
3.4 配置自定义拦截器
1、自定义拦截器,需实现 RequestInterceptor 接口,重写 apply 方法。
public class FeignAuthRequestInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate requestTemplate) {
// 业务逻辑
String access_token = UUID.randomUUID().toString();
requestTemplate.header("Authorization", access_token);
}
}
2、配置类中引入或者 yaml 文件中直接配置。
# 配置类
public class FeignConfig {
/**
* 自定义拦截器
* @return
*/
@Bean
public FeignAuthRequestInterceptor feignAuthRequestInterceptor(){
return new FeignAuthRequestInterceptor();
}
}
# yaml文件引入
feign:
client:
config:
order-service: #对应微服务
requestInterceptors[0]: #配置拦截器
com.xuwei.order.Interceptor.FeignAuthRequestInterceptor
3、测试,查看认证是否生效。