• SpringCloudAlibaba OpenFeign整合及详解


    SpringCloudAlibaba OpenFeign

    在前面,我们使用Nacos服务注册发现后,服务远程调用可以使用RestTemplate+Ribbon或者OpenFeign调用。实际开发中很少使用RestTemplate这种方式进行调用服务,每次调用需要填写地址,还要配置各种的参数,很麻烦。使用OpenFeign的方式就可以解决这种问题。

    那么说起了OpenFeign,就需要提及一下Feign了,因为OpenFeign是Feign的增强版。Feign是一个轻量级Restful HTTP服务客户端,内置ribbon用作客户端负载均衡,使用Feign时只需要定义一个接口加上注解,符合面向接口的编程习惯,使远程调用服务更加容易。

    OpenFeign是对Feign的进一步封装,使其支持Spring MVC的标准注解和HttpMessageConverters,如@RequestMapping等。

    集成OpenFeign

    在消费者客户端中集成OpenFeign

    导入依赖

    <dependency>
        <groupId>org.springframework.cloudgroupId>
        <artifactId>spring-cloud-starter-openfeignartifactId>
    dependency>
    
    • 1
    • 2
    • 3
    • 4

    bootstrap.yml中增加OpenFeign对Sentinel的支持,增加feign.sentinel.enabled配置项

    server:
      port: 9001
    spring:
      application:
        name: consumer # 应用名
    
      cloud:
        nacos:
          discovery:
            server-addr: localhost:8848 # nacos服务地址
        sentinel:
          transport:
            port: 8719 # 启动http server,并且该服务与Sentinel仪表板进行交互,使sentinel可以控制应用,若端口占用则8719+1依次扫描
            dashboard: 127.0.0.1:8080 # 仪表版访问地址
    feign:	# 增加对sentinel的支持
      sentinel:
        enabled: true
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    这里说一下如果不加feign.sentinel.enabled=true的配置,那么在@FeignClient中定的fallback属性定义的异常、限流等自定义的处理逻辑不会生效

    在主启动类上加入@EnableFeignClients注解,标记为启用OpenFeign,具体如下:

    @SpringBootApplication
    @EnableDiscoveryClient
    @EnableFeignClients
    public class Consumer {
    
        public static void main(String[] args) {
            SpringApplication.run(Consumer.class, args);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    笔者这里再提一句,假如是分布式项目,将openfeign抽取为一个单独的服务模块时,可能出现我openfeign中的包名和其他模块不一致,可以自己声明FeignClient组件的basePackages设置一下FeignClient的扫描路径。示例如下:

    @SpringBootApplication
    @EnableDiscoveryClient
    @EnableFeignClients(basePackages = "com.alibaba.provider.feigns")
    public class Consumer {
    
        public static void main(String[] args) {
            SpringApplication.run(Consumer.class, args);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    增加一个FeignClient的客户端

    /**
     * "provider" : 表示调用的生产者服务名
     * fallback:异常时进入的处理类
     */
    @FeignClient(value = "provider", fallback = OpenFeignTestServiceFallback.class)
    public interface OpenFeignTestService {
    
        @RequestMapping(value = "/openFeignProviderTest", method = RequestMethod.GET)
        public String openFeignProviderTest();
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    创建OpenFeignTestServiceFallback做fallback处理

    @Component
    public class OpenFeignTestServiceFallback implements OpenFeignTestService{
    
        @Override
        public String openFeignProviderTest() {
            return "我是兜底方法";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    创建controller做个接口,方便调用

    @RestController
    public class OpenFeignTestController {
        
        @Resource
        private OpenFeignTestService openFeignTestService;
        
        @RequestMapping("/openFeignTest")
        public String openFeignTest() {
            return openFeignTestService.openFeignProviderTest();
        }
        
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    在服务生产者方,提供接口

    @RestController
    public class OpenFeignProviderTest {
    
        @RequestMapping("/openFeignProviderTest")
        public String openFeignProviderTest() {
            return "OpenFeignTestController#openFeignProviderTest" + RandomUtils.nextInt(0, 1000);
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    测试接口

    使用curl或浏览器都行,调用服务消费者方的/openFeignTest接口测试

    curl http://localhost:9001/openFeignTest
    ==>OpenFeignTestController#openFeignProviderTest748
    
    • 1
    • 2

    代码优化一下:

    在@FeignClient注解中,value属性填写的是服务提供者的服务名称,这么把值直接写死是不合适的,假如服务提供者名称变了,那么这里写的就需要修改,而且如果用的地方比较多的情况下,需要到处修改。如何解决呢?

    • 把服务名用一个单独的类,定义静态常量
    • 使用配置文件方式,使用表达式获取即可

    定义静态常量类就不再演示,使用配置文件方式:

    provider:
      name: provider
    
    • 1
    • 2

    在FeignClient客户端使用表达式获取

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

    结合sentinel规则使用

    不管是RestTemplate+Ribbon还是OpenFeign的远程调用,都是支持Sentinel的,到sentinel面板中配置资源流控规则

    注意:sentinel是懒加载方式,需要先去调用一次接口,才能在控制台添加规则

    在sentinel面板,新增openFeignProviderTest流控规则,阈值类型QPS,单机阈值1

    在这里插入图片描述

    欧克,我们对服务端的,/openFeignProviderTest做了限流,那么试试多次连续访问客户端接口试试。

    在这里插入图片描述

    可以看到,有请求会进入到fallback降级中。

    那么对于异常该怎么处理呢?

    修改服务端接口代码,设置一个异常,进行测试接口

    在这里插入图片描述

    欧克,发现,服务端如果是出现了异常,仍然会进入客户端的fallback处理中,是不是非常奈斯。

    那么同样的,假如服务器进入宕机状态,会如何?关掉服务端试试。

    在这里插入图片描述

    行的,一样可以进入到fallback处理中。

    实现负载均衡

    OpenFeign也具有负载均衡的功能,多个服务端时,采用对应的算法寻找一个服务端进行请求。

    下面,服务端将输出当前项目的端口号,并且再新建一个服务端的项目

    @RestController
    public class OpenFeignProviderTest {
    
        @Value("${server.port}")
        private Integer port;
        
        @RequestMapping("/openFeignProviderTest")
        public String openFeignProviderTest() {
            return "OpenFeignTestController#openFeignProviderTest" + port;
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    再新建一个项目,端口为8003即可。

    server:
      port: 8002
    ...多余部分省略
    
    server:
      port: 8003
    ...多余部分省略
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    项目启动,nacos中可以发现,服务端两个实例

    在这里插入图片描述

    多次调用客户端接口,看结果

    在这里插入图片描述

    依次轮询方式请求服务。

    OpenFeign的超时配置

    2020版本以前的OpenFeign的默认等待接口返回数据的时间是1s,超过1秒就报错,如果有fallback,那么执行fallback的处理。

    2020版本后,源码如下:

    public Options() {
        //10L: connectTimeout
        //60L: readTimeout
        this(10L, TimeUnit.SECONDS, 60L, TimeUnit.SECONDS, true);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    connectTimeout是10s,readTimeout是60s

    普通接口是没有问题的,但是如果是一些耗时业务,执行过程中一定是大于1s的,不太合理。

    那么OpenFeign提供了超时的配置。消费者bootstrap.yml文件中增加超时配置:

    • feign.client.config.default.connectTimeout:建立连接的超时时间
    • feign.client.config.default.readTimeout:建立连接后从服务器读取资源所用时间的超时时间配置
    server:
      port: 9001
    spring:
      application:
        name: consumer # 应用名
    
      cloud:
        nacos:
          discovery:
            server-addr: localhost:8848 # nacos服务地址
        sentinel:
          transport:
            port: 8719 # 启动http server,并且该服务与Sentinel仪表板进行交互,使sentinel可以控制应用,若端口占用则8719+1依次扫描
            dashboard: 127.0.0.1:8080 # 仪表版访问地址
    feign:
      sentinel:
        enabled: true
      client:
        config:
          default:
            connectTimeout: 2000 # 建立连接超时时间
            readTimeout: 2000   # 读取资源超时时间
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    @RequestMapping("/openFeignProviderTest")
    public String openFeignProviderTest() throws InterruptedException {
        Thread.sleep(5000);
        return "OpenFeignTestController#openFeignProviderTest" + port;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在这里插入图片描述

    这里故意睡眠5s,超时时间是2s那么,一定是超时了,会进入fallback.

    时间调长点试试

    ..
    feign:
      sentinel:
        enabled: true
      client:
        config:
          default:
            connectTimeout: 7000 # 建立连接超时时间
            readTimeout: 7000   # 读取资源超时时间
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    在这里插入图片描述

    没问题

    OpenFeign的日志

    为方便查找异常,一般在本地开发环境中,把OpenFeign远程调用接口的日志详情打印出来。

    服务消费者bootstrap.yml文件新增日志级别的配置,新增配置项logging.level.声明接口的包名,完整配置如下:

    server:
      port: 9001
    spring:
      application:
        name: consumer # 应用名
    
      cloud:
        nacos:
          discovery:
            server-addr: localhost:8848 # nacos服务地址
        sentinel:
          transport:
            port: 8719 # 启动http server,并且该服务与Sentinel仪表板进行交互,使sentinel可以控制应用,若端口占用则8719+1依次扫描
            dashboard: 127.0.0.1:8080 # 仪表版访问地址
    feign:
      sentinel:
        enabled: true
    
      client:
        config:
          default:
            connectTimeout: 5000 # 建立连接超时时间
            readTimeout: 5000   # 读取资源超时时间
    
    logging:
      level:
        com.alibaba.provider.feigns: debug # 打印自己项目中com.alibaba.provider.feigns包的日志,级别是debug级别
    
    • 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

    然后在java中创建一个配置类

    @Configuration
    public class OpenFeignLoggerConfiguration {
        
        @Bean
        public Logger.Level openFeignLoggerLevel() {
            return Logger.Level.FULL;   // FULL日志级别
        }
        
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    OpenFeign日志有以下几个级别

    • NONE:无记录,默认的
    • BASIC:只记录请求方法和url及响应状态代码和执行时间
    • HEADERS:只记录基本信息及请求和响应头
    • FULL:记录请求和响应的头文件,正文和元数据,信息最全

    配置后日之后,如下:

    在这里插入图片描述

    信息非常完整,方便排错。

    请求和响应压缩

    OpenFeign支持通过简单配置实现请求和响应进行gzip压缩,提高数据传输的效率。

    开启压缩可以有效节约网络资源,但是在压缩和解压过程中会增加CPU的压力,最好把最小请求长度参数调大一些。

    bootstrap.yml文件中,增加参数配置(服务消费者端):

    server:
      port: 9001
    spring:
      application:
        name: consumer # 应用名
    
      cloud:
        nacos:
          discovery:
            server-addr: localhost:8848 # nacos服务地址
        sentinel:
          transport:
            port: 8719 # 启动http server,并且该服务与Sentinel仪表板进行交互,使sentinel可以控制应用,若端口占用则8719+1依次扫描
            dashboard: 127.0.0.1:8080 # 仪表版访问地址
    feign:
      sentinel:
        enabled: true
    
      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 # 响应压缩启用
          
    
    logging:
      level:
        com.alibaba.provider.feigns: debug # 打印com.alibaba.provider.feigns包的日志,级别是debug级别
    
    • 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

    压缩后,可以看下日志情况:

    在这里插入图片描述

    OpenFeign的参数传递

    简单参数传递

    服务端接口

    @PostMapping("/sampleParamsProviderTest")
    public String sampleParamsProviderTest(@RequestParam("name") String name, @RequestParam("id") Integer id) {
        return "OpenFeignProviderTest# sampleParamsProviderTest#" + port + "id=" + id + ",name=" + name;
    }
    
    • 1
    • 2
    • 3
    • 4

    消费者OpenFeign

    @FeignClient(value = "${provider.name}", fallback = OpenFeignTestServiceFallback.class)
    public interface OpenFeignTestService {
    
        @PostMapping("/sampleParamsProviderTest")
        public String sampleParamsProviderTest(@RequestParam("name") String name, @RequestParam("id") Integer id);
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    消费者fallback

    @Component
    public class OpenFeignTestServiceFallback implements OpenFeignTestService {
        @Override
        public String sampleParamsProviderTest(String name, Integer id) {
            return "我是兜底方法" + name + id;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    消费者提供一个接口做测试

    @GetMapping("/sampleParamsProviderTest")
    public String sampleParamsProviderTest() {
        return openFeignTestService.sampleParamsProviderTest("gangge", 1);
    }
    
    • 1
    • 2
    • 3
    • 4

    在这里插入图片描述

    @SpringQueryMap对象传递

    那么先创建一个要传递的对象,服务提供者和消费者共用

    public class Params {
        
        private Integer id;
        private String name;
    
        .../getter、setter方法
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    服务提供者,创建一个给消费者调用的接口

    @GetMapping("/springQueryMapProviderTest")
    public String springQueryMapProviderTest(Params params) {
        return "OpenFeignProviderTest# sampleParamsProviderTest#" + port + "id=" + params.getId() + ",name=" + params.getName();
    }
    
    • 1
    • 2
    • 3
    • 4

    消费者OpenFeign

    @GetMapping("/springQueryMapProviderTest")
    public String springQueryMapProviderTest(@SpringQueryMap Params params);
    
    • 1
    • 2

    消费者fallback

    @Override
    public String springQueryMapProviderTest(Params params) {
        return "我是兜底方法" + params.getId() + params.getName();
    }
    
    • 1
    • 2
    • 3
    • 4

    消费者接口

    @GetMapping("/springQueryMapTest")
    public String springQueryMapTest() {
        Params params = new Params();
        params.setId(1);
        params.setName("gangge");
        return openFeignTestService.springQueryMapProviderTest(params);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    结果

    在这里插入图片描述

    复杂对象传递

    对象套对象的情况,准备一个ComplexObjectResult对象(提供者和消费者都需要创建)

    ComplexObject

    public class ComplexObject {
        private Params params;
    
        public Params getParams() {
            return params;
        }
    
        public void setParams(Params params) {
            this.params = params;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    Result

    public class Result {
        private Integer code;
        private String describe;
    
        public Integer getCode() {
            return code;
        }
    
        public void setCode(Integer code) {
            this.code = code;
        }
    
        public String getDescribe() {
            return describe;
        }
    
        public void setDescribe(String describe) {
            this.describe = describe;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    在服务提供方编写接口,接受消费者调用,后返回Result对象

    @PostMapping("/complexObjectProviderTest")
    public Result complexObjectProviderTest(@RequestBody ComplexObject complexObject) {
        Result result = new Result();
        result.setCode(200);
        result.setDescribe("#complexObjectProviderTest" + complexObject.getParams().getName() + complexObject.getParams().getId());
        return result;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    消费者声明客户端接口和fallback操作

    @PostMapping("/complexObjectProviderTest")
    public Result complexObjectProviderTest(@RequestBody ComplexObject complexObject);
    
    @Override
    public Result complexObjectProviderTest(ComplexObject complexObject) {
        return null;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    返回Result对象**

    @PostMapping("/complexObjectProviderTest")
    public Result complexObjectProviderTest(@RequestBody ComplexObject complexObject) {
        Result result = new Result();
        result.setCode(200);
        result.setDescribe("#complexObjectProviderTest" + complexObject.getParams().getName() + complexObject.getParams().getId());
        return result;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    消费者声明客户端接口和fallback操作

    @PostMapping("/complexObjectProviderTest")
    public Result complexObjectProviderTest(@RequestBody ComplexObject complexObject);
    
    @Override
    public Result complexObjectProviderTest(ComplexObject complexObject) {
        return null;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    复杂对象时@RequestBody注解完全可以,但是只能有一个@RequestBody的参数

  • 相关阅读:
    适合程序员的linux发行版
    被PMP考试“折磨”出来的考试心得,值得你一览
    vue3为什么用proxy替代object.defineProperty
    甩出11张图-让我们来构想(实现)一个倒排索引
    【云原生】k8s 管理平台 rancher
    JAVA开发 使用Apache PDFBox库生成PDF文件,绘制表格
    C复习-标准函数库:数值计算+字符串转换+日期+信号处理+locale
    软件工程毕业设计课题(82)微信小程序毕业设计PHP共享停车位小程序系统设计与实现
    PE文件-C++-MFC-IDA-逆向分析-x32dbg
    流式数据湖平台实战 | HudiSQL DML
  • 原文地址:https://blog.csdn.net/weixin_45248492/article/details/132634506