springcloud 總集:https://www.tapme.top/blog/detail/2019-02-28-11-33java
本次用到所有代碼見文章最下方。git
全部的系統都會遇到故障,分佈式系統單點故障機率更高。如何構建應用程序來應對故障,是每一個軟件開發人員工做的關鍵部分。可是一般在構建系統時,大多數工程師只考慮到基礎設施或關鍵服務完全發生故障,使用諸如集羣關鍵服務器、服務間的負載均衡以及異地部署等技術。儘管這些方法考慮到組件系統的完全故障,但他們之解決了構建彈性系統的一小部分問題。當服務崩潰時,很容易檢測到該服務以及失效,所以應用程序能夠饒過它。然而,當服務運行緩慢時,檢測到這個服務性能愈加低下並繞過它是很是困難的,由於如下幾個緣由:github
性能較差的遠程服務會致使很大的潛在問題,它們不只難以檢測,還會觸發連鎖反應,從而影響整個應用程序生態系統。若是沒有適當的保護措施,一個性能不佳的服務能夠迅速拖垮整個應用程序。基於雲、基於微服務的應用程序特別容易受到這些類型的終端影響,由於這些應用由大量細粒度的分佈式服務組成,這些服務在完成用戶的事務時涉及不一樣的基礎設施。spring
客戶端彈性模式是在遠程服務發生錯誤或表現不佳時保護遠程資源(另外一個微服務調用或者數據庫查詢)免於崩潰。這些模式的目標是爲了能讓客戶端「快速失敗」,不消耗諸如數據庫鏈接、線程池之類的資源,還能夠避免遠程服務的問題向客戶端的消費者進行傳播,引起「雪崩」效應。spring cloud 主要使用的有四種客戶端彈性模式:數據庫
上一篇已經說過,這裏再也不贅述。json
本模式模仿的是電路中的斷路器。有了軟件斷路器,當遠程服務被調用時,斷路器將監視這個調用,若是調用時間太長,斷路器將介入並中斷調用。此外,若是對某個遠程資源的調用失敗次數達到某個閾值,將會採起快速失敗策略,阻止未來調用失敗的遠程資源。服務器
<!-- more -->微信
當遠程調用失敗時,將執行替代代碼路徑,並嘗試經過其餘方式來處理操做,而不是產生一個異常。也就是爲遠程操做提供一個應急措施,而不是簡單的拋出異常。架構
艙壁模式是創建在造船的基礎概念上。咱們都知道一艘船會被劃分爲多個水密艙(艙壁),於是即便少數幾個部位被擊穿漏水,整艘船並不會被淹沒。將這個概念帶入到遠程調用中,若是全部調用都使用的是同一個線程池來處理,那麼頗有可能一個緩慢的遠程調用會拖垮整個應用程序。在艙壁模式中能夠隔離每一個遠程資源,並分配各自的線程池,使之互不影響。app
下圖展現了這些模式是如何運用到微服務中的:
使用 Netflix 的 Hystrix 庫來實現上述彈性模式。繼續使用上一節的項目,給 licensingservice 服務實現彈性模式。
首先修改 POM 文件,添加下面兩個依賴:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-hystrix</artifactId> </dependency> <!--本依賴不是必須的,spring-cloud-starter-hystrix已經帶了,可是在Camden.SR5發行版本中使用了1.5.6,這個版本有一個不一致的地方,在沒有後備的狀況下會拋出java.lang.reflect.UndeclaredThrowableException而不是com.netflix.hystrix.exception.HystrixRuntimeException, 在後續版本中修復了這個問題--> <dependency> <groupId>com.netflix.hystrix</groupId> <artifactId>hystrix-javanica</artifactId> <version>1.5.9</version> </dependency>
而後在啓動類上加入@EnableCircuitBreaker
啓用 Hystrix。
首先修改 organizationservice 項目中的 OrganizationController,模擬延遲,每隔兩次讓線程 sleep 2 秒
@RestController public class OrganizationController { private static int count=1; @GetMapping(value = "/organization/{orgId}") public Object getOrganizationInfo(@PathVariable("orgId") String orgId) throws Exception{ if(count%2==0){ TimeUnit.SECONDS.sleep(2); } count++; Map<String, String> data = new HashMap<>(2); data.put("id", orgId); data.put("name", orgId + "公司"); return data; } }
只需在方法上添加@HystrixCommand
,便可實現超時短路。若是 Spring 掃描到該註解註釋的類,它將動態生成一個代理,來包裝這個方法,並經過專門用於處理遠程調用的線程池來管理對該方法的全部調用。
修改 licensingservice 服務中的 OrganizationByRibbonService,OrganizationFeignClient,給其中的方法加上@HystrixCommand
的註解。而後再訪問接口localhost:10011/licensingByRibbon/11313,localhost:10011/licensingByFeign/11313。屢次訪問可發現拋出錯誤com.netflix.hystrix.exception.HystrixRuntimeException
,斷路器生效,默認狀況下操時時間爲 1s。
{ "timestamp": 1543823192424, "status": 500, "error": "Internal Server Error", "exception": "com.netflix.hystrix.exception.HystrixRuntimeException", "message": "OrganizationFeignClient#getOrganization(String) timed-out and no fallback available.", "path": "/licensingByFeign/11313/" }
可經過設置註解參數來修改操時時間。設置超時時間大於 2s 後便不會報操時錯誤。(不知道爲何在 Feign 中設置失敗,ribbon 中正常。)。通常都是將配置寫在配置文件中。
@HystrixCommand(commandProperties = { @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "20000") })
因爲遠程資源的消費者和資源自己之間存在存在一個"中間人",所以開發人員可以攔截服務故障,並選擇替代方案。在 Hystrix 中進行後備處理,很是容易實現。
只需在@HystrixCommand
註解中加入屬性 fallbackMethod="methodName",那麼在執行失敗時,便會執行後備方法。注意防備方法必須和被保護方法在同一個類中,而且方法簽名必須相同。修改 licensingservice 中 service 包下的 OrganizationByRibbonService 類,改成以下:
@Component public class OrganizationByRibbonService { private RestTemplate restTemplate; @Autowired public OrganizationByRibbonService(RestTemplate restTemplate) { this.restTemplate = restTemplate; } @HystrixCommand(commandProperties = { @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1000") },fallbackMethod = "getOrganizationWithRibbonBackup") public Organization getOrganizationWithRibbon(String id) throws Exception { ResponseEntity<Organization> responseEntity = restTemplate.exchange("http://organizationservice/organization/{id}", HttpMethod.GET, null, Organization.class, id); return responseEntity.getBody(); } public Organization getOrganizationWithRibbonBackup(String id)throws Exception{ Organization organization = new Organization(); organization.setId("0"); organization.setName("組織服務調用失敗"); return organization; } }
啓動應用,屢次訪問localhost:10011/licensingByRibbon/11313/,能夠發現調用失敗時,會啓用後備方法。
在 feign 中實現後備模式,須要編寫一個 feign 接口的實現類,而後在 feign 接口中指定該類。以 licensingservice 爲例。首先在 client 包中添加一個 OrganizationFeignClientImpl 類,代碼以下:
@Component public class OrganizationFeignClientImpl implements OrganizationFeignClient{ @Override public Organization getOrganization(String orgId) { Organization organization=new Organization(); organization.setId("0"); organization.setName("後備模式返回的數據"); return organization; } }
而後修改 OrganizationFeignClient 接口的註解,將@FeignClient("organizationservice")
改成@FeignClient(name="organizationservice",fallback = OrganizationFeignClientImpl.class
。
重啓項目,屢次訪問localhost:10011/licensingByFeign/11313/,可發現後備服務起做用了。
在確認是否要啓用後備服務時,要注意如下兩點:
在基於微服務的應用程序中,一般須要調用多個微服務來完成特定的任務,在不適用艙壁的模式下,這些調用默認是使用同一批線程來執行調用的,而這些線程是爲了處理整個 Java 容器的請求而預留的。所以在存在大量請求的狀況下,一個服務出現性能問題會致使 Java 容器內的全部線程被佔用,同時阻塞新請求,最終容器完全崩潰。
Hystrix 使用線程池來委派全部對遠程服務的調用,默認狀況下這個線程池有 10 個工做線程。可是這樣很容易出現一個運行緩慢的服務佔用所有的線程,全部 hystrix 提供了一種一種易於使用的機制,在不一樣的遠程資源調用間建立‘艙壁’,將不一樣服務的調用隔離到不一樣的線程池中,使之互不影響。
要實現隔離的線程池,只須要在@HystrixCommand
上加入線程池的註解,這裏以 ribbon 爲例(Feign 相似)。修改 licensingservice 中 service 包下的 OrganizaitonByRibbonService 類,將getOrganizationWithRibbon
方法的註解改成以下:
@HystrixCommand(commandProperties = { @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1000") }, fallbackMethod = "getOrganizationWithRibbonBackup", threadPoolKey = "licenseByOrgThreadPool", threadPoolProperties = { @HystrixProperty(name = "coreSize", value = "30"), @HystrixProperty(name = "maxQueueSize", value = "10") })
若是將maxQueueSize
屬性值設爲-1,將使用SynchronousQueue
保存全部的傳入請求,同步隊列會強制要求正在處理中的請求數量永遠不能超過線程池的大小。設爲大於 1 的值將使用LinkedBlockingQueue
。
注意:示例代碼中都是硬編碼屬性值到 Hystrix 註解中的。在實際應用環境中,通常都是將配置項配置在 Spring Cloud Config 中的,方便統一管理。
本次用到所有代碼:點擊跳轉
本篇原創發佈於:FleyX 的我的博客
掃碼關注微信公衆號:FleyX 學習筆記,獲取更多幹貨