spring_cloud_feign实践

简单使用

https://docs.spring.io/spring-cloud-openfeign/docs/current/reference/html/ 官网

在eureka的实践中,service-a的基础上。

pom.xml添加依赖

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

在springboot启动类上

1
2
@EnableEurekaClient
@EnableFeignClients
1
2
3
4
5
6
7
8
9
10
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
public class ServiceAApplication {

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

}

创建serviceb 服务

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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>service-b</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>service-b</name>
<description>Demo project for Spring Boot</description>

<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.3.7.RELEASE</spring-boot.version>
<spring-cloud.version>Hoxton.SR9</spring-cloud.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.3.7.RELEASE</version>
<configuration>
<mainClass>com.example.serviceb.ServiceBApplication</mainClass>
</configuration>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

</project>
1
2
3
4
5
6
7
8
9
10
11
12
# 应用名称
spring:
application:
name: service-b

server:
port: 8763

eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
1
2
3
4
5
6
7
8
9
10
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
public class ServiceBApplication {

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

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@RequestMapping("/test")
@RestController
public class TestController {

@Value("${server.port}")
String port;

@Autowired
private ServiceaClient serviceaClient;

@RequestMapping("/test1")
public String test() {
return "i am service b from port:" + port;
}


@RequestMapping("/test2")
public String test2() {
return "调用:" + serviceaClient.test();
}

}

定义一个feign接口,通过@ FeignClient(“服务名”),来指定调用哪个服务。

1
2
3
4
5
6
7
@FeignClient("servic-a")
public interface ServiceaClient {

@RequestMapping("/test")
String test();

}

servicea 服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@RestController
public class TestController {

@Value("${server.port}")
String port;

@Autowired
private ServicebClient servicebClient;

@RequestMapping("/test")
public String test() {
return "i am from port:" + port;
}


@RequestMapping("/test2")
public String test2() {
return "调用:" + servicebClient.test();
}

}
1
2
3
4
5
6
7
@FeignClient("servic-b")
public interface ServicebClient {

@RequestMapping("/test/test1")
String test();

}

启动 eureka-server、service-a、service-b。

调用servicea的test2 和 调用 serviceb的test2

根据eureka的源码那章的阅读,可知,获取注册表接口是有时间间隔的最好等一会儿再调用

1
2
3
4
5
http://localhost:8762/test2
调用:i am service b from port:8763

http://localhost:8763/test/test2
调用:i am from port:8762

覆盖feign默认值(自定义配置)

以记录日志为例

feign打印日志的级别为debug。所以打日志开启到debug就是可以看到日志了。

1
2
3
logging:
level:
com.example: debug

java类方式配置

单一个FeignClient使用

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

/**
* 打印请求日志
* <p>
* NONE: 不记录任何信息
* BASIE:仅记录请求方法,URL以及响应状态码和执行时间
* HEADERS:除了记录BASIE级别得信息之外,还会记录请求和响应得头信息
* FULL:记录所有请求与响应得明细,包括头信息,请求体,元数据等。
*
* @return
*/
@Bean
public feign.Logger.Level multipartLoggerLevel() {
return feign.Logger.Level.FULL;
}

}
1
@FeignClient(name = "SERVICE-B", configuration = FeignConfig.class)

所有feignclient生效:

FeignConfig上加上注解@Configuration

也可以在@EnableFeignClients(defaultConfiguration = GlobalFeignConfiguration.class)

yml配置

单个feignClient使用

1
2
3
4
5
feign:
client:
config:
SERVICE-B:
loggerLevel: full

所有feignclient生效:

1
2
3
4
5
feign:
client:
config:
default:
loggerLevel: full

所有类似配置都如上

