抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

Fegin的是使用

Fegin简介

​ Feign是一个声明式的Web服务客户端。这使得Web服务客户端的写入更加方便 要使用Feign创建一个界面并对其进行注释。它具有可插拔注释支持,包括Feign注释和JAX-RS注释。Feign还支持可插拔编码器和解码器。Spring Cloud添加了对Spring MVC注释的支持,并在Spring Web中使用默认使用的HttpMessageConverters。Spring Cloud集成Ribbon和Eureka以在使用Feign时提供负载均衡的http客户端

Feign的目标

​ feign是声明式的web service客户端,它让微服务之间的调用变得更简单了,类似controller调用service。Spring Cloud集成了Ribbon和Eureka,可在使用Feign时提供负载均衡的http客户端。

基础概念

声明式REST服务调用

​ 通过spring官方文档可以了解到,Feign是一个声明式web 服务调用服务,他使得一切web服务得以简化。我们只需要创建一个接口并用注解和JAX-RS注解的方式来配置它,即可完成对服务提供方的接口绑定。

远程过程调用协议(RPC)

RPC(Remote Procedure Call)—远程过程调用,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。RPC协议假定某些传输协议的存在,如TCP或UDP,为通信程序之间携带信息数据。在OSI网络通信模型中,RPC跨越了传输层应用层。RPC使得开发包括网络分布式多程序在内的应用程序更加容易。 —– 百度百科

RPC代表
  • JAVA RMI (二进制协议)
  • WebService (文本协议)
消息传递

​ RPC是一种请求-响应协议,一次RPC在客户端初始化,再由客户端将请求消息传递到远程的服务端,执行指定的带有参数的过程。经过远程服务端执行过后,将结果作为响应内容返回到客户端。

存根

​ 在一次分布式计算RPC中,客户端和服务端转换参数的一段代码,由于存根的参数转化,RPC执行过程如同本地执行函数调用。存根必须在客户端和服务端两端均装载,并且必须保持兼容。

Feign的原理

  1. 启动时,程序会进行包扫描,扫描所有包下所有@FeignClient注解的类,并将这些类注入到spring的IOC容器中。当定义的Feign中的接口被调用时,通过JDK的动态代理来生成RequestTemplate。
  2. RequestTemplate中包含请求的所有信息,如请求参数,请求URL等
  3. RequestTemplate生成Request,然后将Request交给client处理,这个client默认是JDK的HTTPUrlConnection,也可以是OKhttp、Apache的HTTPClient等
  4. 最后client封装成LoadBaLanceClient,结合ribbon负载均衡地发起调用

简单实现Feign整合

流程图

​ 我们可以看到,客户端通过调用一定的Feign协议就可以实现请求和响应。接下来让我们来手动操作一下,感受一下Feign的奇妙吧。

注册中心

​ 注册中心不需要做任何改动,按照之前的文章搭建即可。

客户端

增加POM依赖

首先在pom.xml文件中,增加相关依赖

1
2
3
4
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
启动类

使用@EnableFeignClients开启fegin的支持

1
2
3
4
5
6
7
8
9
10
@SpringBootApplication
@EnableEurekaClient
//开启fegin支持,clients是指哪个类开启fegin
@EnableFeignClients(clients = UserService.class)
public class UserApplication {

public static void main(String[] args) {
SpringApplication.run(UserApplication.class);
}
}
Fegin接口

Fegin修饰的接口不需要实现类,他会自动进行动态代理,通过RestTemplate完成调用。

1
2
3
4
5
6
7
8
9
10
11
@FeignClient(name = "ORDER-SERVER", path = "/order")
public interface UserService {

@GetMapping("/createorder/{userid}")
public Order addOrder(@PathVariable("userid") String userid);


@PostMapping("/query")
public String query(@RequestBody Order order);

}

注意:这里服务名不区分大小写,所以使用order-server和ORDER-SERVER都是可以的。另外,在Brixton.SR5版本中,原有的serviceId属性已经被废弃,若要写属性名,可以使用name或value。

访问接口

使用@Autowired直接注入上面定义的UserService实例,并在query函数中调用这个绑定了ORDER-SERVER服务接口的客户端来向该服务发起/query接口的调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@RestController
public class UserController {

@Autowired
private UserService userService;

@GetMapping("/addorder/{userid}")
@ResponseBody
public Order addOrder(@PathVariable("userid") String userid) {
return userService.addOrder(userid);
}

@GetMapping("/query")
public String query(@RequestBody Order order) {
return userService.query(order);
}
}

服务端

服务端只要提供好对应的 http接口即可 不需要做任何变动

接口
1
2
3
4
5
6
7
8
9
10
@RestController
@RequestMapping("/order")
public interface OrderService {

@GetMapping("/createorder/{userid}")
public Order createOrder(@PathVariable("userid") String userId);

@PostMapping("/query")
public String query(@RequestBody Order order);
}
实现类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@Component
public class OrderServiceImpl implements OrderService {

@Value("${server.port}")
private int serverPort;

@Override
public Order createOrder(String userId) {
Order order = new Order();
order.setUserid(userId);
order.setMoney(0.0F);
order.setCreatreDate(new Date());
order.setOrderId((long) serverPort);
System.out.println(serverPort + "创建订单");
return order;
}



public String query(Order order) {
System.out.println(order);
System.out.println("调用查询接口,线程号:" + Thread.currentThread().getId());
if (Thread.currentThread().getId() % 2 == 0) {
int k = 1 / 0;
System.out.println("出现了异常,线程号:" + Thread.currentThread().getId());

}
return "调用查询接口...";
}
}

注意:在这里接口和实现类不是同一个接口类,并且接口的名称不一致但是接口的调用地址一致

测试

通过postmn进行测试成功

​ 可以看到,使用feign之后,我们调用eureka 注册的其他服务,在代码中就像各个service之间相互调用那么简单。

FeignClient注解的一些属性

属性名 默认值 作用 备注
value 空字符串 调用服务名称,和name属性相同
serviceId 空字符串 服务id,作用和name属性相同 已过期
name 空字符串 调用服务名称,和value属性相同
url 空字符串 全路径地址或hostname,http或https可选
decode404 FALSE 配置响应状态码为404时是否应该抛出FeignExceptions
configuration {} 自定义当前feign client的一些配置 参考FeignClientsConfiguration
fallback void.class 熔断机制,调用失败时,走的一些回退方法,可以用来抛出异常或给出默认返回数据。 底层依赖hystrix,启动类要加上@EnableHystrix
path 空字符串 自动给所有方法的requestMapping前加上前缀,类似与controller类上的requestMapping
primary TRUE

客户端Hystrix整合

引入POM文件

在客户端引入Hystrix依赖

1
2
3
4
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>

熔断器使用

在网络请求时,可能会出现异常请求,如果还想再异常情况下使系统可用,那么就需要容错处理,比如:网络请求超时时给用户提示“稍后重试”或使用本地快照数据等等。

Spring Cloud Feign就是通过Fallback实现的,有两种方式:

  1. @FeignClient.fallback = UserFeignFallback.class指定一个实现Feign接口的实现类。

  2. @FeignClient.fallbackFactory = UserFeignFactory.class指定一个实现FallbackFactory工厂接口类

注意:feign的注解@FeignClient:fallbackFactory与fallback方法不能同时使用,这个两个方法其实都类似于Hystrix的功能,当网络不通时返回默认的配置数据。

配置文件配置

在application.properties 启用hystrix

1
feign.hystrix.enabled=true

请务必注意,从Spring Cloud Dalston开始,Feign默认是不开启Hystrix的。

因此,如使用Dalston以及以上版本请务必额外设置属性:feign.hystrix.enabled=true,否则 断路器不会生效。

Spring Cloud Angel/Brixton/Camden中,Feign默认都是开启Hystrix的。

fallback 实现方式

创建UserServiceFallBack的回调实现,由spring创建使用@Component(其他的注册也可以)注解

