声明式服务调用Feign
使用RestTemplate进行调用其他服务的API的时候,请求的参数需要在URL中进行拼接,当有多参数的时候,拼接字符串的方式效率就会显得比较低下,而Feign能够解决这样的问题
什么是Feign
Feign是SpringCloud提供的声明式模板化的Http客户端,只需要创建一个接口并且添加一个注解即可,SpringCloud集成了Feign并且对其进行了增强,Feign支持SpringMvc的注解,Feign默认集成Ribbon,也实现了负载均衡的效果
案例展示
1.首先创建服务提供者
application.yml
server:
port: 9092
spring:
cloud:
nacos:
discovery:
server-addr: 192.168.112.130:8848
application:
name: feign-provider #服务名称
controller
@RestController
@RequestMapping("/provider")
public class ProviderController {
@Autowired
private UserService userService;
@RequestMapping("/getUserById/{id}")
public User getUserById(@PathVariable Integer id){
return userService.getUserById(id);
}
@RequestMapping("/deleteUserById")
public User deleteUserById(@RequestParam("id")Integer id){
return userService.deleteUserById(id);
}
@RequestMapping("addUser")
public User addUser(@RequestBody User user){
return userService.addUser(user);
}
}
Service
public interface UserService {
User getUserById(Integer id);
User deleteUserById(Integer id);
User addUser(User user);
}
2.创建Feign接口
导入Feign依赖
<!--Spring Cloud OpenFeign Starter -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
@FeignClient("feign-provider")
@RequestMapping("/provider")
public interface UserFeign {
@RequestMapping("/getUserById/{id}")
User getUserById(@PathVariable("id") Integer id); //restful拼接传参
@RequestMapping("/deleteUserById")
User deleteUserById(@RequestParam("id")Integer id);//?拼接URL
@RequestMapping("addUser")
User addUser(@RequestBody User user);//将User对象转成json串
}
Feign接口中接受的参数大致分三大类:restful路径传参,?拼接传参,java对象为参数(其中将对象转换成了json串)
注意:此时Feign接口中的SpirngMvc注解不再是它原本的意思,而是在Feign中的独有的意思,通过这些注解将他们拼接成URL
3.创建消费者
导入依赖
<!--nacos客户端-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--feign接口-->
<dependency>
<groupId>com.li</groupId>
<artifactId>feign_interface</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
application.yml
server:
port: 8080
spring:
cloud:
nacos:
discovery:
server-addr: 192.168.112.130 #nacos服务的地址
application:
name: feign-consumer
Controller
@RestController
@RequestMapping("/consumer")
public class ConsumerController {
@Autowired
private UserFeign userFeign;
@RequestMapping("/getUserById/{id}")
public User getUserById(@PathVariable("id")Integer id){
return userFeign.getUserById(id);
}
@RequestMapping("/deleteUserById")
public User deleteUserById(@RequestParam("id")Integer id){
return userFeign.deleteUserById(id);
}
@RequestMapping("addUser")
public User addUser(@RequestBody User user){
return userFeign.addUser(user);
}
}
启动类
```java
@SpringBootApplication
@EnableFeignClients//开启feign接口扫描
@EnableDiscoveryClient//向注册中心注册该服务,并可以获取其他服务的调用地址
public class FeignConsumerApp {
public static void main(String[] args) {
SpringApplication.run(FeignConsumerApp.class);
}
}
测试
Feign原理
1.用过@EnableFeignClients注解扫描Feign的注解,扫描遍历feign接口的包,生成feign接口的代理对象放入spring容器中
2.为feign接口的方法创建RequestTemplate:当consumer调用feign代理类时,代理类会调用SynchronousMethodHandler.invoke()创建RequestTemplate
3.代理类会通过RequestTemplate创建Request,然后client(URLConnetct、HttpClient、OkHttp)使用Request发送请求
Object executeAndDecode(RequestTemplate template) throws Throwable {
//生成请求对象
Request request = this.targetRequest(template);
if (this.logLevel != Level.NONE) {
this.logger.logRequest(this.metadata.configKey(), this.logLevel, request);
}
long start = System.nanoTime();
Response response;
try {
//发起请求
response = this.client.execute(request, this.options);
} catch (IOException var15) {
... ... ...
throw FeignException.errorExecuting(request, var15);
}
}
也就是说feign的注解是用来拼接URL参数的
断点验证
Feign优化
Feign的优化分为三种:1.开启feign日志 2.feign超时 3.http连接池 4.gzip压缩
1.开启feign日志
feign:
client:
config:
default:
loggerLevel: full
logging:
level:
com.li.feign: debug
2.feign超时
模拟超时
修改provider生产者
public class UserServiceImpl implements UserService {
@Override
public User getUserById(Integer id) {
try {
Thread.sleep(2000);//休眠2s
} catch (InterruptedException e) {
e.printStackTrace();
}
return new User(id,"lz1",18);
}
结果超时
进行feign时间配置
feign:
client:
config:
feign-provider:
ConnectTimeout: 5000 #请求连接的超时时间
ReadTimeout: 5000 #请求处理的超时时间
或者
ribbon:
ConnectTimeout: 5000 #请求连接的超时时间
ReadTimeout: 5000 #请求处理的超时时间
日志的配置会影响feign的超时时间
3.http连接池
http 的背景原理
a. 两台服务器建立 http 连接的过程是很复杂的一个过程,涉及到多个数据包的交换,并
且也很耗时间。
b. Http 连接需要的 3 次握手 4 次分手开销很大,这一开销对于大量的比较小的 http 消
息来说更大。
2优化解决方案
a. 如果我们直接采用 http 连接池,节约了大量的 3 次握手 4 次分手;这样能大大提升吞
吐率。
b. feign 的 http 客户端支持 3 种框架;HttpURLConnection、httpclient、okhttp;默认是
HttpURLConnection。
c. 传统的 HttpURLConnection 是 JDK 自带的,并不支持连接池,如果要实现连接池的
机制,还需要自己来管理连接对象。对于网络请求这种底层相对复杂的操作,如果有可用的
其他方案,也没有必要自己去管理连接对象。
d. HttpClient 相比传统 JDK 自带的 HttpURLConnection,它封装了访问 http 的请求头,
参数,内容体,响应等等;它不仅使客户端发送 HTTP 请求变得容易,而且也方便了开发人
员测试接口(基于 Http 协议的),即提高了开发的效率,也方便提高代码的健壮性;另外
高并发大量的请求网络的时候,还是用“连接池”提升吞吐量。
在消费者端导入依赖
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
</dependency>
此时client由最初的ClientDefault变成了ApacheHttpClient
4.Feign的gzip压缩
Feign 是通过 http 调用的,那么就牵扯到一个数据大小的问题。如果不经过压缩就发送请求、获取响应,那么会因为流量过大导致浪费流量,这时就需要使用数据压缩,将大流量压缩成小流量。
修改application.yml配置文件
server:
port: 8080
compression:
enabled: true #开启gzip压缩
spring:
cloud:
nacos:
discovery:
server-addr: 192.168.112.130 #nacos服务的地址
application:
name: feign-consumer
ribbon:
ConnectTimeout: 5000 #请求连接的超时时间
ReadTimeout: 5000 #请求处理的超时时间
当改为gzip压缩后,响应头的Content-Encoding变成了gzip的方式