实用篇-Feign客户端

Feign是一个http客户端,用来替代我们前面在 '实用篇-Eureka注册中心' 学的RestTemplate

一、基于Feign的远程调用

前提: 去掉前面环境隔离的代码,还有建议别使用集群搭建,尽可能避免因为其它之前代码的原因无法进行下面的实验

先来看RestTemplate发起远程调用的示例代码

        //2.利用RestTemplate发起http请求,查询用户
        //2.1 url路径
        String url = "http://UserService:8081/user/" + order.getUserId();
        //这个方法第一个参数是访问路径,第二个参数是把响应得到的Json数据封装成实体类对象
        User user = restTemplate.getForObject(url, User.class);

RestTemplate方式调用存在的问题:

  • 代码可读性差,编程体验不统一
  • URL参数复杂,造成难以维护

Feign的介绍

Feign是一个声明式的http客户端,作用是帮助我们优雅的实现http请求的发送,解决RestTemplate复杂URL的问题。官网如下

https://github.com/OpenFeign/feign

如何定义和使用Feign,具体操作如下:
第一步: 在order-service微服务项目的pom.xml添加如下



    org.springframework.cloud
    spring-cloud-starter-openfeign

第二步: 在order-service微服务项目的OrderApplication启动类添加如下

@EnableFeignClients

第三步: 在order-service微服务项目的src/main/java/cn/itcast/order目录新建clients.UserClient接口写入如下

package cn.itcast.order.clients;

import cn.itcast.order.pojo.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

@FeignClient("UserService")
public interface UserClient {

     @GetMapping("/user/{id}")
     User findById(@PathVariable("id") Long id);

}

第四步: 把order-service微服务项目的service目录的OrderService类,修改为如下
 

package cn.itcast.order.service;

import cn.itcast.order.clients.UserClient;
import cn.itcast.order.mapper.OrderMapper;
import cn.itcast.order.pojo.Order;
import cn.itcast.order.pojo.User;
import org.checkerframework.checker.units.qual.A;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.sql.PreparedStatement;

@Service
public class OrderService {

    @Autowired
    private OrderMapper orderMapper;

    @Autowired
    private UserClient userClient;

    public Order queryOrderById(Long orderId){
        //查询订单
        Order order = orderMapper.findById(orderId);
        //用Feign远程调用
        User user = userClient.findById(order.getUserId());
        //封装
        order.setUser(user);
        //返回
        return order;
    }

//    @Autowired
//    private RestTemplate restTemplate;
//
//    public Order queryOrderById(Long orderId) {
//        // 1.查询订单
//        Order order = orderMapper.findById(orderId);
//        //2.利用RestTemplate发起http请求,查询用户
//        //2.1 url路径
//        String url = "http://UserService:8081/user/" + order.getUserId();
//        //这个方法第一个参数是访问路径,第二个参数是把响应得到的Json数据封装成实体类对象
//        User user = restTemplate.getForObject(url, User.class);
//        //3.封装user到Order
//        order.setUser(user);
//        // 4.返回
//        return order;
//    }
}

第五步: 测试。浏览器访问http://localhost:8081/order/102,看是否能请求到UserService服务的数据 

注意一定要注释掉前面配置的命名空间,我这里是把上一节创建的namespace给删除了,但是order-service的配置文件中并没有修改配置,这里我们需要把这里注释掉实用篇-Feign客户端_第1张图片

访问到了 

实用篇-Feign客户端_第2张图片并且我们进行多次访问,可以看到每个实例被调用的次数大致一样

这是因为Feign已经帮我们实现好了负载均衡

二、自定义Feign的配置

Feign运行自定义配置来覆盖默认配置,可以修改的配置如下表

类型

作用

说明

feign.Logger.Level

修改日志级别

包含四种不同的级别: NONE、BASIC、HEADERS、FULL

feign.codec.Decoder

响应结果的解析器

http远程调用的结果做解析,例如把json字符串转为java对象

feign.code.Encoder

请求参数编码

将请求参数编码,便于通过http请求发送

feign.Contract

支持的注解格式

默认是SpringMVC的注解

feign.Retryer

失败重试机制

请求失败的重试机制,默认是没有,不过会使用Ribbon的重试

一般我们需要配置的就是日志级别。日志的四种日志级别的作用如下

  • NONE: 没有任何日志,默认就是NONE
  • BASIC: 当发送一次http请求时,会记录该请求是什么时候发的、什么时候结束的、耗时多久等基本信息
  • HEADERS: 除了记录基本信息,还会记录请求头、响应头信息
  • FULL: 除了记录上面的所有信息,还会记录请求体、响应体信息

修改日志级别有两种方式

第一种方式:修改配置文件

第一步:在order-service微服务项目的application.yml添加如下

