美文网首页配置中心apollo
apollo客户端之Config模型

apollo客户端之Config模型

作者: Mr_1214 | 来源:发表于2018-09-14 16:11 被阅读222次

    apollo提供了基础数据库存储的配置管理中心,服务分client、adminService、configService、portal等模块。其中client服务在configService获取配置数据,监听配置数据,提供给具体应用的配置初始化以及配置动态更新操作

    配置信息模型接口
    config.png
    Config
    /**
     * 配置信息模型接口
     * @author Jason Song(song_s@ctrip.com)
     */
    public interface Config {
      /**
       * 获取String类型配置根据配置KEY
       */
      public String getProperty(String key, String defaultValue);
    
      /**
       * 获取int类型配置根据配置KEY
       */
      public Integer getIntProperty(String key, Integer defaultValue);
    
      /**
       * 获取long类型配置根据配置KEY
       */
      public Long getLongProperty(String key, Long defaultValue);
    
      /**
       * 获取short类型配置根据配置KEY
       */
      public Short getShortProperty(String key, Short defaultValue);
    
      /**
       * 获取float类型配置根据配置KEY
       */
      public Float getFloatProperty(String key, Float defaultValue);
    
      /**
       * 获取double类型配置根据配置KEY
       */
      public Double getDoubleProperty(String key, Double defaultValue);
    
      /**
       * 获取byte类型配置根据配置KEY
       */
      public Byte getByteProperty(String key, Byte defaultValue);
    
      /**
       * Return the boolean property value with the given key, or {@code defaultValue} if the key
       * doesn't exist.
       */
      public Boolean getBooleanProperty(String key, Boolean defaultValue);
    
      /**
       * Return the array property value with the given
       */
      public String[] getArrayProperty(String key, String delimiter, String[] defaultValue);
    
      /**
       * Return the Date property value with the given name, or {@code defaultValue} if the name doesn't exist.
       */
      public Date getDateProperty(String key, Date defaultValue);
    
      /**
       * Return the Date property value with the given name, or {@code defaultValue} if the name doesn't exist.
       */
      public Date getDateProperty(String key, String format, Date defaultValue);
    
      /**
       * Return the Date property value with the given name, or {@code defaultValue} if the name doesn't exist.
       */
      public Date getDateProperty(String key, String format, Locale locale, Date defaultValue);
    
      /**
       * Return the Enum property value with the given key, or {@code defaultValue} if the key doesn't exist.
       */
      public <T extends Enum<T>> T getEnumProperty(String key, Class<T> enumType, T defaultValue);
    
      /**
       * Return the duration property value(in milliseconds) with the given name, or {@code
       * defaultValue} if the name doesn't exist. Please note the format should comply with the follow
       * example (case insensitive). Examples:
       * <pre>
       *    "123MS"          -- parses as "123 milliseconds"
       *    "20S"            -- parses as "20 seconds"
       *    "15M"            -- parses as "15 minutes" (where a minute is 60 seconds)
       *    "10H"            -- parses as "10 hours" (where an hour is 3600 seconds)
       *    "2D"             -- parses as "2 days" (where a day is 24 hours or 86400 seconds)
       *    "2D3H4M5S123MS"  -- parses as "2 days, 3 hours, 4 minutes, 5 seconds and 123 milliseconds"
       * </pre>
       *
       * @param key          the property name
       * @param defaultValue the default value when name is not found or any error occurred
       * @return the parsed property value(in milliseconds)
       */
      public long getDurationProperty(String key, long defaultValue);
    
      /**
       * 添加配置改变监听器
       * @param listener the config change listener
       */
      public void addChangeListener(ConfigChangeListener listener);
    
      /**
       * 添加配置改变的监听器
       */
      public void addChangeListener(ConfigChangeListener listener, Set<String> interestedKeys);
    
      /**
       * 获取所有配置KEY
       */
      public Set<String> getPropertyNames();
    }
    

    Config接口,配置信息超类,提供给应用获取配置信息的接口
    功能如下:

    1. 提供基础类型值的配置获取
    2. 提供注册配置改变的监听器,用于配置改变回调
    AbstractConfig
    public abstract class AbstractConfig implements Config {
    /**
       * 监听器集合
       */
      private final List<ConfigChangeListener> m_listeners = Lists.newCopyOnWriteArrayList();
      private final Map<ConfigChangeListener, Set<String>> m_interestedKeys = Maps.newConcurrentMap();
      private final ConfigUtil m_configUtil;
      /**
       * 配置信息缓存
       */
      private volatile Cache<String, Integer> m_integerCache;
      private volatile Cache<String, Long> m_longCache;
      private volatile Cache<String, Short> m_shortCache;
      private volatile Cache<String, Float> m_floatCache;
      private volatile Cache<String, Double> m_doubleCache;
      private volatile Cache<String, Byte> m_byteCache;
      private volatile Cache<String, Boolean> m_booleanCache;
      private volatile Cache<String, Date> m_dateCache;
      private volatile Cache<String, Long> m_durationCache;
      private final Map<String, Cache<String, String[]>> m_arrayCache;
      private final List<Cache> allCaches;
    
      
     /**
       * 获取配置并缓存到本地缓存
       * @param key
       * @param parser
       * @param cache
       * @param defaultValue
       * @param <T>
       * @return
       */
       private <T> T getValueAndStoreToCache(String key, Function<String, T> parser, Cache<String, T> cache, T defaultValue) {
        long currentConfigVersion = m_configVersion.get();
        String value = getProperty(key, null);
    
        if (value != null) {
          T result = parser.apply(value);
    
          if (result != null) {
            synchronized (this) {
              if (m_configVersion.get() == currentConfigVersion) {
                cache.put(key, result);
              }
            }
            return result;
          }
        }
    
        return defaultValue;
      }
      
        /**
       * 配置改变调用监听器触发回调方法
       * @param changeEvent
       */
      protected void fireConfigChange(final ConfigChangeEvent changeEvent) {
        for (final ConfigChangeListener listener : m_listeners) {
          // check whether the listener is interested in this change event
          if (!isConfigChangeListenerInterested(listener, changeEvent)) {
            continue;
          }
          m_executorService.submit(new Runnable() {
            @Override
            public void run() {
              String listenerName = listener.getClass().getName();
              Transaction transaction = Tracer.newTransaction("Apollo.ConfigChangeListener", listenerName);
              try {
                listener.onChange(changeEvent);
                transaction.setStatus(Transaction.SUCCESS);
              } catch (Throwable ex) {
                transaction.setStatus(ex);
                Tracer.logError(ex);
                logger.error("Failed to invoke config change listener {}", listenerName, ex);
              } finally {
                transaction.complete();
              }
            }
          });
        }
      }
      
       /**
       * 对比最新配置与本地缓存差异构建改变事件
       * @param namespace
       * @param previous
       * @param current
       * @return
       */
      List<ConfigChange> calcPropertyChanges(String namespace, Properties previous,
                                             Properties current) {
        if (previous == null) {
          previous = new Properties();
        }
    
        if (current == null) {
          current = new Properties();
        }
    
        Set<String> previousKeys = previous.stringPropertyNames();
        Set<String> currentKeys = current.stringPropertyNames();
    
        Set<String> commonKeys = Sets.intersection(previousKeys, currentKeys);
        Set<String> newKeys = Sets.difference(currentKeys, commonKeys);
        Set<String> removedKeys = Sets.difference(previousKeys, commonKeys);
    
        List<ConfigChange> changes = Lists.newArrayList();
    
        for (String newKey : newKeys) {
          changes.add(new ConfigChange(namespace, newKey, null, current.getProperty(newKey),
              PropertyChangeType.ADDED));
        }
    
        for (String removedKey : removedKeys) {
          changes.add(new ConfigChange(namespace, removedKey, previous.getProperty(removedKey), null,
              PropertyChangeType.DELETED));
        }
    
        for (String commonKey : commonKeys) {
          String previousValue = previous.getProperty(commonKey);
          String currentValue = current.getProperty(commonKey);
          if (Objects.equal(previousValue, currentValue)) {
            continue;
          }
          changes.add(new ConfigChange(namespace, commonKey, previousValue, currentValue,
              PropertyChangeType.MODIFIED));
        }
    
        return changes;
      }
     } 
    

    AbstractConfig抽象实现类,完成了配置的通用功能
    功能如下:

    1. 提供配置本地缓存功能
    2. calcPropertyChanges:对比本地缓存与最新配置构建配置改变事件
    3. 提供监听器注册以及监听器回调触发功能fireConfigChange
    DefaultConfig
    /**
     * 配置信息默认实现
     * @author Jason Song(song_s@ctrip.com)
     */
    public class DefaultConfig extends AbstractConfig implements RepositoryChangeListener {
      private static final Logger logger = LoggerFactory.getLogger(DefaultConfig.class);
      /**
       * 命名空间
       */
      private final String m_namespace;
      /**
       * 本地文件缓存配置
       */
      private Properties m_resourceProperties;
      /**
       * 配置中心配置本地缓存
       */
      private AtomicReference<Properties> m_configProperties;
      /**
       * 配置仓库服务,用于获取远程配置中心配置
       */
      private ConfigRepository m_configRepository;
      /**
       * 流控信息
       */
      private RateLimiter m_warnLogRateLimiter;
    
      /**
       * 初始化,通过配置仓库加载配置中心配置信息
       */
      private void initialize() {
        try {
          m_configProperties.set(m_configRepository.getConfig());
        } catch (Throwable ex) {
          Tracer.logError(ex);
          logger.warn("Init Apollo Local Config failed - namespace: {}, reason: {}.",
              m_namespace, ExceptionUtil.getDetailMessage(ex));
        } finally {
          //注册当前实例到配置仓库服务上,用于监听配置仓库上的配置变化
          //register the change listener no matter config repository is working or not
          //so that whenever config repository is recovered, config could get changed
          m_configRepository.addChangeListener(this);
        }
      }
    
      /**
       * 获取配置信息
       * @param key          the property name
       * @param defaultValue the default value when key is not found or any error occurred
       * @return
       */
      @Override
      public String getProperty(String key, String defaultValue) {
    
        //System.getProperty方式获取配置优先级最高
        // step 1: check system properties, i.e. -Dkey=value
        String value = System.getProperty(key);
    
        //远程仓库方式获取配置优先级次之
        // step 2: check local cached properties file
        if (value == null && m_configProperties.get() != null) {
          value = m_configProperties.get().getProperty(key);
        }
    
        //System.getenv获取配置优先级第三
        /**
         * step 3: check env variable, i.e. PATH=...
         * normally system environment variables are in UPPERCASE, however there might be exceptions.
         * so the caller should provide the key in the right case
         */
        if (value == null) {
          value = System.getenv(key);
        }
    
        //本地配置资源获取配置优先级最低
        // step 4: check properties file from classpath
        if (value == null && m_resourceProperties != null) {
          value = (String) m_resourceProperties.get(key);
        }
    
        if (value == null && m_configProperties.get() == null && m_warnLogRateLimiter.tryAcquire()) {
          logger.warn("Could not load config for namespace {} from Apollo, please check whether the configs are released in Apollo! Return default value now!", m_namespace);
        }
    
        return value == null ? defaultValue : value;
      }
    
      /**
       * 配置仓库中的配置改变回调函数
       * @param namespace the namespace of this repository change
       * @param newProperties the properties after change
       */
      @Override
      public synchronized void onRepositoryChange(String namespace, Properties newProperties) {
        if (newProperties.equals(m_configProperties.get())) {
          return;
        }
        Properties newConfigProperties = new Properties();
        newConfigProperties.putAll(newProperties);
    
        Map<String, ConfigChange> actualChanges = updateAndCalcConfigChanges(newConfigProperties);
    
        //check double checked result
        if (actualChanges.isEmpty()) {
          return;
        }
    
        //调用配置改变回调方法,通知配置改变监听器
        this.fireConfigChange(new ConfigChangeEvent(m_namespace, actualChanges));
    
        Tracer.logEvent("Apollo.Client.ConfigChanges", m_namespace);
      }
    
      private Map<String, ConfigChange> updateAndCalcConfigChanges(Properties newConfigProperties) {
        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. use getProperty to update configChanges's old value
        for (ConfigChange change : configChanges) {
          change.setOldValue(this.getProperty(change.getPropertyName(), change.getOldValue()));
        }
    
        //2. update m_configProperties
        m_configProperties.set(newConfigProperties);
        clearConfigCache();
    
        //3. use getProperty to update configChange's new value and calc the final changes
        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();
      }
    
      /**
       * 加载本地资源配置信息根据命名空间;META-INF/config/{namespace}.properties
       * @param namespace
       * @return
       */
      private Properties loadFromResource(String namespace) {
        String name = String.format("META-INF/config/%s.properties", namespace);
        InputStream in = ClassLoaderUtil.getLoader().getResourceAsStream(name);
        Properties properties = null;
    
        if (in != null) {
          properties = new Properties();
    
          try {
            properties.load(in);
          } catch (IOException ex) {
            Tracer.logError(ex);
            logger.error("Load resource config for namespace {} failed", namespace, ex);
          } finally {
            try {
              in.close();
            } catch (IOException ex) {
              // ignore
            }
          }
        }
    
        return properties;
      }
    }
    
    

    DefaultConfig类为客户端提供的默认配置获取类,此类主要完成以下功能

    1. 初始化从配置仓库拉取远程的配置信息
    2. 注册当前实例(配置仓库的监听器)到配置仓库,用于监听配置仓库的改变
    3. 提供配置仓库改变的回调函数onRepositoryChange
    4. 根据KEY查找配置值信息
    5. 加载当前配置命名空间的本地资源(loadFromResource)

    其中获取配置有以下几种方式,按优先级由高到低如下:

    1. 通过System.getProperty方式获取配置
    2. 远程仓库方式获取配置
    3. System.getenv获取配置
    4. 本地配置资源获取配置

    相关文章

      网友评论

        本文标题:apollo客户端之Config模型

        本文链接:https://www.haomeiwen.com/subject/aviogftx.html