如何自定义feign调用实现hystrix超时、异常熔断

需求描述

spring cloud 项目中feign 整合 hystrix经常使用,但是最近发现hystrix功能强大,但是对我们来说有些大材小用。

首先我只需要他的一个熔断作用,就是说请求超时、异常了返回 FeignClient注解中配置的fallback,不需要非阻塞操作、也不需要重试,hystrix 调用feign时候做了线程池隔离处理,这样增加了项目复杂度(线程池参数配置、线程少了请求服务直接拒绝,多了线程得管理。。。)

目前feign 超时之后是直接抛异常的,这样的话虽然是及时熔断了,但是正常的程序逻辑不走了配置的fallback也没有作用,这个配置项得配合 hystrix 才行。

我需要的是这样的效果

  try{
     feign.api();
  }catch(){
  return fallback();
 }

但是每个feign调用都手动加上try..catch 实在是太low了,最好能写个类似切面一样的玩意。

这时候就想到了 hystrix,既然人家框架已经做了,我直接看下代码,copy不完了么

源码学习

前两天发布了一篇文章也是关于feign、hystrix 调用集成的

基于之前的分析关键代码 HystrixInvocationHandler (feign.hystrix)

@Override
  public Object invoke(final Object proxy, final Method method, final Object[] args)
      throws Throwable {
    .............
  // setterMethodMap 封装 hystrixCommand 配置信息(超时时间、是否重试.....)
    HystrixCommand<Object> hystrixCommand = new HystrixCommand<Object>(setterMethodMap.get(method)) {
      @Override
      protected Object run() throws Exception {
        ....
        HystrixInvocationHandler.this.dispatch.get(method).invoke(args);
       ....
      }
      @Override
      protected Object getFallback() {
        .........
      }
    };
   ......
    return hystrixCommand.execute();
  }

按照之前分析源码方式,直接看哪里被调用了就可以看到, hystrix 实际上自己封装了一个 feign.Builer 类名是 feign.hystrix.HystrixFeign.Builder 用的是建造者模式,生成的类是在调用服务时用到

看到 关键的 build() 方法

Feign build(final FallbackFactory<?> nullableFallbackFactory) {
      super.invocationHandlerFactory(new InvocationHandlerFactory() {
        // 重新定义一个 InvocationHandler 实现 类似 aop效果
        @Override public InvocationHandler create(Target target,
            Map<Method, MethodHandler> dispatch) {
          return new HystrixInvocationHandler(target, dispatch, setterFactory, nullableFallbackFactory);
        }
      });
      super.contract(new HystrixDelegatingContract(contract));
      return super.build();
    }

spring 动态代理我这里不多说了,核心就是 InvocationHandler (如果是jdk动态代理的话),那么 feign 这里也是,我们看看feign 调用声明是个接口,实际上是spring 动态代理生成了代理类,调用方法时实际调用的是

java.lang.reflect.InvocationHandler#invoke

方案构想

那么我们只需要借鉴下 hystrix 的方式,自己实现一个feign.build ,将 InvocationHandler 换成自己的,

然后在我们自己的 InvocationHandler 中调用feign 官方的 InvocationHandler 就行,也就是 feign.hystrix.HystrixInvocationHandler#invoke这个方法中的this.dispatch.get(method).invoke(args);这个代码

方案具体代码实现

方案一

自己实现

import feign.Feign;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
/**
 * 自定义feign 构建
 * @author hgf
 */
