如上图所示,Apollo portal 更新配置后,进行轮询的客户端获取更新通知,然后再调用接口获取最新配置。不仅仅只有轮询,还有定时更新(默认 5 分钟一次)。目的就是让客户端能够稳定的获取到最新的配置。
一起来看看他的设计。
具体的类是 RemoteConfigRepository
,每一个 Config —— 也就是 namespace 都有一个 RemoteConfigRepository 对象,表示这个 Config 的远程配置仓库,可以利用这个仓库请求远程服务,得到配置。
RemoteConfigRepository 的构造方法需要一个 namespace
字符串,表明这个 Repository 所属的 Config 名称。
下面是该类的构造方法。
public RemoteConfigRepository(String namespace) { m_namespace = namespace;// Config 名称 m_configCache = new AtomicReference<>(); // Config 引用 m_configUtil = ApolloInjector.getInstance(ConfigUtil.class);// 单例的 config 配置,存放 application.properties m_httpUtil = ApolloInjector.getInstance(HttpUtil.class);// HTTP 工具 m_serviceLocator = ApolloInjector.getInstance(ConfigServiceLocator.class);// 远程服务 URL 更新类 remoteConfigLongPollService = ApolloInjector.getInstance(RemoteConfigLongPollService.class);// 长轮询服务 m_longPollServiceDto = new AtomicReference<>();// 长轮询发现的当前配置发生变化的服务 m_remoteMessages = new AtomicReference<>(); m_loadConfigRateLimiter = RateLimiter.create(m_configUtil.getLoadConfigQPS());// 限流器 m_configNeedForceRefresh = new AtomicBoolean(true);// 是否强制刷新 m_loadConfigFailSchedulePolicy = new ExponentialSchedulePolicy(m_configUtil.getOnErrorRetryInterval(),//1 m_configUtil.getOnErrorRetryInterval() * 8);// 1 * 8;失败定时重试策略: 最小一秒,最大 8 秒. gson = new Gson();// json 序列化 this.trySync(); // 第一次同步 this.schedulePeriodicRefresh();// 定时刷新 this.scheduleLongPollingRefresh();// 长轮询刷新 }
可以看到,在构造方法中,就执行了 3 个本地方法,其中就包括定时刷新和长轮询刷新。这两个功能在 apollo 的 github 文档中也有介绍:
1.客户端和服务端保持了一个长连接,从而能第一时间获得配置更新的推送。 2.客户端还会定时从Apollo配置中心服务端拉取应用的最新配置。 3.这是一个fallback机制,为了防止推送机制失效导致配置不更新。 4.客户端定时拉取会上报本地版本,所以一般情况下,对于定时拉取的操作,服务端都会返回304 - Not Modified。 5.定时频率默认为每5分钟拉取一次,客户端也可以通过在运行时指定System Property: apollo.refreshInterval来覆盖,单位为分钟。
所以,长连接是更新配置的主要手段,然后用定时任务辅助长连接,防止长连接失败。
那就看看长连接和定时任务的具体代码。
定时任务主要由一个单 core 的线程池维护这定时任务。
static { // 定时任务,单个 core. 后台线程 m_executorService = Executors.newScheduledThreadPool(1, ApolloThreadFactory.create("RemoteConfigRepository", true)); } private void schedulePeriodicRefresh() { // 默认 5 分钟同步一次. m_executorService.scheduleAtFixedRate( new Runnable() { @Override public void run() { trySync(); } }, m_configUtil.getRefreshInterval(), m_configUtil.getRefreshInterval(),// 5 m_configUtil.getRefreshIntervalTimeUnit());//单位:分钟 }
具体就是每 5 分钟执行 sync 方法。我简化了一下 sync 方法,一起看看:
protected synchronized void sync() { ApolloConfig previous = m_configCache.get(); // 加载远程配置 ApolloConfig current = loadApolloConfig(); //reference equals means HTTP 304 if (previous != current) { m_configCache.set(current); // 触发监听器 this.fireRepositoryChange(m_namespace, this.getConfig()); } }
首先,拿到上一个 config 对象的引用,然后,加载远程配置,判断是否相等,如果不相等,更新引用缓存,触发监听器。
可以看出,关键是加载远程配置和触发监听器,这两个操作。
loadApolloConfig 方法主要逻辑就是通过 HTTP 请求从 configService 服务里获取到配置。大概步骤如下:
好,到这里,定时任务就算处理完了,总之就是调用 sync 方法,请求远程 configServer 服务,得到结果后,更新 Config 对象里的配置,并通知监听器。
再来说说长轮询。
长轮询实际上就是在一个类似死循环里,不停请求 ConfigServer 的配置变化通知接口 notifications/v2,如果配置有变更,就会返回变更信息,然后向定时任务线程池提交一个任务,任务内容是执行 sync 方法。
在请求 ConfigServer 的时候,ConfigServer 使用了 Servlet 3 的异步特性,将 hold 住连接 30 秒,等到有通知就立刻返回,这样能够实现一个基于 HTTP 的长连接。
关于为什么使用 HTTP 长连接,初次接触 Apollo 的人都会疑惑,为什么使用这种方式,而不是"那种"方式?
下面是作者宋顺的回复:
总结一下:
本文没有贴很多的代码。因为不是一篇源码分析的文章。
总之,Apollo 的更新配置设计就是通过定时轮询和长轮询进行组合而来。
定时轮询负责调用获取配置接口,长轮询负责调用配置更新通知接口,长轮询得到结果后,将提交一个任务到定时轮询线程池里,执行同步操作——也就是调用获取配置接口。
为什么使用 HTTP 长轮询? 简单!简单!简单!
本文由创作,采用 知识共享署名4.0 国际许可协议进行许可
本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名
最后编辑时间为: 2018/06/30 17:07