Spring Cloud OpenFeign 默认为 feign ( BeanTypebeanName: ClassName) 提供以下 bean:

  • DecoderfeignDecoder: ResponseEntityDecoder(包装 a SpringDecoder
  • Encoder伪装编码器:SpringEncoder
  • Logger伪装记录器:Slf4jLogger
  • MicrometerCapabilitymicrometerCapability:如果feign-micrometer在类路径上并且MeterRegistry可用
  • CachingCapabilitycacheCapability:如果@EnableCaching使用了注解。可以通过 禁用feign.cache.enabled
  • Contract假装合同:SpringMvcContract
  • Feign.BuilderfeignBuilder:FeignCircuitBreaker.Builder
  • ClientfeignClient:如果 Spring Cloud LoadBalancer 在类路径上,FeignBlockingLoadBalancerClient则使用。如果它们都不在类路径上,则使用默认的 feign 客户端。

OkHttpClient 和 ApacheHttpClient 和 ApacheHC5 feign 客户端可以通过分别设置feign.okhttp.enabledorfeign.httpclient.enabledfeign.httpclient.hc5.enabledto来使用true,并将它们放在类路径中。org.apache.http.impl.client.CloseableHttpClient您可以通过在使用 Apache 或okhttp3.OkHttpClient使用 OK HTTP 或org.apache.hc.client5.http.impl.classic.CloseableHttpClient使用 Apache HC5 时提供 bean 来自定义使用的 HTTP 客户端。

Spring Cloud OpenFeign默认为 feign提供以下 bean,但仍会从应用上下文中查找这些类型的 bean 来创建 feign 客户端:

  • Logger.Level
  • Retryer
  • ErrorDecoder
  • Request.Options
  • Collection<RequestInterceptor>
  • SetterFactory
  • QueryMapEncoder
  • CapabilityMicrometerCapability并且CachingCapability默认提供)

Retryer.NEVER_RETRY默认情况下会创建一个具有该类型的 bean Retryer,这将禁用重试。请注意,这种重试行为与 Feign 默认的行为不同,它会自动重试 IOException,将它们视为瞬态网络相关异常,以及从 ErrorDecoder 抛出的任何 RetryableException。

超时配置

OpenFeign 使用两个超时参数:

  • connectTimeout防止由于服务器处理时间长而阻塞调用者。
  • readTimeout从连接建立时开始应用,在返回响应时间过长时触发。
1
2
3
4
5
6
7
feign:
client:
config:
default:
loggerLevel: full
connectTimeout: 5000
readTimeout: 5000

以毫秒为单位。这里设置为5秒,按情况设置。

断路器

未开启时,启动 eureka-server、service-a

这时调用a的test2接口:

1
2
3
http://localhost:8762/test2

{"timestamp":"2022-06-13T10:04:55.536+00:00","status":500,"error":"Internal Server Error","message":"","path":"/test2"}

feign的jar包中包含了Hystrix的包。

开启

方式一:

在spring boot 启动类加上@EnableHystrix 或者@EnableCircuitBreaker

方式二:

旧版本:

1
2
3
feign:
hystrix:
enabled: ture

新版本:

1
2
3
feign:
circuitbreaker:
enabled: ture

这时访问是一样的,因为没有配置断路器的回调方法

超时

1
2
3
4
5
6
7
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 8000

如果feign超时会返回,如果未超feign的超时,断路器超时会调用下面的降级方法。

fallback

直接写到@FeignClient的fallback:

1
2
3
4
5
6
7
8
9
@Component
public class ServicebClientFallBack implements ServicebClient{

@Override
public String test() {
return "test2fallback 方法执行";
}

}
1
2
3
4
5
6
7
@FeignClient(name = "SERVICE-B", fallback = ServicebClientFallBack.class)
public interface ServicebClient {

@RequestMapping("/test/test1")
String test();

}
1
2
3
http://localhost:8762/test2

调用:test2fallback 方法执行

写到@FeignClient的fallbackFactory

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

