news 2026/9/13 6:13:09

Neon 自动扩缩容核心组件 vm-monitor 深度解析:cgroup 内存监控、LFC 文件缓存与 WebSocket 控制面

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Neon 自动扩缩容核心组件 vm-monitor 深度解析:cgroup 内存监控、LFC 文件缓存与 WebSocket 控制面

Neon 自动扩缩容核心组件 vm-monitor 深度解析:cgroup 内存监控、LFC 文件缓存与 WebSocket 控制面

【免费下载链接】neonNeon: Serverless Postgres. We separated storage and compute to offer autoscaling, code-like database branching, and scale to zero.项目地址: https://gitcode.com/GitHub_Trending/ne/neon

vm-monitor(下文简称 monitor)是 Neon 自动扩缩容(autoscaling)系统的核心组件之一。它与autoscale-schedulerautoscaler-agent协同工作,承担两项关键职责:在内存接近上限时向 agent 发出即时扩容请求,以及通过管理 Postgres 的本地文件缓存(LFC)与 cgroup 来落地扩容/缩容决策。本文以 libs/vm_monitor/README.md 为骨架,结合仓库源码,深入讲解其架构、通信协议、内存监控与文件缓存管理机制,帮助读者理解 Neon 是如何在虚拟机粒度上实现"按需伸缩"的。

1. 定位:monitor 在自动扩缩容体系中的角色

Neon 的自动扩缩容系统由三部分构成:

  • autoscale-scheduler:负责全局调度决策,决定某个 VM 何时扩容、缩容;
  • autoscaler-agent:运行在每个 VM 上的代理,负责与 Kubernetes/NeonVM 交互执行资源调整;
  • vm-monitor:运行在 VM 内部(由compute_ctl拉起),承担"内务总管"角色——它感知 Postgres 的真实内存压力,代表 Postgres 向 agent 提出扩容诉求,并在扩容获批后把新资源落实到文件缓存与 cgroup 上。

monitor 的两大职责(README 原文)可以概括为:

  1. 通知 agent 进行即时扩容:当 Postgres 所在 cgroup 的内存使用量超过阈值时,monitor 通过 WebSocket 向 agent 发送扩容请求,避免 Postgres 被 OOM-kill;
  2. 执行伸缩决策:管理 Postgres 的本地文件缓存(file cache)以及承载 Postgres 的 cgroup,把扩容/缩容目标具体落地。

CPU 与内存的扩缩容通过NeonVM(Neon 自研的、面向 Kubernetes 的 QEMU 工具)完成;而控制"何时收到内存告警"的手段,则是把 Postgres 启动在neon-postgres这个 cgroup 中,并设置其memory.{max,high}阈值。monitor 的早期原型在独立的 vm-monitor 仓库中开发,该仓库现已不再维护,但其提交历史对排查问题仍有参考价值——当前实现已迁移至本仓库的libs/vm_monitor目录。

2. 架构总览:四个松散耦合的子系统

monitor 由几个松散耦合的子系统组成(README 的 Structure 一节),源码层面分别对应libs/vm_monitor/src/下的模块:

子系统源码模块职责
Server(HTTP/WS 服务端)lib.rs基于axum的 HTTP 服务器,接受连接并将其升级为 WebSocket;同一时刻只允许一个连接
Filecache(文件缓存管理器)filecache.rs与 Postgres 文件缓存(LFC)通信的结构体;启动时建立连接并保持整个 monitor 生命周期
Cgroup watcher(cgroup 监控器)cgroup.rs轮询neon-postgrescgroup 的内存使用量,并把滚动聚合结果发送给 runner
Runner(核心协调器)runner.rs将 filecache 与 cgroup watcher 结合,通过Dispatcher与 agent 通信,按需调用两者的函数完成扩容/缩容

其中 Dispatcher(dispatcher.rs)和协议类型(protocol.rs)负责与 agent 之间的数据交换与连接管理。

模块之间的关系:Runner是主入口(runner.rs的模块注释明确写道"This is the 'Monitor' part of the monitor binary and is the main entrypoint for all functionality"),它持有DispatcherOption<FileCacheState>Option<CgroupState>——后两者是可选的原因在于:是否传入pgconnstr(管理 LFC)与 cgroup 名称由启动参数决定。

3. 启动方式与命令行参数

