自定義springcloud-gateway熔斷處理

一违霞、場景

使用spring cloud gateway后霜第,有了熔斷泌类,問題也就隨之而來,服務(wù)間調(diào)用有了hystrix可以及時的排除壞接口弹砚、壞服務(wù)的問題桌吃,對系統(tǒng)很有幫助苞轿。但是搬卒!不是所有的接口都是極短時間內(nèi)完成的,不是所有的接口都可以設(shè)置一樣的超時時間的摆寄!

那么我們面臨一個問題微饥,那就是百分之99的接口都可以在1s內(nèi)完美完成,但是就是那幾個特殊接口矩肩,需要十幾秒蛮拔,幾十秒的等待時間痹升,而默認(rèn)熔斷的時間又只有一個疼蛾。

二、分析

在前面springcloudgateway源碼解析之請求篇中我們知道請求會經(jīng)過一些列的過濾器(GatewayFilter),而springcloudgateway的降級熔斷處理就是由一個特殊的過濾器來處理的衍慎,通過源碼分析我們關(guān)注到HystrixGatewayFilterFactory這個類稳捆,這個類的作用就是生產(chǎn)GatewayFilter用的麦轰,我們看下它的實現(xiàn)

可以看到紅框處最后構(gòu)建了一個匿名的GatewayFilter對象返回末荐,這個對象在接口請求過程中會被加載到過濾器鏈條中新锈,仔細(xì)看到 這里是創(chuàng)建了一個RouteHystrixCommand這個命令對象,最終調(diào)用command.toObservable()方法處理請求块请,如果超時熔斷調(diào)用resumeWithFallback方法

通過源碼分析 gateway在路由時可以指定HystrixCommandKey负乡,并且對HystrixCommandKey設(shè)置超時時間

三、方案

知道網(wǎng)關(guān)熔斷的原理就好辦了,自定義熔斷的過濾器配置到接口請求過程中狸涌,由過濾器來讀取接口熔斷配置并構(gòu)建HystrixObservableCommand處理請求。

自定義一個類XXXGatewayFilterFactory繼承AbstractGatewayFilterFactory朝捆,將api和對應(yīng)的timeout配置化芙盘,來實現(xiàn)細(xì)化到具體接口的熔斷配置脸秽,具體實現(xiàn)如下:

package org.unicorn.framework.gateway.filter;

import cn.hutool.core.collection.CollectionUtil;

import com.netflix.hystrix.HystrixCommandGroupKey;

import com.netflix.hystrix.HystrixCommandKey;

import com.netflix.hystrix.HystrixCommandProperties;

import com.netflix.hystrix.HystrixObservableCommand;

import com.netflix.hystrix.exception.HystrixRuntimeException;

import org.springframework.beans.factory.ObjectProvider;

import org.springframework.cloud.gateway.filter.GatewayFilter;

import org.springframework.cloud.gateway.filter.GatewayFilterChain;

import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;

import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;

import org.springframework.cloud.gateway.support.TimeoutException;

import org.springframework.core.annotation.AnnotatedElementUtils;

import org.springframework.http.server.reactive.ServerHttpRequest;

import org.springframework.stereotype.Component;

import org.springframework.util.AntPathMatcher;

import org.springframework.web.bind.annotation.ResponseStatus;

import org.springframework.web.reactive.DispatcherHandler;

import org.springframework.web.server.ResponseStatusException;

import org.springframework.web.server.ServerWebExchange;

import org.springframework.web.util.UriComponentsBuilder;

import reactor.core.publisher.Mono;

import rx.Observable;

import rx.RxReactiveStreams;

import rx.Subscription;

import java.net.URI;

import java.util.Collections;

import java.util.List;

import java.util.function.Function;

/**

* @Author: xiebin

* @Description:

* @Date:Create:in 2022-07-06 9:17

*/

@Component