public class CusFeignBuilder extends Feign.Builder{
    public CusFeignBuilder() {
        this.invocationHandlerFactory((target, dispatch) -> {
            Class<?> type = target.type();
            FeignClient annotation = type.getAnnotation(FeignClient.class);
            // 构造 fallback 实例
            Object fallBackObj = null;
            if (annotation != null && !annotation.fallback().equals(void.class)) {
                try {
                    fallBackObj = annotation.fallback().newInstance();
                } catch (InstantiationException | IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
            return new CusFeignInvocationHandler(target, dispatch, fallBackObj);
        });
    }
}
import feign.Feign;
import feign.InvocationHandlerFactory;
import feign.Target;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.openfeign.FeignClient;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import static com.eco.common.utils.Md5Util.logger;
import static feign.Util.checkNotNull;
/**
 * 自定义的feign调用
 */
@Slf4j
public class CusFeignInvocationHandler implements InvocationHandler {
    private final Target target;
    private final Map<Method, InvocationHandlerFactory.MethodHandler> dispatch;
    private final Object fallbackObj;
    private final Map<String, Method> fallbackMethodMap = new ConcurrentHashMap<>();
    CusFeignInvocationHandler(Target target, Map<Method, InvocationHandlerFactory.MethodHandler> dispatch, Object  fallbackObj) {
        this.target = checkNotNull(target, "target");
        this.dispatch = checkNotNull(dispatch, "dispatch for %s", target);
        this.fallbackObj = fallbackObj;
    }
    public Object feignInvoke(Object proxy, Method method, Object[] args) throws Throwable {
        if ("equals".equals(method.getName())) {
            try {
                Object
                        otherHandler =
                        args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
                return equals(otherHandler);
            } catch (IllegalArgumentException e) {
                return false;
            }
        } else if ("hashCode".equals(method.getName())) {
            return hashCode();
        } else if ("toString".equals(method.getName())) {
            return toString();
        }
        return dispatch.get(method).invoke(args);
    }
    @Override
    public boolean equals(Object obj) {
        if (obj instanceof CusFeignInvocationHandler) {
            CusFeignInvocationHandler other = (CusFeignInvocationHandler) obj;
            return target.equals(other.target);
        }
        return false;
    }
    @Override
    public int hashCode() {
        return target.hashCode();
    }
    @Override
    public String toString() {
        return target.toString();
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            return feignInvoke(proxy, method, args);
        } catch (Throwable throwable) {
            String configKey = Feign.configKey(target.type(), method);
            logger.error("{} 请求 出现异常 ==> {}", configKey, throwable.getMessage());
            try {
                return getFallbackReturn(method, args, throwable);
            } catch (Throwable e) {
                throw throwable;
            }
        }
    }
    /**
     * 反射调用 {@link FeignClient#fallback()}生成失败返回值
     * @param method            当前feign方法
     * @param args              参数
     * @param throwable         异常
     */
    public Object getFallbackReturn(Method method, Object[] args, Throwable throwable) throws Throwable {
        if (fallbackObj == null) {
            throw new RuntimeException("fallbackObj is null");
        }
        String configKey = Feign.configKey(target.type(), method);
        Method fallbackMethod = fallbackMethodMap.get(configKey);
        if (fallbackMethod == null) {
            Class<?> declaringClass = method.getDeclaringClass();
            FeignClient annotation = declaringClass.getAnnotation(FeignClient.class);
            if (annotation == null) {
                throw new RuntimeException("FeignClient annotation not found");
            }
            // 失败返回
            Class<?> fallback = annotation.fallback();
            fallbackMethod = fallback.getMethod(method.getName(), method.getParameterTypes());
            fallbackMethodMap.put(configKey, fallbackMethod);
        }
        if (fallbackMethod == null) {
            throw new RuntimeException("fallbackMethodMap not found");
        }
        return fallbackMethod.invoke(fallbackObj, args);
    }
}

然后在 spring 容器中注册这个bean就行

@Bean
    CusFeignBuilder cusFeignBuilder(){
        return new CusFeignBuilder();
    }

方案二

集成 sentinel ,今天写博客再回头看源码时候才发现的

加入依赖

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

配置开启

feign.sentinel.enabled=true

手动实现feign 接口,将实体类注册到 spring 中

@Component
public class DeviceApiFallBack implements DeviceApi{
  @Override
        public ServerResponse<String> login(String appId) {
            return ServerResponse.createByErrorMessage("请求失败");
        }
}

其实看代码知道原理一样,无非实现方式不一样

两个方案其实都行,方案一自己实现代码量多,方案二sentinel 官方实现,但是需要引入依赖,增加复杂度,而且 接口实现需要注册到spring 中

目前我选的还是方案一,简单。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • SpringBoot如何使用feign实现远程接口调用和错误熔断

