preface

As shown in the figure above, after Apollo Portal updates the configuration, the polling client gets the update notification and then invokes the interface to get the latest configuration. Not only polling, but also periodic updates (every 5 minutes by default). The purpose is to ensure that the client can steadily obtain the latest configuration.

Take a look at his design.

The core code

The specific class is RemoteConfigRepository. Each Config — or namespace — has a RemoteConfigRepository object that represents the remote repository for that Config. You can use this repository to request a remote service to get the configuration.

The constructor of RemoteConfigRepository requires a namespace string indicating the name of the Config to which the Repository belongs.

Here is the constructor of the class.

public RemoteConfigRepository(String namespace) {
    m_namespace = namespace;/ / Config name
    m_configCache = new AtomicReference<>(); / / reference Config
    m_configUtil = ApolloInjector.getInstance(ConfigUtil.class);// Singleton config to store application.properties
    m_httpUtil = ApolloInjector.getInstance(HttpUtil.class);/ / HTTP tool
    m_serviceLocator = ApolloInjector.getInstance(ConfigServiceLocator.class);// Remote service URL update class
    remoteConfigLongPollService = ApolloInjector.getInstance(RemoteConfigLongPollService.class);// Long polling service
    m_longPollServiceDto = new AtomicReference<>();// The long poll finds the service whose current configuration has changed
    m_remoteMessages = new AtomicReference<>();
    m_loadConfigRateLimiter = RateLimiter.create(m_configUtil.getLoadConfigQPS());/ / current limiter
    m_configNeedForceRefresh = new AtomicBoolean(true);// Whether to force refresh
    m_loadConfigFailSchedulePolicy = new ExponentialSchedulePolicy(m_configUtil.getOnErrorRetryInterval(),/ / 1
        m_configUtil.getOnErrorRetryInterval() * 8);/ / 1 * 8; Periodic retry policy: Minimum one second to maximum eight seconds.
    gson = new Gson();// json serialization
    this.trySync(); // First synchronization
    this.schedulePeriodicRefresh();// Refresh periodically
    this.scheduleLongPollingRefresh();// Long polling refresh
}
Copy the code

As you can see, in the constructor, three local methods are executed, including a timed refresh and a long poll refresh. These two features are also covered in Apollo’s Github documentation:

1. The client maintains a long connection with the server so that the client can receive configuration update push immediately. 2. The client will periodically pull the latest configuration of the application from the Apollo configuration Center server. 3. This is a fallback mechanism, in case the configuration is not updated due to the failure of the push mechanism. 4. The client will report the local version of the scheduled pull operation. Therefore, the server returns 304-Not Modified for the scheduled pull operation. 5. The timing frequency is pulled every 5 minutes by default. The client can also override the value by specifying System Property: Apollo. refreshInterval at runtime, in minutes.

Therefore, long connections are the main means to update configurations. Scheduled tasks are used to assist long connections to prevent long connection failures.

Look at the code for long connections and timed tasks.

Timing task

A scheduled task is maintained primarily by a single core thread pool.

static {
    // Timed task, single core. Background thread
    m_executorService = Executors.newScheduledThreadPool(1,
        ApolloThreadFactory.create("RemoteConfigRepository".true));
}

private void schedulePeriodicRefresh(a) {
    // Synchronizes data every 5 minutes by default.
    m_executorService.scheduleAtFixedRate(
        new Runnable() {
          @Override
          public void run(a) {
            trySync();
          }
        }, m_configUtil.getRefreshInterval(), m_configUtil.getRefreshInterval(),/ / 5
        m_configUtil.getRefreshIntervalTimeUnit());// Unit: minutes
}
Copy the code

The sync method is executed every 5 minutes. I’ve simplified the sync method, so let’s take a look:

protected synchronized void sync(a) {
  ApolloConfig previous = m_configCache.get();
  // Load the remote configuration
  ApolloConfig current = loadApolloConfig();

  //reference equals means HTTP 304
  if(previous ! = current) { m_configCache.set(current);// Trigger the listener
    this.fireRepositoryChange(m_namespace, this.getConfig()); }}Copy the code

First, get a reference to the previous Config object, then load the remote configuration, determine if it is equal, and if not, update the reference cache, triggering the listener.

As you can see, the key is to load the remote configuration and trigger the listener, the two operations.

The main logic of the loadApolloConfig method is to get the configuration from the configService service via HTTP requests. The steps are as follows:

  1. First, limit the current. Gets the service list. Then, depending on whether there is an update notification, decide how many times to retry this time. If there is an update, retry twice, and vice versa.
  2. If the configuration Service fails to receive an update, it takes a break. If so, it takes a break. Otherwise, SchedulePolicy is used.
  3. After getting the data, reset the forced refresh state and failure rest state and return to the configuration.

Trigger listener steps:

  1. Loop through the remote repository’s listeners, calling their onRepositoryChange method. It’s actually Config.
  2. Then, the reference inside the Config is updated and the task is looping to the thread pool — executing the onChange method of the Config listener.

Call sync, request the remote configServer service, update the Config object, and notify the listener.

Let’s talk about long polling.

Long connection/long polling

Long polling is essentially a dead-loop in which notifications of configuration changes from ConfigServer are kept, notifications/v2 is returned if configuration changes are made, and a task is submitted to the scheduled task thread pool to execute sync.

When requesting a ConfigServer, the ConfigServer uses the asynchronous nature of Servlet 3 to hold the connection for 30 seconds and return on notification, enabling a long HTTP-based connection.

As for why you use HTTP long connections, people who are new to Apollo are wondering, why do you use this method instead of “that” method?

Here is the reply from song Shun:

To sum up:

  1. Why not use a messaging system? It’s too complicated to kill.
  2. Why not use TCP long connections? It has high requirements on the network environment and is prone to push failure. And there are double write problems.
  3. Why use HTTP long polling? Sufficient performance, combined with Servlet3’s asynchronous features, to maintain 10,000 level connections (one long connection per client). Using HTTP directly with servlets is more convenient than using TCP connections alone. The HTTP request/response mode ensures that no double writes occur. The main thing is simplicity, and performance is not the bottleneck for now.

conclusion

There is not much code posted in this article. Because it is not a source code analysis article.

Overall, Apollo’s update configuration design is a combination of timed polling and long polling.

The timed poll is responsible for calling the fetch configuration interface, and the long poll is responsible for calling the configuration update notification interface. After the long poll gets the result, a task will be submitted to the timed poll thread pool to perform the synchronization operation — that is, the call fetch configuration interface.

Why use HTTP long polling? Simple! Simple! Simple!