关于feign:撸了一个-Feign-增强包-V20-升级版

38次阅读

共计 4615 个字符,预计需要花费 12 分钟才能阅读完成。

前言

大略在两年前我写过一篇 撸了一个 Feign 加强包,过后筹备是利用 SpringBoot + K8s 构建利用,这个库能够相似于 SpringCloud 那样联合 SpringBoot 应用申明式接口来达到服务间通信的目标。

但前期因为技术栈发生变化(改为 Go),导致该我的项目只实现了根本需要后就搁置了。

偶合的时最近外部有局部我的项目又打算采纳 SpringBoot + K8s 开发,于是便着手持续保护;现曾经外部迭代了几个版本比较稳定了,也减少了一些实用功能,在此分享给大家。

https://github.com/crossoverJie/feign-plus

首先是新增了一些 features:

  • 更加对立的 API。
  • 对立的申请、响应、异样日志记录。
  • 自定义拦截器。
  • Metric 反对。
  • 异样传递。

示例

联合下面提到的一些个性做一些简略介绍,对立的 API 次要是在应用层面:

在上一个版本中申明接口如下:

@FeignPlusClient(name = "github", url = "${github.url}")
public interface Github {@RequestLine("GET /repos/{owner}/{repo}/contributors")
    List<GitHubRes> contributors(@Param("owner") String owner, @Param("repo") String repo);
}

其中的 @RequestLine 等注解都是应用 feign 包所提供的。

这次更新后改为如下形式:

@RequestMapping("/v1/demo")
@FeignPlusClient(name = "demo", url = "${feign.demo.url}", port = "${feign.demo.port}")
public interface DemoApi {@GetMapping("/id")
    String sayHello(@RequestParam(value = "id") Long id);

    @GetMapping("/id/{id}")
    String id(@PathVariable(value = "id") Long id);

    @PostMapping("/create")
    Order create(@RequestBody OrderCreateReq req);

    @GetMapping("/query")
    Order query(@SpringQueryMap OrderQueryDTO dto);
}

相熟的滋味,根本都是 Spring 自带的注解,这样在应用上学习老本更低,同时与我的项目中本来的接口写法保持一致。

@SpringQueryMap(top.crossoverjie.feign.plus.contract.SpringQueryMap) 是由 feign-plus 提供,其实就是从 SpringCloud 中 copy 过去的。

我这里写了两个 demo 来模仿调用:

provider:作为服务提供者提供了一系列接口供生产方调用,并对外提供了一个 api 模块。


demo:作为服务消费者依赖 provider-api 模块,依据其中申明的接口进行近程调用。

配置文件:

server:
  port: 8181

feign:
  demo:
    url : http://127.0.0.1
    port: 8080

logging:
  level:
    top:
      crossoverjie: debug

management:
  endpoints:
    web:
      base-path: /actuator
      exposure:
        include: '*'
  metrics:
    distribution:
      percentiles:
        all: 0.5,0.75,0.95,0.99
    export:
      prometheus:
        enabled: true
        step: 1m
spring:
  application:
    name: demo

当咱们拜访 http://127.0.0.1:8181/hello/2 接口时从控制台能够看到调用后果:

日志记录

从上图中能够看出 feign-plus 会用 debug 记录申请 / 响应后果,如果须要打印进去时须要将该包下的日志级别调整为 debug:

logging:
  level:
    top:
      crossoverjie: debug

因为内置了拦截器,也能够本人继承 top.crossoverjie.feign.plus.log.DefaultLogInterceptor 来实现本人的日志拦挡记录,或者其余业务逻辑。

@Component
@Slf4j
public class CustomFeignInterceptor extends DefaultLogInterceptor {
    @Override
    public void request(String target, String url, String body) {super.request(target, url, body);
        log.info("request");
    }

    @Override
    public void exception(String target, String url, FeignException feignException) {super.exception(target, url, feignException);
    }

    @Override
    public void response(String target, String url, Object response) {super.response(target, url, response);
        log.info("response");
    }
}

监控 metric

feign-plus 会自行记录每个接口之间的调用耗时、异样等状况。

拜访 http://127.0.0.1:8181/actuator/prometheus 会看到相干埋点信息,通过 feign_call* 的 key 能够自行在 Grafana 配置相干面板,相似于下图:

异样传递

