一、Feign接口转换
1、【microcloud项目】既然所有的远程接口全部定义在了“common-api”子模块之中,那么可以修改此模块的应用,加入feign相关依赖处理。
build.gradle
project(":common-api") { // 进行子模块的配置
dependencies { // 配置模块所需要的依赖库
compile("org.springframework.boot:spring-boot-starter-web") // SpringBoot依赖
compile('org.springframework.cloud:spring-cloud-starter-openfeign')
}
}
2、【common-api子模块】创建服务接口
@FeignClient(“dept.provider”) 、定义要访问的微服务实例名称
@GetMapping("/provider/dept/get/{deptno}") 、远程REST接口
package com.yootk.service;
import com.yootk.common.dto.DeptDTO;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.List;
import java.util.Map;
@FeignClient("dept.provider") // 定义要访问的微服务实例名称
public interface IDeptService { // 业务接口
/**
* 根据部门的编号获取部门的完整信息
* @param id 要查询的部门编号
* @return 编号存在则以DTO对象的形式返回部门数据,如果不存在返回null
*/
@GetMapping("/provider/dept/get/{deptno}") // 远程REST接口
public DeptDTO get(@PathVariable("deptno") long id);
/**
* 增加部门对象
* @param dto 保存要增加部门的详细数据
* @return 增加成功返回true,否则返回false
*/
@PostMapping("/provider/dept/add")
public boolean add(DeptDTO dto);
/**
* 列出所有的部门数据信息
* @return 全部数据的集合, 如果没有任何的部门数据则集合为空(size() == 0)
*/
@GetMapping("/provider/dept/list")
public List<DeptDTO> list();
/**
* 进行部门的分页数据加载操作
* @param currentPage 当前所在页
* @param lineSize 每页加载的数据行数
* @param column 模糊查询的数据列
* @param keyword 模糊查询关键字
* @return 部门集合数据以及统计数据,返回的数据项包括:
* 1、key = allDepts、value = List集合(部门的全部数据对象)
* 2、key = allRecorders、value = 总记录数;
* 3、key = allPages、value = 页数。
*/
@GetMapping("/provider/dept/split")
public Map<String, Object> split(
@RequestParam("cp") int currentPage,
@RequestParam("ls") int lineSize,
@RequestParam("col") String column,
@RequestParam("kw") String keyword);
}
3、【consumer-springboot-80 子模块】controller
package com.yootk.consumer.action;
import com.yootk.common.dto.DeptDTO;
import com.yootk.service.IDeptService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/consumer/dept/*") // 两个不同的服务路径
public class DeptConsumerAction { // 消费端Action
@Autowired // 由容器帮助用户自动实例化接口对象
private IDeptService deptService;
@GetMapping("add") // 消费端接口名称
public Object addDept(DeptDTO dto) {
return this.deptService.add(dto);
}
@GetMapping("get")
public Object get(Long deptno) {
return this.deptService.get(deptno);
}
@GetMapping("list")
public Object list() {
return this.deptService.list();
}
@GetMapping("split")
public Object split(int cp, int ls, String col, String kw) {
return this.deptService.split(cp, ls, col, kw);
}
}
4、【consumer-springboot-80 子模块】启动类
@EnableFeignClients(“com.yootk.service”) // Feign扫描包
package com.yootk.consumer;
import muyan.yootk.config.ribbon.DeptProviderRibbonConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients("com.yootk.service") // Feign扫描包
public class StartConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(StartConsumerApplication.class, args);
}
}
二、Feign转换日志
在之前已经成功的实现了Feign与远程接口之间的转换处理,但是在最终进行调用的时候并没有后台有任何的输出,实际上这些过程都是存在有与之匹配的转换日志的,这些日志默认并没有开启,可以通过自定义的方式进行配置。所有的日志处理在Feign组件包中都通过了Logger的日志处理类来定义,观察此类的具体实现源代码。
如果要想在当前的项目之中进行Feign日志转换的处理操作,那么需要定义有一个专属的Feign配置类。
1、【common-api子模块】定义一个Feign 配置类
package com.yootk.service.config;
import feign.Logger;
import org.springframework.context.annotation.Bean;
public class FeignConfig { // 定义Feign配置类
@Bean
public Logger.Level level() {
return Logger.Level.FULL; // 输出完全的日志信息
}
}
2、【common-api子模块】如果要想让Feign配置类生效,那么可以考虑修改接口的定
@FeignClient(value = “dept.provider”, configuration = FeignConfig.class) // configuration = FeignConfig.class
package com.yootk.service;
import com.yootk.common.dto.DeptDTO;
import com.yootk.service.config.FeignConfig;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.List;
import java.util.Map;
@FeignClient(value = "dept.provider", configuration = FeignConfig.class) // 定义要访问的微服务实例名称
public interface IDeptService { // 业务接口
/**
* 根据部门的编号获取部门的完整信息
* @param id 要查询的部门编号
* @return 编号存在则以DTO对象的形式返回部门数据,如果不存在返回null
*/
@GetMapping("/provider/dept/get/{deptno}") // 远程REST接口
public DeptDTO get(@PathVariable("deptno") long id);
/**
* 增加部门对象
* @param dto 保存要增加部门的详细数据
* @return 增加成功返回true,否则返回false
*/
@PostMapping("/provider/dept/add")
public boolean add(DeptDTO dto);
/**
* 列出所有的部门数据信息
* @return 全部数据的集合, 如果没有任何的部门数据则集合为空(size() == 0)
*/
@GetMapping("/provider/dept/list")
public List<DeptDTO> list();
/**
* 进行部门的分页数据加载操作
* @param currentPage 当前所在页
* @param lineSize 每页加载的数据行数
* @param column 模糊查询的数据列
* @param keyword 模糊查询关键字
* @return 部门集合数据以及统计数据,返回的数据项包括:
* 1、key = allDepts、value = List集合(部门的全部数据对象)
* 2、key = allRecorders、value = 总记录数;
* 3、key = allPages、value = 页数。
*/
@GetMapping("/provider/dept/split")
public Map<String, Object> split(
@RequestParam("cp") int currentPage,
@RequestParam("ls") int lineSize,
@RequestParam("col") String column,
@RequestParam("kw") String keyword);
}
3、【consumer-springboot-80子模块】修改application.yml 配置文件,追加Feign 转换日志输出配置项:
logging:
level:
com.yootk.service.IDeptService: DEBUG
此时重新启动消费端,随后通过 Postman随意发出一个请求,观察后台的日志信息
[IDeptService#get] ---> GET http://dept.provider/provider/dept/get/1 HTTP/1.1
[IDeptService#get] ---> END HTTP (0-byte body)
[IDeptService#get] <--- HTTP/1.1 200 (61ms)
[IDeptService#get] cache-control: no-cache, no-store, max-age=0, must-revalidate
[IDeptService#get] connection: keep-alive
[IDeptService#get] content-type: application/json
[IDeptService#get] date: Sun, 15 Aug 2021 07:43:52 GMT
[IDeptService#get] expires: 0
[IDeptService#get] keep-alive: timeout=60
[IDeptService#get] pragma: no-cache
[IDeptService#get] transfer-encoding: chunked
[IDeptService#get] x-content-type-options: nosniff
[IDeptService#get] x-frame-options: DENY
[IDeptService#get] x-xss-protection: 1; mode=block
[IDeptService#get]
[IDeptService#get] {"deptno":1,"dname":"开发部","loc":"yootk8002"}
[IDeptService#get] <--- END HTTP (50-byte body)
三、Feign连接池
每一次重复的建立请求连接都需要损耗大量的性能,最佳的做法是将已经存在的连接保存起来,供下次用户继续使用,这样可以提于处理性能。在使用Feign组件实现远程微服务调用时,全部的处理操作都是基于HTTP协议完成的,而HTTP协议是基于TCP协议开发。由于HTTP协议采用了无状态的处理形式,这样一来用户每一次发出请求都需要进行重复的连接打开与关闭操作,而这样的处理形式最终一定会导致严重的性能损耗。
在HTTP / 1.0协议采用的是全双工协议,所以为了创建可靠的连接,就需要在连接建立与连接断开时采用“三次握手”与“四次挥手”的处理机制,这样在每次进行请求和响应时都会造成大量的资源消耗,所以为了解决这样的缺陷,最佳的做法是采用持久化连接的形式来实现Socket连接复用,这样用户只需要创建一次连接,就可以实现多次的请求与响应处理.
在HTTP/1.1协议中,为了提高Socket复用性采用了多路复用的设计原则,这样可以让一个连接来为不同的用户提供服务,从而实现了服务端的服务处理性能,而在客户端如果要想实现持久化连接,则可以基于HttpClient组件提供的连接池来进行管理,以实现同一条TCР链路上的连接复用。
1、【microcloud项目】进行HTTP连接池相关依赖的引入:
ext.versions = [
httpclient : '4.5.13', // HttpClient版本号
feignHttpclient : '11.6', // FeignHttpClient版本号
]
ext.libraries = [
// 以下的配置为Feign与HttpClient有关的依赖库
'httpclient' : "org.apache.httpcomponents:httpclient:${versions.httpclient}",
'feign-httpclient' : "io.github.openfeign:feign-httpclient:${versions.feignHttpclient}"
]
project(":consumer-springboot-80") { // 消费端模块
dependencies {
implementation(project(":common-api")) // 导入公共的子模块
implementation('com.alibaba.cloud:spring-cloud-starter-alibaba-nacos-discovery') {
exclude group: 'com.alibaba.nacos', module: 'nacos-client' // 移除旧版本的Nacos依赖
}
implementation(libraries.'nacos-client') // 引入与当前的Nacos匹配的依赖库
implementation(libraries.'httpclient') // 引入httpclient组件
implementation(libraries.'feign-httpclient') // 引入feign-httpclient组件
}
}
2、【consumer-springboot-80子模块】修改application.yml 配置文件,添加HttpClient连接池配置
feign:
httpclient:
enabled: true # 启用httpclient连接池
max-connections: 200 # httpclient处理的最大连接数量
max-connections-per-route: 50 # 单个路径连接的最大数量
connection-timeout: 2000 # 超时等待
四、Feign数据压缩传输
对于当前的微服务如果要想得到较好的性能,那么首先要解决的就是数据的传输性能,虽然利用HttpClient连接池可以很好的实现连接性能的提升,但是除了连接之外还存在有传输性能的提升。为了提升数据传输的性能,往往会对数据采用压缩的方式进行传输,而在接收的时候再进行解压缩的处理操作。
所以要想提升传输性能,最佳的做法就是对传输的数据进行压缩(文本数据的压缩效果很好,基本上可以得到原始大小的40%左右),所以要想提升Feign处理性能就可以在Feign使用中进行压缩传输的配置启用。
1、【consumer-springboot-80子模块】修改applicationyml 配置文件,添加压缩配置
server: # 服务端配置
port: 80 # 这个接口可以随意,反正最终都是由前端提供服务
compression: # 启用压缩配置
enabled: true # 配置启用
mime-types: application/json,application/xml,text/html,text/xml,text/plain # 压缩类型
feign:
httpclient:
enabled: true # 启用httpclient连接池
max-connections: 200 # httpclient处理的最大连接数量
max-connections-per-route: 50 # 单个路径连接的最大数量
connection-timeout: 2000 # 超时等待
compression: # 压缩处理
request: # 请求压缩配置
enabled: true # 启用请求压缩
mime-types: application/json,application/xml,text/html,text/xml,text/plain # 压缩类型
min-request-size: 512 # 达到此阈值的时候启用压缩
response: # 响应处理
enabled: true # 响应压缩
五、Feign客户端失败回退
基于Hystrix来定义的服务提供端的失败降级处理,对于服务提供端的降级服务实际上并不方便维护,因为一个微服务提供端会有很多个服务接口,这么多的接口如果要想进行Falback方法的定义,也实在是很麻烦了,可以考虑通过Feign机制来完成。
1、【common-api子模块】创建一个Fallback服务降级处理类,对IDeptService业务接口的服务进行降级方法定义。
DeptServiceFallbackFactory
package com.yootk.service.fallback;
import com.yootk.common.dto.DeptDTO;
import com.yootk.service.IDeptService;
import feign.hystrix.FallbackFactory;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Component // 进行Bean注册
public class DeptServiceFallbackFactory implements FallbackFactory<IDeptService> {
@Override
public IDeptService create(Throwable cause) { // 定义失败回退处理
return new IDeptService() {
@Override
public DeptDTO get(long id) {
DeptDTO dto = new DeptDTO();
dto.setDeptno(id);
dto.setDname("【部门名称】" + cause.getMessage()); // 设置异常信息
dto.setLoc("【部门位置】" + cause.getMessage());
return dto;
}
@Override
public boolean add(DeptDTO dto) {
return false;
}
@Override
public List<DeptDTO> list() {
return new ArrayList<DeptDTO>();
}
@Override
public Map<String, Object> split(int currentPage, int lineSize, String column, String keyword) {
return new HashMap<>();
}
};
}
}
2、【common-api子模块】如果此时要想与IDeptService业务接口实现失败回退的匹配
那么就要修改接口定义:
package com.yootk.service;
import com.yootk.common.dto.DeptDTO;
import com.yootk.service.config.FeignConfig;
import com.yootk.service.fallback.DeptServiceFallbackFactory;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.List;
import java.util.Map;
@FeignClient(value = "dept.provider",// 定义要访问的微服务实例名称
configuration = FeignConfig.class,
fallbackFactory = DeptServiceFallbackFactory.class) // 部门降级配置
public interface IDeptService { // 业务接口
/**
* 根据部门的编号获取部门的完整信息
* @param id 要查询的部门编号
* @return 编号存在则以DTO对象的形式返回部门数据,如果不存在返回null
*/
@GetMapping("/provider/dept/get/{deptno}") // 远程REST接口
public DeptDTO get(@PathVariable("deptno") long id);
/**
* 增加部门对象
* @param dto 保存要增加部门的详细数据
* @return 增加成功返回true,否则返回false
*/
@PostMapping("/provider/dept/add")
public boolean add(DeptDTO dto);
/**
* 列出所有的部门数据信息
* @return 全部数据的集合, 如果没有任何的部门数据则集合为空(size() == 0)
*/
@GetMapping("/provider/dept/list")
public List<DeptDTO> list();
/**
* 进行部门的分页数据加载操作
* @param currentPage 当前所在页
* @param lineSize 每页加载的数据行数
* @param column 模糊查询的数据列
* @param keyword 模糊查询关键字
* @return 部门集合数据以及统计数据,返回的数据项包括:
* 1、key = allDepts、value = List集合(部门的全部数据对象)
* 2、key = allRecorders、value = 总记录数;
* 3、key = allPages、value = 页数。
*/
@GetMapping("/provider/dept/split")
public Map<String, Object> split(
@RequestParam("cp") int currentPage,
@RequestParam("ls") int lineSize,
@RequestParam("col") String column,
@RequestParam("kw") String keyword);
}
3、【consumer-springboot-80子模块】修改application.yml配置文件进行Feign失败回退启用feign :
feign:
hystrix: # Hystrix配置
enabled: true # 启用熔断与失败回退
4、【consumer-springboot-80子模块】由于Feign 失败回退采用了“@Component”注解,所以要想让其生效,则必须修改启动类,追加包的扫描配置
package com.yootk.consumer;
import muyan.yootk.config.ribbon.DeptProviderRibbonConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.ComponentScan;
@SpringBootApplication
@EnableDiscoveryClient
@RibbonClient
@RibbonClient(name = "dept.provider", configuration = DeptProviderRibbonConfig.class) // 自定义Ribbon配置
@ComponentScan({"com.yootk.service", "com.yootk.consumer"})
@EnableFeignClients("com.yootk.service") // Feign扫描包
public class StartConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(StartConsumerApplication.class, args);
}
}