首页 > 其他分享 >微服务容错实战之openfeign

微服务容错实战之openfeign

时间:2024-05-26 17:00:44浏览次数:29  
标签:实战 feign 请求 openfeign 配置 class 容错 线程 public

Netflix开源软件套件(Netflix Open Source Software,简称Netflix OSS)是Netflix公司开源的一系列优秀的软件工具和框架,用于构建高性能、可扩展、弹性和可靠的分布式系统。提供了丰富的工具和解决方案,如Eureka、Feign、Ribbon、Hystrix、Zuul等。

这里我们借助Feign、Ribbon和Hystrix组件,用于实现微服务中的容错设计。Spring Cloud中提供了对这些组件的集成,在spring-cloud-starter-openfeign包中。

引入如下依赖即可使用feign

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

Feign

Feign是一个声明式的HTTP调用客户端,可以通过接口定义的方式进行服务调用。传统的服务调用,需要进行Request封装、创建连接、解析结果等,十分繁琐。而使用Feign的话,一下就简洁了不少。

使用示例

  • 在SpringBootApplication类上添加注解@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication
@EnableCircuitBreaker
public class WarehouseApplication {
    public static void main(String[] args) {
        SpringApplication.run(WarehouseApplication.class, args);
    }
}
  • 创建FeignCLient客户端,定义接口服务
// 在没有集成服务发现时,需要通过url配置服务具体连接信息
@FeignClient(value = "product", contextId = "product-info", url="127.0.0.1:8089")
public interface ProductServiceClient {

    @GetMapping(value = "/restful/products", consumes = MediaType.APPLICATION_JSON)
    Product[] getProducts();
}
  • 使用FeignClient接口
public class warehouseService {

    @Autowired
    private ProductServiceClient serviceClient;
    
    public Product[] listProduct() {
    
        Product[] products = serviceClient.getProducts();
        return products;
    }
}

高级配置

请求拦截器

Feign发送请求前需要生成一个请求对象,我们可以往这个对象的请求头中添加一些数据,例如Auth信息。Feign中提供了这样的机制,暴露出一个RequestInterceptor接口,接口方法会在发送请求之前执行。

创建一个RequestInterceptor的实现类,根据实际情况重写apply方法。

@Configuration
public class FeignInterceptor implements RequestInterceptor {

    @Override
    public void apply(RequestTemplate requestTemplate) {
        
        HttpServletRequest request = getHttpServletRequest();
        if (Objects.isNull(request)) {
            return;
        }
        Map<String, String> headers = getHeaders(request);
        
        // 把当前线程中请求对象中信息,复制到feign创建的reqeust请求中
        for (Map.Entry<String, String> entry : headers.entrySet()) {
    requestTemplate.header(entry.getKey(), entry.getValue());
        }
    }
}

执行日志

默认情况下,Feign不会记录访问日志,为了便于调试定位问题,我们可以更改配置,使其记录部分日志。Feign提供了一个Logger接口,里面定义了四种日志策略,如下

  • NONE:不记录任何日志信息,这是默认值。
  • BASIC:仅记录请求的方法,URL以及响应状态码和执行时间
  • HEADERS:在BASIC的基础上,额外记录了请求和响应的头信息
  • FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据。

这里我们选择HEADERS

feign:
  client:
    config:    
      default: # 这里用default就是全局配置,如果是写服务名称,则是针对某个微服务的配置
loggerLevel: HEADERS

因为Feign中打印的日志级别为DEBUG,所以需要将项目中Feignclient接口所在包的日志级别设置为BEBUG。

logging:
  level:
    com.long.clients: debug

Http客户端

Feign中发送请求时,会使用一个Client来执行。默认的CLient实现是为每一个请求都新建一个连接HttpURLConnection,这导致其性能较差,详见feign.Client.Default类。

除了默认的Client实现之外,Feign还支持高性能的HttpClient和OkHttp,只要导入相关依赖,就可以集成使用。HttpClient和OkHttp内部都使用了连接池,可以将已建立的连接缓存起来复用,避免了重复建立连接的时间花销,大幅提升了性能。

HttpClient

使用apache httpclient,添加依赖后无需进行额外配置,有关配置项是feign.httpclient.enabled,默认为true。相关实现类为HttpClientFeignLoadBalancedConfiguration和FeignRibbonClientAutoConfiguration。

<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-httpclient</artifactId>
</dependency>
Okhttp

使用okhttp,添加依赖后,还需要手动添加配置项。

<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-okhttp</artifactId>
</dependency>
feign:
  okhttp:
    enabled: true

okhttp中的连接池

public final class RealConnectionPool {
  /**
   * Background threads are used to cleanup expired connections. There will be at most a single
   * thread running per connection pool. The thread pool executor permits the pool itself to be
   * garbage collected.
   */
private static final Executor executor= new ThreadPoolExecutor(0 /* corePoolSize */,
      Integer.MAX_VALUE /* maximumPoolSize */, 60L /* keepAliveTime */, TimeUnit.SECONDS,
      new SynchronousQueue<>(), Util.threadFactory("OkHttp ConnectionPool", true));     
}

重试器

Feign中有一个重试机制(Retryer接口),在发送请求执行过程中,如果抛出了可重试异常(例如超时,异常为RetryableException),会进行重试操作。这类异常一般与网络IO相关,例如断连,有关的代码逻辑见SynchronousMethodHandler.invoke()。

超时配置

超时有关的配置对象是Request.Options,其中配置有两个部分,一个连接超时,二是读取超时,默认的超时配置分别为10秒和60秒。

public Options() {
  this(10, TimeUnit.SECONDS, 60, TimeUnit.SECONDS, true);
}

public static class Options {

  private final long connectTimeout;
  private final TimeUnit connectTimeoutUnit;
  private final long readTimeout;
  private final TimeUnit readTimeoutUnit;
  private final boolean followRedirects;
}
导致的问题

重试机制存在的问题是,有可能导致重复调用。例如调用接口后已经执行成功,但在返回数据的时候发生了IO错误,这时的重试,就是重复调用。

发生重复调用时,如果调用的接口不是幂等的,就会有异常行为,假设为扣款操作,会进行两次扣费。所以,如果服务不是幂等的,需要关闭重试操作。

配置重试器

默认的策略是会进行重试操作,见Retryer.Default,最大重试次数5,最大间隔时间1S。我们也可以根据自己的需要实现Retryer接口或者自定义Default,例如不进行重试,直接抛出异常。

  • 创建Retryer
public class FeignConfig {
    @Bean
    public Retryer retryer(){
        // 初始重试间隔时间 (默认按照1.5倍递增)
// 最大重试间隔时间
// 最大尝试次数
return new Retryer.Default(100,1000,1);
    }   
}
  • 绑定到feignclient.configuration
@FeignClient(value = "product", contextId = "product-info", configuration = {FeignConfig.class})
public interface ProductServiceClient {
}

自定义配置

默认配置

feign中有一个配置类FeignClientsConfiguration,在其中定义了相关组件的默认实现。在没有进行自定义配置时,会使用FeignClientsConfiguration中创建的对象。

作用域

feign中进行自定义配置时,有两个维度,一个是针对单client,一个是针对default。在client没有进行单独配置时,使用default配置;单个client配置只作用于自己。

feign:
  client:
    config:
      default: # 这里用default就是全局配置,如果是写服务名称,则是针对某个微服务的配置
loggerLevel: HEADERS
        connectTimeout: 1000
        readTimeout: 1000
      product: # 只针对product服务
loggerLevel: HEADERS
        connectTimeout: 5000
        readTimeout: 5000
配置方式

有两种配置方式,一是在配置文件中,二是在代码中。

在代码中进行配置时,需要先创建配置类,并在配置类中创建相关的Bean。

@Slf4j
@Configuration
public class FeignConfig {

