Feign
基于Feign的远程调用
声明式http客户端,其实就是一个简化http发送的客户端。
orderservice中引入
- 引入依赖:
<!--openFeign依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
-
配置类上开启Feign自动装配 @EnableFeignClients
-
编写Feign接口
@FeignClient("userservice") // 指定服务名称
public interface UserClient {
@GetMapping("/user/{id}")
User findById(@PathVariable("id") Long id);
}
以看到其实和写一个接口类似,这样的好处是,不仅方便程序员编写,而且让程序员使用时认为像是在本地调用一样方便。
传统的利用restTemplate方式发送:
String url = "http://userservice/user/" + order.getUserId();
User user = restTemplate.getForObject(url, User.class);
大家可以类比去记忆。
- 使用
直接像使用本地bean一样去注入和调用即可,不影响代码可读性还。
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Resource
private RestTemplate restTemplate;
@Autowired
private UserClient userClient;
public Order queryOrderById(Long orderId) {
// 1.查询订单
Order order = orderMapper.findById(orderId);
/* // 2.查询用户,远程调用
String url = "http://userservice/user/" + order.getUserId();
User user = restTemplate.getForObject(url, User.class);*/
User user = userClient.findById(order.getUserId());
// 3. 封装
order.setUser(user);
// 4.返回
return order;
}
}
调用成功:
Feign本身就自带的Ribbon负载均衡。
自定义配置
正常情况下,默认自动装配的配置就足够了。
一般最多就是改改日志,其他的一般不修改。
下面介绍两种修改方式:
- 基于配置文件
针对某个服务:
feign:
client:
config:
userservice: # 针对某个微服务的配置
loggerLevel: FULL # 日志级别
针对全部服务:
feign:
client:
config:
default: # 用default就是针对全部服务,如果写服务名称,则是针对某个微服务的配置
loggerLevel: FULL # 日志级别
四种日志级别:
- NONE:不记录任何日志信息,这是默认值。
- BASIC:仅记录请求的方法,URL以及响应状态码和执行时间
- HEADERS:在BASIC的基础上,额外记录了请求和响应的头信息
- FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据。
- 基于java代码修改
@Configuration
public class DefaultFeignConfiguration {
@Bean
public Logger.Level feignLogLevel() {
return Logger.Level.BASIC;
}
}
全局生效,将其放到启动类的@EnableFeignClients这个注解中:
@EnableFeignClients(defaultConfiguration = DefaultFeignConfiguration.class)
局部生效,将其放到对应服务的@FeignClient这个注解中:
@FeignClient(value = "userservice", configuration = DefaultFeignConfiguration.class)
FULL级别的日志信息:
05-30 10:30:12:184 DEBUG 6836 --- [nio-8080-exec-3] cn.itcast.order.web.UserClient : [UserClient#findById] ---> GET http://userservice/user/1 HTTP/1.1
05-30 10:30:12:184 DEBUG 6836 --- [nio-8080-exec-3] cn.itcast.order.web.UserClient : [UserClient#findById] ---> END HTTP (0-byte body)
05-30 10:30:12:190 DEBUG 6836 --- [nio-8080-exec-3] cn.itcast.order.web.UserClient : [UserClient#findById] <--- HTTP/1.1 200 (5ms)
05-30 10:30:12:190 DEBUG 6836 --- [nio-8080-exec-3] cn.itcast.order.web.UserClient : [UserClient#findById] connection: keep-alive
05-30 10:30:12:190 DEBUG 6836 --- [nio-8080-exec-3] cn.itcast.order.web.UserClient : [UserClient#findById] content-type: application/json
05-30 10:30:12:190 DEBUG 6836 --- [nio-8080-exec-3] cn.itcast.order.web.UserClient : [UserClient#findById] date: Thu, 30 May 2024 02:30:12 GMT
05-30 10:30:12:190 DEBUG 6836 --- [nio-8080-exec-3] cn.itcast.order.web.UserClient : [UserClient#findById] keep-alive: timeout=60
05-30 10:30:12:190 DEBUG 6836 --- [nio-8080-exec-3] cn.itcast.order.web.UserClient : [UserClient#findById] transfer-encoding: chunked
05-30 10:30:12:190 DEBUG 6836 --- [nio-8080-exec-3] cn.itcast.order.web.UserClient : [UserClient#findById]
05-30 10:30:12:190 DEBUG 6836 --- [nio-8080-exec-3] cn.itcast.order.web.UserClient : [UserClient#findById] {"id":1,"username":"柳岩","address":"湖南省衡阳市"}
05-30 10:30:12:190 DEBUG 6836 --- [nio-8080-exec-3] cn.itcast.order.web.UserClient : [UserClient#findById] <--- END HTTP (59-byte body)
性能优化
Feign底层发起http请求,依赖于其它的框架。
其底层客户端实现包括:
-
URLConnection:默认,不支持连接池
-
Apache HttpClient:支持连接池
-
OKHttp:支持连接池
优化方法:
- 使用连接池代替默认的URLConnection
- 日志级别最好使用basic 和 none
引入依赖:
<!--httpClient的依赖 -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
</dependency>
配置连接池:
feign:
client:
config:
default: # default全局的配置
loggerLevel: BASIC # 日志级别
httpclient:
enabled: true # 开启feign使用HttpClient
max-connections: 100 # 最大的连接数
max-connections-per-route: 20 # 每个路径的最大连接数
Feign 常用使用方式
- 继承
本身就是微服务为了解耦,这样反而让服务端和客户端耦合了。
而且springMVC对应方法参数,比如@PathVariable继承不下来的,因此Controller中必须再次声明方法、参数列表、注解。
- 抽取
将FeignClient 抽取为独立模块,并且把接口有关的POJO、Feign配置都放到这个模块中,提供给所有消费者使用。
如果不抽取,那么每个服务调用相同的接口都需要重写写相同的代码。
抽取之后,只要引入依赖,即可像在本地一样去使用即可。
现在来实践一下吧。
创建feign-api子模块,并导入依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
将order-service中的UserClient、实体类User、DefaultFeignConfigurationy移动到feign-api项目中。
order-service中引入api模块:
<!--feign-api模块-->
<dependency>
<groupId>cn.itcast.demo</groupId>
<artifactId>feign-api</artifactId>
<version>1.0</version>
</dependency>
orderservice上启动类的EnableFeignClients注解上加上此服务需要使用的Client接口,可以引入多个。
@EnableFeignClients(clients = {UserClient.class})
也可以
@EnableFeignClients(basePackage = "feignClient位置")
将爆红的地方导包(因为位置变了,现在我们用的feign模块中的)。
重启,调用一下: