ambari 服务配置(Service Configuration)源码解析

一、服务配置简介

ambari在安装服务(如:HDFS、SPARK、HIVE)时,会将各个服务的配置项内容(如hdfs-site.xml, core-site.xml)之间呈现在web管理界面上,用户可以任意修改服务的配置项。ambari会自动将这些服务配置分发给其集群内的所有主机,ambari-agent服务会将这些配置内容更新各个服务本身的配置文件,实现服务配置自动化,避免了在安装分布式服务(如HDFS、ZOOKEEPER、HBASE)时,需要使用scp命令手动拷贝配置文件到其他机器的问题。

二、源码解析

1. 服务配置项加载

在manager界面选择添加服务, 选中一个服务后, 进入配置服务步骤,页面先去请求了/api/v1/stacks/xxx/versions/xxxservices?StackServices/service_name.in(HDFS,HIVE,AMBARI_INFRA,SENTRY,YARN,MAPREDUCE2,ZOOKEEPER,LEAPSQL)&fields=configurations/*,configurations/dependencies/*,StackServices/config_types/*&_=1556434141234接口,服务端处理类是StackServiceResourceProvider,而在其子查询中,会去访问/services/xxx/configurations/xxx接口,该接口对应处理类是StackConfigurationResourceProvider类,获取服务配置关键代码如下(现在还没有在服务器上创建该service, 只能通过读取stack目录/LEAP/{stackVersion}/{serviceName}/configuration目录获取服务的所有配置):

public class StackConfigurationResourceProvider extends
    ReadOnlyResourceProvider {
    
    public Set<Resource> getResources(Request request, Predicate predicate) {
        Set<StackConfigurationResponse> responses = getResources(new Command<Set<StackConfigurationResponse>>() {
      @Override
      public Set<StackConfigurationResponse> invoke() throws AmbariException {
        return getManagementController().getStackConfigurations(requests);
      }
    });
   
    }
}

getStackConfigurations()方法关键代码如下:

private Set<StackConfigurationResponse> getStackConfigurations(StackConfigurationRequest request) {
    if (propertyName != null) {
      properties = ambariMetaInfo.getPropertiesByName(stackName, stackVersion, serviceName, propertyName);
    } else {
      properties = ambariMetaInfo.getServiceProperties(stackName, stackVersion, serviceName);
    }
    for (PropertyInfo property: properties) {
      response.add(property.convertToResponse(request.getLocale()));
    }  
}

AmbariMetaInfo类的getServiceProperties方法通过stackname, stackversion, servicename, 找到对应的ServiceInfo实例对象, 该对象里面含有该字段: private List<PropertyInfo> properties. 而该字段被赋值是在server初始启动时, ServiceModule类解析时, 调用populateConfigurationModules()类方法时. 赋值关键代码如下:

private void populateConfigurationModules() {
    ConfigurationDirectory configDirectory = serviceDirectory.getConfigurationDirectory(
        serviceInfo.getConfigDir(), AmbariMetaInfo.SERVICE_PROPERTIES_FOLDER_NAME);

    if (configDirectory != null) {
      for (ConfigurationModule config : configDirectory.getConfigurationModules()) {
          ConfigurationInfo info = config.getModuleInfo();
          serviceInfo.getProperties().addAll(info.getProperties());
          serviceInfo.setTypeAttributes(config.getConfigType(), info.getAttributes());
          configurationModules.put(config.getConfigType(), config);
    }
}

在ConfigurationDirectory类初始化时, 会去读取configuration文件下的所有xml文件, 转化的关键代码如下:

private void parsePath() {
    File[] configFiles = directory.listFiles(AmbariMetaInfo.FILENAME_FILTER);
    if (configFiles != null) {
      for (File configFile : configFiles) {
        if (configFile.getName().endsWith(AmbariMetaInfo.SERVICE_CONFIG_FILE_NAME_POSTFIX)) {
          String configType = ConfigHelper.fileNameToConfigType(configFile.getName());
          ConfigurationXml config = null;

          config = unmarshaller.unmarshal(ConfigurationXml.class, configFile);
          ConfigurationInfo configInfo = new ConfigurationInfo(parseProperties(config,
                configFile.getName()), parseAttributes(config));
          ConfigurationModule module = new ConfigurationModule(configType, configInfo);
          configurationModules.put(configType, module);
}

而ConfigurationXml类的结构代码如下:

@XmlRootElement(name="configuration")
public class ConfigurationXml implements Validable{
  
  @XmlAnyAttribute
  private Map<QName, String> attributes = new HashMap<QName, String>();

  @XmlElement(name="property")
  private List<PropertyInfo> properties = new ArrayList<PropertyInfo>();

  @XmlTransient
  private boolean valid = true;
}

其中attributes字段就是指configution标签本身的属性键值对, 例如: <configuration supports_final="true">.

这里要区分一个重要的概念就是properties是指configuration标签所有的子标签,而attributes是指configuration标签本身的键值对.

2. 保存服务配置到数据库

在确认配置选择下一步安装服务时, 会去调用该接口: /api/v1/clusters/{clusterName}. 而请求方式是PUT. 表单数据格式如下:

[{"Clusters":{"desired_config":[{"type":"leapid-log4j-env","tag":"version1583928110866","properties":{"leapid.log.MaxBackupIndex":"10","leapid.log.MaxFileSize":"100","leapid.log.console.Threshold":"ERROR","leapid.log.dir":"/usr/leapid-admin/logs","leapid.log.file.Threshold":"ERROR"}]

该表单的数据只会提交新加或者修改的配置, 以configType来计算, 例如修改了configType为hdfs-site的某一项配置, 将会提交这整个configType.

该请求在后端会被交给ClusterResourceProvider类处理, 该类会去调用AmbariManagerControllerImpl类的updateCluster()方法, 核心代码如下:

private synchronized RequestStatusResponse updateCluster(ClusterRequest request,     
    Map<String, String> requestProperties) {
    // set or create configuration mapping (and optionally create the map of properties)
    if (isConfigurationCreationNeeded) {
      List<ConfigurationRequest> desiredConfigs = request.getDesiredConfig();

      if (!desiredConfigs.isEmpty()) {
        Set<Config> configs = new HashSet<Config>();
        String note = null;

        for (ConfigurationRequest cr : desiredConfigs) {
          String configType = cr.getType();
          if (null != cr.getProperties()) {
            // !!! empty property sets are supported, and need to be able to use
            // previously-defined configs (revert)
            Map<String, Config> all = cluster.getConfigsByType(configType);
            if (null == all ||                              // none set
                !all.containsKey(cr.getVersionTag()) ||     // tag not set
                cr.getProperties().size() > 0) {            // properties to set

              // Ensure the user is allowed to update all properties
              validateAuthorizationToUpdateServiceUsersAndGroups(cluster, cr);
              cr.setClusterName(cluster.getClusterName());
              configurationResponses.add(createConfiguration(cr)); 
            }
          }
          note = cr.getServiceConfigVersionNote();
          configs.add(cluster.getConfig(configType, cr.getVersionTag()));
        }
        if (!configs.isEmpty()) {
                String authName = getAuthName();
                serviceConfigVersionResponse = cluster.addDesiredConfig(authName, 
                configs, note);
        }   
    }
}

在这里面最终调用了createrConfiguration方法, 关键代码如下:

public synchronized ConfigurationResponse createConfiguration(
      ConfigurationRequest request) {
    Config config = createConfig(cluster, request.getType(), requestProperties,
      request.getVersionTag(), propertiesAttributes);

    return new ConfigurationResponse(cluster.getClusterName(), config);
}

public Config createConfig(Cluster cluster, String type, Map<String, String> properties,
    String versionTag, Map<String, Map<String, String>> propertiesAttributes) {
    Config config = configFactory.createNew(cluster, type,
      properties, propertiesAttributes);

    if (!StringUtils.isEmpty(versionTag)) {
      config.setTag(versionTag);
    }

    config.persist();

    cluster.addConfig(config);

    return config;
  }

在createConfig()方法里, 会生成ConfigImpl对象实例, 该对象的persist()方法会生成ClusterConfigEntity实体类, 并调用相应的dao将数据存入数据库, 同时将该config对象实例加入cluster对象的allconfigs属性集合中.

在所有Config对象创建完成后, 上面的updateCluster方法最后调用addDesiredConfig()方法, 关键代码如下:

public ServiceConfigVersionResponse addDesiredConfig(String user, Set<Config> configs, String serviceConfigVersionNote) {
    ServiceConfigVersionResponse serviceConfigVersionResponse = applyConfigs(
          configs, user, serviceConfigVersionNote);

    configHelper.invalidateStaleConfigsCache(getDesiredConfigs());
    return serviceConfigVersionResponse;
}

ServiceConfigVersionResponse applyConfigs(Set<Config> configs, String user, String serviceConfigVersionNote) {
    if (serviceName == null) {
      ArrayList<String> configTypes = new ArrayList<>();
      for (Config config: configs) {
        configTypes.add(config.getType());
      }
      LOG.error("No service found for config types '{}', service config version not created", configTypes);
      return null;
    } else {
      return createServiceConfigVersion(serviceName, user, serviceConfigVersionNote);
    }
}

最终调用的createServiceConfigVersion()方法会去创建ServiceConfigEntity对象, 并调用相应的dao将数据存入数据库中.

三、 Manager界面出现重启标识的原因

1. 当在界面上添加服务时, 会创建ServiceHostComponentImpl对象. server向agent端发送start命令, 在命令中会带上所有config的name以及它们所对应的tag.

2. agent端在收到executecommand后, 会将这些config tag记录存入/var/lib/ambari-agent/data/cluster_config.json文件中

3. 在向server回执的心跳数据中, 会带上这些config tag, server端收到回复后, 回去调用ServiceHostComponentImple的updateActualConfig()方法更新actualConfig值, 同时设置restartServiceRequired为true.

 

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值