Java使用Gateway自定義負載均衡過濾器
背景
最近項目中需要上傳視頻文件,由于視頻文件可能會比較大,但是我們應用服務器tomcat設置單次只支持的100M,因此決定開發(fā)一個分片上傳接口。
把大文件分成若干個小文件上傳。所有文件上傳完成后通過唯一標示進行合并文件。
我們的開發(fā)人員很快完成了開發(fā),并在單元測試中表現(xiàn)無誤。上傳代碼到測試環(huán)境,喔嚯?。?!出錯了。
經(jīng)過一段時間的辛苦排查終于發(fā)現(xiàn)問題,測試環(huán)境多實例,分片上傳的接口會被路由到不同的實例,導致上傳后的分片文件在不同的機器,那么也就無法被合并。
知道了原因就好解決,經(jīng)過一系列的過程最終決定修改網(wǎng)關把uuid相同的請求路由到相同的實例上,這樣就不會出錯了!
準備
由于是公司代碼不方便透露,現(xiàn)使用本地測試代碼。
準備:Eureka注冊中心,Gateway網(wǎng)關,測試微服務
啟動后服務如下兩個測試的微服務,一個網(wǎng)關服務
gateway版本
<spring-cloud.version>Greenwich.SR2</spring-cloud.version> <spring-boot.version>2.1.6.RELEASE</spring-boot.version>
此處就說下我網(wǎng)關的配置。
#網(wǎng)關名 spring.cloud.gateway.routes[0].id=route-my-service-id #網(wǎng)關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。網(wǎng)關過濾器,該過濾器可以指定過濾的條件,只有達到了條件的才進入該過濾器。
如果想知道自帶有哪些配置,我們可以查看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。
調(diào)試代碼試一試呢?發(fā)現(xiàn)果然要走。(此處圖片中應該是my-service-id)。
最終被路由到這個地方,負載均衡使用的是ribbon,關于ribbon暫時不討論。
重點在這兒,通過現(xiàn)在的uri選擇到具體的uri。而這個方法恰恰是一個protected方法,我們可以重寫該方法加上我們自己的業(yè)務邏輯。
protected ServiceInstance choose(ServerWebExchange exchange) { return loadBalancer.choose( ((URI) exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR)).getHost()); }
實現(xiàn)
重寫LoadBalancerClientFilter中的choose方法,實現(xiàn)自定義邏輯
/** * @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()); } }
定義通用選擇實例規(guī)則
/** * @Description 自定義選擇實例規(guī)則 * @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); }
實現(xiàn)自定義路由策略
/** * @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,因為此時取模就不同了,還是會到不同請求。但是這種情況很少發(fā)生,所以暫時不考慮。
或許有hash槽的方式可以解決一點問題,后續(xù)研究?。。。?!
到此這篇關于Java使用Gateway自定義負載均衡過濾器的文章就介紹到這了,更多相關Java 自定義負載均衡過濾器內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
MyBatis中多對一和一對多數(shù)據(jù)的處理方法
這篇文章主要介紹了MyBatis中多對一和一對多數(shù)據(jù)的處理,本文通過示例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-01-01Spring?Boot?中的?Native?SQL基本概念及使用方法
在本文中,我們介紹了 Spring Boot 中的 Native SQL,以及如何使用 JdbcTemplate 和 NamedParameterJdbcTemplate 來執(zhí)行自定義的 SQL 查詢或更新語句,需要的朋友跟隨小編一起看看吧2023-07-07SpringBoot中Shiro緩存使用Redis、Ehcache的方法
這篇文章主要介紹了SpringBoot中Shiro緩存使用Redis、Ehcache的方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2019-09-09