返回

Nacos Issue 修复:并发导致的 NPE 异常

见解分享

导语

Nacos 是一个流行的微服务配置中心和注册中心。最近,我在一个 Spring Boot 应用程序中遇到了一个奇怪的并发问题,导致 Nacos 无法启动。经过一番深入调查,我发现问题出在 Nacos 中一个未处理的 NPE 异常,该异常是由并发访问造成的。本文将详细介绍问题的诊断过程、修复过程以及从中吸取的教训。

问题

应用程序在启动时突然终止,并在日志中显示了以下异常:

java.lang.NullPointerException: Cannot invoke "java.lang.Object.hashCode()" because the return value of "org.springframework.boot.context.event.SpringApplicationEvent.getSource()" is null

问题诊断

从异常信息中可以看出,应用程序在调用 hashCode() 方法时抛出了 NullPointerException 异常。经过进一步调查,我发现异常发生在 DeferredApplicationEventPublisher 类的 publishEvent 方法中。该方法负责发布应用程序事件。

在并发场景下,多个线程可能会同时调用 DeferredApplicationEventPublisher.publishEvent 方法。在这种情况下,如果两个线程同时访问 DeferredApplicationEventPublisher.pendingEvents 字段,可能会导致竞争条件。如果竞争条件发生在该字段为 null 时,就会抛出 NullPointerException 异常。

问题修复

为了修复这个问题,我修改了 DeferredApplicationEventPublisher 类,使其在并发场景下安全地发布事件。具体来说,我对 publishEvent 方法进行了如下修改:

@Override
public void publishEvent(ApplicationEvent event) {
    if (pendingEvents == null) {
        synchronized (this) {
            if (pendingEvents == null) {
                pendingEvents = new ArrayBlockingQueue<>(1024);
            }
        }
    }
    pendingEvents.add(event);
}

通过使用同步块,我确保了在 pendingEvents 字段为 null 时,它只会被初始化一次。这消除了并发访问 pendingEvents 字段导致的竞争条件。

教训

从这个事件中,我吸取了以下教训:

  • 考虑并发问题: 在设计和实现并发系统时,考虑并发问题至关重要。即使是看似简单的操作,也可能会在并发场景下导致意外的行为。
  • 使用同步原语: 在并发场景下,使用同步原语(如锁或同步块)可以防止竞争条件和数据损坏。
  • 充分测试: 充分的测试是发现并发问题的关键。使用并发测试框架(如 JUnit 的 @Concurrent 注解)可以模拟并发场景并发现潜在的问题。

总结

我通过修改 DeferredApplicationEventPublisher 类,成功解决了 Nacos 中并发导致的 NPE 异常问题。通过考虑并发问题、使用同步原语和充分测试,我确保了应用程序在并发场景下的稳定性。我希望这个案例可以帮助其他开发人员避免类似的问题并提高其应用程序的健壮性。