Per-username Session Quota
该插件用于按用户名实施会话配额限制。
- 会话计数按用户名维护,并在集群范围内同步。
- 当达到配置的配额时,认证会以
quota_exceeded被拒绝。 - 使用已有
clientid重连不会额外消耗配额。 - 支持按用户名配置配额覆盖,可实现自定义限制、无限会话或禁止连接。
NOTE
通过将用户名设置为命名空间(在 client_attrs_init 配置中设置 client_attrs.tns)也可以实现按用户名限制会话数。 只有在命名空间方案不是这样设计时,才需要使用本插件。
配置
插件配置项:
max_sessions_per_username(默认:100)— 必须是正整数(>= 1)。snapshot_min_age_ms(默认:300000,范围:120000–900000)— 快照在允许重建前的最小存活时间。超出范围的值会被夹紧。snapshot_request_timeout_ms(默认:5000)
配置语义:
max_sessions_per_username:每个用户名默认允许的最大并发会话数。单个用户名可通过 overrides API 覆盖该值。snapshot_min_age_ms:触发快照重建前,快照必须达到的最小存活时间(毫秒)。用于防止在大集群中频繁重建。snapshot_request_timeout_ms:列表 API 在处理快照请求时的超时预算。
校验规则:
max_sessions_per_username必须 >= 1。小于 1 或非数字的值会被拒绝。- 对于数值字段,如果字符串可以转换为正整数,也会被接受。
通过标准插件配置 API 更新插件配置:
PUT /api/v5/plugins/<name-vsn>/config
运行时 API
该插件通过 plugin API gateway 暴露运行时 API。
基础路径:/api/v5/plugin_api/emqx_username_quota
Snapshot:GET /quota/usernames 返回的是预构建快照结果,而不是在每次请求时扫描实时会话数据。快照是按用户名会话计数生成的时间点副本,并按计数排序,以便高效支持基于游标的分页。快照在后台异步构建并缓存;只有当当前快照年龄超过 snapshot_min_age_ms 时才会触发新一轮构建。若实时计数与快照值发生偏移,响应项中会包含 snapshot_used 字段,便于调用方同时查看缓存值和当前值。
首个请求等待:如果首个请求到达时系统尚无任何快照,服务会等待正在进行的快照构建完成(最长等待时间为请求截止时间减去 1 秒)。若构建及时完成,则返回正常 200 响应;否则返回 503,并附带部分数据(见下文)。
会话查询
GET /quota/usernames— 列出所有存在活跃会话的用户名GET /quota/usernames/:username— 获取指定用户名详情GET /metrics— 以 Prometheus 文本格式导出插件指标POST /kick/:username— 踢掉指定用户名的所有会话
Snapshot 管理
DELETE /quota/snapshot— 强制重建快照
配额覆盖
POST /quota/overrides— 设置按用户名的配额覆盖DELETE /quota/overrides— 删除按用户名的配额覆盖GET /quota/overrides— 列出所有配额覆盖
GET /quota/usernames
查询参数:
limit:正整数,最大值100(默认100)used_gte:必填(未提供cursor时)— 会话数下限过滤条件。仅返回至少达到该会话数的用户名。必须是 >= 1 的正整数。cursor:可选,为上一次列表调用返回的不透明游标;若省略则返回第一页。
参数规则:
- 仅提供
used_gte:可以(第一页) - 仅提供
cursor:可以(used_gte已编码进游标) - 同时提供
used_gte和cursor:返回 400BAD_REQUEST— 过滤条件已锁定在游标中 - 两者都不提供:返回 400
BAD_REQUEST
行为说明:
- 结果始终按会话数、再按用户名排序。
- 分页基于游标。第一页不传
cursor。 - 每条记录都包含
username、实时used与limit(实际生效配额)。 - 如果实时
used与快照计数不同,则会返回snapshot_used。
成功响应结构:
data:用户名配额条目meta.limit:页大小(分页限制)meta.count:当前页条目数meta.total:快照中的总条目数meta.next_cursor:下一页游标(如有)meta.snapshot:快照元数据:nodegeneration(递增快照 ID)taken_at_ms(毫秒级快照时间戳)
错误响应:
400 BAD_REQUEST:缺少used_gte,或同时提供了used_gte和cursor400 INVALID_CURSOR:游标引用了不可用节点或格式错误503 SERVICE_UNAVAILABLE:快照正在重建- 响应体包含
snapshot_build_in_progress: true、data和meta data:从构建中的快照读取的部分第一页结果(若构建刚开始,可能为空)meta.count:部分条目数,meta.partial: true- 请使用有界退避重试相同请求
- 响应体包含
DELETE /quota/snapshot
强制立即重建快照。该接口在异步启动重建后返回 200 与 {"status": "ok"}。快照会在后台完成重建。
GET /quota/usernames/:username
返回指定用户名的详情。响应字段包括:username、used、limit、clientids。
若该用户名没有活跃会话,则返回 404 NOT_FOUND。
GET /metrics
以 Prometheus 文本格式返回插件指标。 在 replicant 节点上,请求会被转发到 snapshot owner 所在的 core 节点。
当前导出的指标:
emqx_username_count— 当前快照中的用户名总数
POST /kick/:username
踢掉指定用户名的所有会话。返回 {"kicked": N},其中 N 是被踢掉的会话数。
若该用户名没有活跃会话,则返回 404 NOT_FOUND。
POST /quota/overrides
设置按用户名的配额覆盖。请求体是一个 JSON 数组:
[
{"username": "user1", "quota": 1000},
{"username": "vip", "quota": "nolimit"},
{"username": "blocked", "quota": 0}
]覆盖语义:
quota 值 | 含义 |
|---|---|
| 正整数 | 该用户名的自定义会话上限 |
"nolimit" | 无限会话(不做配额控制) |
0 | 封禁 — 拒绝所有新连接 |
覆盖值会持久化到磁盘,并在集群范围内复制。当某个用户名没有覆盖配置时,使用全局 max_sessions_per_username。
DELETE /quota/overrides
按用户名删除覆盖配置。请求体是用户名字符串数组:
["user1", "blocked"]GET /quota/overrides
列出所有覆盖配置。返回 {"data": [{"username": "...", "quota": ...}, ...]}。
架构
Snapshot owner 路由
快照构建发生在 core 节点上。GET /quota/usernames 与 GET /metrics 会被路由到 snapshot owner 所在的 core 节点,该节点定义为当前运行中的 core 节点列表按排序后的第一个。
蓝绿快照
系统维护两套快照缓冲区(blue 和 green)。其中一套负责服务读取请求,另一套用于构建下一版快照。构建完成后,二者角色交换。这样在重建期间不会出现数据空窗期 —— 旧快照会一直可用,直到新快照准备完成。
后台快照构建
快照重建在后台进程中运行,并通过 yield 节流避免阻塞服务器。因此即使快照构建进行中,列表 API 仍然保持可响应。
运维说明
突发连接下的配额超限
配额判定发生在认证阶段,而会话计数最终落地发生在会话生命周期 Hook 阶段。 在高并发连接突发场景下(尤其是集群环境中),这会产生一个短暂的同步窗口,使得某个用户名的观测并发会话数可能暂时超过 max_sessions_per_username。
实际含义:
- 本插件在集群范围内提供的是“最终一致”的配额控制,能够应对突发负载。
- 它并不是在极端连接洪峰下的严格逐包准入闸门。
插件启动时的引导
当插件安装到一个正在运行的集群时,已有客户端会话是在 Hook 注册之前建立的。 插件启动时,会遍历所有本地 channel,将每个会话注册进配额状态,以完成初始化引导。
为了避免对 Core 节点造成大量 DB 写入风暴(特别是当 replicant 节点上已有大量连接时),引导循环做了节流:
- 每批注册 100 个会话。
- 每处理完一批,引导过程会等待最后一条写入记录复制回本地表之后再继续。轮询间隔为 10ms。
- 如果 10 秒内复制仍未完成,则记录一条
error级日志并中止引导。 在超时前已经注册的会话会被保留;剩余会话将在后续客户端重连触发 Hook 注册时自然补齐。
处理列表 API 返回的 503
当服务器繁忙或正在构建快照时,列表 API 会返回 503。
503 响应体中包含一个 data 数组,其内容是从构建中的快照表读取到的部分第一页结果。这样调用方可以立刻获得尽力而为的数据,而不是空响应。meta.partial: true 表示该数据并不完整。如果构建刚刚开始,这个部分页面也可能为空。
对 API 客户端的建议:
- 先检查
data中是否已有部分可用结果。 - 使用有界退避策略进行重试。