簡介
Spring Cloud Feign是一個聲明式的Web Service客戶端,它的目的就是讓Web Service調用更加簡單。Feign提供了HTTP請求的模板,通過編寫簡單的接口和插入注解,就可以定義好HTTP請求的參數、格式、地址等信息。Feign會完全代理HTTP請求,開發時只需要像調用方法一樣調用它就可以完成服務請求及相關處理。開源地址:https://github.com/OpenFeign/feign。Feign整合了Ribbon負載和Hystrix熔斷,可以不再需要顯式地使用這兩個組件??傮w來說,Feign具有如下特性:
- 可插拔的注解支持,包括Feign注解和JAX-RS注解;
- 支持可插拔的HTTP編碼器和解碼器;
- 支持Hystrix和它的Fallback;
- 支持Ribbon的負載均衡;
- 支持HTTP請求和響應的壓縮。
Spring Cloud Feign致力于處理客戶端與服務器之間的調用需求。隨著業務的擴展和微服務數量的增多,不可避免的需要面對如下問題:
- 彈性客戶端
- 雪崩效應
簡單來說,使用Spring Cloud Feign組件,他本身整合了Ribbon和Hystrix??稍O計一套穩定可靠的彈性客戶端調用方案,避免整個系統出現雪崩效應。
雪崩效應
在微服務架構中,微服務是完成一個單一的業務功能,這樣做的好處是可以做到解耦,每個微服務可以獨立演進。但是,一個應用可能會有多個微服務組成,微服務之間的數據交互通過遠程過程調用完成。這就帶來一個問題,假設微服務A調用微服務B和微服務C,微服務B和微服務C又調用其它的微服務,這就是所謂的“扇出”。如果扇出的鏈路上某個微服務的調用響應時間過長或者不可用,對微服務A的調用就會占用越來越多的系統資源,進而引起系統崩潰,產生“雪崩效應”。引發雪崩效應的原因有:
- 硬件故障:如服務器宕機,機房斷電,光纖被挖斷等;
- 流量激增:如異常流量,重試加大流量等;
- 緩存穿透:一般發生在應用重啟,所有緩存失效時,以及短時間內大量緩存失效時。大量的緩存不命中,使請求直擊后端服務,造成服務提供者超負荷運行,引起服務不可用;
- 程序BUG:如程序邏輯導致內存泄漏,JVM長時間FullGC等;
- 同步等待:服務間采用同步調用模式,同步等待造成的資源耗盡;
- 服務降級故障:服務的降級可以是以間歇性的故障開始,并形成不可逆轉的勢頭??赡荛_始只是一小部分服務調用變慢,直到突然間應用程序容器耗盡了線程(所有線程都在等待調用完成)并徹底崩潰。
彈性客戶端
客戶端彈性模式是在遠程服務發生錯誤或表現不佳時保護遠程資源(另一個微服務調用或者數據庫查詢)免于崩潰。這些模式的目標是為了能讓客戶端“快速失敗”,不消耗諸如數據庫連接、線程池之類的資源,還可以避免遠程服務的問題向客戶端的消費者進行傳播,引發“雪崩”效應。spring cloud Feign主要使用的有四種客戶端彈性模式:
客戶端負載均衡(client load balance)模式
Spring Cloud Feign集成Ribbon處理。Ribbon 是一個基于 http 和 tcp 客戶端的負載均衡,可以配置在客戶端,以輪詢、隨機、權重(權重意思是請求時間越久的server,其被分配給客戶端使用的可能性就越低。)等方式實現負載均衡。Feign其實不是做負載均衡的,負載均衡是Ribbon的功能,Feign只是集成了Ribbon 而已。Feign的作用的替代RestTemplate,性能比較低,但是可以使代碼可讀性很強。
斷路器(circuit breaker)模式
本模式模仿的是電路中的斷路器。有了軟件斷路器,當遠程服務被調用時,斷路器將監視這個調用,如果調用時間太長,斷路器將介入并中斷調用。此外,如果對某個遠程資源的調用失敗次數達到某個閾值,將會采取快速失敗策略,阻止將來調用失敗的遠程資源。
后備(fallback)模式
當遠程調用失敗時,將執行替代代碼路徑,并嘗試通過其他方式來處理操作,而不是產生一個異常。也就是為遠程操作提供一個應急措施,而不是簡單的拋出異常。
艙壁/隔板(bulkhead)模式
艙壁模式是建立在造船的基礎概念上。一艘船會被劃分為多個水密艙(艙壁),因而即使少數幾個部位被擊穿漏水,整艘船并不會被淹沒。將這個概念帶入到遠程調用中,如果所有調用都使用的是同一個線程池來處理,那么很有可能一個緩慢的遠程調用會拖垮整個應用程序。在艙壁模式中可以隔離每個遠程資源,并分配各自的線程池,使之互不影響。
Hystrix介紹
Hystrix,英文翻譯是豪豬,是一種保護機制,Netflix公司的一款組件。主頁:https://github.com/Netflix/Hystrix/。Hystix是Netflix開源的一個延遲和容錯庫,用于隔離訪問遠程服務、第三方庫,防止出現級聯失敗。
Hystrix特性
1.斷路器機制-斷路器模式
斷路器很好理解, 當Hystrix Command請求后端服務失敗數量超過一定比例(默認50%), 斷路器會切換到開路狀態(Open)。這時所有請求會直接失敗而不會發送到后端服務。斷路器保持在開路狀態一段時間后(默認5秒), 自動切換到半開路狀態(HALF-OPEN)。這時會判斷下一次請求的返回情況, 如果請求成功, 斷路器切回閉路狀態(CLOSED), 否則重新切換到開路狀態(OPEN)。Hystrix的斷路器就像我們家庭電路中的保險絲, 一旦后端服務不可用, 斷路器會直接切斷請求鏈, 避免發送大量無效請求影響系統吞吐量, 并且斷路器有自我檢測并恢復的能力。
熔斷器模式就像是那些容易導致錯誤的操作的一種代理。這種代理能夠記錄最近調用發生錯誤的次數,然后決定使用允許操作繼續,或者立即返回錯誤。熔斷器就是保護服務高可用的最后一道防線。 熔斷器開關相互轉換的邏輯如下圖:
2.Fallback-后備模式
Fallback相當于是降級操作。對于查詢操作, 我們可以實現一個fallback方法, 當請求后端服務出現異常的時候, 可以使用fallback方法返回的值. fallback方法的返回值一般是設置的默認值或者來自緩存。
3.資源隔離-艙壁(bulkhead)模式
在Hystrix中, 主要通過線程池來實現資源隔離。通常在使用的時候應該根據調用的遠程服務劃分出多個線程池。例如調用產品服務的Command放入A線程池, 調用賬戶服務的Command放入B線程池. 這樣做的主要優點是運行環境被隔離開了。這樣就算調用服務的代碼存在bug或者由于其他原因導致自己所在線程池被耗盡時, 不會對系統的其他服務造成影響。 但是帶來的代價就是維護多個線程池會對系統帶來額外的性能開銷。如果是對性能有嚴格要求而且確信自己調用服務的客戶端代碼不會出問題的話, 可以使用Hystrix的信號模式(Semaphores)來隔離資源。
Spring Cloud Feign 應用
上面主要是講述了Feign模式管理客戶端方面應對的一些問題和理論知識,下面將講述Feign結合Ribbon和Hystrix在項目中的落地應用。
創建Feign
在Spring Boot項目中, 推薦在pom中添加Feign依賴(feign默認會使用JDK自帶的 HttpUrlConnection ,相對于Apache的HttpComponent缺失連接池等擴展信息,詳情見:FeignRibbonClientAutoConfiguration)。
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-httpclient</artifactId> </dependency>
在App啟動類中,設置啟用Feign:
@EnableFeignClients public class App { }
搭建一個Feign Client基本配置:
@FeignClient(value="wl-service") public interface WlFeignClient { @RequestMapping(method = RequestMethod.GET, value= "/stores") List<Store> getStores(); @RequestMapping(method = RequestMethod.POST, value= "/stores/{storeId}", consumes = "application/json") Store update(@PathVariable("storeId") Long storeId, Store store);
@FeignClient
在此處可以配置客戶端訪問服務的方式,及通過服務名走服務發現模式和http地址模式,其參數可配置如:
- 服務發現:@FeignClient(value = "wl-v1-00", fallback = ArticleSystemRemoteFallback.class)
- http地址:@FeignClient(name = "wl-test", url = "${test.url}", path = "combwl", fallbackFactory = GoodsGroupFeignFallbackFactory.class)
- 服務發現 模式value及微服務的名稱,fallback即定義后備模式,當觸發熔斷時,定義后備返回接口,便于客戶端“快速失敗”。
- http地址模式 url即是對應微服務的訪問地址,path可設置或不設置,表示該服務下面的通用訪問路徑。fallback是定義后備模式。
FeignClient模式通過Apache的HttpComponent封裝調用時,需注意多參數,json等數據的細節處理。
Feign Hystrix斷路器模式
Feign本身集成了Hystrix。但默認情況下沒有啟動。必須顯示聲明(feign.hystrix.enabled=true)。客戶端應用就是在配置文件中設置hystrix的配置,項目自動根據配置文件參數調整。hystrix配置(所有的配置可以參考com.netflix.hystrix.HystrixCommandProperties這個類)。
常用的hystrix配置設置如下:
hystrix: command: default: circuitBreaker: # 是否開啟熔斷(默認true) enabled: true # 熔斷生效至少請求數量(默認20),當同一HystrixCommand請求數量低于此值時,熔斷不會開啟 requestVolumeThreshold:20 # 失敗次數超過比例才開啟熔斷 errorThresholdPercentage: 50 # 強制開啟熔斷 #forceOpen: true # 強制關閉熔斷 #forceClosed: true execution: isolation: # THREAD:單獨開啟線程執行;SEMAPHORE:在調用線程上執行(由于我們現有框架中FeignUserContextInterceptor中使用了ThreadLocal,所以必須使用第二種方式) 18. strategy: SEMAPHORE 19. thread: 20. # 執行超時時間(這個時間設置很重要,因為HystrixCommand會包裝RibbonClient實例,那么這個時間就必須要大于ribbion timeout * retry,后面Ribbon章節會介紹) timeoutInMilliseconds: 2000 semaphore: # 由于我們使用SEMAPHORE模式,當每個feign并發發起請求超過此值時,請求會被拒絕,直接調用降級方法,異常信息關鍵字:could not acquire a semaphore for execution maxConcurrentRequests: 1000 fallback: isolation: semaphore: # 由于我們使用SEMAPHORE模式,當每個feign并發發起請求調用降級方法超過此值,調用降級方法會被拒絕,直接拋出異常,異常信息關鍵字:fallback execution rejected maxConcurrentRequests: 1000
Feign Hystrix后備模式
后備模式就是在遠程調用服務時,被斷路器切斷或服務調用超時時,返回的一種備用方案。應用舉例如下:
直接設置fallback,該模式不便于調試具體遠程服務調用出錯的信息。
/** * 服務發現模式 */ @FeignClient(name = "eureka-client",fallback = OpenFeignFallbackServiceImpl.class)//eureka-client工程的服務名稱 public interface OpenFeignService { @GetMapping("/name")//這里的請求路徑需要和eureka-client中的請求路徑一致 public String test();//這里的方法名需要和eureka-client中的方法名一致 } /** * 服務發現-對應后備模式的方法定義 */ @Service public class OpenFeignFallbackServiceImpl implements OpenFeignService{ @Override public String test() { return "調用服務失??!"; }
除了fallback模式,還可以調用fallbackFactory,這種可以記錄遠程調用失敗的具體明細異常。建議采用此方案設置后備模式。
/** * 聲明調用客戶端 */ @FeignClient(name = "wl-sku", url = "${wl.url}", path = "wl", fallbackFactory = WlSkuFeignFallbackFactory.class) public interface WlSkuFeign { /** * 基于商品編碼獲取商品銷售屬性明細 * * @param relationId 參數編碼 * @return */ @RequestMapping(method = RequestMethod.GET, path = "/item/{relation_id}") ResponseBody<GoodsItemDto, EmptyMeta> getItemDetail(@PathVariable("relation_id") Integer relationId); } /** * 申明后備模式 * */ @Component public class WlSkuFeignFallbackFactory implements FallbackFactory<WlSkuFeign> { @Override public WlSkuFeign create(Throwable cause) { return relationId -> { ErrorLogger.getInstance().log("商品sku getItemDetail降級服務", cause); return ResponseBody.fallback(cause, new Error("getItemDetail", "商品服務不可用")); }; } }
Feign Hystrix艙壁(bulkhead)模式
Feign集成了Hystrix,也可以設置客戶端為艙壁模式。通過設置Hystrix的配置文件即可。
Hystrix隔離級別由SEMAPHORE(信號量)模式切換為THREAD(線程池)模式,同時服務追蹤功能相應調整適用THREAD模式。該模式有如下特性:
- 各上游服務(feign客戶端)線程資源隔離,相互不影響,可以實現完全的獨立配置。
- 由于feign請求是獨立線程,才可以真正意義上的實現超時降級功能(使用semaphore實際上是假的超時功能,比如超時設置1S,實際執行3S,但整體還是會執行3S,只是3S后會拋出TimeoutException觸發降級),而thread模式則能夠正在的在1S后直接Interrupt請求線程且立刻觸發降級,達到真正的斷流保護作用。
- 開啟線程池模式會額外開銷服務器資源,在開啟這種模式時,線程池的數量,服務器資源還是需要監控,綜合設置。
Hystrix艙壁(bulkhead)模式常用配置文件:
# 全局統一配置 hystrix: command: default: execution: isolation: # 更改為THREAD,其余SEMAPHORE開頭的配置可以去掉 strategy: THREAD thread: # 默認1000 timeoutInMilliseconds: 2000 threadpool: default: # 這個屬性很重要,默認false。當false時:maximumSize=coreSize,當true時:取值Math.max(maximumSize,coreSize),所以如果想設置最大數,必須設置為true allowMaximumSizeToDivergeFromCoreSize: false # 默認10 coreSize: 10 maximumSize: 10 # 默認1M,線程池內超過coreSize的線程允許最大空閑時間 keepAliveTimeMinutes: 1 # 等待隊列,默認-1即SynchronousQueue,直接交由線程池拒絕或者等待 maxQueueSize: -1 # 默認5,這個值的出現是因為線程池的queueSize無法動態變更,所以用這個值可以動態變更來前置檢測是否拒絕,當maxQueueSize為-1或者0時,這個檢測直接通過后交由線程池自己處理,當maxQueueSize大于0時,由queueSize<queueSizeRejectionThreshold來決定是否拒絕請求,所以如果設置maxQueueSize,最終隊列拒絕效果是以此值為準 queueSizeRejectionThreshold: 5
Feign Ribbon 負載均衡模式
Feign可通過配置參數設定Ribbon的運行模式,Ribbon配置(所有配置參考com.netflix.client.config.CommonClientConfigKey和com.netflix.client.config.DefaultClientConfigImpl)。一般設置負載均衡的重試機制,服務輪詢模式,請求響應時間等參數。
Feign模式下Ribbon常用配置參數如下:
ribbon: # 默認相同的route不重試,可以避免一些各種重試引起的問題,簡單化(但服務提供方還是應該盡量保證冪等性) MaxAutoRetries: 0 # 默認只重試不同route一次 MaxAutoRetriesNextServer: 1 # 由于在前面feign文檔中已經講到使用自己配置的HttpClient連接池,所以不需要配置ribbon連接池相關的任何屬性(因為考慮到每個服務提供方的不同,后期可能會更改回來使用ribbon連接池方式) # 默認5000 ReadTimeout: 5000 # 默認2000 ConnectTimeout: 2000 # NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #配置規則 隨機 # NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule #配置規則 輪詢 # NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RetryRule #配置規則 重試 # NFLoadBalancerRuleClassName: com.netflix.loadbalancer.WeightedResponseTimeRule #配置規則 響應時間權重 NFLoadBalancerRuleClassName: com.netflix.loadbalancer.BestAvailableRule #配置規則 最空閑連接策 # 后續可能會自定義一些負載均衡策略,通過這里來設置 # ribbon子容器饑餓加載,避免偶爾因為服務重啟后第一次發起請求時延遲加載耗時造成fallback,但是會增加系統啟動時間(新版才支持) eager-load: enabled: true clients: - a - b - c #一個客戶端遠程多個微服務,可針對單個微服務做特殊配置 # ribbon客戶端名稱(即feign客戶端名稱) <clientName>: ribbon: listOfServers: www.baidu.com xxx: xxx
Spring Cloud Feign 注意事項
fallback
Feign降級本地實現,必須實現當前Feign接口,且必須聲明為一個bean,feign調用異常時會自動調用實現方法。
@Component public class WlFeignFallback implements WlFeign { @Override public ResponseBody<List<Object>, EmptyMeta> getTest() { return ResponseBody.fail(new Error("xx", "xx")); } }
fallbackFactory
Feign降級工廠類,必須實現feign.hystrix.FallbackFactory接口,適用于復雜的根據異常類型動態選擇降級實現類(也必須實現當前Feign接口),并且這個工廠類也必須聲明為一個bean。(可以獲取詳細異常信息,首選)。
configuration
自定義的獨立Feign客戶端的配置類,可以覆蓋Feign默認的任何通用的Logger.Level,Retryer,Request.Options,RequestInterceptor,SetterFactory。特別注意,自定義的Configuration類不能加 @Configuration 注解,否則會被自動掃描,注冊到通用配置中,會被全局Feign使用,同時方法必須加 @Bean 注解。
/** * feign全局配置 * * @Configuration 加上為全局,不加為自定義 */ @Configuration public class FeignConfiguration { /** * feign日志 */ @Profile({"self", "local", "dev"}) @Bean public Logger.Level level() { return Logger.Level.FULL; } /** * http請求時長,最好小于hystrix時長 */ @Bean public Request.Options options() { return new Request.Options(2000, 3500); } /** * 使用默認的不重試機制,單獨feign有特殊需求單獨配置 */ public Retryer retryer() { // 最小重試間隔,最大重試間隔,最多嘗試次數(包括第一次) return new Retryer.Default(100L, 500L, 2); }
url/path
顯示聲明固定服務訪問路徑,最終訪問路徑為:url+path( @FeignClient )+path( @RequestMapping )注意,無論使用自動服務發現還是固定訪問路徑方式, @FeignClient 注解的name或者value屬性不能為空(serviceId已經摒棄)。
方法返回類型
通過Feign調用遠程服務,可以定義調用的方法返回void,業務對象類型或者 feign.Response 復雜類型。
method
必須使用 @RequestMapping 顯式聲明method,不能使用 @GetMapping 或者 @PostMapping 。
// 顯示指定方法 @RequestMapping(method = RequestMethod.POST)
consumes
凡是使用PHP服務,因為請求必須為json,必須添加consumes=MediaType.APPLICATION_JSON_VALUE(不能使用MediaType.APPLICATION_JSON_UTF8_VALUE,因為apache http ContentType在校驗時不允許有'“‘,',‘,';‘出現,詳情參考: org.apache.http.entity.ContentType valid(String s) 方法)。
GET請求復雜對象
// 方式1:使用Map傳輸 @RequestMapping(path="xxx", method=GET) ResponseBody<T> test(@RequestParam Map<String, Object> map) { } // 方式2:獨立設置param @RequestMapping(path="xxx", method=GET) ResponseBody<T> test(@RequestParam("aaa") String aa, @RequestParam("bb") int bb) { }
支持application/x-www-form-urlencoded格式http接口
// 如果接口返回類型是text/html,必須用string接受,然后手動反序列化,如果是applicatin/json,則可以直接用對象接受 @RequestMapping(path="xxx", method=POST, consumes=MediaType.APPLICATION_FORM_URLENCODED_VALUE) public String test(@RequestBody MultiValueMap<String, String> map) { } @RequestMapping(path="xxx", method=POST, consumes=MediaType.APPLICATION_FORM_URLENCODED_VALUE) public RequestBody test(String content) { }
Feign,Hystrix,Ribbon配置參數注意
Feign本身可以設置重試,還可以設置請求時長,Hystrix設置熔斷,Ribbon可以設置重試機制,請求時長。這些參數在配置時,要合理設置,避免沖突。為了確保Ribbon重試的時候不被熔斷,就需要讓Hystrix的超時時間大于Ribbon的超時時間,否則Hystrix命令超時后,該命令直接熔斷,重試機制就沒有任何意義了。
#ribbon超時配置為2000,請求超時后,該實例會重試1次,更新實例會重試1次。 service-hi: ribbon: ReadTimeout: 2000 ConnectTimeout: 1000 MaxAutoRetries: 1 MaxAutoRetriesNextServer: 1 hystrix: command: default: execution: timeout: enabled: true isolation: thread: timeoutInMilliseconds: 8000
Feign的HTTP Client
Feign在默認情況下使用的是JDK原生的URLConnection發送HTTP請求,沒有連接池,但是對每個地址會保持一個長連接,即利用HTTP的persistence connection 。建議采用Apache的HTTP Client替換Feign原始的http client, 從而獲取連接池、超時時間等與性能息息相關的控制能力。Spring Cloud從 Brixtion.SR5
版本開始支持這種替換,首先在項目中聲明Apache HTTP Client和 feign-httpclient
依賴。
<!-- 使用Apache HttpClient替換Feign原生httpclient --> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> </dependency> <dependency> <groupId>com.netflix.feign</groupId> <artifactId>feign-httpclient</artifactId> <version>${feign-httpclient}</version> </dependency>
為了合理的利用Apache HTTP Client做http請求,建議自定義http請求的配置參數。
@Bean(destroyMethod = "close") public CloseableHttpClient httpClient() { // 最終存活時間還需要看服務端的keep-alive設置,和空閑時間以及間歇的validate是否通過 PoolingHttpClientConnectionManager pool = new PoolingHttpClientConnectionManager(30, TimeUnit.SECONDS); pool.setMaxTotal(2000); // 目前只有一個路由,默認等于最大值,根據業務并發量設置 pool.setDefaultMaxPerRoute(2000); // 檢查非活動連接,避免服務端重啟后或者服務端keep-alive過期主動關閉連接造成失效,對于微服務場景可能還比較普遍,但受限HTTP設計理念,這也并發完全可靠,使用re-try/re-execute機制來彌補,考慮到可能很多Niginx配置為5秒keep-alive // TODO 這個值還待商榷 pool.setValidateAfterInactivity(5 * 1000); return HttpClients.custom() .setConnectionManager(pool) // 連接空閑10s就回收,這個會啟動獨立線程檢測,所以必須聲明destroy方法來關閉獨立線程 .evictIdleConnections(10, TimeUnit.SECONDS) // 建立連接時間和從連接池獲取連接時間,以及數據傳輸時間 .setDefaultRequestConfig(RequestConfig.custom() // http建立連接超時時間 .setConnectTimeout(1000) // 從連接池獲取連接超時時間 .setConnectionRequestTimeout(3000) // socket超時時間 .setSocketTimeout(10000) .build()) // 自定義重試機制 .setRetryHandler((exception, executionCount, context) -> { // 目前只允許重試一次 if (executionCount > 1) { return false; } // 如果是服務端主動關閉連接的,數據并沒有被服務端接受,可以重試 if (exception instanceof NoHttpResponseException) { return true; } // 不要重試SSL握手異常 if (exception instanceof SSLHandshakeException) { return false; } // 超時 if (exception instanceof InterruptedIOException) { return false; } // 目標服務器不可達 if (exception instanceof UnknownHostException) { return false; } // SSL握手異常 if (exception instanceof SSLException) { return false; } HttpClientContext clientContext = HttpClientContext.adapt(context); HttpRequest request = clientContext.getRequest(); String get = "GET"; // GET方法是冪等的,可以重試 if (request.getRequestLine().getMethod().equalsIgnoreCase(get)) { return true; } return false; }) // 默認的ConnectionKeepAliveStrategy就是動態根據keep-alive計算的 .build(); }
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持服務器之家。