一、背景
- 遠程調用最常見的問題: 通信消耗與連接數佔用
- 高併發的情況下,因通信次數的增加,總的通信時間消耗將會變的不那麼理想
- 同時,因爲對依賴服務的線程池資源有限,將出現排隊等待與響應延遲的情況
- Hystrix提供了HystrixCollapser來實現請求的合併,以減少通信消耗和線程數的佔用
HystrixCollapser 實現了在 HystrixCommand 之前放置一個合併處理器:
- 將處於一個很短時間窗(默認10毫秒)內對同一依賴服務的多個請求進行整合並以批量方式發起請求的功能(服務提供方也需要提供相應的批量實現接口)
- 通過 HystrixCollapser 的封裝,開發者不需要去關注線程合併的細節過程,只需要關注批量化服務和處理
二、實例
1. 部分源碼
/**
* BatchReturnType:合併後批量請求的返回類型
* ResponseType:單個請求返回的類型
* RequestArgumentType:請求參數類型
*/
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);
...
}
2. 簡單的示例
假設,當前微服務 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); } } }
getRequestArgument
:返回給定的單個請求參數 userIdcreateCommand
:collapsedRequests 參數保存延遲時間窗中收集到的所有獲取單個 User 的請求通過獲取這些請求的參數來組織準備的批量請求命令 UserBatchCommand 實例
mapResponseToRequests
:在批量命令 UserBatchCommand 實例被觸發執行完成之後,該方法開始執行- batchResponse 參數保存了 createCommand 中組織的批量請求命令的返回結果
- collapsedRequests 參數代表了每個被合併的請求
通過遍歷批量結果 batchResponse 對象,爲 collapsedRequests 中每個合併前的單個請求設置返回結果,以此完成批量結果到單個請求結果的轉換