【讨论】Apollo为什么不在Client端长轮询同步时的返回结果中直接返回更新的结果呢?

226 阅读2分钟

如题

阅读 Apollo源码解析 -- Client轮询配置 后的一些思考

首先介绍一下Client端同步 Config Service 配置的流程

image

如图所示,Client同步配置有两种方式

  1. Client定时轮询。
  2. Client发起长轮询,利用Spring的DeferredResult,若Config Service 端监听到有新的通知,则返回有新通知消息,这时Client端立即发起对有新通知的Namespace的配置的读取。

从第二种同步配置的方式可以看到,返回的新通知并不包含配置信息,所以Client端还需要进行一次同步配置的请求,那为什么不在返回新通知时直接返回更新的配置呢?

如果在返回新通知时直接返回更新的配置,我们考虑一种极端的请求和返回的顺序,

  1. Client端发起长轮询,Config Service接受到长轮询,并且监听到有新通知,然后返回给Client端。
  2. 这时Config Service端接收到新的配置发布,并且更新了配置(假如就是Client端监听的Namespace)。
  3. Client端发起定时轮询,Config Service接受到请求后返回配置,接收到返回结果后 Client 端更新本地配置。
  4. 这时Client端接受到第1步中的长轮询返回结果,并且根据里面的配置更新本地配置,如果这些配置里包括第2步更新的配置,那么新发布的配置就会被覆盖,而这个配置将会在下一次定时轮询或者再次发起长轮询时监听到该配置有变化时返回该配置才会更新,所以说再次获取该配置前这个配置是已经过期了的。

定时轮询和推送(发起长轮询监听)的冲突,这就是不在Client端长轮询的返回结果中直接返回更新的结果的原因吧。

那Apollo是怎么处理这个冲突的呢?

除了不在Client端长轮询的返回结果中直接返回更新的结果,而是只是返回新消息通知,然后Client 端再发起一次同步配置请求外,还有一个很关键很巧妙的处理,

处理定时轮询和长轮询的逻辑在类 com.ctrip.framework.apollo.internals.RemoteConfigRepository 里,一个Namespace对应一个RemoteConfigRepository对象,里面定义了一个线程池 m_executorService

  private final static ScheduledExecutorService m_executorService;

  static {
    m_executorService = Executors.newScheduledThreadPool(1,
        ApolloThreadFactory.create("RemoteConfigRepository", true));
  }

这个线程池同时负责定时轮询的配置同步线程和长轮询有新通知时发起的配置同步线程,但是由于该线程池的容量只是1,所以这两个线程不会同时进行,必须等同步结果返回后才会进行下一个任务执行,于是就不会发生上面提到的冲突了,每次同步配置都是拉取该Namespace的最新配置。