第29课:微服务异步调用、线程池优化 & 事件驱动开发

文章目录

适配版本 :Spring Boot 4.0.8、Spring Framework 7.0.x、Spring Cloud 2025.1.3(Oakwood)、JDK 21、Micrometer 2.x

课程定位:微服务高阶阶段开篇,从异步调用原理到线程池精细化调优,从日志上下文透传到事件驱动解耦,构建高性能微服务架构

一、开篇:从同步阻塞到异步非阻塞的性能跃迁

第25-28课我们完成了可观测性体系的全部建设。Prometheus告诉你系统状态如何,SkyWalking告诉你请求经过了哪些服务,ELK告诉你系统在某个时刻发生了什么。可观测性解决了"如何发现问题",但还有一个更根本的问题:如何从架构上避免问题?

考虑一个典型的下单场景:用户提交订单后,系统需要执行一系列操作------扣减库存、生成订单记录、发送短信通知、推送用户积分、记录操作日志。如果这些操作全部同步执行,用户需要等待所有步骤完成才能得到响应。假设扣减库存耗时200ms、生成订单100ms、发送短信300ms、推送积分150ms、记录日志50ms------总计800ms。其中,发送短信和推送积分与订单创建的核心流程无关,用户完全不需要等待它们完成。

这就是异步调用要解决的问题:将非核心的、耗时的操作从主流程中剥离,让主流程快速返回响应,后台线程异步完成剩余工作。同步调用模型下,整个请求的响应时间由最慢的环节决定;异步模型下,响应时间只取决于核心流程。

但异步不是"银弹"。引入异步后,你会遇到一系列新问题:线程池怎么配置才不会OOM?异步方法抛出的异常为什么消失了?日志中的TraceID为什么断了?多个业务模块之间的调用耦合越来越重,怎么解耦?

本课将逐一解决这些问题。Spring Boot 4.0带来了一个根本性的变化------虚拟线程默认化,这意味着@Async的底层执行策略发生了范式转变。原来为平台线程精心调优的线程池参数可能不再适用,需要重新审视。

二、@Async异步调用完整实战

2.1 异步调用的三种实现方式

Spring Boot中实现异步调用有三种方式,适用场景各不相同:

方式 实现 适用场景 返回值
@Async注解 方法上添加@Async 无返回值的后台任务 void / Future
CompletableFuture 手动编排异步任务 需要组合多个异步结果 CompletableFuture
TaskExecutor 手动提交任务 需要精细控制任务执行 Future

2.2 @Async注解实战

第一步:启用异步支持。 在启动类或配置类上添加@EnableAsync注解:

java 复制代码
@SpringBootApplication
@EnableAsync
public class OrderServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(OrderServiceApplication.class, args);
    }
}

第二步:定义异步方法。 在Service中定义异步方法,方法上添加@Async注解:

java 复制代码
@Service
@Slf4j
public class OrderAsyncService {

    @Async
    public void sendSmsNotification(Long orderId, String phone) {
        log.info("发送短信通知: orderId={}, phone={}", orderId, phone);
        // 调用短信服务,耗时约300ms
        smsClient.send(phone, "您的订单已创建,订单号:" + orderId);
        log.info("短信通知发送完成: orderId={}", orderId);
    }

    @Async
    public void addUserPoints(Long userId, int points) {
        log.info("增加用户积分: userId={}, points={}", userId, points);
        // 调用积分服务,耗时约150ms
        pointsClient.addPoints(userId, points);
    }
}

第三步:调用异步方法。 在Controller或Service中注入并调用:

java 复制代码
@RestController
@RequestMapping("/api/order")
public class OrderController {

    @Autowired
    private OrderAsyncService orderAsyncService;

    @PostMapping("/create")
    public Result<OrderVO> createOrder(@RequestBody OrderCreateRequest request) {
        // 1. 同步执行:创建订单(核心流程)
        OrderVO order = orderService.createOrder(request);

        // 2. 异步执行:发送短信通知(非核心流程)
        orderAsyncService.sendSmsNotification(order.getId(), request.getPhone());

        // 3. 异步执行:推送用户积分(非核心流程)
        orderAsyncService.addUserPoints(request.getUserId(), order.getTotalAmount().intValue());

        return Result.success(order);
    }
}

关键规则:

  • 异步方法必须是public的
  • 异步方法不能是static的
  • 异步方法必须通过Spring代理 调用------在同一个类中直接调用@Async方法不会生效
  • 异步方法所在的Bean必须由Spring管理(@Service、@Component等)

踩坑提示 :同一个类中的方法互相调用@Async方法不会生效 ,因为调用没有经过Spring代理。解决方案是注入自身(@Autowired private OrderAsyncService self;)或通过ApplicationContext.getBean()获取代理对象。

2.3 带返回值的异步方法

@Async方法可以返回CompletableFuture<T>,调用方可以通过get()或thenAccept()获取结果:

java 复制代码
@Async
public CompletableFuture<UserDTO> getUserAsync(Long userId) {
    log.info("异步查询用户: userId={}", userId);
    UserDTO user = userMapper.selectById(userId);
    return CompletableFuture.completedFuture(user);
}

调用方可以并行调用多个异步方法,最后汇总结果:

java 复制代码
public OrderDetailVO getOrderDetail(Long orderId) {
    CompletableFuture<UserDTO> userFuture = orderAsyncService.getUserAsync(userId);
    CompletableFuture<ProductDTO> productFuture = orderAsyncService.getProductAsync(productId);
    CompletableFuture<StockDTO> stockFuture = orderAsyncService.getStockAsync(productId);

    // 等待所有异步任务完成
    CompletableFuture.allOf(userFuture, productFuture, stockFuture).join();

    OrderDetailVO detail = new OrderDetailVO();
    detail.setUser(userFuture.join());
    detail.setProduct(productFuture.join());
    detail.setStock(stockFuture.join());
    return detail;
}

执行效果:三个查询并行执行,总耗时 = max(200ms, 150ms, 100ms) = 200ms,而非串行的450ms。

三、Spring Boot 4.0虚拟线程与线程池配置

3.1 虚拟线程默认化:范式转变

Spring Boot 4.0最大的变化之一:虚拟线程从3.2的"opt-in实验特性"变成了"推荐默认值" 。在Java 21+环境中,Tomcat和Jetty的请求处理线程默认使用虚拟线程,@Async任务和定时任务也遵循同一模型。

这意味着原来精心配置的线程池参数可能不再适用。虚拟线程与平台线程的底层模型完全不同:

维度 平台线程 虚拟线程
内存占用 ~1MB/线程 ~几KB/线程
最大数量 数千 数百万
创建开销 高 极低
适用场景 CPU密集型 IO密集型
ThreadLocal 安全 需注意清理策略

迁移时需要检查的配置项:

  • spring.threads.virtual.enabled(4.0中默认为true)
  • 自定义的ThreadPoolTaskExecutor配置是否与虚拟线程冲突
  • ThreadLocal的使用模式------虚拟线程可以创建数百万实例,ThreadLocal的清理策略需要重新审视
  • CPU密集型任务不受益于虚拟线程,需要单独配置平台线程池

3.2 虚拟线程下@Async的自动配置

在Spring Boot 4.0中,当虚拟线程启用时,自动配置的AsyncTaskExecutor是一个使用虚拟线程的SimpleAsyncTaskExecutor,而非传统的ThreadPoolTaskExecutor。

重要限制 :@Async异步任务默认仍使用平台线程 ,需要单独配置才能使用虚拟线程。如果你希望@Async任务也使用虚拟线程,需要显式配置:

java 复制代码
@Configuration
@EnableAsync
public class AsyncVirtualThreadConfig implements AsyncConfigurer {

    @Override
    public Executor getAsyncExecutor() {
        // 使用虚拟线程的SimpleAsyncTaskExecutor
        return new SimpleAsyncTaskExecutor(
            Thread.ofVirtual().name("async-vt-", 0).factory()
        );
    }
}

