Feign的基本使用、日志配置和连接池配置详解
一、概述
Feign
主要是微服务项目中远程调用的一种实现方式。
常见的远程调用方式有以下几种。
1) RestTemplate
早期的时候远程调用使用的是RestTemplate
。
String userId = "ZXCVASDD";
// ip和端口在使用注册中心后可以由服务名代替
String url = "http://userservice/findDataByParams/" + userId;
// 发送Http请求 实现不同模块之间的远程调用
User user = restTemplate.getForObject(url, User.class);
这样的编码方式存在很多问题:
-
代码可读性差,不符合主流编码风格。对于没有接触过远程调用的人来说,非常难以理解。
-
大量的
Http
路径维护在代码当中,而且参数复杂url
今后难以维护。
因此这样的方式,已经越来越少被使用了。
2) Dubbo
Dubbo
是一款优秀的远程RPC
框架。
在高并发的情况下,RPC
请求会比Http
请求性能更加优秀。
@RestController
@RequestMapping("/user")
public class UserController {
// version 用于灰度发布 代表当前Dubbo服务的版本
// retries 请求失败时的重试次数
@DubboReference(version = "2.0.0", retries = 2)
private UserService userService;
@GetMapping("/{id}")
public User queryById(@PathVariable("id") Long id) {
return userService.queryById(id);
}
}
Dubbo
的编码风格,比较符合主流的编码习惯,对于程序员来说上手不会特别困难。
并且高并发下Dubbo
有着不错的性能。
依然是微服务做远程调用的主流选择之一。
3) Feign
Feign
是一个声明式的Http
客户端,
和RestTemplate
一样都是用过发送Http
请求来完成远程调用。
@Service
public class CardService {
@Autowired
private UserFeignClient userFeignClient;
public Card feignQueryOrderById(Long cardId) {
//查询身份证
Card card = cardMapper.findById(cardId);
//使用Feign进行远程调用
User user = (User) userClient.findById(card.getUserId());
//封装user信息
card.setUser(user);
//返回
return card;
}
}
但是Feign
解决了RestTemplate
的所有缺点。
实现了更优雅,可读性更高的代码。
并且Feign
内部还集成了负载均衡、日志 、连接池等解决方案、功能非常强大。
基本上已经完全替代了RestTemplate
。
官方地址:https://github.com/OpenFeign/feign
二、Feign 的使用步骤详解
前言:
假设有两个微服务:
提供者为:UserService
消费者为:CardService
下面就基于这两个微服务演示Feign
的使用。
2.1 提供者端先提供可以被远程调用的接口
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
/**
* 根据id查询用户
* 路径: /user/110
* @param id 用户id
* @return 当前id对应的用户对象
*/
@GetMapping("/{id}")
public User queryById(@PathVariable("id") Long id) {
System.out.println("truth: " + truth);
return userService.queryById(id);
}
}
2.2 消费者端引入依赖
消费者端就是需要远程调用其它微服务的那一端。
任何微服务都可能成为消费者端。视具体的业务而定。
<!-- SpringBoot 整合Feign 起步依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2.3 消费者端引导类上添加注解@EnableFeignClients
@EnableFeignClients // 开启Feign的支持
@SpringBootApplication
public class CardApplication {
public static void main(String[] args) {
SpringApplication.run(CardApplication.class, args);
}
}
2.4 当前消费者端编写Feign的客户端
//@FeignClient 声明这是一个Feign客户端
//这个注解的参数就是当前客户端想要远程调用的服务名称
// @FeignClient(value = "userservice" path = "/user") 这么写的话 path的路径就指向具体的controller
//那么这个Client下面的方法,也只能在对应的controller中获取,灵活性会差一些
//所以可以把完整路径写在接口方法签名的上方
@FeignClient(value = "userservice")
public interface UserClient {
@GetMapping("/user/{id}")
Card findById(@PathVariable("id") Long id);
}
微服务名称定义在微服务提供者端的application.yml
中的如下位置:
spring:
application:
name: userservice # 服务名称
这里可以发现,Feign
客户端主要基于SpringMVC
的注解来声明远程调用的信息。
比较符合主流的编码习惯,学习成本低。
2.5 当前消费者端调用刚才定义的Feign客户端
@Service
public class CardService {
@Autowired
private UserClient userClient;
public Card feignQueryOrderById(Long cardId) {
//查询身份证
Card card = cardMapper.findById(cardId);
//使用Feign进行远程调用
User user = (User) userClient.findById(card.getUserId());
//封装user信息
card.setUser(user);
//返回
return card;
}
}
这样消费者端的Service
层就完成了Feign
的远程调用。
看起来和单体架构的写法没什么区别。
而且代码风格也更加优雅,可读性和可维护性都大大增强。
三、给需要调用的Feign客户端设置异常处理机制
假设调用UserClient
中的方法发生异常时,我们也可以自定义异常类去处理。
相当于SpringMVC
中的统一异常处理。
3.1 定义异常处理类
异常处理类需要实现FallbackFactory
接口,泛型就写定义的Feign客户端的类名
@Slf4j
public class UserClientFallbackFactory implements FallbackFactory<UserClient> {
@Override
public UserClient create(Throwable throwable) {
return new UserClient() {
@Override
public User findById(Long id) {
log.error("查询用户异常", throwable);
return new User();
}
};
}
}
这样在调用UserClient
中的服务发生异常时,就会触发create
方法。
返回一个各个属性都没赋值的User
对象,而不是直接报错让程序终止。
同时日志也会记录下这次异常,方便以后排查。
3.2 在对应微服务的Feign
客户端给@FeignClient
注解添加参数fallbackFactory
//@FeignClient 声明这是一个Feign客户端
//value 对应的微服务名称
//fallbackFactory 调用UserClient中的方法发生异常时的处理
@FeignClient( = "userservice", fallbackFactory = UserClientFallbackFactory.class)
public interface UserClient {
@GetMapping("/user/{id}")
Card findById(@PathVariable("id") Long id);
}
四、Feign 的日志配置
Feign支持很多的自定义配置,如下表所示:
类型 | 作用 | 说明 |
---|---|---|
feign.Logger.Level | 修改日志级别 | 包含四种不同的级别:NONE、BASIC、HEADERS、FULL |
feign.codec.Decoder | 响应结果的解析器 | http远程调用的结果做解析,例如解析json字符串为java对象 |
feign.codec.Encoder | 请求参数编码 | 将请求参数编码,便于通过http请求发送 |
feign. Contract | 支持的注解格式 | 默认是SpringMVC的注解 |
feign. Retryer | 失败重试机制 | 请求失败的重试机制,Feign默认是没有实现重试机制 |
关于重试机制:Feign底层依赖了Ribbon,因此会使用Ribbon的重试,这样Feign就相当于有了重试机制!!
一般情况下,默认值就能满足我们使用,如果要自定义时,只需要创建自定义的@Bean覆盖默认Bean即可。
平时开发比较常用的还是对日志级别的配置。
所以这里就以日志级别的配置来演示Feign
自定义配置的使用。
4.1 使用application.yml
配置文件修改日志级别
4.1.1 针对单个服务配置日志级别
feign:
client:
config:
userservice: # 针对userservice这个微服务的日志级别
loggerLevel: FULL # 日志级别
4.1.2 所有微服务配置日志级别
feign:
client:
config:
default: # default就是全局配置,如果是写服务名称,则是针对某个具体的微服务的配置
loggerLevel: FULL # 日志级别
4.2 使用Java代码自定义日志级别
在消费者端声明一个类,在类中定义Logger.Level
对象
public class DefaultFeignConfiguration {
@Bean
public Logger.Level feignLogLevel(){
return Logger.Level.BASIC; // 日志级别为BASIC
}
}
想要使这个类生效,需要给SpringBoot
启动类上的@EnableFeignClients
添加参数
- 全局生效:
@EnableFeignClients(defaultConfiguration = DefaultFeignConfiguration .class)
- 局部生效:
//值针对userservice这个微服务生效
@FeignClient(value = "userservice", configuration = DefaultFeignConfiguration .class)
4.3 关于日志的级别的说明
日志级别 | 说明 | 备注 |
---|---|---|
NONE | 就是没有任何日志记录 | Feign 的默认日志级别 |
BASIC | 记录请求的方法,URL以及响应状态码和执行开始和结束时间 | 记录请求基本信息 |
HEADERS | 在BASIC的基础上,额外记录了请求和响应的头信息 | |
FULL | 记录所有请求和响应的明细,包括头信息、请求体、元数据 | 是最全的日志 |
使用建议:
如果是调试错误,可以使用FULL
级别。
平常的话,使用BASIC
级别即可。
五、使用Feign连接池
Feign
发起Http
请求,底层客户端实现通常包括以下几种方式:
实现方式 | 说明 |
---|---|
URLConnection | 默认实现,不支持连接池 |
Apache HttpClient | 支持连接池 |
OKHttp | 支持连接池 |
之所以要使用连接池,也是因为每次Http
请求,需要三次握手去建立连接,完成后再断开连接。
这在高并发的情况下性能损耗是比较大的。
因此想要提高Feign
的性能,可以使用有连接池的底层实现,代替默认的URLConnection
。
5.1 消费者端引入依赖
<!--Apache HttpClient的依赖 -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
</dependency>
5.2 配置Feign连接池
feign:
client:
config:
default: # default 全的配置
loggerLevel: BASIC # 日志级别配置 BASIC基本的请求和响应信息
httpclient:
enabled: true # 开启feign对HttpClient的支持
max-connections: 100 # 连接池的 最大的连接数
max-connections-per-route: 50 # 连接池的 每个路径的最大连接数
六、分模块开发抽取Feign模块
上面Feign
的使用步骤中对Feign
的使用方案,大部分新手都会这么去使用Feign
。
但是这样会引发一个设计层面问题:
假设一个消费者端要使用提供者UserService
中提供的而服务。那么就要写一次UserClient
.
那么在多个不同的消费者端都要使用提供者UserService
中提供的而服务时,UserClient
会写非常多次。
这非常不利于将来的维护,而且代码也过于冗余。
解决方案:
利用Maven
分模块开发抽取Feign
模块。
这样只需要把每个服务的Client
,都只在单独的Feign
模块中写一次。
并且把接口有关的POJO
、默认的Feign
配置都放到这个模块中。
各个消费者端再引入这个单独的Feign
模块就可以了。
具体操作:
6.1 单独创建一个module,命名为feign-api
6.2 给feign-api模块引入feign客户端的起步依赖
<!-- feign客户端的依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
6.3 在所有需要远程调用其它服务的消费者端引入feign-api模块的对应依赖
<!-- 这里的groupId 视自己创建模块时设置的groupId即可 -->
<dependency>
<groupId>com.feign.demo</groupId>
<artifactId>feign-api</artifactId>
<version>1.0</version>
</dependency>
6.4 配置包扫描
之所以要配置包扫描,是因为消费者端的包扫描路径是在SpringBoot
启动类的包及其子包下。
这个路径和feign-api
模块中写的各个服务的Feign
客户端不一定一致。
如果两个路径不一致,就会导致消费者端的Spring
容器中没有对应服务的Feign
客户端对象。
导致依赖注入失败。
方式一:配置具体需要引入的Feign客户端
在引入feign-api
的消费者端的SpringBoot
的引导类上配置注解
@EnableFeignClients(clients = {UserClient.class})
注解参数中指定了具体需要引入哪个提供者服务的UserClient
端
clients
参数是一个数组,可以传递多个值。
@EnableFeignClients(clients = {UserClient.class})
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}
方式二:直接配置Feign的包扫描路径
在引入feign-api
的消费者端的SpringBoot
的引导类上配置注解
@EnableFeignClients(basePackages = "com.feign.clients")
@EnableFeignClients(basePackages = "com.feign.clients")
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/116500.html