⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 juejin.cn/post/7525726627960291391 「一旅人」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注微信公众号:【芋道源码】有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言都将得到认真回复。甚至不知道如何读源码也可以请教噢。
  4. 新的源码解析文章实时收到通知。每周更新一篇左右。
  5. 认真的源码交流微信群。

引言:当咖啡店遭遇程序员

“顾客挤爆柜台时,优秀的店长不会催促咖啡师加速,而是启动一套科学的协作机制——就像Spring事件驱动,用发布-订阅模式让系统像顶级咖啡团队般优雅应对洪峰流量”

一、咖啡店里的监听器:3位灵魂角色

真实战场还原(每秒1000订单的咖啡店):

1. 事件定义:咖啡店的「订单小票」

public class OrderEvent extends ApplicationEvent {
// final修饰的订单ID:就像咖啡师绝不涂改的订单小票
private final String orderId;

// 创建时间:记录订单诞生时刻(线程安全不可变)
private final LocalDateTime createTime = LocalDateTime.now();

// 无setter:防止多线程并发篡改订单
}

2. 事件发布:店长的「广播系统」

@Service
public class OrderService {
// 店长的麦克风(构造器注入更优雅)
private final ApplicationEventPublisher eventPublisher;

public void createOrder(Order order) {
// 核心业务:生成订单(咖啡店接单)
eventPublisher.publishEvent(new OrderEvent(this, order.getId())); // 📢 广播订单
}
}

3. 事件监听:咖啡团队的「技能响应」

@Component
public class CoffeeMakerListener {
@EventListener
@Order(1) // 优先级:先做咖啡再推荐甜点
public void makeCoffee(OrderEvent event) {
// 专注做咖啡,不关心谁结账
log.info("咖啡师:开始制作订单{}的拿铁...", event.getOrderId());
}
}

二、扛住亿级流量的3把利器

场景1:冷启动缓存预加载(防雪崩)

@Component
public class CachePreloader {
// 在Spring容器"开店准备完成"时触发
@EventListener(ContextRefreshedEvent.class)
public void initCache() {
// 异步加载省时30%(实测数据)
CompletableFuture.runAsync(() -> {
provinceService.loadProvincesToCache();
productService.preloadHotProducts();
});
}
}

场景2:事务成功后的缓存清理(保一致性)

// 只在数据库提交成功后执行(避免脏清理)
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void cleanCache(OrderUpdateEvent event) {
// 异步清理:不阻塞结账队伍
redisTemplate.executeAsync(new RedisCallback<>() {
@Override
public Void doInRedis(RedisConnection connection) {
connection.del(("order:" + event.getId()).getBytes());
return null;
}
});
}

场景3:无侵入式功能扩展

改造前(臃肿的收银台):

public void pay() {
paymentService.pay(); // 核心支付
auditService.log(); // 审计代码入侵
riskService.check(); // 风控代码耦合
marketingService.addPoints(); // 新增需求污染核心
}

事件驱动改造后:

// 纯净支付核心(专注收钱)
public void pay(Long orderId) {
paymentService.process(orderId);
eventPublisher.publishEvent(new PaymentSuccessEvent(orderId)); // 📢 广播支付成功
}

// 新增积分模块(无需修改支付代码)
@Component
public class PointListener {
@EventListener
public void addPoints(PaymentSuccessEvent event) {
// 积分服务独立演进
pointService.award(event.getOrderId(), 100);
}
}

三、血泪教训:3个深夜加班事故

事故1:多线程篡改事件(订单混乱)

// 错误!事件必须是只读的
@EventListener
public void handle(OrderEvent event) {
event.setStatus("MODIFIED"); // ⚠️ 多线程并发修改引发订单错乱
}

正确做法:事件类设计为final字段 + 无setter

事故2:异步事件丢失(顾客投诉)

@SpringBootApplication
@EnableAsync // 必须显式开启异步
public class Application {
@Bean("eventExecutor")
public Executor taskExecutor() {
// 关键参数:拒绝策略用CallerRunsPolicy(避免丢单)
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
return executor;
}
}

// 指定线程池执行
@Async("eventExecutor")
@EventListener
public void asyncHandle(OrderEvent event) {...}

事故3:事件循环调用(咖啡师卡死)

// 错误:在事件处理中发布新事件
@EventListener
public void handleA(EventA a) {
publisher.publishEvent(new EventB());
}

@EventListener
public void handleB(EventB b) {
publisher.publishEvent(new EventA()); // ♻️ 死循环!
}

四、关键抉择:监听器 vs MQ 架构对垒