# 第一种自定义feign配置的方式
feign:
  client:
    config: 
      # default表示OrderService微服务向任意微服务发送请求时,都会以这种生效feign配置
      # 除了default还可以写具体的微服务名,表示当OrderService微服务向特定微服务发送请求时,这种feign配置才会生效
      default:
        # loggerLevel是固定写法,不能改其它名字
        loggerLevel: FULL

第二步: 重启order-service微服务,浏览器访问 http://localhost/order/105,打开控制台,看日志

实用篇-Feign客户端_第3张图片

第二种方式: java代码方式

为避免第一种方式的影响,我们需要先把第一种配置文件的相关代码注释掉

第一步: 在order-service微服务项目的src/main/java/cn/itcast/order目录新建config.DefaultFeignConfiguration类,写入如下

package cn.itcast.order.config;
import feign.Logger;
import org.springframework.context.annotation.Bean;


public class DefaultFeignConfiguration {

     @Bean
     public Logger.Level logLevel(){
          //日志级别不演示FULL,看一下BASIC级别的日志信息是什么样的
          return Logger.Level.BASIC;
     }

}

第二步: 分两种,一种是局部。另一种是全局

局部: 当OrderService微服务向特定微服务发送请求时,这种feign配置才会生效。
那么第二步就应该是在order-service微服务项目的UserClient接口的@FeignClient注解里面添加如下。只需要修改@FeignClient注解即可

 

package cn.itcast.order.clients;

import cn.itcast.order.config.DefaultFeignConfiguration;
import cn.itcast.order.pojo.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

/**
 * @author 35238
 * @date 2023/4/26 0026 17:19
 */
//这个接口就是用来发请求的客户端
@FeignClient(value ="UserService",configuration = DefaultFeignConfiguration.class)//括号里面写服务名称,要向哪个服务发送请求
public interface UserClient {

    @GetMapping("/user/{id}")
    User xxfindById(@PathVariable("id") Long id);

}

全局: 不管OrderService微服务向哪个微服务发送请求时,feign配置都会生效。
那么第二步就应该是在order-service微服务项目的OrderApplication启动类添加如下。只需要修改@EnableFeignClients注解即可

package cn.itcast.order;

import cn.itcast.order.config.DefaultFeignConfiguration;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

@MapperScan("cn.itcast.order.mapper")
@SpringBootApplication
@EnableFeignClients(defaultConfiguration = DefaultFeignConfiguration.class)
public class OrderApplication {

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

//    /**
//     * 创建RestTemplate并注入Spring容器
//     * @return
//     */
//    @Bean
//    @LoadBalanced
//    public RestTemplate restTemplate(){
//        return new RestTemplate();
//    }


//    /**
//     * 配置负载均衡规则为随机
//     * 注入spring容器
//     * @return
//     */
//    @Bean
//    public IRule randomRule(){
//        return new RandomRule();
//    }
}

这里我们仅演示全局配置,重启order-service,访问http://localhost:8081/order/102

实用篇-Feign客户端_第4张图片

在正常使用的情况下,日志级别建议设置为BASIC。在调试错误的信息,日志级别建议设置为FULL

三、Feign的性能优化

Feign其实是有优化的空间,我们下面就学习如何把Feign进行性能优化。Feign是声明式的客户端,帮助我们把声明变成http请求,但是最终发送http请求时,还是会用到别的一些客户端,默认使用的是URLConnection这种客户端,URLConnection是jdk自带的

Feign的底层实现,或者说Feign的底层客户端实现:

  • URLConnection: 默认实现,不支持连接池,性能较差
  • Apache HttpClient: 支持连接池,建立连接池可以减少连接创建和销毁的性能损耗,避免连接每次三次握手、断开每次四次挥手
  • OKHttp: 支持连接池,建立连接池可以减少连接创建和销毁的性能损耗,避免连接每次三次握手、断开每次四次挥手

因此优化Feign的性能主要包括:

  • 使用连接池代替默认的URLConnection
  • 日志级别,最好使用none(默认)或basic,因为日志输出的越多,性能也会有损耗

例如我们把Feign底层客户端换成HttpClient。具体操作如下

第一步: 在order-service微服务项目的pom.xml,添加如下



    io.github.openfeign
    feign-httpclient

第二步: 在order-service微服务项目的application.yml,添加如下

# 更换feign客户端为httpClient
feign:
  httpclient:
    enabled: true #支持HttpClient的开关
    max-connections: 200 #最大连接数
    max-connections-per-route: 50 #请求路径的最大连接数

添加完后,application.yml文件如下 

server:
  port: 8080
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/cloud_order?useSSL=false
    username: root
    password:
    driver-class-name: com.mysql.jdbc.Driver
  application:
        # order的服务名称。也就是这个order-service注册到Eureka之后,这个order-service会叫什么名字
   name: OrderService
   # 因为nacos是spring的配置,所以要写在spring配置的下面
  cloud:
    nacos:
      server-addr: localhost:8848
      discovery:
        cluster-name: HZ