    这篇文章主要介绍了SpringBoot如何使用feign实现远程接口调用和错误熔断,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 1.第一步,新建两个简单的springboot项目并创建rest接口 demo系统的rest接口 plus系统的调用接口 2.在项目pom文件里导入feign和hystrix的pom依赖包 <properties> <java.version>1.8</java.version> <s

  • feign的ribbon超时配置和hystrix的超时配置说明

    先看下我的配置: ribbon: MaxAutoRetries: 1 #最大重试次数,当Eureka中可以找到服务,但是服务连不上时将会重试 MaxAutoRetriesNextServer: 1 #切换实例的重试次数 OkToRetryOnAllOperations: false # 对所有的操作请求都进行重试,如果是get则可以,如果是post,put等操作没有实现幂等的情况下是很危险的,所以设置为false ConnectTimeout: 1000 #请求连接的超时时间 ReadTimeo

  • 详解Spring Cloud Feign 熔断配置的一些小坑

    1.在使用feign做服务调用时,使用继承的方式调用服务,加入Hystrix的熔断处理fallback配置时,会报错,已解决. 2.使用feign默认配置,熔断不生效,已解决. 最近在做微服务的学习,发现在使用feign做服务调用时,使用继承的方式调用服务,加入Hystrix的熔断处理fallback配置时,会报错,代码如下: @RequestMapping("/demo/api") public interface HelloApi { @GetMapping("user/

  • feign客户端设置超时时间操作

    我就废话不多说了,大家还是直接看代码吧~ /** * feign调用客户端 */ @FeignClient(name = "user", url = "${user.url}", configuration = MyFeignDecoder.class) public interface UserClient { @GetMapping("/rest/user/get/detail/{loginName}") JSONObject getUser

  • SpringCloud微服务之Hystrix组件实现服务熔断的方法

    一.熔断器简介 微服务架构特点就是多服务,多数据源,支撑系统应用.这样导致微服务之间存在依赖关系.如果其中一个服务故障,可能导致系统宕机,这就是所谓的雪崩效应. 1.服务熔断 微服务架构中某个微服务发生故障时,要快速切断服务,提示用户,后续请求,不调用该服务,直接返回,释放资源,这就是服务熔断. 熔断生效后,会在指定的时间后调用请求来测试依赖是否恢复,依赖的应用恢复后关闭熔断. 2.服务降级 服务器高并发下,压力剧增的时候,根据当业务情况以及流量,对一些服务和页面有策略的降级(可以理解为关闭不必

  • 如何自定义feign调用实现hystrix超时、异常熔断

    需求描述 spring cloud 项目中feign 整合 hystrix经常使用,但是最近发现hystrix功能强大,但是对我们来说有些大材小用. 首先我只需要他的一个熔断作用,就是说请求超时.异常了返回 FeignClient注解中配置的fallback,不需要非阻塞操作.也不需要重试,hystrix 调用feign时候做了线程池隔离处理,这样增加了项目复杂度(线程池参数配置.线程少了请求服务直接拒绝,多了线程得管理...) 目前feign 超时之后是直接抛异常的,这样的话虽然是及时熔断了,

  • 记一次线上SpringCloud Feign请求服务超时异常排查问题

    由于近期线上单量暴涨,第三方反馈部分工单业务存在查询处理失败现象,经排查是当前系统通过FeignClient调用下游系统出现部分超时失败(异常代码贴在下方). Caused by: feign.RetryableException: Read timed out executing POST http://xxxx        at feign.FeignException.errorExecuting(FeignException.java:84) ~[feign-core-10.1.0.j

  • 详解python中自定义超时异常的几种方法

    最近在项目中调用第三方接口时候,经常会出现请求超时的情况,或者参数的问题导致调用异代码异常.针对超时异常,查询了python 相关文档,没有并发现完善的包来根据用户自定义的时间来抛出超时异常的模块.所以自己干脆自己来实现一个自定义的超时异常.目前找到了两种方式来实现超时异常的功能(signal.alarm().threading实现超时异常) 方法1 thread + time 原理:将要调用的功能函数放入子线程,通过设定子线程的阻塞时间,超时则主线程并不会等待子线程的执行.主线程退出,子线程就

  • Feign调用接口解决处理内部异常的问题

    问题描述: 当使用feign调用接口,出现400-500-的接口问题时.会出错feign:FeignException.(因为是错误,只能用catch Throwable,不可使用catch Exception捕获异常)导致程序无法继续运行. 问题原因: 由于feign默认的错误处理类是FunFeignFallback会throw new AfsBaseExceptio导致外部无法捕获异常. package com.ruicar.afs.cloud.common.core.feign; impo

  • 基于springboot服务间Feign调用超时的解决方案

    解决springboot服务间Feign调用超时问题概述 1.起因 在完成项目功能需求的开发,经过自己测试以及通过测试组测试通过后,昨晚正式部署到线上环境进行正式运行前的最后一次的测试.但是在测试中,由A服务调用B服务接口时,***通过Feign调用(其实就是http请求,当A服务调用B服务时,如果不配置超时时间,那么A发出请求后,B应该立即响应,否则A服务会认为B已经断开连接)出现***连接超时的错误,错误信息:Read timed out- 2.原因 用idea开发debug模式调试代码时,

  • 解决微服务feign调用添加token的问题

    微服务feign调用添加token 1.一般情况是这么配置的 具体的怎么调用就不说了 如下配置,就可以在请求头中添加需要的请求头信息. package localdate; import feign.RequestInterceptor; import feign.RequestTemplate; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; imp

  • Feign调用全局异常处理解决方案

    异常信息形如: TestService#addRecord(ParamVO) failed and no fallback available.: 对于failed and no fallback available.这种异常信息,是因为项目开启了熔断: feign.hystrix.enabled: true 当调用服务时抛出了异常,却没有定义fallback方法,就会抛出上述异常.由此引出了第一个解决方式. 解决方案: 自定义Feign解析器: import com.alibaba.fastj

  • Feign调用中的两种Header传参方式小结

    目录 Feign调用中的两种Header传参方式 在请求拦截器中统一配置 通过@RequestHeader注解 调用feign接口时,如何往header中添加参数 总结 Feign调用中的两种Header传参方式 在Spring Cloud Netflix栈中,各个微服务都是以HTTP接口的形式暴露自身服务的,因此在调用远程服务时就必须使用HTTP客户端. 我们可以使用JDK原生的URLConnection.Apache的Http Client.Netty的异步HTTP Client, Spri

  • 详解feign调用session丢失解决方案

    最近在做项目的时候发现,微服务使用feign相互之间调用时,存在session丢失的问题.例如,使用Feign调用某个远程API,这个远程API需要传递一个鉴权信息,我们可以把cookie里面的session信息放到Header里面,这个Header是动态的,跟你的HttpRequest相关,我们选择编写一个拦截器来实现Header的传递,也就是需要实现RequestInterceptor接口,具体代码如下: @Configuration @EnableFeignClients(basePack

  • spring cloud hystrix 超时时间使用方式详解

    我们在使用后台微服务的时候,各个服务之前会有很多请求和交叉业务.这里会引起雪崩.超时等异常处理.SpringCloud Hystrix服务降级.容错机治理使 hystrix 有很好的支持,引入后实现断路器功能. 1:pom 引入jar包 <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</a

随机推荐