Rubin's Blog

  • 首页
  • 关于作者
  • 隐私政策
享受恬静与美好~~~
分享生活的点点滴滴~~~
  1. 首页
  2. SpringCloud
  3. 正文

SpringCloud Netflix之Feign远程调用组件

2021年 10月 22日 664点热度 1人点赞 0条评论

之前服务消费者调用服务提供者的时候我们使用的是RestTemplate技术,调用方式如下:

这种调用方式的缺点是拼接url和调用restTmplate.getForObJect这两个地方都比较模板化,而且手动拼写url比较容易出错还比较低级。

Feign简介

Feign是Netflix开发的一个轻量级RESTful的HTTP服务客户端(用它来发起请求,远程调用)。Feign是以Java接口注解的方式调用HTTP请求,而不像Java中通过封装HTTP请求报文的方式直接调用,被广泛应用与SpringCloud的解决方案中。

类似于Dubbo,服务消费者拿到服务提供者的接口,然后想调用本地方法一样去调用,实际上发出的是远程的请求。Feign的优点如下:

  • Feign可帮助我们更加便捷,优雅的调用HTTP API,而不需要我们手动去拼接url然后调用RestTemplate的API。在SpringCloud中,使用Feign非常简单,在消费者端创建一个接口,并在接口上添加一些注解,代码就完成了
  • SpringCloud对Feign进行了增强,使Feign支持了SpringMVC的注解

Feign配置应用

在 服务消费者 服务中引入Feign依赖:

<dependency>
     <groupId>org.springframework.cloud</groupId>
     <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

改造启动类如下:

package com.rubin.scn.service.autodeliver;

import com.netflix.hystrix.contrib.metrics.eventstream.HystrixMetricsStreamServlet;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker
@EnableFeignClients
public class ScnServiceAutoDeliverBootstrap {

    public static void main(String[] args) {
        SpringApplication.run(ScnServiceAutoDeliverBootstrap.class, args);
    }

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

    @Bean
    public ServletRegistrationBean getServlet() {
        HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
        ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
        registrationBean.setLoadOnStartup(1);
        registrationBean.addUrlMappings("/actuator/hystrix.stream");
        registrationBean.setName("HystrixMetricsStreamServlet");
        return registrationBean;
    }

}

创建Feign接口如下:

package com.rubin.scn.service.autodeliver.feign;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;

@FeignClient(value = "scn-service-resume", path = "resume")
public interface IResumeClient {

    @GetMapping("feign")
    String feign();

}

这里需要注意以下几点:

  1. @FeignClient注解的name属性用于指定要调用的服务提供者名称,和服务提供者配置文件中的spring.application.name要保持一致
  2. 接口中的接口方法,就好比是远程服务提供者Controller中的方法,在参数绑定时使用@PathVariable、@RequestParam、@RequestHeader等SpringMVC的注解,并且一定要指定value属性

使用方式很简单,直接注入使用即可:

package com.rubin.scn.service.autodeliver.controller;

import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import com.rubin.scn.common.entity.RResume;
import com.rubin.scn.service.autodeliver.feign.IResumeClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationDetails;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import java.util.List;
import java.util.Map;

@RestController
@RequestMapping("auto-deliver")
public class AutoDeliverController {

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private DiscoveryClient discoveryClient;

    @Autowired
    private IResumeClient iResumeClient;

    @GetMapping("service/{id}")
    public List<ServiceInstance> getServiceInfos(@PathVariable("id") String id) {
        return discoveryClient.getInstances(id);
    }

    @GetMapping("service/ip")
    public String getIp() {
        OAuth2AuthenticationDetails oAuth2AuthenticationDetails = (OAuth2AuthenticationDetails) SecurityContextHolder.getContext().getAuthentication().getDetails();
        Map<String, Object> decodedDetails = (Map<String, Object>) oAuth2AuthenticationDetails.getDecodedDetails();
        return decodedDetails.get("clientIp").toString();
    }

