SpringCloudAlibaba系列微服务搭建笔记四_OpenFeign

七、OpenFeign远程调用

7.1 简介

前面说的Resttemplate+Ribbon的方式调用服务每次都需要填写远程地址和配置各种参数,非常麻烦。
OpenFeign远程调用框架相比而言更加的简洁好用。
OpenFeign之前有一个Feign框架,OpenFeign算是它的增强版,进一步封装支持了Spring MVC的标准注解和HttpMessage-Converters,如@RequestMapping等。

直接复制一份Sentinel那个项目,改下名字作为这次的试验项目

7.2 集成OpenFeign

  1. 添加依赖
<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
  1. 修改bootstrap.yml配置文件
feign:
  sentinel:
    enabled: true  # 开启 openfeign 对 sentinel 的限流支持
  1. 创建一个接口作为远程调用接口的本地媒介
// value指定需要远程调用的服务的名称 
@FeignClient(value = "sentinel-provider", fallback = OpenFeignTestFallback.class)
public interface OpenFeignTestService {
    /**
     * 这个方法的信息就是来自要远程调用的那个接口方法,需要指定它的接口路径和类型
     */
    @RequestMapping(value = "/providerTest", method = RequestMethod.GET)
    public String providerTest();
}
  1. 创建接口对应兜底实现
@Component
public class OpenFeignTestFallback implements OpenFeignTestService {
    public String providerTest() {
        return "return from OpenFeignTest 兜底方法";
    }
}
  1. 在启动类中贴上@EnableFeignClients注解
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients     // 开启 Feign 服务
public class Concumer8041 {
    public static void main(String[] args) {
        SpringApplication.run(Concumer8041.class, args);
        System.out.println("-------------启动成功------------");
    }
}
  1. 创建一个测试接口
@RestController
@RefreshScope // 配置自动更新
public class TestController {
    @Resource
    private OpenFeignTestService openFeignTestService;
    
    @RequestMapping("/openFeignTest")
    public String openFeignTest() {
        return openFeignTestService.providerTest(); // 以本地接口的方式,实际上调用的是远程接口
    }
    ...
}

启动消费者服务时出现问题:
java.lang.IllegalAccessError: class org.springframework.cloud.openfeign.HystrixTargeter$$EnhancerBySpringCGLIB$$a79797c cannot access its superclass org.springframework.cloud.openfeign.HystrixTargeter
解决:去掉spring-boot-devtools热部署依赖
参考:https://blog.csdn.net/moakun/article/details/113764393

  1. 远程调用接口(服务提供)
@RestController
public class TestController {
    @Value("${server.port}")
    private String port;

    @RequestMapping("/providerTest")
    public String providerTest() {
        return "return from service provider test,服务端口:" + port;
    }
}
  1. 测试
    调用localhost:8041/openFeignTest接口,它又去调用了远程接口,成功拿到了返回结果
    在这里插入图片描述
    接下来在/providerTest接口(被远程调用的接口)制造人为异常
    @RequestMapping("/providerTest")
    public String providerTest() {
        int i = 1 / 0;
        return "return from service provider test,服务端口:" + port;
    }

再次调用localhost:8041/openFeignTest接口,这时候它去调用远程接口的时候出现异常了,然后会调用fallback属性指定的兜底方法做响应。
在这里插入图片描述

7.3 代码优化

在我们创建本地接口去对应远程被调接口的时候,通过@FeignClient注解的value属性指定了服务的名称,但是这样就写死了,后续如果那个被调服务更改了名字,那么每个通过@FeignClient注解调用它的接口都需要更改,不方便。

// value指定需要远程调用的服务的名称 
@FeignClient(value = "sentinel-provider", fallback = OpenFeignTestFallback.class)
public interface OpenFeignTestService {
    /**
     * 这个方法的信息就是来自要远程调用的那个接口方法,需要指定它的接口路径和类型
     */
    @RequestMapping(value = "/providerTest", method = RequestMethod.GET)
    public String providerTest();
}

两个方案

  • 用静态变量
  • 用配置文件
    一般来说用配置文件会比较合适。

更改nacos上的配置

provider:
    name: service-provider