    @Bean
    public Logger.Level feignLoggerLevel() {
        return Logger.Level.HEADERS;
    }

    @Bean
    public RequestInterceptor requestInterceptor(){
        return new RequestInterceptor() {
            @Override
            public void apply(RequestTemplate requestTemplate) {
                log.info("feign 请求前添加请求头");
                requestTemplate.header("traceId",traceId);
            }
        };
    }
    
    @Bean
    public Retryer retryer(){
        // 初始重试间隔时间 (默认按照1.5倍递增)
// 最大重试间隔时间
// 最大重试次数
return new Retryer.Default(100,1000,5);
    }

    @Bean
    public Request.Options options(){
        // 链接服务端超时时间ms
// 接受服务端响应时间ms
// 是否支持重定向
return new Request.Options(1000,1000,true);
    }
}

配置类创建好后,可以将其作为default配置,也可以是针对某个client。

default

@EnableFeignClients(defaultConfiguration = FeignConfig.class)
public class WarehouseApplication {
    public static void main(String[] args) {
        SpringApplication.run(WarehouseApplication.class, args);
    }
}

client

@FeignClient(value = "product", contextId = "product-info", configuration = {FeignConfig.class})
public interface ProductServiceClient {
}

集成Ribbon

openfeign中默认集成了ribbon,在starter openfeign引入了spring-cloud-starter-netflix-ribbon依赖,所以可以直接使用ribbon,不用额外引入依赖。

配置

要使用负载均衡,首先得搭建一个服务注册中心,如Eureka或者Nacos。

然后在项目中引入Eureka或者Nacos相关的依赖,并在配置文件中配置服务注册中心地址。

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
spring:
  cloud:
    nacos:
      server-addr: 127.0.0.1:8848
      discovery:
        namespace: fc44d960-44bf-4a41-b718-af4b061b2da3

最后启用服务发现,在Application类上配置如下注解。

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

使用

在@feignclient注解中设置服务Id,如auth-api

@FeignClient(value = "auth-api", contextId = "user-info")
public interface AuthClient {
}

在进行请求时,ribbon通过服务发现接口,获取该服务的可用地址列表。如果该服务存在多个实例,ribbon然后根据自己的负载均衡策略(如轮询、随机),选择一个地址。服务发现接口由Eureka、Nacos等中间件实现。

集成Hystrix

配置

feign可以通过feign-hystrix与Hystrix进行无缝集成,只需加入相关依赖和配置。

由于Hystrix已经不再更新,在Hoxton以上的spring cloud版本,可以集成其它的服务治理框架来进行熔断保护,例如Resilience4j或Sentinel。

添加依赖

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

集成时还需要feign-hystrix依赖,但是starter-openfeign中已经引入,就不需要再次引入了。

<dependency>
  <groupId>io.github.openfeign</groupId>
  <artifactId>feign-hystrix</artifactId>
</dependency>

在openfeign中,需要通过配置手动启用Hystrix。

启用Hystrix之后,本质上是对openfeign中请求发送模块再进行一次封装,记录每次请求的结果进行监测,在达到熔断条件时,进行服务熔断。服务熔断后,针对相应的请求,走服务降级逻辑。

feign:
  hystrix:
    enabled: true

使用

在@FeignClient注解中,新增fallback配置,fallback是一个降级回调。

在feign抛出异常时,hystrix会捕获feign中抛出的连接超时或者读取超时异常,转而调用fallback中的方法。

在服务熔断后,hystrix会拦截对其的调用,转而调用fallback中的方法。

@FeignClient(name = "warehouse", fallback = ProductServiceFallback.class)
public interface ProductServiceClient {
}

ProductServiceFallback实现该接口,并重写相应方法

@Component
public class ProductServiceFallback implements ProductServiceClient{
    
    @Override
    public Product[] getProducts() {
        return new Product[0];
    }
}

在FeignClient接口中的方法上添加@HystrixCommand注解,并配置相关的commandKey和threadPoolKey。

