详解SpringCloud微服务技术栈:Feign远程调用、最佳实践、错误排查

‍作者简介:一位大四、研0学生,正在努力准备大四暑假的实习
上期文章:详解SpringCloud微服务技术栈:Nacos配置管理
订阅专栏:微服务技术全家桶
希望文章对你们有所帮助

之前使用RestTemplate来实现远程调用,这种方式存在了一些问题,更优的解决方式是使用Feign来实现远程调用。
这里将会讲解如何用Feign实现远程调用,并进行最佳实践。

Feign的远程调用与最佳实践

  • 基于Feign实现远程调用
  • 自定义配置
  • 性能优化
  • Feign最佳实践
    • 分析
    • 实现

基于Feign实现远程调用

RestTemplate的问题:

1、代码可读性差,变成体验不统一
2、参数比较复杂的url,难以维护

Feign是一个声明式的http客户端,官方地址:Feign官网
声明式在Spring中开始提到,是利用配置文件来加事务。而声明式http客户端也是类似,我们只需要把发http请求所需要的信息声明出来即可,剩下的东西都交给Feign来实现。

使用Feign的步骤:
1、引入依赖:

<dependency>
	<groupId>org.springframework.cloudgroupId>
	<artifactId>spring-cloud-starter-openfeignartifactId>
dependency>

2、在order-service的启动类添加开启Feign的功能:加注解@EnableFeignClients
3、做声明(编写Feign客户端):

@FeignClient("userservice") //声明出服务名称
public interface UserClient {

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

主要是基于SpringMVC的注解来声明远程调用的信息,如服务名称、 请求方式、请求路径、请求参数、返回值类型等,直接声明这些信息就行。
这样的方式会简单很多,注解开发太方便了,节约了很多学习成本,即便url的参数很复杂,我们利用注解开发写参数列表也是很方便的。

现在我们可以直接使用这个客户端了,直接修改order查询的接口:

    @Resource
    private OrderMapper orderMapper;

    @Resource
    private UserClient userClient;

    public Order queryOrderById(Long orderId){
        //查询订单
        Order order = orderMapper.findById(orderId);
        //根据用户id来查询用户,用Feign实现远程调用
        User user = userClient.findById(order.getUserId());
        //将用户注入到order中
        order.setUser(user);
        // 4.返回
        return order;
    }

同时多次刷新网址,可以验证出Feign还集成了Ribbon负载均衡,是比较强大的。

总结Feign使用步骤:

引入依赖
添加@EnableFeignClients注解
编写FeignClient接口
使用FeignClient中定义的方法来代替RestTemplate

自定义配置

Feign可以让我们自定义配置来覆盖默认配置,一般需要配置的是日志级别的类型feign.Logger.Level。
配置Feign日志有2种方式。

方式一:配置文件
1、全局生效

feign:
  client:
    config:
      default:
        loggerLevel: FULL # 最高级别,日志中会包含发起的请求,以及远程调用等信息

详解SpringCloud微服务技术栈:Feign远程调用、最佳实践、错误排查_第1张图片

2、局部生效

feign:
  client:
    config:
      userservice:
        loggerLevel: FULL

方式二:Java代码
先声明一个Bean:

public class DefaultFeignConfiguration {

