- 后端
- RPC框架
【免费下载链接】finagle
A fault tolerant, protocol-agnostic RPC system
导读
Mux 是 Finagle 自研的多路复用 RPC 协议,为 ThriftMux、MySQL 等协议栈提供底层传输能力。本文以官方指标文档 doc/src/sphinx/metrics/Mux.rst 为骨架,逐一解析 Mux 会话生命周期(draining/leased/drained)、帧与流控(framer)、传输失败、TLS 协商及握手延迟等全部指标的含义、前缀规则与源码实现位置,帮助你准确读懂 Finagle 上报的mux/*监控数据,并据此定位会话挂起、连接被异常终止、协议降级等线上问题。
Mux 协议与指标前缀约定
Mux 是一个面向多路复用会话(multiplexed session)的二进制协议,客户端与服务端共享一套消息类型(Tdispatch/Rdispatch、Treq/Rreq、Tping/Rping、Tdrain/Rdrain、Tlease 等),并支持握手阶段协商帧大小、TLS 与压缩能力。协议主体实现在 finagle-mux/src/main/scala/com/twitter/finagle/Mux.scala,会话层实现位于 finagle-mux/src/main/scala/com/twitter/finagle/mux/pushsession。
理解以下指标前需要先掌握两类命名前缀:
<server_label>/mux/...与<client_label>/mux/...:label由com.twitter.finagle.param.Label参数决定,即客户端/服务端实例的名称。源码中 Mux.Client 通过params[Stats].statsReceiver.scope("mux")将指标限定在client_label/mux之下;服务端同样以statsReceiver.scope("mux")组织(见 Mux.Server 的defaultSessionFactory)。- 无前缀的裸名称(如
clienthangup、serverhangup):表示指标直接挂载在会话级默认统计路径下,不经过mux命名空间。
会话生命周期指标:draining、drained 与 leased
Mux 会话的调度状态机为Dispatching -> Leasing -> Draining -> Drained(见 MuxClientSession.scala),相关指标精确对应这几次状态迁移:
| 指标 | 类型 | 含义 |
|---|---|---|
<server_label>/mux/draining | counter | 服务端发起会话排空(session draining)的次数 |
<server_label>/mux/leased | counter(verbosity:debug) | 服务端在会话处于非 draining/drained 状态下发出的租约(lease)次数 |
<client_label>/mux/draining | counter | 客户端观察到的服务端发起会话排空的次数 |
<client_label>/mux/drained | counter | 服务端发起的排空成功完成(收到服务端确认并结束会话)的次数 |
源码印证:客户端会话中三个计数器定义在 MuxClientSession.scala:
private[this] val leaseCounter = statsReceiver.counter(Verbosity.Debug, "leased") private[this] val drainingCounter = statsReceiver.counter("draining") private[this] val drainedCounter = statsReceiver.counter("drained")当客户端收到服务端的Tdrain消息时(MuxClientSession.scala),会立即回写Rdrain应答、将状态置为Draining并递增draining计数器;当所有未完成 dispatch 归零后(pendingDispatches == 0),状态转为Drained并递增drained计数器(MuxClientSession.scala)。服务端侧在 MuxServerSession.scala 中于发起Tdrain(Tags.ControlTag)时递增自己的draining计数。
leased计数只在Dispatching/Leasing状态下收到Tlease消息时递增(MuxClientSession.scala),与 Finagle 的 GC 回避(GC Avoidance)租约机制相关。监控建议:若客户端mux/draining频繁增长而mux/drained不增长,说明排空过程被卡住(可能仍有未完成请求),可结合 finagle-mux/src/main/scala/com/twitter/finagle/mux/lease/exp/ClockedDrainer.scala 中drain/undrain/forcedgcs/naturalgcs等租约管理指标交叉定位。
消息处理正确性指标:duplicate_tag 与 orphaned_tdiscard
这两个指标反映服务端在处理多路复用标签(tag)时遇到的协议异常情况:
| 指标 | 类型 | 含义 |
|---|---|---|
<server_label>/mux/duplicate_tag | counter | 服务端正在处理某个 tag 的请求时,又收到了使用同一 tag 的新请求的次数 |
<server_label>/mux/orphaned_tdiscard | counter | 服务端收到没有对应请求的Tdiscard消息次数;典型场景是请求已被响应后才收到该 tag 的取消消息 |
源码印证:两者都定义在 ServerTracker.scala 中:
duplicate_tag在服务端发现 tag 已被占用时递增(ServerTracker.scala),随后会中断该 tag 上原有的 pending dispatch;orphaned_tdiscard在服务端收到Tdiscard但找不到对应请求时递增(ServerTracker.scala),此时服务端无需回写Rdiscarded。
源码注释特别说明这两个计数器是"按需创建"(on-demand)的——因为这类事件足够稀少,不值得常驻一个计数器。监控建议:正常情况下两者应接近 0;若duplicate_tag持续增长,通常意味着客户端与服务端的 tag 分配逻辑出现失配(例如客户端重试或中断处理有误),属于需要告警的协议层异常信号。
请求上下文大小:request_context_bytes
| 指标 | 类型 | 含义 |
|---|---|---|
<server_label>/mux/request_context_bytes | stat | 每个请求所携带的 context(广播上下文)字节数 |
源码印证:定义于 ServerProcessor.scala:
private[this] val contextBytesStat = statsReceiver.stat("request_context_bytes")在dispatch处理Tdispatch消息时,服务端对tdispatch.contexts的序列化大小累加到该 stat(ServerProcessor.scala),随后通过Contexts.broadcast.letUnmarshal还原广播上下文。监控建议:该 stat 是直方图而非计数器,关注其分布而非累计值。若 P99 显著偏高,说明客户端携带了过大的广播 context(如过长的 Dtab 或自定义 context 键值),会直接推高每个请求的带宽与解码开销。
连接挂断指标:clienthangup 与 serverhangup
| 指标 | 类型 | 含义 |
|---|---|---|
clienthangup | counter | 客户端一侧突然终止(abruptly terminated)会话的次数 |
serverhangup | counter | 服务端一侧突然终止会话的次数 |
与draining的"优雅排空"不同,hangup表示连接被非协议方式强行断开(如 TCP 重置、超时关闭、进程崩溃)。这两个指标不带mux/前缀,直接位于会话统计路径下。监控建议:两者中的任何一个出现非预期增长,都说明对端在未走Tdrain流程的情况下断开了连接;结合传输层指标(下文的read/failures、write/failures)可以进一步判断断连发生在握手阶段还是正常数据阶段。
帧与流控指标:mux/framer 系列
启用 mux framing(帧化/分片)后,以下指标用于观察传输层字节流量与流控状态:
| 指标 | 类型 | 含义 |
|---|---|---|
<label>/mux/framer/write_stream_bytes | histogram | mux framing 启用时写入传输层的字节数 |
<label>/mux/framer/read_stream_bytes | histogram | mux framing 启用时从传输层读取的字节数 |
<label>/mux/framer/pending_write_streams | gauge | 未完成的写流(outstanding write streams)数量 |
<label>/mux/framer/pending_read_streams | gauge | 未完成的读流(outstanding read streams)数量 |
<label>/mux/framer/write_window_bytes | gauge | 分片(fragment)的最大尺寸;值为 -1 表示写入不分片 |
源码印证:前四个指标统一定义在 SharedNegotiationStats.scala 中,其中字节直方图与 pending gauge 均以Verbosity.Debug级别注册(意味着默认情况下不会上报到生产监控,需开启 debug verbosity 才能观测):
val writeStreamBytes = sr.stat(framerVerbosity, "mux", "framer", "write_stream_bytes") val readStreamBytes = sr.stat(framerVerbosity, "mux", "framer", "read_stream_bytes") // ... sr.addGauge(framerVerbosity, "mux", "framer", "pending_write_streams") { ... } sr.addGauge(framerVerbosity, "mux", "framer", "pending_read_streams") { ... }write_window_bytes与握手阶段协商的MaxFrameSize参数直接相关。Mux.scala 中的MaxFrameSize参数允许配置单帧最大字节数,超过该值的消息会被分片为多个片段传输,其默认值为Int.MaxValue.bytes(约 2 GiB),因此默认情况下写入通常不分片,write_window_bytes为 -1 恰好印证这一默认行为。帧大小在握手期间通过MuxFramer.Header键交换(见 transport/MuxFramer.scala),客户端与服务端分别在 Mux.Client.headers 与 Mux.Server.headers 中编码发送。
监控建议:pending_write_streams与pending_read_streams分别反映写方向与读方向的积压情况,是判断 Mux 会话是否被流控卡住的直接依据;两个字节直方图可用来核对客户端与服务端之间的实际传输吞吐是否与业务请求量匹配。
传输层失败指标:read/failures 与 write/failures
| 指标 | 类型 | 含义 |
|---|---|---|
<label>/mux/transport/read/failures/ | counter | mux 读路径上发生的任何异常,包括握手异常、thrift 降级(针对服务端)等 |
<label>/mux/transport/write/failures/ | counter | mux 写路径上发生的任何异常,同样包括握手异常、thrift 降级等 |
指标名的尾缀/表示该处为按异常类型区分的子树——Finagle 的 stats 体系会为不同类型的异常各建一个计数,例如.../failures/org.apache.thrift.TApplicationException之类。监控建议:当对端使用旧版或非 Mux 协议栈(如纯 Thrift 直连)时,握手会失败并反映在这些计数中;服务端侧还会发生 thrift 降级(downgrade)路径,因此服务端这两个计数出现少量增长并不一定代表故障,需要结合握手成功率(见下一节)综合判断。
TLS 协商指标:tls/upgrade/success 与 tls/upgrade/incompatible
| 指标 | 类型 | 含义 |
|---|---|---|
<label>/mux/tls/upgrade/success | counter | 客户端或服务端成功将连接升级为 TLS 的次数 |
<label>/mux/tls/upgrade/incompatible | counter | 客户端或服务端因 TLS 要求或能力不兼容而建立会话失败的次数 |
源码印证:两个计数定义于 SharedNegotiationStats.scala,其中success以默认 verbosity 注册,incompatible亦如此(tlsVerbosity = Verbosity.Default),因此这两项在生产监控中默认可见:
val tlsSuccess = sr.counter(tlsVerbosity, "mux", "tls", "upgrade", "success") val tlsFailures = sr.counter(tlsVerbosity, "mux", "tls", "upgrade", "incompatible")Mux 支持"机会式 TLS"(Opportunistic TLS):客户端与服务端在握手阶段通过MuxOpportunisticTls.Header交换各自期望的 TLS 等级(Off / Desired / Required),协商成功后再动态在 Netty pipeline 中注入 SSL 处理器(见 Mux.scala 的tlsEnable与 Mux.scala 的服务端对应实现)。协商等级由com.twitter.finagle.param.OppTls参数控制,OpportunisticTlsParams混入Client/Server以供配置。
监控建议:incompatible增长表示一方要求Required而另一方仅支持Off/Desired,或 TLS 证书配置缺失——注意 Mux.Client.validateTlsParamConsistency 会在启用机会式 TLS 但缺少 SSL 配置时直接抛出IllegalStateException,这类配置错误应在部署时被拦截,而非等到运行时才暴露为incompatible计数。
握手延迟指标:handshake_latency_us
| 指标 | 类型 | 含义 |
|---|---|---|
<label>/mux/handshake_latency_us | histogram(verbosity:debug) | Mux 握手(协商帧大小、TLS、压缩等)的延迟 |
源码印证:定义于 MuxClientNegotiatingSession.scala:
private[this] val muxHandshakeLatencyStat = stats.stat(Verbosity.Debug, "handshake_latency_us")该指标以微秒为单位统计从发起协商到会话就绪的耗时,覆盖帧大小、机会式 TLS 等级与压缩偏好的交换过程。与framer系列一样注册为verbosity:debug,默认不进入生产指标,仅在调试会话建立性能时启用。
verbosity:debug 指标的观测方式
本文档中标记verbosity:debug的指标共有三个:<server_label>/mux/leased、<label>/mux/framer/*系列(字节直方图与 pending gauge)以及<label>/mux/handshake_latency_us。在 Finagle 的 stats 体系中,Verbosity.Debug表示该指标属于"按需观测"类别,默认不会被生产 StatsReceiver 暴露(可参考 finagle-core/src/main/scala/com/twitter/finagle/stats/JavaLoggerStatsReceiver.scala 中按 verbosity 区分日志级别的处理方式)。排查会话建立缓慢、帧传输积压等问题时,需要显式开启 debug verbosity 的采集通道才能看到这些数据。
指标速查总表
| 指标名 | 类型 | verbosity | 统计内容 |
|---|---|---|---|
<server_label>/mux/draining | counter | 默认 | 服务端发起会话排空的次数 |
<server_label>/mux/leased | counter | debug | 服务端在非排空状态下发出的租约次数 |
<client_label>/mux/draining | counter | 默认 | 客户端观察到的服务端排空次数 |
<client_label>/mux/drained | counter | 默认 | 服务端排空成功完成的次数 |
<server_label>/mux/duplicate_tag | counter | 默认 | 服务端收到重复 tag 请求的次数 |
<server_label>/mux/orphaned_tdiscard | counter | 默认 | 服务端收到无对应请求的 Tdiscard 次数 |
<server_label>/mux/request_context_bytes | stat | 默认 | 每请求携带的 context 字节数 |
clienthangup | counter | 默认 | 客户端侧突然终止会话的次数 |
serverhangup | counter | 默认 | 服务端侧突然终止会话的次数 |
<label>/mux/framer/write_stream_bytes | histogram | debug | framing 下写入传输层的字节数 |
<label>/mux/framer/read_stream_bytes | histogram | debug | framing 下从传输层读取的字节数 |
<label>/mux/framer/pending_write_streams | gauge | debug | 未完成写流数量 |
<label>/mux/framer/pending_read_streams | gauge | debug | 未完成读流数量 |
<label>/mux/framer/write_window_bytes | gauge | debug | 分片最大尺寸,-1 表示不分片 |
<label>/mux/transport/read/failures/ | counter | 默认 | 读路径异常(含握手、thrift 降级) |
<label>/mux/transport/write/failures/ | counter | 默认 | 写路径异常(含握手、thrift 降级) |
<label>/mux/tls/upgrade/success | counter | 默认 | TLS 升级成功次数 |
<label>/mux/tls/upgrade/incompatible | counter | 默认 | TLS 协商不兼容失败次数 |
<label>/mux/handshake_latency_us | histogram | debug | Mux 握手延迟(微秒) |
结语:如何用这套指标定位线上问题
将上述指标组合起来,可以形成一套完整的 Mux 会话健康排查路径:
- 会话无法优雅关闭:观察
<client_label>/mux/draining与<client_label>/mux/drained的差值,若排空请求频繁但完成数长期不增长,说明有请求未结束、排空被阻塞; - 连接被异常断开:
clienthangup/serverhangup增长时,同步查看<label>/mux/transport/read/failures/与write/failures/下的异常类型,区分握手失败与数据阶段异常; - 协议失配或版本不兼容:
duplicate_tag非零增长表示 tag 管理失配;tls/upgrade/incompatible增长表示 TLS 等级协商失败; - 吞吐与积压:开启 debug verbosity 后,用
framer系列的字节直方图与 pending gauge 判断读写方向是否存在积压,用handshake_latency_us判断连接建立是否变慢。
关于 ThriftMux(基于 Mux 的 Thrift 多路复用实现)的补充指标,可继续参阅姊妹文档 ThriftMux 指标说明。
- 后端
- RPC框架
【免费下载链接】finagle
A fault tolerant, protocol-agnostic RPC system
相关推荐
KCP协议终极指南:重塑视频监控低延迟传输的完全解析
KCP协议终极指南:重塑视频监控低延迟传输的完全解析 KCP协议是一种快速可靠的自动重传请求(ARQ)协议,专为优化网络数据传输的速度和可靠性而设计。在视频监控
网络通信如何零基础搭一套智能问数系统:对话式查询数据库 5 分钟出图完整实操指南
如何零基础搭一套智能问数系统:对话式查询数据库 5 分钟出图完整实操指南 还在等数据组排期出报表?用 SQLBot,一句话就能把数据问出来。这套开源的智能问数系
后端人工智能大模型RAGAI 应用数据可视化前端MCP 服务theMLbook动画插图制作:从静态图表到动态可视化的完整流程
theMLbook动画插图制作:从静态图表到动态可视化的完整流程 theMLbook是一个专注于重现《百页机器学习书》中插图的Python开源项目,通过动画可视
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考