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 {@Beanpublic StringToZonedDateTimeConverter stringToZonedDateTimeConverter() {return new StringToZonedDateTimeConverter();}@Beanpublic RouteLocatorBuilder routeLocatorBuilder(ConfigurableApplicationContext context) {return new RouteLocatorBuilder(context);}@Bean@ConditionalOnMissingBeanpublic 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 **/@Configurationpublic 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 自定義負載均衡過濾器內容請搜索好吧啦網以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持好吧啦網!
相關文章: