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

 更新时间:2020年07月06日 10:13:16   作者:爱唱歌的Coder  
这篇文章主要介绍了Java使用Gateway自定义负载均衡过滤器,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

背景

最近项目中需要上传视频文件,由于视频文件可能会比较大,但是我们应用服务器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槽的方式可以解决一点问题,后续研究!!!!!

到此这篇关于Java使用Gateway自定义负载均衡过滤器的文章就介绍到这了,更多相关Java 自定义负载均衡过滤器内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • java音频播放示例分享(java如何播放音频)

    java音频播放示例分享(java如何播放音频)

    java如何播放音频?下面的代码就介绍了java音频播放示例,需要的朋友可以参考下
    2014-04-04
  • 面试JAVA时,问到spring该怎么回答

    面试JAVA时,问到spring该怎么回答

    这篇文章主要介绍了Spring面试资料,学Java的小伙伴都知道Spring是面试的必问环节,看完了一天就可掌握数据结构和算法的面试题,快来看看吧
    2021-08-08
  • java中CompletableFuture异步执行方法

    java中CompletableFuture异步执行方法

    本文主要介绍了java中CompletableFuture异步执行方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-06-06
  • Java堆空间爆满导致宕机的问题分析及解决

    Java堆空间爆满导致宕机的问题分析及解决

    团队有一个服务,一直运行的好好的,突然访问异常了,先是请求超时,然后直接无法访问,本文将给大家介绍Java堆空间爆满导致宕机的问题分析及解决,需要的朋友可以参考下
    2024-02-02
  • MyBatis代码自动生成器Mybatis-Generator的使用详解

    MyBatis代码自动生成器Mybatis-Generator的使用详解

    本文详细介绍如何在SpringBoot项目中使用MyBatis-Generator进行代码生成,包括配置文件的添加、POM依赖配置、运行配置等步骤,通过自动生成代码,可以简化MyBatis的繁琐配置和SQL编写,提高开发效率,注意要考虑MySQL版本兼容性,以及确保路径配置正确
    2024-10-10
  • Spring data JPA只查询部分字段问题及解决

    Spring data JPA只查询部分字段问题及解决

    这篇文章主要介绍了Spring data JPA只查询部分字段问题及解决,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-08-08
  • Java基础:彻底搞懂java多线程

    Java基础:彻底搞懂java多线程

    篇文章主要介绍了Java多线程的相关资料,帮助大家更好的理解和学习Java线程相关知识,感兴趣的朋友可以了解下,希望能给你带来帮助
    2021-08-08
  • 解决springboot druid数据库连接池连接失败后一直重连问题

    解决springboot druid数据库连接池连接失败后一直重连问题

    这篇文章主要介绍了解决springboot druid数据库连接池连接失败后一直重连问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-11-11
  • PowerJob分布式任务调度源码流程解读

    PowerJob分布式任务调度源码流程解读

    这篇文章主要为大家介绍了PowerJob分布式任务调度源码流程解读,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2024-02-02
  • Java中的静态代码块与构造代码块详解

    Java中的静态代码块与构造代码块详解

    静态代码块在JVM加载类时执行,仅执行一次,用于初始化静态变量或执行一次性设置,构造代码块在每次创建对象时执行,用于实例变量的初始化或执行创建对象时的公共逻辑,静态代码块按定义顺序执行,构造代码块在构造方法前执行
    2024-11-11

最新评论