关键区别:

  • Tomcat请求处理线程:默认使用虚拟线程(Spring Boot 4.0自动配置)
  • @Async任务线程:默认使用平台线程池,需显式配置虚拟线程
  • CPU密集型任务:应使用平台线程池,虚拟线程的优势在于IO等待

3.3 线程池精细化配置

对于需要精细控制的场景(如CPU密集型任务、需要限制并发数的IO任务),仍需自定义ThreadPoolTaskExecutor。

通过YAML配置(推荐用于简单场景):

yaml 复制代码
spring:
  task:
    execution:
      pool:
        core-size: 8          # 核心线程数
        max-size: 16          # 最大线程数
        queue-capacity: 100   # 队列容量
        keep-alive: 10s       # 空闲线程存活时间
      thread-name-prefix: async-  # 线程名前缀

当ThreadPoolTaskExecutor自动配置时,线程池默认使用8个核心线程,这些线程可以根据负载动态增长和收缩。可以通过spring.task.execution命名空间微调默认设置。配置为queue-capacity: 100后,当队列满(100个任务)时,线程池增加到最大16个线程;空闲10秒后,线程被回收。

通过Java配置(推荐用于生产环境):

java 复制代码
@Configuration
@EnableAsync
public class AsyncThreadPoolConfig {

    @Bean("orderTaskExecutor")
    public ThreadPoolTaskExecutor orderTaskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10);
        executor.setMaxPoolSize(50);
        executor.setQueueCapacity(200);
        executor.setKeepAliveSeconds(60);
        executor.setThreadNamePrefix("order-async-");
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.setWaitForTasksToCompleteOnShutdown(true);
        executor.setAwaitTerminationSeconds(30);
        executor.initialize();
        return executor;
    }
}

使用指定线程池:

java 复制代码
@Async("orderTaskExecutor")
public void sendSmsNotification(Long orderId, String phone) {
    // 使用 orderTaskExecutor 线程池执行
}

3.4 线程池参数调优指南

参数对照表:

参数 作用 默认值 生产建议
corePoolSize 核心线程数 8 IO密集型:CPU核数×2;CPU密集型:CPU核数+1
maxPoolSize 最大线程数 Integer.MAX_VALUE 根据系统承载能力设置,建议core×2~5
queueCapacity 队列容量 Integer.MAX_VALUE 必须显式设置,否则队列无限大,OOM风险
keepAliveSeconds 空闲线程存活时间 60s 60~300s
rejectedExecutionHandler 拒绝策略 AbortPolicy 见下表

队列容量是最大的坑 :Spring默认队列容量是Integer.MAX_VALUE,意味着线程池永远不会触发maxPoolSize(因为队列永远塞不满),直到内存被撑爆(OOM)。生产环境必须显式配置queueCapacity。

拒绝策略选择:

策略 行为 适用场景
AbortPolicy 抛出RejectedExecutionException 默认策略,任务不可丢弃
CallerRunsPolicy 由调用线程执行 任务不可丢弃且可以降级
DiscardPolicy 静默丢弃 任务可丢弃(如日志上报)
DiscardOldestPolicy 丢弃最旧的任务 保留最新任务

IO密集型 vs CPU密集型:

任务类型 特征 corePoolSize建议 说明
IO密集型 等待外部IO(DB、HTTP、MQ) CPU核数 × 2~4 线程大部分时间在等待
CPU密集型 计算(压缩、加密、图像处理) CPU核数 + 1 线程持续占用CPU

3.5 虚拟线程 vs 平台线程池的选择

条件 推荐方案
Java 21+ 且任务为IO密集型 虚拟线程 (spring.threads.virtual.enabled=true)
Java 21+ 但任务为CPU密集型 平台线程池 (显式配置ThreadPoolTaskExecutor)
Java 17 或更低版本 平台线程池
需要精细控制并发数 平台线程池(虚拟线程无法限制并发数)

四、异步异常处理与日志上下文透传

4.1 异步异常处理的"黑洞"问题

@Async方法抛出的异常不会传播到调用方 。如果@Async方法返回void,异常只能被日志记录;如果返回CompletableFuture,异常会被包装在Future中,调用方需要显式调用get()才能看到异常。

