← 返回题目列表

观察者模式同步通知和异步通知怎么选择?

高频 中等 第 6 / 27 题 更新于 2026/07/28
观察者模式同步通知异步通知线程池

简化版

同步通知适合观察者逻辑轻、需要立即得到结果、失败要影响主流程的场景;异步通知适合耗时、非核心、允许最终一致的后续动作。选择时要看是否影响主流程、是否需要事务一致性、是否能接受延迟和重试。

详细版

观察者模式可以同步通知,也可以异步通知。

同步通知:

  • 发布方线程直接调用观察者;
  • 执行顺序清晰;
  • 异常容易传递;
  • 但观察者慢会拖慢主流程。

异步通知:

  • 事件发布后交给线程池、事件队列或消息队列;
  • 主流程响应更快;
  • 适合短信、日志、数据同步等非核心动作;
  • 但要处理失败重试、幂等、顺序和监控。

如果观察者处理结果会决定主流程是否成功,通常同步更合适;如果只是附加动作,异步更合适。跨服务可靠通知则更适合消息队列。

完整版教学

一、同步通知的特点

同步通知最直接:

for (Observer observer : observers) {
    observer.update(event);
}

发布方线程会依次调用每个观察者。它的优点是简单、顺序清楚、调试方便。

但缺点也明显:只要一个观察者慢,整个发布过程就慢;一个观察者抛异常,也可能中断后续观察者或影响主流程。

同步通知适合:

  • 观察者逻辑很轻;
  • 处理结果必须立即生效;
  • 失败必须阻断主流程;
  • 需要强一致性的本地逻辑。

二、异步通知的特点

异步通知会把观察者处理放到另一个执行环境:

发布事件 -> 放入线程池/队列 -> 观察者异步处理

主流程发布事件后可以更快返回,不必等待所有观察者处理完成。

异步通知适合:

  • 发送短信、邮件;
  • 写行为日志;
  • 推送运营数据;
  • 刷新缓存;
  • 同步搜索索引;
  • 调用非核心外部系统。

这些动作失败时通常不应该直接导致主业务失败,而是应该重试或补偿。

三、异步不是免费午餐

异步通知会带来新的工程问题:

  1. 失败怎么处理:记录日志、重试、进入死信队列还是人工补偿?
  2. 是否幂等:同一个事件重复消费会不会产生重复发券、重复积分?
  3. 顺序是否重要:多个事件乱序会不会影响结果?
  4. 线程池是否隔离:慢任务会不会拖垮其他监听器?
  5. 监控是否完善:异步失败不会直接暴露给用户,更需要告警。

所以面试中不要只说“异步提高性能”,还要说异步需要可靠性设计。

四、事务场景要关注提交时机

如果业务还在事务中就异步通知,观察者可能读到未提交或不存在的数据。

例如订单还没提交,异步监听器已经去查订单表,结果查不到订单。

解决方式包括:

  • 事务提交后再发布事件;
  • 使用 @TransactionalEventListener(AFTER_COMMIT)
  • 通过本地消息表保证事务和事件写入一致;
  • 使用消息队列实现可靠投递。

这类问题在订单、支付、库存系统里很常见。

五、如何做选择

可以用三个问题判断:

  1. 观察者失败是否应该影响主流程?
  2. 观察者结果是否必须立即可见?
  3. 观察者执行是否耗时或依赖外部系统?

如果前两个答案是肯定,倾向同步。

如果第三个答案是肯定,并且允许最终一致,倾向异步。

如果是跨服务核心事件,优先考虑消息队列,而不是简单线程池。

六、延迟、隔离与可靠性的取舍

主流程 20 ms,3 个同步监听器各 10 ms,总延迟约 50 ms;并行异步可让主线程更早返回,但任务仍需线程池容量和失败处理。

sync: publisher -> A -> B -> C; async: publisher -> queue/executor -> observers

这个推演把模式带来的收益和成本放在同一条调用链上。设计评审时既要确认结果正确,也要确认额外层次没有改变原有契约。

七、边界、代价与验证

检查维度应确认的内容
正确性异步只是改变执行模型,可靠性不会凭空出现,必须配合持久化、重试、幂等和监控。
适用边界同步适合强顺序、低延迟监听;异步适合耗时解耦,但会引入排队、丢失、重复和最终一致性。
测试证据覆盖正常路径、异常路径、空值或未知类型,并断言调用次数与顺序
运行成本记录额外对象、调用层数、延迟、线程或内存开销,不用模式名称代替测量

模式落地后要用单元测试验证独立职责,用集成测试验证对象装配和真实调用入口。涉及并发、远程或异步时,还要补充竞态、超时、重复执行与资源释放测试。

易错点:异步只是改变执行模型,可靠性不会凭空出现,必须配合持久化、重试、幂等和监控。

八、常见误区与追问

  • 误区:把监听器加到线程池就获得可靠事件。 进程崩溃可能丢失内存队列任务,拒绝策略也可能丢任务;可靠性需额外机制。
  • 误区:发布事件天然等于异步和最终一致性。 观察者只定义一对多通知关系;同步还是异步、是否跨进程以及一致性语义都由具体实现决定。
  • 误区:示例代码能运行,就代表模式边界设计正确。 能运行只证明一条路径,仍要检查异常、并发、顺序、生命周期以及新增实现时是否修改稳定代码。
  • 追问:如何估算线程池是否够用? 结合到达速率、平均处理时长和峰值计算并发需求,再设置有界队列与拒绝告警。
  • 追问:观察者失败后由谁重试? 模式本身没有答案;同步场景要定义异常传播,异步场景要由执行器、事件总线或消息系统提供重试与死信策略。
  • 追问:怎样验证这套设计没有改变原有语义? 先做基线行为测试,再对新增层验证返回值、异常类型、调用次数和顺序;性能敏感处还要比较改造前后的量化指标。

九、加强记忆

同步通知胜在简单可控,适合轻量且强一致的观察者;异步通知胜在解耦和削峰,适合耗时、非核心、允许最终一致的动作。异步一定要配套幂等、重试、监控和事务提交时机设计。