@FeignClient(name = "warehouse", fallback = ProductServiceFallback.class)
public interface ProductServiceClient {

    @GetMapping(value = "/restful/products", consumes = MediaType.APPLICATION_JSON)
    @HystrixCommand(commandKey = "simplecmd", threadPoolKey = "simpleThreadPool")
    Product[] getProducts();
}

Hystrix配置

我们在feign集成hystrix时,需要将@HystrixCommand注解设置在接口方法上,并对其进行了commandKey、threadPoolKey等配置。

  • commandKey表示对应的HystrixCommand对象(hystrix中的核心类),我们可以对这个对象进行配置,如果没配置,则使用Hystrix中默认的配置。
  • threadPoolKey表示对应的线程池名称,如果我们没有创建对应的线程池,则使用Hystrix中默认的线程池。

command

command可以配置多个,可以一个接口服务使用一个command,也可以多个接口服务使用同一个command。

command中主要进行execution和circuitBreake配置,详见HystrixCommandProperties。

  • execution,执行配置,使用默认的线程池隔离,可配置超时时间。
  • circuitBreaker,断路器配置,设置触发熔断的条件,如最小请求数和请求失败概率。

    关联配置为metrics.rollingStats.timeInMilliseconds,用于设置Metrics数据的滚动时间窗口,指定了数据统计的时间范围。默认为10,表示一个时间窗口为10S。在这个时间窗口内,Hystrix会统计并更新各种指标,如请求数、失败数、超时数等,用于评估熔断器和线程池的运行状况。

    以simplecmd为例,10秒内超过10个请求,并有50%的请求都失败时,就达到熔断的条件了。熔断后该服务示例休眠十秒钟,即十秒内,不调用该服务实例。

hystrix:
  command:
    # 对单独的hystrix command进行设置,命令名默认为方法名,可以使用注解的commandkey属性进行设置
    # 不同服务应该设置不同conmandKey,否则一个断路器断开后,会影响所有的服务
simplecmd:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 2000 # 设置超时时间
circuitBreaker: # 断路器配置
        requestVolumeThreshold: 10 # 触发熔断的最小请求数量
errorThresholdPercentage: 50 # 触发熔断的错误百分比
sleepWindowInMilliseconds: 10000# 熔断后的休眠时间
    # 对所有的hytrtix command进行设置,优先级不如单独设置
default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 1500 # 设置超时时间
circuitBreaker: # 断路器配置
        requestVolumeThreshold: 5 # 触发熔断的最小请求数量
errorThresholdPercentage: 50 # 触发熔断的错误百分比
sleepWindowInMilliseconds: 5000 # 熔断后的休眠时间

threadPool

在execution中配置使用线程池进行隔离后,如果不为其设置threadPoolKey,将使用默认的线程池。多个服务都使用一个线程池时,一个服务阻塞会耗尽线程池中的线程,这会导致其它服务也被阻塞。所以,有必要为不同服务设置不同的线程池。

hystrix:
  # 线程池配置
threadpool:
    # 单独配置
simpleThreadPool:
      # 核心线程数量
coreSize: 10
      # 最大线程数量
maximumSize: 15
      # 是否允许线程池扩展到最大线程池数量
allowMaximumSizeToDivergeFromCoreSize: false
      # 多余线程被回收前的存活时间
keepAliveTimeMinutes: 1
      # 任务队列最大长度
maxQueueSize: 1000
      # 队列内任务长度达到该值后,即使队列未满,也会拒绝新的请求,可用于动态调整队列长度
queueSizeRejectionThreshold: 600
    # 默认配置
default:
      # 默认10
coreSize: 10
      maximumSize: 10

标签:实战,feign,请求,openfeign,配置,class,容错,线程,public
From: https://www.cnblogs.com/cd-along/p/18213949

相关文章

  • Openfeign集成Ribbon、Hystrix原理解析
    本篇内容为解析SpringCloudOpenfeign在如下场景中的运行原理Openfeign单独使用集成负载均衡器,这里选择Ribbon,也可以选择SpringLoadBalancer集成断路器,这里选择Hystrix,也可以选择Sentinel相关依赖如下,使用的SpringCloud版本为Hoxton.SR3<dependency><groupId>org.s......
  • 集合竞价选股策略实战测试
    2.3.2版本发布的集合竞价选股策略是网友吴PSYP提供的,团队按照策略实现的选股算法,最近半个月对策略进行的实战测试,从集合竞价选股开始,到股票收盘,收盘价格大于集合竞价价格,算作盈利,测试结果如下,一共12个交易日,共132只股票,其中收盘价格大于集合竞价价格的一共有105只,占80%,其中收盘涨......
  • kaggle竞赛实战2
    接上一篇,本篇针对merchant以及transaction数据集进行预处理,包括缺失值、inf值处理以及object类型数据的独热编码转化,完成后详细代码如下:#In[5]:importosimportnumpyasnpimportpandasaspd#In[6]:pd.read_excel('d:/Data_Dictionary.xlsx',header=2,sheet_nam......
  • [书生·浦语大模型实战营]——第三节:茴香豆:搭建你的 RAG 智能助理
    0.RAG概述定义:RAG(RetrievalAugmentedGeneration)技术,通过检索与用户输入相关的信息片段,并结合外部知识库来生成更准确、更丰富的回答。解决LLMs在处理知识密集型任务时可能遇到的挑战,如幻觉、知识过时和缺乏透明、可追溯的推理过程等。提供更准确的回答、降低推理成......
  • 详细分析crontab定时执行任务(附Demo | 定时清空Tomcat的实战)
    目录前言1.基本知识2.Demo3.实战3.1错误版本3.2正确版本前言由于用户量大,且导出的日志以及缓存特别多,急需定期删除文件1.基本知识crontab是一个用于定时执行任务的命令行工具,通常在Unix和类Unix系统中可用,表示一个包含需要定时执行的任务列表的表格......
  • 【机器学习聚类算法实战-5】机器学习聚类算法之DBSCAN聚类、K均值聚类算法、分层聚类
    ......
  • C语言游戏实战(12):植物大战僵尸(坤版)
    植物大战僵尸前言:本游戏使用C语言和easyx图形库编写,通过这个项目我们可以深度的掌握C语言的各种语言特性和高级开发技巧,以及锻炼我们独立的项目开发能力,在开始编写代码之前,我们需要先了解一下游戏的基本规则和功能:游戏界面:游戏界面是一个矩形区域,玩家可以在区域内进行......
  • 【QGIS入门实战精品教程】10.7: 基于DEM的地形因子分析(坡度、坡向、粗糙度、山体阴影、
    文章目录一、加载dem二、山体阴影三、坡度四、坡向五、地形耐用指数六、地形位置指数七、地表粗糙度一、加载dem二、山体阴影方法一:符号系统利用符号系统中的山体阴影,渲染出阴影效果。方法二:山体阴影工具该算法计算输入中的数字化地形模型的山体阴......
  • Netty_Redis_Zookeeper高并发实战-读书笔记
    转载自:https://www.cnblogs.com/leihongzhi/p/17381156.html 第1章    高并发时代的必备技能1.nettyNetty是JBOSS提供的一个Java开源框架,基于NIO的客户端/服务器编程框架,能够快速开发高并发、高可用、高可靠的网络服务器程序,也能开发高可用、高可靠的客户端程序。NIO是......
  • Vue3实战笔记(45)—VUE3封装一些echarts常用的组件,附源码
    文章目录前言一、柱状图框选二、折线图堆叠总结前言日前使用hooks的方式封装组件,在我使用复杂的图标时候遇到了些问题,预想在onMounted中初始化echarts,在使用hooks的时候,组件没有渲染完,使用实例会出现各种各样的问题,并且在hooks中使用一些外部属性也属实遇到了些麻烦......