解决方案:实现AsyncUncaughtExceptionHandler

java 复制代码
@Configuration
@EnableAsync
public class AsyncExceptionConfig implements AsyncConfigurer {

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return new CustomAsyncExceptionHandler();
    }
}

@Slf4j
public class CustomAsyncExceptionHandler implements AsyncUncaughtExceptionHandler {

    @Override
    public void handleUncaughtException(Throwable ex, Method method, Object... params) {
        log.error("异步方法执行异常: method={}, params={}", method.getName(), Arrays.toString(params), ex);
        // 可以在这里发送告警、记录审计日志等
        alertService.sendAlert("异步任务异常: " + method.getName(), ex);
    }
}

通过@EnableAsync配置:

java 复制代码
@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return new CustomAsyncExceptionHandler();
    }
}

4.2 MDC日志上下文透传:解决TraceID断链

@Async方法会将任务放到线程池中的另一个线程执行,而MDC(Mapped Diagnostic Context)是基于ThreadLocal的,新线程里没有父线程的TraceID,导致日志断链。

解决方案:自定义TaskDecorator

Spring提供了TaskDecorator接口,允许在任务提交到线程池时,包装任务执行逻辑,把父线程的MDC上下文传递过去:

java 复制代码
import org.slf4j.MDC;
import org.springframework.core.task.TaskDecorator;

public class MdcTaskDecorator implements TaskDecorator {

    @Override
    public Runnable decorate(Runnable runnable) {
        // 在父线程中获取 MDC 上下文(此时还在父线程中)
        Map<String, String> contextMap = MDC.getCopyOfContextMap();
        return () -> {
            try {
                // 在子线程中恢复 MDC 上下文
                if (contextMap != null) {
                    MDC.setContextMap(contextMap);
                }
                runnable.run();
            } finally {
                // 子线程执行完成后清除 MDC,防止污染线程池中的其他任务
                MDC.clear();
            }
        };
    }
}

注册到线程池:

java 复制代码
@Bean("orderTaskExecutor")
public ThreadPoolTaskExecutor orderTaskExecutor() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(10);
    executor.setMaxPoolSize(50);
    executor.setQueueCapacity(200);
    executor.setThreadNamePrefix("order-async-");
    executor.setTaskDecorator(new MdcTaskDecorator());  // 关键:注入 MDC 装饰器
    executor.initialize();
    return executor;
}

为什么MDC.clear()必须在finally中执行? 线程池会复用线程。如果上一个任务的MDC没有被清理,下一个任务会"继承"上一个任务的TraceID,导致日志串号------这是生产环境中最隐蔽的排查陷阱之一。

4.3 异步调用的可观测性

Spring Boot 4.0的Actuator和Micrometer可以暴露线程池的指标(活跃线程数、队列深度、任务总数),帮助在生产环境中监控异步任务的健康状况:

yaml 复制代码
management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics,prometheus

关键指标:

指标 含义 告警阈值
executor.active 活跃线程数 持续接近maxSize
executor.queued 队列中等待的任务数 持续>0
executor.completed 已完成任务数 用于趋势分析

五、Spring事件驱动开发

5.1 为什么要用事件驱动

在微服务架构中,一个业务操作往往需要触发多个下游操作。以"用户注册"为例:注册成功后需要发送欢迎邮件、发放新人优惠券、初始化用户积分、记录注册日志。如果全部在UserService.register()中同步调用,会导致:

  • 代码耦合严重 :UserService依赖了邮件、优惠券、积分、日志四个服务
  • 性能瓶颈:所有操作串行执行,注册接口响应时间 = 各操作耗时之和
  • 扩展困难 :新增一个"注册后发送站内信"的功能,需要修改UserService的代码

Spring事件驱动 基于发布-订阅模式(观察者模式),使得系统中不同模块可以通过事件进行松散耦合的通信,而不需要直接依赖彼此。UserService只需要发布一个"用户已注册"事件,邮件、优惠券、积分等模块各自监听该事件并执行自己的逻辑。

