
书接前文:
当项目中使用了 OpenFeign 后,可以很方便的进行远程服务调用,现在有个问题,假如远程服务出现故障了,调不了远程的接口,这边又着急等着返回结果,怎么办呢?
当然是使用 服务降级 ,本篇就使用 OpenFeign 进行远程调用,并结合 Sentinel 对出现的异常、故障等问题进行服务降级。
仍以前面 open-feign-service 服务为调用方, nacos-provider 服务为提供方来进行操练。
Open-feign-service 除了引入 spring-cloud-starter-openfeign 外,再引入 spring-cloud-starter-alibaba-sentinel 组件,另外我们这里使用 nacos 的配置中心做 Sentinel 限流规则的持久化,所以还需要引入 spring-cloud-alibaba-sentinel-datasource 和 sentinel-datasource-nacos :
- <!-- 引入二方库 -->
- <dependency>
- <groupId>cn.chendapeng.springcloud</groupId>
- <artifactId>internal-common</artifactId>
- <version>0.0.1-SNAPSHOT</version>
- </dependency>
-
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-web</artifactId>
- </dependency>
- <dependency>
- <groupId>com.alibaba.cloud</groupId>
- <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
- </dependency>
-
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-loadbalancer</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-openfeign</artifactId>
- </dependency>
-
- <dependency>
- <groupId>com.alibaba.cloud</groupId>
- <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
- </dependency>
- <dependency>
- <groupId>com.alibaba.cloud</groupId>
- <artifactId>spring-cloud-alibaba-sentinel-datasource</artifactId>
- </dependency>
- <dependency>
- <groupId>com.alibaba.csp</groupId>
- <artifactId>sentinel-datasource-nacos</artifactId>
- </dependency>
- 复制代码

配置文件 application.yml :
- spring:
- application:
- name: open-feign-service
-
- cloud:
- nacos:
- discovery:
- server-addr: 192.168.242.112:81
- sentinel:
- transport:
- dashboard: localhost:8080
- port: 8719
- # https://github.com/alibaba/Sentinel/issues/1213
- web-context-unify: false
- # Sentinel 规则持久化到 Nacos
- datasource:
- rule1:
- nacos:
- serverAddr: 192.168.242.112:81
- groupId: DEFAULT_GROUP
- dataId: sentinelFlowRule.json
- ruleType: flow
-
- feign:
- client:
- config:
- # 默认的超时时间设置
- default:
- connectTimeout: 5000
- readTimeout: 5000
- # 在指定的 FeignClient 设置超时时间,覆盖默认的设置
- nacos-provider:
- connectTimeout: 1000
- readTimeout: 1000
- loggerLevel: full
- # 激活 Sentinel
- sentinel:
- enabled: true
- 复制代码
这里增加了 Sentinel 的数据持久化内容,以及激活 OpenFeign 与 Sentinel 联合使用的 feign.sentinel.enabled=true 配置。
不管是 Sentinel 限流后返回,还是 OpenFeign 的 fallback 返回,本质上他们都是出现异常了,这里配置一下全局的统一异常处理。
首先,增加一个业务异常类:
- public class BusinessException extends RuntimeException {
-
- private String code;
-
- private String message;
-
- public BusinessException(String code, String message) {
- this.code = code;
- this.message = message;
- }
-
- public String getCode() {
- return code;
- }
-
- public void setCode(String code) {
- this.code = code;
- }
-
- @Override
- public String getMessage() {
- return message;
- }
-
- public void setMessage(String message) {
- this.message = message;
- }
- }
- 复制代码
然后使用 Spring 的 @RestControllerAdvice 注解进行全局的异常进行处理:
- @RestControllerAdvice
- public class GlobalExceptionHandler {
-
- private static final Logger LOGGER = LoggerFactory.getLogger(GlobalExceptionHandler.class);
-
- /**
- * 业务异常,统一处理
- * @param e 异常对象
- * @return ResponseResult 全局异常响应
- */
- @ExceptionHandler(BusinessException.class)
- public ResponseResult<String> businessException(BusinessException e) {
- LOGGER.info("code={}, message={}", e.getCode(), e.getMessage());
- return ResponseResult.fail(e.getCode(), e.getMessage());
- }
-
- // 其他异常...
- }
- 复制代码
这样,只要指定了抛出的异常类型,就会返回统一的响应格式。
在上一篇文章中,我们通过 FeignClient 接口调用远程的服务:
- @Service
- @FeignClient("nacos-provider")
- public interface ProductService {
-
- /**
- * 调用远程服务 nacos-provider 的 product/{id} 接口
- * @param id 参数 id
- * @return 返回
- */
- @GetMapping("/product/{id}")
- String getProductById(@PathVariable("id") Long id);
- }
- 复制代码
如果远程接口不通,这里可以在 @FeignClient 注解上增加一个属性 fallback ,该属性定义一个容错的处理类,当调用远程接口失败或超时时,会调用对应接口的容错逻辑,fallback 指定的类必须实现 @FeignClient 标记的接口。
先来定义一个实现 ProductService 的类:
- @Component
- @Slf4j
- public class ProductServiceImpl implements ProductService {
-
- /**
- * 调用远程服务 nacos-provider 的 product/{id} 接口失败后的处理方法
- *
- * @param id 参数 id
- * @return 返回
- */
- @Override
- public String getProductById(Long id) {
- log.error("调用接口 getProduct 失败,id={}", id);
- //return "OpenFeign 降级";
- throw new BusinessException(ResponseCode.RPC_ERROR.getCode(), ResponseCode.RPC_ERROR.getMessage());
- }
- }
- 复制代码
该类需要被 Spring 识别,所以加个 @Component 。该类的实现方法可以添加实际业务的处理逻辑,本案例只是打印一些信息后直接抛出自定义的异常。
Tips :ResponseCode.RPC_ERROR 在二方库中有定义。
给 FeignClient 接口增加 fallback 属性:
- @FeignClient(name = "nacos-provider", fallback = ProductServiceImpl.class)
- 复制代码
OK,不启动服务提供方 nacos-provider,直接调用接口测试。
接口返回:

控制台打印信息:

这样就实现了 fallback 的容错处理,即时远程服务不可用,也能进行降级处理。
在 Controller 层使用 FeignClient 定义的接口进行远程调用服务时,还可以定义 Sentinel 资源,并设置规则对资源进行限流。
@SentinelResource 的一些使用方法在前几篇文章中已有提及,这里再结合 OpenFeign 使用,本例中有如下定义:
- @GetMapping("/product/{id}")
- @SentinelResource(value = "getProduct",
- blockHandler = "getProductBlock",
- fallback = "getProductFallback")
- public String getProduct(@PathVariable("id") Long id) {
- return productService.getProductById(id);
- }
-
- public String getProductBlock(Long id, BlockException e) {
- log.error("访问资源 getProduct 被限流,id={}", id);
- throw new BusinessException("C0002", "访问资源 getProduct 被限流");
- }
-
- public String getProductFallback(Long id) {
- log.error("访问资源 getProduct fallback");
- return "请稍后重试";
- }
- 复制代码
在前面的准备工作中,我们已经配置了 Sentinel 资源限流规则持久化到 Nacos,现在 Nacos 中配置一下资源 getProduct 的限流规则:
- [
- {
- "resource": "getProduct",
- "limitApp": "default",
- "grade": 1,
- "count": 1,
- "strategy": 0,
- "controlBehavior": 0,
- "clusterMode": false
- }
- ]
- 复制代码

限流规则是 QPS 阈值为1,只要我1秒大于1次请求就会被限流。
启动远程服务 nacos-provider ,下面来验证一下。
1秒只发一个请求的结果:

1秒内快速刷新几次,造成QPS大于1,将会被限流:

OpenFeign 整合 Sentinel 需要引入 Sentinel 相关依赖包;feign.sentinel.enabled=true 来开启 Feign 与 Sentinel的结合使用;fallback 属性,该属性定义远程接口访问有问题时的容错处理逻辑的类;fallback 定义的类需实现 @FeignClient 定义的接口。