Java使用Gateway自定义负载均衡过滤器

作者:爱唱歌的Coder 时间:2022-06-27 03:55:09 

背景

最近项目中需要上传视频文件,由于视频文件可能会比较大,但是我们应用服务器tomcat设置单次只支持的100M,因此决定开发一个分片上传接口。
把大文件分成若干个小文件上传。所有文件上传完成后通过唯一标示进行合并文件。
我们的开发人员很快完成了开发,并在单元测试中表现无误。上传代码到测试环境,喔嚯!!!出错了。
经过一段时间的辛苦排查终于发现问题,测试环境多实例,分片上传的接口会被路由到不同的实例,导致上传后的分片文件在不同的机器,那么也就无法被合并。
知道了原因就好解决,经过一系列的过程最终决定修改网关把uuid相同的请求路由到相同的实例上,这样就不会出错了!

准备

由于是公司代码不方便透露,现使用本地测试代码。
准备:Eureka注册中心,Gateway网关,测试微服务

启动后服务如下两个测试的微服务,一个网关服务

Java使用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)。

Java使用Gateway自定义负载均衡过滤器

最终被路由到这个地方,负载均衡使用的是ribbon,关于ribbon暂时不讨论。

Java使用Gateway自定义负载均衡过滤器

重点在这儿,通过现在的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

标签:Java,负载均衡过滤器
0
投稿

猜你喜欢

  • Jmeter如何添加循环控制器

    2021-06-26 20:09:52
  • Android使用手势监听器GestureDetector遇到的不响应问题

    2022-02-27 23:52:40
  • Java Fluent Mybatis 分页查询与sql日志输出详解流程篇

    2023-01-06 05:35:43
  • c#图像截取实例

    2022-02-21 07:49:58
  • 详解Android开发中Fragment的使用

    2023-05-02 20:32:25
  • C#调用带结构体指针Dll的方法

    2022-06-14 19:55:32
  • Java聊天室之实现一个服务器与多个客户端通信

    2021-06-03 11:34:45
  • JAVA中实现链式操作(方法链)的简单例子

    2022-12-16 00:54:50
  • C#操作XML文件步骤

    2021-11-04 21:51:44
  • 深入解析Java接口(interface)的使用

    2022-05-07 07:50:51
  • C# 泛型接口的抗变和协变

    2022-05-07 09:34:31
  • 使用JPA进行CriteriaQuery进行查询的注意事项

    2023-07-03 23:36:05
  • MybatisPlus分页排序查询字段带有下划线的坑及解决

    2022-08-16 22:26:28
  • Unity shader实现遮罩效果

    2023-06-28 10:20:09
  • Spring Boot 整合mybatis 与 swagger2

    2022-05-01 06:32:17
  • Java使用BigDecimal进行运算封装的实际案例

    2023-06-20 02:22:26
  • 使用 Java8 实现观察者模式的方法(下)

    2021-08-03 04:21:37
  • Java实现简单的迷宫游戏详解

    2022-11-22 05:00:52
  • SpringBoot2.1.4中的错误处理机制

    2023-11-06 02:48:47
  • Minio与SpringBoot使用okhttp3问题解决

    2021-06-25 19:17:08
  • asp之家 软件编程 m.aspxhome.com