    @GetMapping("resume/{userId}")
    public RResume getResumeByUserId(@PathVariable("userId") Long userId) {
        List<ServiceInstance> instances = getServiceInfos("scn-service-resume");
        ServiceInstance instance = instances.get(0);
        String host = instance.getHost();
        Integer port = instance.getPort();
        String url = new StringBuffer("http://")
                .append(host)
                .append(":")
                .append(port)
                .append("/resume/detail?userId=")
                .append(userId)
                .toString();
        RResume rResume = restTemplate.getForObject(url, RResume.class);
        return rResume;
    }

    @GetMapping("resumes")
    public List getResumes() {
        List result = restTemplate.getForObject("http://scn-service-resume/resume/list", List.class);
        return result;
    }

    @GetMapping("hello")
    public String hello() throws InterruptedException {
        return restTemplate.getForObject("http://scn-service-resume/resume/hello", String.class);
    }

    // 使用@HystrixCommand注解进行熔断控制
    @HystrixCommand(
            // 线程池标识,要保持唯一,不唯一的话就公用了
            threadPoolKey = "timeout",
            // 线程池细节属性配置
            threadPoolProperties = {
                    @HystrixProperty(name = "coreSize", value = "1"), // 线程数
                    @HystrixProperty(name = "maxQueueSize", value = "20") // 等待队列长度
            },
            // commandProperties熔断的⼀些细节属性配置
            commandProperties = {
                    @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1000"),
                    // 统计时间窗口长度
                    @HystrixProperty(name = "metrics.rollingStats.timeInMilliseconds", value = "8000"),
                    // 统计时间窗口内的最小请求数
                    @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "2"),
                    // 统计时间窗口内的最内的错误数量百分比阀值
                    @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "50"),
                    // 自我修复时的活动窗口长度
                    @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "3000")
            },
            fallbackMethod = "rubinFallback"
    )
    @GetMapping("timeout")
    public String timeout() {
        return restTemplate.getForObject("http://scn-service-resume/resume/timeout", String.class);
    }

    public String rubinFallback() {
        return "rubin fallback.";
    }

    @GetMapping("feign")
    public String feign() {
        return iResumeClient.feign();
    }

}

Feign对负载均衡的支持

Feign本身已经集成了Ribbon的依赖和自动配置,因此不用我们额外引入依赖,可以直接通过ribbon.xxx来进行全局配置,也可以根据服务名.ribbon.xxx来进行针对服务的细节配置。

Feign默认的请求处理超时时间是1s。有时候我们的业务确实需要执行一定的时间,在这个时候,我们就需要调整请求处理超时时长。Feign有自己的超时设置,但是如果配置了Ribbon的超时,则会以Ribbon的配置为准。我们一般支配之Ribbon的超时配置即可,配置示例如下:

scn-service-resume:
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule
    #请求连接超时时间 超时时间为连接时间+请求处理时间 如果配制了ribbon的超时时间和重试 hystrix的超时时间要大于 ribbon的超时时间*重试次数
    #ConnectTimeout: 2000
    #请求处理超时时间
    #ReadTimeout: 5000
    #对所有操作都进行重试
    OkToRetryOnAllOperations: true
    # 根据如上配置,当访问到故障请求的时候,它会再尝试访问一次当前实例(次数由MaxAutoRetries配置),
    # 如果不行,就换一个实例进行访问,如果还不行,再换一次实例访问(更换次数由MaxAutoRetriesNextServer配置),
    # 如果依然不行,返回失败信息。
    MaxAutoRetries: 0 #对当前选中实例重试次数,不包括第一次调用
    MaxAutoRetriesNextServer: 0 #切换实例的重试次数

Feign对熔断器的支持

在Feign客户端工程陪u知文件中添加入下配置来开启Feign对熔断器的支持(默认是关闭的):

feign:
  hystrix:
    enabled: true

关于Hystrix的配置,我们可以参考博文SpringCloud Netflix之Hystrix熔断器。但是需要注意的一点是:熔断器的超时时间 >= (ribbon.ConnectTimeout + ribbon. ReadTimeout) * 重试次数。

对于自定义降级逻辑的处理,我们可以定义自定义的Fallback处理类,实现对应的Feign接口并使用@Component注解将其驻入容器,示例如下:

package com.rubin.scn.service.autodeliver.feign.fallback;

import com.rubin.scn.service.autodeliver.feign.IResumeClient;
import org.springframework.stereotype.Component;

@Component
public class ResumeClientFallback implements IResumeClient {
    @Override
    public String feign() {
        return "feign fallback.";
    }
}

在Feign接口中指定改熔断回退处理类:

package com.rubin.scn.service.autodeliver.feign;

import com.rubin.scn.service.autodeliver.feign.fallback.ResumeClientFallback;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;

@FeignClient(value = "scn-service-resume", path = "resume", fallback = ResumeClientFallback.class)
public interface IResumeClient {

    @GetMapping("feign")
    String feign();

}

Feign对请求压缩和响应压缩的支持

Feign支持对请求和响应进行GZIP压缩,以减少通信过程中的性能损耗。通过下面的配置参数即可开启请求与响应的压缩功能:

feign:
  # 请求响应启用压缩
  compression:
    response:
      enabled: true
    request:
      enabled: true
      mime-types: text/html,application/xml,application/json
      min-request-size: 2048

Feign的日志级别配置

Feign是HTTP请求客户端,类似于浏览器,但是其日志功能默认是关闭的。如果我们在开发环境下,一般调试时想要查看一下比较详细的调用日志(响应头、状态码等等)我们就需要对Feign的日志级别做一下配置调整。

开启Feign的日志级别我们需要定义一个Feign日志级别的配置类如下:

package com.rubin.scn.service.autodeliver.feign.logger;

import feign.Logger;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.context.annotation.Bean;

@SpringBootConfiguration
public class FeignLoggerConfig {

    // Feign的日志级别(Feign请求过程信息)
    // NONE:默认的,不显示任何日志----性能最好
    // BASIC:仅记录请求方法、URL、响应状态码以及执行时间----生产问题追踪
    // HEADERS:在BASIC级别的基础上,记录请求和响应的header
    // FULL:记录请求和响应的header、body和元数据----适用于开发及测试环境定位问题
    @Bean
    Logger.Level feignLevel() {
        return Logger.Level.FULL;
    }

}

再在配制中指定该Feign接口的日志级别为DEBUG:

# 开启对应Feign客户端的日志级别 适配Feign日志级别的打印 Feign日志只会对日志级别为DEBUG的做出响应
logging:
  level:
    com.rubin.scn.service.autodeliver.feign.IResumeClient: DEBUG

最后,将该配置类配置在Feign接口类中:

package com.rubin.scn.service.autodeliver.feign;

import com.rubin.scn.service.autodeliver.feign.fallback.ResumeClientFallback;
import com.rubin.scn.service.autodeliver.feign.logger.FeignLoggerConfig;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;

@FeignClient(value = "scn-service-resume", path = "resume", fallback = ResumeClientFallback.class, configuration = FeignLoggerConfig.class)
public interface IResumeClient {

    @GetMapping("feign")
    String feign();

}

以上就是关于Feign的内容了,欢迎小伙伴们积极留言交流~~~

本作品采用 知识共享署名 4.0 国际许可协议 进行许可
标签: SpringCloud
最后更新:2022年 6月 9日

RubinChu

一个快乐的小逗比~~~

打赏 点赞
< 上一篇
下一篇 >

文章评论

razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
取消回复
文章目录
  • Feign简介
  • Feign配置应用
  • Feign对负载均衡的支持
  • Feign对熔断器的支持
  • Feign对请求压缩和响应压缩的支持
  • Feign的日志级别配置
最新 热点 随机
最新 热点 随机
问题记录之Chrome设置屏蔽Https禁止调用Http行为 问题记录之Mac设置软链接 问题记录之JDK8连接MySQL数据库失败 面试系列之自我介绍 面试总结 算法思维
数据结构与算法概述 java面试系列之反射 Kafka高级特性之物理存储 MySQL学习之字符串类型 java面试系列之网络编程 MySQL之Sharding-JDBC数据脱敏

COPYRIGHT © 2021 rubinchu.com. ALL RIGHTS RESERVED.

Theme Kratos Made By Seaton Jiang

京ICP备19039146号-1