上次我们深入讲解了Ribbon 的架构原理,这次我们再来看下 Feign 远程调用的架构原理。
一、理解远程调用
远程调用怎么理解呢?
远程调用
和 本地调用
是相对的,那我们先说本地调用更好理解些,本地调用就是同一个 Service 里面的方法 A 调用方法 B。
那远程调用就是不同 Service 之间的方法调用。Service 级的方法调用,就是我们自己构造请求 URL和请求参数,就可以发起远程调用了。
在服务之间调用的话,我们都是基于 HTTP 协议,一般用到的远程服务框架有 OKHttp3,Netty, HttpURLConnection 等。其调用流程如下:
但是这种虚线方框中的构造请求的过程是很 繁琐 的,有没有更 简便 的方式呢?
Feign 就是来简化我们发起远程调用的代码的,那简化到什么程度呢? 简化成就像调用本地方法那样简单。
比如我的开源项目 PassJava 中的使用 Feign 执行远程调用的代码:
//远程调用拿到该用户的学习时长
R memberStudyTimeList = studyTimeFeignService.getMemberStudyTimeListTest(id);
而 Feign 又是 Spring Cloud 微服务技术栈中非常重要的一个组件,如果让你来设计这个微服务组件,你会怎么来设计呢?
我们需要考虑这几个因素 :
-
如何使远程调用像本地方法调用简单?
-
Feign 如何找到远程服务的地址的?
-
Feign 是如何进行负载均衡的?
接下来我们围绕这些核心问题来一起看下 Feign 的设计原理。
二、Feign 和 OpenFeign
OpenFeign 组件的前身是 Netflix Feign 项目,它最早是作为 Netflix OSS 项目的一部分,由 Netflix 公司开发。后来 Feign 项目被贡献给了开源组织,于是才有了我们今天使用的 Spring Cloud OpenFeign 组件。
Feign 和 OpenFeign 有很多大同小异之处,不同的是 OpenFeign 支持 MVC 注解。
可以认为 OpenFeign 为 Feign 的增强版 。
简单总结下 OpenFeign 能用来做什么:
-
OpenFeign 是声明式的 HTTP 客户端,让远程调用更简单。
-
提供了HTTP请求的模板,编写简单的接口和插入注解,就可以定义好HTTP请求的参数、格式、地址等信息
-
整合了Ribbon(负载均衡组件)和 Hystix(服务熔断组件),不需要显示使用这两个组件
-
Spring Cloud Feign 在 Netflix Feign的基础上扩展了对SpringMVC注解的支持
三、OpenFeign 如何用?
OpenFeign 的使用也很简单,这里还是用我的开源 SpringCloud 项目 PassJava 作为示例。
开源地址: https://github.com/Jackson0714/PassJava-Platform
喜欢的小伙伴来点个 Star 吧,冲 2K Star。
Member 服务远程调用 Study 服务的方法 memberStudyTime(),如下图所示。
第一步 :Member 服务需要定义一个 OpenFeign 接口:
@FeignClient("passjava-study")
public interface StudyTimeFeignService {
@RequestMapping("study/studytime/member/list/test/{id}")
public R getMemberStudyTimeListTest(@PathVariable("id") Long id);
}
我们可以看到这个 interface 上添加了注解 @FeignClient
,而且括号里面指定了服务名:passjava-study。 显示声明 这个接口用来远程调用 passjava-study
服务。
第二步 :Member 启动类上添加 @EnableFeignClients
注解开启远程调用服务,且需要开启服务发现。如下所示:
@EnableFeignClients(basePackages = "com.jackson0714.passjava.member.feign")
@EnableDiscoveryClient
第三步 :Study 服务定义一个方法,其方法路径和 Member 服务中的接口 URL 地址一致即可。
URL 地址:"study/studytime/member/list/test/{id}"
@RestController
@RequestMapping("study/studytime")
public class StudyTimeController {
@RequestMapping("/member/list/test/{id}")
public R memberStudyTimeTest(@PathVariable("id