monitor 提供了两种启动途径(Cargo.toml 的[[bin]]声明):

  1. 独立二进制vm-monitor:入口为 monitor.rs,用于在完整的自动扩缩容系统中单独测试 monitor(monitor 之前由 vm-builder 启动,现在可以用这个二进制模拟当时的场景);仅支持 Linux(非 Linux 平台直接panic!("the monitor requires cgroups, which are only available on linux"))。
  2. compute_ctl内嵌启动:作为计算节点进程的一部分运行。

3.1 命令行参数

monitor 的命令行参数定义在 lib.rs 的 Args 结构,使用clap解析:

参数说明典型值
--cgroup/-c需要监控memory.high事件的 cgroup 名称,即 Postgres 运行的 cgroupneon-postgres
--pgconnstr/-p用于管理 Postgres 文件缓存的连接串compute 节点的内部连接串
--addr/-a监听连接请求的地址面向 agent:0.0.0.0:10301;面向 informant:127.0.0.1:10369

3.2 compute_ctl 如何拉起 monitor

在 Neon 生产路径中,monitor 由compute_ctl拉起。compute.rs 的 start_vm_monitor 展示了完整的启动逻辑:

  • 当环境变量AUTOSCALING存在时才启动 monitor,否则不启动;
  • 若计算规格中disable_lfc_resizing为真,则向 monitor 传pgconnstr(即不启用 LFC 自动调整);
  • 传入参数为:cgroup(来自params.cgroup)、pgconnstr(来自params.filecache_connstr)、addr(来自params.vm_monitor_addr);
  • 同时创建CancellationToken,用于在 Postgres 退出后统一取消 monitor 的所有线程(见 compute.rs 的清理逻辑)。

可见 monitor 的生命周期与 Postgres 绑定:Postgres 退出时 monitor 被取消,释放文件 watcher。

4. 通信层:单连接 WebSocket 与协议版本协商

4.1 单连接模型

Server 是一个简单的axum服务器,路由为GET /monitor(lib.rs),请求会被升级为 WebSocket 连接。同一时刻只允许一个连接:当新连接到达时,ws_handler会先向broadcast::Sender发送信号关闭旧连接(源码注释形象地称之为 "the cycle of death and rebirth"),再启动新的 monitor 实例(lib.rs)。

start_monitor使用 4 秒超时等待Runner::new完成初始化;初始化失败或超时都会记录日志并返回,允许后续新连接重试(lib.rs)。所有线程通过spawn_with_cancel包裹,收到CancellationToken取消信号(例如 Postgres 退出或新连接到来)后优雅退出(lib.rs)。

4.2 协议版本协商

Dispatcher::new(dispatcher.rs)在建连时执行协议握手:

  1. 等待 agent 发送其支持的协议版本区间(JSON,形如{min, max});
  2. monitor 计算双方最高共同版本(highest_shared_version,取两个区间交集的 max 值);
  3. 若区间无交集,向 agent 返回ProtocolResponse::Error并终止。

当前协议的PROTOCOL_MIN_VERSIONPROTOCOL_MAX_VERSION均为V1_0(protocol.rs),即当前只有 v1.0 一个版本。

4.3 消息类型

协议消息通过serde序列化为 JSON 文本,tag 字段为type出站消息(monitor → agent,protocol.rs OutboundMsgKind):

消息含义
UpscaleRequest {}monitor 发现内存压力过大,紧急请求扩容
UpscaleConfirmation {}处理完 agent 下发的扩容通知后回执
DownscaleResult { ok, status }缩容尝试的结果;ok=false表示因内存仍在高位等原因暂不可缩容,status说明原因
InvalidMessage { error }/InternalError { error }协议或内部处理错误
HealthCheck {}双向心跳(由 agent 发起)

入站消息(agent → monitor,protocol.rs InboundMsgKind):

消息含义
UpscaleNotification { granted }agent 告知已获批新资源(Resources{cpu, mem}),monitor 需落地扩容并回复UpscaleConfirmation
DownscaleRequest { target }agent 请求降低资源占用,monitor 处理后回复DownscaleResult
InvalidMessage/InternalError对端错误通告
HealthCheck {}心跳

Resources(protocol.rs)包含 vCPU 数(cpu: f64)与内存字节数(mem: u64),序列化时使用Allocation这一字段名,以匹配 agent 侧的api.Allocation类型。值得注意的细节是Runner.counter(消息 ID 计数器)始终保持奇数,用于避免与 agent 生成的偶数 ID 冲突(runner.rs)。

