Java使用Gateway自定義負(fù)載均衡過濾器
背景
最近項(xiàng)目中需要上傳視頻文件,由于視頻文件可能會(huì)比較大,但是我們應(yīng)用服務(wù)器tomcat設(shè)置單次只支持的100M,因此決定開發(fā)一個(gè)分片上傳接口。
把大文件分成若干個(gè)小文件上傳。所有文件上傳完成后通過唯一標(biāo)示進(jìn)行合并文件。
我們的開發(fā)人員很快完成了開發(fā),并在單元測(cè)試中表現(xiàn)無誤。上傳代碼到測(cè)試環(huán)境,喔嚯?。?!出錯(cuò)了。
經(jīng)過一段時(shí)間的辛苦排查終于發(fā)現(xiàn)問題,測(cè)試環(huán)境多實(shí)例,分片上傳的接口會(huì)被路由到不同的實(shí)例,導(dǎo)致上傳后的分片文件在不同的機(jī)器,那么也就無法被合并。
知道了原因就好解決,經(jīng)過一系列的過程最終決定修改網(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>
此處就說下我網(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í)無非就是不同的過濾器,然后對(duì)請(qǐng)求進(jìn)行處理,和zuul類似。gateway自帶了很多過濾器。過濾器分為兩種:
1、GlobalFilter 。顧名思義,全局過濾器,所有請(qǐng)求都會(huì)走的過濾器。常見的自帶過濾器LoadBalancerClientFilter(負(fù)載均衡過濾器,后面我們就是修改這個(gè)地方)。
2、GatewayFilter。網(wǎng)關(guān)過濾器,該過濾器可以指定過濾的條件,只有達(dá)到了條件的才進(jìn)入該過濾器。
如果想知道自帶有哪些配置,我們可以查看gateway的自動(dòng)注入類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)在這兒,通過現(xiàn)在的uri選擇到具體的uri。而這個(gè)方法恰恰是一個(gè)protected方法,我們可以重寫該方法加上我們自己的業(yè)務(wù)邏輯。
protected ServiceInstance choose(ServerWebExchange exchange) {
return loadBalancer.choose(
((URI) exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR)).getHost());
}
實(shí)現(xiàn)
重寫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ù)載均衡過濾器。
/**
* @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)求就可以通過hash取模路由到相同的機(jī)器上了,當(dāng)然這樣還是存在問題,比如多添加一個(gè)實(shí)例,或者掛了一個(gè)實(shí)例,掛之前有一個(gè)請(qǐng)求到A,掛之后可能到B,因?yàn)榇藭r(shí)取模就不同了,還是會(huì)到不同請(qǐng)求。但是這種情況很少發(fā)生,所以暫時(shí)不考慮。
或許有hash槽的方式可以解決一點(diǎn)問題,后續(xù)研究?。。。?!
到此這篇關(guān)于Java使用Gateway自定義負(fù)載均衡過濾器的文章就介紹到這了,更多相關(guān)Java 自定義負(fù)載均衡過濾器內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java中CompletableFuture異步執(zhí)行方法
本文主要介紹了java中CompletableFuture異步執(zhí)行方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-06-06
Java堆空間爆滿導(dǎo)致宕機(jī)的問題分析及解決
團(tuán)隊(duì)有一個(gè)服務(wù),一直運(yùn)行的好好的,突然訪問異常了,先是請(qǐng)求超時(shí),然后直接無法訪問,本文將給大家介紹Java堆空間爆滿導(dǎo)致宕機(jī)的問題分析及解決,需要的朋友可以參考下2024-02-02
MyBatis代碼自動(dòng)生成器Mybatis-Generator的使用詳解
本文詳細(xì)介紹如何在SpringBoot項(xiàng)目中使用MyBatis-Generator進(jìn)行代碼生成,包括配置文件的添加、POM依賴配置、運(yùn)行配置等步驟,通過自動(dòng)生成代碼,可以簡(jiǎn)化MyBatis的繁瑣配置和SQL編寫,提高開發(fā)效率,注意要考慮MySQL版本兼容性,以及確保路徑配置正確2024-10-10
解決springboot?druid數(shù)據(jù)庫連接池連接失敗后一直重連問題
這篇文章主要介紹了解決springboot?druid數(shù)據(jù)庫連接池連接失敗后一直重連問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-11-11
PowerJob分布式任務(wù)調(diào)度源碼流程解讀
這篇文章主要為大家介紹了PowerJob分布式任務(wù)調(diào)度源碼流程解讀,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2024-02-02