从配置获取

@FeignClient(value = "${provider.name}", fallback = OpenFeignTestFallback.class)

7.4 结合Sentinel使用

openfeign是可以和sentinel结合使用的。
通常是给被调用的接口添加流控,因此这里就给/providerTest接口添加流控规则
在这里插入图片描述
在达到限流条件的时候,会进入fallback指定的兜底类执行兜底方法。

@FeignClient(value = "${provider.name}", fallback = OpenFeignTestFallback.class)
public interface OpenFeignTestService {
    /**
     * 这个方法就是来自要远程调用的那个接口方法的信息,需要指定它的接口路径和类型
     */
    @RequestMapping(value = "/providerTest", method = RequestMethod.GET)
    public String providerTest();
}

只要是被调用的接口没有调用成功,都会进入兜底方法。
这就是比较简单的结合用法。

7.5 负载均衡

复制一份服务供应模块,配置好nacos即可,通过服务名调用接口默认实现轮询。
参考:https://blog.csdn.net/qq_31856061/article/details/127090837

7.6 超时配置

openfeign默认等待超时为1s,超过一秒将进入fallback函数响应
provider接口睡眠2s模拟业务处理,访问
在这里插入图片描述
修改配置文件配置请求超时时间

feign:
  sentinel:
    enabled: true  # 开启 openfeign 对 sentinel 的限流支持
  client:                    # 新增
    config:                  # 新增
      default:               # 新增
        connectTimeout: 5000 # 新增 连接超时时间 单位毫秒
        readTimeout: 5000    # 新增 建立连接后从服务器读取资源超时时间 单位毫秒

设置好后就可以成功调用那些响应慢的接口而不会进入兜底方法

7.7 OpenFeign远程调用日志

openfeign默认不输出日志到控制台,可以通过配置开启日志

  1. 配置日志等级
logging:                                            # 新增
  level:                                            # 新增
    com.echoo.cloud.openfeign.consumer.service.OpenFeignTestService: debug # 新增 

在这里插入图片描述

  1. 添加一个配置类
@Configuration
public class OpenFeignLoggerConfiguration {
    @Bean
    public Logger.Level openFeignLoggerLevel() {
        return Logger.Level.FULL; // FULL 日志级别
    }
}

OpenFeign有四种日志级别:
NONE 无记录(默认)
BASIC 只记录请求方法、URL、响应状态码和执行时间四个基本信息
HEADERS 只记录基本信息及请求和响应头
FULL 记录所有信息

如此设置后,远程调用日志就会打印在控制台
在这里插入图片描述

7.8 压缩请求和响应

当请求或响应传输的数据量比较大,可以开启openfeign的压缩功能对请求和响应进行压缩,提高传输效率
配置:

feign:
  sentinel:
    enabled: true  # 开启 openfeign 对 sentinel 的限流支持
  client:
    config:
      default:
        connectTimeout: 5000 # 连接超时时间 单位毫秒
        readTimeout: 5000    # 建立连接后从服务器读取资源超时时间 单位毫秒
  compression:               # 新增 压缩
    request:                 # 新增 请求设置
      enabled: true          # 新增 允许压缩请求
      mime-types: text/xml,application/xml,application/json # 新增 需要压缩的请求类型
      min-request-size: 2048 # 新增 最小请求长度 单位字节
    response:                # 新增 响应
      enabled: true          # 新增 允许压缩响应

日志中可以看到用了gzip压缩
在这里插入图片描述

7.9 接口传递参数

7.9.1 传递简单参数

首先在服务提供者模块中写一个新的接口,包含了两个简单参数,一个String类型,一个Integer类型

    @PostMapping("/simpleParamsTest")
    public String simpleParamsTest(@RequestParam("name") String name, @RequestParam("num") Integer num) {
        return String.format("return from service provider simpleParamsTest,服务端口:[%s],参数:[%s,%d]", port, name, num);
    }

在消费者中对应的接口中增加一个对应的方法

@FeignClient(value = "${provider.name}", fallback = OpenFeignTestFallback.class)
public interface OpenFeignTestService {
    @RequestMapping(value = "/providerTest", method = RequestMethod.GET)
    public String providerTest();
	
