Java使用Gateway自定义负载均衡过滤器
作者:爱唱歌的Coder 发布时间:2022-06-27 03:55:09
背景
最近项目中需要上传视频文件,由于视频文件可能会比较大,但是我们应用服务器tomcat设置单次只支持的100M,因此决定开发一个分片上传接口。
把大文件分成若干个小文件上传。所有文件上传完成后通过唯一标示进行合并文件。
我们的开发人员很快完成了开发,并在单元测试中表现无误。上传代码到测试环境,喔嚯!!!出错了。
经过一段时间的辛苦排查终于发现问题,测试环境多实例,分片上传的接口会被路由到不同的实例,导致上传后的分片文件在不同的机器,那么也就无法被合并。
知道了原因就好解决,经过一系列的过程最终决定修改网关把uuid相同的请求路由到相同的实例上,这样就不会出错了!
准备
由于是公司代码不方便透露,现使用本地测试代码。
准备:Eureka注册中心,Gateway网关,测试微服务
启动后服务如下两个测试的微服务,一个网关服务
gateway版本
<spring-cloud.version>Greenwich.SR2</spring-cloud.version>
<spring-boot.version>2.1.6.RELEASE</spring-boot.version>
此处就说下我网关的配置。
#网关名
spring.cloud.gateway.routes[0].id=route-my-service-id
#网关uri,lb代表负载均衡,后面是服务名,必须要和微服务名一致,不能错,错了肯定不能路由
spring.cloud.gateway.routes[0].uri=lb://my-service-id
#断言,配置的路径
spring.cloud.gateway.routes[0].predicates[0]=Path=/my-service-id/v3/**
#截取uri前面两个位置的
spring.cloud.gateway.routes[0].filters[0]=StripPrefix=2
分析
想要修改路由就要知道gateway是如何把我们的请求路由到各个微服务的实例上的。
gateway其实无非就是不同的过滤器,然后对请求进行处理,和zuul类似。gateway自带了很多过滤器。过滤器分为两种:
1、GlobalFilter 。顾名思义,全局过滤器,所有请求都会走的过滤器。常见的自带过滤器LoadBalancerClientFilter(负载均衡过滤器,后面我们就是修改这个地方)。
2、GatewayFilter。网关过滤器,该过滤器可以指定过滤的条件,只有达到了条件的才进入该过滤器。
如果想知道自带有哪些配置,我们可以查看gateway的自动注入类GatewayAutoConfiguration。
/**
* @author Spencer Gibb
*/
@Configuration
@ConditionalOnProperty(name = "spring.cloud.gateway.enabled", matchIfMissing = true)
@EnableConfigurationProperties
@AutoConfigureBefore({ HttpHandlerAutoConfiguration.class,
WebFluxAutoConfiguration.class })
@AutoConfigureAfter({ GatewayLoadBalancerClientAutoConfiguration.class,
GatewayClassPathWarningAutoConfiguration.class })
@ConditionalOnClass(DispatcherHandler.class)
public class GatewayAutoConfiguration {
@Bean
public StringToZonedDateTimeConverter stringToZonedDateTimeConverter() {
return new StringToZonedDateTimeConverter();
}
@Bean
public RouteLocatorBuilder routeLocatorBuilder(
ConfigurableApplicationContext context) {
return new RouteLocatorBuilder(context);
}
@Bean
@ConditionalOnMissingBean
public PropertiesRouteDefinitionLocator propertiesRouteDefinitionLocator(
GatewayProperties properties) {
return new PropertiesRouteDefinitionLocator(properties);
}
省略.......
然后查看负载均衡配置。
GatewayLoadBalancerClientAutoConfiguration
/**
* @author Spencer Gibb
*/
@Configuration
@ConditionalOnClass({ LoadBalancerClient.class, RibbonAutoConfiguration.class,
DispatcherHandler.class })
@AutoConfigureAfter(RibbonAutoConfiguration.class)
@EnableConfigurationProperties(LoadBalancerProperties.class)
public class GatewayLoadBalancerClientAutoConfiguration {
// GlobalFilter beans
//负载均衡
@Bean
@ConditionalOnBean(LoadBalancerClient.class)
@ConditionalOnMissingBean(LoadBalancerClientFilter.class)
public LoadBalancerClientFilter loadBalancerClientFilter(LoadBalancerClient client,
LoadBalancerProperties properties) {
return new LoadBalancerClientFilter(client, properties);
}
}
进入LoadBalancerClientFilter,其实就是一个GlobalFilter。
调试代码试一试呢?发现果然要走。(此处图片中应该是my-service-id)。
最终被路由到这个地方,负载均衡使用的是ribbon,关于ribbon暂时不讨论。
重点在这儿,通过现在的uri选择到具体的uri。而这个方法恰恰是一个protected方法,我们可以重写该方法加上我们自己的业务逻辑。
protected ServiceInstance choose(ServerWebExchange exchange) {
return loadBalancer.choose(
((URI) exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR)).getHost());
}
实现
重写LoadBalancerClientFilter中的choose方法,实现自定义逻辑
/**
* @Description 自定义负载均衡
* @Author Singh
* @Date 2020-07-02 10:36
* @Version
**/
public class CustomLoadBalancerClientFilter extends LoadBalancerClientFilter implements BeanPostProcessor {
private final DiscoveryClient discoveryClient;
private final List<IChooseRule> chooseRules;
public CustomLoadBalancerClientFilter(LoadBalancerClient loadBalancer,
LoadBalancerProperties properties,
DiscoveryClient discoveryClient) {
super(loadBalancer, properties);
this.discoveryClient = discoveryClient;
this.chooseRules = new ArrayList<>();
chooseRules.add(new EngineeringChooseRule());
}
protected ServiceInstance choose(ServerWebExchange exchange) {
if(!CollectionUtils.isEmpty(chooseRules)){
Iterator<IChooseRule> iChooseRuleIterator = chooseRules.iterator();
while (iChooseRuleIterator.hasNext()){
IChooseRule chooseRule = iChooseRuleIterator.next();
ServiceInstance choose = chooseRule.choose(exchange,discoveryClient);
if(choose != null){
return choose;
}
}
}
return loadBalancer.choose(
((URI) exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR)).getHost());
}
}
定义通用选择实例规则
/**
* @Description 自定义选择实例规则
* @Author Singh
* @Date 2020-07-02 11:03
* @Version
**/
public interface IChooseRule {
/**
* 返回null那么使用gateway默认的负载均衡策略
* @param exchange
* @param discoveryClient
* @return
*/
ServiceInstance choose(ServerWebExchange exchange, DiscoveryClient discoveryClient);
}
实现自定义路由策略
/**
* @Description 微服务负载均衡策略
* @Author Singh
* @Date 2020-07-02 11:10
* @Version
**/
public class EngineeringChooseRule implements IChooseRule {
@Override
public ServiceInstance choose(ServerWebExchange exchange, DiscoveryClient discoveryClient) {
URI originalUrl = (URI) exchange.getAttributes().get(GATEWAY_REQUEST_URL_ATTR);
String instancesId = originalUrl.getHost();
if(instancesId.equals("my-service-id")){
if(originalUrl.getPath().contains("/files/upload")){
try{
List<ServiceInstance> instances = discoveryClient.getInstances(instancesId);
MultiValueMap<String, String> queryParams = exchange.getRequest().getQueryParams();
String uuid = queryParams.get("uuid").get(0);
int hash = uuid.hashCode() >>> 16 ;
int index = hash % instances.size();
return instances.get(index);
}catch (Exception e){
//do nothing
}
}
}
return null;
}
}
最后注入自定义负载均衡过滤器。
/**
* @Description
* @Author Singh
* @Date 2020-07-01 17:57
* @Version
**/
@Configuration
public class GetawayConfig {
// @Bean
// public RouteLocator routeLocator(RouteLocatorBuilder builder) {
// //lb://hjhn-engineering/files/upload
// return builder.routes()
// .route(r ->r.path("/**").filters(
// f -> f.stripPrefix(2).filters(new EngineeringGatewayFilter())
// ).uri("lb://hjhn-engineering")
// ) .build();
// }
@Bean
public LoadBalancerClientFilter loadBalancerClientFilter(LoadBalancerClient client,
LoadBalancerProperties properties,
DiscoveryClient discoveryClient) {
return new CustomLoadBalancerClientFilter(client, properties,discoveryClient);
}
}
如此,相同uuid的请求就可以通过hash取模路由到相同的机器上了,当然这样还是存在问题,比如多添加一个实例,或者挂了一个实例,挂之前有一个请求到A,挂之后可能到B,因为此时取模就不同了,还是会到不同请求。但是这种情况很少发生,所以暂时不考虑。
或许有hash槽的方式可以解决一点问题,后续研究!!!!!
来源:https://blog.csdn.net/m0_37954663/article/details/107102980


