1.Feign

1.1Feign简介

Feign是一个声明性的Web服务客户端。它使编写Web服务客户端变得更容易。feigin是一种模板化,声明式的http客户端,feign可以通过注解绑定到接口上来简化Http请求访问。与当我们访问别的服务端口的时候 大部分使用httpclient等请求进行调用不同,在eureka注册的服务,我们可以使用Feign 声明接口的形式来进行相关服务的调用,并提供了失败回退(其实是Hystrix组件的使用)。Feign只是一个便利的rest框架,简化调用,最后还是通过ribbon在注册服务器中找到服务实例,然后对请求进行分配。
Feign是简化Java HTTP客户端开发的工具(java-to-httpclient-binder),它的灵感来自于Retrofit、JAXRS-2.0和WebSocket。Feign的初衷是降低统一绑定Denominator到HTTP API的复杂度,不区分是否为restful。
Fegin的关键机制是使用了动态代理
1)、首先,对某个接口定义了@FeginClient注解,Fegin就会针对这个接口创建一个动态代理
2)、接着调用接口的时候,本质就是调用Fegin创建的动态代理
3)、Fegin的动态代理会根据在接口上的@RequestMapping等注解,来动态构造要请求的服务的地址
4)、针对这个地址,发起请求、解析响应Fegin是和Ribbon以及Eureka紧密协作的
1)、首先Ribbon会从Eureka Client里获取到对应的服务注册表,也就知道了所有的服务都部署在了哪些机器上,在监听哪些端口
2)、然后Ribbon就可以使用默认的Round Robin算法,从中选择一台机器
3)、Fegin就会针对这台机器,构造并发起请求

1.2Ribbon、Feign和OpenFeign的区别

1.2.1Ribbon

Ribbon 是 Netflix开源的基于HTTP和TCP等协议负载均衡组件
Ribbon 可以用来做客户端负载均衡,调用注册中心的服务
Ribbon的使用需要代码里手动调用目标服务,请参考官方示例:https://github.com/Netflix/ribbon

1.2.2Feign

Feign是Spring Cloud组件中的一个轻量级RESTfulHTTP服务客户端
Feign内置了Ribbon,用来做客户端负载均衡,去调用服务注册中心的服务。
从jar包来看,openFeign中不仅内置了ribbon还内置了Hystrix服务熔断,所以Feign、openFeign也是负载均衡的

图片.png
Feign的使用方式是:使用Feign的注解定义接口,调用这个接口,就可以调用服务注册中心的服务
Feign支持的注解和用法请参考官方文档:https://github.com/OpenFeign/feign
Feign本身不支持Spring MVC的注解,它有一套自己的注解

1.2.3OpenFeign

OpenFeign是Spring Cloud 在Feign的基础上支持了Spring MVC的注解,如@RequesMapping等等。
OpenFeign的@FeignClient可以解析SpringMVC的@RequestMapping注解下的接口,并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务。

1.2.4feign和openFeign比较

服务间的调用 - 图2

1.2.5ribbon和feign比较

Ribbon和Feign都是用于调用其他服务的,不过方式不同。
1.启动类使用的注解不同
Ribbon用的是@RibbonClient,Feign用的是@EnableFeignClients。

2.服务的指定位置不同
Ribbon是在@RibbonClient注解上声明,Feign则是在定义抽象方法的接口中使用@FeignClient声明。

3.调用方式不同
Ribbon需要自己构建http请求,模拟http请求(也就是封装request对象,包括请求头信息,请求参数等等)然后使用RestTemplate发送给其他服务,步骤相当繁琐。也就是上面提到的需要代码里手动调用目标服务
Feign则是在Ribbon的基础上进行了一次改进,采用接口的方式,将需要调用的其他服务的方法定义成抽象方法即可,不需要自己构建http请求。不过要注意的是抽象方法的注解、方法签名要和提供服务的方法完全一致。

1.3feign快速入门案例

(1)在tensquare_qa模块添加依赖

  1. <dependency>
  2. <groupId>org.springframework.cloud</groupId>
  3. <artifactId>spring‐cloud‐starter‐openfeign</artifactId>
  4. </dependency>

(2)修改tensquare_qa模块的启动类,添加注解

@EnableDiscoveryClient
@EnableFeignClients


package com.tensquare.qa;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import util.IdWorker;
import util.JwtUtil;

@SpringBootApplication
@EnableEurekaClient
@EnableDiscoveryClient
@EnableFeignClients
public class QaApplication {

    public static void main(String[] args) {
        SpringApplication.run(QaApplication.class, args);
    }

    @Bean
    public IdWorker idWorker() {
        return new IdWorker(1, 1);
    }

    @Bean
    public JwtUtil jwtUtil() {
        return new JwtUtil();
    }

}

(3)在tensquare_qa模块创建 com.tensquare.qa.client包,包下创建接口

package com.tensquare.qa.client;

import com.tensquare.qa.client.impl.LabelClientImpl;
import entity.Result;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

/**
 * @author: Luck-zb
 * description:微服务之间调用 -- 使用feign技术,调用base微服务
 * <p>
 * 测试 -- Hystrix熔断器测试,使用规则:feign中提供了熔断器技术,所以引入了opn feign的jar包后即可使用熔断器
 * 使用方法,在@FeignClient注解中,通过fallback属性,指定一个实现类,当服务异常的时候,就会执行fallback中
 * 指定的实现,可以自定义一些处理,比如返回提示信息等
 * <p>
 * Date:2021/2/1 - 9:18
 */
@Component
@FeignClient(value = "tensquare-base", fallback = LabelClientImpl.class)
public interface LabelClient {

    /**
     * 根据id查询
     *
     * @param labelId
     * @return
     */
    @RequestMapping(value = "/label/{labelId}", method = RequestMethod.GET)
    Result findById(@PathVariable("labelId") String labelId);
}

@FeignClient注解用于指定从哪个服务中调用功能 ,注意 里面的名称与被调用的服务名保持一致,并且不能包含下划线。
@RequestMapping注解用于对被调用的微服务进行地址映射。注意 @PathVariable注解一定要指定参数名称,否则出错

(5)修改tensquare_qa模块的 ProblemController

 @Autowired
    LabelClient labelClient;

    /**
     * 使用feign技术实现微服务之间的调用 -- 测试
     * <p>
     * 下面两个RequestMapping的路径在于测试,在这里的controller中RequestMapping中的value属性值,
     * 是没有特殊要求的,就是这里访问的还是ProblemController中的接口,所以这里路径是可以自己随便写的,
     * 但是要注意不能与其它方法冲突,比如路径参数,请求方式也一样,
     * <p>
     * 真正通过feign调用其它服务,是通过xxxClient中的方法来调用的,对xxxClient方法中的路径才有要求,
     * 必须写接口的全路径,参数等也必须保持一致,而且@PathVariable注解中必须指定属性映射参数
     * <p>
     * 当使用zuul网关后,请求头信息会丢失,为了解决这个问题,使用了一个Zuul网关过滤器来实现,
     * 也用该方法来进行测试
     *
     * @param labelId
     * @return
     */
//    @RequestMapping(value = "/label/{labelId}",method = RequestMethod.GET)
    @RequestMapping(value = "/test/{labelId}", method = RequestMethod.GET)
    public Result testFeign(@PathVariable String labelId) {
        String authorization = request.getHeader("Authorization");
        if (logger.isInfoEnabled()) {
            logger.info("receive header :{}", authorization);
        }
        Result result = labelClient.findById(labelId);
        return result;
    }

(6)测试:http://localhost:9003/problem/test/1 能看到标签的信息

1.4负载均衡测试

测试:同时开启多个基础微服务,看是否是轮流调用。
修改tensquare_base工程LabelController的findById方法

@RequestMapping(value="/{id}", method = RequestMethod.GET)
    public Result findById(@PathVariable String id){
        System.out.println("No.1");
        return new Result(true,StatusCode.OK,"查询成
功",labelService.findById(id)  );
    }

启动基础微服务后,修改端口和输出信息,再次启动基础微服务启动问答微服务,浏览器执行http://localhost:9003/problem/test/1 看是否轮流调用。结果,使用feign的服务调用是负载均衡的