5.2 同步事件实战

定义事件:

java 复制代码
public class UserRegisteredEvent extends ApplicationEvent {

    private final Long userId;
    private final String phone;

    public UserRegisteredEvent(Object source, Long userId, String phone) {
        super(source);
        this.userId = userId;
        this.phone = phone;
    }

    public Long getUserId() { return userId; }
    public String getPhone() { return phone; }
}

发布事件:

java 复制代码
@Service
public class UserService {

    @Autowired
    private ApplicationEventPublisher eventPublisher;

    public void register(UserCreateRequest request) {
        // 1. 核心业务:创建用户
        User user = new User();
        user.setPhone(request.getPhone());
        userMapper.insert(user);

        // 2. 发布事件(同步)
        eventPublisher.publishEvent(new UserRegisteredEvent(this, user.getId(), request.getPhone()));
    }
}

监听事件:

java 复制代码
@Component
@Slf4j
public class UserRegisteredEventListener {

    @EventListener
    public void handleWelcomeEmail(UserRegisteredEvent event) {
        log.info("发送欢迎邮件: userId={}", event.getUserId());
        emailService.sendWelcomeEmail(event.getUserId());
    }

    @EventListener
    public void handleCoupon(UserRegisteredEvent event) {
        log.info("发放新人优惠券: userId={}", event.getUserId());
        couponService.grantNewUserCoupon(event.getUserId());
    }

    @EventListener
    public void handlePoints(UserRegisteredEvent event) {
        log.info("初始化用户积分: userId={}", event.getUserId());
        pointsService.initPoints(event.getUserId());
    }
}

5.3 异步事件:@Async + @EventListener

默认情况下,Spring事件是同步的,发布者线程会阻塞直到所有监听器完成处理。如果监听器的处理耗时较长(如发送邮件、调用外部服务),会导致发布者的响应时间增加。

解决方案:为监听器添加@Async注解:

java 复制代码
@Component
@Slf4j
public class UserRegisteredEventListener {

    @Async("orderTaskExecutor")
    @EventListener
    public void handleWelcomeEmail(UserRegisteredEvent event) {
        log.info("异步发送欢迎邮件: userId={}", event.getUserId());
        emailService.sendWelcomeEmail(event.getUserId());
    }

    @Async("orderTaskExecutor")
    @EventListener
    public void handleCoupon(UserRegisteredEvent event) {
        log.info("异步发放新人优惠券: userId={}", event.getUserId());
        couponService.grantNewUserCoupon(event.getUserId());
    }
}

关键理解 :@EventListener和@Async配合使用时,事件发布者立即返回,监听器在异步线程中执行。这实现了"发布即返回"的效果。

踩坑提示 :@Async + @EventListener组合要求@EnableAsync已启用,且监听器必须由Spring管理。同时,异步监听器的异常处理同样需要AsyncUncaughtExceptionHandler。

5.4 事务事件:@TransactionalEventListener

Spring事件的一个经典陷阱:在事务提交前发布事件。如果事务回滚,事件已经发出去了,监听器可能已经执行了不可逆的操作(如发送了邮件)。

@TransactionalEventListener解决了这个问题------它允许将监听器绑定到事务的特定阶段:

java 复制代码
@Component
@Slf4j
public class UserRegisteredTransactionListener {

    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    @Async("orderTaskExecutor")
    public void handleAfterCommit(UserRegisteredEvent event) {
        log.info("事务提交后处理: userId={}", event.getUserId());
        // 只有事务提交成功后才执行
        emailService.sendWelcomeEmail(event.getUserId());
        couponService.grantNewUserCoupon(event.getUserId());
    }

    @TransactionalEventListener(phase = TransactionPhase.AFTER_ROLLBACK)
    public void handleAfterRollback(UserRegisteredEvent event) {
        log.warn("事务回滚,执行清理: userId={}", event.getUserId());
        // 事务回滚后的清理逻辑
    }
}

事务阶段对照表:

phase 含义 适用场景
BEFORE_COMMIT 事务提交前 需要在提交前做最后检查
AFTER_COMMIT 事务成功提交后(默认) 发送邮件、发放优惠券
AFTER_ROLLBACK 事务回滚后 清理资源、记录回滚日志
AFTER_COMPLETION 事务完成(提交或回滚) 无论结果如何都要执行

关键规则 :@TransactionalEventListener默认在AFTER_COMMIT阶段执行。如果没有事务上下文,监听器默认不会执行 。可以通过fallbackExecution = true让监听器在没有事务时也执行:

java 复制代码
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT, fallbackExecution = true)

5.5 事件驱动 vs 消息队列的选型

维度 Spring事件 消息队列(RocketMQ/Kafka)
作用范围 单个JVM内 跨服务、跨系统
可靠性 进程重启后事件丢失 持久化,支持重试
异步性 同步或异步 天然异步
适用场景 模块解耦、业务编排 服务解耦、流量削峰
复杂度 低(零依赖) 高(需维护MQ集群)

选型建议:

  • 同一服务内的模块解耦 → Spring事件
  • 跨服务的异步通信 → 消息队列(第31课详解)
  • 需要事务保证的事件 → @TransactionalEventListener
  • 需要可靠投递的事件 → 消息队列 + 本地消息表

六、踩坑指南

坑一:@Async方法在同一个类中调用不生效

现象 :在Service中定义了一个@Async方法,但在同一个类的另一个方法中直接调用,异步未生效。

原因 :Spring的@Async基于AOP代理实现。同一个类内部的方法调用不会经过代理,因此@Async注解被忽略。

解决 :注入自身(@Autowired private OrderAsyncService self;),通过self.asyncMethod()调用;或将异步方法抽取到独立的Service中。

坑二:线程池队列无限大导致OOM

现象:系统在高峰期出现OOM,线程池活跃线程数始终等于corePoolSize,从未增长到maxPoolSize。

原因 :queueCapacity使用了默认值Integer.MAX_VALUE,队列永远塞不满,maxPoolSize永远不会触发,任务无限堆积在队列中,最终OOM。

解决 :显式配置queueCapacity(建议200~1000),并设置合理的拒绝策略(CallerRunsPolicy或AbortPolicy)。

坑三:虚拟线程下ThreadLocal未清理导致内存泄漏

现象:启用虚拟线程后,内存使用量持续增长。

原因:虚拟线程可以创建数百万个,每个虚拟线程的ThreadLocal如果未清理,会累积占用大量内存。

解决 :在虚拟线程中,避免使用ThreadLocal ,改用ScopedValue(JDK 21预览特性);如果必须使用ThreadLocal,确保在finally中调用remove()。

坑四:异步方法抛出的异常"消失"了

现象 :@Async方法执行失败,但调用方没有任何感知,日志中也没有错误信息。

原因 :@Async void方法的异常不会被传播到调用方,默认只被日志记录(SimpleAsyncUncaughtExceptionHandler)。如果日志级别配置不当,异常可能被吞掉。

解决 :实现AsyncUncaughtExceptionHandler,在异步方法抛出未捕获异常时记录详细日志并发送告警。

坑五:MDC在异步线程中丢失导致TraceID断链

现象:异步方法中的日志没有TraceID,无法与主线程的日志关联。

原因 :MDC基于ThreadLocal,异步线程无法访问父线程的MDC。

解决 :自定义TaskDecorator,在任务提交时捕获MDC快照,在任务执行时恢复(详见第4.2节)。

坑六:@TransactionalEventListener在无事务时静默跳过

现象 :@TransactionalEventListener监听器没有执行,但没有任何错误日志。

原因 :@TransactionalEventListener默认只在事务上下文中执行。如果没有活动事务,监听器会被静默跳过。

解决 :设置fallbackExecution = true,或确保发布事件的方法上有@Transactional注解。

坑七:事件监听器中事务传播导致死锁

现象:同步事件监听器中执行了数据库操作,与主事务产生锁竞争,导致死锁。

原因 :同步@EventListener会加入当前事务,监听器中的数据库操作与主事务共享同一个数据库连接,可能导致锁竞争。

