Request Collapsing Techniques: Hystrix Collapser, BatchCollapser, and ConcurrentHashMultiset
This article compares three request‑collapsing techniques—Hystrix Collapser, a custom BatchCollapser, and Guava's ConcurrentHashMultiset—explaining their designs, configurations, code implementations, and suitable scenarios for reducing downstream load in Java backend systems.
Combining similar or duplicate requests upstream before sending them downstream can greatly reduce downstream load and improve overall system throughput. The article introduces three request‑collapsing techniques— hystrix collapser , ConcurrentHashMultiset , and a custom BatchCollapser —and compares their applicable scenarios through concrete implementations.
Preface
In typical request‑response models each request occupies its own thread and memory space, leading to high I/O cost when many requests perform the same operation. Merging these I/O operations can significantly lower the burden on downstream services.
Hystrix Collapser
Hystrix, an open‑source library from Netflix, provides circuit‑breaker functionality and also a request‑collapser. Using the hystrix-javanica module, developers can annotate methods with @HystrixCollapser and @HystrixCommand to enable request merging. The collapser works via AOP, requiring a bean definition such as:
<code><aop:aspectj-autoproxy/>
<bean id="hystrixAspect" class="com.netflix.hystrix.contrib.javanica.aop.aspectj.HystrixCommandAspect"/></code>Key points for using @HystrixCollapser :
Add @HystrixCollapser on the method to be merged and @HystrixCommand on the batch method.
The single method can accept only one parameter; multiple parameters must be wrapped in a custom class.
Return types must be Future<SingleReturn> for the single method and List<SingleReturn> for the batch method, with matching element counts.
A simple example:
<code>public class HystrixCollapserSample {
@HystrixCollapser(batchMethod = "batch")
public Future<Boolean> single(String input) {
return null; // never executed
}
public List<Boolean> batch(List<String> inputs) {
return inputs.stream().map(it -> Boolean.TRUE).collect(Collectors.toList());
}
}</code>The internal workflow involves registering a collapser bean, detecting the annotation, storing request arguments in a concurrent map, creating an Observable, and using a timer thread to batch and execute the requests.
BatchCollapser (Custom Implementation)
When the result of each request is not needed, a lightweight batch collapser can be built. It stores incoming requests in a thread‑safe container (a LinkedBlockingDeque ) and triggers execution either after a time interval or when a request count threshold is reached.
Key design aspects:
The container must allow duplicate elements and preserve order.
It must be safely consumable by multiple threads without explicit locking.
Implementation uses java.util.concurrent utilities and a singleton factory to manage collapser instances. The timer is replaced by a ScheduledExecutorService to avoid blocking issues.
<code>public class BatchCollapser<E> implements InitializingBean {
private static final Logger logger = LoggerFactory.getLogger(BatchCollapser.class);
private static final ScheduledExecutorService SCHEDULE_EXECUTOR = Executors.newScheduledThreadPool(1);
private volatile LinkedBlockingDeque<E> batchContainer = new LinkedBlockingDeque<>();
private Handler<List<E>, Boolean> cleaner;
private long interval;
private int threshHold;
// constructor, afterPropertiesSet, submit, clean, getInstance ...
}</code>Important notes:
The collapser is a global singleton to be reusable across different request types.
A handler instance determines how the batched list is processed.
Using ScheduledExecutorService avoids the blocking behavior of java.util.Timer .
ConcurrentHashMultiset (Guava)
For scenarios with high request duplication, Guava's ConcurrentHashMultiset provides a thread‑safe multiset that counts occurrences of each element without locking. It is ideal for statistical aggregation where many identical requests can be merged into a single count.
Typical usage:
<code>if (ConcurrentHashMultiset.isEmpty()) {
return;
}
List<Request> transferList = Lists.newArrayList();
ConcurrentHashMultiset.elementSet().forEach(request -> {
int count = ConcurrentHashMultiset.count(request);
if (count <= 0) return;
transferList.add(count == 1 ? request : new Request(request.getIncrement() * count));
ConcurrentHashMultiset.remove(request, count);
});</code>Conclusion
hystrix collapser : suitable when each request's result is required, accepting the extra latency cost.
BatchCollapser : appropriate when results are not needed and merging can be triggered by time or count thresholds.
ConcurrentHashMultiset : best for high‑frequency duplicate requests in statistical or aggregation scenarios.
Combining a custom BatchCollapser with ConcurrentHashMultiset as the container can leverage both time‑based batching and efficient duplicate counting.
Code Ape Tech Column
Former Ant Group P8 engineer, pure technologist, sharing full‑stack Java, job interview and career advice through a column. Site: java-family.cn
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.