什么是服務(wù)降級?DUBBO服務(wù)降級策略不能降級哪類異常?
JAVA前線?
歡迎大家關(guān)注公眾號「JAVA前線」查看更多精彩分享,主要包括源碼分析、實際應(yīng)用、架構(gòu)思維、職場分享、產(chǎn)品思考等等,同時也非常歡迎大家加我微信「java_front」一起交流學習
1 服務(wù)雪崩
在分析服務(wù)降級之前,我們首先談一談什么是服務(wù)雪崩。現(xiàn)在我們假設(shè)存在A、B、C、D四個系統(tǒng),系統(tǒng)間存在如下調(diào)用鏈路:

在正常情況下系統(tǒng)之間調(diào)用快速且正常,系統(tǒng)運行平穩(wěn)。但是此時用戶訪問系統(tǒng)A的流量激增,這些流量在瞬間透傳到B、C、D三個系統(tǒng)。B、C系統(tǒng)服務(wù)器節(jié)點較多抗住了這些流量,但是D系統(tǒng)服務(wù)器節(jié)點較少,沒有抗住這些流量,導(dǎo)致D系統(tǒng)的資源逐漸耗盡,只能提供慢服務(wù),最終結(jié)果是響應(yīng)用戶時延很長。

此時用戶發(fā)現(xiàn)響應(yīng)很慢,以為是自己網(wǎng)絡(luò)不好會反復(fù)重試,那么成倍的流量會打到系統(tǒng)中,導(dǎo)致上游系統(tǒng)資源也逐漸耗盡了,整個訪問鏈路都最終都不可用。

