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

背景

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

准备

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

启动后服务如下两个测试的微服务,一个网关服务
使用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)。
使用Gateway自定义负载均衡过滤器
最终被路由到这个地方,负载均衡使用的是ribbon,关于ribbon暂时不讨论。
使用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槽的方式可以解决一点问题,后续研究!!!!!

不对的地方多多指正!!!!!!!!!!!!!

区块链毕设网(www.qklbishe.com)全网最靠谱的原创区块链毕设代做网站
部分资料来自网络,侵权联系删除!
资源收费仅为搬运整理打赏费用,用户自愿支付 !
qklbishe.com区块链毕设代做网专注|以太坊fabric-计算机|java|毕业设计|代做平台 » 使用Gateway自定义负载均衡过滤器

提供最优质的资源集合

立即查看 了解详情