Java使用Gateway自定義負(fù)載均衡過(guò)濾器
背景
最近項(xiàng)目中需要上傳視頻文件,由于視頻文件可能會(huì)比較大,但是我們應(yīng)用服務(wù)器tomcat設(shè)置單次只支持的100M,因此決定開(kāi)發(fā)一個(gè)分片上傳接口。
把大文件分成若干個(gè)小文件上傳。所有文件上傳完成后通過(guò)唯一標(biāo)示進(jìn)行合并文件。
我們的開(kāi)發(fā)人員很快完成了開(kāi)發(fā),并在單元測(cè)試中表現(xiàn)無(wú)誤。上傳代碼到測(cè)試環(huán)境,喔嚯?。?!出錯(cuò)了。
經(jīng)過(guò)一段時(shí)間的辛苦排查終于發(fā)現(xiàn)問(wèn)題,測(cè)試環(huán)境多實(shí)例,分片上傳的接口會(huì)被路由到不同的實(shí)例,導(dǎo)致上傳后的分片文件在不同的機(jī)器,那么也就無(wú)法被合并。
知道了原因就好解決,經(jīng)過(guò)一系列的過(guò)程最終決定修改網(wǎng)關(guān)把uuid相同的請(qǐng)求路由到相同的實(shí)例上,這樣就不會(huì)出錯(cuò)了!
準(zhǔn)備
由于是公司代碼不方便透露,現(xiàn)使用本地測(cè)試代碼。
準(zhǔn)備:Eureka注冊(cè)中心,Gateway網(wǎng)關(guān),測(cè)試微服務(wù)
啟動(dòng)后服務(wù)如下兩個(gè)測(cè)試的微服務(wù),一個(gè)網(wǎng)關(guān)服務(wù)
gateway版本
<spring-cloud.version>Greenwich.SR2</spring-cloud.version> <spring-boot.version>2.1.6.RELEASE</spring-boot.version>
此處就說(shuō)下我網(wǎng)關(guān)的配置。
#網(wǎng)關(guān)名 spring.cloud.gateway.routes[0].id=route-my-service-id #網(wǎng)關(guān)uri,lb代表負(fù)載均衡,后面是服務(wù)名,必須要和微服務(wù)名一致,不能錯(cuò),錯(cuò)了肯定不能路由 spring.cloud.gateway.routes[0].uri=lb://my-service-id #斷言,配置的路徑 spring.cloud.gateway.routes[0].predicates[0]=Path=/my-service-id/v3/** #截取uri前面兩個(gè)位置的 spring.cloud.gateway.routes[0].filters[0]=StripPrefix=2
分析
想要修改路由就要知道gateway是如何把我們的請(qǐng)求路由到各個(gè)微服務(wù)的實(shí)例上的。
gateway其實(shí)無(wú)非就是不同的過(guò)濾器,然后對(duì)請(qǐng)求進(jìn)行處理,和zuul類(lèi)似。gateway自帶了很多過(guò)濾器。過(guò)濾器分為兩種:
1、GlobalFilter 。顧名思義,全局過(guò)濾器,所有請(qǐng)求都會(huì)走的過(guò)濾器。常見(jiàn)的自帶過(guò)濾器LoadBalancerClientFilter(負(fù)載均衡過(guò)濾器,后面我們就是修改這個(gè)地方)。
2、GatewayFilter。網(wǎng)關(guān)過(guò)濾器,該過(guò)濾器可以指定過(guò)濾的條件,只有達(dá)到了條件的才進(jìn)入該過(guò)濾器。
如果想知道自帶有哪些配置,我們可以查看gateway的自動(dòng)注入類(lèi)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); } 省略.......
然后查看負(fù)載均衡配置。
GatewayLoadBalancerClientAutoConfiguration
/** * @author Spencer Gibb */ @Configuration @ConditionalOnClass({ LoadBalancerClient.class, RibbonAutoConfiguration.class, DispatcherHandler.class }) @AutoConfigureAfter(RibbonAutoConfiguration.class) @EnableConfigurationProperties(LoadBalancerProperties.class) public class GatewayLoadBalancerClientAutoConfiguration { // GlobalFilter beans //負(fù)載均衡 @Bean @ConditionalOnBean(LoadBalancerClient.class) @ConditionalOnMissingBean(LoadBalancerClientFilter.class) public LoadBalancerClientFilter loadBalancerClientFilter(LoadBalancerClient client, LoadBalancerProperties properties) { return new LoadBalancerClientFilter(client, properties); } }
進(jìn)入LoadBalancerClientFilter,其實(shí)就是一個(gè)GlobalFilter。
調(diào)試代碼試一試呢?發(fā)現(xiàn)果然要走。(此處圖片中應(yīng)該是my-service-id)。
最終被路由到這個(gè)地方,負(fù)載均衡使用的是ribbon,關(guān)于ribbon暫時(shí)不討論。
重點(diǎn)在這兒,通過(guò)現(xiàn)在的uri選擇到具體的uri。而這個(gè)方法恰恰是一個(gè)protected方法,我們可以重寫(xiě)該方法加上我們自己的業(yè)務(wù)邏輯。
protected ServiceInstance choose(ServerWebExchange exchange) { return loadBalancer.choose( ((URI) exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR)).getHost()); }
實(shí)現(xiàn)
重寫(xiě)LoadBalancerClientFilter中的choose方法,實(shí)現(xiàn)自定義邏輯
/** * @Description 自定義負(fù)載均衡 * @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()); } }
定義通用選擇實(shí)例規(guī)則
/** * @Description 自定義選擇實(shí)例規(guī)則 * @Author Singh * @Date 2020-07-02 11:03 * @Version **/ public interface IChooseRule { /** * 返回null那么使用gateway默認(rèn)的負(fù)載均衡策略 * @param exchange * @param discoveryClient * @return */ ServiceInstance choose(ServerWebExchange exchange, DiscoveryClient discoveryClient); }
實(shí)現(xiàn)自定義路由策略
/** * @Description 微服務(wù)負(fù)載均衡策略 * @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; } }
最后注入自定義負(fù)載均衡過(guò)濾器。
/** * @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的請(qǐng)求就可以通過(guò)hash取模路由到相同的機(jī)器上了,當(dāng)然這樣還是存在問(wèn)題,比如多添加一個(gè)實(shí)例,或者掛了一個(gè)實(shí)例,掛之前有一個(gè)請(qǐng)求到A,掛之后可能到B,因?yàn)榇藭r(shí)取模就不同了,還是會(huì)到不同請(qǐng)求。但是這種情況很少發(fā)生,所以暫時(shí)不考慮。
或許有hash槽的方式可以解決一點(diǎn)問(wèn)題,后續(xù)研究!?。。。?/p>
到此這篇關(guān)于Java使用Gateway自定義負(fù)載均衡過(guò)濾器的文章就介紹到這了,更多相關(guān)Java 自定義負(fù)載均衡過(guò)濾器內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Eclipse查看開(kāi)發(fā)包jar里源代碼的方法
這篇文章主要介紹了Eclipse查看開(kāi)發(fā)包jar里源代碼的方法的相關(guān)資料,需要的朋友可以參考下2017-07-07使用java寫(xiě)的矩陣乘法實(shí)例(Strassen算法)
這篇文章主要給大家介紹了關(guān)于如何使用java寫(xiě)的矩陣乘法(Strassen算法)的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-02-02MyBatis中多對(duì)一和一對(duì)多數(shù)據(jù)的處理方法
這篇文章主要介紹了MyBatis中多對(duì)一和一對(duì)多數(shù)據(jù)的處理,本文通過(guò)示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-01-01Mac系統(tǒng)搭建JDK及JMETER過(guò)程解析
這篇文章主要介紹了Mac系統(tǒng)搭建JDK及JMETER過(guò)程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-08-08Java Arrays.AsList原理及用法實(shí)例
這篇文章主要介紹了Java Arrays.AsList原理及用法實(shí)例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-11-11Spring?Boot?中的?Native?SQL基本概念及使用方法
在本文中,我們介紹了 Spring Boot 中的 Native SQL,以及如何使用 JdbcTemplate 和 NamedParameterJdbcTemplate 來(lái)執(zhí)行自定義的 SQL 查詢(xún)或更新語(yǔ)句,需要的朋友跟隨小編一起看看吧2023-07-07SpringBoot中Shiro緩存使用Redis、Ehcache的方法
這篇文章主要介紹了SpringBoot中Shiro緩存使用Redis、Ehcache的方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-09-09