HystrixTargeter.targetWithFallback方法实现了@FeignClient.fallback处理逻辑,通过源码可以知道UserFeignFallback回调类是从Spring容器中获取的,所以UserFeignFallback由spring创建。

UserService 接口类

添加fallback注解的配置,配置我们自己的 UserServiceFallBack类

1
2
3
4
5
6
7
8
9
10
11
12
@FeignClient(name = "ORDER-SERVER", path = "/order", fallback = UserServiceFallBack.class)
public interface UserService {

@GetMapping("/createorder/{userid}")
public Order addOrder(@PathVariable("userid") String userid);


@PostMapping("/query")
public String query(@RequestBody Order order);

}

UserServiceFallBack类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Component
public class UserServiceFallBack implements UserService {
@Override
public Order addOrder(String userid) {

return null;
}

@Override
public String query(Order order) {
System.out.println("查询订单异常.......");
return "查询订单异常";
}
}
测试

服务提供方异常

客户端控制台输出

1
2
3
查询订单异常.......
查询订单异常.......
查询订单异常.......

服务提供方抛出的500错误代码,但是客户端程序还可以正常运行输出了UserServiceFallBack.query方法返回的结果。

FallbackFactory工厂

上面的实现方式简单,但是获取不到HTTP请求错误状态码和信息 ,这时就可以使用工厂模式来实现Fallback

同样工厂实现类也要交由spring管理,同时结合UserFeignFallback使用,这里需要注意的create方法返回值类型一定要实现Feign接口,否则会报错。

UserServiceFallbackFactory

UserServiceFallbackFactory创建可一个UserService的匿名内部类,完成了异常的打印和返回

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Component
public class UserServiceFallbackFactory implements FallbackFactory<UserService> {

@Override
public UserService create(Throwable throwable) {
return new UserService() {
@Override
public Order addOrder(String userid) {
return null;
}
@Override
public String query(Order order) {
String errorMsage = "查询订单异常.......,errorMessage:" + throwable.getMessage();
System.out.println(errorMsage);
return errorMsage;
}
};
}
}
UserService 接口类

将fallback 替换为fallbackFactory方式

1
2
3
4
5
6
7
8
9
10
11
@FeignClient(name = "ORDER-SERVER", path = "/order", fallbackFactory = UserServiceFallbackFactory.class)
public interface UserService {

@GetMapping("/createorder/{userid}")
public Order addOrder(@PathVariable("userid") String userid);


@PostMapping("/query")
public String query(@RequestBody Order order);

}
测试

熔断器监控

以上的配置只是让熔断器生效,但是是没有开启熔断器监控的,并且对应的 /actuator/hystrix.stream并没有开启。

注意:想要开启需要在启动类加入 @EnableCircuitBreaker 或者@EnableHystrix 注解。

1
2
3
4
5
6
7
8
9
10
11
12
@SpringBootApplication
@EnableEurekaClient
//开启fegin支持,clients是指哪个类开启fegin
@EnableFeignClients(clients = UserService.class)
//开启熔断器监控
@EnableCircuitBreaker
public class UserApplication {

public static void main(String[] args) {
SpringApplication.run(UserApplication.class);
}
}

自定义ErrorDecoder

ErrorDecoder接口处理请求错误信息,默认实现ErrorDecoder.Default抛出FeignException异常

FeignException.status 方法返回HTTP状态码,FallbackFactory.create默认情况下可以强制转换成FeignException异常这样就可以获取到HTTP状态码了。

FeginErrorDecoder

自定义FeginErrorDecoder

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
public class FeginErrorDecoder implements ErrorDecoder {
@Override
public Exception decode(String methodKey, Response response) {
ServiceException serviceException = new ServiceException();
serviceException.setMethod(methodKey);
if (response.status() >= 400 && response.status() <= 499) {
serviceException.setCode(response.status());
serviceException.setErrorMessage(response.reason());
serviceException.setMessage("页面或者参数错误");
}
if (response.status() >= 500 && response.status() <= 599) {
serviceException.setCode(response.status());
serviceException.setErrorMessage(response.reason());
serviceException.setMessage("服务器错误");
}
return serviceException;
}
}


