Two ways to get configuration from apollo client

Posted by Nexus10 on Fri, 17 Jan 2020 10:40:57 +0100

In general, use the listener ConfigChangeListener that implements apollo to get the configuration in real time through onChange method.

However, if the timeliness requirements for configuration changes are not high, and you just want to use the new configuration when using the configuration, you can get the configuration directly from the environment without implementing the listener.

Article directory

apollo mechanism

graphic

code analysis

Let's use the old friend refresh method to reveal it.

First, through AbstractApplicationContext.refresh():

invokeBeanFactoryPostProcessors(beanFactory);

After many hardships, we found the PropertySourcesProcessor, executed its postProcessBeanFactory method, and entered initializePropertySources().

Main process

1. Create Config

2. Package Config with CompositePropertySource and insert environment

private void initializePropertySources() {
  if (environment.getPropertySources().contains(PropertySourcesConstants.APOLLO_PROPERTY_SOURCE_NAME)) {
    // Once the apollo configuration has been initialized, it will return directly
    return;
  }
    // Create a PropertySource to load all configurations
  CompositePropertySource composite = new CompositePropertySource(PropertySourcesConstants.APOLLO_PROPERTY_SOURCE_NAME);
......

  while (iterator.hasNext()) {
    int order = iterator.next();
    for (String namespace : NAMESPACE_NAMES.get(order)) {
        // Get a config, if not, a config will be created (subsequent analysis, very important)
      Config config = ConfigService.getConfig(namespace);
        // Use CompositePropertySource to wrap config as a PropertySource, and then add the value environment
      composite.addPropertySource(configPropertySourceFactory.getConfigPropertySource(namespace, config));
    }
  }

  // clean up
  NAMESPACE_NAMES.clear();

  // If there are Apollo bootstrap property sources, make sure that the Apollo bootstrap property sources are in the first place and the above composite is set after it. Otherwise, the composite is set in the first place
  if (environment.getPropertySources()
      .contains(PropertySourcesConstants.APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
    ensureBootstrapPropertyPrecedence(environment);
    environment.getPropertySources()
        .addAfter(PropertySourcesConstants.APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME, composite);
  } else {
    // After setting the composite to the first one, even if we do not apply the listener method, we can get the configuration directly from the environment through getProperty even if we are notified of the configuration change
    environment.getPropertySources().addFirst(composite);
  }
}

So, after understanding the two parts, let's taste the mechanism in detail. A config, why can you know the change of apollo in real time?

Create remotecononfigrepository

As mentioned earlier, if you can't get config from ConfigService.getConfig(namespace), you will create a config. The remotecononfigrepository is created. Look at its construction methods. In addition to various attribute assignments, three methods will be called, which are very important.

public RemoteConfigRepository(String namespace) {
  m_namespace = namespace;
  m_configCache = new AtomicReference<>();
  m_configUtil = ApolloInjector.getInstance(ConfigUtil.class);
  m_httpUtil = ApolloInjector.getInstance(HttpUtil.class);
  m_serviceLocator = ApolloInjector.getInstance(ConfigServiceLocator.class);
  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(),
      m_configUtil.getOnErrorRetryInterval() * 8);
  gson = new Gson();
  // First synchronization apollo
  this.trySync();
  // Scheduled refresh configuration (304 is returned in most cases, which can prevent long polling failure)
  this.schedulePeriodicRefresh();
  // Long polling refresh configuration (the main way to get configuration in real time)
  this.scheduleLongPollingRefresh();
}
First synchronization apollo
@Override
protected synchronized void sync() {
  Transaction transaction = Tracer.newTransaction("Apollo.ConfigService", "syncRemoteConfig");

  try {
    ApolloConfig previous = m_configCache.get();
    // Send a get request to get the current apollo configuration information according to the apollo address, appId, maxRetries and other information you set
    ApolloConfig current = loadApolloConfig();

    // Update local apollo configuration information
    if (previous != current) {
      logger.debug("Remote Config refreshed!");
      m_configCache.set(current);
      // The task of refreshing the configuration may call this method, get the configuration, and notify the listener of the client
      this.fireRepositoryChange(m_namespace, this.getConfig());
    }

    if (current != null) {
      Tracer.logEvent(String.format("Apollo.Client.Configs.%s", current.getNamespaceName()),
          current.getReleaseKey());
    }

    transaction.setStatus(Transaction.SUCCESS);
  } catch (Throwable ex) {
    transaction.setStatus(ex);
    throw ex;
  } finally {
    transaction.complete();
  }
}
Enable scheduled refresh configuration
private void schedulePeriodicRefresh() {
  logger.debug("Schedule periodic refresh with interval: {} {}",
      m_configUtil.getRefreshInterval(), m_configUtil.getRefreshIntervalTimeUnit());
  m_executorService.scheduleAtFixedRate(
      new Runnable() {
        @Override
        public void run() {
          Tracer.logEvent("Apollo.ConfigService", String.format("periodicRefresh: %s", m_namespace));
          logger.debug("refresh config for namespace: {}", m_namespace);
          // trySync is the same method as the first sync above
          trySync();
          Tracer.logEvent("Apollo.Client.Version", Apollo.VERSION);
        }
          //The default interval is 5 minutes
      }, m_configUtil.getRefreshInterval(), m_configUtil.getRefreshInterval(),
      m_configUtil.getRefreshIntervalTimeUnit());
}
Enable long polling refresh configuration
public boolean submit(String namespace, RemoteConfigRepository remoteConfigRepository) {
  boolean added = m_longPollNamespaces.put(namespace, remoteConfigRepository);
  m_notifications.putIfAbsent(namespace, INIT_NOTIFICATION_ID);
  if (!m_longPollStarted.get()) {
    // Open pull  
    startLongPolling();
  }
  return added;
}

