1. 程式人生 > >Spring cloud Feign 深度學習與應用

Spring cloud Feign 深度學習與應用

簡介

    Spring Cloud Feign是一個宣告式的Web Service客戶端,它的目的就是讓Web Service呼叫更加簡單。Feign提供了HTTP請求的模板,通過編寫簡單的介面和插入註解,就可以定義好HTTP請求的引數、格式、地址等資訊。Feign會完全代理HTTP請求,開發時只需要像呼叫方法一樣呼叫它就可以完成服務請求及相關處理。開源地址:https://github.com/OpenFeign/feign。Feign整合了Ribbon負載和Hystrix熔斷,可以不再需要顯式地使用這兩個元件。總體來說,Feign具有如下特性:

  • 可插拔的註解支援,包括Feign註解和JAX-RS註解;
  • 支援可插拔的HTTP編碼器和解碼器;
  • 支援Hystrix和它的Fallback;
  • 支援Ribbon的負載均衡;
  • 支援HTTP請求和響應的壓縮。

    Spring Cloud Feign致力於處理客戶端與伺服器之間的呼叫需求。隨著業務的擴充套件和微服務數量的增多,不可避免的需要面對如下問題:

  1. 彈性客戶端
  2. 雪崩效應

    簡單來說,使用Spring Cloud Feign元件,他本身整合了Ribbon和Hystrix。可設計一套穩定可靠的彈性客戶端呼叫方案,避免整個系統出現雪崩效應。

雪崩效應

    在微服務架構中,微服務是完成一個單一的業務功能,這樣做的好處是可以做到解耦,每個微服務可以獨立演進。但是,一個應用可能會有多個微服務組成,微服務之間的資料互動通過遠端過程呼叫完成。這就帶來一個問題,假設微服務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
View Code

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
View Code

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
View Code

 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();
    }
View Code

參考文件