Open Feign学习
什么是Feign
? Feign 是Spring Cloud Netflix 组件中的一量级Restful 的 HTTP 服务客户端,实现了负载均衡和 Rest 调用的开源框架,封装了Ribbon 和RestTemplate , 实现了WebService 的面向接口编程,进一步降低了项目的耦合度。后期不在维护后 Cloud 就出了OpenFeign,在之前的框架中在服务调用中使用熔断hystrix 新的alibaba 框架使用了Sentinel 来替换之前的服务熔断
什么是服务调用
? 顾名思义,就是服务之间的接口互相调用,在微服务架构中很多功能都需要调用多个服务才能完成某一项功能。
? 个人理解,在微服务概念中,服务分布在不同的环境下,我们可以将所有的服务都注册到注册中心,然后利用我们的Http请求的方式对某一个服务进行请求。
为什么要使用Feign
? Feign 旨在使编写 JAVA HTTP 客户端变得更加简单,Feign 简化了RestTemplate 代码,实现了Ribbon 负载均衡,使代码变得更加简洁,也少了客户端调用的代码,使用 Feign 实现负载均衡是首选方案,只需要你创建一个接口,然后在上面添加注解即可。 Feign 是声明式服务调用组件,其核心就是:像调用本地方法一样调用远程方法,无感知远程 HTTP 请求。让开发者调用远程接口就跟调用本地方法一样的体验,开发者完全无感知这是远程方法,无需关注与远程的交互细节,更无需关注分布式环境开发。
Feign vs OpenFeign
Feign 内置了Ribbon ,用来做客户端负载均衡调用服务注册中心的服务。 Feign 支持的注解和用法参考官方文档:https://github.com/OpenFeign/feign 官方文档,使用 Feign 的注解定义接口,然后调用这个接口,就可以调用服务注册中心的服务。
Feign 本身并不支持Spring MVC 的注解,它有一套自己的注解,为了更方便的使用Spring Cloud 孵化了OpenFeign 。并且支持了Spring MVC 的注解,如@RequestMapping ,@PathVariable 等等。 OpenFeign 的@FeignClient 可以解析Spring MVC 的@RequestMapping 注解下的接口,并通过动态代理方式产生实现类,实现类中做负载均衡调用服务。
如何使用
-
添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
-
新建服务调用接口 主要是用来调用其他服务的接口 package com.ruoyi.system.api;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import com.ruoyi.common.core.constant.ServiceNameConstants;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.system.api.factory.RemoteUserFallbackFactory;
import com.ruoyi.system.api.model.LoginUser;
@FeignClient(contextId = "remoteUserService", value = ServiceNameConstants.SYSTEM_SERVICE, fallbackFactory = RemoteUserFallbackFactory.class)
public interface RemoteUserService
{
@GetMapping(value = "/user/info/{username}")
public R<LoginUser> getUserInfo(@PathVariable("username") String username);
}
-
新建RemoteUserFallbackFactory.java 降级实现 package com.ruoyi.system.api.factory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.system.api.RemoteUserService;
import com.ruoyi.system.api.model.LoginUser;
import feign.hystrix.FallbackFactory;
@Component
public class RemoteUserFallbackFactory implements FallbackFactory<RemoteUserService>
{
private static final Logger log = LoggerFactory.getLogger(RemoteUserFallbackFactory.class);
@Override
public RemoteUserService create(Throwable throwable)
{
log.error("用户服务调用失败:{}", throwable.getMessage());
return new RemoteUserService()
{
@Override
public R<LoginUser> getUserInfo(String username)
{
return R.fail("获取用户失败:" + throwable.getMessage());
}
};
}
}
-
调用者消费者TestUserController.java 新增info 查询用户方法 import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class TestUserController
{
@Autowired
private RemoteUserService remoteUserService;
@GetMapping("/user/{username}")
public Object info(@PathVariable("username") String username)
{
return remoteUserService.getUserInfo(username);
}
}
-
启动类添加@EnableRyFeignClients 注解,默认的@EnableRyFeignClients 扫描范围com.ruoyi 。 这个注解是若依大大根据之前的**@EnableFeignCilents** 进行封装的 -
启动后访问http://localhost:8888/user/admin ,返回正确数据表示测试通过。 -
提示 目前已经存在ruoyi-api-system 系统接口模块,用于服务调用
负载均衡
Feign 默认集成了Ribbon ,Nacos 也很好的兼容了Feign ,默认实现了负载均衡的效果。
请求传参
Get 方式传参,使用@PathVariable 、@RequestParam 注解接收请求参数
@GetMapping(value = "/user/info/{username}")
public R<LoginUser> getUserInfo(@PathVariable("username") String username);
Post 方式传参,使用@RequestBody 注解接收请求参数。
@PostMapping("/operlog")
public R<Boolean> saveLog(@RequestBody SysOperLog sysOperLog);
性能优化
Gzip压缩
gzip 是一种数据格式,采用deflate 算法压缩数据。gzip 大约可以帮我们减少70% 以上的文件大小。
全局配置
server:
compression:
enabled: true
mime-types: text/html,text/xml,text/plain,application/xml,application/json
局部配置
feign:
compression:
request:
enabled: true
mime-types: text/xml,application/xml,application/json
min-request-size: 2048
response:
enabled: true
提示
开启压缩可以有效节约网络资源,但是会增加CPU压力,建议把最小压缩的文档大小适度调大一点。
Http连接池
两台服务器建立HTTP 连接的过程涉及到多个数据包的交换,很消耗时间。采用HTTP 连接池可以节约大量的时间提示吞吐量。
Feign 的HTTP 客户端支持3种框架:HttpURLConnection 、HttpClient 、OkHttp 。
默认是采用java.net.HttpURLConnection ,每次请求都会建立、关闭连接,为了性能考虑,可以引入httpclient 、okhttp 作为底层的通信框架。
例如将Feign 的HTTP 客户端工具修改为HttpClient 。
1、添加依赖
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
</dependency>
2、全局配置
feign:
httpclient:
enabled: true
3、测试验证
@GetMapping("/user/pojo")
public Object selectUser(SysUser user);
@Autowired
private RemoteUserService remoteUserService;
@GetMapping("/user/pojo")
public Object UserInfo(SysUser user)
{
return remoteUserService.selectUser(user);
}
@GetMapping("/pojo")
public R<SysUser> selectUser(@RequestBody SysUser user)
{
return R.ok(userService.selectUserByUserName(user.getUserName()));
}
4、启动后访问http://localhost:8888/user/pojo?userName=ry ,返回正确数据表示测试通过。
日志配置
浏览器发起的请求可以通过F12 查看请求和响应信息。如果想看微服务中每个接口我们可以使用日志配置方式进行查看详细信息。
配置文件logback.xml 设置com.ruoyi 日志级别为debug
全局配置
@Bean
public Logger.Level getLog()
{
return Logger.Level.FULL;
}
局部配置
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import feign.Logger;
@Configuration
public class FeignConfiguration
{
@Bean
Logger.Level feignLoggerLevel()
{
return Logger.Level.FULL;
}
}
@FeignClient(contextId = "remoteUserService", value = ServiceNameConstants.SYSTEM_SERVICE, fallbackFactory = RemoteUserFallbackFactory.class, configuration = FeignConfiguration.class)
public interface RemoteUserService
{
}
请求超时
Feign 的负载均衡底层用的就是Ribbon ,所以请求超时其实就只需要配置Ribbon 参数。
全局配置 一般都是在全局里面进行修改 局部的没怎么用过
ribbon:
ReadTimeout: 10000
ConnectTimeout: 10000
局部配置
ruoyi-xxxx:
ribbon:
ReadTimeout: 10000
ConnectTimeout: 10000
异常配置
1、配置开启 基本不用了
使用sentinel 进行熔断了 基本都是一样的
feign:
hystrix:
enabled: true
2、FeignClient 接口服务加入fallbackFactory
@FeignClient(fallbackFactory = RemoteUserFallbackFactory.class)
3、添加接口实现异常类
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import feign.hystrix.FallbackFactory;
@Component
public class RemoteUserFallbackFactory implements FallbackFactory<RemoteUserService>
{
private static final Logger log = LoggerFactory.getLogger(RemoteUserFallbackFactory.class);
@Override
public RemoteUserService create(Throwable throwable)
{
log.error("用户服务调用失败:{}", throwable.getMessage());
return new RemoteUserService()
{
@Override
public Object getUserInfo(String username)
{
return "获取用户失败:" + throwable.getMessage();
}
};
}
}
请求拦截器
在微服务应用中,通过feign 的方式实现http 的调用,可以通过实现feign.RequestInterceptor 接口在feign 执行后进行拦截,对请求头等信息进行修改。
例如项目中利用feign 拦截器将本服务的userId 、userName 、authentication 传递给下游服务
package com.ruoyi.common.security.feign;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Component;
import com.ruoyi.common.core.constant.CacheConstants;
import com.ruoyi.common.core.utils.ServletUtils;
import com.ruoyi.common.core.utils.StringUtils;
import feign.RequestInterceptor;
import feign.RequestTemplate;
@Component
public class FeignRequestInterceptor implements RequestInterceptor
{
@Override
public void apply(RequestTemplate requestTemplate)
{
HttpServletRequest httpServletRequest = ServletUtils.getRequest();
if (StringUtils.isNotNull(httpServletRequest))
{
Map<String, String> headers = ServletUtils.getHeaders(httpServletRequest);
String userId = headers.get(CacheConstants.DETAILS_USER_ID);
if (StringUtils.isNotEmpty(userId))
{
requestTemplate.header(CacheConstants.DETAILS_USER_ID, userId);
}
String userName = headers.get(CacheConstants.DETAILS_USERNAME);
if (StringUtils.isNotEmpty(userName))
{
requestTemplate.header(CacheConstants.DETAILS_USERNAME, userName);
}
String authentication = headers.get(CacheConstants.AUTHORIZATION_HEADER);
if (StringUtils.isNotEmpty(authentication))
{
requestTemplate.header(CacheConstants.AUTHORIZATION_HEADER, authentication);
}
}
}
}
|