← 返回题目列表

观察者模式中某个观察者异常会影响其他观察者吗?

高频 中等 第 11 / 27 题 更新于 2026/08/02
观察者模式异常隔离失败处理事件通知

简化版

会不会影响取决于通知模型和异常处理策略。同步通知里,如果发布方不捕获异常,一个观察者抛错可能中断后续观察者并回滚主流程;如果逐个捕获异常,则可以隔离失败。异步通知通常能隔离主流程,但仍要处理重试、告警、死信和幂等。工程上要按观察者是否关键来决定:关键观察者失败应阻断,非关键观察者失败应隔离并记录。

详细版

观察者模式常见风险是“一个监听器拖垮一串监听器”。比如订单支付成功后通知多个观察者:

积分监听器 -> 短信监听器 -> 发票监听器 -> 营销监听器

如果积分监听器抛异常,短信和发票是否还执行?这个问题没有固定答案,要看业务语义。

常见策略:

  • fail-fast:关键监听器失败,直接中断;
  • fail-safe:单个监听器失败不影响其他监听器;
  • 分级处理:核心监听器同步阻断,非核心监听器异步隔离;
  • 收集错误:全部执行完,再统一返回失败列表。

记忆钩子:观察者异常处理不是“catch 或不 catch”的代码细节,而是业务可靠性契约。

完整版教学

面试提示:异常隔离题要回答“谁失败、谁感知、是否继续、如何补偿”,否则容易停留在 try-catch 层面。

一、同步通知默认容易被异常打断

最简单的发布逻辑通常这样写:

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

如果第 2 个观察者抛异常,第 3 个、第 4 个就不会执行。异常还可能继续向上抛,让发布事件的主流程失败。

这对某些场景是正确的,例如权限校验、风控拦截;但对短信、日志、营销通知可能就不合适。

二、逐个捕获可以隔离非关键观察者

如果观察者是非关键动作,可以逐个隔离:

for (Observer observer : observers) {
    try {
        observer.onEvent(event);
    } catch (Exception ex) {
        log.error("listener failed, listener={}, eventId={}",
            observer.name(), event.id(), ex);
    }
}

这样一个观察者失败,不会阻塞其他观察者。但这不代表失败可以被忽略,日志、指标、告警仍然要有。

三、关键观察者应该明确标记

可以给观察者增加元信息:

interface EventListener<E> {
    String name();
    boolean required();
    void onEvent(E event);
}

执行时:

try {
    listener.onEvent(event);
} catch (Exception ex) {
    if (listener.required()) {
        throw ex;
    }
    log.warn("optional listener failed: {}", listener.name(), ex);
}

这样业务能清楚区分“必须成功”和“尽力而为”。

四、异步通知隔离主流程但不等于可靠

异步通知通常不会阻塞主流程:

publish event -> submit to executor -> return

但异步带来新问题:

  • 线程池满了怎么办;
  • 任务执行失败是否重试;
  • 重试是否会重复执行;
  • 失败任务是否进入死信或补偿表;
  • 监听器延迟是否可观测。

所以异步只是隔离延迟和异常传播,不自动保证可靠投递。

五、不同业务动作应该选择不同失败策略

观察者动作失败策略原因
风控校验阻断安全优先
扣库存阻断或补偿防止超卖
发短信隔离并重试不应影响支付成功
写审计日志高可靠记录合规要求
营销埋点隔离丢弃或补偿非核心

面试回答时要强调“按业务重要性分级”,不要把所有监听器一刀切。

六、收集错误适合批量通知

有时希望所有监听器都尝试执行,然后返回失败明细:

List<ListenerError> errors = new ArrayList<>();
for (EventListener listener : listeners) {
    try {
        listener.onEvent(event);
    } catch (Exception ex) {
        errors.add(new ListenerError(listener.name(), ex.getMessage()));
    }
}
if (!errors.isEmpty()) {
    throw new EventPublishException(errors);
}

这种方式适合管理后台、批处理、测试框架等需要完整错误报告的场景。

七、异常隔离必须配合可观测性

失败被隔离后,调用方可能感知不到,所以要补监控:

event_type=OrderPaid
listener=SendSmsListener
status=failed
error=TimeoutException
event_id=E202
retry=1

对关键监听器要报警;对非关键监听器也要有失败率指标,避免长时间静默失败。

八、常见误区与追问

  • 误区:观察者异常一定不能影响主流程。 风控、权限、库存等关键观察者失败时就应该阻断或补偿。
  • 误区:捕获异常后打印日志就结束了。 还需要指标、告警、重试、死信或人工补偿。
  • 误区:异步通知就不会丢事件。 线程池拒绝、进程崩溃、任务失败都可能导致事件丢失。
  • 误区:所有监听器都用同一种异常策略。 应按核心链路、合规链路、非核心链路分级。
  • 追问:如何防止一个慢监听器拖慢整体? 同步场景设置超时,非关键动作异步化,线程池隔离。
  • 追问:失败后要不要重试? 幂等动作可以重试,非幂等动作要先设计去重键和状态机。
  • 追问:异常隔离会不会掩盖问题? 会,所以隔离必须同时配套可观测和告警。

九、加强记忆

  1. 同步通知不捕获异常会中断后续观察者。
  2. 非关键观察者可隔离失败,但不能静默失败。
  3. 关键观察者要明确 required 语义。
  4. 异步隔离主流程,不等于可靠投递。
  5. 失败策略按业务重要性分级。
  6. 异常隔离必须配日志、指标和告警。