feign-core 高级用法

1,497 阅读3分钟

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 即可,至于代理对象的生成不是本文关注的重点;

  1. 跟踪debug ,找到ribbon 相关类的调用;

debug 后,得到如下时序图:

2.3 汇总结论😎

汇总结论前,我们先看一下 spring-boot 与 ribbon 整合时的时序图(RestTemplate形式下);


  1. 两者对比发现,ribbon 与spring-boot 、feign-core 的切入点都在 Client 上;

  2. Client 是 客户端发起请求的的接口,ribbon 封装的也正是负载均衡 + HTTP调用;

  3. 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结果:

WechatIMG43.png

由此看出,feign-core 调用方式默认不携带header 中的值;

3.2 改造🔧

作为一款🐂🍺的工具,feign-core 也提供了服务增强的方式;即实现RequestInterceptor 接口;

改造前 我们先模拟一个需求:

  1. feign-core 请求保留用户的请求IP;

  2. 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 配置类,建议封装到公共基础包中,方便全局统一处理;

参见📚

feign-core 基本使用

feign-core 与spring-boot 整合

负载均衡-ribbon