public class ServicebClientFallBack implements ServicebClient{

private Throwable throwable;

public ServicebClientFallBack(Throwable throwable) {
this.throwable = throwable;
}

@Override
public String test() {
return "test2fallback 方法执行";
}

}
1
2
3
4
5
6
7
8
9
@Component
public class ServicebClientFallBackFactory implements FallbackFactory<ServicebClientFallBack> {

@Override
public ServicebClientFallBack create(Throwable throwable) {
return new ServicebClientFallBack(throwable);
}

}
1
2
3
4
5
6
7
@FeignClient(name = "SERVICE-B", fallbackFactory = ServicebClientFallBackFactory.class)
public interface ServicebClient {

@RequestMapping("/test/test1")
String test();

}

可以根据Throwable cause 返回不同的信息

压缩

1
2
3
4
5
6
7
8
9
10
11
12
13
14
feign:
hystrix:
enabled: true
client:
config:
default:
loggerLevel: full
connectTimeout: 5000
readTimeout: 5000
compression:
request:
enabled: true
response:
enabled: true

执行后发现日志:

1
Accept-Encoding: gzip
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
feign:
hystrix:
enabled: true
client:
config:
default:
loggerLevel: full
connectTimeout: 5000
readTimeout: 5000
compression:
request:
enabled: true
mime-types: text/xml,application/xml,application/json
min-request-size: 2048
response:
enabled: true

断路器监控面板

1
2
3
4
5
6
7
8
9
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
1
2
3
4
5
6
7
8
9
hystrix:
dashboard:
proxy-stream-allow-list: "*"

management:
endpoints:
web:
exposure:
include: '*'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
@EnableHystrixDashboard
public class ServiceAApplication {

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

@Bean
public ServletRegistrationBean getServlet() {
HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
registrationBean.setLoadOnStartup(1);
registrationBean.addUrlMappings("/actuator/hystrix.stream");//访问路径
registrationBean.setName("hystrix.stream");
return registrationBean;
}

}

访问:http://localhost:8762/actuator/hystrix.stream 一直ping,但是没有信息

访问:http://localhost:8762/test2 接口后

再访问 http://localhost:8762/actuator/hystrix.stream 会出现data信息

访问:http://localhost:8762/hystrix

输入 http://localhost:8762/actuator/hystrix.stream 点击 Monitor Stream

可以看到图形界面。

总结

熔断:熔断机制是赌赢雪崩效应的一种微服务链路保护机制。

  • 凡是依赖都有可能会失败。
  • 凡是资源都有限制,比如 CPU、Memory、Threads、Queue。
  • 网络并不可靠,可能存在网络抖动等其他问题。
  • 延迟是应用稳定的杀手,延迟会占据大量的资源。

当请求失败率达到一定的阈值时,会打开断路器开关,直接拒绝后续的请求,并且具有弹性机制,在后端服务恢复后,会自动关闭断路器开关。

降级: 服务分优先级,牺牲非核心服务(不可用),保证核心服务稳定;从整体负荷考虑;

限流:限制并发的请求访问量,超过阈值则拒绝;

Hystrix是Netflix公司开源的一款容错框架。 它可以完成以下几件事情:

熔断器:当请求失败率达到一定的阈值时,会打开断路器开关,直接拒绝后续的请求,并且具有弹性机制,在后端服务恢复后,会自动关闭断路器开关。
资源隔离:包括线程池隔离和信号量隔离,避免某个依赖出现问题会影响到其他依赖。
降级回退:当断路器开关被打开,服务调用超时/异常,或者资源不足(线程、信号量)会进入指定的fallback降级方法。
请求合并:可以实现将一段时间内的请求合并,然后只对后端服务发送一次请求。
请求结果缓存:hystrix实现了一个内部缓存机制,可以将请求结果进行缓存,那么对于相同的请求则会直接走缓存而不用请求后端服务。


spring_cloud_feign实践
http://hanqichuan.com/2022/06/11/spring_cloud/spring_cloud_feign实践/
作者
韩启川
发布于
2022年6月11日
许可协议