📢 大家好,我是 【战神刘玉栋】,有10多年的研发经验,致力于前后端技术栈的知识沉淀和传播。 💗
🌻 近期刚转战 CSDN,会严格把控文章质量,绝不滥竽充数,如需交流,欢迎留言评论。👍
文章目录
- 写在前面的话
- Feign 基础入门
- Feign 超时配置
- Feign 常见问题
- Feign 知识拓展
- 总结陈词
写在前面的话
前不久博主整理了 《企业实战分享 · MyBatis 使用合集》,粉丝私信反响不错,今天继续整理它的姐妹篇。
博主所在公司的技术栈为:后端 SpringCloud + 前端 Vue/Nuxt,在后端开发中,内部服务间的调用采用了Feign
来完成,这个和MyBatis
一样属于企业开发的高频用法,因此也做一下知识汇总,希望与君共勉。
Feign 基础入门
技术简介
Feign 是 Spring Cloud 提供的声明式、模板化的 HTTP 客户端, 它使得调用远程服务就像调用本地服务一样简单,只需要创建一个接口并添加一个注解即可。
Spring Cloud 集成 Feign 并对其进行了增强,使 Feign 支持了 Spring MVC 注解;Feign 默认集成了 Ribbon,所以Fegin默认就实现了负载均衡的效果。
博主所在公司的新框架对内部远程调用做了一次升级,采用 OpenFeign 调用方式替代旧框架内部服务之间的 Dubbo 和 HttpUtils 调用方式。
OpenFeign 是一个声明式、模板化的 HTTP 客户端,可以帮助开发者更快捷、优雅地调用HTTP API。
使用前提
Tips:简单介绍使用Feign的一些前置步骤,这些其实都是框架搭建人员负责的,具体开发一般关心后续实质用法。
Step1、引入 Maven 依赖
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
Step2、在启动类添加@EnableFeignClients,或者为Feign接口添加@Component等注解,总之,需要被Spring框架识别为一个组件,以便Spring可以管理它。
Step3、在服务提供者定义一下 Controller,
Step4、在服务消费者定义Feign 客户端,并按需使用
基础用法
以门户调用医嘱的接口为例,实现 Feign 调用有两种方式:
1、服务消费方门户,自己开发对应的 Feign 接口,指向对方服务;
2、服务提供方医嘱, 在 api 模块书写对应的 Feign 接口,完成后发布私服,提供服务消费方门户调用;
Tips:两种方式各有优缺点,方案1可以减少不需要的接口和实体影响,改造期间减少对方服务影响;方案2可以减少重复编码,增加复用。原则上推荐在消费端书写 Feign 接口。
示例 - 服务提供方
@PostMapping(value="/extHandle")
ApiResult<String> extHandle(@RequestBody V2ShiftRecordPrintRo v2ShiftRecordPrintRo) throws Exception {//省去具体业务逻辑
}
示例 - Feign 接口
@FeignClient(value = FeignConstant.Service.PORTAL, url = FeignConstant.Url.PORTAL, path = "/inner/ext")
public interface PortalClient {@PostMapping(value="/extHandle")ApiResult<String> extHandle(@RequestBody V2ShiftRecordPrintRo v2ShiftRecordPrintRo);
}
示例 - 服务调用方
@Autowired
private PortalClient portalClient;@PostMapping(value = "/feignTest")
public ResultVO<Map<String, Object>> feignTest(@RequestBody V2ShiftRecordPrintRo param) throws Exception {ApiResult<String> stringApiResult = portalClient.extHandle(param);return ResultVO.success("feign测试接口成功", param);
}
Feign 超时配置
Feign 客户端是默认开启支持 Ribbon 的,两个超时属性就是连接超时 ConnectTimeout 和读超时 ReadTimeout,在默认情况下,也就是没有任何配置下,Feign 的超时时间会被 Ribbon 覆盖,两个超时时间都是1秒。
ConnectTimeout 是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间。
ReadTimeout 是建立连接后从服务端读取到可用资源的时间,它通常要考虑到服务端接口的耗时。
方案一:Ribbon全局配置
在调用方的yml配置文件中添加配置,设置超时时间为5s:
ribbon:#建立连接超时时间ConnectTimeout: 5000#建立连接之后,读取响应资源超时时间ReadTimeout: 5000
方案二:Feign配置
注: Feign配置会优先于Ribbon配置。配置完之后也是同样的效果。
feign:client:config:#这里填具体的服务名称(也可以填default,表示对所有服务生效)app-order:#connectTimeout和readTimeout这两个得一起配置才会生效connectTimeout: 5000readTimeout: 5000
目前公司框架,就网关有特别配置,各自项目自己配置,因为每个项目场景不太一样,这个让每个项目主管根据自己需要配置吧,架构部一般推荐接口访问要1秒以内,当然这是理想状态。
方案三:Feign配置具体接口
框架内部实现的自定义注解,允许通过添加此注解,按接口级别设置超时时间。
通过在对应的方法上添加 @RequestOption 注解来实现自定义超时配置(单位:秒),示例如下:
Tips:具体实现后续框架封装专栏再分享,这里先提供一个思路。
@FeignClient(contextId = "PatientClient", value = "demo-service", path = "/inner/patient")
public interface PatientClient {@GetMapping("/getById")@RequestOption(callTimeoutSeconds = 3)ApiResult<Patient> getById(@RequestParam String patientId);
}
Feign 常见问题
Feign POST not supported
Feign 调用的时候出现 Request method POST not supported的解决方案
背景说明:门户通过 Feign-Get 调用某个EMR接口,对方也是Get接口,但是却报错上面提示。
@GetMapping("/getAffiliateAccountByPrimaryAccount")
ApiResult<List<Map<String, Object>>> getAffiliateAccountByPrimaryAccount(@FeignBody("staffNo") String staffNo);
分析解决:排查发现是大概率注解使用不对,将@FeignBody修改为@RequestParam解决。
扩展说明:使用Feign来调用Get请求时,如果方法的参数是一个对象,则会被强行转变成Post请求,然后抛出服务被拒绝的错误,解决办法使用 @SpringQueryMap 注解。
@GetMapping("/search/page")
Page<User> pageSearchUser(@SpringQueryMap Page<User> page, @RequestParam String key);
关于Feign的入参有什么讲究
Feign接口通常有一个或者多个入参。
若接口有多个入参,应该将入参封装成实体参数,对字段有非空要求的,在实体内使用@NotNull、@NotEmpty等等注解,在接口中使用 @RequestBody 修饰实体。对于请求头的入参,直接添加在实体入参时候,用@RequestHeader修饰入参,无需放在实体内。除此之外,@RequestBody只支持修饰一个实体参数。
若接口只有一个入参,正常传参即可。
@FeignClient(contextId = "ManageAuthClient",value = OnelinkFeignConstant.Service.PORTAL, url = OnelinkFeignConstant.Url.PORTAL, path = "/inner/manage/auth")
public interface ManageAuthClient {@PostMapping("/addRoleAuthDefault")ApiResult<Void> addRoleAuthDefault(@RequestBody @Valid RoleAuthModel roleAuthModel,@RequestHeader("operator") String operator) throws Exception;
}public class RoleAuthModel {@NotNull(message = "roleCode不能为空")//角色代码private String roleCode;@NotNull(message = "appCode不能为空")//系统代码private String appCode;//组件列表private List<Map<String, Object>> portletList;
}
如何打印Feign请求详细调试日志
在application.yml中配置日志级别与客户端包路径
feign:client:config:default: # 日志级别为 full(该日志配置不能提交到线上环境)loggerLevel: full
logging:level:# 设置具体FeignClient的日志级别com.zoe.onelink.product.api.TrainProductClient: debug
还有哪些常用配置:
feign:client:config:# 全局默认配置(若对以下配置不熟悉请勿随意配置)default:# 连接超时时间connect-timeout: 2000# 读取超时时间read-timeout: 3000# 自定义编码器encoder: feign.jackson.JacksonEncoderribbon:# 对当前节点的最大重试次数,不包括首次调用,默认值为0。MaxAutoRetries: 0# 下个节点数最大重试次数,不包括首个节点,默认值为1。不重试该值需要设置为-1 (0的话也不重试,但是会触发一次服务选举)MaxAutoRetriesNextServer: -1# 是否对所有请求进行失败重试, 设置为 false, 让feign只针对Get请求进行重试.OkToRetryOnAllOperations: false# true: 无论是接口请求超时、服务端处理失败、建立连接失败等,统一返回true,即可以重试okToRetryOnAllErrors: false# true: 只要是在跟服务端建立连接时出现错误,无论建立连接超时、建立连接失败等,统一返回trueokToRetryOnConnectErrors: false
Feign 知识拓展
原理说明
1、将 Feign 接口的代理类扫描到Spring容器中:@EnableFeignClients 注解开启扫描,FeignClientsRegistrar 可以扫描被 @FeignClient 标识的接口,进而生成代理类,并把接口和代理类交给Spring的容器管理;
2、为接口的方法创建 RequestTemplate:当消费者调用 Feign 代理类时,代理类会通过调用SynchronousMethodHandler.invoke()
创建 RequestTemplate 对象,进而代理类会通过 RequestTemplate 创建 Request,最后选择合适的请求客户端发出请求;
框架封装
博主所在公司框架对 Spring Cloud OpenFeign 做了二次封装,包含但不限于如下功能:
增加远程调用内部服务自动鉴权机制。
对远程调用返回结果进行了统一的格式封装。
对远程调用异常进行了统一的处理。
支持蓝绿、灰度流量、标签路由等功能。
注册中心实例上下线实时感知。
增强请求重试机制。
Tips:后续整理框架封装专栏再展开。
Feign拦截器
和其他技术一样,Feign 也可以通过自定义拦截器实现一些附加逻辑操作,这边是入门篇,不展开介绍,后续和上面其他框架内容一起展开。
关键词:RequestInterceptor
总结陈词
上文分享若干企业实际开发中,Feign
的日常使用场景及应对方案,希望对大家有帮助。
💗 后续会逐步分享企业实际开发中的实战经验,有需要交流的可以联系博主。