简单使用
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 {
@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 ( BeanType
beanName: ClassName
) 提供以下 bean:
Decoder
feignDecoder: ResponseEntityDecoder
(包装 a SpringDecoder
)
Encoder
伪装编码器:SpringEncoder
Logger
伪装记录器:Slf4jLogger
MicrometerCapability
micrometerCapability:如果feign-micrometer
在类路径上并且MeterRegistry
可用
CachingCapability
cacheCapability:如果@EnableCaching
使用了注解。可以通过 禁用feign.cache.enabled
。
Contract
假装合同:SpringMvcContract
Feign.Builder
feignBuilder:FeignCircuitBreaker.Builder
Client
feignClient:如果 Spring Cloud LoadBalancer 在类路径上,FeignBlockingLoadBalancerClient
则使用。如果它们都不在类路径上,则使用默认的 feign 客户端。
OkHttpClient 和 ApacheHttpClient 和 ApacheHC5 feign 客户端可以通过分别设置feign.okhttp.enabled
orfeign.httpclient.enabled
或feign.httpclient.hc5.enabled
to来使用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
Capability
(MicrometerCapability
并且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 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实现了一个内部缓存机制,可以将请求结果进行缓存,那么对于相同的请求则会直接走缓存而不用请求后端服务。