談到服務降級,Dubbo 自己就提供了服務降級的機制;而 Dubbo 的服務降級機制主要是利用服務消費者的 mock 屬性。html
服務消費者的 mock 屬性有如下三種使用方式,下面將帶着例子簡單介紹一下。java
例子:
mock=return+null,即當服務提供者出現異常(宕機或者業務異常),則返回null給服務消費者。git
2021-01-26 09:39:54.631 [main] [INFO ] [o.a.d.r.z.ZookeeperRegistry] [] [] - [DUBBO] Notify urls for subscribe url consumer://127.0.0.1/com.winfun.service.DubboServiceOne?application=dubbo-service&category=providers,configurators,routers&check=false&dubbo=2.0.2&init=false&interface=com.winfun.service.DubboServiceOne&lazy=true&methods=sayHello&mock=return+null&pid=36270&qos.enable=false&reference.filter=default,dubboLogFilter&release=2.7.7&retries=1&side=consumer&sticky=false×tamp=1611625194544
例子:
mock="return null",即當服務提供者出現異常(宕機或者業務異常),則返回null給服務消費者。github
public class HelloController{ @DubboReference(check = false,lazy = true,retries = 1,mock = "return null") private DubboServiceOne dubboServiceOne; //..... }
例子:
Mock實現類 爲 Dubbo接口 的實現類,而且 Mock實現類 與 Dubbo接口 放同一個項目中。apache
/** * * DubboServiceTwo * @author winfun * @date 2020/10/29 5:00 下午 **/ public interface DubboServiceTwo { /*** * say hi * @author winfun * @param name name * @return {@link ApiResult<String> } **/ ApiResult<String> sayHi(String name); } /** * DubboServiceTwo 降級實現類 * @author winfun * @date 2021/1/26 9:21 上午 **/ public class DubboServiceTwoMock implements DubboServiceTwo{ /*** * say hi * @author winfun * @param name name * @return {@link ApiResult <String> } **/ @Override public ApiResult<String> sayHi(String name) { return ApiResult.fail("Mock實現類-服務降級了"); } }
下面將使用第二種方式來展現 Duboo 自身提供的服務降級機制。架構
/** * Say Hello * @author winfun * @date 2020/10/29 5:12 下午 **/ @RestController public class HelloController { @DubboReference(check = false,lazy = true,retries = 1,mock="return {\"code\":1,\"message\":\"熔斷限流了\"}") private DubboServiceOne dubboServiceOne; @GetMapping("/hello/{name}") public ApiResult sayHello(@PathVariable("name") String name){ return this.dubboServiceOne.sayHello(name); } }
/** * DubboServiceOneImpl * @author winfun * @date 2020/10/29 5:04 下午 **/ @DubboService(interfaceClass = DubboServiceOne.class) public class DubboServiceOneImpl implements DubboServiceOne { /*** * say hello * @author winfun * @param name name * @return {@link ApiResult<String> } **/ @SneakyThrows @Override public ApiResult<String> sayHello(String name) { // dubbo 接口默認超時時間爲1s,咱們這裏直接休眠5s Thread.sleep(5000); return ApiResult.success("hello "+name); }
狀況一:服務提供者沒起來app
這個時候服務消費者不會進行重試,只會直接進行服務降級,返回咱們設定的 Mock 值。框架
2021-01-27 18:02:12.288 [http-nio-8081-exec-1] [WARN ] [o.a.d.r.c.s.w.MockClusterInvoker] [/hello/name] [16117417328056102141] - [DUBBO] fail-mock: sayHello fail-mock enabled , url : zookeeper://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?application=dubbo-service&check=false&dubbo=2.0.2&init=false&interface=com.winfun.service.DubboServiceOne&lazy=true&methods=sayHello&mock=return+%7B%22code%22%3A1%2C%22message%22%3A%22%E7%86%94%E6%96%AD%E9%99%90%E6%B5%81%E4%BA%86%22%7D&pid=51874&qos.enable=false&reference.filter=default,dubboLogFilter,-sentinel.dubbo.consumer.filter®ister.ip=172.26.144.16&release=2.7.7&retries=0&side=consumer&sticky=false×tamp=1611741716665, dubbo version: 2.7.7, current host: 172.26.144.16 org.apache.dubbo.rpc.RpcException: No provider available from registry 127.0.0.1:2181 for service com.winfun.service.DubboServiceOne on consumer 172.26.144.16 use dubbo version 2.7.7, please check status of providers(disabled, not registered or in blacklist). //.....省略 2021-01-27 18:02:12.289 [http-nio-8081-exec-1] [INFO ] [o.a.d.r.c.s.w.MockClusterInvoker] [/hello/name] [16117417328056102141] - [DUBBO] Exception when try to invoke mock. Get mock invokers error for service:com.winfun.service.DubboServiceOne, method:sayHello, will construct a new mock with 'new MockInvoker()'., dubbo version: 2.7.7, current host: 172.26.144.16 org.apache.dubbo.rpc.RpcException: No provider available from registry 127.0.0.1:2181 for service com.winfun.service.DubboServiceOne on consumer 172.26.144.16 use dubbo version 2.7.7, please check status of providers(disabled, not registered or in blacklist). //....省略
你們可能會存在一個質疑點:我上面說的是不會進行重試,可是控制檯咱們看到了兩次異常;其實第二次異常,是獲取 MockInvoker 致使的,因此你們能夠先忽略,下面我會詳細分析 MockClusterInvoker 的源碼,就知道爲啥會有兩次異常了。分佈式
狀況二:服務提供者超時ide
注意:Dubbo 提供的服務降級機制,不支持對服務提供者業務異常的狀況。
這個時候消費者會進行 1+retries 次調用,而後再進行服務降級。
org.apache.dubbo.rpc.RpcException: Failed to invoke the method sayHello in the service com.winfun.service.DubboServiceOne. Tried 2 times of the providers [127.0.0.1:20881] (1/1) from the registry 127.0.0.1:2181 on the consumer 172.26.144.16 using the dubbo version 2.7.7. Last error is: Invoke remote method timeout. method: sayHello, provider: dubbo://127.0.0.1:20881/com.winfun.service.DubboServiceOne?anyhost=true&application=dubbo-service&check=false&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&init=false&interface=com.winfun.service.DubboServiceOne&lazy=true&methods=sayHello&mock=return+%7B%22code%22%3A1%2C%22message%22%3A%22%E7%86%94%E6%96%AD%E9%99%90%E6%B5%81%E4%BA%86%22%7D&pid=52020&qos.enable=false&reference.filter=default,dubboLogFilter,-sentinel.dubbo.consumer.filter®ister.ip=172.26.144.16&release=2.7.7&remote.application=dubbo-provider-one&retries=1&service.filter=default,dubboLogFilter&side=consumer&sticky=false×tamp=1611742232968, cause: org.apache.dubbo.remoting.TimeoutException: Waiting server-side response timeout by scan timer. start time: 2021-01-27 18:10:42.189, end time: 2021-01-27 18:10:43.204, client elapsed: 1 ms, server elapsed: 1014 ms, timeout: 1000 ms, request: Request [id=1, version=2.0.2, twoway=true, event=false, broken=false, data=null], channel: /172.26.144.16:56663 -> /172.26.144.16:20881 at org.apache.dubbo.rpc.cluster.support.FailoverClusterInvoker.doInvoke(FailoverClusterInvoker.java:113) //....省略
關於服務降級的邏輯,主要在 Dubbo 提供的 MockClusterInvoker 類中。
下面咱們直接看看 MockClusterInvoker 的源碼:
public class MockClusterInvoker<T> implements Invoker<T> { // ...省略 @Override public Result invoke(Invocation invocation) throws RpcException { Result result = null; // 從 consumer 的註冊url中獲取方法的 mock 屬性值 String value = getUrl().getMethodParameter(invocation.getMethodName(), MOCK_KEY, Boolean.FALSE.toString()).trim(); // mock 爲空或者false,不進行服務降級處理 if (value.length() == 0 || "false".equalsIgnoreCase(value)) { //no mock result = this.invoker.invoke(invocation); // 是否強行執行服務降級處理 } else if (value.startsWith("force")) { if (logger.isWarnEnabled()) { logger.warn("force-mock: " + invocation.getMethodName() + " force-mock enabled , url : " + getUrl()); } //force:direct mock result = doMockInvoke(invocation, null); } else { //fail-mock try { result = this.invoker.invoke(invocation); //fix:#4585 if(result.getException() != null && result.getException() instanceof RpcException){ RpcException rpcException= (RpcException)result.getException(); // 若是是業務異常,直接拋出 if(rpcException.isBiz()){ throw rpcException; }else { // 服務降級處理 result = doMockInvoke(invocation, rpcException); } } } catch (RpcException e) { // 若是是業務異常,直接拋出 if (e.isBiz()) { throw e; } if (logger.isWarnEnabled()) { logger.warn("fail-mock: " + invocation.getMethodName() + " fail-mock enabled , url : " + getUrl(), e); } // 服務降級處理 result = doMockInvoke(invocation, e); } } return result; } @SuppressWarnings({"unchecked", "rawtypes"}) private Result doMockInvoke(Invocation invocation, RpcException e) { Result result = null; Invoker<T> minvoker; // 獲取服務降級 Invoker 列表 List<Invoker<T>> mockInvokers = selectMockInvoker(invocation); if (CollectionUtils.isEmpty(mockInvokers)) { // 服務降級 Invoker 列表爲空,默認使用 Dubbo 提供的 MockInvoker 類 minvoker = (Invoker<T>) new MockInvoker(getUrl(), directory.getInterface()); } else { minvoker = mockInvokers.get(0); } try { // 服務降級處理 result = minvoker.invoke(invocation); } catch (RpcException me) { if (me.isBiz()) { result = AsyncRpcResult.newDefaultAsyncResult(me.getCause(), invocation); } else { throw new RpcException(me.getCode(), getMockExceptionMessage(e, me), me.getCause()); } } catch (Throwable me) { throw new RpcException(getMockExceptionMessage(e, me), me.getCause()); } return result; } //... }
從上面的源碼分析可知:
在 MockClusterInvoker#invoke 方法裏面,若是是業務異常是不會作 mock 作處理的。而其餘關於 RPC 的異常,是會作 mock 處理的,例如咱們這裏拿來測試的服務提供者超時異常和服務提供者不存在;
在 doMockInvoke 方法中,首先會調用 selectMockInvoker 方法會嘗試獲取 MockInvoker,而默認狀況下,都是沒有的,因此最後會使用 Dubbo 提供的 MockInvoker。
這裏也回答了狀況一例子中,會拋出兩次異常的緣由,就是由於獲取 MockInvoker 的時候,會調用 selectMockInvoker,而若是服務沒啓動,那麼必須還會再拋一次 No provider 的異常啦~
最後,你們若是有興趣,能夠自行繼續研究 MockInvoker 的源碼,特別是裏面的 invoke 和 parseMockValue 方法,分別是服務降級的邏輯和 Mock 屬性內容的解析。
若是不是看了 MockInvoker#parseMockValue 方法,一開始我都不知道如何返回 Json 格式的結果,只會直接返回 null,哈哈哈。因此源碼仍是得多看!
到此,關於 Dubbo 提供的服務降級的 Mock 機制已經簡單地過了一遍,可是咱們能夠發現有一個問題:
其實通過上面的例子分析和源碼分析,咱們會發現一個問題:因爲 dubbo 不帶熔斷機制,因此儘管每次由於 RPC 異常而致使調用失敗,也不會進行熔斷處理;即無論調用失敗多少次,消費者仍是會繼續進行調用。
其實這樣會致使服務的資源浪費:
因此,爲 dubbo 配一個熔斷機制是很是有必要的了。
我我的對熔斷的理解:
若是當調用失敗達到指定的次數,則將熔斷器打開一段時間,即將請求鏈路斷開;在指定時間內,都再也不讓消費者向提供者發送請求;當熔斷時間到了,就將熔斷器設置爲半打開的狀態,此時消費者能夠往提供者發送請求,並統計成功次數,若是達到指定的成功次數,熔斷器則變爲關閉狀態,即將請求鏈路打開,不然熔斷器又變回打開狀態。
因此說,只要給dubbo加上熔斷機制,不論是業務錯誤仍是請求超時,只要時間內達到了必定的次數就作上述的熔斷處理,這樣就能夠防止沒有必要的調用。
熔斷框架比較:https://www.icode9.com/content-4-620551.html
通過上面的對比,我我的是建議使用 Sentinel 的,由於它提供了更加靈活的使用方式,而且支持更多的規則,還提供了一個易用強大的控制檯。
Sentinel 提供了三大接入方式:利用 sentinel-core 組件進行硬代碼、利用 sentinel-annotation-aspectj 組件提供的註解功能、各類主流框架的接入方式。
下面我將分別使用三種方式爲 Dubbo接口 接入限流和熔斷的機制。
在接入以前,咱們須要注意的一個點是:要爲 Dubbo接口 接入熔斷&限流等機制,必需要爲 Dubbo接口 指定資源,而且爲資源初始化規則;不然只是引入了組件,也沒法使用 Sentinel 提供的限流&熔斷功能。
引入依賴:
引入 sentinel-core 依賴,直接植入硬代碼進行熔斷限流。
<dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-core</artifactId> <version>1.8.0</version> </dependency>
Controller 代碼:
/** * Say Hello * @author winfun * @date 2020/10/29 5:12 下午 **/ @Slf4j @RequestMapping("/hello") @RestController public class HelloController { public static final String RESOURCE_NAME = "dubboServiceOne"; @DubboReference(check = false,lazy = true,retries = 0) private DubboServiceOne dubboServiceOne; /** * 初始化流控規則和熔斷規則 * ps:由於咱們沒有接入 Sentinel Dashboard,因此得本身在代碼裏面設置好 */ static{ // 初始化流控規則 final List<FlowRule> flowRules = new ArrayList<>(); final List<DegradeRule> degradeRules = new ArrayList<>(); // 限流規則 final FlowRule flowRule = new FlowRule(); flowRule.setResource(RESOURCE_NAME); flowRule.setGrade(RuleConstant.FLOW_GRADE_QPS); // 1 QPS flowRule.setCount(1); flowRules.add(flowRule); // 熔斷規則 final DegradeRule degradeRule = new DegradeRule(); degradeRule.setResource(RESOURCE_NAME); degradeRule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT); // 2個異常數 degradeRule.setCount(2); // 時間窗口長度,單位爲秒 degradeRule.setTimeWindow(5); // 最小請求數 degradeRule.setMinRequestAmount(5); // 熔斷時長:當5秒內,10個請求裏面出現2個異常,則進行熔斷,熔斷時長爲10s degradeRule.setStatIntervalMs(10000); degradeRules.add(degradeRule); FlowRuleManager.loadRules(flowRules); DegradeRuleManager.loadRules(degradeRules); } @GetMapping("/{name}") public ApiResult sayHello(@PathVariable("name") final String name){ final String hello = this.sayHelloByDubbo2Code(name); return ApiResult.success(hello); } /*** * 接入Sentinel方式:植入硬代碼進行熔斷限流 * @author winfun * @param name name * @return {@link ApiResult<String> } **/ private ApiResult<String> sayHelloByDubbo2Code(final String name) { ApiResult<String> result; Entry entry = null; try { entry = SphU.entry(RESOURCE_NAME); result = this.dubboServiceOne.sayHello(name); } catch (BlockException e) { if (e instanceof DegradeException){ log.error("資源:{} 被熔斷了,message is {}",RESOURCE_NAME,e.getMessage()); result = ApiResult.fail("hello fallback"); }else { log.error("資源:{} 被流控了",RESOURCE_NAME); result = ApiResult.fail("hello block"); } } catch (Exception e){ log.error("業務處理髮生異常,exception is {}",e.getMessage()); // 若須要配置降級規則,須要經過這種方式記錄業務異常 Tracer.traceEntry(e, entry); result = ApiResult.fail("exception"); //throw new RuntimeException("業務處理髮生異常"); } finally { // 務必保證 exit,務必保證每一個 entry 與 exit 配對 if (entry != null) { entry.exit(); } } return result; } }
單元測試:
利用 MockMvc 來進行接口測試,循環調用。
@AutoConfigureMockMvc @SpringBootTest(classes = DubboServiceApplication.class) @RunWith(SpringRunner.class) public class ServiceOneApplicationTests { @Autowired private MockMvc mockMvc; @SneakyThrows @Test public void sayHello(){ for (int i = 0; i < 100; i++) { // 休眠500毫秒,即1秒兩次調用,能夠觸發流控規則 Thread.sleep(500); MvcResult result = mockMvc.perform(MockMvcRequestBuilders.get("/hello/winfun")) .andReturn(); System.out.println(result.getResponse().getContentAsString()); } } }
在看單元測試的結果前,咱們先分析一下咱們的流控&熔斷配置:
單元測試中,咱們是不會開啓服務提供者的,先看看控制檯打印的日誌信息:
2021-01-27 11:42:18.568 [main] [ERROR] [c.w.c.HelloController] [] [] - 資源:dubboServiceOne 發生異常,message is No provider available from registry 127.0.0.1:2181 for service com.winfun.service.DubboServiceOne on consumer 127.0.0.1 use dubbo version 2.7.7, please check status of providers(disabled, not registered or in blacklist). {"code":1,"message":"exception","data":null,"traceId":null} 2021-01-27 11:42:19.147 [main] [ERROR] [c.w.c.HelloController] [] [] - 資源:dubboServiceOne 被流控了 {"code":1,"message":"hello block","data":null,"traceId":null} 2021-01-27 11:42:19.653 [main] [ERROR] [c.w.c.HelloController] [] [] - 資源:dubboServiceOne 發生異常,message is No provider available from registry 127.0.0.1:2181 for service com.winfun.service.DubboServiceOne on consumer 127.0.0.1 use dubbo version 2.7.7, please check status of providers(disabled, not registered or in blacklist). {"code":1,"message":"exception","data":null,"traceId":null} 2021-01-27 11:42:20.160 [main] [ERROR] [c.w.c.HelloController] [] [] - 資源:dubboServiceOne 被流控了 {"code":1,"message":"hello block","data":null,"traceId":null} 2021-01-27 11:42:20.666 [main] [ERROR] [c.w.c.HelloController] [] [] - 資源:dubboServiceOne 發生異常,message is No provider available from registry 127.0.0.1:2181 for service com.winfun.service.DubboServiceOne on consumer 127.0.0.1 use dubbo version 2.7.7, please check status of providers(disabled, not registered or in blacklist). {"code":1,"message":"exception","data":null,"traceId":null} 2021-01-27 11:42:21.172 [main] [ERROR] [c.w.c.HelloController] [] [] - 資源:dubboServiceOne 被流控了 {"code":1,"message":"hello block","data":null,"traceId":null} 2021-01-27 11:42:21.681 [main] [ERROR] [c.w.c.HelloController] [] [] - 資源:dubboServiceOne 發生異常,message is No provider available from registry 127.0.0.1:2181 for service com.winfun.service.DubboServiceOne on consumer 127.0.0.1 use dubbo version 2.7.7, please check status of providers(disabled, not registered or in blacklist). {"code":1,"message":"exception","data":null,"traceId":null} 2021-01-27 11:42:22.190 [main] [ERROR] [c.w.c.HelloController] [] [] - 資源:dubboServiceOne 被流控了 {"code":1,"message":"hello block","data":null,"traceId":null} 2021-01-27 11:42:22.695 [main] [ERROR] [c.w.c.HelloController] [] [] - 資源:dubboServiceOne 發生異常,message is No provider available from registry 127.0.0.1:2181 for service com.winfun.service.DubboServiceOne on consumer 127.0.0.1 use dubbo version 2.7.7, please check status of providers(disabled, not registered or in blacklist). {"code":1,"message":"exception","data":null,"traceId":null} 2021-01-27 11:42:23.202 [main] [ERROR] [c.w.c.HelloController] [] [] - 資源:dubboServiceOne 被流控了 {"code":1,"message":"hello block","data":null,"traceId":null} 2021-01-27 11:42:23.709 [main] [ERROR] [c.w.c.HelloController] [] [] - 資源:dubboServiceOne 發生異常,message is No provider available from registry 127.0.0.1:2181 for service com.winfun.service.DubboServiceOne on consumer 127.0.0.1 use dubbo version 2.7.7, please check status of providers(disabled, not registered or in blacklist). {"code":1,"message":"exception","data":null,"traceId":null} 2021-01-27 11:42:24.215 [main] [ERROR] [c.w.c.HelloController] [] [] - 資源:dubboServiceOne 被流控了 {"code":1,"message":"hello block","data":null,"traceId":null} 2021-01-27 11:42:24.724 [main] [ERROR] [c.w.c.HelloController] [] [] - 資源:dubboServiceOne 發生異常,message is No provider available from registry 127.0.0.1:2181 for service com.winfun.service.DubboServiceOne on consumer 127.0.0.1 use dubbo version 2.7.7, please check status of providers(disabled, not registered or in blacklist). {"code":1,"message":"exception","data":null,"traceId":null} 2021-01-27 11:42:25.232 [main] [ERROR] [c.w.c.HelloController] [] [] - 資源:dubboServiceOne 被流控了 {"code":1,"message":"hello block","data":null,"traceId":null} 2021-01-27 11:42:25.738 [main] [ERROR] [c.w.c.HelloController] [] [] - 資源:dubboServiceOne 被熔斷了,message is null {"code":1,"message":"hello fallback","data":null,"traceId":null} 2021-01-27 11:42:26.247 [main] [ERROR] [c.w.c.HelloController] [] [] - 資源:dubboServiceOne 被熔斷了,message is null {"code":1,"message":"hello fallback","data":null,"traceId":null} 2021-01-27 11:42:26.757 [main] [ERROR] [c.w.c.HelloController] [] [] - 資源:dubboServiceOne 被熔斷了,message is null {"code":1,"message":"hello fallback","data":null,"traceId":null} 2021-01-27 11:42:27.265 [main] [ERROR] [c.w.c.HelloController] [] [] - 資源:dubboServiceOne 被熔斷了,message is null {"code":1,"message":"hello fallback","data":null,"traceId":null} 2021-01-27 11:42:27.769 [main] [ERROR] [c.w.c.HelloController] [] [] - 資源:dubboServiceOne 被熔斷了,message is null {"code":1,"message":"hello fallback","data":null,"traceId":null} 2021-01-27 11:42:28.274 [main] [ERROR] [c.w.c.HelloController] [] [] - 資源:dubboServiceOne 被熔斷了,message is null {"code":1,"message":"hello fallback","data":null,"traceId":null} 2021-01-27 11:42:28.779 [main] [ERROR] [c.w.c.HelloController] [] [] - 資源:dubboServiceOne 被熔斷了,message is null {"code":1,"message":"hello fallback","data":null,"traceId":null} 2021-01-27 11:42:29.284 [main] [ERROR] [c.w.c.HelloController] [] [] - 資源:dubboServiceOne 被熔斷了,message is null {"code":1,"message":"hello fallback","data":null,"traceId":null} 2021-01-27 11:42:29.790 [main] [ERROR] [c.w.c.HelloController] [] [] - 資源:dubboServiceOne 發生異常,message is No provider available from registry 127.0.0.1:2181 for service com.winfun.service.DubboServiceOne on consumer 127.0.0.1 use dubbo version 2.7.7, please check status of providers(disabled, not registered or in blacklist). {"code":1,"message":"exception","data":null,"traceId":null} 2021-01-27 11:42:30.299 [main] [ERROR] [c.w.c.HelloController] [] [] - 資源:dubboServiceOne 被流控了 {"code":1,"message":"hello block","data":null,"traceId":null} 2021-01-27 11:42:30.803 [main] [ERROR] [c.w.c.HelloController] [] [] - 資源:dubboServiceOne 被熔斷了,message is null {"code":1,"message":"hello fallback","data":null,"traceId":null} 2021-01-27 11:42:31.309 [main] [ERROR] [c.w.c.HelloController] [] [] - 資源:dubboServiceOne 被熔斷了,message is null {"code":1,"message":"hello fallback","data":null,"traceId":null} 2021-01-27 11:42:31.814 [main] [ERROR] [c.w.c.HelloController] [] [] - 資源:dubboServiceOne 被熔斷了,message is null {"code":1,"message":"hello fallback","data":null,"traceId":null} // .....
總結:
咱們能夠看到,控制檯的日誌打印是符合咱們的預期的:
而且,流控規則的優先級優先於熔斷規則。
引入依賴:
引入 sentinel-annotation-aspectj 依賴。
<dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-transport-simple-http</artifactId> <version>1.8.0</version> </dependency>
注入切面:
手動注入 SentinelResourceAspect,由於 Sentinel 提供的這個切面,是沒有 @Component 等註解的,因此仍是得本身注入到 Spring 容器中。
/** * 須要引入一下,由於 Sentile 提供的這個切面是沒有加 @Component 註解的 * @return */ @Bean public SentinelResourceAspect sentinelResourceAspect() { return new SentinelResourceAspect(); }
咱們使用這個 @SentinelResource 註解,可直接在 Controller 的方法加註解,也能夠本身再弄一個 Service 來加註解,這兩個均可以.
由於咱們的 Controller 層就是隻調用了 Dubbo 接口,因此就直接在 Controller 的方法上面加註解了,而若是業務是很是多的,那就能夠考慮單獨弄個 Service 來給 Dubbo 接口再包一層。
Spring AOP 同時支持 JDK 和 CGLIB 動態代理,SpringBoot 2.x 默認使用 CGLIB 動態代理。
Controller 代碼:
/** * Say Hello * @author winfun * @date 2020/10/29 5:12 下午 **/ @Slf4j @RequestMapping("/hello") @RestController public class HelloController { public static final String RESOURCE_NAME = "dubboServiceOne"; @DubboReference(check = false,lazy = true,retries = 0) private DubboServiceOne dubboServiceOne; /** * 初始化流控規則和熔斷規則 * ps:由於咱們沒有接入 Sentinel Dashboard,因此得本身在代碼裏面設置好 */ static{ // 初始化流控規則 final List<FlowRule> flowRules = new ArrayList<>(); final List<DegradeRule> degradeRules = new ArrayList<>(); // 限流規則 final FlowRule flowRule = new FlowRule(); flowRule.setResource(RESOURCE_NAME); flowRule.setGrade(RuleConstant.FLOW_GRADE_QPS); // 1 QPS flowRule.setCount(1); flowRules.add(flowRule); // 熔斷規則 final DegradeRule degradeRule = new DegradeRule(); degradeRule.setResource(RESOURCE_NAME); degradeRule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT); // 2個異常數 degradeRule.setCount(1); // 時間窗口長度,單位爲秒 degradeRule.setTimeWindow(5); // 最小請求數 degradeRule.setMinRequestAmount(5); // 熔斷時長:當5秒內,10個請求裏面出現2個異常,則進行熔斷,熔斷時長爲10s degradeRule.setStatIntervalMs(10000); degradeRules.add(degradeRule); FlowRuleManager.loadRules(flowRules); DegradeRuleManager.loadRules(degradeRules); } @GetMapping("/{name}") @SentinelResource(value=RESOURCE_NAME,fallback = "sayHelloFallback",blockHandler = "sayHelloBlock") public ApiResult sayHello(@PathVariable("name") final String name){ String hello = this.dubboServiceOne.sayHello(name); return ApiResult.success(hello); } /** * Fallback 函數,函數簽名與原函數一致或加一個 Throwable 類型的參數. * @param name * @param throwable * @return {@link ApiResult<String> } */ public ApiResult<String> sayHelloFallback(final String name, final Throwable throwable){ log.error("資源:{} 發生異常,message is {}",RESOURCE_NAME,throwable.getMessage()); return ApiResult.fail("hello exception"); } /** * BlockHandler 函數 * blockHandler 函數訪問範圍須要是 public,返回類型須要與原方法相匹配,參數類型須要和原方法相匹配而且最後加一個額外的參數,類型爲 BlockException * @param name * @param e * @return {@link ApiResult<String> } */ public ApiResult<String> sayHelloBlock(final String name, final BlockException e){ ApiResult<String> result; if (e instanceof DegradeException){ log.error("資源:{} 被熔斷了,message is {}",RESOURCE_NAME,e.getMessage()); result = ApiResult.fail("hello fallback"); }else { log.error("資源:{} 被流控了",RESOURCE_NAME); result = ApiResult.fail("hello block"); } return result; } }
bolckHandler 和 fallbackHandler 的返回值都要原方法一致。
單元測試:
@AutoConfigureMockMvc @SpringBootTest(classes = DubboServiceApplication.class) @RunWith(SpringRunner.class) public class ServiceOneApplicationTests { @Autowired private MockMvc mockMvc; @SneakyThrows @Test public void sayHello(){ for (int i = 0; i < 100; i++) { // 休眠500毫秒,即1秒兩次調用,能夠出發流控規則 Thread.sleep(500); MvcResult result = mockMvc.perform(MockMvcRequestBuilders.get("/hello/winfun")) .andReturn(); System.out.println(result.getResponse().getContentAsString()); } } }
控制檯打印:能夠看到和上面使用 Sentinel-core 植入硬代碼的打印是同樣的
2021-01-27 11:43:36.995 [main] [ERROR] [c.w.c.HelloController] [] [] - 資源:dubboServiceOne 發生異常,message is No provider available from registry 127.0.0.1:2181 for service com.winfun.service.DubboServiceOne on consumer 127.0.0.1 use dubbo version 2.7.7, please check status of providers(disabled, not registered or in blacklist). {"code":1,"message":"hello exception","data":null,"traceId":null} 2021-01-27 11:43:37.560 [main] [ERROR] [c.w.c.HelloController] [] [] - 資源:dubboServiceOne 發生異常,message is No provider available from registry 127.0.0.1:2181 for service com.winfun.service.DubboServiceOne on consumer 127.0.0.1 use dubbo version 2.7.7, please check status of providers(disabled, not registered or in blacklist). {"code":1,"message":"hello exception","data":null,"traceId":null} 2021-01-27 11:43:38.098 [main] [ERROR] [c.w.c.HelloController] [] [] - 資源:dubboServiceOne 被流控了 {"code":1,"message":"hello block","data":null,"traceId":null} 2021-01-27 11:43:38.605 [main] [ERROR] [c.w.c.HelloController] [] [] - 資源:dubboServiceOne 發生異常,message is No provider available from registry 127.0.0.1:2181 for service com.winfun.service.DubboServiceOne on consumer 127.0.0.1 use dubbo version 2.7.7, please check status of providers(disabled, not registered or in blacklist). {"code":1,"message":"hello exception","data":null,"traceId":null} 2021-01-27 11:43:39.114 [main] [ERROR] [c.w.c.HelloController] [] [] - 資源:dubboServiceOne 被流控了 {"code":1,"message":"hello block","data":null,"traceId":null} 2021-01-27 11:43:39.620 [main] [ERROR] [c.w.c.HelloController] [] [] - 資源:dubboServiceOne 發生異常,message is No provider available from registry 127.0.0.1:2181 for service com.winfun.service.DubboServiceOne on consumer 127.0.0.1 use dubbo version 2.7.7, please check status of providers(disabled, not registered or in blacklist). {"code":1,"message":"hello exception","data":null,"traceId":null} 2021-01-27 11:43:40.129 [main] [ERROR] [c.w.c.HelloController] [] [] - 資源:dubboServiceOne 被流控了 {"code":1,"message":"hello block","data":null,"traceId":null} 2021-01-27 11:43:40.638 [main] [ERROR] [c.w.c.HelloController] [] [] - 資源:dubboServiceOne 發生異常,message is No provider available from registry 127.0.0.1:2181 for service com.winfun.service.DubboServiceOne on consumer 127.0.0.1 use dubbo version 2.7.7, please check status of providers(disabled, not registered or in blacklist). {"code":1,"message":"hello exception","data":null,"traceId":null} 2021-01-27 11:43:41.144 [main] [ERROR] [c.w.c.HelloController] [] [] - 資源:dubboServiceOne 被流控了 {"code":1,"message":"hello block","data":null,"traceId":null} 2021-01-27 11:43:41.651 [main] [ERROR] [c.w.c.HelloController] [] [] - 資源:dubboServiceOne 發生異常,message is No provider available from registry 127.0.0.1:2181 for service com.winfun.service.DubboServiceOne on consumer 127.0.0.1 use dubbo version 2.7.7, please check status of providers(disabled, not registered or in blacklist). {"code":1,"message":"hello exception","data":null,"traceId":null} 2021-01-27 11:43:42.160 [main] [ERROR] [c.w.c.HelloController] [] [] - 資源:dubboServiceOne 被流控了 {"code":1,"message":"hello block","data":null,"traceId":null} 2021-01-27 11:43:42.668 [main] [ERROR] [c.w.c.HelloController] [] [] - 資源:dubboServiceOne 發生異常,message is No provider available from registry 127.0.0.1:2181 for service com.winfun.service.DubboServiceOne on consumer 127.0.0.1 use dubbo version 2.7.7, please check status of providers(disabled, not registered or in blacklist). {"code":1,"message":"hello exception","data":null,"traceId":null} 2021-01-27 11:43:43.173 [main] [ERROR] [c.w.c.HelloController] [] [] - 資源:dubboServiceOne 被流控了 {"code":1,"message":"hello block","data":null,"traceId":null} 2021-01-27 11:43:43.679 [main] [ERROR] [c.w.c.HelloController] [] [] - 資源:dubboServiceOne 發生異常,message is No provider available from registry 127.0.0.1:2181 for service com.winfun.service.DubboServiceOne on consumer 127.0.0.1 use dubbo version 2.7.7, please check status of providers(disabled, not registered or in blacklist). {"code":1,"message":"hello exception","data":null,"traceId":null} 2021-01-27 11:43:44.185 [main] [ERROR] [c.w.c.HelloController] [] [] - 資源:dubboServiceOne 被流控了 {"code":1,"message":"hello block","data":null,"traceId":null} 2021-01-27 11:43:44.691 [main] [ERROR] [c.w.c.HelloController] [] [] - 資源:dubboServiceOne 發生異常,message is No provider available from registry 127.0.0.1:2181 for service com.winfun.service.DubboServiceOne on consumer 127.0.0.1 use dubbo version 2.7.7, please check status of providers(disabled, not registered or in blacklist). {"code":1,"message":"hello exception","data":null,"traceId":null} 2021-01-27 11:43:45.198 [main] [ERROR] [c.w.c.HelloController] [] [] - 資源:dubboServiceOne 被流控了 {"code":1,"message":"hello block","data":null,"traceId":null} 2021-01-27 11:43:45.704 [main] [ERROR] [c.w.c.HelloController] [] [] - 資源:dubboServiceOne 被熔斷了,message is null {"code":1,"message":"hello fallback","data":null,"traceId":null} 2021-01-27 11:43:46.208 [main] [ERROR] [c.w.c.HelloController] [] [] - 資源:dubboServiceOne 被熔斷了,message is null {"code":1,"message":"hello fallback","data":null,"traceId":null} 2021-01-27 11:43:46.714 [main] [ERROR] [c.w.c.HelloController] [] [] - 資源:dubboServiceOne 被熔斷了,message is null {"code":1,"message":"hello fallback","data":null,"traceId":null} 2021-01-27 11:43:47.222 [main] [ERROR] [c.w.c.HelloController] [] [] - 資源:dubboServiceOne 被熔斷了,message is null {"code":1,"message":"hello fallback","data":null,"traceId":null} 2021-01-27 11:43:47.729 [main] [ERROR] [c.w.c.HelloController] [] [] - 資源:dubboServiceOne 被熔斷了,message is null {"code":1,"message":"hello fallback","data":null,"traceId":null} 2021-01-27 11:43:48.233 [main] [ERROR] [c.w.c.HelloController] [] [] - 資源:dubboServiceOne 被熔斷了,message is null {"code":1,"message":"hello fallback","data":null,"traceId":null} 2021-01-27 11:43:48.736 [main] [ERROR] [c.w.c.HelloController] [] [] - 資源:dubboServiceOne 被熔斷了,message is null {"code":1,"message":"hello fallback","data":null,"traceId":null} 2021-01-27 11:43:49.247 [main] [ERROR] [c.w.c.HelloController] [] [] - 資源:dubboServiceOne 被熔斷了,message is null {"code":1,"message":"hello fallback","data":null,"traceId":null} 2021-01-27 11:43:49.758 [main] [ERROR] [c.w.c.HelloController] [] [] - 資源:dubboServiceOne 發生異常,message is No provider available from registry 127.0.0.1:2181 for service com.winfun.service.DubboServiceOne on consumer 127.0.0.1 use dubbo version 2.7.7, please check status of providers(disabled, not registered or in blacklist). {"code":1,"message":"hello exception","data":null,"traceId":null} 2021-01-27 11:43:50.266 [main] [ERROR] [c.w.c.HelloController] [] [] - 資源:dubboServiceOne 被流控了 {"code":1,"message":"hello block","data":null,"traceId":null} 2021-01-27 11:43:50.773 [main] [ERROR] [c.w.c.HelloController] [] [] - 資源:dubboServiceOne 被熔斷了,message is null {"code":1,"message":"hello fallback","data":null,"traceId":null} 2021-01-27 11:43:51.277 [main] [ERROR] [c.w.c.HelloController] [] [] - 資源:dubboServiceOne 被熔斷了,message is null {"code":1,"message":"hello fallback","data":null,"traceId":null} // .....
雖然使用 @SentinelResource 註解避免了 sentinel-core 那樣的硬代碼,可是對 Dubbo接口 增長熔斷機制仍是比較麻煩的,須要對每一個業務方法增長註解;可是別慌,Sentinel 針對 Dubbo 提供了組件 sentinel-apache-dubbo-adapter,只要引入這個依賴,便可對全部 Dubbo 接口進行熔斷和流控。
原理:
它的原理仍是比較簡單的,利用的是 Dubbo 提供的 Filter 機制,爲服務提供者和服務消費者分別創建了 SentinelDubboProviderFilter 和 SentinelDubboConsumerFilter 過濾器。在過濾器的 invoke 方法裏面就植入了 Sentinel 的代碼,大概以下:
private Result syncInvoke(Invoker<?> invoker, Invocation invocation) { Entry interfaceEntry = null; Entry methodEntry = null; String prefix = DubboAdapterGlobalConfig.getDubboConsumerResNamePrefixKey(); String interfaceResourceName = getInterfaceName(invoker, prefix); String methodResourceName = getMethodName(invoker, invocation, prefix); try { interfaceEntry = SphU.entry(interfaceResourceName, ResourceTypeConstants.COMMON_RPC, EntryType.OUT); methodEntry = SphU.entry(methodResourceName, ResourceTypeConstants.COMMON_RPC, EntryType.OUT, invocation.getArguments()); Result result = invoker.invoke(invocation); if (result.hasException()) { Tracer.traceEntry(result.getException(), interfaceEntry); Tracer.traceEntry(result.getException(), methodEntry); } return result; } catch (BlockException e) { return DubboAdapterGlobalConfig.getConsumerFallback().handle(invoker, invocation, e); } catch (RpcException e) { Tracer.traceEntry(e, interfaceEntry); Tracer.traceEntry(e, methodEntry); throw e; } finally { if (methodEntry != null) { methodEntry.exit(1, invocation.getArguments()); } if (interfaceEntry != null) { interfaceEntry.exit(); } } }
從上面源碼咱們能夠發現,這樣會有一個小問題;若是使用 sentinel-apache-dubbo-adapter 組件,Sentinel 沒法將 No provider xxxx
這個異常做爲熔斷機制的一個異常點,由於過濾器的 invoke 方法是須要先獲取到 Invoker 的,因此若是服務提供者未啓動,那麼在進入過濾器以前,就已經拋出異常了。至於要怎麼解決,只須要配合上面介紹的 Dubbo 自身提供的服務降級機制便可。
初始化規則:
使用以前,咱們還須要注意如何初始化規則,sentinel-apache-dubbo-adapter 組件對 Dubbo 接口的資源名定義有本身的一套規則:
全局 fallback 函數:
Sentinel Dubbo Adapter 還支持配置全局的 fallback 函數,能夠在 Dubbo 服務被限流/降級/負載保護的時候進行相應的 fallback 處理。用戶只須要實現自定義的 DubboFallback 接口,並經過 DubboFallbackRegistry 註冊便可。默認狀況會直接將 BlockException 包裝後拋出。
引入依賴:
<!-- 適配 dubbo 框架,裏面帶了 sentinel-core --> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-apache-dubbo-adapter</artifactId> <version>1.8.0</version> </dependency>
Controller:
由於咱們只是對 sentinel-apache-dubbo-adapter 組件作測試,因此咱們只針對 Dubbo 接口初始化了流控規則和熔斷規則;業務上,能夠更細粒度地對 Dubbo 接口的方法進行流控規則和熔斷規則的配置。
/** * Say Hello * @author winfun * @date 2020/10/29 5:12 下午 **/ @Slf4j @RestController public class HelloController { public static final String DUBBO_INTERFACE_RESOURCE_NAME = "com.winfun.service.DubboServiceOne"; @DubboReference(check = false,lazy = true,retries = 0) private DubboServiceOne dubboServiceOne; /** * 初始化流控規則和熔斷規則 * ps:由於咱們沒有接入 Sentinel Dashboard,因此得本身在代碼裏面設置好 */ static{ /** * 給dubbo接口設置流控,維度:接口或方法 * 以 DubboServiceOne 接口爲例子: * 接口:com.winfun.service.DubboServiceOne * 方法:com.winfun.service.DubboServiceOne:sayHello(java.lang.String) */ // 限流規則 final FlowRule dobboInterfaceFlowRule = new FlowRule(); dobboInterfaceFlowRule.setResource(DUBBO_INTERFACE_RESOURCE_NAME); dobboInterfaceFlowRule.setGrade(RuleConstant.FLOW_GRADE_QPS); // 1 QPS dobboInterfaceFlowRule.setCount(1); flowRules.add(dobboInterfaceFlowRule); // 熔斷規則 final DegradeRule dubboInterfaceDegradeRule = new DegradeRule(); dubboInterfaceDegradeRule.setResource(DUBBO_INTERFACE_RESOURCE_NAME); dubboInterfaceDegradeRule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT); // 2個異常數 dubboInterfaceDegradeRule.setCount(2); // 時間窗口長度,單位爲秒 dubboInterfaceDegradeRule.setTimeWindow(5); // 最小請求數 dubboInterfaceDegradeRule.setMinRequestAmount(5); // 熔斷時長:當5秒內,10個請求裏面出現2個異常,則進行熔斷,熔斷時長爲10s dubboInterfaceDegradeRule.setStatIntervalMs(10000); degradeRules.add(dubboInterfaceDegradeRule); FlowRuleManager.loadRules(flowRules); DegradeRuleManager.loadRules(degradeRules); // 全局 Fallback 函數 DubboAdapterGlobalConfig.setConsumerFallback(new DefaultDubboFallback()); } @GetMapping("/hello/{name}") public ApiResult sayHello(@PathVariable("name") final String name){ return this.dubboServiceOne.sayHello(name); } }
單元測試:
@Slf4j @AutoConfigureMockMvc @SpringBootTest(classes = DubboServiceApplication.class) @RunWith(SpringRunner.class) public class ServiceOneApplicationTests { @Autowired private MockMvc mockMvc; @Test public void sayHello2(){ for (int i = 0; i < 100; i++) { try { // 休眠500毫秒,即1秒兩次調用,能夠出發流控規則 Thread.sleep(500); MvcResult result = mockMvc.perform(MockMvcRequestBuilders.get("/hello/winfun")) .andReturn(); log.info("業務處理成功,結果:{}", result.getResponse().getContentAsString()); } catch (Exception e) { log.error("業務處理髮生異常,exception is {}", e.getMessage()); } } } }
控制檯打印:因爲我此次 Dubbo 的限流和熔斷是基於 Trace 組件來搭建的,因此日誌會有點多,下面在作單元測試時,會將Trace的log信息去除掉;因爲配置和 Sentinel-core 例子、@SentinelResource 例子的配置是同樣的,所以咱們能夠發現日誌一模一樣。
2021-01-27 14:49:56.883 [main] [ERROR] [c.w.s.ServiceOneApplicationTests] [] [] - 業務處理髮生異常,exception is Request processing failed; nested exception is java.lang.Exception: Exception 來搞事了 2021-01-27 14:49:57.428 [main] [ERROR] [c.w.f.DefaultDubboFallback] [] [] - 資源:com.winfun.service.DubboServiceOne 被流控了 {"code":1,"message":"hello block","data":null,"traceId":null} 2021-01-27 14:49:58.059 [main] [ERROR] [c.w.s.ServiceOneApplicationTests] [] [] - 業務處理髮生異常,exception is Request processing failed; nested exception is java.lang.Exception: Exception 來搞事了 2021-01-27 14:49:58.562 [main] [ERROR] [c.w.f.DefaultDubboFallback] [] [] - 資源:com.winfun.service.DubboServiceOne 被流控了 {"code":1,"message":"hello block","data":null,"traceId":null} 2021-01-27 14:49:59.079 [main] [ERROR] [c.w.s.ServiceOneApplicationTests] [] [] - 業務處理髮生異常,exception is Request processing failed; nested exception is java.lang.Exception: Exception 來搞事了 2021-01-27 14:49:59.582 [main] [ERROR] [c.w.f.DefaultDubboFallback] [] [] - 資源:com.winfun.service.DubboServiceOne 被流控了 {"code":1,"message":"hello block","data":null,"traceId":null} 2021-01-27 14:50:00.098 [main] [ERROR] [c.w.s.ServiceOneApplicationTests] [] [] - 業務處理髮生異常,exception is Request processing failed; nested exception is java.lang.Exception: Exception 來搞事了 2021-01-27 14:50:00.601 [main] [ERROR] [c.w.f.DefaultDubboFallback] [] [] - 資源:com.winfun.service.DubboServiceOne 被流控了 {"code":1,"message":"hello block","data":null,"traceId":null} 2021-01-27 14:50:01.113 [main] [ERROR] [c.w.s.ServiceOneApplicationTests] [] [] - 業務處理髮生異常,exception is Request processing failed; nested exception is java.lang.Exception: Exception 來搞事了 2021-01-27 14:50:01.617 [main] [ERROR] [c.w.f.DefaultDubboFallback] [] [] - 資源:com.winfun.service.DubboServiceOne 被流控了 {"code":1,"message":"hello block","data":null,"traceId":null} 2021-01-27 14:50:02.129 [main] [ERROR] [c.w.s.ServiceOneApplicationTests] [] [] - 業務處理髮生異常,exception is Request processing failed; nested exception is java.lang.Exception: Exception 來搞事了 2021-01-27 14:50:02.633 [main] [ERROR] [c.w.f.DefaultDubboFallback] [] [] - 資源:com.winfun.service.DubboServiceOne 被流控了 {"code":1,"message":"hello block","data":null,"traceId":null} 2021-01-27 14:50:03.147 [main] [ERROR] [c.w.s.ServiceOneApplicationTests] [] [] - 業務處理髮生異常,exception is Request processing failed; nested exception is java.lang.Exception: Exception 來搞事了 2021-01-27 14:50:03.653 [main] [ERROR] [c.w.f.DefaultDubboFallback] [] [] - 資源:com.winfun.service.DubboServiceOne 被流控了 {"code":1,"message":"hello block","data":null,"traceId":null} 2021-01-27 14:50:04.170 [main] [ERROR] [c.w.s.ServiceOneApplicationTests] [] [] - 業務處理髮生異常,exception is Request processing failed; nested exception is java.lang.Exception: Exception 來搞事了 2021-01-27 14:50:04.676 [main] [ERROR] [c.w.f.DefaultDubboFallback] [] [] - 資源:com.winfun.service.DubboServiceOne 被流控了 {"code":1,"message":"hello block","data":null,"traceId":null} 2021-01-27 14:50:05.183 [main] [ERROR] [c.w.f.DefaultDubboFallback] [] [] - 資源:com.winfun.service.DubboServiceOne 被熔斷了,message is null {"code":1,"message":"hello fallback","data":null,"traceId":null} 2021-01-27 14:50:05.688 [main] [ERROR] [c.w.f.DefaultDubboFallback] [] [] - 資源:com.winfun.service.DubboServiceOne 被熔斷了,message is null {"code":1,"message":"hello fallback","data":null,"traceId":null} 2021-01-27 14:50:06.194 [main] [ERROR] [c.w.f.DefaultDubboFallback] [] [] - 資源:com.winfun.service.DubboServiceOne 被熔斷了,message is null {"code":1,"message":"hello fallback","data":null,"traceId":null} 2021-01-27 14:50:06.700 [main] [ERROR] [c.w.f.DefaultDubboFallback] [] [] - 資源:com.winfun.service.DubboServiceOne 被熔斷了,message is null {"code":1,"message":"hello fallback","data":null,"traceId":null} 2021-01-27 14:50:07.207 [main] [ERROR] [c.w.f.DefaultDubboFallback] [] [] - 資源:com.winfun.service.DubboServiceOne 被熔斷了,message is null {"code":1,"message":"hello fallback","data":null,"traceId":null} 2021-01-27 14:50:07.712 [main] [ERROR] [c.w.f.DefaultDubboFallback] [] [] - 資源:com.winfun.service.DubboServiceOne 被熔斷了,message is null {"code":1,"message":"hello fallback","data":null,"traceId":null} 2021-01-27 14:50:08.220 [main] [ERROR] [c.w.f.DefaultDubboFallback] [] [] - 資源:com.winfun.service.DubboServiceOne 被熔斷了,message is null {"code":1,"message":"hello fallback","data":null,"traceId":null} 2021-01-27 14:50:08.726 [main] [ERROR] [c.w.f.DefaultDubboFallback] [] [] - 資源:com.winfun.service.DubboServiceOne 被熔斷了,message is null {"code":1,"message":"hello fallback","data":null,"traceId":null} 2021-01-27 14:50:09.246 [main] [ERROR] [c.w.s.ServiceOneApplicationTests] [] [] - 業務處理髮生異常,exception is Request processing failed; nested exception is java.lang.Exception: Exception 來搞事了 2021-01-27 14:50:09.749 [main] [ERROR] [c.w.f.DefaultDubboFallback] [] [] - 資源:com.winfun.service.DubboServiceOne 被流控了 {"code":1,"message":"hello block","data":null,"traceId":null} 2021-01-27 14:50:10.255 [main] [ERROR] [c.w.f.DefaultDubboFallback] [] [] - 資源:com.winfun.service.DubboServiceOne 被熔斷了,message is null {"code":1,"message":"hello fallback","data":null,"traceId":null} 2021-01-27 14:50:10.762 [main] [ERROR] [c.w.f.DefaultDubboFallback] [] [] - 資源:com.winfun.service.DubboServiceOne 被熔斷了,message is null {"code":1,"message":"hello fallback","data":null,"traceId":null} 2021-01-27 14:50:11.270 [main] [ERROR] [c.w.f.DefaultDubboFallback] [] [] - 資源:com.winfun.service.DubboServiceOne 被熔斷了,message is null {"code":1,"message":"hello fallback","data":null,"traceId":null} // .....
到此,關於 Sentinel 提供的三種接入方式,咱們都已經嘗試了;只不過對於熔斷機制的配置還沒所有完一遍,咱們上面例子僅僅是利用了 異常數的機制,其實還有慢調用機制和異常數比例機制,固然了,這些機制其實在代碼層面的玩法都是同樣的,你們能夠自行嘗試。
固然了,除了流控和熔斷,Sentinel 還提供了多種機制,例如熱點參數限流、系統自適應限流,黑白名單控制和實時監控數據等等,感興趣的能夠去研究一下,這裏不作詳細介紹和使用。
可是到這裏還沒結束,由於咱們能夠發現,咱們須要在代碼層面爲每個須要限流或者熔斷的接口初始化限流規則或熔斷規則,因此這個是很是麻煩的,不過不用怕,Sentinel 還提供了 Sentinel控制檯 組件,咱們能夠接入這個組件,例如它來給資源配置流控、熔斷和熱點等規則。
這個不用多詳細介紹,直接看官方文檔便可:
啓動控制檯
主要是先獲取到 Sentinel-Dashboard 這個組件的 jar 包,而後啓動便可;由於 Sentinel-Dashboard 這個項目是一個 Spring Boot 項目,咱們直接打成 Jar 包,利用 java -jar 命令啓動便可,沒有任何難度。
接着須要注意的是,Sentinel 1.6.0 以後,Sentinel控制檯增長了登陸校驗,默認帳號和密碼都是 sentinel。
這個也不用多詳細介紹,直接看官方文檔便可:
客戶端接入控制檯
客戶端接入主要就是引入依賴,而後增長啓動命令便可,也是很是的簡單。
可是要注意的一個點是:若是是 Spring Cloud 項目,能夠在配置文件配置 Sentinel控制檯 的配置;但若是是 Spring Boot+Dubbo的項目,必定要按照文檔的走,利用 JVM 參數來指定 Sentinel控制檯 的配置。
接着,不是項目一啓動,就會在 Sentinel控制檯 爲全部 Dubbo接口 建立對應的資源,而是當第一次成功調用 Dubbo接口 後,纔會在 Sentinel控制檯 初始化對應的資源;而且,只有當服務被第一次成功調用接口,纔會在 Sentinel控制檯 顯示。
當資源建立後,咱們能夠在 簇點鏈路中,爲資源新建流控、熔斷等規則。
到此,關於 Dubbo 的服務降級和熔斷機制,已經所有介紹完畢,配合上例子和源碼的講解,你們應該會有必定程度上的理解了。
在分佈式系統和微服務架構中,服務間堅老是互相依賴,爲服務加入熔斷和降級是必不可少的,但願你們能一塊兒研究研究,若是有什麼疑惑或者建議,可評論在下方,歡迎點贊收藏~