	/** 新增的接口方法 */
    @PostMapping("/simpleParamsTest")
    public String simpleParamsTest(@RequestParam("name") String name, @RequestParam("num") Integer num);
}

然后再实现对应的兜底方法

@Component
public class OpenFeignTestFallback implements OpenFeignTestService {
    public String providerTest() {
        return "return from OpenFeignTest 兜底方法";
    }
	/** 对应的兜底方法 */
    public String simpleParamsTest(String name, Integer num) {
        return "return from simpleParamsTest 兜底方法";
    }
}

最后在消费者中添加一个接口,用来远程调用测试接口,记得调用远程接口时候传参

    @PostMapping("/simpleParamsTest")
    public String simpleParamsTest() {
        return openFeignTestService.simpleParamsTest("mary", 66);
    }

访问测试没毛病,参数传递没问题
在这里插入图片描述

7.9.2 GET请求传递对象

OpenFeign使用@SpringQueryMap注解让Get请求也能简单的传递对象。

  1. 创建传递对象
    因为服务消费者和服务提供者都要用到这个对象,因此把该类放到公共模块commom中,因为我这儿还没公共模块,需要新建一个commom模块
    在这里插入图片描述
  2. 创建数据对象
@Getter
@Setter
public class Params {
    private Integer num;
    private String name;
}
  1. 在服务提供者模块添加一个测试接口
    @GetMapping("/springQueryMapProviderTest")
    public String springQueryMapProviderTest(Params params) {
        return String.format("return from service provider simpleParamsTest,服务端口:[%s],参数:[name=%s,num=%d]", port, params.getName(), params.getNum());
    }
  1. 在服务消费者模块添加对应的接口和实现对应的兜底方法
	@GetMapping("/springQueryMapProviderTest")
    public String springQueryMapProviderTest(@SpringQueryMap Params params);
    public String springQueryMapProviderTest(Params params) {
        return "return from springQueryMapProviderTest 兜底方法";
    }

@SpringQueryMap会自动把路径对应的参数封装到对象中

  1. 在服务消费者模块中添加一个测试接口
    @GetMapping("/springQueryMapTest")
    public String springQueryMapTest() {
        Params params = new Params();
        params.setName("Mike");
        params.setNum(788);
        return openFeignTestService.springQueryMapProviderTest(params);
    }
  1. 访问测试接口,没毛病,对象正确传递
    在这里插入图片描述

7.9.3 传递复杂对象

  1. 创建复杂对象
    对象中包含对象
@Getter
@Setter
public class ComplexObject {
    private Params params;
}
  1. 创建返回值对象
    用于封装返回信息
@Setter
@Getter
public class Result {
    private Integer code;
    private String description;
}
  1. 在服务提供者模块添加接口
    @PostMapping("/simpleParamsTest")
    public Result complexObjectProviderTest(@RequestBody ComplexObject object) {
        Result result = new Result();
        result.setCode(200);
        result.setDescription(String.format("return from service provider simpleParamsTest,服务端口:[%s],参数:[%s,%d]", port, object.getParams().getName(), object.getParams().getNum()));
        return result;
    }

接口接收对象记得用@RequestBody,单个参数用@RequestParam

  1. 在服务消费者模块添加对应的接口方法和兜底方法
    @PostMapping("/complexObjectProviderTest")
    public Result complexObjectProviderTest(@RequestBody ComplexObject object);
    public Result complexObjectProviderTest(ComplexObject object) {
        Result result = new Result();
        result.setCode(500);
        result.setDescription("return from complexObjectProviderTest 兜底方法");
        return result;
    }
  1. 在服务消费者模块添加测试接口
    @PostMapping("/complexObjectTest")
    public Result complexObjectTest() {
        Params params = new Params();
        params.setName("Julia");
        params.setNum(5959);
        ComplexObject complexObject = new ComplexObject();
        complexObject.setParams(params);
        return openFeignTestService.complexObjectProviderTest(complexObject);
    }
  1. 访问测试接口,复杂对象正确传递
    在这里插入图片描述

版权声明:本文为qq_31856061原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。