1. Feign 和 OpenFeign
Feign
Feign是Springcloud组件中的一个轻量级Restful的HTTP服务客户端,Feign内置了Ribbon,用来做客户端负载均衡,去调用服务注册中心的服务。Feign的使用方式是:使用Feign的注解定义接口,调用这个接口,就可以调用服务注册中心的服务
依赖包:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
OpenFeign
OpenFeign是springcloud在Feign的基础上支持了SpringMVC的注解,如@RequestMapping等等。OpenFeign的@FeignClient可以解析SpringMVC的@RequestMapping注解下的接口,并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务。
依赖包:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2. 利用OpenFeign调用远程服务
OpenFeign是在基于客户端的。
2.1 依赖包
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2.2 启动类
需要加@EnableFeignClients 注解
2.3 服务调用
1. 创建一个接口,接口加@FeignClient配置。该接口不需要实现
2. 将接口注入到FeignController,让后调用接口方法。
一般来说,我们直接注入接口是不行的,会报错。但是这里不会报错,一定记得在启动类加上@EnableFeignClients注解,该该注解就不会报错了
3. 那这是如果调用的呢,原理是什么。
Feign相当于是对RestTemplate进行的封装,RestTemplate调用是需要知道一个url的,且需要用RestTemplate的一些方法进行调用。
而OpenFeign这是利用注解进行调用,以下面例子为例:
当调用appInter.gethelloOne()方法时,Feign在这里检测到调用,把这个调用拦截住,利用@FeignClient注解,将provider和方法的RequestMapping的路径组成一个请求路径,http://provider/helloOne,然后Feign去调用远程服务,当远程服务进行返回时,feign把响应拦截处理,最终返回给appInter.gethelloOne()调用的返回。
Feign的操作本质上就是通过动态代理的方式,生成一个代理类,在代理类中进行了一系列的操作,肯定有RestTemplate调用远程服务,Ribbon的负载均衡。
服务调用方
@FeignClient(name = "provider")
public interface AppInter {
@RequestMapping("/helloOne")
public String getHelloOne();
}
@RestController
public class FeignController {
@Autowired
AppInter appInter;
@RequestMapping("/helloOne")
public String HelloOne(){
return appInter.getHelloOne();
}
}
服务提供方
@RestController
public class HelloController {
@Value("${server.port}")
private String port;
@RequestMapping("/helloOne")
public String getHelloOne(){
return port+":"+"helloOne";
}
}
2.4 Get和Post
Feign所有带参数的请求,默认都是Post请求,如果想要使用指定的方式,需要引入依赖httpClient。。目前发现不配置也行
2.5 引入API
1. 创建一个新boot项目。创建一个API,相当于消费者和服务提供者直接的接口
package com.tzw.user;
import org.springframework.web.bind.annotation.RequestMapping;
@RequestMapping("/user")
public interface UserApi {
@RequestMapping("/alive")
public String alive();
}
2. 将此项目打包,
mvn install
3. 将此项目依赖到客户端和服务端
<dependency>
<groupId>com.tzw</groupId>
<artifactId>user-api</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
4. 客户端代码,extends UserApi,客户端继承API
package com.example.consumer;
import com.tzw.user.UserApi;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.*;
@FeignClient(name = "provider")
public interface AppInter extends UserApi {
@RequestMapping("/helloOne")
public String getHelloOne();
@RequestMapping(value="/helloTwo")
public String getHelloTwo(@RequestParam String name);
@RequestMapping(value="/helloThree")
public String getHelloThree(@RequestBody String name);
@RequestMapping("/user")
public String getUserbyId(@RequestParam String id);
}
5. 服务提供端代码,implement UserAPI,服务提供端实现该API。
package com.example.provider;
import com.tzw.user.UserApi;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
@RestController
public class HelloController implements UserApi {
@Value("${server.port}")
private String port;
@RequestMapping("/helloOne")
public String getHelloOne(){
return port+":"+"helloOne";
}
@GetMapping("/helloTwo")
public String getHelloTwo(@RequestParam String name){
return port+":"+name;
}
@PostMapping("/helloThree")
public String getHelloThree(@RequestBody String name){
return port+":"+name;
}
@Override
public String alive() {
return "I'm alive";
}
@RequestMapping("/user")
public String getUserbyId(@RequestParam String id){
return "用户名称:张三-----"+"用户id:"+id;
}
}
6. 总结
API相当于两个系统间的接口,一般有服务提供者设计,开发时可以直接维护该API。注意打包时的问题,boot-inf mata-inf,解决办法
API中可以用@RequestMapping注释,用来在Feign调用是拼接URl。也就是说,以上的例子,feign在调用alive是,url应该是http://localhost:9999/user/alive。而不是http://localhost:9999/alive。
这也是OpenFeign的特性,OpenFeign可以解析@RequestMapping这些注解,并进行url拼接。
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build>
服务消费者继承该API,可以直接进行调用。
服务提供这实现该API,以供服务消费调用。实现方法可以不在加@RequestMapping等注释,在接口中加注释,实现类可以不用加。
2.6 Feign日志
配置日志的两种方式,
通过配置文件配置
#配置日志
feign.client.config.provider.logger-level=full
#上面的logger-level只对下面的 debug级别日志做出响应。
# feign日志已什么级别监控哪个接口
logging.level.com.example.consumer.AppInter=debug
feign.client.config.provider.logger-level=full
注:provider是调用的远程服务名称
//上面有4种日志类型
none:不记录任何日志,默认值
basic:仅记录请求方法,url,响应状态码,执行时间。
headers:在basic基础上,记录header信息
full:记录请求和响应的header,body,元数据。
# feign日志已什么级别监控哪个接口 logging.level.com.example.consumer.AppInter=debug
logging.level.com.example.consumer.*=debug 可以用* 指代所以接口。
二通过配置类配置
/**
* 开启日志等级
* @return
*/
@Bean
public Logger.Level logLevel(){
return Logger.Level.FULL;
}
2.7超时
Feign默认支持Ribbon;Ribbon的重试机制和Feign的重试机制有冲突,所以源码中默认关闭Feign的重试机制,使用Ribbon的重试机制.
配置超时时间,
连接超时,在获取连接的时候的超时。一般是网络导致的。
业务逻辑超时,一般是业务逻辑过程中的超时。
#连接超时时间(ms)
ribbon.ConnectTimeout=1000
#业务逻辑超时时间(ms)
ribbon.ReadTimeout=6000
2.8 重试
有超时就有重试。
#同一台实例最大重试次数,不包括首次调用
ribbon.MaxAutoRetries=1
#重试负载均衡其他的实例最大重试次数,不包括首次调用
ribbon.MaxAutoRetriesNextServer=1
#是否所有操作都重试
ribbon.OkToRetryOnAllOperations=false
使用ribbon重试机制,请求失败后,每个6秒会重新尝试
HyStrix
这个重试机制不太友好,就引出了Hystrix机制
spring cloud 用的是 hystrix,是一个容错组件。
Hystrix实现了 超时机制和断路器模式。