猜你喜欢
- Flyweight定义:避免大量拥有相同内容的小类的开销(如耗费内存),使大家共享一个类(元类)。为什么使用共享模式/享元模式面向对象语言的
- 1、前言WorkManager 是适合用于持久性工作的推荐解决方案。如果工作始终要通过应用重启和系统重新启动来调度,便是持久性的工
- 一. Dispatchers.IO1.Dispatchers.IO在协程中,当需要执行IO任务时,会在上下文中指定Dispatchers.I
- 目录前言闲扯使用技术所需知识储备实现步骤总结前言现代互联网项目中,很多场景下都需要使用一种叫做验证码的技术,常用的有图片验证码,滑块验证码,
- 本文为大家整理了C#图片切割、图片压缩、缩略图生成的实现代码,大家可以收藏,方便以后使用,具体内容如下/// 图片切割函数 /// <
- 最近总是有人来和我说我以前写的一个小app无法正常获取数据~Android简易版天气预报app 今天就又运行了下来查找问题,发现或许是接口有
- 本文实例讲述了退出Android程序时清除所有activity的方法。分享给大家供大家参考,具体如下:在一个项目中,要退出android程序
- 本文实例为大家分享了Android滑动组件悬浮固定在顶部效果的具体代码,供大家参考,具体内容如下要想实现的效果是如下:场景:有些时候是内容中
- Warning:这是《Java 程序员进阶之路》专栏的第 55 篇。回来后小二找到了我,于是我就写下了这篇文章丢给他,并严厉地告诉他:再搞不
- 单行文本的输入存在严重的缺陷,也不适合实际的运用,本节通过一个无功能的记事本来介绍可以进行多行输入的JTextArea:JTextArea(
- autoMapping和autoMappingBehavior的区别autoMappingBehaviormybatis核心配置文件中set
- 1.过滤器 (Filter)过滤器的配置比较简单,直接实现Filter 接口即可,也可以通过@WebFilter注解实现对特定URL拦截,看
- 从什么是IOC开始?Spring——春天,Java编程世界的春天是由一位音乐家—
- 代码编译运行环境:VS2017+Debug+Win32按照参数形式的不同,C++应该有三种函数调用方式:传值调用、引用调用和指针调用。对于基
- 一.hutool工具摘抄一段hutool工具的简介:Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,
- 主要介绍springboot项目中配置文件的加密前言为了保证服务器相关信息的保密,一般会采用加密的方式进行对配置文件原文的加密,今天介绍下s
- Service的生命周期 (适用于2.1及以上)1. 被startService的无论是否有任何活动绑定到该Service,都在后台运行。o
- 用C#如何生成二维码,我们可以通过现有的第三方dll直接来实现,下面列出几种不同的生成方法:1):通过QrCodeNet(Gma.QrCod
- 前言当指执行插入排序、希尔排序、归并排序等算法时,比较两个对象“大小”的比较操作。我们很容易理解整型的 i>j 这样的比较方式,但当我
- 成功本文通过java语言实现ECC+AES混合加密。ECC加密算法具有密钥分配与管理简单,安全强度高等优点,AES的加密算法具有速度快,强度