5. 内存监控:CgroupWatcher 的工作原理

5.1 cgroup v2 与采样配置

CgroupWatcher使用cgroups-rscrate 管理 cgroup(仅 Linux,见 Cargo.toml 的target.'cfg(target_os = "linux")'.dependencies)。构造时强制要求系统处于cgroups v2(unified)模式,否则直接报错(cgroup.rs)。

采样配置(cgroup.rs Config)及默认值:

配置项默认值含义
memory_poll_interval100ms拉取内存统计的间隔
memory_history_len5用于构建滚动聚合的样本数,即使用约 500ms 的历史做决策
memory_history_log_interval20周期性日志的最近样本数(约每 2s 输出一次,避免刷屏)
memory_history_log_noskip_interval15s数据无明显变化时,最多跳过日志的时长

5.2 监控主循环

watch(cgroup.rs)是 CgroupWatcher 的入口,以watch::Sender<(Instant, MemoryHistory)>向 runner 推送聚合结果,具体流程:

  1. 以 100ms 为周期读取 cgroup 内存子系统统计;
  2. 内存样本取自memory_statactive_anon + inactive_anon,即"不可回收内存"(non-reclaimable)的近似(cgroup.rs);
  3. 用环形缓冲区(ring_buf_recent_values_iter)维护最近若干样本,计算滚动平均值avg_non_reclaimable,连同样本数与时间跨度构成MemoryHistory(cgroup.rs);
  4. 周期性输出日志;若数据与上次记录"足够接近"(status_is_close_or_similar:差值小于较小值的 1/8 且不超过 128MiB,cgroup.rs),则跳过日志以降低噪音。

环形缓冲区与相似度判定逻辑分别有单元测试覆盖(ring_buf_itercheck_similarity_behaviour,见 cgroup.rs),可作为理解边界行为的参考。

6. 文件缓存管理:LFC 的大小计算与动态调整

6.1 Postgres 侧的 LFC 与 GUC

Neon 的 Postgres 本地文件缓存(Local File Cache, LFC)由pgxn/neon/file_cache.c实现,涉及两个 GUC(file_cache.c):

  • neon.max_file_cache_size:缓存上限,PGC_POSTMASTER级别、MB 单位、默认 0(禁用);
  • neon.file_cache_size_limit:当前软限制,PGC_SIGHUP级别、MB 单位,可在运行期通过pg_reload_conf()热更新;lfc_check_limit_hook保证它不能大于neon.max_file_cache_size(file_cache.c)。

6.2 monitor 侧的配置

FileCacheConfig(filecache.rs)默认值如下:

配置项默认值含义
resource_multiplier0.75缓存目标大小 = 总资源 × 75%
min_remaining_after_cache256 MiB扣掉缓存后系统必须保留的最小内存;低于共享内存场景是因为"超额分配是安全的"——内存不足时内核会从 page cache 驱逐,而不是杀掉进程
spread_factor0.1控制缓存从 0 增长到目标大小的速率;约等于"每给缓存增加 1 字节,系统预留 N 字节"

validate(filecache.rs)对配置做一致性校验:resource_multiplier必须在 (0,1) 之间;且resource_multiplier × (spread_factor + 1) < 1,否则两条增长曲线无法在合理区间相交,配置不合法。

6.3 缓存大小计算公式

calculate_cache_size(filecache.rs)根据总内存计算期望缓存大小,取两条曲线的较小值,并向下取整到 MiB:

size = min( available / (1 + spread_factor), total × resource_multiplier ) 其中 available = max(total − min_remaining_after_cache, 0)

直观理解:当内存较少时,spread_factor曲线占主导(为系统其余部分保留增长空间);内存充足时,resource_multiplier曲线封顶(不超过总内存的 75%)。

6.4 连接、查询与设置

  • 连接FileCacheState::newtokio_postgres建立连接,连接对象以spawn_with_cancel独立运行(filecache.rs);
  • 读取当前大小get_file_cache_size查询SELECT pg_size_bytes(current_setting('neon.file_cache_size_limit')),得到字节数(filecache.rs);
  • 设置大小set_file_cache_size先读取neon.max_file_cache_size封顶,再以整数 MB 形式执行ALTER SYSTEM SET neon.file_cache_size_limit = <N>;,随后调用SELECT pg_reload_conf();使其生效(filecache.rs);
  • 失败重试query_with_retry在查询失败时重建数据库连接并重试一次(filecache.rs)。

