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

发布时间:2024年01月17日

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

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

基于Feign实现远程调用

RestTemplate的问题:

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

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

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

<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-openfeign</artifactId>
</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 # 最高级别,日志中会包含发起的请求,以及远程调用等信息

在这里插入图片描述

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、引入依赖:

	<!--引入HttpClient依赖-->
    <dependency>
        <groupId>io.github.openfeign</groupId>
        <artifactId>feign-httpclient</artifactId>
    </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:
在这里插入图片描述
在这里插入图片描述
这两个的函数其实是差不多的,因为消费者要通过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.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>

2、将order-service中编写的UserClient、User、DefaultFeignConfiguration都复制到feign-api项目中
在这里插入图片描述
上面拥有的部分,在order-service里面都可以直接删除了,以后需要直接去用feign-api里面的东西就可以了。

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

	<dependency>
		<!--groupId是你创建api的这个module时候的相关包-->
        <groupId>com.wang</groupId>
        <artifactId>feign-api</artifactId>
        <version>1.0</version>
    </dependency>

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

5、重启测试

运行后发现会报错,查看报错信息:
在这里插入图片描述
没有找到UserClient的对象,但是编译没有报错,只是运行报错了,查看OrderService:
在这里插入图片描述
可以想到,Spring没有获得这个容器的对象,这种情况的发生是因为扫描包出现了问题,只是因为启动类指定的Mapper是在cn.itcast.order下面的,然而UserClient是在cn.itcast.feign下面的。
在这里插入图片描述
简单粗暴的解决方式是直接把MapperScan扫描范围扩大,但是这肯定是不合适的,启动类应该默认扫描的范围就是所在的包下面的,应该想想别的办法。

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

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

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

@EnableFeignClients(clients = {UserClient.class}, defaultConfiguration = DefaultFeignConfiguration.class)
文章来源:https://blog.csdn.net/m0_52380556/article/details/135630617
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。