解决 :将监听器改为异步(@Async)或使用@TransactionalEventListener(phase = AFTER_COMMIT),让监听器在主事务提交后执行。

七、课后作业

作业一 :在service-order中创建OrderAsyncService,实现两个异步方法:sendSmsNotification(模拟耗时300ms)和addUserPoints(模拟耗时150ms)。在订单创建接口中调用这两个异步方法,验证接口响应时间不包含异步方法的耗时。

作业二 :配置自定义ThreadPoolTaskExecutor(核心线程10、最大线程50、队列容量200),并通过@Async("orderTaskExecutor")指定使用该线程池。在/actuator/metrics中查看线程池的活跃线程数和队列深度指标。

作业三 :实现AsyncUncaughtExceptionHandler,在异步方法抛出异常时记录详细日志并发送告警。验证异常处理方法被正确调用。

作业四 :实现MdcTaskDecorator,解决异步方法中TraceID断链问题。验证:在异步方法中输出的日志包含与主线程相同的TraceID。

作业五(进阶) :实现基于@TransactionalEventListener的用户注册事件。当用户注册事务提交成功后,异步执行"发送欢迎邮件"和"发放优惠券"两个监听器。验证:如果注册事务回滚,监听器不会执行。

八、下节预告

第30课将进入分布式事务解决方案 & Seata最终一致性实战 。内容包括分布式事务问题剖析、CAP/BASE理论、TCC/XA/AT三种模式的原理与对比、Seata架构详解、AT模式实战(含@GlobalTransactional注解)、事务回滚与超时处理,以及生产环境的事务适配方案。本课解决了单服务内的异步与解耦 问题,第30课将解决跨服务的分布式事务一致性问题------当一个业务操作需要跨多个服务、多个数据库时,如何保证数据的一致性。

🔗《最新版 SpringCloud 2025 从入门到实战》系列课程导航

去订阅

第一部分:微服务前置基础 & 新版环境搭建(第1-5课)

第二部分:注册中心核心(Nacos 最新版)(第6-9课)

第三部分:配置中心核心(Nacos配置中心)(第10-12课)

第四部分:服务通信核心(OpenFeign + LoadBalancer)(第13-16课)

第五部分:网关核心(SpringCloud Gateway 新版)(第17-20课)

第六部分:熔断、限流、降级(Sentinel 新版)(第21-24课)

第七部分:微服务监控、链路追踪、日志体系(第25-28课)

第八部分:微服务高阶特性 & 分布式核心能力(第29-31课)

第九部分:企业级完整项目实战 & 架构复盘(第32-35课)

相关推荐
.Hypocritical.2 小时前
【Java 进阶】泛型深度解析|泛型类、泛型方法、通配符、类型擦除、PECS 原则
java·开发语言·泛型
这个DBA有点耶2 小时前
MySQL B-tree索引并发控制优化:写密集场景的锁瓶颈与内核级解法
java·数据库·程序人生·算法·database·dba
java_nnnn2 小时前
Redis 初识:从单体架构到分布式架构的演进
redis·分布式·架构
RuoyiOffice2 小时前
Spring AI+Vue3 企业知识库设计:让每个答案有依据,让每份资料守住权限
java·spring boot·vue3·rag·企业知识库·spring ai·ruoyi office
Tsuki_tl2 小时前
Redis过期策略
redis·缓存·过期策略·内存淘汰机制·分布式开发·惰性删除·定期删除
CarIise2 小时前
Maven私服(Nexus)
微服务
敲上瘾2 小时前
分布式系统——Redis哨兵:自动故障转移与选举原理
linux·数据库·redis·分布式·缓存·微服务
倔强的石头1063 小时前
【Linux指南】进程通信系列(二):匿名管道 —— pipe 的本质、fork 继承与四种读写规则
java·linux·运维
调试优选官3 小时前
2026年IoT物联网开发赛道年度榜单深度解析:D-coding物联网系统定制与源码交付能力专项解读
java·物联网·struts·iot·开发经验