Spring Cloud Hystrix的請求合併
通常微服務架構中的依賴通過遠端呼叫實現,而遠端呼叫中最常見的問題就是通訊消耗與連線數佔用。在高併發的情況之下,因通訊次數的增加,總的通訊時間消耗將會變的不那麼理想。同時,因為對依賴服務的執行緒池資源有限,將出現排隊等待與響應延遲的情況。為了優化這兩個問題,Hystrix提供了HystrixCollapser來實現請求的合併,以減少通訊消耗和執行緒數的佔用。
HystrixCollapser實現了在HystrixCommand之前放置一個合併處理器,它將處於一個很短時間窗(預設10毫秒)內對同一依賴服務的多個請求進行整合並以批量方式發起請求的功能(服務提供方也需要提供相應的批量實現介面)。通過HystrixCollapser的封裝,開發者不需要去關注執行緒合併的細節過程,只需要關注批量化服務和處理。下面我們從HystrixCollapser的使用例項,對其合併請求的過程一探究竟。
Hystrix的請求合併示例
public abstract class HystrixCollapser<BatchReturnType, ResponseType, RequestArgumentType> implements
HystrixExecutable<ResponseType>, HystrixObservable<ResponseType> {
...
public abstract RequestArgumentType getRequestArgument();
protected abstract HystrixCommand<BatchReturnType> createCommand(Collection<CollapsedRequest<ResponseType, RequestArgumentType>> requests);
protected abstract void mapResponseToRequests(BatchReturnType batchResponse, Collection<CollapsedRequest<ResponseType, RequestArgumentType>> requests);
...
}
從HystrixCollapser
抽象類的定義中可以看到,它指定了三個不同的型別:
-
BatchReturnType
:合併後批量請求的返回型別 -
ResponseType
:單個請求返回的型別 -
RequestArgumentType
:請求引數型別
而對於這三個型別的使用可以在它的三個抽象方法中看到:
-
RequestArgumentType getRequestArgument()
:該函式用來定義獲取請求引數的方法。 -
HystrixCommand<BatchReturnType> createCommand(Collection<CollapsedRequest<ResponseType, RequestArgumentType>> requests)
:合併請求產生批量命令的具體實現方法。 -
mapResponseToRequests(BatchReturnType batchResponse, Collection<CollapsedRequest<ResponseType, RequestArgumentType>> requests)
:批量命令結果返回後的處理,這裡需要實現將批量結果拆分並傳遞給合併前的各個原子請求命令的邏輯。
接下來,我們通過一個簡單的示例來直觀的理解實現請求合併的過程。
假設,當前微服務USER-SERVICE
提供了兩個獲取User
的介面:
-
/users/{id}
:根據id返回User物件的GET請求介面。 -
/users?ids={ids}
:根據ids引數返回User物件列表的GET請求介面,其中ids為以逗號分割的id集合。
而在服務消費端,為這兩個遠端介面已經通過RestTemplate
實現了簡單的呼叫,具體如下:
@Service
public class UserServiceImpl implements UserService {
@Autowired
private RestTemplate restTemplate;
@Override
public User find(Long id) {
return restTemplate.getForObject("http://USER-SERVICE/users/{1}", User.class, id);
}
@Override
public List<User> findAll(List<Long> ids) {
return restTemplate.getForObject("http://USER-SERVICE/users?ids={1}", List.class, StringUtils.join(ids, ","));
}
}
接著,我們來實現將短時間內多個獲取單一User物件的請求命令進行合併的實現:
- 第一步:為請求合併的實現準備一個批量請求命令的實現,具體如下:
public class UserBatchCommand extends HystrixCommand<List<User>> {
UserService userService;
List<Long> userIds;
public UserBatchCommand(UserService userService, List<Long> userIds) {
super(Setter.withGroupKey(asKey("userServiceCommand")));
this.userIds = userIds;
this.userService = userService;
}
@Override
protected List<User> run() throws Exception {
return userService.findAll(userIds);
}
}
批量請求命令實際上就是一個簡單的HystrixCommand實現,從上面的實現中可以看到它通過呼叫userService.findAll
方法來訪問/users?ids={ids}
介面以返回User的列表結果。
- 第二步,通過繼承
HystrixCollapser
實現請求合併器:
public class UserCollapseCommand extends HystrixCollapser<List<User>, User, Long> {
private UserService userService;
private Long userId;
public UserCollapseCommand(UserService userService, Long userId) {
super(Setter.withCollapserKey(HystrixCollapserKey.Factory.asKey("userCollapseCommand")).andCollapserPropertiesDefaults(
HystrixCollapserProperties.Setter().withTimerDelayInMilliseconds(100)));
this.userService = userService;
this.userId = userId;
}
@Override
public Long getRequestArgument() {
return userId;
}
@Override
protected HystrixCommand<List<User>> createCommand(Collection<CollapsedRequest<User, Long>> collapsedRequests) {
List<Long> userIds = new ArrayList<>(collapsedRequests.size());
userIds.addAll(collapsedRequests.stream().map(CollapsedRequest::getArgument).collect(Collectors.toList()));
return new UserBatchCommand(userService, userIds);
}
@Override
protected void mapResponseToRequests(List<User> batchResponse, Collection<CollapsedRequest<User, Long>> collapsedRequests) {
int count = 0;
for (CollapsedRequest<User, Long> collapsedRequest : collapsedRequests) {
User user = batchResponse.get(count++);
collapsedRequest.setResponse(user);
}
}
}
在上面的建構函式中,我們為請求合併器設定了時間延遲屬性,合併器會在該時間窗內收集獲取單個User的請求並在時間窗結束時進行合併組裝成單個批量請求。下面getRequestArgument
方法返回給定的單個請求引數userId,而createCommand
和mapResponseToRequests
是請求合併器的兩個核心:
-
createCommand
:該方法的collapsedRequests
引數中儲存了延遲時間窗中收集到的所有獲取單個User的請求。通過獲取這些請求的引數來組織上面我們準備的批量請求命令UserBatchCommand
例項。 -
mapResponseToRequests
:在批量命令UserBatchCommand
例項被觸發執行完成之後,該方法開始執行,其中batchResponse
引數儲存了createCommand
中組織的批量請求命令的返回結果,而collapsedRequests
引數則代表了每個被合併的請求。在這裡我們通過遍歷批量結果batchResponse
物件,為collapsedRequests
中每個合併前的單個請求設定返回結果,以此完成批量結果到單個請求結果的轉換。
請求合併的原理分析
下圖展示了在未使用HystrixCollapser
請求合併器之前的執行緒使用情況。可以看到當服務消費者同時對USER-SERVICE
的/users/{id}
介面發起了五個請求時,會向該依賴服務的獨立執行緒池中申請五個執行緒來完成各自的請求操作。
而在使用了HystrixCollapser
請求合併器之後,相同情況下的執行緒佔用如下圖所示。由於同一時間發生的五個請求處於請求合併器的一個時間窗內,這些發向/users/{id}
介面的請求被請求合併器攔截下來,並在合併器中進行組合,然後將這些請求合併成一個請求發向USER-SERVICE
的批量介面/users?ids={ids}
,在獲取到批量請求結果之後,通過請求合併器再將批量結果拆分並分配給每個被合併的請求。從圖中我們可以看到以來,通過使用請求合併器有效地減少了對執行緒池中資源的佔用。所以在資源有效並且在短時間內會產生高併發請求的時候,為避免連線不夠用而引起的延遲可以考慮使用請求合併器的方式來處理和優化。
使用註解實現請求合併器
在快速入門的例子中,我們使用@HystrixCommand
註解優雅地實現了HystrixCommand
的定義,那麼對於請求合併器是否也可以通過註解來定義呢?答案是肯定!
以上面實現的請求合併器為例,也可以通過如下方式實現:
@Service
public class UserService {
@Autowired
private RestTemplate restTemplate;
@HystrixCollapser(batchMethod = "findAll", collapserProperties = {
@HystrixProperty(name="timerDelayInMilliseconds", value = "100")
})
public User find(Long id) {
return null;
}
@HystrixCommand
public List<User> findAll(List<Long> ids) {
return restTemplate.getForObject("http://USER-SERVICE/users?ids={1}", List.class, StringUtils.join(ids, ","));
}
}
@HystrixCommand
我們之前已經介紹過了,可以看到這裡通過它定義了兩個Hystrix命令,一個用於請求/users/{id}
介面,一個用於請求/users?ids={ids}
介面。而在請求/users/{id}
介面的方法上通過@HystrixCollapser
註解為其建立了合併請求器,通過batchMethod
屬性指定了批量請求的實現方法為findAll
方法(即:請求/users?ids={ids}
介面的命令),同時通過collapserProperties
屬性為合併請求器設定相關屬性,這裡使用@HystrixProperty(name="timerDelayInMilliseconds", value = "100")
將合併時間窗設定為100毫秒。這樣通過@HystrixCollapser
註解簡單而又優雅地實現了在/users/{id}
依賴服務之前設定了一個批量請求合併器。
請求合併的額外開銷
雖然通過請求合併可以減少請求的數量以緩解依賴服務執行緒池的資源,但是在使用的時候也需要注意它所帶來的額外開銷:用於請求合併的延遲時間窗會使得依賴服務的請求延遲增高。比如:某個請求在不通過請求合併器訪問的平均耗時為5ms,請求合併的延遲時間窗為10ms(預設值),那麼當該請求的設定了請求合併器之後,最壞情況下(在延遲時間窗結束時才發起請求)該請求需要15ms才能完成。
由於請求合併器的延遲時間窗會帶來額外開銷,所以我們是否使用請求合併器需要根據依賴服務呼叫的實際情況來選擇,主要考慮下面兩個方面:
- 請求命令本身的延遲。如果依賴服務的請求命令本身是一個高延遲的命令,那麼可以使用請求合併器,因為延遲時間窗的時間消耗就顯得莫不足道了。
- 延遲時間窗內的併發量。如果一個時間窗內只有1-2個請求,那麼這樣的依賴服務不適合使用請求合併器,這種情況下不但不能提升系統效能,反而會成為系統瓶頸,因為每個請求都需要多消耗一個時間窗才響應。相反,如果一個時間窗內具有很高的併發量,並且服務提供方也實現了批量處理介面,那麼使用請求合併器可以有效的減少網路連線數量並極大地提升系統吞吐量,此時延遲時間窗所增加的消耗就可以忽略不計了。
原文:http://blog.didispace.com/spring-cloud-hystrix-request-collapse/
本文節選自我的《Spring Cloud微服務實戰》,更多內容可購買我的書或加入我的知識星球參與討論
相關文章
- Singleflight(合併請求)
- 7、Spring Cloud HystrixSpringCloud
- Spring Cloud 關於:Spring Cloud Netflix HystrixSpringCloud
- Spring Cloud 之 Hystrix.SpringCloud
- Spring cloud(4)-熔斷(Hystrix)SpringCloud
- Spring Cloud Netflix—如何加入HystrixSpringCloud
- Spring Cloud 學習筆記 ——Spring Cloud Config 請求失敗重試SpringCloud筆記
- 合併HTTP請求vs並行HTTP請求,到底誰更快?HTTP並行
- 合併HTTP請求 vs 並行HTTP請求,到底誰更快?HTTP並行
- Spring Cloud Hystrix 容錯保護SpringCloud
- Spring Cloud Hystrix應用篇(十一)SpringCloud
- Spring Cloud Hystrix原碼篇(十一)SpringCloud
- Spring Cloud Gateway 之 請求應答日誌列印SpringCloudGateway
- 保姆級教程 | Merge Request 分支合併請求
- Spring Cloud Hystrix 服務容錯保護SpringCloud
- springcloud學習筆記(四)Spring Cloud HystrixSpringGCCloud筆記
- Spring Cloud Hystrix:服務容錯保護SpringCloud
- 請求合併與拆分在併發場景中應用
- 不要再手動合併你的拉取請求(PR)
- Spring Cloud Zuul API服務閘道器之請求路由SpringCloudZuulAPI路由
- spring cloud微服務分散式雲架構--hystrix的使用SpringCloud微服務分散式架構
- Spring Cloud 原始碼學習之 Hystrix 入門SpringCloud原始碼
- Spring Cloud實戰系列(四) - 熔斷器HystrixSpringCloud
- Spring Cloud Netflix—斷路器:Hystrix客戶端SpringCloud客戶端
- Spring Cloud 2021.0.1 移除了Hystrix、Zuul等Netflix元件SpringCloudZuul元件
- Spring Cloud正式移除Hystrix、Zuul等Netflix OSS元件SpringCloudZuul元件
- Git多人合作,合併請求並解決衝突Git
- Hystrix 原始碼解析 —— 命令合併執行原始碼
- Git如何合併一個已經在GitHub上提交但沒有合併的Pull Request請求Github
- Java後端中的請求最佳化:從請求合併到非同步處理的實現策略Java後端非同步
- spring cloud gateway 原始碼解析(3)記錄請求引數及返回的jsonSpringCloudGateway原始碼JSON
- Spring cloud系列十五 使用執行緒池優化feign的http請求元件SpringCloud執行緒優化HTTP元件
- Spring Cloud OpenFeign:基於Ribbon和Hystrix的宣告式服務呼叫SpringCloud
- 踩坑 Spring Cloud Hystrix 執行緒池佇列配置SpringCloud執行緒佇列
- Spring Cloud中Hystrix、Ribbon及Feign的熔斷關係是什麼?SpringCloud
- Spring Cloud:使用Hystrix實現斷路器原理詳解(下)SpringCloud
- Spring Cloud構建微服務架構-Hystrix服務降級SpringCloud微服務架構
- 併發請求的重複插入問題
- 簡單使用spring cloud 服務註冊做一個請求轉發中心SpringCloud