feign的作用
feign主要是以类似restful风格形式接口,实现了微服务之间优雅的请求调用,解决restTemplate难以管理和维护大量服务地址和请求参数的问题。feign底层也基于ribbon,实现了负载均衡和重试等机制。
如何使用feign
如何使用feign?
- 引入feign客户端依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
-
启动类OrderApplication上配置@EnableFeignClients开启自动装配
-
编写客户端接口
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); }
-
改用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; } }
-
重启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:支持连接池
改变客户端实现步骤:
- 引入依赖
<dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-httpclient</artifactId> </dependency>
- 配置文件开启支持
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();
}
}
再重启测试,没有问题,调用成功