news 2026/9/23 4:04:26

EMQX 的 ESSENTIAL 模式内存优化:`EMQX_FEATURES=ESSENTIAL` 下 Erlang 代码加载模式切换为 interactive 的机制与实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
EMQX 的 ESSENTIAL 模式内存优化:`EMQX_FEATURES=ESSENTIAL` 下 Erlang 代码加载模式切换为 interactive 的机制与实战
  • 后端
  • 物联网
  • 消息队列
  • 通信

【免费下载链接】emqx

The most scalable and reliable MQTT broker for AI, IoT, IIoT and connected vehicles

项目地址:https://gitcode.com/gh_mirrors/em/emqx
点击查看免费下载

导读

本文围绕 EMQX(当前开源仓库gh_mirrors/em/emqx)的一项内存优化特性展开:当以EMQX_FEATURES=ESSENTIAL启动节点时,Erlang 代码加载模式默认由embedded切换为interactive,从而让被裁剪(disabled)特性的.beam模块按需加载、不再常驻内存,显著降低 ESSENTIAL 模式节点的常驻内存(resident memory)占用。读完本文,你将掌握 ESSENTIAL 模式与特性裁剪的运作方式、CODE_LOADING_MODE的两种加载模式的差异、该优化在 bin/emqx 启动脚本中的落地位置,以及如何显式覆盖该默认行为。

背景:EMQX 的特性裁剪与 ESSENTIAL 模式

EMQX 支持通过环境变量EMQX_FEATURES启动前选择节点加载的应用(application)集合,这是一项 boot-time 能力,定义与解析逻辑集中在 apps/emqx_machine/src/emqx_machine_features.erl 中:

  • FULL(默认):启动全部应用;
  • ESSENTIAL:只启动核心 broker 以及认证/授权(authn/authz)相关的核心基础设施,dashboard、REST API、数据集成(data integration)、各类网关、插件等可选特性全部不启动;
  • <list>:逗号分隔的特性名列表(如dashboard,data_integration),启动核心 broker 加上所列特性。

该环境变量的默认值、取值与说明,可在 apps/emqx_conf/etc/emqx.env(EMQX_FEATURES="${EMQX_FEATURES:-FULL}",默认注释掉)中查看;emqx命令(bin/emqx,软件包安装时为/usr/bin/emqx)在每次调用时都会 source 该文件,因此服务启动、前台启动、emqx ctl都能读到相同的值,且该变量在解析emqx.conf之前生效,无法写入emqx.conf

从源码结构看,特性与具体应用(umbrella app)的映射关系维护在emqx_machine_features:known_features/0中,例如dashboard特性包含emqx_dashboardemqx_management等应用,data_integration特性包含emqx_connectoremqx_bridgeemqx_rule_engine等应用,并支持依赖解析(如schema_registry是多个特性的依赖)。启动时,apps/emqx_machine/src/emqx_machine_boot.erl 的filter_allowed_umbrella_apps/1会调用emqx_machine_features:is_umbrella_application_enabled/1,据此过滤出真正允许启动的应用列表;核心应用(emqxemqx_machineemqx_conf等)始终在列,认证/授权后端(emqx_auth_*)也被视为核心基础设施、永不裁剪(见base_core_apps/0auth_apps/0)。

核心变更:ESSENTIAL 模式下默认切换到 interactive 代码加载

本次变更(对应 change 条目feat-17803,同时记录于 changes/6.3.0.en.md)的核心内容如下:

当以EMQX_FEATURES=ESSENTIAL启动 EMQX 时,Erlang 代码加载模式默认变为interactive,使得被禁用特性的.beam文件按需加载(on demand),而不是在启动时全部加载。由于被跳过特性的模块永远不会常驻内存,ESSENTIAL 模式节点的常驻内存占用显著下降。该模式仍可通过显式设置CODE_LOADING_MODE覆盖。

代码加载模式:embedded 与 interactive 的区别

Erlang/OTP 的代码服务器(code server)支持两种加载模式,由 VM 启动参数-mode决定:

  • embedded所有模块在启动时一次性全部加载。代码服务器启动即把所有code path中的模块读入内存,运行期间不再从磁盘加载新模块。优点是启动后行为确定、没有运行期加载开销;缺点是任何被打包进发行版的模块(无论是否被使用)都会常驻内存,导致较大的常驻内存占用。
  • interactive模块按需加载(lazy loading)。只有被实际调用的模块才在首次调用时从磁盘加载到内存。优点是内存占用小、启动快;缺点是首次调用某个模块时存在一次磁盘读取。

传统上,EMQX 的发行版为了追求启动后的运行稳定性,默认采用embedded模式,这正是 bin/emqx 中CODE_LOADING_MODE="${CODE_LOADING_MODE:-embedded}"(约第 281 行)的含义:默认值embedded,但允许被环境变量覆盖。

变更的落地位置

该默认行为切换发生在 bin/emqx 启动脚本中(约第 263–267 行):

## starts only the slim apps list, and we flip code loading to interactive so ## the skipped apps' .beam files don't sit resident in memory. if [ "${EMQX_FEATURES:-}" = 'ESSENTIAL' ]; then export CODE_LOADING_MODE="${CODE_LOADING_MODE:-interactive}" fi

这段逻辑的关键点:

  1. 仅当EMQX_FEATURES精确等于ESSENTIAL时才触发。FULL 预设或自定义特性列表(如dashboard,data_integration)不会自动切换,仍然使用embedded默认值。这是因为自定义列表通常仍会加载较多模块,保持embedded可以避免运行期按需加载的开销。
  2. 使用${CODE_LOADING_MODE:-interactive}的“默认值替换”写法:如果用户在环境中已经显式设置了CODE_LOADING_MODE,则尊重用户设置、不覆盖;只有当该变量未设置或为空时才默认interactive。这与脚本中CODE_LOADING_MODE="${CODE_LOADING_MODE:-embedded}"的写法一致,体现了“显式设置优先、未设置走默认”的覆盖策略。
  3. 变量最终传给 VM 启动参数。脚本后续在构造erlexec/iex启动命令行时,会通过-mode "$CODE_LOADING_MODE"(erl 控制台路径)或--erl "-mode $CODE_LOADING_MODE"(iex 控制台路径)将模式传给 VM(见 bin/emqx 约第 1295、1315 行)。

为什么能显著降低常驻内存

embedded模式下,发行版中所有.beam模块——包括那些因 ESSENTIAL 特性裁剪而永远不会被调用的模块——都会在启动时被加载并常驻内存。ESSENTIAL 模式本意是裁掉 dashboard、数据集成、网关等大量可选应用,但如果代码加载模式仍是embedded,这些被裁应用的模块依然会占据内存,内存收益大打折扣。

切换到interactive后,由于被裁剪特性的应用根本不会被启动(filter_allowed_umbrella_apps/1已将其从启动列表过滤),其模块永远不会被调用、也永远不会被加载,因此.beam文件不会常驻内存。可以推断,裁剪的功能越多,内存节省越明显;对于仅承载核心 MQTT 转发与认证/授权的最小节点,这一优化能带来可观的常驻内存下降。

如何在实践中使用与验证

1. 以 ESSENTIAL 模式启动

最直接的用法是在启动前设置环境变量:

export EMQX_FEATURES=ESSENTIAL ./bin/emqx start

或使用 Docker 运行(可参考仓库中的冒烟测试脚本 scripts/test/essential-auth-smoke/run.sh 的用法):

docker run -d --name emqx-essential \ -e EMQX_FEATURES=ESSENTIAL \ emqx/emqx-enterprise:latest

注意:ESSENTIAL 模式下 dashboard 与 REST API 不启动,因此无法通过 dashboard 或管理 API 观察节点;运行时管理可借助./bin/emqx ctl./bin/emqx eval

2. 显式覆盖代码加载模式

如果出于某些原因(例如希望 ESSENTIAL 节点也保持embedded的确定性加载,或反之在 FULL 模式下也启用按需加载),可以直接设置CODE_LOADING_MODE

# 在 ESSENTIAL 模式下强制回退到 embedded export EMQX_FEATURES=ESSENTIAL export CODE_LOADING_MODE=embedded ./bin/emqx start # 或在 FULL 模式下主动启用 interactive(按需加载) export EMQX_FEATURES=FULL export CODE_LOADING_MODE=interactive ./bin/emqx start

由于脚本采用${CODE_LOADING_MODE:-...}的写法,任何显式设置都会优先于默认值。

3. 观察生效结果

启动后可通过./bin/emqx eval检查当前代码服务器模式与已加载模块数量,例如:

./bin/emqx eval 'code:get_mode().'
  • ESSENTIAL 模式且未显式覆盖时,应返回interactive
  • FULL 模式或显式设置CODE_LOADING_MODE=embedded时,应返回embedded

如需确认被裁剪特性的模块确实未加载,可以对比code:all_loaded()返回的模块列表(interactive 模式下不应包含emqx_dashboardemqx_connectoremqx_bridge等被禁用应用的模块),也可以结合系统工具观察节点 RSS 的下降。

4. 一个可复现的 ESSENTIAL 冒烟示例

