Administrator
发布于 2023-04-25 / 15 阅读
0
0

微服务(七)——HTTP客户端Feign

feign的作用

feign主要是以类似restful风格形式接口,实现了微服务之间优雅的请求调用,解决restTemplate难以管理和维护大量服务地址和请求参数的问题。feign底层也基于ribbon,实现了负载均衡和重试等机制。

如何使用feign

如何使用feign?

  1. 引入feign客户端依赖
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>

     

  2. 启动类OrderApplication上配置@EnableFeignClients开启自动装配

  3. 编写客户端接口
     

    package cn.lsy.order.client;
    
    import cn.lsy.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);
    }
    

     

  4. 改用feign替代restTemplate进行远程调用
     

    @Service
    public class OrderService {
    
        @Autowired
        private OrderMapper orderMapper;
    
        @Autowired
        private UserClient userClient;
    
        public Order queryOrderById(Long orderId) {
            // 1.查询订单
            Order order = orderMapper.findById(orderId);
            //使用feign进行远程调用
            User user = userClient.findById(order.getUserId());
            //封装user到order
            order.setUser(user);
            // 4.返回
            return order;
        }
    }

     

  5. 重启order-service服务测试,没有问题,调用成功

Feign配置

主要有两种配置方式:

  • 配置文件
    feign:
      client:
        config:
          default:
            logger-level: FULL

     

  • 自定义配置类,并加入到启动类的@EnableFeignClients中

    package cn.lsy.order.configuration;
    
    import feign.Logger;
    import org.springframework.context.annotation.Bean;
    
    public class FeignClientConfiguration {
        @Bean
        public Logger.Level feignLogLevel(){
            return Logger.Level.FULL;
        }
    }
    

     

    @EnableFeignClients(defaultConfiguration = FeignClientConfiguration.class)

     

测试http://localhost:8080/order/102

配置前:

配置后,确实多了很多日志信息输出,包括请求时间,请求头,返回信息等

以上都是全局配置,如果针对服务配置,那么方式一中,default需要改成指定服务名如userservice。方式二中,在UserClient的FeignClient注解上加上配置类@FeignClient(value = "userservice",configuration = FeignClientConfiguration.class)

总结:

性能优化

Feign属于声明式客户端,底层还是依靠别的客户端实现,主要有:

  • UrlConnection:默认实现,不支持连接池
  • Apache httpClient:支持连接池
  • OkHttp:支持连接池

改变客户端实现步骤:

  1. 引入依赖
    <dependency>
        <groupId>io.github.openfeign</groupId>
        <artifactId>feign-httpclient</artifactId>
    </dependency>

     

  2. 配置文件开启支持
    feign:
      httpclient:
        enabled: true #开启feign对httpclient的支持
        max-connections: 200 #最大连接数  连接数结合根据项目实际情况来设置
        max-connections-per-route: 50 #每个请求路径的最大连接数

     

总结,优化Feign性能的主要方式有:

  • 使用连接池代替默认的UrlConnection
  • 日志级别,最好用basic或none

实践优化方案

方案一,因为服务提供方的controller和服务调用方的client具有高度相似性,如请求方式、参数、返回类型,所以可以提供统一的父接口Api,使UserClient和UserController都继承统一的接口,遵循了一种面向契约的思想。

spring官方一般不推荐在服务端和客户端之间共享接口,因为会造成紧耦合。

 

方案二,抽取共有特性,封装一个独立的服务包。比如多个微服务模块都需要查询用户信息,那么我们需要把用户查询相关的模块提取出来,单独封装,然后给其它微服务模块引入使用。

缺点就是,只需要一小部分功能,也需要引入整个依赖包。这个就看我们项目的实际情况了。还是比较推荐这种方式。

总结:

提取feign模块

来,一起试一下

第一步:新建一个module,引入依赖

第二步,将User相关的数据类和相关配置提取出来,放到feign-api项目里。

package cn.lsy.feign.client;

import cn.lsy.feign.configuration.FeignClientConfiguration;
import cn.lsy.feign.pojo.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

@FeignClient(value = "userservice",configuration = FeignClientConfiguration.class)
public interface UserClient {
    @GetMapping("/user/{id}")
    User findById(@PathVariable("id")Long id);
}


package cn.lsy.feign.configuration;

import feign.Logger;
import org.springframework.context.annotation.Bean;

public class FeignClientConfiguration {
    @Bean
    public Logger.Level feignLogLevel(){
        return Logger.Level.FULL;
    }
}


package cn.lsy.feign.pojo;

import lombok.Data;

@Data
public class User {
    private Long id;
    private String username;
    private String address;
}

配置文件

feign:
  httpclient:
    enabled: true #开启feign对httpclient的支持
    max-connections: 200 #最大连接数  连接数结合根据项目实际情况来设置
    max-connections-per-route: 50 #每个请求路径的最大连接数

第三步,order-service引入feign-api的依赖

<dependency>
    <groupId>cn.lsy.demo</groupId>
    <artifactId>feign-api</artifactId>
    <version>1.0</version>
</dependency>

第四部,修改order-service中user相关的import导入,移除配置文件中feign的配置

package cn.lsy.order.pojo;

import cn.lsy.feign.pojo.User;
import lombok.Data;

@Data
public class Order {
    private Long id;
    private Long price;
    private String name;
    private Integer num;
    private Long userId;
    private User user;
}


package cn.lsy.order.service;

import cn.lsy.feign.client.UserClient;
import cn.lsy.feign.pojo.User;
import cn.lsy.order.mapper.OrderMapper;
import cn.lsy.order.pojo.Order;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class OrderService {

    @Autowired
    private OrderMapper orderMapper;

    @Autowired
    private UserClient userClient;

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

第5步,重启order-service测试

报错了,提示未注入bean。因为@EnableFeignClient默认扫描包在当前目录,而UserClient在feign-api模块目录下,所以没有扫描到,不会自动装配对象。有两种方式导入包

这里我们使用第二种方式:

package cn.lsy.order;

import cn.lsy.feign.client.UserClient;
import com.alibaba.cloud.nacos.ribbon.NacosRule;
import com.netflix.loadbalancer.IRule;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;

@MapperScan("cn.lsy.order.mapper")
@SpringBootApplication
@EnableFeignClients(clients = UserClient.class)
public class OrderApplication {

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

//    @Bean
//    @LoadBalanced
//    public RestTemplate restTemplate(){
//        return new RestTemplate();
//    }

    @Bean
    public IRule rule(){
        return new NacosRule();
    }

}

再重启测试,没有问题,调用成功


评论