#        namespace: d8ec5ecb-2268-4551-ac2d-f08953292b28
#        ephemeral: false # 是否是临时实例 false代表否,表示非临时实例

mybatis:
  type-aliases-package: cn.itcast.user.pojo
  configuration:
    map-underscore-to-camel-case: true
logging:
  level:
    cn.itcast: debug
  pattern:
    dateformat: MM-dd HH:mm:ss:SSS

# Nacos的负载均衡策略,优先向本地集群发送请求,注意UserService是你本地集群对应的服务名称,也就是注册到注册中心时的服务名称
UserService:
  ribbon:
    # 负载均衡规则为NacosRule
    NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule

#eureka:
#  client:
#    service-url:
#      # eureka的服务地址。如果有多个的话,逗号隔开。也就是把当前这个order-service微服务注册给哪个Eureka
#      defaultZone: http://localhost:8686/eureka
ribbon:
  eager-load:
    enabled: true  # 开启饥饿加载
    # 指定对UserService这个服务开启饥饿加载
    #UserService是你注册到Eureka时的服务名称。如果有多个服务需要做饥饿加载,就-往下写
    clients:
     - UserService

# 第一种自定义feign配置的方式
feign:
  httpclient:
    enabled: true  #支持HttpClient的开关
    max-connections: 200  # 最大连接数  实际开发中要根据压测来进行参数设置
    max-connections-per-route: 50  # 每一个请求路径下的最大连接数 实际开发中要根据压测来进行参数设置

启动效果跟前面一样的,这里只是改变了连接池

四、Feign最佳实践分析

方式一:

继承。给消费者的FeignClient和提供者的controller定义统一的父接口作为标准

特点: 紧耦合,仅适用于面向契约编程的思想上来使用

操作: 让controller和FeignClient继承同一接口

实用篇-Feign客户端_第5张图片

方式二:

抽取。 将FeignClient抽取为独立模块,并且把接口有关的POJO、默认的Feign配置都放到这个模块中,提供给所有消费者使用

特点; 低耦合,但是高冗余

操作: 将FeignClient、POJO、Feign的默认配置都定义到一个项目中,供所有消费者使用

实用篇-Feign客户端_第6张图片

"抽取"方案演示

实现思路

1、首先创建一个module,命名为feign-api,然后引入feign的starter依赖

2、就可以把order-service中的UserClient、User、DefaultFeignConfiguration都复制到feign-api项目中

3、要使用怎么办,直接在order-service中引入feign-api的依赖即可

4、然后修改order-service中的所有与"UserClient、User、DefaultFeignConfiguration"有关的Import部分,改成导入feign-api中的包

具体操作步骤如下

第一步: 在cloud-demo总项目中新建一个项目,项目名为feign-api实用篇-Feign客户端_第7张图片

第二步: 把feign-api微服务项目的pom.xml修改为如下



    
        cloud-demo
        cn.itcast.demo
        1.0
    
    4.0.0

    feign-api

    
        8
        8
    

    
        
            org.springframework.cloud
            spring-cloud-starter-openfeign
        
    

第三步: 在feign-api项目的src/main/java目录下新建cn.itcast.feign包,接着把order-service项目的src/main/java/cn.itcast.order/clients目录、config目录、pojo目录复制到feign-api项目的src/main/java/cn.itcast.feign目录里面。注意粘贴后的clients目录的UserClient接口会爆红,改一下import即可

实用篇-Feign客户端_第8张图片

第四步: 以后哪个项目需要使用Feign时,就直接在pom.xml使用刚写好的feign-api项目即可。例如把order-service项目中使用,我们可以把在order-service项目中有关Feign配置的接口和类删掉(此时order-service项目有关Feign的功能类会报错,等下会解决),然后在order-service项目pom.xml中引入刚写好的feign-api项目依赖,接着在报错的有关Feign功能类里面重新引入一下即可解决报错

实用篇-Feign客户端_第9张图片

  
        
            cn.itcast.demo
            feign-api
            1.0
        

第五步: 解决bug。当我们启动OrderApplication、UserApplication、UserApplication2服务时,会发现报错了,找不到feign-api项目的UserClient接口

在order-service项目的启动类,指定FeignClient字节码,也就是直接指定扫描哪个写好的UserClient,也可以指定多个

@EnableFeignClients(clients = UserClient.class,defaultConfiguration = DefaultFeignConfiguration.class)

实用篇-Feign客户端_第10张图片

第六步。测试(先确保你的nacos已经启动),重启OrderApplication、UserApplication、UserApplication2服务

浏览器访问:http://localhost:8080/order/102

实用篇-Feign客户端_第11张图片

实用篇-Feign客户端_第12张图片

你可能感兴趣的:(分布式微服务,微服务,java,ribbon,spring,cloud)