当前位置: 首页 > 科技观察

我卷了一个Feign增强包V2.0升级版

时间:2023-03-17 22:44:29 科技观察

前言大约两年前写了一篇文章,卷了一个Feign增强包。当时打算用SpringBoot+K8s来搭建应用。这个库可以像SpringCloud一样结合SpringBoot使用声明式接口实现服务间通信。但是由于技术栈的变化(改成了Go),这个项目只实现了基本的需求就被搁置了。无独有偶,最近内部项目有计划使用SpringBoot+K8s进行开发,所以继续维护;现在内部已经迭代了几个版本,比较稳定,也增加了一些实用的功能,在这里分享给大家。https://github.com/crossoverJie/feign-plus。首先是增加一些特性:更加统一的API。统一的请求、响应和异常日志记录。自定义拦截器。公制支持。异常交付。例子结合上面提到的一些特点做一个简单的介绍。统一API主要在使用层面:在之前的版本中,接口声明如下:@FeignPlusClient(name="github",url="${github.url}")publicinterfaceGithub{@RequestLine("GET/repos/{owner}/{repo}/contributors")Listcontributors(@Param("owner")Stringowner,@Param("repo")Stringrepo);}@RequestLine和其他注释是由feign包提供。本次更新后改为如下方法:@RequestMapping("/v1/demo")@FeignPlusClient(name="demo",url="${feign.demo.url}",port="${feign.demo.port}")publicinterfaceDemoApi{@GetMapping("/id")StringsayHello(@RequestParam(value="id")Longid);@GetMapping("/id/{id}")Stringid(@PathVariable(value="id")Longid);@PostMapping("/create")订单创建(@RequestBodyOrderCreateReqreq);@GetMapping("/query")Orderquery(@SpringQueryMapOrderQueryDTOdto);}熟悉的味道,基本都是Spring自带的注解,这样在使用中学习成本更低,同时保持一致用项目中原有的界面写法。@SpringQueryMap(top.crossoverjie.feign.plus.contract.SpringQueryMap)是feign-plus提供的,其实是从SpringCloud复制过来的。这里我写了两个demo来模拟调用:provider:作为服务提供者,提供一系列接口供消费者调用,对外提供一个api模块。demo:作为服务消费者,依赖provider-api模块,根据其中声明的接口进行远程调用。配置文件:服务器:端口:8181feign:演示:url:http://127.0.0.1端口:8080日志记录:level:top:crossoverjie:debugmanagement:endpoints:web:base-path:/actuatorexposure:include:'*'metrics:distribution:percentiles:all:0.5,0.75,0.95,0.99export:prometheus:enabled:truestep:1mspring:application:name:demo当我们访问http://127.0.0.1:8181/hello/2接口时control在平台上可以看到调用结果:logrecord从上图可以看出feign-plus会使用debug记录请求/响应结果。如果需要打印出来,需要调整包下的日志级别进行调试:logging:level:top:crossoverjie:debug因为内置了拦截器,所以也可以继承top.crossoverjie.feign.plus。log.DefaultLogInterceptor实现自己的日志拦截记录,或者其他业务逻辑。@Component@Slf4jpublicclassCustomFeignInterceptorextendsDefaultLogInterceptor{@Overridepublicvoidrequest(Stringtarget,Stringurl,Stringbody){super.request(target,url,body);log.info("请求");}@Overridepublicvoidexception(Stringtarget,Stringurl,FeignExceptionfeignException){super.exception(target,url,feignException);}@Overridepublicvoidresponse(Stringtarget,Stringurl,Objectresponse){super.response(target,url,response);日志信息(“响应”);}}监控metricfeign-plus会自己记录各个接口之间的调用耗时和异常。访问http://127.0.0.1:8181/actuator/prometheus会看到相关的埋点信息。可以通过feign_call*键自行在Grafana中配置相关面板,类似下图:应该使用exceptiontransferrpc(remotecall)真的和本地调用类似,异常传递必不可少。//providerpublicOrderquery(OrderQueryDTOdto){log.info("dto={}",dto);if(dto.getId().equals("1")){thrownewDemoException("providertestexception");}返回新订单(dto.getId());}//消费者尝试{demoApi.query(newOrderQueryDTO(id,"zhangsan"));}catch(DemoExceptione){log.error("feignCall:{},sourceApp:[{}],sourceStackTrace:{}",e.getMessage(),e.getAppName(),e.getDebugStackTrace(),e);例如,如果在提供者中抛出自定义异常,在消费者中可以通过try/catch捕获该异常。为了在feign-plus中实现这个功能,需要几个步骤:自定义一个普通的异常。服务提供者需要实现一个全局的拦截器来统一异常发生时对外的响应数据。服务消费者需要定制一个异常解码器bean。这里我在provider中自定义了一个DemoException:通常这个类应该定义在公司内部的通用包中,这里为了演示方便。然后定义一个HttpStatus类,统一对外响应。@Data@AllArgsConstructor@NoArgsConstructorpublicclassHttpStatus{privateStringappName;私有整数代码;私人字符串消息;privateStringdebugStackTrace;}这个也要放在common包里。然后在provider中定义全局异常处理:当发生异常时,会返回一个http_code=500的数据:此时又会有一个争论的话题:HTTP接口是否返回全200再通过代码判断,还是参考到http_code返回?我不会在这里讨论太多。具体可以参考老鼠大叔的文章:《一梭子:所有RESTAPI都使用POST》feign-plus默认使用http_code!=200来考虑异常。这里的http_status也参考了Google的api设计:具体可以参考这个链接:https://cloud.google.com/apis/design/errors#propagating_errors。然后定义一个异常解析器:@ConfigurationpublicclassFeignExceptionConfig{@BeanpublicFeignErrorDecoderfeignExceptionDecoder(){return(methodName,response,e)->{HttpStatusstatus=JSONUtil.toBean(response,HttpStatus.class);返回新的DemoException(stat.getAppName(),status.getCode(),status.getMessage(),status.getDebugStackTrace());};}}通常这段代码也放在基础包中。这样,当服务提供者抛出异常时,消费者就可以成功获取异常:实现原理实现原理其实比较简单。了解rpc原理的应该知道,服务提供者返回的异常调用者是接收不到的。是的,它是否由语言实现并不重要。毕竟两个进程之间的栈是完全不同的,不在同一个服务器上,甚至不在同一个区域。因此,提供者抛出异常后,消费者只能得到一系列的消息。我们只能根据这条消息解析出异常信息,然后重新创建一个内部自定义异常(比如这里的DemoException)。这就是我们的自定义异常解析器所做的。下图是这个异常传递的大致流程:在codemessage模式下,feign-plus默认使用http_code!=200抛异常,所以响应http_code=200形式的数据,codemessage不会传一个例外。仍然是一个正常的任务。但是也可以基于这种模式来传递异常,但是不能统一。比如有的团队习惯编码!=0表示异常,甚至字段都不是编码;或者在message或msg字段中放置一些异常信息。每个团队和个人习惯不同,无法抽象出一个标准,所以也没有做相关的适配。这也证明了使用国际标准的好处。限于篇幅,有相关需求的朋友也可以在评论区交流,实现起来会比现在复杂一点。总结项目源码:https://github.com/crossoverJie/feign-plus。基于2022年云原生的背景,当然还是推荐大家使用gRPC进行服务间通信,这样就不用去维护这样一个库了。但是当调用一些第三方接口,而对方没有提供SDK的时候,这个库也是有用的。虽然使用nativefeign也可以达到同样的目的,但是使用这个库可以让开发体验和Spring保持一致。同时内置日志、Metric等功能,避免重复开发。