关键字:微服务, Spring Cloud, OpenFeign, 服务调用, RPC
Feign(NetFlix)---->维护---->OpenFeign(开源社区维护)
Feign
本身并不支持Spring MVC
的注解,它有一套自己的注解。
后来Spring Cloud
孵化了OpenFeign
,为了更方便的使用,使其支持了Spring MVC
的注解,如@RequestMapping
,@PathVariable
等等。
并支持使用 Spring Web 中默认使用的相同 HttpMessageConverters。
OpenFeign
的@FeignClient
可以解析Spring MVC
的@RequestMapping
注解下的接口,并通过动态代理方式产生实现类,实现类中做负载均衡调用服务。
后面都是讨论OpenFegin,简称Fegin了。Feign正统在OpenFegin! doge
OpenFegin是一个声明式的伪http客户端
使用简单:写一个接口,加一个注解,调用服务方代码。
被调用方几乎什么都不用做,只要到注册中心登记即可。本身就是要重复利用他们已经写好的接口。
具有可插拔的注解特性(支持SpringMVC注解),可使用Feign注解和JAX-RS注解.
支持可插拔的编码器和解码器,默认集成了Ribbon做负载均衡。
是客户端组件
ruoyi系统中Log\Auth\User用了远程服务调用,用工厂模式给他的报错加了层工厂类,return错误的时候重写了以下方法。
会调用同一url中的其他服务中的接口方法(对应其他服务中的controller),在这里只是映射下路径换个名字。所以不需要实现接口。
为了方便统一管理,就都在api模块了。其实依赖了api模块,相当于自己有api模块。
调用匹配机制:服务id + url。
服务id在底层会被Ribbon解析成ip+端口,拼接成目标服务真正的uri,调用httpclient访问。
@FeignClient(contextId = "remoteUserService", value = ServiceNameConstants.FORUM_SERVICE, fallbackFactory = RemoteUserFallbackFactory.class)
public interface RemoteUserService
注解上的value是服务名
有两个主体:
- 服务提供方,让自己的Controller接口可以被其他服务复用。
- 本身RPC目的之一就是利用现成的、已经写好的接口
- 所以提供方只要像注册中心注册服务就好,其他什么都不用做
- 调用方:远程调用提供方的Controller接口,
- 操作方式是在自己Service层写一个对应远程Controller接口的Interface,然后用在方法注解编写目标接口的访问路径(uri)
- 返回值和形参一样就行,方法名不需要一样(因为是依赖服务id+uri来对应的)
要记得把服务注册到注册中心。openFeign底层的Ribbon要从注册中心获取服务列表
入口类要加入注解@EnableDiscoveryClient,consul的注解,此处案例注册中心用Consul
org.springframework.cloud.client.discovery.EnableDiscoveryClient也有这个注解
服务调用方(消费者)引入OpenFeign的依赖
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
dependency>
在服务调用方入口类上加注解@EnableFeignClients。
该注解会读取Feign相关配置、扫描@Feign注解、注册Bean等,详情见原理解析。
被调用的服务提供方入口类不用加这个注解,除非它同时也是个调用方。
用于远程调用的接口可以专门写个包或是模块统一管理,如feignClient包或者api模块。
//例1 注解默认参数为name(别名value):服务名
@FeignClient("被调用的服务id")
public interface ProductClient{
//...
}
//例2 ruoyi-cloud开源项目案例
@FeignClient(contextId = "remoteUserService", value = ServiceNameConstants.SYSTEM_SERVICE, fallbackFactory = RemoteUserFallbackFactory.class)
public interface RemoteUserService
{
/**
* 通过用户名查询用户信息
*
* @param username 用户名
* @param source 请求来源
* @return 自己封装的统一返回类型
*/
@GetMapping("/user/info/{username}")
public R<LoginUser> getUserInfo(@PathVariable("username") String username, @RequestHeader(SecurityConstants.FROM_SOURCE) String source);
/**
* 注册用户信息
*
* @param sysUser 用户信息
* @param source 请求来源
* @return 自己封装的统一返回类型
*/
@PostMapping("/user/register")
public R<Boolean> registerUserInfo(@RequestBody SysUser sysUser, @RequestHeader(SecurityConstants.FROM_SOURCE) String source);
}
该案例中,value使用封装好的常量ServiceNameConstants.SYSTEM_SERVICE,该常量被设置为服务名。
- contextid等参数干嘛的?
注入并调用
public class SysLoginService
{
@Autowired
private RemoteUserService remoteUserService;
/**
* 登录
*/
public LoginUser login(String username, String password)
{
//...
// 查询用户信息
R<LoginUser> userResult = remoteUserService.getUserInfo(username, SecurityConstants.INNER);
//...
}
}
集成了Ribbon,默认轮询负载均衡。
Get
方式传参,使用@PathVariable
、@RequestParam
注解接收请求参数。
@GetMapping(value = "/user/info/{username}")
public R<LoginUser> getUserInfo(@PathVariable("username") String username);
@RequestParam(“参数名”)
Post
方式传参,使用@RequestBody
注解接收请求参数。
@PostMapping("/operlog")
public R<Boolean> saveLog(@RequestBody SysOperLog sysOperLog);
反客为主了属于是,原来是参数从url中匹配,现在是调用时传给url,url给远程服务,远程服务再从url取
在openfeign传递参数必须加入注解(在接口声明处),不然会报参数传入过多错误。
跨模块公用的类。多个模块都要用,所以可以声明在专门的模块中统一管理,再让需要调用公共接口的模块依赖这个模块。如ruoyi-cloud中的api模块。
数组:
/test?ids=21&ids=22
@GetMapping("/test3")
String tests(@RequestParam("ids") String[] ids)
可以调用方传集合,被调用方用vo中的list接受,类似前端传数据,名字对应就行,会自动映射到vo中的list
在调用方设置请求被调用方时的超时时间
要求服务一秒内响应,否则报错
修改默认超时设置
feign.client.config.服务id.connectTimeout=5000 #配置指定服务连接超时
feign.client.config.服务id.readTimeout=5000 #配置指定服务等待超时
feign.client.config.default.connectTimeout=5000 #配置所有服务连接超时
feign.client.config.default.readTimeout=5000 #配置所有服务等待超时
Feign
的负载均衡底层用的就是Ribbon
,所以请求超时其实就只需要配置Ribbon
参数。
全局配置
# 请求处理的超时时间
ribbon:
ReadTimeout: 10000
ConnectTimeout: 10000
局部配置
# ruoyi-xxxx 为需要调用的服务名称
ruoyi-xxxx:
ribbon:
ReadTimeout: 10000
ConnectTimeout: 10000
在调试时开启详细日志
logging.level.com.包名=debug
feign为每个客户端提供了日志对象
feign.clientl.config.服务id.loggerLevel=full
全局配置
@Bean
public Logger.Level getLog()
{
return Logger.Level.FULL;
}
局部配置
import feign.Logger;
/**
* Feign 客户端配置
*
*/
@Configuration
public class FeignConfiguration
{
@Bean
Logger.Level feignLoggerLevel()
{
return Logger.Level.FULL;
}
}
// ====== 在客户端接口指定此配置 ======
/**
* 用户服务
*
* @author ruoyi
*/
@FeignClient(contextId = "remoteUserService", value = ServiceNameConstants.SYSTEM_SERVICE, fallbackFactory = RemoteUserFallbackFactory.class, configuration = FeignConfiguration.class)
public interface RemoteUserService
{
}
两台服务器建立HTTP
连接的过程涉及到多个数据包的交换,很消耗时间。采用HTTP
连接池可以节约大量的时间提示吞吐量。
Feign
的HTTP
客户端支持3种框架:HttpURLConnection
、HttpClient
、OkHttp
。
默认是采用java.net.HttpURLConnection
,每次请求都会建立、关闭连接,为了性能考虑,可以引入httpclient
、okhttp
作为底层的通信框架。
例如将Feign
的HTTP
客户端工具修改为HttpClient
。
1、添加依赖
<dependency>
<groupId>io.github.openfeigngroupId>
<artifactId>feign-httpclientartifactId>
dependency>
2、全局配置
feign:
httpclient:
# 开启httpclient
enabled: true
在可以通过实现feign.RequestInterceptor
接口在feign执行后
进行拦截,对请求头等信息进行修改。
例如项目中利用feign
拦截器将本服务的userId
、userName
、authentication
传递给下游服务
import feign.RequestInterceptor;
import feign.RequestTemplate;
/**
* feign 请求拦截器
*
*/
@Component
public class FeignRequestInterceptor implements RequestInterceptor
{
@Override
public void apply(RequestTemplate requestTemplate){
}
}