public class UnicornHystrixGatewayFilterFactoryextends AbstractGatewayFilterFactory {

private static final StringNAME ="unicornHystrix";

? ? private ObjectProviderdispatcherHandlerProvider;

? ? private AntPathMatcherantPathMatcher;

? ? public UnicornHystrixGatewayFilterFactory(ObjectProvider dispatcherHandlerProvider) {

super(Config.class);

? ? ? ? this.dispatcherHandlerProvider = dispatcherHandlerProvider;

? ? ? ? this.antPathMatcher =new AntPathMatcher();

? ? }

@Override

? ? public ListshortcutFieldOrder() {

return Collections.singletonList(NAME_KEY);

? ? }

/**

* 獲取服務(wù)ID

*

? ? * @param exchange

? ? * @return

? ? */

? ? public StringserviceId(ServerWebExchange exchange) {

ServerHttpRequest request = exchange.getRequest();

? ? ? ? String path = request.getPath().pathWithinApplication().value();

? ? ? ? try {

path = path.split("/")[1];

? ? ? ? ? ? return path;

? ? ? ? }catch (Exception e) {

return "default";

? ? ? ? }

}

/**

? ? * @param key

? ? * @param timeout

? ? * @return

? ? */

? ? private HystrixObservableCommand.SetterinitSetter(String key, Integer timeout) {

HystrixObservableCommand.Setter setter = HystrixObservableCommand.Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey(key)).andCommandKey(HystrixCommandKey.Factory.asKey(key));

? ? ? ? if (timeout !=null) {

setter.andCommandPropertiesDefaults(HystrixCommandProperties.Setter().withExecutionTimeoutInMilliseconds(timeout));

? ? ? ? }

return setter;

? ? }

/**

? ? * @param exchange

? ? * @param chain

? ? * @param config

? ? * @return

? ? */

? ? private UnicornRouteHystrixCommandinitUnicornRouteHystrixCommand(ServerWebExchange exchange, GatewayFilterChain chain, Config config) {

//路由配置的超時設(shè)置

? ? ? ? List apiTimeoutList = config.getTimeouts();

? ? ? ? ServerHttpRequest request = exchange.getRequest();

? ? ? ? String path = request.getPath().pathWithinApplication().value();

? ? ? ? UnicornRouteHystrixCommand command;

? ? ? ? if (CollectionUtil.isNotEmpty(apiTimeoutList)) {

//request匹配屬于那種模式

? ? ? ? ? ? ApiHystrixTimeout apiHystrixTimeout = getApiHystrixTimeout(apiTimeoutList, path);

? ? ? ? ? ? command =new UnicornRouteHystrixCommand(config.getFallbackUri(), exchange, chain, initSetter(apiHystrixTimeout.getApiPattern(), apiHystrixTimeout.getTimeout()));

? ? ? ? }else {

command =new UnicornRouteHystrixCommand(config.getFallbackUri(), exchange, chain, initSetter(serviceId(exchange), null));

? ? ? ? }

return command;

? ? }

/**

? ? * @param apiTimeoutList

? ? * @param path

? ? * @return

? ? */

? ? private ApiHystrixTimeoutgetApiHystrixTimeout(List apiTimeoutList, String path) {

for (ApiHystrixTimeout apiTimeoutPattern : apiTimeoutList) {

if (this.antPathMatcher.match(apiTimeoutPattern.getApiPattern(), path)) {

return apiTimeoutPattern;

? ? ? ? ? ? }

}

ApiHystrixTimeout apiHystrixTimeout =new ApiHystrixTimeout();

? ? ? ? apiHystrixTimeout.setApiPattern("default");

? ? ? ? apiHystrixTimeout.timeout =null;

? ? ? ? return apiHystrixTimeout;

? ? }

@Override

