关键字:微服务, Spring Cloud, OpenFeign, 服务调用, RPC

Feign与OpenFeign简介

Feign(NetFlix)---->维护---->OpenFeign(开源社区维护)

Feign本身并不支持Spring MVC的注解,它有一套自己的注解。
后来Spring Cloud孵化了OpenFeign,为了更方便的使用,使其支持了Spring MVC的注解,如@RequestMapping@PathVariable等等。
并支持使用 Spring Web 中默认使用的相同 HttpMessageConverters。
OpenFeign@FeignClient可以解析Spring MVC@RequestMapping注解下的接口,并通过动态代理方式产生实现类,实现类中做负载均衡调用服务。

  • 代理类中做负载均衡和Ribbon是怎么统一的?

后面都是讨论OpenFegin,简称Fegin了。Feign正统在OpenFegin! doge

OpenFegin是一个声明式的伪http客户端

  • 声明式?这是官网说的么?什么意思?
  • 伪是什么意思?

使用简单:写一个接口,加一个注解,调用服务方代码。
被调用方几乎什么都不用做,只要到注册中心登记即可。本身就是要重复利用他们已经写好的接口。

  • 因为是http调用,所以要调用的是服务提供方负责处理网络请求的controller层。
  • Q: 那Dubbo等RPC框架是调用其他服务的什么层?
    • A: Dubbo使用自己的RPC协议,服务提供方可以用dubbo的@Service注解标记自己的Service层,而调用方用@DubboReference来引用他们。

具有可插拔的注解特性(支持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来对应的)
  • 可不可以说生产者消费者?生产者消费者模型到底指什么?
  • Q:为什么是interface?
    • A: 因为底层使用了JDK动态代理,而JDK动态代理的原理是实现interface

要记得把服务注册到注册中心。openFeign底层的Ribbon要从注册中心获取服务列表
入口类要加入注解@EnableDiscoveryClient,consul的注解,此处案例注册中心用Consul
org.springframework.cloud.client.discovery.EnableDiscoveryClient也有这个注解

  • 这个注解什么用?nacos也可以使用
  • ribbon不能单独?feign呢?
  • 似乎,,可以用uri的方式调用,,那样的话单独应该可以吧

服务调用方(消费者)引入OpenFeign的依赖

<!-- SpringCloud Openfeign -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</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	#配置所有服务等待超时

ribbon超时设置

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连接的过程涉及到多个数据包的交换,很消耗时间。采用HTTP连接池可以节约大量的时间提示吞吐量。

FeignHTTP客户端支持3种框架:HttpURLConnectionHttpClientOkHttp

默认是采用java.net.HttpURLConnection,每次请求都会建立、关闭连接,为了性能考虑,可以引入httpclientokhttp作为底层的通信框架。

例如将FeignHTTP客户端工具修改为HttpClient

1、添加依赖

<!-- feign httpclient -->
<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-httpclient</artifactId>
</dependency>

2、全局配置

feign:
  httpclient:
    # 开启httpclient
    enabled: true

请求拦截器

在可以通过实现feign.RequestInterceptor接口在feign执行后进行拦截,对请求头等信息进行修改。

例如项目中利用feign拦截器将本服务的userIduserNameauthentication传递给下游服务

import feign.RequestInterceptor;
import feign.RequestTemplate;

/**
 * feign 请求拦截器
 * 
 */
@Component
public class FeignRequestInterceptor implements RequestInterceptor
{
    @Override
    public void apply(RequestTemplate requestTemplate){
    }
}
Logo

快速构建 Web 应用程序

更多推荐