好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

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

需求描述

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

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

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

我需要的是这样的效果

?

1

2

3

4

5

try {

     feign.api();

  } catch (){

  return fallback();

}

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

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

源码学习

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

基于之前的分析关键代码

?

1

HystrixInvocationHandler (feign.hystrix)

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

@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() 方法

?

1

2

3

4

5

6

7

8

9

10

11

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 动态代理生成了代理类,调用方法时实际调用的是

?

1

java.lang.reflect.InvocationHandler#invoke

方案构想

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

然后在我们自己的 InvocationHandler 中调用feign 官方的 InvocationHandler 就行,也就是

?

1

feign.hystrix.HystrixInvocationHandler#invoke

这个方法中的

?

1

this .dispatch.get(method).invoke(args);

这个代码

方案具体代码实现

方案一

自己实现

?

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

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);

         });

     }

}

?

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

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

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就行

?

1

2

3

4

@Bean

     CusFeignBuilder cusFeignBuilder(){

         return new CusFeignBuilder();

     }

方案二

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

加入依赖

?

1

2

3

4

< dependency >

     < groupId >com.alibaba.cloud</ groupId >

     < artifactId >spring-cloud-starter-alibaba-sentinel</ artifactId >

</ dependency >

配置开启

?

1

feign.sentinel.enabled= true

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

?

1

2

3

4

5

6

7

@Component

public class DeviceApiFallBack implements DeviceApi{

   @Override

         public ServerResponse<String> login(String appId) {

             return ServerResponse.createByErrorMessage( "请求失败" );

         }

}

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

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

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

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

原文链接:https://blog.csdn.net/weixin_39660224/article/details/109497272

查看更多关于如何自定义feign调用实现hystrix超时、异常熔断的详细内容...

  阅读:19次