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

网友投稿 331 2022-12-02


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

背景

最近项目中需要上传视频文件,由于视频文件可能会比较大,但是我们应用服务器tomcat设置单次只支持的100M,因此决定开发一个分片上传接口。

把大文件分成若干个小文件上传。所有文件上传完成后通过唯一标示进行合并文件。

我们的开发人员很快完成了开发,并在单元测试中表现无误。上传代码到测试环境,喔嚯!!!出错了。

经过一段时间的辛苦排查终于发现问题,测试环境多实例,分片上传的接口会被路由到不同的实例,导致上传后的分片文件在不同的机器,那么也就无法被合并。

知道了原因就好解决,经过一系列的过程最终决定修改网关把uuid相同的请求路由到相同的实例上,这样就不会出错了!

准备

由于是公司代码不方便透露,现使用本地测试代码。

准备:Eureka注册中心,Gateway网关,测试微服务

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

gateway版本

Greenwich.SR2

2.1.6.RELEASE

此处就说下我网关的配置。

#网关名

spring.cloud.gateway.routes[0].id=route-my-service-id

#网关uri,lb代表负载均衡,后面是服务名,必须要和微服务名一致,不能错,错了肯定不能路由

spring.cloud.gateway.rhttp://outes[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 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 iChooseRuleIterator = chooseRules.iterator();

while (iChooseRuleIterator.hasNext()){

IChooseRule chooseRule = iChooseRuleIterator.nexyJziKakt();

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 instances = discoveryClient.getInstances(instancesId);

MultiValueMap 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取模路由到相同的机器上了,当然这样还是存在问题,比如多添加一个实例,或者挂了一个实例,挂之前有一个请求yJziKak到A,挂之后可能到B,因为此时取模就不同了,还是会到不同请求。但是这种情况很少发生,所以暂时不考虑。

或许有hash槽的方式可以解决一点问题,后续研究!!!!!


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:Java软件生产监控工具Btrace使用方法详解
下一篇:Java基于Guava Retrying实现重试功能
相关文章

 发表评论

暂时没有评论,来抢沙发吧~