class ServiceException extends RuntimeException {

private int code;

private String message;

private String method;

private String errorMessage;


public int getCode() {
return code;
}

public void setCode(int code) {
this.code = code;
}

public String getErrorMessage() {
return errorMessage;
}

public void setErrorMessage(String errorMessage) {
this.errorMessage = errorMessage;
}

public String getMethod() {
return method;
}

public void setMethod(String method) {
this.method = method;
}

@Override
public String getMessage() {
return message;
}

public void setMessage(String message) {
this.message = message;
}

@Override
public String getLocalizedMessage() {
return "错误码:" + code + ",错误信息:" + message + ",方法:" + method + ",具体错误信息:" + errorMessage;
}
}
配置类配置
1
2
3
4
5
6
7
8
@Configuration
public class FeginConfiguration {

@Bean
public ErrorDecoder getErrorDecoder() {
return new FeginErrorDecoder();
}
}
流程

​ 在Feign客户端发生http请求层面的错误时会调用decode方法。在decode方法中实现自定义的错误处理,当出现异常时首先会通过FeginErrorDecoder进行异常的封装,然后会调用UserServiceFallbackFactory进行异常的回调处理

测试

feign使用的一些问题

第一次请求失败

原因:由于spring的懒加载机制导致大量的类只有在真正使用的才会真正创建,由于默认的熔断超时时间(1秒)过短,导致第一次请求很容易失败,特别互相依赖复杂的时候。

1
2
3
4
#设置hystrix超时时间
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=60000
#请求处理的超时时间
ribbon.ReadTimeout=10000

熔断高频出错问题

问题

在进行feign调用的是一直发生熔断 ,feign和hystrix的超时时间都调整了,就是一直不起作用

熔断产生的原因

openFeign默认支持Ribbon(软负载)和Hystrix(熔断)

关于Ribbon(软负载)

而Ribbon最重要的两个超时包含:

  • 连接超时ConnectTimeout

  • 读超时ReadTimeout

默认情况下,也就是没有任何配置下,Feign的超时时间会被Ribbon覆盖 ,两个超时时间都是1秒,如果调用的逻辑复杂,用时超过1秒,肯定会发生熔断现象(读超时ReadTimeout引起)

关于Hystrix(熔断)

如果Hystrix也开启(可以通过配置文件自定义)了超时,也需要将它的超时时间设置一下, 否则会和上面的Ribbon一样,会覆盖掉Fegin

解决方案一

在调用方的yml配置文件中添加配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 配置超时时间
feign:
hystrix:
enabled: true #开启熔断
hystrix:
command:
default:
execution:
timeout:
enabled: true
isolation:
thread:
timeoutInMilliseconds: 6000
ribbon:
ConnectTimeout: 500
ReadTimeout: 5000

注意:上面ReadTimeout:5000timeoutInMilliseconds: 5000 一定要大于业务逻辑处理时间,比如你业务处理为10秒,此处就应该大于10秒,否则还会出现熔断 如果上面的 hystrix.command.execution.timeout.enabled设置为false,那么 timeoutInMilliseconds的值就可以不用设置了

熔断解决方案二
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
hystrix:
command:
default:
execution:
timeout:
enabled: true
isolation:
thread:
timeoutInMilliseconds: 60000
feign:
hystrix:
enabled: true #开启熔断
client:
config:
#这里也可以填写自己的业务服务名称也可以填default,表示所有服务生效
default:
#只关注readTimeout
connectTimeout: 500
readTimeout: 5000

注意:feign的配置会覆盖ribbon,也就是说Feign配置会优先于Ribbon配置。配置完之后也是同样的效果

  1. 默认情况下,也就是没有任何配置下,Ribbon会覆盖Feign,超时时间都是1秒

  2. 如果Hystrix+ribbon开启它也会覆盖Feign

  3. Hystrix+feign的配置会覆盖ribbon,

