[HADOOP-11238]hdfs namenode getGroups延迟

在namenode日志看到如下错误

2016-05-11 01:00:26,360 WARN org.apache.hadoop.security.Groups: Potential performance problem: getGroups(user=xxx) took 5046 milliseconds

这个方法是hadoop为了获取某个用户是哪个组。

分析补丁

补丁:https://issues.apache.org/jira/browse/HADOOP-11238
先贴出修改前的源代码

public List<String> getGroups(String user) throws IOException {
    // No need to lookup for groups of static users
    List<String> staticMapping = staticUserToGroupsMap.get(user);
    if (staticMapping != null) {
      return staticMapping;
    }
    // Return cached value if available
    CachedGroups groups = userToGroupsMap.get(user);
    long startMs = timer.monotonicNow();
    if (!hasExpired(groups, startMs)) {
      if(LOG.isDebugEnabled()) {
        LOG.debug("Returning cached groups for '" + user + "'");
      }
      if (groups.getGroups().isEmpty()) {
        // Even with enabling negative cache, getGroups() has the same behavior
        // that throws IOException if the groups for the user is empty.
        throw new IOException("No groups found for user " + user);
      }
      return groups.getGroups();
    }

    // Create and cache user's groups
    List<String> groupList = impl.getGroups(user);
    long endMs = timer.monotonicNow();
    long deltaMs = endMs - startMs ;
    UserGroupInformation.metrics.addGetGroups(deltaMs);
    if (deltaMs > warningDeltaMs) {
      LOG.warn("Potential performance problem: getGroups(user=" + user +") " +
          "took " + deltaMs + " milliseconds.");
    }
    groups = new CachedGroups(groupList, endMs);
    if (groups.getGroups().isEmpty()) {
      if (isNegativeCacheEnabled()) {
        userToGroupsMap.put(user, groups);
      }
      throw new IOException("No groups found for user " + user);
    }
    userToGroupsMap.put(user, groups);
    if(LOG.isDebugEnabled()) {
      LOG.debug("Returning fetched groups for '" + user + "'");
    }
    return groups.getGroups();
  }

导致缓慢的原因是,当缓存超过时间失效时,同时有多个线程调用impl.getGroups(user)方法,我们默认是使用org.apache.hadoop.security.ShellBasedUnixGroupsMapping的实现,这个实际是调用系统命令id来获取用户组信息。多线程调用时,可能会同时启动几十个进程,导致返回缓慢。 我之前想到的修改方法是使用线程同步的方式,避免多个线程同时更新cache。搜索一下issue果然有人实现了,但那个是很久就提交了,没实现NegativeCache,所以官方合并是我上面给出来的补丁。 HADOOP-11238实现的方案是使用Google Guava第三方库来缓存。不了解具体实现,看注释同样实现了多线程请求时,如果在缓存中不存在,也只有一个线程加载,其他线程等待这个线程加载完成。如果在缓存中存在,直接返回缓存内容,如果缓存超时,刷新数据过程中,其他线程仍然获取旧数据,不会等待这个刷新。
NegativeCache是为了缓存空组(某个用户不属于任何组)的情况,超时时间和cache不一样,所以额外加了个变量。

相关参数

hadoop.security.group.mapping
可选值有
org.apache.hadoop.security.ShellBasedUnixGroupsMapping 直接调用系统命令id
org.apache.hadoop.security.JniBasedUnixGroupsMapping 使用jni调用hadoop自己实现获取组的c++程序
org.apache.hadoop.security.JniBasedUnixGroupsNetgroupMappingWithFallback 使用jni调用,如果加载c++程序失败,改用ShellBase的方式
org.apache.hadoop.security.LdapGroupsMapping 使用ldap方式获取 也可以自己实现一个方法,可以通过读取文件的方式来获取组和用户的映射

踩坑

cloudera manager里面的hadoop.security.group.mapping配置是在“服务范围”内的,也就是对客户端和服务端都会生效。设置为jni的方式后,客户端可能会加载不到native lib导致任务失败。

updatedupdated2023-12-062023-12-06