Typically this means that requests within a single user-request (ie. HTTP request) are collapsed.
No interaction with other user requests.
1 queue per user request.
GLOBAL :全域性。
Requests from any thread (ie. all HTTP requests) within the JVM will be collapsed.
1 queue for entire app.
1: public Observable<ResponseType> submitRequest(final RequestArgumentType arg){
2: /*
3: * We only want the timer ticking if there are actually things to do so we register it the first time something is added.
4: */5: if (!timerListenerRegistered.get() && timerListenerRegistered.compareAndSet(false, true)) {
6: /* schedule the collapsing task to be executed every x milliseconds (x defined inside CollapsedTask) */7: timerListenerReference.set(timer.addListener(new CollapsedTask()));
8: }
9:
10: // loop until succeed (compare-and-set spin-loop)11: while (true) {
12: // 獲得 RequestBatch13: final RequestBatch<BatchReturnType, ResponseType, RequestArgumentType> b = batch.get();
14: if (b == null) {
15: return Observable.error(new IllegalStateException("Submitting requests after collapser is shutdown"));
16: }
17:
18: // 新增到 RequestBatch19: final Observable<ResponseType> response;
20: if (arg != null) {
21: response = b.offer(arg);
22: } else {
23: response = b.offer( (RequestArgumentType) NULL_SENTINEL);
24: }
25:
26: // 新增成功,返回 Observable27: // it will always get an Observable unless we hit the max batch size28: if (response != null) {
29: return response;
30: } else {
31: // 新增失敗,執行 RequestBatch ,並建立新的 RequestBatch32: // this batch can't accept requests so create a new one and set it if another thread doesn't beat us33: createNewBatchAndExecutePreviousIfNeeded(b);
34: }
35: }
36: }
複製程式碼
1: public Observable<ResponseType> offer(RequestArgumentType arg){
2: // 執行已經開始,新增失敗3: /* short-cut - if the batch is started we reject the offer */4: if (batchStarted.get()) {
5: returnnull;
6: }
7:
8: /*
9: * The 'read' just means non-exclusive even though we are writing.
10: */11: if (batchLock.readLock().tryLock()) {
12: try {
13: // 執行已經開始,新增失敗14: /* double-check now that we have the lock - if the batch is started we reject the offer */15: if (batchStarted.get()) {
16: returnnull;
17: }
18:
19: // 超過佇列最大長度,新增失敗20: if (argumentMap.size() >= maxBatchSize) {
21: returnnull;
22: } else {
23: // 建立 CollapsedRequestSubject ,並新增到佇列24: CollapsedRequestSubject<ResponseType, RequestArgumentType> collapsedRequest = new CollapsedRequestSubject<ResponseType, RequestArgumentType>(arg, this);
25: final CollapsedRequestSubject<ResponseType, RequestArgumentType> existing = (CollapsedRequestSubject<ResponseType, RequestArgumentType>) argumentMap.putIfAbsent(arg, collapsedRequest);
26: /**
27: * If the argument already exists in the batch, then there are 2 options:
28: * A) If request caching is ON (the default): only keep 1 argument in the batch and let all responses
29: * be hooked up to that argument
30: * B) If request caching is OFF: return an error to all duplicate argument requests
31: *
32: * This maintains the invariant that each batch has no duplicate arguments. This prevents the impossible
33: * logic (in a user-provided mapResponseToRequests for HystrixCollapser and the internals of HystrixObservableCollapser)
34: * of trying to figure out which argument of a set of duplicates should get attached to a response.
35: *
36: * See https://github.com/Netflix/Hystrix/pull/1176 for further discussion.
37: */38: if (existing != null) {
39: boolean requestCachingEnabled = properties.requestCacheEnabled().get();
40: if (requestCachingEnabled) {
41: return existing.toObservable();
42: } else {
43: return Observable.error(new IllegalArgumentException("Duplicate argument in collapser batch : [" + arg + "] This is not supported. Please turn request-caching on for HystrixCollapser:" + commandCollapser.getCollapserKey().name() + " or prevent duplicates from making it into the batch!"));
44: }
45: } else {
46: return collapsedRequest.toObservable();
47: }
48:
49: }
50: } finally {
51: batchLock.readLock().unlock();
52: }
53: } else {
54: returnnull;
55: }
56: }
複製程式碼
/* package-private */voidremove(RequestArgumentType arg){
if (batchStarted.get()) {
//nothing we can doreturn;
}
if (batchLock.readLock().tryLock()) {
try {
/* double-check now that we have the lock - if the batch is started, deleting is useless */if (batchStarted.get()) {
return;
}
argumentMap.remove(arg);
} finally {
batchLock.readLock().unlock();
}
}
}
複製程式碼
1: privatevoidcreateNewBatchAndExecutePreviousIfNeeded(RequestBatch<BatchReturnType, ResponseType, RequestArgumentType> previousBatch){
2: if (previousBatch == null) {
3: thrownew IllegalStateException("Trying to start null batch which means it was shutdown already.");
4: }
5: if (batch.compareAndSet(previousBatch, new RequestBatch<BatchReturnType, ResponseType, RequestArgumentType>(properties, commandCollapser, properties.maxRequestsInBatch().get()))) {
6: // this thread won so trigger the previous batch7: previousBatch.executeBatchIfNotAlreadyStarted();
8: }
9: }
複製程式碼
1: publicvoidexecuteBatchIfNotAlreadyStarted(){
2: /*
3: * - check that we only execute once since there's multiple paths to do so (timer, waiting thread or max batch size hit)
4: * - close the gate so 'offer' can no longer be invoked and we turn those threads away so they create a new batch
5: */6: // 設定 執行已經開始7: if (batchStarted.compareAndSet(false, true)) {
8: // 獲得 寫鎖9: /* wait for 'offer'/'remove' threads to finish before executing the batch so 'requests' is complete */10: batchLock.writeLock().lock();
11:
12: try {
13: // 將多個命令請求分片成 N 個【多個命令請求】。14: // shard batches15: Collection<Collection<CollapsedRequest<ResponseType, RequestArgumentType>>> shards = commandCollapser.shardRequests(argumentMap.values());
16: // for each shard execute its requests 17: for (final Collection<CollapsedRequest<ResponseType, RequestArgumentType>> shardRequests : shards) {
18: try {
19: // 將多個命令請求合併,建立一個 HystrixCommand20: // create a new command to handle this batch of requests21: Observable<BatchReturnType> o = commandCollapser.createObservableCommand(shardRequests);
22:
23: // 將一個 HystrixCommand 的執行結果,對映回對應的命令請求們24: commandCollapser.mapResponseToRequests(o, shardRequests).doOnError(new Action1<Throwable>() {
25:
26: /**
27: * This handles failed completions
28: */29: @Override30: publicvoidcall(Throwable e){
31: // handle Throwable in case anything is thrown so we don't block Observers waiting for onError/onCompleted32: Exception ee;
33: if (e instanceof Exception) {
34: ee = (Exception) e;
35: } else {
36: ee = new RuntimeException("Throwable caught while executing batch and mapping responses.", e);
37: }
38: logger.debug("Exception mapping responses to requests.", e);
39: // if a failure occurs we want to pass that exception to all of the Futures that we've returned40: for (CollapsedRequest<ResponseType, RequestArgumentType> request : argumentMap.values()) {
41: try {
42: ((CollapsedRequestSubject<ResponseType, RequestArgumentType>) request).setExceptionIfResponseNotReceived(ee);
43: } catch (IllegalStateException e2) {
44: // if we have partial responses set in mapResponseToRequests45: // then we may get IllegalStateException as we loop over them46: // so we'll log but continue to the rest47: logger.error("Partial success of 'mapResponseToRequests' resulted in IllegalStateException while setting Exception. Continuing ... ", e2);
48: }
49: }
50: }
51:
52: }).doOnCompleted(new Action0() {
53:
54: /**
55: * This handles successful completions
56: */57: @Override58: publicvoidcall(){
59: // check that all requests had setResponse or setException invoked in case 'mapResponseToRequests' was implemented poorly60: Exception e = null;
61: for (CollapsedRequest<ResponseType, RequestArgumentType> request : shardRequests) {
62: try {
63: e = ((CollapsedRequestSubject<ResponseType, RequestArgumentType>) request).setExceptionIfResponseNotReceived(e,"No response set by " + commandCollapser.getCollapserKey().name() + " 'mapResponseToRequests' implementation.");
64: } catch (IllegalStateException e2) {
65: logger.debug("Partial success of 'mapResponseToRequests' resulted in IllegalStateException while setting 'No response set' Exception. Continuing ... ", e2);
66: }
67: }
68: }
69:
70: }).subscribe();
71:
72: } catch (Exception e) {
73: // 異常74: logger.error("Exception while creating and queueing command with batch.", e);
75: // if a failure occurs we want to pass that exception to all of the Futures that we've returned76: for (CollapsedRequest<ResponseType, RequestArgumentType> request : shardRequests) {
77: try {
78: request.setException(e);
79: } catch (IllegalStateException e2) {
80: logger.debug("Failed trying to setException on CollapsedRequest", e2);
81: }
82: }
83: }
84: }
85:
86: } catch (Exception e) {
87: // 異常88: logger.error("Exception while sharding requests.", e);
89: // same error handling as we do around the shards, but this is a wider net in case the shardRequest method fails90: for (CollapsedRequest<ResponseType, RequestArgumentType> request : argumentMap.values()) {
91: try {
92: request.setException(e);
93: } catch (IllegalStateException e2) {
94: logger.debug("Failed trying to setException on CollapsedRequest", e2);
95: }
96: }
97: } finally {
98: batchLock.writeLock().unlock();
99: }
100: }
101: }
複製程式碼
publicclassRealCollapserTimerimplementsCollapserTimer{
/* single global timer that all collapsers will schedule their tasks on */privatefinalstatic HystrixTimer timer = HystrixTimer.getInstance();
@Overridepublic Reference<TimerListener> addListener(TimerListener collapseTask){
return timer.addTimerListener(collapseTask);
}
}
複製程式碼