private void doLongPollingRefresh(String appId, String cluster, String dataCenter) {
  final Random random = new Random();
  ServiceDTO lastServiceDto = null;
  while (!m_longPollingStopped.get() && !Thread.currentThread().isInterrupted()) {
    if (!m_longPollRateLimiter.tryAcquire(5, TimeUnit.SECONDS)) {
      // Wait for 5 seconds.
      try {
        TimeUnit.SECONDS.sleep(5);
      } catch (InterruptedException e) {
      }
    }
      
      // Obtain all kinds of information, initiate requests, and check whether the configuration in apollo has changed
      ......
          
      // If the configuration changes, take the initiative to obtain the new configuration, and call the onChange method of the implementation class of ConfigChangeListener    
      if (response.getStatusCode() == 200 && response.getBody() != null) {
        updateNotifications(response.getBody());
        updateRemoteNotifications(response.getBody());
        transaction.addData("Result", response.getBody().toString());
        notify(lastServiceDto, response.getBody());
      }
       ......
    } finally {
      transaction.complete();
    }
  }
}

The notify method will eventually enter the firerepository change

fireRepositoryChange

This method is mentioned in the sync() method introduced earlier. This method will be executed by two types of refresh configuration tasks to inform each listener of configuration changes.
1. Get new configuration

2. Statistics of changes in various configurations

3. Notify each listener

protected void fireRepositoryChange(String namespace, Properties newProperties) {
  // Traverse all listeners
  for (RepositoryChangeListener listener : m_listeners) {
    try {
      // Pass the latest configuration information to the listener
      listener.onRepositoryChange(namespace, newProperties);
    } catch (Throwable ex) {
      Tracer.logError(ex);
      logger.error("Failed to invoke repository change listener {}", listener.getClass(), ex);
    }
  }
}

@Override
public synchronized void onRepositoryChange(String namespace, Properties newProperties) {
  if (newProperties.equals(m_configProperties.get())) {
    return;
  }

  ConfigSourceType sourceType = m_configRepository.getSourceType();
  Properties newConfigProperties = new Properties();
  newConfigProperties.putAll(newProperties);

  // Sort out the configuration information and determine the type of configuration change
  Map<String, ConfigChange> actualChanges = updateAndCalcConfigChanges(newConfigProperties, sourceType);

  //check double checked result
  if (actualChanges.isEmpty()) {
    return;
  }

  // Call onChange method of each ConfigChangeListener implementation class to send configuration information
  this.fireConfigChange(new ConfigChangeEvent(m_namespace, actualChanges));

  Tracer.logEvent("Apollo.Client.ConfigChanges", m_namespace);
}

private Map<String, ConfigChange> updateAndCalcConfigChanges(Properties newConfigProperties,
    ConfigSourceType sourceType) {
  // Configuration of initial statistical changes  
  List<ConfigChange> configChanges =
      calcPropertyChanges(m_namespace, m_configProperties.get(), newConfigProperties);

  ImmutableMap.Builder<String, ConfigChange> actualChanges =
      new ImmutableMap.Builder<>();

  /** === Double check since DefaultConfig has multiple config sources ==== **/

  //1. Set old values for configuration
  for (ConfigChange change : configChanges) {
    change.setOldValue(this.getProperty(change.getPropertyName(), change.getOldValue()));
  }

  //2. Update m "configproperties
  updateConfig(newConfigProperties, sourceType);
  clearConfigCache();

  //3. Traverse all new configurations, and finally confirm the type of each configuration (ADDED/MODIFIED/DELETED)
  for (ConfigChange change : configChanges) {
    change.setNewValue(this.getProperty(change.getPropertyName(), change.getNewValue()));
    switch (change.getChangeType()) {
      case ADDED:
        if (Objects.equals(change.getOldValue(), change.getNewValue())) {
          break;
        }
        if (change.getOldValue() != null) {
          change.setChangeType(PropertyChangeType.MODIFIED);
        }
        actualChanges.put(change.getPropertyName(), change);
        break;
      case MODIFIED:
        if (!Objects.equals(change.getOldValue(), change.getNewValue())) {
          actualChanges.put(change.getPropertyName(), change);
        }
        break;
      case DELETED:
        if (Objects.equals(change.getOldValue(), change.getNewValue())) {
          break;
        }
        if (change.getNewValue() != null) {
          change.setChangeType(PropertyChangeType.MODIFIED);
        }
        actualChanges.put(change.getPropertyName(), change);
        break;
      default:
        //do nothing
        break;
    }
  }
  return actualChanges.build();
}
Published 18 original articles, won praise 1, visited 1353
Private letter follow

Topics: Attribute