? ? public GatewayFilterapply(Config config) {

return (exchange, chain) -> {

UnicornRouteHystrixCommand command = initUnicornRouteHystrixCommand(exchange, chain, config);

? ? ? ? ? ? return Mono.create(s -> {

Subscription sub =command.toObservable().subscribe(s::success, s::error, s::success);

? ? ? ? ? ? ? ? s.onCancel(sub::unsubscribe);

? ? ? ? ? ? }).onErrorResume((Function>) throwable -> {

if (throwableinstanceof HystrixRuntimeException) {

HystrixRuntimeException e = (HystrixRuntimeException) throwable;

? ? ? ? ? ? ? ? ? ? HystrixRuntimeException.FailureType failureType = e.getFailureType();

? ? ? ? ? ? ? ? ? ? switch (failureType) {

case TIMEOUT:

return Mono.error(new TimeoutException());

? ? ? ? ? ? ? ? ? ? ? ? case COMMAND_EXCEPTION: {

Throwable cause = e.getCause();

? ? ? ? ? ? ? ? ? ? ? ? ? ? if (causeinstanceof ResponseStatusException || AnnotatedElementUtils

.findMergedAnnotation(cause.getClass(), ResponseStatus.class) !=null) {

return Mono.error(cause);

? ? ? ? ? ? ? ? ? ? ? ? ? ? }

}

default:

break;

? ? ? ? ? ? ? ? ? ? }

}

return Mono.error(throwable);

? ? ? ? ? ? }).then();

? ? ? ? };

? ? }

@Override

? ? public Stringname() {

return NAME;

? ? }

private class UnicornRouteHystrixCommandextends HystrixObservableCommand {

private final URIfallbackUri;

? ? ? ? private final ServerWebExchangeexchange;

? ? ? ? private final GatewayFilterChainchain;

? ? ? ? /**

? ? ? ? * @param fallbackUri

? ? ? ? * @param exchange

? ? ? ? * @param chain

? ? ? ? */

? ? ? ? public UnicornRouteHystrixCommand(URI fallbackUri, ServerWebExchange exchange, GatewayFilterChain chain, HystrixObservableCommand.Setter setter) {

super(setter);

? ? ? ? ? ? this.fallbackUri = fallbackUri;

? ? ? ? ? ? this.exchange = exchange;

? ? ? ? ? ? this.chain = chain;

? ? ? ? }

@Override

? ? ? ? protected Observableconstruct() {

return RxReactiveStreams.toObservable(this.chain.filter(exchange));

? ? ? ? }

@Override

? ? ? ? protected ObservableresumeWithFallback() {

if (null ==fallbackUri) {

return super.resumeWithFallback();

? ? ? ? ? ? }

URI uri =exchange.getRequest().getURI();

? ? ? ? ? ? boolean encoded = ServerWebExchangeUtils.containsEncodedParts(uri);

? ? ? ? ? ? URI requestUrl = UriComponentsBuilder.fromUri(uri)

.host(null)

.port(null)

.uri(this.fallbackUri)

.build(encoded)

.toUri();

? ? ? ? ? ? exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR, requestUrl);

? ? ? ? ? ? ServerHttpRequest request =this.exchange.getRequest().mutate().uri(requestUrl).build();

? ? ? ? ? ? ServerWebExchange mutated =exchange.mutate().request(request).build();

? ? ? ? ? ? DispatcherHandler dispatcherHandler = UnicornHystrixGatewayFilterFactory.this.dispatcherHandlerProvider.getIfAvailable();

? ? ? ? ? ? return RxReactiveStreams.toObservable(dispatcherHandler.handle(mutated));

? ? ? ? }

}

public static class ApiHystrixTimeout {

public StringgetApiPattern() {

return apiPattern;

? ? ? ? }

public void setApiPattern(String apiPattern) {

this.apiPattern = apiPattern;

? ? ? ? }

public IntegergetTimeout() {

return timeout;

? ? ? ? }

public void setTimeout(Integer timeout) {

this.timeout = timeout;

? ? ? ? }

private StringapiPattern;

? ? ? ? private Integertimeout;

? ? }