仓库提供了完整的 ESSENTIAL 模式冒烟测试 scripts/test/essential-auth-smoke/run.sh:它用-e EMQX_FEATURES=ESSENTIAL启动单节点,通过bootstrap.csv种子内置数据库认证、acl.conf配置文件授权,再以真实 MQTT 客户端验证认证/授权行为。该测试同时印证了两个事实:

  • ESSENTIAL 模式下认证/授权栈完整可用(认证/授权被视为核心基础设施,不会被裁剪);
  • 脚本中未显式设置CODE_LOADING_MODE,因此节点实际运行于interactive加载模式。

特性解析与能力下发的源码佐证

为了让内存优化的收益落在运行期,EMQX 在启动时还会把特性解析结果以能力(capability)形式下发给基础emqx应用,避免热路径对emqx_machine产生向上依赖:

  • apps/emqx_machine/src/emqx_machine_features.erl 的publish_capabilities/1在解析完EMQX_FEATURES后,通过emqx_features:set_capability/2写入persistent_term
  • apps/emqx/src/emqx_features.erl 提供observability_enabled/0client_info_enabled/0访问器,其模块文档明确指出:能力未设置或处于 FULL 预设时默认返回true(行为与之前完全一致),而 ESSENTIAL 等受限模式下消息热路径上的可观测性计数、client-info 统计等记账逻辑可以被跳过。

从源码结构可以推断,该能力下发与interactive加载模式是一对互补设计:前者让热路径在运行期跳过不必要的记账,后者让被裁剪模块根本不进入内存,两者共同服务于 ESSENTIAL 模式的“最小内存占用”目标。

适用前提与注意事项

  • 以当前仓库(EMQX 7.x 系列)实际内容为准。本文引用的行为均来自当前仓库的 bin/emqx、apps/emqx_machine/src/emqx_machine_features.erl、apps/emqx_machine/src/emqx_machine_boot.erl 及 changes/6.3.0.en.md。
  • 特性裁剪在启动时一次性决定。某特性未启动,就无法在运行期启用;要改变特性集合必须修改EMQX_FEATURES并重启节点(见 apps/emqx_conf/etc/emqx.env 中的说明)。
  • 该默认切换只针对ESSENTIAL精确匹配。如果设置成自定义特性列表,代码加载模式仍是embedded默认值;集群中所有节点应保持一致的特性配置。
  • interactive意味着运行期按需加载:虽然被裁剪特性的模块永不加载,但未被裁剪特性的模块会在首次调用时从磁盘加载,首次调用可能引入少量延迟;这是内存收益与确定性之间的权衡,可通过显式设置CODE_LOADING_MODE在两者间切换。
  • 后端
  • 物联网
  • 消息队列
  • 通信

【免费下载链接】emqx

The most scalable and reliable MQTT broker for AI, IoT, IIoT and connected vehicles

项目地址:https://gitcode.com/gh_mirrors/em/emqx
点击查看免费下载

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

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

AI工业视觉检测:如何把老师傅经验翻译成算法并接入工控系统

质检线上的老师傅&#xff0c;往往是整个车间里最“贵”的人。他拿放大镜看一个冲压件&#xff0c;三秒钟就能告诉你毛刺在哪个位置、压伤的痕迹是旧伤还是新伤、这个料要不要返工。这种基于十几年肌肉记忆的“手感”&#xff0c;恰恰是最难被量化、也最难被复制的东西。我们做…

作者头像 李华
网站建设 2026/9/23 3:54:28

计算机组成原理入门:从数据通路到控制器详解

简介&#xff1a;面向计算机组成原理零基础读者的入门PDF&#xff0c;从冯诺依曼体系结构切入&#xff0c;系统讲解运算器、控制器、存储器、输入输出设备五大部件&#xff0c;进而展开CPU内部结构、存储系统的层次划分、程序执行全流程&#xff0c;以及数据表示、总线系统与发…

作者头像 李华
网站建设 2026/9/23 3:52:35

AI大模型Python实战V7.5:环境搭建、流式输出与本地部署全链路指南

1. 这套V7.5版本到底在解决什么问题先把话说在前头&#xff0c;这个标题里的“AI大模型Python线下V7.5版本”听起来像是一个课程或者训练营的版本号&#xff0c;但如果你真在一线做过大模型应用开发&#xff0c;就会明白它背后指向的是一套完整的、可落地的技术方案组合。它不是…

作者头像 李华
网站建设 2026/9/23 3:52:01

1:1模仿张一鸣阅读法:一年读完50本书的实操拆解与避坑指南

开头就直接上干货&#xff1a;我试着1:1模仿张一鸣的阅读习惯和信息管理方式&#xff0c;坚持了整整一年&#xff0c;实际读完了50本书&#xff0c;不是收藏夹吃灰的那种“读完”&#xff0c;是每本都做了笔记、每两周逼自己做一个行动实验的那种。这篇文章不聊鸡汤&#xff0c…

作者头像 李华