揭秘 Feign 调用机制:微服务通信的无缝集成
在微服务架构中,服务之间的通信是核心中的核心。之前的文章Spring Boot 项目高效 HTTP 通信:常用客户端大比拼!介绍了基于SpringBoot的项目开发中常用的Http客户端,并分析了RestTemplate和Okhttp3的关于源码,详见深入 Spring RestTemplate 源码:掌握 HTTP 通信核心技术和揭开 OkHttp3 高效处理 HTTP 请求的神秘面纱:池技术与拦截器。
今天我们将在此基础上,继续探究Feign的调用机制。了解它是如何在无需指定域名的情况下,找到并调用微服务的。
一、什么是Feign
Feign 是一个声明式的 Web 服务客户端。它通过注解的方式,将接口方法映射为 HTTP 请求,极大简化了微服务之间的通信。Feign 的设计灵感来源于 Retrofit、JAX-RS 和 WebSocket,在 Spring Cloud 生态中扮演着重要角色,广泛应用于微服务间通信。
二、Feign快速上手
1. 导入maven依赖
首先,在项目的 pom.xml 中添加以下依赖,以便使用 Spring Cloud OpenFeign。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2. 定义Feign的client
@FeignClient(value = "customer-api")
public interface CustomerFeign {
@GetMapping(value = "customer/get-customer-info")
Response<EmployeeVo> getCustomerInfo(@RequestParam("customerId") String customerId);
}
通过 @FeignClient 注解,声明了一个名为 CustomerFeign 的接口,该接口将作为与customer-api微服务通信的客户端。
value 属性指定了目标微服务的名称,Feign客户端会根据这个名称在服务注册与发现组件(如Eureka)中查找并调用相应的服务。
关于Eureka的使用及原理见Eureka系列。
3. 启动Feign客户端
在 Spring Boot 应用的启动类中,添加 @EnableFeignClients 注解以启用 Feign 客户端功能。
@Slf4j
@EnableFeignClients
@EnableAspectJAutoProxy(exposeProxy = true)
@SpringCloudApplication
@EnableApolloConfig
public class Main {
……
}
(关于这个注解以及@FeignClient的实现细节,后面有时间了会出一篇博客专门来讲)。
三、Feign为什么不需要域名?
Feign 的强大之处在于它不需要具体的域名即可找到服务。其背后依赖于 Ribbon 和 Eureka 的集成。
Feign 通过与 Ribbon 和 Eureka 的无缝集成,实现了动态服务发现和负载均衡。核心方法如下:
- cleanUrl(String originalUrl, String host)方法
static URI cleanUrl(String originalUrl, String host) {
String newUrl = originalUrl;
// 规范化 URL 并去掉主机信息
if (originalUrl.startsWith("https://")) {
newUrl = originalUrl.substring(0, 8)
+ originalUrl.substring(8 + host.length());
}
else if (originalUrl.startsWith("http")) {
newUrl = originalUrl.substring(0, 7)
+ originalUrl.substring(7 + host.length());
}
StringBuffer buffer = new StringBuffer(newUrl);
if ((newUrl.startsWith("https://") && newUrl.length() == 8)
|| (newUrl.startsWith("http://") && newUrl.length() == 7)) {
buffer.append("/");
}
return URI.create(buffer.toString());
}
cleanUrl 方法负责去掉 URL 中的域名部分,以服务名称代替。例如 http://customer-api或者https://customer-api。
2. execute(Request request, Request.Options options)方法
public Response execute(Request request, Request.Options options) throws IOException {
try {
// 构建请求
URI asUri = URI.create(request.url());
String clientName = asUri.getHost();
// 清除域名
URI uriWithoutHost = cleanUrl(request.url(), clientName);
FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(
this.delegate, request, uriWithoutHost);
// 选择合适的实例
IClientConfig requestConfig = getClientConfig(options, clientName);
// 发送请求
return lbClient(clientName)
.executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse();
}
catch (ClientException e) {
IOException io = findIOException(e);
if (io != null) {
throw io;
}
throw new RuntimeException(e);
}
}
在 execute 方法中,Feign 通过服务名称找到对应的服务实例,并利用 Ribbon 的负载均衡策略进行调用。
private FeignLoadBalancer lbClient(String clientName) {
return this.lbClientFactory.create(clientName);
}
服务实例列表的获取,负载均衡策略的选择,表面看是封装在了executeWithLoadBalancer方法中,实则隐含在 Ribbon 的底层处理逻辑中(下节探讨)。
四、总结
Feign 在 Spring Cloud 生态中,提供了高效、优雅的 HTTP 请求方式,其通过与 Ribbon 的深度集成,实现了灵活的服务调用,无需域名即可动态寻找并访问目标服务,大大简化了微服务环境中的服务间通信配置。
下一篇文章,我们将深入探讨Ribbon是如何获取服务实例列表并执行负载均衡相关策略选择具体服务实例的。敬请期待!