    @Bean
    public Logger.Level LogLevel(){
        return Logger.Level.BASIC;
    }
}

想要这个类生效,需要配置。
1、全局配置:把它放到@EnableFeignClients这个注解中:

@EnableFeignClients(defaultConfiguration = DefaultFeignConfiguration.class)

2、局部配置:把它放到@FeignClient这个注解中:

@FeignClient(value = "userservice", configuration = DefaultFeignConfiguration.class)

重启order-service,访问网址后查看控制台的日志,除了SQL语句之外,还有关于Feign的日志信息(只有请求行和相应行)

性能优化

Feign其实性能一直很不错了,但是还是可以被优化,Feign底层的客户端实现:

URLConnection:默认实现,不支持连接池
Apache HttpClient:支持连接池
OKHttp:支持连接池

Feign底层还是会用到其他的客户端,默认使用URLConnection,但是它不支持连接池,这就会使得性能不是太好。因此更推荐使用其他两种。

Feign的性能优化主要包括:

使用连接池代替默认的URLConnection
日志级别,最好用BASIC或NONE,日志级别太高也会造成一些性能损耗

将URLConnection换成Apache HttpClient:
1、引入依赖:

	
    <dependency>
        <groupId>io.github.openfeigngroupId>
        <artifactId>feign-httpclientartifactId>
    dependency>

2、配置连接池:

feign:
  httpclient:
    enabled: true # 支持HttpClient的开关
    max-connections: 200 # 最大连接数
    max-connections-per-route: 50 # 单个路径的最大连接数

其中,最大连接数和单个路径最大连接数并不能那么容易确定,具体的连接数需要根据业务的情况,对于不同的业务,可以用jmeter进行压力测试。

Feign最佳实践

分析

方式一:继承

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

乍一看还是有点抽象的,但是我们可以打开一下order-service的UserClient接口与user-service的UserController:
详解SpringCloud微服务技术栈:Feign远程调用、最佳实践、错误排查_第2张图片
详解SpringCloud微服务技术栈:Feign远程调用、最佳实践、错误排查_第3张图片
这两个的函数其实是差不多的,因为消费者要通过Feign解析网址请求,就需要带上相应的信息(请求方式、网址、参数等),而提供者则需要提供正确的方式给消费者,因此信息上两者基本上都是差不多的。
所以理论上可以定义一个统一的父接口来作为标准。

public interface UserAPI{
	@GetMapping
	User findById(@PathVariable("id") Long id);
}

而消费者和提供者只需要分别继承和实现这个接口就可以了。
但是这种方式有一定的问题,Spring官方也不推荐让客户端和服务器端共用一个接口,因为这样的耦合度太高了。

方式二:抽取

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

解析一下,在之前的代码中,order-service的UserClient会去调用user-service中的UserController,但是如果还有很多模块的UserClient都要调用,可能就会有很多地方重复写了,因此可以专门定义一个feign-api,在里面声明UserClient,并且涉及到的实体类、默认配置都在feign-api中实现。而order-service要使用的时候,只需要引入feign-api的依赖就可以了。

实现

这里将会实现方式二,步骤如下:
1、新建module,命名为feign-api,然后引入feign的starter依赖

	<dependency>
        <groupId>org.springframework.cloudgroupId>
        <artifactId>spring-cloud-starter-openfeignartifactId>
    dependency>

2、将order-service中编写的UserClient、User、DefaultFeignConfiguration都复制到feign-api项目中
详解SpringCloud微服务技术栈:Feign远程调用、最佳实践、错误排查_第4张图片
上面拥有的部分,在order-service里面都可以直接删除了,以后需要直接去用feign-api里面的东西就可以了。

3、在order-service中引入feign-api的依赖

	<dependency>
		
        <groupId>com.wanggroupId>
        <artifactId>feign-apiartifactId>
        <version>1.0version>
    dependency>

4、修改order-service中的所有与上述组件有关的import部分,改成导入feign-api的包

5、重启测试

运行后发现会报错,查看报错信息:
详解SpringCloud微服务技术栈:Feign远程调用、最佳实践、错误排查_第5张图片
没有找到UserClient的对象,但是编译没有报错,只是运行报错了,查看OrderService:
详解SpringCloud微服务技术栈:Feign远程调用、最佳实践、错误排查_第6张图片
可以想到,Spring没有获得这个容器的对象,这种情况的发生是因为扫描包出现了问题,只是因为启动类指定的Mapper是在cn.itcast.order下面的,然而UserClient是在cn.itcast.feign下面的。
详解SpringCloud微服务技术栈:Feign远程调用、最佳实践、错误排查_第7张图片
简单粗暴的解决方式是直接把MapperScan扫描范围扩大,但是这肯定是不合适的,启动类应该默认扫描的范围就是所在的包下面的,应该想想别的办法。

方式一:指定FeignClient所在包(全盘拿来)

@EnableFeignClients(basePackages = "cn.itcast.feign.clients", defaultConfiguration = DefaultFeignConfiguration.class)

方式二:指定FeignClient字节码(精准定位,推荐方案)

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

你可能感兴趣的:(微服务技术全家桶,spring,cloud,微服务,spring,java,后端,Feign)