后端物联网消息队列通信【免费下载链接】emqxThe most scalable and reliable MQTT broker for AI, IoT, IIoT and connected vehicles项目地址https://gitcode.com/gh_mirrors/em/emqx点击查看免费下载导读本文基于 EMQX 开源仓库的变更记录 fix-18077.en.md深入剖析一项关于集群稳定性的重要修复当节点尚未完全启动完毕时如果收到来自 CLI 或 API 的cluster join请求可能导致整个节点崩溃。文章将结合源码实现讲解 EMQX 如何通过启动完成度守卫拒绝此类请求、返回清晰错误信息并说明其底层原理、replicant 例外场景与升级兼容性处理帮助运维与开发人员理解并规避集群操作时序问题。问题背景为什么启动期间的 join 会导致节点崩溃在 EMQX 集群架构中cluster join是一个重量级操作。当节点加入集群时会触发 MriaEMQX 内置的分布式数据库的重新启动restart并伴随着一套完整的应用重启流程stop_apps/0→ensure_apps_started/0。问题在于如果节点自身的启动过程尚未完成即各个应用尤其是依赖 Mria 的应用仍在逐个启动中此时执行 join 触发 Mria 重启正在启动中的 Mria-backed 应用会直接崩溃crash进而拖垮整个节点。这正是变更记录中描述的场景joining restarts the internal database while applications are still starting, which could bring the whole node down.修复前的行为是崩溃修复后的行为是拒绝并给出清晰错误提示待节点完全启动后可重试。修复方案总览双层启动完成度守卫从源码结构看本次修复在 emqx_cluster.erl 中引入了两个维度的保护发起方本地节点守卫join/1在真正执行 join 之前先检查本节点是否已完成启动若未完成且本节点角色为 core则直接拒绝。目标方远端节点守卫通过can_i_join/1RPC 回调在 join 目标节点上再次检查其启动完成度与单节点许可模式双端校验。同时emqx_machine与emqx_node_readiness两个模块共同提供了节点是否已完全启动的判定依据。boot_in_progress标志的生命周期启动入口置位标志EMQX 的启动入口 emqx_machine.erl 在start/0的第一步就调用ok emqx_cluster:set_booting(true),该调用在引导开始时就设置boot_in_progress标志注释明确说明其意图在emqx_machine_boot:post_boot/0声明启动完成之前拒绝集群 join——因为 join 会重启 Mria而这对于仍在启动中的应用是致命的。启动完成清除标志emqx_machine_boot.erl 的post_boot/0是启动流程的收尾阶段post_boot() - ok ensure_apps_started(), ok print_vsn(), ... ok start_autocluster(), %% Boot is complete and the ekka join callbacks are registered %% (start_autocluster/0 above): joining is safe from here on. ok emqx_cluster:set_booting(false), ignore.注意这里的执行顺序非常关键start_autocluster/0emqx_machine_boot.erl负责注册 ekka 的 join/leave 回调start_autocluster() - ekka:callback(stop, fun emqx_machine_boot:stop_apps/0), ekka:callback(start, fun emqx_machine_boot:ensure_apps_started/0), _ ekka:autocluster(emqx), ok.只有在这些回调注册完成后才清除boot_in_progress标志。这意味着一旦标志被清除后续 join 触发的应用重启流程通过 ekka 回调进入stop_apps/0/ensure_apps_started/0已经有了完备的依托不会与仍在进行的首轮启动产生冲突。标志的存储方式set_booting/1的实现emqx_cluster.erl有一个值得注意的细节-spec set_booting(boolean()) - ok. set_booting(Bool) when is_boolean(Bool) - %% persistent: the value must survive a later application:load(emqx) %% because it can be set before the emqx application is loaded application:set_env(emqx, boot_in_progress, Bool, [{persistent, true}]).使用persistent选项写入应用环境是因为该标志可能在emqx应用被加载之前就已设置persistent保证后续的application:load(emqx)不会将其覆盖。读取侧is_booting/0则通过application:get_env(emqx, boot_in_progress, false)获取默认值为false。启动完成度的完整判定is_boot_complete/0boot_in_progress标志只覆盖受管理的首轮启动直到 ekka join 回调注册完成。为了覆盖更完整的场景is_boot_complete/0emqx_cluster.erl将其与节点就绪标志结合is_boot_complete() - not is_booting() andalso emqx_node_readiness:is_ready().模块文档明确指出is_booting/0覆盖首次受管理启动直到 ekka join 回调注册的阶段而emqx_node_readiness还额外覆盖了集群重加入rejoin所触发的ensure_apps_started/0重跑阶段。节点就绪标志Node Readinessemqx_node_readiness.erl 维护一个独立的就绪标志is_ready/0通过persistent_term:get(?KEY, true)读取默认值为truemark_ready/0/mark_not_ready/0分别置位与清除在ensure_apps_started/0emqx_machine_boot.erl中先mark_not_ready()再逐个启动应用最后mark_ready()同时该函数也是 ekka 集群 join/leave 回调因此 join 后插件与应用的重启也受就绪标志保护。该模块的文档还说明GET /statusREST API 与集群 join 检查都会读取此标志。也就是说这套就绪机制不仅服务于 join也服务于 MQTT 连接进程与网关——节点未就绪时拒绝新连接防止客户端在认证、授权与插件钩子安装完成之前接入。join 请求的拒绝逻辑本地守卫join/1的入口守卫emqx_cluster.erljoin(PeerNode) - case not is_boot_complete() andalso mria_rlog:role() : core of true - {error, This node has not fully booted yet. Please retry after it is started.}; false - do_join(PeerNode) end.两个关键条件缺一不可not is_boot_complete()本节点尚未完全启动mria_rlog:role() : core本节点是 core 节点。replicant 节点是例外——详见下文专门小节。命中守卫时返回固定错误消息This node has not fully booted yet. Please retry after it is started.这是用户侧CLI/API最终看到的内容。join 请求的拒绝逻辑目标节点守卫RPC 回调仅仅在发起方做检查是不够的一个已启动的节点可能向一个仍在启动中的目标节点发起 join或反过来。因此 emqx_cluster.erl 提供了 RPC 回调can_i_join/1-spec can_i_join(node()) - ok | {error, string()}. can_i_join(_RequestingNode) - maybe ok ? check_boot_complete(), ok ? check_single_node_mode() end.该回调在 join 目标节点上执行依次校验check_boot_complete/0目标节点是否已完成启动否则返回带节点名的错误Node ~s has not fully booted yet. Please retry after it is started.check_single_node_mode/0目标节点是否处于单节点许可模式社区版默认若是则返回Node ~s has a single node license。发起方通过check_permission/1emqx_cluster.erl调用远端check_permission(PeerNode) - try emqx_cluster_proto_v1:can_i_join(node(), PeerNode) catch error:{erpc, noconnection} - {error, {node_down, PeerNode}}; error:{exception, undef, [{emqx_cluster, can_i_join, _, _}]} - %% The peer node is older than 5.9.0 %% This can happen during rolling upgrade. ok end.emqx_cluster_proto_v1.erl 的实现通过erpc:call完成跨节点调用并声明该接口自5.9.0引入-spec can_i_join(node(), node()) - ok | {error, string()}. can_i_join(SelfNode, PeerNode) - erpc:call(PeerNode, emqx_cluster, can_i_join, [SelfNode]).这里有一个面向滚动升级的兼容设计如果对端节点版本低于 5.9.0不存在can_i_join函数会捕获undef异常并返回ok允许 join 继续执行——避免在升级窗口期因新协议检查而阻断老版本节点的正常加入。用户可见行为CLI 与 API 的返回路径CLIemqx ctl cluster join命令行入口位于 emqx_mgmt_cli.erlcluster([join, SNode]) - case emqx_cluster:join(ekka_node:parse_name(SNode)) of ok - emqx_ctl:print(Join the cluster successfully.~n), ... ignore - emqx_ctl:print(Ignore.~n); {error, Reason} Error - emqx_ctl:print(Failed to join the cluster: ~0p~n, [Reason]), Error end;当启动守卫命中时CLI 会输出Failed to join the cluster: This node has not fully booted yet. Please retry after it is started.而不是让节点崩溃。命令用法注册为{cluster join Node, Join the cluster}同文件第 224 行。REST API集群管理接口API 层入口位于 emqx_mgmt_api_cluster.erl同样委托给emqx_cluster:join/1-spec join(node()) - ok | ignore | {error, term()}. join(Node) - emqx_cluster:join(Node).因此 CLI 与 API 两条路径共享同一套守卫逻辑行为一致拒绝时返回明确错误而非触发节点崩溃。replicant 例外为什么启动中的 replicant 允许 joinjoin/1的守卫只在core节点上生效这是经过深思熟虑的设计。测试用例 emqx_mgmt_cli_SUITE.erl 的文档给出了原因Verifies that on a replicant node cluster join is NOT refused while boot is in progress: a replicant cannot finish booting without a core node, so a mid-boot join is what bootstraps it under manual cluster discovery.即replicant 节点在没有 core 节点的情况下根本无法完成启动。在手动集群发现manual cluster discovery模式下replicant 正是通过启动中途执行 join来完成引导的。如果对 replicant 也施加启动守卫就会陷入未启动完成不能 join不 join 无法启动完成的死锁。因此守卫条件是not is_boot_complete() andalso mria_rlog:role() : corereplicant 节点在启动期间仍可正常 join通过mria_rlog:role()判定角色该测试使用 meck 模拟replicant角色验证此行为。测试验证修复的自动化保障修复伴随了针对性的测试用例位于 emqx_mgmt_cli_SUITE.erlt_cluster_join_refused_while_booting第 134-149 行先调用emqx_cluster:set_booting(true)模拟启动中断言cluster join返回{error, This node has not fully booted _}清除标志后同样的命令会继续前进到对端检查返回{error, {node_down, nosuchnode127.0.0.1}}因为对端节点不存在。这验证了守卫只在启动期间生效。t_cluster_join_allowed_while_booting_on_replicant第 157-169 行用 meck 将mria_rlog:role()模拟为replicant验证启动中的 replicant 不会被守卫拦截命令直接进入对端检查。此外 emqx_node_readiness_SUITE.erl 也覆盖了加入节点与目标节点未就绪时 join 均被拒绝的场景印证了双端守卫的一致性。运维建议与总结基于本次修复运维与开发人员在集群操作中应注意等待节点完全就绪再执行 join新增节点启动完成后观察日志中的emqx_is_running信息或GET /status返回正常再执行emqx ctl cluster join避免无谓的错误重试。错误重试是安全且预期的行为若收到This node has not fully booted yet. Please retry after it is started.说明节点仍在启动中等待片刻后重试即可节点不会因此崩溃。replicant 引导不受影响手动集群发现模式下replicant 节点启动中途 join 是受支持的引导方式不会被误拦截。滚动升级兼容新旧版本混布期间新节点的can_i_join检查对 5.9.0 以下的老节点自动放行不会阻断升级流程。总而言之本次修复通过boot_in_progress标志emqx_cluster.erl与节点就绪标志emqx_node_readiness.erl的双重判定在 emqx_machine.erl 与 emqx_machine_boot.erl 的启动流程中建立了完整的生命周期管理将启动期间 join 导致节点宕机这一隐患转化为可预期的、带有清晰错误提示的安全拒绝从根源上提升了集群初始化的健壮性。赞分享后端物联网消息队列通信【免费下载链接】emqxThe most scalable and reliable MQTT broker for AI, IoT, IIoT and connected vehicles项目地址https://gitcode.com/gh_mirrors/em/emqx点击查看免费下载相关推荐EMQX 集群节点加入崩溃修复深度解析mqtt.max_packet_size 不一致与监听器启动时序EMQX 集群节点加入崩溃修复深度解析mqtt.max_packet_size 不一致与监听器启动时序 导读 本文围绕仓库变更记录 changes/ee/fi后端物联网消息队列通信5分钟恢复Windows 10经典界面ExplorerPatcher使用指南5分钟恢复Windows 10经典界面ExplorerPatcher使用指南 从Windows 10升级到Windows 11之后不少人会经历一段不适期任后端物联网消息队列通信CPython 修复 site.addsitedir() 重入崩溃gh-149504 深度解析与 .pth/.start 启动机制CPython 修复 site.addsitedir 重入崩溃gh 149504 深度解析与 .pth/.start 启动机制 本篇技术指南以 CPython编程语言语言运行时解释器标准库上一篇MediaPipe Python 安装3 分钟跑通Bazel 编译避坑下一篇终极罗技鼠标宏实战指南PUBG压枪脚本快速配置与深度优化创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考