ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

EMQX 停止缓存订阅(Subscribe)ACL 授权检查结果:原理、实现与调优指南

2026/9/25 5:17:13 拓冰建站 浏览量
EMQX 停止缓存订阅(Subscribe)ACL 授权检查结果:原理、实现与调优指南 后端物联网消息队列通信【免费下载链接】emqxThe most scalable and reliable MQTT broker for AI, IoT, IIoT and connected vehicles项目地址https://gitcode.com/gh_mirrors/em/emqx点击查看免费下载导读本文围绕 EMQX 5.x 中一项针对授权Authorization / ACL缓存的重要行为调整展开订阅subscribe操作的 ACL 检查结果不再进入授权缓存而发布publish操作仍然照常缓存。该改动源于 changes/ee/fix-16550.en.md其动机非常直接——MQTT 订阅通常在连接生命周期内只发生一次为它缓存检查结果绝大多数时候只是白白占用内存。读完本文你将理解 EMQX 授权缓存的完整读写路径、订阅为何被从缓存中剔除、这一行为对内存与规则热更新的影响以及如何通过authorization.cache配置enable/max_size/ttl/excludes对缓存行为做精细化控制。一、改动背景为什么订阅检查结果不值得缓存原文档给出的理由只有一句话但信息密度很高MQTT subscription is mostly done once per connection life cycle. Holding the subscribe ACL check result in cache is most of the time a waste of RAM.翻译过来即MQTT 订阅在大多数场景下每个连接生命周期内只执行一次为此类一次性检查结果长期持有缓存条目大部分时间都是对 RAM 的浪费。结合 MQTT 协议语义可以这样理解一个客户端连接建立后通常只做一次或少数几次SUBSCRIBE然后长时间收发消息直到断开发布PUBLISH则不同一条连接上可能持续高频地往同一主题发布消息同一动作, 主题组合会被反复命中缓存收益极高订阅一旦成功后续消息投递由订阅树session/subscription 表管理并不会再次触发该主题的 ACL 检查因此缓存订阅检查结果几乎没有“复用”机会。从 emqx_access_control.erl 的授权入口可以看出EMQX 的授权缓存是按进程channel 进程与主题维度设计的每次命中缓存都会跳过整条授权链路的执行对订阅这种“一次性动作”而言跳过执行换来的收益远小于为它维护缓存条目的成本。二、实现证据缓存模块如何对 subscribe 说不改动最核心的实现位于授权缓存模块 emqx_authz_cache.erl。该模块以“动作 主题”为键维护缓存键形如{?MODULE, Topic, QoS, Retain}见 emqx_authz_cache.erl值形如{AuthzResult, CachedAt}。针对订阅动作模块在读写两侧都做了短路处理读取侧——get_authz_cache/2对subscribe动作直接返回not_found不进入任何缓存查找%% apps/emqx/src/emqx_authz_cache.erl get_authz_cache(#{action_type : subscribe}, _Topic) - not_found; get_authz_cache(PubSub, Topic) - case erlang:get(cache_k(PubSub, Topic)) of undefined - not_found; {AuthzResult, CachedAt} - if_expired(get_cache_ttl(), CachedAt, ...) end.写入侧——put_authz_cache/3对subscribe动作直接返回ok什么都不写%% apps/emqx/src/emqx_authz_cache.erl put_authz_cache(#{action_type : subscribe}, _Topic, _AuthzResult) - ok; put_authz_cache(Pub, Topic, AuthzResult) - ... %% 发布动作才真正写入缓存也就是说即使在authorize/4的入口处emqx_authz_cache:is_enabled(Topic)判定该主题允许缓存订阅动作的检查结果也永远不会被写入缓存、永远不会命中缓存。订阅 ACL 检查因此始终走完整的授权链路run_hooks(client.authorize, ...)保证每一次订阅都基于最新规则与最新客户端上下文实时裁决。三、调用链订阅检查如何绕过缓存订阅授权检查的完整调用链如下客户端发送SUBSCRIBE报文channel 进程在 emqx_channel.erl 的do_check_sub_authzs2/3中对每个 Topic Filter 逐一做授权检查主题过滤器中带 QoS由authz_action/1构造出?AUTHZ_SUBSCRIBE(QoS)动作见 emqx_channel.erl共享订阅如$share/g/t/#只检查真实主题t/#由emqx_topic:get_shared_real_topic/1处理见 emqx_channel.erl调用emqx_access_control:authorize(AuthzContext, Action, Topic)在 emqx_access_control.erl 中先经emqx_authz_cache:is_enabled(Topic)判断主题是否启用缓存若启用则进入check_authorization_cache/3check_authorization_cache/3见 emqx_access_control.erl调用emqx_authz_cache:get_authz_cache(Action, Topic)——对subscribe动作返回not_found于是走do_authorize_with_cache_policy/3执行真实的规则匹配规则匹配结果返回后尝试put_authz_cache(Action, Topic, AuthzResult)——对subscribe动作同样被短路为ok。最终效果是订阅路径 每次实时全量检查零缓存发布路径 命中缓存时直接返回未命中才执行检查并回填缓存。四、配套机制发布缓存如何工作对照理解理解了订阅不缓存再对照发布缓存机制会更有画面感。发布路径的缓存逻辑同一文件、同一套键值结构包含如下关键点命中判定check_authorization_cache/3在not_found时计数cache_miss命中时计数cache_hit并触发client.check_authz_complete钩子钩子参数中会带上来源标记cache见 emqx_access_control.erl因此外部可以通过该钩子观察到某次授权结果是来自缓存还是实时检查是否可缓存授权链路的返回结果中若带有is_cacheable false则即使发布动作也不会写入缓存由should_cache_authz_result/1控制见 emqx_access_control.erl。这为“某些动态来源如实时计算的规则天然不可缓存”提供了扩展点容量与淘汰缓存条数受max_size限制默认 32键以队列queue维护先进先出顺序缓存满且最新条目未过期时淘汰最旧条目evict_authz_cache若满且全部过期则整体清空见 emqx_authz_cache.erlTTL 与全局排空条目带时间戳过期即失效drain_cache/0会在persistent_term中写入一个全局排空时间戳使所有早于该时间戳的缓存条目立即视为过期见 emqx_authz_cache.erl 与 emqx_authz_cache.erl。ACL 规则或授权配置变更时EMQX 会调用emqx_authz_cache:drain_cache()主动失效缓存见 emqx_authz/emqx_authz.erl避免旧规则被继续命中按客户端定向清理drain_cache/1可通过 ClientId 定向向对应 channel 进程发送clean_authz_cache消息channel 进程收到后清空自己的授权缓存见 emqx_channel.erl。因为订阅不再写入缓存drain_cache之后也无需担心残留的订阅缓存条目——测试用例 emqx_authz_cache_SUITE.erl 中甚至特意注释“subscribe is not cached, so we publish to verify cache works after drain”即用发布操作来验证排空后缓存重建仍正常。五、配置项authorization.cache 全参数说明该行为与authorization.cache配置块紧密相关schema 定义位于 emqx_schema.erl字段说明与默认值如下配置项类型默认值说明authorization.cache.enablebooleantrue是否启用授权缓存订阅动作不受影响始终不缓存authorization.cache.max_sizeinteger1–104857632每个 channel 进程缓存的最大条目数达到上限后按 FIFO 淘汰最旧条目authorization.cache.ttlduration1m缓存条目存活时间超过 TTL 的条目在读取/清理时失效authorization.cache.excludesarray of binary[]匹配这些主题模式如nocache/#的授权结果不缓存对应的 i18n 描述可参见 rel/i18n/emqx_schema.hocon 与 rel/i18n/emqx_schema.hoconexcludes的官方描述为“Exclude caching ACL check results for topics matching the given patterns”。配置示例emqx.conf/ HOCONauthorization { cache { enable true max_size 32 ttl 1m excludes [nocache/#, system//status] } }几点实操建议enable与订阅无关即使enable true订阅检查结果依然不缓存enable false时发布检查也全部实时执行适合 ACL 规则极其频繁变化的场景代价是发布性能下降excludes的典型用途对 ACL 结果高度动态、随客户端上下文实时变化的主题如设备上线后才确定的权限可在excludes中列出以彻底绕过缓存对订阅动作而言无需配置它天然就不缓存max_size与ttl的权衡缓存条目存于 channel 进程的进程字典erlang:get/put中max_size直接影响单连接内存占用由于订阅不再参与缓存同一连接上缓存条目仅由发布动作产生相同max_size下实际占用的 RAM 较改动前显著下降。六、内存收益与行为影响评估内存收益以典型物联网场景为例一条连接上客户端往往订阅 1N 个主题、随后持续上报数据。改动前每个订阅主题的检查结果都会占用一个缓存条目直到 TTL 到期或被淘汰改动后这部分内存完全省去缓存只服务于真正高频复用的发布主题。在多租户网关、消息量大、连接数高的部署中这一改动能直接降低每个 channel 进程的进程字典占用。行为影响订阅检查每次都实时执行ACL 规则变更后无需等待 TTL 或手动 drain新的订阅立即应用最新规则订阅路径每次都会触发完整的client.authorize钩子链订阅频率极高的场景如短时间内反复订阅/退订检查开销会高于改动前但此类行为在 MQTT 实践中并不常见与节省的内存相比通常值得发布路径行为不变缓存命中率、cache_hit/cache_miss指标与排空机制均不受影响。七、测试验证仓库中的 emqx_authz_cache_SUITE.erl 覆盖了授权缓存的关键行为可直接佐证本文结论t_cache_exclude第27-37行将nocache/#加入excludes后订阅nocache//#并发布消息断言缓存列表为空验证excludes生效t_clean_authz_cache第39-59行先订阅并发布以产生缓存条目再向 channel 进程发送clean_authz_cache消息断言缓存被清空验证定向清理路径t_drain_authz_cache第61-80行调用drain_cache/0后缓存条目立即失效随后通过发布操作验证缓存能够重建且注释明确指出“订阅不缓存故用发布来验证”。这些用例与 emqx_authz_cache.erl 中订阅短路的实现相互印证共同构成该改动的完整证据链。小结EMQX 通过 fix-16550.en.md 这项改动将授权缓存的适用范围收敛为“发布动作 可缓存来源 未排除主题”订阅动作的检查结果彻底不落缓存。从实现上看这是 emqx_authz_cache.erl 中读写两侧各一行 pattern 匹配的短路从效果上看它贴合 MQTT 订阅“连接生命周期内低频发生”的语义在几乎不影响正确性的前提下省掉了大量冗余内存。对运维与开发者而言理解这一行为后可以更准确地利用authorization.cache的enable/max_size/ttl/excludes做内存与实时性的精细化权衡。赞分享后端物联网消息队列通信【免费下载链接】emqxThe most scalable and reliable MQTT broker for AI, IoT, IIoT and connected vehicles项目地址https://gitcode.com/gh_mirrors/em/emqx点击查看免费下载相关推荐EMQX Auto Subscribe 自动订阅功能详解配置模板、占位符与源码实现原理EMQX Auto Subscribe 自动订阅功能详解配置模板、占位符与源码实现原理 EMQX 的 emqx_auto_subscribe 应用位于 ap后端物联网消息队列通信EMQX 加固安全模式下的内部订阅校验topic 校验、授权、MQTT 能力检查与订阅 Hook 全解析EMQX 加固安全模式下的内部订阅校验topic 校验、授权、MQTT 能力检查与订阅 Hook 全解析 导读 本文围绕 EMQX 安全加固hardened后端物联网消息队列通信EMQX 授权缓存authz cache内存优化客户端断开即清理的设计与实现EMQX 授权缓存authz cache内存优化客户端断开即清理的设计与实现 本文围绕 EMQX 中 PR 15899 引入的优化——客户端断开连接时立即后端物联网消息队列通信创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考