public static class Config {

private Stringid;

? ? ? ? private URIfallbackUri;

? ? ? ? /**

* url -> timeout ms

*/

? ? ? ? private Listtimeouts;

? ? ? ? public StringgetId() {

return id;

? ? ? ? }

public ConfigsetId(String id) {

this.id = id;

return this;

? ? ? ? }

public URIgetFallbackUri() {

return fallbackUri;

? ? ? ? }

public ConfigsetFallbackUri(URI fallbackUri) {

if (fallbackUri !=null && !"forward".equals(fallbackUri.getScheme())) {

throw new IllegalArgumentException("Hystrix Filter currently only supports 'forward' URIs, found " + fallbackUri);

? ? ? ? ? ? }

this.fallbackUri = fallbackUri;

return this;

? ? ? ? }

public ListgetTimeouts() {

return timeouts;

? ? ? ? }

public ConfigsetTimeouts(List timeouts) {

this.timeouts = timeouts;

return this;

? ? ? ? }

}

}

配置示例

spring.cloud.gateway.default-filters[0].name=unicornHystrix

spring.cloud.gateway.default-filters[0].args.fallbackUri=forward:/defaultFallback

spring.cloud.gateway.default-filters[0].args.timeouts[0].apiPattern=/gf-oss-service//oss/part/upload

spring.cloud.gateway.default-filters[0].args.timeouts[0].timeout=100000

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末练湿,一起剝皮案震驚了整個濱河市肥哎,隨后出現(xiàn)的幾起案子断国,更是在濱河造成了極大的恐慌,老刑警劉巖霞捡,帶你破解...
    沈念sama閱讀 211,639評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件碧信,死亡現(xiàn)場離奇詭異街夭,居然都是意外死亡,警方通過查閱死者的電腦和手機呈枉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,277評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來酥泞,“玉大人啃憎,你說我怎么就攤上這事∶蹑ⅲ” “怎么了贩毕?”我有些...
    開封第一講書人閱讀 157,221評論 0 348
  • 文/不壞的土叔 我叫張陵耳幢,是天一觀的道長。 經(jīng)常有香客問我启上,道長店印,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,474評論 1 283
  • 正文 為了忘掉前任包券,我火速辦了婚禮溅固,結(jié)果婚禮上兰珍,老公的妹妹穿的比我還像新娘。我一直安慰自己亮元,他們只是感情好唠摹,可當(dāng)我...
    茶點故事閱讀 65,570評論 6 386
  • 文/花漫 我一把揭開白布勾拉。 她就那樣靜靜地躺著盗温,像睡著了一般肌访。 火紅的嫁衣襯著肌膚如雪艇劫。 梳的紋絲不亂的頭發(fā)上店煞,一...
    開封第一講書人閱讀 49,816評論 1 290
  • 那天顷蟀,我揣著相機與錄音骡技,去河邊找鬼。 笑死囤萤,一個胖子當(dāng)著我的面吹牛是趴,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播富雅,決...
    沈念sama閱讀 38,957評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼没佑,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了蛤奢?” 一聲冷哼從身側(cè)響起让腹,我...
    開封第一講書人閱讀 37,718評論 0 266
  • 序言:老撾萬榮一對情侶失蹤骇窍,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后痢掠,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,176評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡雄驹,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,511評論 2 327
  • 正文 我和宋清朗相戀三年医舆,在試婚紗的時候發(fā)現(xiàn)自己被綠了象缀。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,646評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡霞怀,死狀恐怖毙石,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情徐矩,我是刑警寧澤州泊,帶...
    沈念sama閱讀 34,322評論 4 330
  • 正文 年R本政府宣布遥皂,位于F島的核電站,受9級特大地震影響弟孟,放射性物質(zhì)發(fā)生泄漏样悟。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,934評論 3 313
  • 文/蒙蒙 一陈症、第九天 我趴在偏房一處隱蔽的房頂上張望震糖。 院中可真熱鬧,春花似錦论咏、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,755評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽葵硕。三九已至贯吓,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背昏苏。 一陣腳步聲響...
    開封第一講書人閱讀 31,987評論 1 266
  • 我被黑心中介騙來泰國打工贤惯, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人孵构。 一個月前我還...
    沈念sama閱讀 46,358評論 2 360
  • 正文 我出身青樓颈墅,卻偏偏與公主長得像,于是被迫代替她去往敵國和親官还。 傳聞我的和親對象是個殘疾皇子毒坛,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,514評論 2 348

推薦閱讀更多精彩內(nèi)容