Spring Cloud(十二)声名式服务调用:Feign 的使用(下)
前言
本文是对上一篇博文的扩充,很多平时用不到的特性就开始简略一写,Spring Cloud各版本之间的差距很大的,用不到的可能下一个版本就被kill掉了。由于笔者写本文开始的时候误解了Feign的继承特性,导致实验没有成功,今天是周六加班过程中画了个图,参考了一些资料才得出正确的结果,本人是一边学习一边写的博客做验证,更新估计是快不了了……不多说了,继续Feign的学习。
五、 继承特性
我们发现每次定义Feign调用服务提供者的接口时都要再写一份和所调用的接口方法名参数列表均相同的接口,那么有没有一种可以不用写这些接口就能直接使用Feign去调用服务提供者的接口的方法呢?答案就是本节我给大家带来的Feign的继承特性,结构基本如下:
贴一下服务提供者项目的结构图
首先为了能方便区分之前的接口,我们这次在服务提供者项目中创建一个interfaces
的包,在包中创建我们要用的接口,上代码
package com.cnblogs.hellxz.interfaces;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
/**
* <b>类名</b>: HelloService
* <p><b>描 述</b>: 用于定义Controller的接口,实现放在Controller中
* 这个类的主要作用是方便Feign中继承特性的测试
* </p>
*
* <p><b>创建日期</b> 2018/6/23 11:43 </p>
* @author HELLXZ 张
* @version 1.0
* @since jdk 1.8
*/
public interface HelloService {
/**
* 声明一个接口,没有实现
*/
@GetMapping(value = "/refactor-service/{name}")
String hello(@PathVariable("name") String name);
}
上述代码需要注意的点:
- 我们会想到Feign的客户端会继承这个接口,Feign中是不能有
@GetMapping
等这样的组合注解的,那么在这个接口中我们是否也要写成@RequestMapping
并指定请求方法呢? 经过本人实测,是不用的。这里完全按照SpringMVC这一套来就可以- 这是个接口类,没有实现
上面的接口是没有实现的,所以我在controller
包中为它提供一个实现类
package com.cnblogs.hellxz.controller;
import com.cnblogs.hellxz.interfaces.HelloService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
/**
* 实现了HelloService接口的Controller
*/
@RestController //必须添加这个注解,告诉SpringMVC这是一个Controller
public class HelloServiceImplController implements HelloService {
private final Logger logger = LoggerFactory.getLogger(HelloServiceImplController.class);
/**
* 实现了HelloService中的hello方法,通过这个方法来返回结果
*/
@Override
public String hello(@PathVariable String name) {
logger.info("refactorHelloService的hello方法执行了,入参:name:{}", name);
return "hello,"+name;
}
}
上述代码中需要注意的是:
- @RestController注解必须加上
- 重写接口的方法的时候,
@GetMapping
等url之类的不用写- 在方法的参数注解依旧要写,不然会接收不到参数,我为了排查这个问题打印了日志,如上个代码块中的
@PathVariable
就是获取参数的
现在我们将服务提供者mvn install
安装到本地仓库中,这样我们就可以在其它项目中依赖到服务提供者
接下来,我们在FeignCustomer这个项目的pom的dependencies
节点中添加服务提供者的依赖
<!--添加服务提供者的依赖-->
<dependency>
<groupId>com.cnblogs.hellxz</groupId>
<artifactId>EurekaServiceProvider</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
完成到这一步,我们还需要一个Feign客户端来调用服务提供者的服务,这里和我们之前用的方法不是很相同,代码如下:
package com.cnblogs.hellxz.client;
import com.cnblogs.hellxz.interfaces.HelloService;
import org.springframework.cloud.netflix.feign.FeignClient;
/**
* 继承服务提供者的HelloService的接口,从而拥有这个接口的所有方法
* 那么在这个Feign中就只需要使用HelloService定义的接口方法
*/
@FeignClient("eureka-service")
public interface RefactorHelloServiceFeign extends HelloService {
}
注意:
- 这里我们无需重写与实现HelloService的接口
- 用extends关键字,而不是implements,用implements会要求必须实现
接下来我们在HelloController
中注入上边的Feign,并加入调用Controller,省略之前的代码
//省略类头
@Autowired
private RefactorHelloServiceFeign refactorHelloServiceFeign;
//省略其它Controller方法
/**
* 测试Feign的继承特性
*/
@GetMapping("/refactor/{name}")
@ResponseBody
public String refactorHelloService(@PathVariable String name){
return refactorHelloServiceFeign.hello(name);
}
使用Postman测试一下这个Controller
2. 优缺点
优点:在定义Feign中无需重写要调用的方法,减少工作量,代码也更灵活
缺点:代码耦合性更高,需要更严格的规范,不然接口变动易发生牵一发而动全身,从而增加维护难度
六、Ribbon配置
Spring Cloud Feign的负载均衡是通过Spring Cloud Ribbon实现的,当我们为Feign接口使用@FeignClient(value="eureka-service")
的时候,不仅为我们创建了Feign客户端,还为我们创建了一个名为“eureka-service”的Ribbon,客户端。
了解了以上这点,我们可以通过在配置文件中加入指定参数从而实现自定义Feign中的Ribbon的配置
以下Ribbon的配置就不在代码中体现了
1.全局配置
通过ribbon.<key>=<value>
的方式来修改全局默认配置
举例(yml):
ribbon:
# 同一实例最大重试次数,不包括首次调用
MaxAutoRetries: 1
# 重试其他实例的最大重试次数,不包括首次所选的server
MaxAutoRetriesNextServer: 2
# 是否所有操作都进行重试
OkToRetryOnAllOperations: false
2. 指定服务配置
通过<client>.ribbon.<key>=<value>
的方式,修改指定服务的配置
举例(properties)
eureka-service.ribbon.ConnectTimeout=500
eureka-service.ribbon.ReadTimeout=2000
eureka-service.ribbon.OkToRetryOnAllOperations=true
eureka-service.ribbon.MaxAutoRetriesNextServer=2
eureka-service.ribbon.MaxAutoRetries=1
其中使用的eureka-service是要被配置的服务名
七、重试机制
Spring Cloud Feign默认实现了重试机制(内部同样是Ribbon进行的重试),当一次请求服务提供者,而服务提供者在Feign的延迟时间内没有返回结果,那么Feign会默认发起第二次请求,如果是因为网络问题,使第一次请求失败,第二次请求可能就会成功,通过这一点来实现服务的高可用。如果服务提供者有多个实例,还可以将第二次请求的机会交给其他实例。
我们可以通过上一部分的配置Ribbon,来改变重试的不同操作,比如eureka-service.ribbon.MaxAutoRetriesNextServer=2
这个配置说明最多只能在两个实例间重试,
eureka-service.ribbon.MaxAutoRetries=1
这个配置说明最多自动重试一次
我们可以设计实验:
- 将服务提供者开放一个提供线程sleep的延迟方法,让它的响应时间远超过ReadTimout的值,在这个方法中写日志,看看会有几次请求进入
- 第二个实验是将上述实验的服务提供者启动两个实例,查看输出
实验1:
服务提供者的GetRequestController
中添加方法:
/**
* 测试Feign延迟重试的代码
* 这里我们为这个方法加上超过Feign默认2000ms以上的延迟,我们只需要通过查看日志输出即可
*/
@GetMapping("/retry")
public String feignRetry(){
logger.info("feignRetry方法调用成功");
try {
Thread.sleep(20000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "ok";
}
在FeignCustomer的EurekaServiceFeign
中加入调用上边服务提供者的方法
/**
* 测试重连机制
*/
@RequestMapping(value = "/retry", method = RequestMethod.GET)
String feignRetry();
在HelloController
中添加调用Feign上边方法的Controller
/**
* 测试重连机制
*/
@GetMapping("/retry")
@ResponseBody
public String retry(){
return eurekaServiceFeign.feignRetry();
}
在application.yml
中添加
ribbon:
# 同一实例最大重试次数,不包括首次调用
MaxAutoRetries: 1
# 重试其他实例的最大重试次数,不包括首次所选的server
MaxAutoRetriesNextServer: 2
# 是否所有操作都进行重试
OkToRetryOnAllOperations: false
测试1:
可见重试的确是有的,但是本实验中明明设置了最多只重试一次,也就是说应该打印两次,在idea中提示无法解析这些配置,可能是我在FeignCustomer项目中用的Spring Cloud的依赖太新了,这个问题先放在这里,因为这个配置没有生效,所以第二个实验也就做不下去了……有知道的同学麻烦告知一二 [双手合十]
八、Hystrix配置
Spring Cloud Feign自然也是Hystrix的实现,也就拥有了断路的功能,这里我们看下如何设置其中的Hystrix的属性
1. 全局配置
通过前缀hystrix.command.default
加上要配置的属性,比如
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=5000
在配置Hystrix属性之前,我们要确保feign.hystrix.enabled
的参数值不为false,如果为false,则关闭断路器(Hystrix)的支持
关闭熔断器:hystrix.command.default.execution.timeout.enabled=false
关闭Hystrix功能:feign.hystrix.enabled=false
2. 禁用Hystrix
除了全局配置中的关闭方法,如果我们不想全局关闭Hystrix支持,只是针对某个客户端关闭Hystrix功能 ,需要为Feign提供一个配置类,如下
package com.cnblogs.hellxz.config;
import feign.Feign;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Scope;
/**
* 用于禁用Feign中Hystrix的配置
*/
public class DisableHystrixConfiguration {
@Bean
@Scope("prototype")
public Feign.Builder feignBuilder(){
return Feign.builder();
}
}
在Feign类的@FeignClient
注解中,使用configuration参数指向刚刚创建的配置类
@FeignClient(value = "eureka-service",configuration = DisableHystrixConfiguration.class) //其中的value的值为要调用服务的名称, configuration中配置的是禁用Feign中的Hystrix功能的类
public interface EurekaServiceFeign {
3. 指定命令配置
Hystrix命令的配置在实际应用时也会根据实际业务情况制定出不同的配置,配置方法也跟传统的Hystrix命令的参数配置相似,采用hystrix.command.<commandKey>
作为前缀,默认情况下<commandKey>会使用Feign客户端的方法名作为标识
,所以,我们针对EurekaServiceFeign中的helloFeign方法的配置为:
hystrix.command.helloFeign.execution.isolation.thread.timeoutInMilliseconds=5000
在使用指定命令配置的时候,需要注意,方法名很有可能重复,这时候相同的方法名的Hystrix配置会共用,当然也可以使用重写Feign.Builder
的实现,并在应用主类中创建它的实例来覆盖自动化配置的HystrixFeign.Builder
实现。
4. 服务降级配置
在学习Feign中服务降级的配置之前,我们回顾一下Hystrix中服务降级是使用的@HystrixCommand
注解加上fallback参数指向降级方法。其实Feign也是这样的,实现也比较简单。
只需要写一个实现了这个Feign的类,打上@Component
注解,然后在每个方法中加入自己想让它失败后的处理逻辑。
注意:要注掉刚才配置的关闭Hystrix的方法,在github的代码中我已经注释掉了
用代码看起来更明显
新建EurekaServiceFeignFallback
实现EurekaServiceFeign
package com.cnblogs.hellxz.client;
import com.cnblogs.hellxz.entity.User;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* 服务降级回调类
*/
@Component //必须加这个注解,让这个类做为Bean交给Spring管理,否则会报找不到降级类
public class EurekaServiceFeignFallback implements EurekaServiceFeign{
/**
* 这里只用这个方法举例了,返回一个“失败"
*/
@Override
public String helloFeign() {
return "失败";
}
@Override
public String greetFeign(String dd) {
return null;
}
@Override
public List<User> getUsersByIds(List<Long> ids) {
return null;
}
@Override
public String getParamByHeaders(String name) {
return null;
}
@Override
public User getUserByRequestBody(User user) {
return null;
}
@Override
public String feignRetry() {
return null;
}
}
为EurekaServiceFeign
的@FeignClient
注解后边加入fallback参数指向刚刚创建的降级类
@FeignClient(value = "eureka-service",configuration = DisableHystrixConfiguration.class, fallback = EurekaServiceFeignFallback.class)
public interface EurekaServiceFeign {
注意:configuration这个参数用不到的话可以不加,这里是之前用来配置禁用Hystrix功能的
现在这就已经配置好了,如果在网络环境不好的情况就会完成服务的降级,因为Feign会自动重试,我的配置并没有生效,所以无从验证。但是我在工作中经常使用它,这样配置是可以实现服务的降级的。
九、其它配置
1.请求压缩(Feign request/response compression)
Spring Cloud Feign支持对请求与响应进行GZIP压缩来减少通信过程中带宽的消耗
只需加入两行配置即可开启请求压缩
feign.compression.request.enabled=true
feign.compression.response.enabled=true
还有一些更细致的配置
feign.compression.request.enabled=true
feign.compression.request.mime-types=text/xml,application/xml,application/json
feign.compression.request.min-request-size=2048
2. Feign日志(Feign logging)
开启Feign的日志功能后,每创建一个Feign客户端同时也会创建一个日志记录器(logger),它的名字默认就是Feign接口的全量命名,并且这个日志logger只能响应DEBUG
级的日志
开启记录
application.yml中填加以logging.level
为前缀,后边加上Feign接口的路径全量命名
logging.level.com.cnblogs.hellxz.client.EurekaServiceFeign: DEBUG
添加上述配置还是算完,官网文档中说日志输出级别默认是NONE
的,我们还需配置下
The
Logger.Level
object that you may configure per client, tells Feign how much to log. Choices are:
NONE
, No logging (DEFAULT).BASIC
, Log only the request method and URL and the response status code and execution time.HEADERS
, Log the basic information along with request and response headers.FULL
, Log the headers, body, and metadata for both requests and responses.
简单翻译下:
- NONE:不记录任何信息
- BASIC:仅记录请求方法和URL,以及状态码和执行时间
- HEADERS:在BASIC基础上同时记录请求和响应的头信息
- FULL:记录所有请求与响应的明细
我们可以改变日志输出等级来完成日志输出,官网给出的例子是FULL
@Configuration
public class FooConfiguration {
@Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
}
除了使用配置类,还可以在主类中加入
@Bean
Logger.Level feignLoggerLevel(){
return Logger.Level.FULL;
}
结束
Spring Cloud Feign这一部分就写到这里了,Feign便于使用,功能强大,相比Hystrix实现更灵巧,但是这里多说一点,就是如果想对某个接口更细致并且性能要求高的情况下,用Hystrix更适用一些。
本来以为Feign这部分没有这么多东西,一边看书,一边看官方文档,这一下子写了这么久……
下一部分我们来看看API网关服务:Spring Cloud Zuul
本文引用:
- 《Spring Cloud 微服务实战》翟永超
- 官方文档