monitor 在启动时会显式设置一次初始缓存大小(即使当前值相同),目的是确认自己具备修改权限(runner.rs)。

7. 伸缩决策:Runner 主循环

7.1 阈值计算

monitor 通过"提前预警"避免 Postgres 被 OOM-kill。cgroup 阈值由Config::cgroup_threshold计算(runner.rs):

threshold = total_mem × (1 − cgroup_min_overhead_fraction)

cgroup_min_overhead_fraction默认 0.15,即保证阈值之上至少保留总内存的 15%,从而保证"当 cgroup 使用量超过总内存的 85% 时必定触发扩容请求"。

Runner 的整体配置(runner.rs Config)默认值:

配置项默认值含义
sys_buffer_bytes100 MiB内核占用的估计内存(/proc/meminfo的 MemTotal 与实际物理内存之差);计算可用内存时先从总内存中扣除
cgroup_min_overhead_fraction0.15阈值之上必须保留的最小内存比例
cgroup_downscale_threshold_buffer_bytes100 MiB缩容时新阈值必须高于"当前使用量 + 该缓冲",否则拒绝缩容

7.2 扩容路径

扩容触发条件与动作(runner.rs run):

  1. runner 监听 cgroup watcher 的watch::Receiver,每次收到新的MemoryHistory时检查avg_non_reclaimable是否 ≥ 当前阈值;
  2. 若超阈值,且距上次扩容请求已超过 1 秒(避免刷屏,对应 issue #5865),则向 agent 发送UpscaleRequest
  3. 当 agent 回传UpscaleNotification{granted}后,handle_upscale(runner.rs)执行落地动作:
    • usable_system_memory = new_mem − sys_buffer_bytes计算新的期望缓存大小并set_file_cache_size
    • 用同样的内存口径重算 cgroup 阈值并更新CgroupState.threshold
    • 完成后回复UpscaleConfirmation

注意handle_upscale中使用了 agent 告知的资源量(而非系统实际自报内存),因此sys_buffer_bytes的校准尤为重要——这也是该字段 TODO 注释强调的"当前仍需信任 agent 的扩容资源量"的原因(runner.rs)。

7.3 缩容路径

try_downscale(runner.rs)是缩容的核心,逻辑较谨慎:

  1. 若 monitor 未管理任何 cgroup/LFC,直接返回成功;
  2. watch::Receiver读取最近一次内存历史,做两道安全闸门:
    • 若距上次统计已超过 5 秒(last_time.elapsed() > 5s),bail 报错——既覆盖"启动后一直没收到指标"的情形,也覆盖"指标断流"的情形;
    • 若累计样本数 ≤ 1,拒绝缩容(返回ok=false),等待积累足够数据;
  3. 计算新阈值cgroup_threshold(usable_system_memory);若新阈值 < 当前使用量 + 100 MiB 缓冲,拒绝缩容并在status中给出数值化原因;
  4. 通过校验后,先缩 LFC(set_file_cache_size),再更新 cgroup 阈值,返回DownscaleResult{ok: true, status}

可见缩容被设计成"宁可多等、不可误伤":内存仍在高位或数据不足时一律暂缓。

7.4 消息分派

process_message(runner.rs)按InboundMsgKind分发:UpscaleNotificationhandle_upscale+ 回执;DownscaleRequesttry_downscale+DownscaleResultHealthCheck→ 原样回HealthCheck;收到 agent 的InvalidMessage/InternalError通告时仅记录warn日志。

8. 关键参数速查与调优提示

结合上述源码,将 monitor 相关的全部可调参数汇总如下:

启动参数(vm-monitor二进制 / compute_ctl)

  • --cgroup:要监控的 cgroup 名(如neon-postgres),Postgres 必须运行于其中;
  • --pgconnstr:Postgres 连接串,用于管理 LFC;由disable_lfc_resizing控制是否传入;
  • --addr:监听地址,agent 侧0.0.0.0:10301,informant 侧127.0.0.1:10369

代码级配置(当前为硬编码默认值)

  • Runner:sys_buffer_bytes=100MiBcgroup_min_overhead_fraction=0.15cgroup_downscale_threshold_buffer_bytes=100MiB
  • CgroupWatcher:memory_poll_interval=100msmemory_history_len=5、日志间隔 20 次/约 2s、跳日志上限 15s;
  • FileCache:resource_multiplier=0.75min_remaining_after_cache=256MiBspread_factor=0.1
  • 阈值规则:扩容阈值 = 总内存 × 0.85;1 秒内不重复请求扩容;缩容需满足"新阈值 ≥ 当前使用量 + 100MiB"且指标新鲜(≤5s)、样本充足(>1)。

Postgres 侧 GUC

  • neon.max_file_cache_size(MB,PGC_POSTMASTER,默认 0 禁用);
  • neon.file_cache_size_limit(MB,PGC_SIGHUP,运行时经ALTER SYSTEM+pg_reload_conf()调整,且不得大于 max)。

调优时需特别注意FileCacheConfig::validate的约束:resource_multiplierspread_factor必须满足resource_multiplier × (spread_factor + 1) < 1,否则配置非法(例如resource_multiplier=0.75搭配spread_factor=1会因两条增长曲线永不达到 75% 而失败)。

9. 测试与验证

monitor 的正确性依赖两处测试支撑:

  1. 单元测试cgroup.rs内置了环形缓冲区迭代(含环绕边界:values(0,4)==[7,8,9,0]等)与内存相似度判定(1/8 与 128MiB 双重边界)的测试(cgroup.rs);
  2. 端到端集成:独立二进制vm-monitor(monitor.rs)的注释说明,它用于"将 monitor 作为整个自动扩缩容系统的一部分进行测试",模拟此前由 vm-builder 启动的部署形态,可配合 agent 验证完整的握手—扩容—缩容闭环。

运行测试只需在仓库根目录执行标准的 Cargo 测试命令(monitor 依赖 Linux 的 cgroups,测试/运行均需在 Linux 上进行):

cargo test -p vm_monitor

10. 总结

vm-monitor是 Neon 自动扩缩容体系里"离 Postgres 最近"的一环:它把"Postgres 的真实内存压力"翻译成对 agent 的扩容诉求,把"调度器批复的资源"翻译成 LFC 大小与 cgroup 阈值的具体调整。本文从 README 的四个子系统出发,逐一对照源码揭示了其单连接 WebSocket 模型、v1.0 协议的消息语义、cgroup v2 内存采样与滚动聚合、LFC 大小计算公式与ALTER SYSTEM热更新路径,以及 Runner 主循环中"扩容果断、缩容谨慎"的完整决策逻辑。对于想要深入理解 Neon 扩缩容机制,或希望复现/扩展其内存监控与缓存管理能力的开发者,libs/vm_monitor 下的源码是最直接、最完整的参考实现。

【免费下载链接】neonNeon: Serverless Postgres. We separated storage and compute to offer autoscaling, code-like database branching, and scale to zero.项目地址: https://gitcode.com/GitHub_Trending/ne/neon

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/13 6:13:01

机械臂选型四步速判法:任务反推工作空间与自由度匹配

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/13 6:12:54

CiA402从站示例解析:对象字典、状态机与SDO/PDO实现

简介&#xff1a;这是一份面向伺服驱动与运动控制开发者的CiA402子协议从站字典示例包&#xff0c;围绕CANopen协议中关键的对象字典概念&#xff0c;演示如何构建符合CIA402规范的CANopen节点&#xff0c;解决设备配置、状态管理与参数读写等实际开发问题&#xff0c;适合正在…

作者头像 李华
网站建设 2026/9/13 6:10:33

Token Embedding技术解析与应用优化指南

1. 从文本到向量&#xff1a;Token Embedding的本质解析在大模型处理文本的第一道工序中&#xff0c;Token Embedding扮演着将离散符号转化为连续向量的关键角色。想象你面前有本外文词典&#xff0c;每个单词都对应着几行解释说明——Embedding就是大模型的"词典解释器&q…

作者头像 李华
网站建设 2026/9/13 6:09:55

基于Simulink的风光储PEM制氢系统仿真建模实践

1. 项目概述与需求解析1.1 这个仿真模型要解决什么问题这两年“绿氢”这个概念在能源圈里被反复提起&#xff0c;但真要把光伏、风电这些不稳定电源和电解制氢设备绑在一起&#xff0c;牵涉到的坑远比想象中多。光伏阵列的输出功率随光照、温度剧烈波动&#xff0c;PEM电解槽对…

作者头像 李华