Feign使用HttpClient

Feign在默认情况下使用的是JDK原生URLConnection发送HTTP请求,没有连接池,但是对每个地址会保持一个长连接,即利用HTTP的persistence connection。我们可以用Apache的HTTP Client替换Feign原始的http client,从而获取连接池、超时时间等与性能息息相关的控制能力。Spring Cloud从Brixtion.SR5版本开始支持这种替换。

导入POM依赖
1
2
3
4
5
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
<version>11.0</version>
</dependency>
配置文件配置
1
feign.httpclient.enabled=true

主要:这个配置可加可不加,在该版本中默认为true,可以不加,在HttpClientFeignLoadBalancedConfiguration源码中有

1
2
3
4
@ConditionalOnProperty(
value = {"feign.httpclient.enabled"},
matchIfMissing = true
)

的配置默认为true

验证

​ 首先在工程配置文件中,将配置项 feign.httpclient.enabled 的值,设置为 false 。然后,在HttpClientFeignLoadBalancedConfiguration 的 feignClient(…)方法内的某行打上断点,重新启动项目,注意观察会发现,整个启动过程中,断点没有被命中。接下来,将配置项 feign.httpclient.enabled 的值设置为 true,再一次启动项目,断点被命中。由此,可以验证 HttpClientFeignLoadBalancedConfiguration 自动配置类被启动。

在feign请求之前操作

feign组件提供了请求操作接口RequestInterceptor,实现之后对apply函数进行重写就能对request进行修改,包括header和body操作。

1
2
3
4
5
6
7
8
9
@Component
public class TokenRequestInterceptor implements RequestInterceptor {
@Override
public void apply(feign.RequestTemplate template) {
String method = template.method();
String url = template.url();
System.out.println("调用方法:" + method + ",URL地址:" + url);
}
}

请求压缩

​ Spring Cloud Feign支持对请求和响应进行GZIP压缩,以减少通信过程中的性能损耗。我们只需通过下面两个参数设置,就能开启请求与响应的压缩功能:

1
2
feign.compression.request.enabled=true
feign.compression.response.enabled=true

​ 同时,我们还能对请求压缩做一些更细致的设置,比如下面的配置内容指定了压缩的请求数据类型,并设置了压缩的大小下限,只有超过这个大小的请求才会对其进行压缩。

1
2
3
feign.compression.request.enabled=true
feign.compression.request.nime-types=text/xml,application/xml,application/json
feign.compression.requestmin-request-size=2048

​ 上述配置的feign.compression.request.nime-types和feign.compression.requestmin-request-size均为默认值。

日志配置

​ Spring Cloud Feign在构建被@FeignClient注解修饰的服务客户端时,会为每一个客户端都创建一个feign的请求细节。可以在application.properties文件中使用logging.level.<FeignClient>的参数配置格式来开启指定Feign客户端的DEBUG日志,其中<FeignClient>为Feign客户端定义捷克队完整路径,比如针对本博文中我们实现的UserService可以如下配置开启:

1
logging.level.com.springcloud.user.service.UserService=DEBUG

​ 但是,只是添加了如上配置,还无法实现对DEBUG日志的输出。这时由于Feign客户端默认对Logger.Level对象定义为NONE级别,该界别不会记录任何Feign调用过程中对信息,所以我们需要调整它对级别,针对全局对日志级别,可以在应用主类中直接假如Logger.Level的Bean创建,具体如下:

1
2
3
4
@Bean
public Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}

在调整日志级别为FULL之后,我们调用接口测试,查看日志

fegin日志级别

对于Feign的Logger级别主要有下面4类,可根据实际需要进行调整使用。

NONE:不记录任何信息。

BASIC:仅记录请求方法、URL以及响应状态码和执行时间。

HEADERS:出了记录BASIC级别的信息之外,还会记录请求和响应的头信息。

FULL:记录所有请求与响应的细节,包括头信息、请求体、元数据等。

评论