维度 Spring监听器 MQ消息队列
适用场景 单机事务协作 ✅ 跨服务通信 ✅
可靠性 进程宕机事件消失 ❌ 持久化/重试 ✅
吞吐量 内存级传输,10w+/s 🚀 受网络限制,1w/s ⚠️
开发效率 免搭建MQ,注解即用 ✅ 需部署中间件 ❌
数据一致性 本地事务保障 ✅ 需分布式事务 ⚠️

黄金决策树:

  • 同JVM事务操作 → Spring监听器(开发效率王炸)
  • 跨服务最终一致 → RocketMQ(可靠性担当)

五、性能调优:监听器的涡轮增压

异步喷射:

@Async // 方法级异步(线程池加速)
@EventListener
public void asyncProcess(LogEvent event) {...}

条件过滤(减少无效处理):

// 只处理VIP客户的订单
@EventListener(condition = "#event.user.level == 'VIP'")
public void handleVipOrder(OrderEvent event) {...}

批量处理(Spring 4.2+特性):

// 一次性处理整批订单(提升数据库IO效率)
@EventListener
public void batchProcess(List<OrderEvent> events) {
orderDao.batchInsert(events.stream().map(OrderConverter::toEntity).toList());
}

六、最佳实践:5条生存法则

单一职责原则

一个监听器只做一件事:如 PaymentListener 只处理支付,CouponListener 只发券

事件轻量化

禁止在事件中携带 HttpSession 等重型对象(建议只传ID)

异常隔离舱

异步事件必须独立捕获异常:

@Async
@EventListener
public void handle(Event event) {
try {
businessLogic();
} catch (Exception e) {
// 记录日志 + 告警(防止雪崩)
log.error("事件处理失败: {}", event, e);
alarmManager.notify(e);
}
}

版本兼容设计

事件类预留版本字段:

public class OrderEvent {
private final String version = "1.0"; // 未来可扩展
}

监控三件套

// 监控处理时长/失败率/QPS
@Around("@annotation(org.springframework.context.event.EventListener)")
public Object monitor(ProceedingJoinPoint pjp) {
Timer.Sample sample = Timer.start();
try {
return pjp.proceed();
} finally {
sample.stop(Metrics.timer("event.process.time"));
}
}

结语:事件驱动的艺术

优秀架构的本质不是预测所有需求,而是拥抱变化。

通过Spring事件监听器,我们将系统拆解为可插拔的乐高模块:

  • 新增功能时 → 添加监听器(无需修改核心代码)
  • 流量暴增时 → 开启异步(无需重构架构)

这恰如经营咖啡店的真谛:

  • “不是雇佣更快的咖啡师,而是设计永不拥堵的协作机制”

程序员彩蛋:

  • 下回当你为需求变更焦头烂额时,不妨问问自己:“我的代码,像一家应对自如的咖啡店吗?”

附录:性能压测数据(阿里云ECS 8核16G)

模式 吞吐量 平均延迟 CPU占用
同步监听 12,000/s 15ms 85%
异步+批量 98,000/s 2ms 62%

技术选型建议:万级QPS以内首选Spring事件,超越则上MQ

文章目录
  1. 1. 引言:当咖啡店遭遇程序员
  2. 2. 一、咖啡店里的监听器:3位灵魂角色
    1. 2.0.1. 1. 事件定义:咖啡店的「订单小票」
    2. 2.0.2. 2. 事件发布:店长的「广播系统」
    3. 2.0.3. 3. 事件监听:咖啡团队的「技能响应」
  • 3. 二、扛住亿级流量的3把利器
    1. 3.0.1. 场景1:冷启动缓存预加载(防雪崩)
    2. 3.0.2. 场景2:事务成功后的缓存清理(保一致性)
    3. 3.0.3. 场景3:无侵入式功能扩展
  • 4. 三、血泪教训:3个深夜加班事故
    1. 4.0.1. 事故1:多线程篡改事件(订单混乱)
    2. 4.0.2. 事故2:异步事件丢失(顾客投诉)
    3. 4.0.3. 事故3:事件循环调用(咖啡师卡死)
  • 5. 四、关键抉择:监听器 vs MQ 架构对垒
  • 6. 五、性能调优:监听器的涡轮增压
  • 7. 六、最佳实践:5条生存法则
    1. 7.0.0.1. 单一职责原则
    2. 7.0.0.2. 事件轻量化
    3. 7.0.0.3. 异常隔离舱
    4. 7.0.0.4. 版本兼容设计
    5. 7.0.0.5. 监控三件套
  • 8. 结语:事件驱动的艺术
  • 9. 附录:性能压测数据(阿里云ECS 8核16G)