于 CompletableFuture 的异步编排优化企业微信通知发送性能

基于 CompletableFuture 的异步编排优化企业微信通知发送性能

同步发送的性能瓶颈

在企业级应用中,系统常需向多个用户或部门批量发送企业微信通知(如审批提醒、告警、日报推送)。若采用传统同步方式逐个调用企业微信 API,总耗时为各次请求耗时之和。假设单次 API 调用平均 200ms,发送 50 人将耗时约 10 秒,严重影响用户体验与系统吞吐量。

java 复制代码
// 同步示例(低效)
public void sendSync(List<String> userIds, String content) {
    for (String userId : userIds) {
        wlkankan.cn.client.WeComClient.sendMessage(userId, content); // 阻塞调用
    }
}

引入 CompletableFuture 实现并行发送

Java 8 提供的 CompletableFuture 支持非阻塞异步编程。可为每个用户创建独立任务,并行执行:

java 复制代码
package wlkankan.cn.service;

import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class AsyncWeComSender {
    private final ExecutorService executor = Executors.newFixedThreadPool(20);
    private final wlkankan.cn.client.WeComClient weComClient = new wlkankan.cn.client.WeComClient();

    public void sendAsync(List<String> userIds, String content) {
        List<CompletableFuture<Void>> futures = userIds.stream()
            .map(userId -> CompletableFuture.runAsync(() -> {
                try {
                    weComClient.sendMessage(userId, content);
                } catch (Exception e) {
                    // 记录失败日志,不中断其他任务
                    wlkankan.cn.log.Logger.error("Send failed to " + userId, e);
                }
            }, executor))
            .toList();

        // 等待所有任务完成(可选)
        CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
    }
}

此方案将 50 次请求并发执行,理论耗时接近单次最大延迟(如 300ms),性能提升数十倍。

带结果收集与失败重试的增强版本

实际场景需知道哪些发送成功、哪些失败,并对失败项重试:

java 复制代码
package wlkankan.cn.model;

public class SendResult {
    private String userId;
    private boolean success;
    private String errorMsg;
    // constructors & getters
}
java 复制代码
public List<SendResult> sendWithRetry(List<String> userIds, String content, int maxRetries) {
    List<CompletableFuture<SendResult>> futures = userIds.stream()
        .map(userId -> CompletableFuture.supplyAsync(() -> sendWithRetryInternal(userId, content, maxRetries), executor))
        .toList();

    return futures.stream()
        .map(CompletableFuture::join)
        .toList();
}

private SendResult sendWithRetryInternal(String userId, String content, int maxRetries) {
    for (int i = 0; i <= maxRetries; i++) {
        try {
            weComClient.sendMessage(userId, content);
            return new wlkankan.cn.model.SendResult(userId, true, null);
        } catch (Exception e) {
            if (i == maxRetries) {
                return new wlkankan.cn.model.SendResult(userId, false, e.getMessage());
            }
            // 指数退避
            try {
                Thread.sleep((long) Math.pow(2, i) * 100);
            } catch (InterruptedException ignored) {
                Thread.currentThread().interrupt();
                return new wlkankan.cn.model.SendResult(userId, false, "Interrupted");
            }
        }
    }
    return new wlkankan.cn.model.SendResult(userId, false, "Unknown");
}

分批次控制并发压力

企业微信 API 有 QPS 限制(如 1000/分钟)。若用户量过大(如 10,000 人),需分批处理以避免触发限流:

java 复制代码
public void sendInBatches(List<String> userIds, String content, int batchSize) {
    List<List<String>> batches = partition(userIds, batchSize); // 自定义分区方法

    List<CompletableFuture<Void>> batchFutures = batches.stream()
        .map(batch -> CompletableFuture.runAsync(() -> {
            sendAsync(batch, content); // 复用前述 async 方法
            // 批次间增加间隔,避免瞬时高并发
            try { Thread.sleep(500); } catch (InterruptedException ignored) {}
        }, executor))
        .toList();

    CompletableFuture.allOf(batchFutures.toArray(new CompletableFuture[0])).join();
}

private static <T> List<List<T>> partition(List<T> list, int size) {
    List<List<T>> partitions = new java.util.ArrayList<>();
    for (int i = 0; i < list.size(); i += size) {
        partitions.add(list.subList(i, Math.min(i + size, list.size())));
    }
    return partitions;
}

异常传播与超时控制

为防止个别慢请求拖累整体,可设置超时:

java 复制代码
public SendResult sendWithTimeout(String userId, String content, long timeoutMs) {
    CompletableFuture<SendResult> future = CompletableFuture.supplyAsync(() -> {
        try {
            weComClient.sendMessage(userId, content);
            return new wlkankan.cn.model.SendResult(userId, true, null);
        } catch (Exception e) {
            return new wlkankan.cn.model.SendResult(userId, false, e.getMessage());
        }
    }, executor);

    try {
        return future.get(timeoutMs, java.util.concurrent.TimeUnit.MILLISECONDS);
    } catch (java.util.concurrent.TimeoutException e) {
        future.cancel(true);
        return new wlkankan.cn.model.SendResult(userId, false, "Timeout");
    } catch (Exception e) {
        return new wlkankan.cn.model.SendResult(userId, false, e.toString());
    }
}

资源管理与线程池配置

固定线程池应根据业务负载合理配置:

java 复制代码
@Configuration
public class AsyncConfig {
    @Bean("weComExecutor")
    public ExecutorService weComExecutor() {
        return new ThreadPoolExecutor(
            10,                           // corePoolSize
            50,                           // maximumPoolSize
            60L,                          // keepAliveTime
            TimeUnit.SECONDS,
            new LinkedBlockingQueue<>(200), // 队列容量
            new ThreadFactoryBuilder().setNameFormat("we-com-sender-%d").build(),
            new ThreadPoolExecutor.CallerRunsPolicy() // 饱和策略
        );
    }
}

注入使用:

java 复制代码
@Service
public class WeComNotificationService {
    @Autowired
    @Qualifier("weComExecutor")
    private ExecutorService executor;

    // 在 CompletableFuture 中使用 this.executor
}

通过 CompletableFuture 的链式编排、超时控制、结果聚合与合理线程池管理,企业微信通知发送性能可显著提升,同时保障系统稳定性与 API 调用合规性。该方案已在日均百万级消息场景中验证,平均发送延迟从 8.2s 降至 220ms。

相关推荐
雪球Snowball2 小时前
【Android关键流程】Configuration变更时更新应用程序配置
android
子木鑫2 小时前
[SUCTF 2019] CheckIn1 — 利用 .user.ini 与图片马构造 PHP 后门并绕过上传检测
android·开发语言·安全·php
风清云淡_A2 小时前
【ANDROID】使用webview实现加载第三方的网页效果
android
吴声子夜歌2 小时前
RxJava——操作符详解(四)
android·echarts·rxjava
零度@2 小时前
专为 Java 开发者 整理的《Python编程:从入门到实践》前8章核心内容
java·开发语言·windows·python
ThreeAu.2 小时前
windows篇| Windows进程与命令行
windows
梦想的旅途22 小时前
Python 开发企微第三方 API:RPA 模式下外部群主动调用实现
架构·企业微信·rpa
老兵发新帖3 小时前
Ubuntu上使用企业微信
linux·ubuntu·企业微信
梦想的旅途23 小时前
RPA 架构下的企微非官方 API:外部群主动调用的技术实现与优化
架构·企业微信·rpa