rpc(近程调用)要应用起来真的相似于本地调用,异样传递必不可少。

// provider
    public Order query(OrderQueryDTO dto) {log.info("dto = {}", dto);
        if (dto.getId().equals("1")) {throw new DemoException("provider test exception");
        }
        return new Order(dto.getId());
    }

// consumer
        try {demoApi.query(new OrderQueryDTO(id, "zhangsan"));
        } catch (DemoException e) {log.error("feignCall:{}, sourceApp:[{}], sourceStackTrace:{}", e.getMessage(), e.getAppName(), e.getDebugStackTrace(), e);
        }    

比方 provider 中抛出了一个自定义的异样,在 consumer 中能够通过 try/catch 捕捉到该异样。

为了在 feign-plus 中实现该性能须要几个步骤:

  1. 自定义一个通用异样。
  2. 服务提供方须要实现一个全局拦截器,当产生异样时对立对外响应数据。
  3. 服务生产方须要自定义一个异样解码器的 bean。

这里我在 provider 中自定义了一个 DemoException

通常这个类应该定义在公司外部的通用包中,这里为了演示不便。

接着定义了一个 HttpStatus 的类用于对立对外响应。

@Data
@AllArgsConstructor
@NoArgsConstructor
public class HttpStatus {
    private String appName;
    private int code;
    private String message;
    private String debugStackTrace;
}

这个也应该放在通用包中。

而后在 provider 中定义全局异样解决:

当出现异常时便会返回一个 http_code=500 的数据:

到这一步又会呈现一个引战话题:HTTP 接口返回到底是全副返回 200 而后通过 code 来来判断,还是参考 http_code 进行返回?

这里不做过多探讨,具体能够参考耗子叔的文章:
“一把梭:REST API 全用 POST”

feign-plus 默认采纳的 http_code !=200 才会认为产生了异样。

而这里的 http_status 也是参考了 Google 的 api 设计:

具体能够参考这个链接:
https://cloud.google.com/apis/design/errors#propagating_errors

而后定义一个异样解析器:

@Configuration
public class FeignExceptionConfig {
    @Bean
    public FeignErrorDecoder feignExceptionDecoder() {return (methodName, response, e) -> {HttpStatus status = JSONUtil.toBean(response, HttpStatus.class);
            return new DemoException(status.getAppName(), status.getCode(), status.getMessage(), status.getDebugStackTrace());
        };
    }
}

通常这块代码也是放在根底包中。


这样当服务提供方抛出异样时,消费者便能胜利拿到该异样:

实现原理

实现原理其实也比较简单,理解 rpc 原理的话应该会晓得,服务提供者返回的异样调用方是不可能接管到的,这和是否由一种语言实现也没关系。

毕竟两个过程之间的栈是齐全不同的,不在一台服务器上,甚至都不在一个地区。

所以 provider 抛出异样后,消费者只能拿到一串报文,咱们只能依据这段报文解析出其中的异样信息,而后再从新创立一个外部自定义的异样(比方这里的 DemoException),也就是咱们自定义异样解析器所干的事件。

下图就是这个异样传递的大抵流程:

code message 模式

因为 feign-plus 默认是采纳 http_code != 200 的形式来抛出异样的,所以采纳 http_code=200, code message 的形式响应数据将不会传递异样,仍然会工作是一次失常调用。

不过基于该模式传递异样也是能够实现的,但没法做到对立,比方有些团队习惯 code !=0 示意异样,甚至字段都不是 code;再或者异样信息有些是放在 message 或 msg 字段中。

每个团队、集体习惯都不雷同,所以没法形象出一个规范,因而也就没做相干适配。

这也印证了应用国际标准所带来的益处。

限于篇幅,如果有相干需要的敌人也能够在评论区沟通,实现上会比当初略微简单一点点🤏🏻。

总结

我的项目源码:
https://github.com/crossoverJie/feign-plus

基于 2022 年云原生这个背景,当然更举荐大家应用 gRPC 来做服务间通信,这样也不须要保护相似于这样的库了。

不过在一些调用第三方接口而对方也没有提供 SDK 时,这个库也有肯定用武之地,尽管应用原生 feign 也能达到雷同目标,但应用该库能够使得与 Spring 开发体验统一,同时内置了日志、metric 等性能,防止了反复开发。

你的点赞与分享是对我最大的反对

正文完
 0