1. 介绍😄
本文主要介绍feign-core 的一些附加属性,关于feign-core怎么玩、原理、如何与spring-boot 整合参见前两篇文章;
本文关注点:
1) feign-core 如何实现在多个服务间的选择(即负载均衡策略如何实现)?
2)服务之间采用 方法级别的调用,如何优雅传递http请求中 header 信息呢 ?
2. 负载均衡
2.1 从何开始
当我点开feign-core 源码的时候,不经意间看到一个ribbon 的目录,😄 猜测这玩意一定是自己就继承好了ribbon,使用maven查看依赖,果然如此,自个就给整好了;
feign-core ribbon 继承目录
feign-core maven 依赖ribbon
2.2 debug走起
这步骤需要项目支持,可以参考 github-源码 ,依次启动 eurake、provider、consumner ;
debug 前预想一下,我们的目的是啥?
1)找出执行远程调用的地方;
根据前两篇文章:feign-core 基本使用、feign-core 与spring-boot 整合,我们得到 SynchronousMethodHandler.invoke() 是最终执行远程调用的地方;直接从此处debug 即可,至于代理对象的生成不是本文关注的重点;
- 跟踪debug ,找到ribbon 相关类的调用;
debug 后,得到如下时序图:
2.3 汇总结论😎
汇总结论前,我们先看一下 spring-boot 与 ribbon 整合时的时序图(RestTemplate形式下);
-
两者对比发现,ribbon 与spring-boot 、feign-core 的切入点都在 Client 上;
-
Client 是 客户端发起请求的的接口,ribbon 封装的也正是负载均衡 + HTTP调用;
-
ribbon 核心功能就是就是将 请求对象Request 中的 ${application-server-name] 替换成 实际的主机地址,在替换过程中 应用负载均衡策略😄;
3. 服务拦截🚀
3.1 现状
springmvc 我们可以使用 Filter、HandlerInterceptor 进行拦截 增强请求,fein-core 的http调用 类似于 dubbo 的 RMI 请求,那么如何实现请求的增强呢?
首先, 启动项目(参见2.2中的项目)我们看一下 正常情况下,feign-core 的请求后,header 值是否会携带?
访问路径 curl:
curl --location --request GET 'localhost:18084/eat/apple' \
--header 'user-token: 12138' \
--header 'yiyi-podenv: edu-12138'
debug provider结果:
由此看出,feign-core 调用方式默认不携带header 中的值;
3.2 改造🔧
作为一款🐂🍺的工具,feign-core 也提供了服务增强的方式;即实现RequestInterceptor 接口;
改造前 我们先模拟一个需求:
-
feign-core 请求保留用户的请求IP;
-
feign-core 请求携带 自定义头 的header (暂定前缀:yiyi)
在consumer 端新增如下配置类
HttpHeaderConstant
/**
* http header 请求常量
*/
public interface HttpHeaderConstant {
String X_FORWARDED_FOR = "x-forwarded-for";
String ENV_PREFIX = "yiyi";
}
FeignHeaderIntercept
@Configuration
public class FeignHeaderIntercept implements RequestInterceptor {
private Logger logger = LoggerFactory.getLogger(FeignHeaderIntercept.class);
@Override
public void apply(RequestTemplate template) {
ServletRequestAttributes requestAttributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
if(Objects.isNull(requestAttributes)){
return;
}
HttpServletRequest request = requestAttributes.getRequest();
Enumeration<String> headerNames = request.getHeaderNames();
if(Objects.isNull(headerNames)){
return ;
}
boolean xForwardIpExist = false;
while (headerNames.hasMoreElements()){
String name = headerNames.nextElement();
String values = request.getHeader(name);
// 获取用户真实IP
if (name.equalsIgnoreCase(HttpHeaderConstant.X_FORWARDED_FOR)) {
if(IpUtils.isLocalAddress(values)){
values = IpUtils.getIpAddr(request);
}
template.header(HttpHeaderConstant.X_FORWARDED_FOR,values);
xForwardIpExist = true;
}
// 自定义http header 获取
if(name.startsWith(HttpHeaderConstant.ENV_PREFIX)){
values = request.getHeader(name);
logger.info("http 拦截 header : name = {},values = {}",name,values);
template.header(name,values);
}
}
if(!xForwardIpExist){
String remoteAddr = IpUtils.getIpAddr(request);
logger.info("http x-forwarded-for 增加remoteAddr header : {}",remoteAddr);
template.header(HttpHeaderConstant.X_FORWARDED_FOR,remoteAddr);
}
}
}
debug查看结果
ok ,达到效果
3.3 注意
FeignHeaderIntercept 配置类,建议封装到公共基础包中,方便全局统一处理;