Feign 是一个声明式的伪 Http 客户端,它使得写 Http 客户端变得更简单。使用 Feign,只需要创建一个接口并注解。它具有可插拔的注解特性,可使用 Feign 注解和 JAX-RS 注解。Feign 支持可插拔的编码器和解码器。Feign 默认集成了
Ribbon
,并和
Eureka
结合,默认实现了负载均衡的效果
- Feign 采用的是基于接口的注解
- Feign 整合了 ribbon
创建服务消费者(Feign)
创建一个工程名为 hello-spring-cloud-web-admin-feign
的服务消费者项目,pom.xml
配置如下:
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.funtl</groupId>
<artifactId>hello-spring-cloud-dependencies</artifactId>
<version>1.0.0-SNAPSHOT</version>
<relativePath>../hello-spring-cloud-dependencies/pom.xml</relativePath>
</parent>
<artifactId>hello-spring-cloud-web-admin-feign</artifactId>
<packaging>jar</packaging>
<name>hello-spring-cloud-web-admin-feign</name>
<url>http://www.funtl.com</url>
<inceptionYear>2018-Now</inceptionYear>
<dependencies>
<!-- Spring Boot Begin -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Spring Boot End -->
<!-- Spring Cloud Begin -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- Spring Cloud End -->
<!-- 解决 thymeleaf 模板引擎一定要执行严格的 html5 格式校验问题 -->
<dependency>
<groupId>net.sourceforge.nekohtml</groupId>
<artifactId>nekohtml</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>com.xyl.hello.spring.cloud.web.admin.feign.WebAdminFeignApplication</mainClass>
</configuration>
</plugin>
</plugins>
</build>
</project>
主要是增加了 Feign 的依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
Application
通过 @EnableFeignClients
注解开启 Feign 功能
package com.xyl.hello.spring.cloud.web.admin.feign;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class WebAdminFeignApplication {
public static void main(String[] args) {
SpringApplication.run(WebAdminFeignApplication.class, args);
}
}
application.yml
设置程序端口号为:8765
spring:
application:
name: hello-spring-cloud-web-admin-feign
thymeleaf:
cache: false
mode: LEGACYHTML5
encoding: UTF-8
servlet:
content-type: text/html
server:
port: 8765
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
创建 Feign 接口
通过 @FeignClient("服务名")
注解来指定调用哪个服务。代码如下:
package com.xyl.hello.spring.cloud.web.admin.feign.service;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(value = "hello-spring-cloud-service-admin")
public interface AdminService {
@RequestMapping(value = "hi", method = RequestMethod.GET)
public String sayHi(@RequestParam(value = "message") String message);
}
创建测试用的 Controller
package com.xyl.hello.spring.cloud.web.admin.feign.controller;
import com.funtl.hello.spring.cloud.web.admin.feign.service.AdminService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class AdminController {
@Autowired
private AdminService adminService;
@RequestMapping(value = "hi", method = RequestMethod.GET)
public String sayHi(@RequestParam String message) {
return adminService.sayHi(message);
}
}
测试访问
在浏览器上多次访问 http://localhost:8765/hi?message=HelloFeign
浏览器交替显示:
Hi,your message is :"HelloFeign" i am from port:8762
Hi,your message is :"HelloFeign" i am from port:8763
使用熔断器防止服务雪崩
概述
在微服务架构中,根据业务来拆分成一个个的服务,服务与服务之间可以通过 RPC
相互调用,在 Spring Cloud 中可以用 RestTemplate + Ribbon
和 Feign
来调用。为了保证其高可用,单个服务通常会集群部署
。由于网络原因或者自身的原因,服务并不能保证 100% 可用,如果单个服务出现问题,调用这个服务就会出现线程阻塞,此时若有大量的请求涌入,Servlet 容器的线程资源会被消耗完毕,导致服务瘫痪。服务与服务之间的依赖性,故障会传播,会对整个微服务系统造成灾难性的严重后果,这就是服务故障的 “雪崩” 效应
。
为了解决这个问题,业界提出了熔断器模型。
Netflix 开源了 Hystrix
组件,实现了熔断器模式
,Spring Cloud 对这一组件进行了整合。在微服务架构中,一个请求需要调用多个服务是非常常见的,如下图:
较底层的服务如果出现故障,会导致连锁故障。当对特定的服务的调用的不可用达到一个阀值(Hystrix 是 5 秒 20 次) 熔断器将会被打开。
熔断器打开后,为了避免连锁故障,通过 fallback
方法可以直接返回一个固定值。
Ribbon 中使用熔断器
在 pom.xml
中增加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
在 Application 中增加 @EnableHystrix 注解
package com.xyl.hello.spring.cloud.web.admin.ribbon;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
@SpringBootApplication
@EnableDiscoveryClient
@EnableHystrix
public class WebAdminRibbonApplication {
public static void main(String[] args) {
SpringApplication.run(WebAdminRibbonApplication.class, args);
}
}
在 Service 中增加 @HystrixCommand 注解
在 Ribbon 调用方法上增加 @HystrixCommand
注解并指定 fallbackMethod
熔断方法
package com.xyl.hello.spring.cloud.web.admin.ribbon.service;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
@Service
public class AdminService {
@Autowired
private RestTemplate restTemplate;
@HystrixCommand(fallbackMethod = "hiError")
public String sayHi(String message){
return restTemplate.getForObject("http://hello-spring-cloud-service-admin/hi?message="+message,String.class);
}
public String hiError(String message) {
return "Hi,your message is :\"" + message + "\" but request error.";
}
}
测试熔断器
此时我们关闭服务提供者,再次请求 http://localhost:8764/hi?message=HelloRibbon
浏览器会显示:
Hi,your message is :"HelloRibbon" but request error.
Feign 中使用熔断器
Feign 是自带熔断器的,但默认是关闭的。需要在配置文件中配置打开它,在配置文件增加以下代码:
feign:
hystrix:
enabled: true
在 Service 中增加 fallback 指定类
package com.xyl.hello.spring.cloud.web.admin.feign.service;
import com.xyl.hello.spring.cloud.web.admin.feign.service.hystrix.AdminServiceHystrix;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(value = "hello-spring-cloud-service-admin", fallback = AdminServiceHystrix.class)
public interface AdminService {
@RequestMapping(value = "hi", method = RequestMethod.GET)
public String sayHi(@RequestParam(value = "message") String message);
}
创建熔断器类并实现对应的 Feign 接口
package com.xyl.hello.spring.cloud.web.admin.feign.service.hystrix;
import com.xyl.hello.spring.cloud.web.admin.feign.service.AdminService;
import org.springframework.stereotype.Component;
@Component
public class AdminServiceHystrix implements AdminService {
@Override
public String sayHi(String message) {
return "Hi,your message is :\"" + message + "\" but request error.";
}
}
测试熔断器
此时我们关闭服务提供者,再次请求 http://localhost:8765/hi?message=HelloFeign
浏览器会显示:
Hi,your message is :"HelloFeign" but request error.
使用熔断器仪表盘监控
在 Ribbon
和 Feign
项目增加 Hystrix
仪表盘功能,两个项目的改造方式相同
在 pom.xml 中增加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
在 Application 中增加 @EnableHystrixDashboard 注解
package com.xyl.hello.spring.cloud.web.admin.ribbon;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;
@SpringBootApplication
@EnableDiscoveryClient
@EnableHystrix
@EnableHystrixDashboard
public class WebAdminRibbonApplication {
public static void main(String[] args) {
SpringApplication.run(WebAdminRibbonApplication.class,args);
}
}
创建 hystrix.stream 的 Servlet 配置
Spring Boot 2.x 版本开启 Hystrix Dashboard 与 Spring Boot 1.x 的方式略有不同,需要增加一个 HystrixMetricsStreamServlet
的配置,代码如下:
package com.xyl.hello.spring.cloud.web.admin.ribbon.config;
import com.netflix.hystrix.contrib.metrics.eventstream.HystrixMetricsStreamServlet;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class HystrixDashboardConfiguration {
@Bean
public ServletRegistrationBean getServlet() {
HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
registrationBean.setLoadOnStartup(1);
registrationBean.addUrlMappings("/hystrix.stream");
registrationBean.setName("HystrixMetricsStreamServlet");
return registrationBean;
}
}
测试 Hystrix Dashboard
浏览器端访问 http://localhost:8765/hystrix
界面如下:
点击 Monitor Stream,进入下一个界面,访问 http://localhost:8765/hi?message=HelloRibbon
此时会出现监控界面:
附:Hystrix 说明
Hystrix Dashboard 界面监控参数
Hystrix 常用配置信息
超时时间(默认1000ms,单位:ms)
- hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds:在调用方配置,被该调用方的所有方法的超时时间都是该值,优先级低于下边的指定配置
- hystrix.command.HystrixCommandKey.execution.isolation.thread.timeoutInMilliseconds:在调用方配置,被该调用方的指定方法(HystrixCommandKey 方法名)的超时时间是该值
线程池核心线程数
hystrix.threadpool.default.coreSize:默认为 10
Queue
- hystrix.threadpool.default.maxQueueSize:最大排队长度。默认 -1,使用 SynchronousQueue。其他值则使用 LinkedBlockingQueue。如果要从 -1 换成其他值则需重启,即该值不能动态调整,若要动态调整,需要使用到下边这个配置
- hystrix.threadpool.default.queueSizeRejectionThreshold:排队线程数量阈值,默认为 5,达到时拒绝,如果配置了该选项,队列的大小是该队列
注意: 如果 maxQueueSize=-1 的话,则该选项不起作用
断路器
- hystrix.command.default.circuitBreaker.requestVolumeThreshold:当在配置时间窗口内达到此数量的失败后,进行短路。默认 20 个(10s 内请求失败数量达到 20 个,断路器开)
- hystrix.command.default.circuitBreaker.sleepWindowInMilliseconds:短路多久以后开始尝试是否恢复,默认 5s
hystrix.command.default.circuitBreaker.errorThresholdPercentage:出错百分比阈值,当达到此阈值后,开始短路。默认 50%
fallback
- hystrix.command.default.fallback.isolation.semaphore.maxConcurrentRequests:调用线程允许请求 HystrixCommand.GetFallback() 的最大数量,默认 10。超出时将会有异常抛出,注意:该项配置对于 THREAD 隔离模式也起作用