Request Collapsing Techniques: Hystrix Collapser, BatchCollapser, and ConcurrentHashMultiset
This article explains three request‑collapsing approaches—Hystrix Collapser, a custom BatchCollapser, and Guava's ConcurrentHashMultiset—detailing their design, configuration, Java implementations, and suitable scenarios for reducing downstream load and improving system throughput.
Combining similar or duplicate requests upstream before sending them downstream can dramatically reduce the load on downstream services and increase overall throughput. This article introduces three request‑collapsing techniques—Hystrix Collapser, a custom BatchCollapser, and Guava's ConcurrentHashMultiset—explaining their principles, configurations, and Java implementations.
Hystrix Collapser
Hystrix, an open‑source library from Netflix, provides a collapser that merges multiple requests into a single batch request. It works together with Hystrix's circuit‑breaker to keep services stable under high concurrency. To use it, you need to include hystrix‑core and hystrix‑javanica dependencies and configure the HystrixAspect bean.
<aop:aspectj-autoproxy/>
<bean id="hystrixAspect" class="com.netflix.hystrix.contrib.javanica.aop.aspectj.HystrixCommandAspect"/>Key points for the collapser:
Add @HystrixCollapser on the method that should be collapsed and @HystrixCommand on the batch method.
The single method must accept a single argument; the batch method receives a java.util.List<SingleParam> .
Return types: java.util.concurrent.Future<SingleReturn> for the single method and java.util.List<SingleReturn> for the batch method, with matching result counts.
Configuration example:
@HystrixCollapser(
batchMethod = "batch",
collapserKey = "single",
scope = com.netflix.hystrix.HystrixCollapser.Scope.GLOBAL,
collapserProperties = {
@HystrixProperty(name = "maxRequestsInBatch", value = "100"),
@HystrixProperty(name = "timerDelayInMilliseconds", value = "1000"),
@HystrixProperty(name = "requestCache.enabled", value = "true")
})BatchCollapser
When the result of individual requests is not needed, a lightweight custom collapser can be built. It stores incoming requests in a thread‑safe container (e.g., LinkedBlockingDeque ) and triggers batch execution either after a time interval or when the container reaches a size threshold.
Key design points:
Uses java.util.concurrent.LinkedBlockingDeque for safe concurrent access.
A ScheduledExecutorService runs a timer thread to periodically drain the container.
When the container size exceeds a configured threshold, the batch is executed immediately.
Implementation snippet:
public class BatchCollapser
implements InitializingBean {
private static final Logger logger = LoggerFactory.getLogger(BatchCollapser.class);
private static final ScheduledExecutorService SCHEDULE_EXECUTOR = Executors.newScheduledThreadPool(1);
private volatile LinkedBlockingDeque
batchContainer = new LinkedBlockingDeque<>();
private Handler
, Boolean> cleaner;
private long interval;
private int threshHold;
// constructor omitted for brevity
@Override
public void afterPropertiesSet() throws Exception {
SCHEDULE_EXECUTOR.scheduleAtFixedRate(() -> {
try { this.clean(); } catch (Exception e) { logger.error("clean container exception", e); }
}, 0, interval, TimeUnit.MILLISECONDS);
}
public void submit(E event) {
batchContainer.add(event);
if (batchContainer.size() >= threshHold) { clean(); }
}
private void clean() {
List
transferList = Lists.newArrayListWithExpectedSize(threshHold);
batchContainer.drainTo(transferList, 100);
if (CollectionUtils.isEmpty(transferList)) return;
try { cleaner.handle(transferList); } catch (Exception e) { logger.error("batch execute error, transferList:{}", transferList, e); }
}
// singleton factory omitted for brevity
}ConcurrentHashMultiset
Guava's ConcurrentHashMultiset is a thread‑safe multiset that keeps a count for each element instead of overwriting duplicates. It is ideal for high‑frequency counting scenarios where many identical elements arrive in a short period.
Typical usage pattern:
if (ConcurrentHashMultiset.isEmpty()) {
return;
}
List
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);
});Summary of Scenarios
Hystrix Collapser : needed when each request's result must be returned and the extra latency is acceptable.
BatchCollapser : suitable when results are not required and merging can be triggered by time or count thresholds.
ConcurrentHashMultiset : best for high‑duplicate‑rate counting or aggregation tasks.
By combining BatchCollapser with ConcurrentHashMultiset , you can enjoy both time‑based merging and efficient in‑memory aggregation, achieving higher throughput with lower downstream pressure.
Top Architect
Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn together.
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.