Skip to content

Conversation

@Carpe-Wang
Copy link
Contributor

@Carpe-Wang Carpe-Wang commented Jun 20, 2025

feat: Add dedicated thread pool for RestTemplate in alert notification system

What's changed?

  • RestTemplate thread pool
    Added a restTemplateThreadPool bean in
    hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/config/RestTemplateConfig.java
    with:

    • Core threads: 10
    • Max threads: 50
    • Queue capacity: 200
    • Thread name prefix: RestTemplate-
    • Rejection policy: CallerRunsPolicy
  • Async alert notification
    In
    hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/notice/impl/AbstractAlertNotifyHandlerImpl.java
    introduced a sendAsync() method and injected the new thread‐pool executor to offload HTTP calls.

  • Parallel dispatch optimization
    Updated
    hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/notice/AlertNoticeDispatch.java
    to:

    • Use CompletableFuture for sending notifications in parallel
    • Handle errors per‐receiver without blocking others
    • Accept the thread‐pool executor via constructor
  • Test updates
    In
    hertzbeat-alerter/src/test/java/org/apache/hertzbeat/alert/notice/AlertNoticeDispatchTest.java
    updated the constructor calls to pass a mock executor and added test cases covering the async paths.

Checklist

  • I have read the Contributing Guide
  • I have written the necessary doc or comment.
  • I have added the necessary unit tests and all cases have passed.

Add or update API

  • I have added the necessary e2e tests and all cases have passed.

@tomsun28 tomsun28 added this to the 1.7.3 milestone Jun 20, 2025
Comment on lines 66 to 68
executor.setCorePoolSize(10);
executor.setMaxPoolSize(50);
executor.setQueueCapacity(200);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hi the http request require timeliness, suggest use the SynchronousQueue instead of executor.setQueueCapacity(200); , and executor.setCorePoolSize(2); executor.setMaxPoolSize(Integer.MAX_VALUE);

Comment on lines 122 to 133
protected CompletableFuture<Void> sendAsync(org.apache.hertzbeat.common.entity.alerter.NoticeReceiver receiver,
org.apache.hertzbeat.common.entity.alerter.NoticeTemplate noticeTemplate,
GroupAlert alert) {
return CompletableFuture.runAsync(() -> {
try {
send(receiver, noticeTemplate, alert);
} catch (Exception e) {
log.error("Async alert notification failed", e);
throw new RuntimeException(e);
}
}, restTemplateThreadPool);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seem no others use this method.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have deleted this piece of code.

Comment on lines 138 to 146
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
.whenComplete((result, exception) -> {
if (exception != null) {
log.warn("Some async notifications failed", exception);
} else {
log.debug("All notifications completed for alert: {}", alert.getGroupLabels());
}
});
})));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hi can you describe why use the CompleableFuture here, sendNoticeMsg does not need to return a result; it just needs to handle exceptions.

@tomsun28
Copy link
Member

hi suggest merge this in 1.7.3 after 1.7.2 released.

Comment on lines +131 to +137
matchNoticeRulesByAlert(alert).ifPresent(noticeRules -> noticeRules.forEach(rule -> workerPool.executeNotify(() -> {
rule.getReceiverId().forEach(receiverId -> {
restTemplateThreadPool.execute(() -> {
try {
sendNoticeMsg(getOneReceiverById(receiverId),
getOneTemplateById(rule.getTemplateId()), alert);
} catch (AlertNoticeException e) {
log.warn("DispatchTask sendNoticeMsg error, message: {}", e.getMessage());
sendNoticeMsg(getOneReceiverById(receiverId), getOneTemplateById(rule.getTemplateId()), alert);
} catch (Exception e) {
log.warn("Async notification failed for receiver {}: {}", receiverId, e.getMessage());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking back at this PR again, it is not recommended that the alterer module use the manager module's bean, and the notification uses two thread pools. suggest that use the workerPool instead of restTemplateThreadPool directly.

        matchNoticeRulesByAlert(alert).ifPresent(noticeRules -> noticeRules.forEach(rule -> rule.getReceiverId()
                .forEach(receiverId -> {
                    workerPool.executeNotify(() -> {
                        try {
                            sendNoticeMsg(getOneReceiverById(receiverId),
                                    getOneTemplateById(rule.getTemplateId()), alert);
                        } catch (AlertNoticeException e) {
                            log.warn("DispatchTask sendNoticeMsg error, message: {}", e.getMessage());
                        }  
                    });
                })));

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants