Presto内存管理源码分析

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Lnho2015/article/details/78628433

1. 内存池初始化

初始化代码在LocalMemoryManager中,启动时将内存分为3个内存池,分别是:

  • RESERVED_POOL:预留内存池,用于执行最耗费内存资源的查询。
  • GENERAL_POOL:普通内存池,用于执行除最耗费内存查询以外的查询。
  • SYSTEM_POOL:系统内存池,预留的用于Presto内部数据结构和临时的分配需求使用的内存。
long maxHeap = Runtime.getRuntime().maxMemory();
//堆内存最大值,由Xmx指定
maxMemory = new DataSize(maxHeap - systemMemoryConfig.getReservedSystemMemory().toBytes(), BYTE);
//扣除系统内存池的剩余最大内存
builder.put(RESERVED_POOL, new MemoryPool(RESERVED_POOL, config.getMaxQueryMemoryPerNode()));
//RESERVED_POOL的大小由MaxQueryMemoryPerNode指定
DataSize generalPoolSize = new DataSize(Math.max(0, maxMemory.toBytes() - config.getMaxQueryMemoryPerNode().toBytes()), BYTE);
builder.put(GENERAL_POOL, new MemoryPool(GENERAL_POOL, generalPoolSize));
//扣除SYSTEM_POOL,再扣除RESERVED_POOL,剩余内存作为GENERAL_POOL
builder.put(SYSTEM_POOL, new MemoryPool(SYSTEM_POOL, systemMemoryConfig.getReservedSystemMemory()));

2. 定时调整内存池分配策略

首先看SqlQueryManager的代码

queryManagementExecutor.scheduleWithFixedDelay(() -> {
    ...
    enforceMemoryLimits();
    ...
}, 1, 1, TimeUnit.SECONDS); //每隔1S触发一次

其调用的是ClusterMemoryManager的process方法,其中有一行:

updateNodes(updateAssignments(queries));

我们看updateAssignments方法,其作用是找出最耗费内存的查询,并放入RESERVED_POOL:

if (reservedPool.getAssignedQueries() == 0 && generalPool.getBlockedNodes() > 0) {
//要求reservedPool当前没有分配查询,并且generalPool中有阻塞的节点
    QueryExecution biggestQuery = null;
    long maxMemory = -1;

    for (QueryExecution queryExecution : queries) {
        if (resourceOvercommit(queryExecution.getSession())) {
            // 不要提升那些请求资源过量使用的查询到reserved pool,因为他们的内存使用是没有限制的。
            continue;
        }
        long bytesUsed = queryExecution.getTotalMemoryReservation();
        if (bytesUsed > maxMemory) {
            biggestQuery = queryExecution;
            maxMemory = bytesUsed;
        }
    }
    if (biggestQuery != null) {
        biggestQuery.setMemoryPool(new VersionedMemoryPoolId(RESERVED_POOL, version));
    }
}

再看外层的updateNodes方法,可以发现RESERVED POOL的这种分配策略会应用到每个节点。也就是说这个查询在每个节点都会独占RESERVED POOL的空间。

// Schedule refresh
for (RemoteNodeMemory node : nodes.values()) {
    node.asyncRefresh(assignments);
}

3. 配置优化

以下是调整前的Presto的配置

query.max-memory=14GB
query.max-memory-per-node=7GB
resources.reserved-system-memory=2GB
-Xmx10G

计算可得:

  • 节点总内存:10GB
  • RESERVED_POOL(预留内存池):7GB
  • GENERAL_POOL(普通内存池):1GB
  • SYSTEM_POOL(系统内存池):2GB

分析:

  • 实际执行查询发现SYSTEM_POOL最大使用量大概在1.3GB,基本符合要求。
  • 当只提交一个查询时,该查询会进入GENERAL POOL,并出现了可用量为负数的情况(即可用量已无法完成查询),而此时RESERVED POOL的可用量却为7GB,一点都没使用。
  • 可以得出目前RESERVED_POOL设置的不太合理,可改为2GB。

保持总内存不变进行修改,修改后配置:

query.max-memory=14GB
query.max-memory-per-node=2GB
resources.reserved-system-memory=2GB
-Xmx10G

经测试,该配置可以较好的利用资源。

猜你喜欢

转载自blog.csdn.net/Lnho2015/article/details/78628433