以上介紹了服務(wù)雪崩場景,我們發(fā)現(xiàn)在鏈路中一個節(jié)點出現(xiàn)問題,導(dǎo)致整個鏈路最終都不可用了,這是不可以接受的。
2 非線性
我們再從另一個概念來理解服務(wù)雪崩:非線性。這個概念在我們生活中無處不在。
你要趕早上8點鐘的火車,如果6:30出發(fā)可以在7:00到達車站,于是你得到一個結(jié)論:只要30分鐘就可以到達車站。
你早上想睡晚一點預(yù)計7:10出發(fā),想著7:40可以到達車站。但是最可能的結(jié)果是你將錯過這趟火車。因為正好遇上早高峰,堵車導(dǎo)致你至少需要花費1個小時才能到達車站。
一個小雪球的重量是100克,打雪仗時你被砸中100次,這對你不會造成任何影響。
但是如果你被10公斤的雪球砸中1次,這可能會對你造成嚴重的傷害。
這就是非線性。事物不是簡單疊加關(guān)系,當達到某個臨界值時會造成一種完全截然不同的結(jié)果。
我們來分析一個互聯(lián)網(wǎng)的秒殺場景。假設(shè)你設(shè)計的秒殺系統(tǒng)當每秒30個人訪問時,響應(yīng)時間是10毫秒。即從用戶點擊按鈕至得到結(jié)果這個過程,只花費了10毫秒。這個時間的流逝基本上察覺不到,性能是不錯的。你感覺很好繼續(xù)設(shè)計:
每秒30個訪問量響應(yīng)時間10毫秒
每秒300個訪問量響應(yīng)時間100毫秒
每秒3000個訪問量響應(yīng)時間1000毫秒
如果你按照這個思路去做系統(tǒng)設(shè)計,將會發(fā)生重大的錯誤。因為當每秒3000個訪問量發(fā)生時,系統(tǒng)的響應(yīng)時間可能不是1000毫秒,而可能直接導(dǎo)致系統(tǒng)崩潰,無法再處理任何的請求。最常見的場景就是當緩存系統(tǒng)失效時,導(dǎo)致的系統(tǒng)雪崩:
(1) 當耗時低的緩存層出現(xiàn)故障時,流量直接打在了耗時高的數(shù)據(jù)庫層,用戶的等待時長就會增加
(2) 等待時長的增加導(dǎo)致用戶更加頻繁去訪問,更多的流量會打在數(shù)據(jù)庫層
(3) 這導(dǎo)致用戶的等待時長進一步增加,再次導(dǎo)致更頻繁的訪問
(4) 當訪問量達到一個極限值時,造成系統(tǒng)崩潰,無法再處理任何請求
流量和響應(yīng)時間絕不是簡單的疊加關(guān)系,當?shù)竭_某個臨界值時,技術(shù)系統(tǒng)將直接崩潰。
3 服務(wù)雪崩應(yīng)對方案
保證系統(tǒng)的穩(wěn)定性和高可用性,我們需要采取一些高可用策略,目的是構(gòu)建一個穩(wěn)定的高可用工程系統(tǒng),我們一般采用如下方案。
3.1 冗余 + 自動故障轉(zhuǎn)移
最基本的冗余策略就是主從模式。原理是準備兩臺機器,部署了同一份代碼,在功能層面是相同的,都可以對外提供相同的服務(wù)。
一臺機器啟動提供服務(wù),這就是主服務(wù)器。另一臺機器啟動在一旁待命,不提供服務(wù),隨時監(jiān)聽主服務(wù)器的狀態(tài),這就是從服務(wù)器。當發(fā)現(xiàn)主服務(wù)器出現(xiàn)故障時,從服務(wù)器立刻替換主服務(wù)器,繼續(xù)為用戶提供服務(wù)。
自動故障轉(zhuǎn)移策略是指當主系統(tǒng)發(fā)生異常時,應(yīng)該可以自動探測到異常,并自動切換為備用系統(tǒng)。不應(yīng)該只依靠人工去切換成,否則故障處理時間會顯著增加。
3.2 降級策略
所謂降級策略,就是當系統(tǒng)遇到無法承受的壓力時,選擇暫時關(guān)閉一些非關(guān)鍵的功能,或者延時提供一些功能,把此刻所有的資源都提供給現(xiàn)在最關(guān)鍵的服務(wù)。
在秒殺場景中下訂單就是最核心最關(guān)鍵的功能。當系統(tǒng)壓力將要到達臨界值時,可以暫時先關(guān)閉一些非核心功能如查詢功能。
當秒殺活動結(jié)束后,再將暫時關(guān)閉的功能開啟。這樣既保證了秒殺活動的順利進行,也保護了系統(tǒng)沒有崩潰。
還有一種降級策略,當系統(tǒng)依賴的下游服務(wù)出現(xiàn)錯誤,甚至已經(jīng)完全不可用了,那么此時就不能再調(diào)用這個下游服務(wù)了,否則可能導(dǎo)致雪崩。所以直接返回兜底方案,把下游服務(wù)直接降級。
這里比較兩個概念:服務(wù)降級與服務(wù)熔斷,因為這兩個概念比較相似。我認為服務(wù)熔斷是服務(wù)降級的一個方法,而服務(wù)降級還有很多其它方法,例如開關(guān)降級、流量降級等等。
3.3 延時策略
用戶下訂單成功后就需要進行支付。假設(shè)秒殺系統(tǒng)下訂單每秒訪問量是3000,我們來思考一個問題,有沒有必要將每秒3000次訪問量的壓力傳遞給支付服務(wù)器?
答案是沒有必要。因為用戶秒殺成功后可以稍晚付款,比如可以跳轉(zhuǎn)到一個支付頁面,提示用戶只要在10分鐘內(nèi)支付完成即可。
這樣每秒3000次訪問量就被分攤至幾分鐘,有效保護了系統(tǒng)。技術(shù)架構(gòu)還可以使用消息隊列做緩沖,讓支付服務(wù)按照自己的能力去處理業(yè)務(wù)。
3.4 隔離策略
物理隔離:應(yīng)用分別部署在不同物理機、不同機房,資源不會互相影響。
線程隔離:不同類型的請求進行分類,交給不同的線程池處理,當一類請求出現(xiàn)高耗時和異常,不影響另一類請求訪問。
4 服務(wù)降級
本文我們重點結(jié)合Dubbo框架談一談服務(wù)降級。現(xiàn)在我們有服務(wù)提供者提供如下服務(wù):
public?interface?HelloService?{
????public?String?sayHello(String?name)?throws?Exception;
}
public?class?HelloServiceImpl?implements?HelloService?{
????public?String?sayHello(String?name)?throws?Exception?{
????????String?result?=?"hello["?+?name?+?"]";
????????return?result;
????}
}
配置文件聲明服務(wù)接口:
<dubbo:service?interface="com.java.front.demo.provider.HelloService"?ref="helloService"?/>
4.1 降級策略配置
Dubbo框架是自帶服務(wù)降級策略的,提供了三種常用的降級策略,我們看一看如何進行配置。
(1) 強制降級策略
<dubbo:reference?id="helloService"?mock="force:return?1"?interface="com.java.front.demo.provider.HelloService"?/>
(2) 異常降級策略
<dubbo:reference?id="helloService"?mock="throw?com.java.front.BizException"?interface="com.java.front.dubbo.demo.provider.HelloService"?/>
(3) 自定義降級策略
package?com.java.front.dubbo.demo.consumer;
import?com.java.front.demo.provider.HelloService;
public?class?HelloServiceMock?implements?HelloService?{
????@Override
????public?String?sayHello(String?name)?throws?Exception?{
????????return?"mock";
????}
}
配置指定自定義降級策略:
<dubbo:reference?id="helloService"?mock="com.java.front.dubbo.demo.consumer.HelloServiceMock"?interface="com.java.front.demo.provider.HelloService"?/>
4.2 源碼分析
public?class?MockClusterInvoker<T>?implements?Invoker<T>?{
????@Override
????public?Result?invoke(Invocation?invocation)?throws?RpcException?{
????????Result?result?=?null;
????????//?檢查是否有mock屬性
????????String?value?=?directory.getUrl().getMethodParameter(invocation.getMethodName(),?Constants.MOCK_KEY,?Boolean.FALSE.toString()).trim();
????????//?沒有mock屬性直接執(zhí)行消費邏輯
????????if?(value.length()?==?0?||?value.equalsIgnoreCase("false"))?{
????????????//?服務(wù)消費默認執(zhí)行FailoverClusterInvoker
????????????result?=?this.invoker.invoke(invocation);
????????}
????????//?不執(zhí)行消費邏輯直接返回
????????else?if?(value.startsWith("force"))?{
????????????if?(logger.isWarnEnabled())?{
????????????????logger.warn("force-mock:?"?+?invocation.getMethodName()?+?"?force-mock?enabled?,?url?:?"?+?directory.getUrl());
????????????}
????????????//?直接執(zhí)行mock邏輯
????????????result?=?doMockInvoke(invocation,?null);
????????}?else?{
????????????try?{
????????????????//?服務(wù)消費默認執(zhí)行FailoverClusterInvoker
????????????????result?=?this.invoker.invoke(invocation);
????????????}?catch?(RpcException?e)?{
????????????????if?(e.isBiz())?{
????????????????????throw?e;
????????????????}
????????????????if?(logger.isWarnEnabled())?{
????????????????????logger.warn("fail-mock:?"?+?invocation.getMethodName()?+?"?fail-mock?enabled?,?url?:?"?+?directory.getUrl(),?e);
????????????????}
????????????????//?服務(wù)消費失敗執(zhí)行mock邏輯
????????????????result?=?doMockInvoke(invocation,?e);
????????????}
????????}
????????return?result;
????}
}
public?class?MockInvoker<T>?implements?Invoker<T>?{
????@Override
????public?Result?invoke(Invocation?invocation)?throws?RpcException?{
????????String?mock?=?getUrl().getParameter(invocation.getMethodName()?+?"."?+?Constants.MOCK_KEY);
????????if?(invocation?instanceof?RpcInvocation)?{
????????????((RpcInvocation)?invocation).setInvoker(this);
????????}
????????if?(StringUtils.isBlank(mock))?{
????????????mock?=?getUrl().getParameter(Constants.MOCK_KEY);
????????}
????????if?(StringUtils.isBlank(mock))?{
????????????throw?new?RpcException(new?IllegalAccessException("mock?can?not?be?null.?url?:"?+?url));
????????}
????????mock?=?normalizeMock(URL.decode(mock));
????????//?直接包裝返回結(jié)果
????????if?(mock.startsWith(Constants.RETURN_PREFIX))?{
????????????mock?=?mock.substring(Constants.RETURN_PREFIX.length()).trim();
????????????try?{
????????????????Type[]?returnTypes?=?RpcUtils.getReturnTypes(invocation);
????????????????Object?value?=?parseMockValue(mock,?returnTypes);
????????????????return?new?RpcResult(value);
????????????}?catch?(Exception?ew)?{
????????????????throw?new?RpcException("mock?return?invoke?error.?method?:"?+?invocation.getMethodName()?+?",?mock:"?+?mock?+?",?url:?"?+?url,?ew);
????????????}
????????}
????????//?拋出異常
????????else?if?(mock.startsWith(Constants.THROW_PREFIX))?{
????????????mock?=?mock.substring(Constants.THROW_PREFIX.length()).trim();
????????????if?(StringUtils.isBlank(mock))?{
????????????????throw?new?RpcException("mocked?exception?for?service?degradation.");
????????????}?else?{
????????????????//?獲取自定義異常
????????????????Throwable?t?=?getThrowable(mock);
????????????????throw?new?RpcException(RpcException.BIZ_EXCEPTION,?t);
????????????}
????????}
????????//?自定義mock策略
????????else?{
????????????try?{
????????????????Invoker?invoker?=?getInvoker(mock);
????????????????return?invoker.invoke(invocation);
????????????}?catch?(Throwable?t)?{
????????????????throw?new?RpcException("Failed?to?create?mock?implementation?class?"?+?mock,?t);
????????????}
????????}
????}
}
5 產(chǎn)生疑問
通過上述源碼我們知道,如果在mock屬性中配置force,那么不會執(zhí)行真正的業(yè)務(wù)邏輯,而是只執(zhí)行mock邏輯,這一部分比較容易理解:
//?不執(zhí)行消費邏輯直接返回
else?if?(value.startsWith("force"))?{
????if?(logger.isWarnEnabled())?{
????????logger.warn("force-mock:?"?+?invocation.getMethodName()?+?"?force-mock?enabled?,?url?:?"?+?directory.getUrl());
????}
????//?直接執(zhí)行mock邏輯
????result?=?doMockInvoke(invocation,?null);
}
但是如果是其它mock配置則首先執(zhí)行業(yè)務(wù)代碼,如果業(yè)務(wù)代碼發(fā)生異常了再執(zhí)行mock邏輯:
try?{
????//?服務(wù)消費默認執(zhí)行FailoverClusterInvoker
????result?=?this.invoker.invoke(invocation);
}?catch?(RpcException?e)?{
????if?(e.isBiz())?{
????????throw?e;
????}
????if?(logger.isWarnEnabled())?{
????????logger.warn("fail-mock:?"?+?invocation.getMethodName()?+?"?fail-mock?enabled?,?url?:?"?+?directory.getUrl(),?e);
????}
????//?服務(wù)消費失敗執(zhí)行mock邏輯
????result?=?doMockInvoke(invocation,?e);
}
這段代碼捕獲了RpcException異常,那么問題來了RpcException是什么類型的異常?我們使用自定義降級策略進行實驗,消費者代碼如下:
package?com.java.front.dubbo.demo.consumer;
import?com.java.front.demo.provider.HelloService;
public?class?HelloServiceMock?implements?HelloService?{
????@Override
????public?String?sayHello(String?name)?throws?Exception?{
????????return?"mock";
????}
}
配置指定自定義策略并設(shè)置服務(wù)超時為2秒:
<dubbo:reference?id="helloService"?mock="com.java.front.dubbo.demo.consumer.HelloServiceMock"?interface="com.java.front.demo.provider.HelloService"?timeOut="2000"?/>
消費者測試代碼如下:
public?static?void?testMock()?{
????ClassPathXmlApplicationContext?context?=?new?ClassPathXmlApplicationContext(new?String[]?{?"classpath*:META-INF/spring/dubbo-consumer1.xml"?});
????context.start();
????HelloService?helloServiceMock?=?(HelloService)?context.getBean("helloService");
????String?result?=?helloServiceMock.sayHello("JAVA前線");
????System.out.println("消費者收到結(jié)果="?+?result);
}
5.1 超時異常
5.1.1 代碼實例
我們在生產(chǎn)者業(yè)務(wù)代碼造成5秒的阻塞,模擬一個慢服務(wù):
public?class?HelloServiceImpl?implements?HelloService?{
????public?String?sayHello(String?name)?throws?Exception?{
????????String?result?=?"hello["?+?name?+?"]";
????????//?模擬耗時操作5秒
????????Thread.sleep(5000L);
????????return?result;
????}
}
消費者執(zhí)行返回mock結(jié)果,說明超時異常屬于RpcException異常,可以被降級策略捕獲:
消費者收到結(jié)果=mock
5.1.2 源碼分析
要分析超時異常為什么可以被降級策略捕獲,我們從以下兩個類分析。DefaultFuture.get方法采用了經(jīng)典多線程保護性暫停模式,并且實現(xiàn)了異步轉(zhuǎn)同步的效果,如果發(fā)生超時異常則拋出TimeoutException異常:
public?class?DefaultFuture?implements?ResponseFuture?{
????@Override
????public?Object?get(int?timeout)?throws?RemotingException?{
????????if?(timeout?<=?0)?{
????????????timeout?=?Constants.DEFAULT_TIMEOUT;
????????}
????????//?response對象為空
????????if?(!isDone())?{
????????????long?start?=?System.currentTimeMillis();
????????????lock.lock();
????????????try?{
????????????????//?進行循環(huán)
????????????????while?(!isDone())?{
????????????????????//?放棄鎖并使當前線程阻塞,直到發(fā)出信號或中斷它或者達到超時時間
????????????????????done.await(timeout,?TimeUnit.MILLISECONDS);
????????????????????//?阻塞結(jié)束后再判斷是否完成
????????????????????if?(isDone())?{
????????????????????????break;
????????????????????}
????????????????????//?阻塞結(jié)束后判斷超過超時時間
????????????????????if(System.currentTimeMillis()?-?start?>?timeout)?{
????????????????????????break;
????????????????????}
????????????????}
????????????}?catch?(InterruptedException?e)?{
????????????????throw?new?RuntimeException(e);
????????????}?finally?{
????????????????lock.unlock();
????????????}
????????????//?response對象仍然為空則拋出超時異常
????????????if?(!isDone())?{
????????????????throw?new?TimeoutException(sent?>?0,?channel,?getTimeoutMessage(false));
????????????}
????????}
????????return?returnFromResponse();
????}
}
DubboInvoker調(diào)用了DefaultFuture.get方法,如果捕獲到上述TimeoutException則會拋出RpcException:
public?class?DubboInvoker<T>?extends?AbstractInvoker<T>?{
????@Override
????protected?Result?doInvoke(final?Invocation?invocation)?throws?Throwable?{
????????try?{
????????????//?request方法發(fā)起遠程調(diào)用?->?get異步轉(zhuǎn)同步并進行超時驗證
????????????RpcContext.getContext().setFuture(null);
????????????Result?result?=?(Result)?currentClient.request(inv,?timeout).get();
????????????return?result;
????????}?catch?(TimeoutException?e)?{
????????????throw?new?RpcException(RpcException.TIMEOUT_EXCEPTION,?"Invoke?remote?method?timeout.?method:?"?+?invocation.getMethodName()?+?",?provider:?"?+?getUrl()?+?",?cause:?"?+?e.getMessage(),?e);
????????}?catch?(RemotingException?e)?{
????????????throw?new?RpcException(RpcException.NETWORK_EXCEPTION,?"Failed?to?invoke?remote?method:?"?+?invocation.getMethodName()?+?",?provider:?"?+?getUrl()?+?",?cause:?"?+?e.getMessage(),?e);
????????}
????}
}
源碼分析到這里已經(jīng)很清楚了,RpcException正是服務(wù)降級策略可以捕獲的異常,所以超時異常是可以被降級的。
5.2 業(yè)務(wù)異常
本文我們把非超時異常統(tǒng)稱為業(yè)務(wù)異常,例如生產(chǎn)者業(yè)務(wù)執(zhí)行時發(fā)生運行時異常可以歸為業(yè)務(wù)異常,下面我們進行試驗。
5.2.1 代碼實例
生產(chǎn)者執(zhí)行過程中拋出運行時異常:
public?class?HelloServiceImpl?implements?HelloService?{
????public?String?sayHello(String?name)?throws?Exception?{
????????throw?new?RuntimeException("BizException")
????}
}
消費者調(diào)用直接拋出異常:
java.lang.RuntimeException:?BizException
??at?com.java.front.dubbo.demo.provider.HelloServiceImpl.sayHello(HelloServiceImpl.java:35)
??at?org.apache.dubbo.common.bytecode.Wrapper1.invokeMethod(Wrapper1.java)
??at?org.apache.dubbo.rpc.proxy.javassist.JavassistProxyFactory$1.doInvoke(JavassistProxyFactory.java:56)
??at?org.apache.dubbo.rpc.proxy.AbstractProxyInvoker.invoke(AbstractProxyInvoker.java:85)
5.2.2 源碼分析
我們發(fā)現(xiàn)服務(wù)降級對業(yè)務(wù)異常沒有生效,需要分析原因,我認為從以下兩點進行分析:
(1) 消費者接收到什么消息
public?class?DefaultFuture?implements?ResponseFuture?{
????public?static?void?received(Channel?channel,?Response?response)?{
????????try?{
????????????DefaultFuture?future?=?FUTURES.remove(response.getId());
????????????if?(future?!=?null)?{
????????????????future.doReceived(response);
????????????}?else?{
????????????????logger.warn("The?timeout?response?finally?returned?at?"
????????????????????????????+?(new?SimpleDateFormat("yyyy-MM-dd?HH:mm:ss.SSS").format(new?Date()))
????????????????????????????+?",?response?"?+?response
????????????????????????????+?(channel?==?null???""?:?",?channel:?"?+?channel.getLocalAddress()
???????????????????????????????+?"?->?"?+?channel.getRemoteAddress()));
????????????}
????????}?finally?{
????????????CHANNELS.remove(response.getId());
????????}
????}
}
response用來接收服務(wù)端發(fā)送的消息,我們看到異常信息存放在Response的exception屬性:
Response?[id=0,?version=null,?status=20,?event=false,?error=null,?result=RpcResult?[result=null,?exception=java.lang.RuntimeException:?BizException]]
(2) 異常在哪里被拋出
我們知道消費者對象是一個代理對象,首先會執(zhí)行到InvokerInvocationHandler:
public?class?InvokerInvocationHandler?implements?InvocationHandler?{
????private?final?Invoker>?invoker;
????public?InvokerInvocationHandler(Invoker>?handler)?{
????????this.invoker?=?handler;
????}
????@Override
????public?Object?invoke(Object?proxy,?Method?method,?Object[]?args)?throws?Throwable?{
????????String?methodName?=?method.getName();
????????Class>[]?parameterTypes?=?method.getParameterTypes();
????????if?(method.getDeclaringClass()?==?Object.class)?{
????????????return?method.invoke(invoker,?args);
????????}
????????if?("toString".equals(methodName)?&&?parameterTypes.length?==?0)?{
????????????return?invoker.toString();
????????}
????????if?("hashCode".equals(methodName)?&&?parameterTypes.length?==?0)?{
????????????return?invoker.hashCode();
????????}
????????if?("equals".equals(methodName)?&&?parameterTypes.length?==?1)?{
????????????return?invoker.equals(args[0]);
????????}
????????//?RpcInvocation?[methodName=sayHello,?parameterTypes=[class?java.lang.String],?arguments=[JAVA前線],?attachments={}]
????????RpcInvocation?rpcInvocation?=?createInvocation(method,?args);
????????//?消費者Invoker?->?MockClusterInvoker(FailoverClusterInvoker(RegistryDirectory(invokers)))
????????Result?result?=?invoker.invoke(rpcInvocation);
????????//?結(jié)果包含異常信息則拋出異常?->?例如異常結(jié)果對象RpcResult?[result=null,?exception=java.lang.RuntimeException:?sayHelloError1?error]
????????return?result.recreate();
????}
}
RpcResult.recreate方法會處理異常,如果發(fā)現(xiàn)異常對象不為空則拋出異常:
public?class?RpcResult?extends?AbstractResult?{
????@Override
????public?Object?recreate()?throws?Throwable?{
????????if?(exception?!=?null)?{
????????????try?{
????????????????Class?clazz?=?exception.getClass();
????????????????while?(!clazz.getName().equals(Throwable.class.getName()))?{
????????????????????clazz?=?clazz.getSuperclass();
????????????????}
????????????????Field?stackTraceField?=?clazz.getDeclaredField("stackTrace");
????????????????stackTraceField.setAccessible(true);
????????????????Object?stackTrace?=?stackTraceField.get(exception);
????????????????if?(stackTrace?==?null)?{
????????????????????exception.setStackTrace(new?StackTraceElement[0]);
????????????????}
????????????}?catch?(Exception?e)?{
????????????}
????????????throw?exception;
????????}
????????return?result;
????}
}
5.2.3 業(yè)務(wù)異常如何降級
通過上述實例我們知道Dubbo自帶的服務(wù)降級策略只能降級超時異常,而不能降級業(yè)務(wù)異常。
那么業(yè)務(wù)異常應(yīng)該如何降級呢?我們可以整合Dubbo、Hystrix進行業(yè)務(wù)異常熔斷,相關(guān)配置也并不復(fù)雜,大家可以網(wǎng)上查閱相關(guān)資料。
6 文章總結(jié)
本文我們首先介紹了服務(wù)雪崩這個場景,并且從非線性角度再次理解了服務(wù)雪崩。隨后我們總結(jié)了服務(wù)雪崩應(yīng)對方案,其中服務(wù)降級是應(yīng)對服務(wù)雪崩的重要方法之一。我們針對超時異常和業(yè)務(wù)異常兩種場,結(jié)合源碼深入分析了Dubbo服務(wù)降級的使用場景,希望本文對大家有所幫助。
JAVA前線?
歡迎大家關(guān)注公眾號「JAVA前線」查看更多精彩分享,主要包括源碼分析、實際應(yīng)用、架構(gòu)思維、職場分享、產(chǎn)品思考等等,同時也非常歡迎大家加我微信「java_front」一起交流學習
