Posted in

Go语言实现RTSP over WebRTC网关:无需FFmpeg转码,纯Go SDP协商与ICE穿透(含TURN服务器集成细节)

第一章:RTSP over WebRTC网关的设计哲学与架构全景

RTSP over WebRTC网关并非简单协议转换器,而是在实时音视频分发范式演进中诞生的“语义桥接者”——它主动消解传统拉流模型的时延、NAT穿透与扩展性瓶颈,将RTSP源的低延迟采集能力与WebRTC的端到端加密、自适应拥塞控制及浏览器原生支持深度融合。

设计哲学的核心信条

  • 零客户端依赖:终端无需安装插件或专用播放器,仅需支持WebRTC的现代浏览器(Chrome 88+、Firefox 91+、Safari 16.4+)即可直连;
  • 流语义保真:不丢弃RTSP的PLAY/PAUSE语义,通过WebRTC DataChannel双向透传控制指令,实现精准启停与关键帧请求(PLI/FIR);
  • 资源感知调度:依据SDP协商结果动态选择H.264/AVC或H.265/HEVC编码路径,并在SFU模式下按订阅者能力分层转码(如720p@30fps → 480p@15fps)。

架构全景的三层协同

层级 组件 关键职责
接入层 RTSP Client Pool 复用TCP连接池管理千级IPC设备,支持带鉴权的rtsp://user:pass@ip:554/stream自动解析
转换层 Unified Media Pipeline 将RTSP/RTP流解复用为裸H.264 Annex-B帧,注入WebRTC RTCRtpSender,并注入SEI消息携带原始PTS戳
分发层 Adaptive SFU 基于QUIC传输层指标(丢包率、RTT)实时调整转发策略:高丢包时启用FEC + NACK混合重传

快速验证网关连通性

启动本地测试网关后,执行以下命令触发端到端链路检测:

# 向网关注册RTSP流(返回唯一streamId)
curl -X POST http://localhost:8080/v1/streams \
  -H "Content-Type: application/json" \
  -d '{"url":"rtsp://wowzaec2demo.streamlock.net/vod/mp4:BigBuckBunny_115k.mov","name":"test-bunny"}'

# 获取WebRTC播放用的offer(含ICE候选与DTLS指纹)
curl "http://localhost:8080/v1/streams/test-bunny/offer" \
  -H "Accept: application/json" \
  -d '{"sdp":"v=0\r\no=- 12345 1 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\nm=video 9 UDP/TLS/RTP/SAVPF 96\r\na=setup:actpass\r\na=mid:video\r\n"}'

响应中的answer.sdp字段可直接馈入RTCPeerConnection.setRemoteDescription()完成握手。整个流程规避了传统RTSP的TCP长连接阻塞问题,将端到端首帧渲染延迟稳定控制在400ms内。

第二章:纯Go实现的SDP协商引擎:从RFC 3264到生产级兼容

2.1 SDP解析与生成:基于go-sdp库的深度定制与边界场景处理

自定义媒体行扩展支持

为兼容私有编解码器标识,需扩展 sdp.MediaDescription 结构体,注入 x-codec-id 属性:

type ExtendedMediaDesc struct {
    *sdp.MediaDescription
    XCodecID string `sdp:"x-codec-id"`
}

func (e *ExtendedMediaDesc) MarshalSDP() string {
    raw := e.MediaDescription.MarshalSDP()
    return strings.Replace(raw, "m=", fmt.Sprintf("a=x-codec-id:%s\r\nm=", e.XCodecID), 1)
}

逻辑说明:MarshalSDP() 覆盖原生序列化逻辑,在首个 m= 行前注入自定义属性;XCodecID 作为非标准但服务端必需的协商字段,避免修改 go-sdp 核心代码。

常见边界场景处理策略

场景 处理方式 触发条件
空白/重复 a=setup 行 预解析去重 + 强制标准化为 "active" WebRTC 信令乱序
IPv6 地址缺失 scope ID 自动补全 %eth0(依据接口路由表) 容器内 SDP 生成

SDP 双向转换流程

graph TD
    A[原始SDP字符串] --> B{含x-codec-id?}
    B -->|是| C[解析为ExtendedMediaDesc]
    B -->|否| D[降级为标准MediaDescription]
    C --> E[校验codec参数合法性]
    D --> E
    E --> F[生成RFC 8866合规输出]

2.2 媒体能力协商:H.264/AV1编码参数对齐、时钟基准同步与PT映射策略

媒体能力协商是WebRTC端到端实时通信的基石,直接影响解码兼容性与播放连续性。

编码参数对齐关键点

  • H.264需协商 profile-level-id(如 42e01f 表示Baseline@Level 3.1)
  • AV1需交换 seq_profileseq_level_idx_0operating_points 字段
  • 分辨率/帧率/色彩空间(如 color_primaries=1, transfer_characteristics=1)必须严格匹配

时钟基准同步机制

WebRTC强制所有媒体流绑定统一NTP时钟源,通过RTCP Sender Report中的ntp_timestamprtp_timestamp建立线性映射:

a=rtcp-rsize
a=clock:20240517T083022.123Z  # RFC 8867 扩展字段,显式声明参考时钟

该字段使接收端可校准本地Jitter Buffer起始PTS,消除跨编解码器音画不同步风险。

PT映射策略表

Payload Type Codec Clock Rate Encoding Parameters
96 H.264 90000 profile-level-id=42e01f;packetization-mode=1
98 AV1 90000 av1-config=…;profile=0
graph TD
    A[Offer SDP] --> B{Codec Capabilities}
    B --> C[H.264 Profile/Level Check]
    B --> D[AV1 Operating Point Negotiation]
    C & D --> E[PT → Codec + Clock Rate Binding]
    E --> F[RTCP SR Timestamp Alignment]

2.3 RTSP Session状态机建模:DESCRIBE/SETUP/PLAY全生命周期Go协程安全管理

RTSP会话需严格遵循状态跃迁规则,避免竞态与资源泄漏。核心在于将每个请求映射为状态转换事件,并由单个协程串行驱动。

状态跃迁约束

  • INIT → DESCRIBED:仅响应有效 DESCRIBE 回复
  • DESCRIBED → READYSETUP 成功后进入就绪态
  • READY → PLAYINGPLAY 请求触发,启动媒体流读取协程

协程安全设计要点

  • 使用 sync.Mutex 保护状态字段(非 RWMutex:写频次低,避免锁升级复杂度)
  • 所有状态变更必须经 setState() 方法统一入口,内嵌原子校验
  • PLAY 启动的读协程通过 ctx.WithCancel() 关联会话生命周期
func (s *RTSPSession) setState(next State) error {
    s.mu.Lock()
    defer s.mu.Unlock()
    if !validTransition(s.state, next) { // 查表校验:INIT→DESCRIBED✅,INIT→PLAYING❌
        return fmt.Errorf("invalid state transition: %s → %s", s.state, next)
    }
    s.state = next
    return nil
}

validTransition 基于预定义映射表执行 O(1) 判断,确保协议合规性;s.mu 防止并发调用导致状态撕裂。

当前状态 允许跃迁至 触发请求
INIT DESCRIBED DESCRIBE
DESCRIBED READY SETUP
READY PLAYING / READY PLAY / PAUSE
graph TD
    INIT -->|DESCRIBE OK| DESCRIBED
    DESCRIBED -->|SETUP OK| READY
    READY -->|PLAY OK| PLAYING
    PLAYING -->|TEARDOWN| INIT
    READY -->|TEARDOWN| INIT

2.4 WebRTC Offer/Answer双向协商:支持Plan-B与Unified-Plan的动态适配机制

WebRTC 协商机制需兼容历史 Plan-B(单媒体轨道聚合)与现代 Unified-Plan(每轨道独立 SDP m= 行)两种语义。浏览器能力差异要求运行时动态识别并切换策略。

协商流程核心逻辑

function negotiate(sessionDescription) {
  const isUnifiedPlan = /a=msid:/g.test(sessionDescription.sdp) && 
                        !/a=ssrc:/g.test(sessionDescription.sdp); // Unified-Plan 特征:含 msid 且无全局 ssrc
  return isUnifiedPlan ? 'unified' : 'plan-b';
}

该检测逻辑基于 SDP 语法特征:Unified-Plan 强制每个 m= 行绑定独立 a=msid,而 Plan-B 在 a=ssrc: 中混用轨道标识。避免依赖 RTCPeerConnection.getConfiguration().sdpSemantics,因部分旧版 Chrome 返回 undefined

动态适配关键决策点

  • ✅ 检测远端 SDP 结构决定本地生成策略
  • ✅ 创建 RTCPeerConnection 时按协商结果设置 sdpSemantics
  • ❌ 禁止跨协商周期混用两种 Plan
特性 Plan-B Unified-Plan
媒体行结构 m=video 聚合多轨道 m= 行,每轨独立
轨道标识方式 a=ssrc: + a=msid: 混用 a=msid: 绑定
graph TD
  A[收到远程Offer] --> B{含 a=msid: 且无 a=ssrc:?}
  B -->|是| C[启用Unified-Plan]
  B -->|否| D[回退Plan-B]
  C --> E[生成Answer时按RFC8829构造]
  D --> F[按RFC3264兼容构造]

2.5 SDP语义校验与降级回退:当浏览器不支持VP9时的实时重协商触发逻辑

SDP媒体行语义校验流程

WebRTC在setRemoteDescription后,需解析SDP中m=video行的编解码器列表,提取a=rtpmap:a=fmtp:属性,比对本地支持能力。

降级触发条件

  • 检测到远端SDP声明VP9(98 payload),但RTCRtpReceiver.getCapabilities('video').codecs中无vp9条目
  • 同时存在备选编码器(如H.264/AV1)且其profile-level-id兼容

实时重协商代码示例

pc.onnegotiationneeded = async () => {
  try {
    const offer = await pc.createOffer(); // 触发新offer生成
    await pc.setLocalDescription(offer);
    // 此处插入SDP rewrite逻辑:移除VP9,提升H.264优先级
    const sdp = pc.localDescription.sdp.replace(
      /a=rtpmap:98 vp9\/90000\r\n/g, '' // 移除VP9声明
    ).replace(
      /a=rtpmap:102 H264\/90000/g, 'a=rtpmap:102 H264\/90000\r\na=fmtp:102 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f'
    );
    await pc.setLocalDescription({ type: 'offer', sdp });
  } catch (e) { console.error('Negotiation failed:', e); }
};

逻辑分析createOffer()在检测到能力不匹配时自动触发;sdp.replace()执行语义级重写,确保profile-level-id符合接收端解码器约束(如baseline profile)。参数level-asymmetry-allowed=1启用非对称等级协商,提升兼容性。

重协商状态流转

graph TD
  A[收到含VP9的远端SDP] --> B{本地能力校验}
  B -- 不支持VP9 --> C[标记need-reload]
  C --> D[触发onnegotiationneeded]
  D --> E[生成新Offer并重写SDP]
  E --> F[完成H.264降级会话]
字段 说明 典型值
payloadType VP9默认为98 98
profile-level-id H.264兼容性关键参数 42e01f(Baseline L3.1)

第三章:零依赖ICE框架构建:基于pion/webrtc的NAT穿透实践

3.1 ICE Agent状态迁移与候选者收集:STUN绑定请求的Go原生实现与超时控制

ICE Agent 的状态迁移由 ConnectionState 驱动,关键跃迁包括 New → Checking → Connected,依赖 STUN 绑定请求的成功响应。

STUN绑定请求构造

req := stun.MustBuild(stun.TransactionID, stun.BindingRequest)
// TransactionID: 唯一标识本次事务,用于匹配响应
// BindingRequest: RFC 5389 定义的标准绑定请求类型

该请求通过 UDP 发送至 STUN 服务器,触发反射候选者发现。

超时控制策略

  • 使用 time.AfterFunc 启动单次超时回调
  • 每个事务绑定独立 context.WithTimeout,避免全局阻塞
  • 重试上限设为 3 次,指数退避(100ms → 200ms → 400ms)
状态 触发条件 后续动作
Checking 至少一个候选对开始检测 启动 STUN 请求
Connected 收到有效 BindingSuccess 停止其余探测
graph TD
    A[New] -->|startGathering| B[Checking]
    B -->|BindingSuccess| C[Connected]
    B -->|All timeouts| D[Failed]

3.2 主机候选者与端口复用优化:利用SO_REUSEPORT提升并发连接建立效率

在高并发服务器场景中,传统单监听套接字易成为连接建立瓶颈。SO_REUSEPORT 允许多个进程/线程绑定同一端口,内核按流进行哈希分发,实现无锁负载均衡。

内核分发机制

int opt = 1;
setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt));
  • SOL_SOCKET:套接字层协议选项域
  • SO_REUSEPORT:启用端口复用(Linux 3.9+)
  • 多个 bind() 调用可成功绑定相同 <IP:Port>,内核依据四元组哈希决定归属

优势对比

维度 单套接字模型 SO_REUSEPORT 模型
连接分发 用户态 accept 队列争用 内核态哈希直派发
CPU 缓存局部性 优(绑定线程亲和性)

并发建连路径优化

graph TD
    A[新连接到达] --> B{内核四元组哈希}
    B --> C[Worker-0 套接字]
    B --> D[Worker-1 套接字]
    B --> E[Worker-N 套接字]
  • 消除 accept() 竞争,降低上下文切换开销
  • 各 worker 独立处理连接,天然支持 NUMA 感知部署

3.3 连接检查(Connectivity Checks)的并发调度模型:基于优先级队列的candidate pair探测策略

连接检查需在有限带宽与RTT约束下快速收敛有效路径。传统FIFO调度易因高延迟pair阻塞低延迟探测,导致ICE完成时间延长。

调度核心:动态优先级计算

优先级 $P{ij} = \alpha \cdot \frac{1}{\text{RTT}{\text{est}}^{(ij)}} + \beta \cdot \text{local_type_weight} + \gamma \cdot \text{remote_type_weight}$,其中 $\alpha=0.6,\beta=0.25,\gamma=0.15$。

候选对调度流程

import heapq

class ConnectivityScheduler:
    def __init__(self):
        self.queue = []  # (priority, timestamp, candidate_pair)

    def push(self, pair, rtt_est=100.0):
        priority = (0.6 / max(rtt_est, 10)) + 0.25 * pair.local_weight + 0.15 * pair.remote_weight
        heapq.heappush(self.queue, (-priority, time.time(), pair))  # 最大堆模拟

逻辑说明:使用负优先级实现最大堆;rtt_est 防御除零;local_weight(host > srflx > relay)体现拓扑亲和性;时间戳辅助公平性兜底。

Pair Type local_weight remote_weight
host ↔ host 1.0 1.0
host ↔ srflx 0.8 0.9
relay ↔ relay 0.3 0.3

执行时序保障

graph TD
    A[新CandidatePair生成] --> B{RTT历史可用?}
    B -->|是| C[计算动态优先级]
    B -->|否| D[赋予基础优先级0.4]
    C --> E[入堆调度]
    D --> E
    E --> F[按优先级并发触发STUN Binding Request]

第四章:TURN服务器集成与高可用网关部署

4.1 TURN over TLS/DTLS:使用gortc/turn实现带身份鉴权的中继通道建立

TURN over TLS/DTLS 是 WebRTC 穿透 NAT 的关键增强机制,既保障信令与媒体传输的机密性,又通过长期凭证(Long-Term Credential)实现服务端强身份校验。

核心配置结构

server := &turn.Server{
    Realm: "example.com",
    AuthHandler: func(username string) (string, bool) {
        // 查询数据库或 JWT 解析获取密码哈希
        return hashFromDB(username), username != ""
    },
    TLSConfig: &tls.Config{ /* mTLS 可选启用 */ },
}

该配置启用 RFC 8656 定义的长期凭证鉴权流程:username 格式为 timestamp:realmAuthHandler 返回对应 password(即 HMAC(key, username)),供 STUN 消息完整性校验。

协议栈对比

协议 加密层 鉴权方式 适用场景
TURN over UDP Long-Term Credential 内网低延迟测试
TURN over TLS TLS 1.2+ Same + TLS client cert 金融级安全要求
TURN over DTLS DTLS 1.2 Same WebRTC 端到端媒体流

建立流程(mermaid)

graph TD
    A[Client 发送 Allocate 请求] --> B{TLS/DTLS 握手完成?}
    B -->|是| C[携带 MESSAGE-INTEGRITY-SHA256]
    C --> D[Server 校验 username/password/HMAC]
    D -->|通过| E[返回 Allocation 成功 + Relay Address]

4.2 TURN Allocation生命周期管理:内存泄漏防护与Channel Binding自动续期机制

内存泄漏防护策略

TURN Allocation 对象需与底层 socket、定时器、内存缓冲区强绑定。若未显式释放,将导致 Allocation 实例长期驻留堆内存,引发 OOM。

  • 使用 RAII 模式封装 Allocation 生命周期
  • 注册 onClose 回调,触发 freeAllResources() 清理通道表、权限表及 UDP socket 引用
  • 启用 weak_ptr 管理 AllocationChannelBinding 的交叉引用

Channel Binding 自动续期机制

void ChannelBinding::refresh() {
    if (isExpired()) {
        sendRefreshRequest(); // 发送 CHANNEL_BIND 请求(RFC 5766 §11.3)
        resetExpiryTimer(600); // 续期周期 = min(lifetime, 600s)
    }
}

逻辑分析refresh() 在每次数据发送前被调用;sendRefreshRequest() 构造带 LIFETIMECHANNEL_NUMBER 属性的 STUN 消息;resetExpiryTimer(600) 防止服务端因超时主动销毁 binding,确保长连接稳定性。

关键参数对照表

参数 含义 推荐值 说明
lifetime Channel 绑定有效期 600s 服务端最大允许值,客户端应取 min(requested, server_max)
refresh_interval 续期触发阈值 lifetime × 0.8 预留网络抖动余量,避免临界失效
graph TD
    A[ChannelBinding 创建] --> B{是否启用 auto-refresh?}
    B -->|是| C[启动 expiry timer]
    C --> D[到期前 120s 触发 refresh]
    D --> E[重置 timer 并更新 expiry]
    B -->|否| F[依赖应用层手动调用]

4.3 多TURN实例负载均衡:基于Consul服务发现的动态TURN池路由策略

传统静态TURN配置难以应对高并发、节点动态扩缩容场景。本方案依托Consul实现服务注册/健康检查与实时路由决策闭环。

动态服务发现集成

TURN服务器启动时自动向Consul注册(含turn://, stun://端点及权重标签):

# 示例:注册带元数据的TURN节点
curl -X PUT "http://consul:8500/v1/agent/service/register" \
  -H "Content-Type: application/json" \
  -d '{
    "ID": "turn-node-01",
    "Name": "turn-server",
    "Address": "10.20.30.41",
    "Port": 3478,
    "Tags": ["udp", "tls"],
    "Meta": {"weight": "80", "region": "shanghai"},
    "Check": {"HTTP": "http://10.20.30.41:8080/health", "Interval": "10s"}
  }'

逻辑说明:Meta.weight用于加权轮询;Check.HTTP触发Consul健康状态同步,失效节点5秒内从服务列表剔除。

路由决策流程

graph TD
  A[客户端请求] --> B{Consul KV获取可用TURN列表}
  B --> C[按weight加权随机选择]
  C --> D[返回turn: URI+credentials]
  D --> E[客户端直连]

负载策略对比

策略 故障收敛时间 权重支持 配置热更新
DNS轮询 ≥60s
Consul动态路由 ≤5s

4.4 网关级NAT穿越兜底方案:STUN→TURN→RelayFallback三级穿透保障链设计

当端到端P2P直连失败时,需构建渐进式容错穿透链。首层依赖STUN获取反射地址并检测NAT类型;若对称型NAT阻断UDP映射一致性,则降级至TURN中继;最终在TURN不可达时激活RelayFallback——轻量级应用层中继服务。

三级触发逻辑

  • STUN探测超时(>500ms)或返回403 Forbidden → 进入TURN流程
  • TURN分配Allocation失败或ChannelBind超时 → 启动RelayFallback握手
# RelayFallback握手协议片段(TLS+WebSocket封装)
import asyncio
async def fallback_handshake(peer_id: str):
    async with websockets.connect(f"wss://relay.example.com/{peer_id}") as ws:
        await ws.send(json.dumps({"type": "auth", "token": gen_jwt()}))
        resp = await ws.recv()
        return json.loads(resp).get("relay_addr")  # 如 "192.168.3.10:44301"

该代码实现RelayFallback的轻量认证与地址分发:peer_id用于服务端路由隔离,gen_jwt()生成含TTL与权限声明的短期令牌,relay_addr为动态分配的独占中继端口,避免端口争用。

方案对比表

方案 延迟 带宽开销 部署复杂度 NAT兼容性
STUN 极低 全类型(除端口限制型)
TURN 80–150ms 高(全流量中继) 100%
RelayFallback 120–200ms 中(仅信令+加密载荷) 低(无ICE/STUN依赖) 全类型(含防火墙深度检测)
graph TD
    A[Peer A发起连接] --> B{STUN探测}
    B -- Success --> C[P2P直连]
    B -- Fail --> D{TURN Allocation}
    D -- Success --> E[TURN中继通道]
    D -- Fail --> F[RelayFallback握手]
    F --> G[应用层加密中继]

第五章:性能压测、线上故障模式与未来演进方向

压测工具链选型与真实业务场景对齐

在电商大促前的全链路压测中,团队摒弃了单纯基于 QPS 的传统指标,转而采用「订单创建成功率 + 支付回调延迟 + 库存扣减一致性」三维黄金指标。使用 JMeter + Prometheus + Grafana 构建闭环观测体系,将压测流量打标为 x-biz-scenario: seckill-v2,通过 SkyWalking 追踪跨 12 个微服务的完整调用链。一次压测暴露了 Redis 分布式锁在高并发下的 SETNX + EXPIRE 竞态问题——当锁设置成功但过期时间未生效时,导致超卖。最终改用 SET key value EX seconds NX 原子指令,并增加 Lua 脚本校验锁所有权。

线上典型故障模式复盘清单

故障类型 触发条件 根因定位手段 恢复耗时
数据库连接池耗尽 流量突增 + 慢 SQL 未熔断 Arthas thread -n 5 查阻塞线程 8.3 分钟
CDN 缓存穿透 热点商品 ID 被恶意构造不存在ID 日志中 cache-miss-rate > 92% + ELK 聚合 22 分钟
Kubernetes DNS 泛洪 多实例高频轮询 k8s-service.default.svc.cluster.local nslookup 延迟突增至 3s+ + CoreDNS metrics 41 分钟

自适应限流策略落地效果

在支付网关层部署 Sentinel 动态规则,不再依赖静态 QPS 阈值。基于过去 5 分钟 P99 RT(187ms)与当前系统 Load(>7.2)自动计算允许通过请求数。2024 年双十二期间,该策略在 Redis 集群节点故障导致响应毛刺时,将错误率从 14.6% 压降至 0.8%,且未影响核心下单路径。

// 生产环境已启用的自适应熔断逻辑片段
if (currentLoad > loadThreshold && recentRtP99 > rtThreshold) {
    // 触发半开状态,放行 5% 探针请求
    if (ThreadLocalRandom.current().nextDouble() < 0.05) {
        return tryHandle(request);
    }
    throw new ServiceUnavailableException("adaptive-circuit-open");
}

混沌工程常态化实践

每月执行 3 类注入实验:① Pod 网络延迟(tc netem delay 200ms 50ms);② MySQL 主节点 CPU 持续 95% 占用;③ Kafka Consumer Group offset 强制重置。2024 Q2 共发现 7 个隐性缺陷,包括订单状态机在分区重平衡后未正确处理 RebalanceInProgressException,已通过 @KafkaListenerContainerProperties.setAckOnError(false) 修复。

云原生可观测性演进路径

正推进 OpenTelemetry Collector 替代现有日志采集 Agent,统一 trace/metrics/logs 采样策略。关键改造点:将业务日志中的 order_iduser_id 字段自动注入 span context,实现日志与链路 100% 关联;同时将 Prometheus 指标按租户维度打标 tenant_id="shanghai-01",支撑多租户 SLO 精细核算。

AIOps 故障预测能力验证

基于历史 18 个月的 JVM GC 日志(含 G1EvacuationPause 时长、Metaspace 使用率)、磁盘 IO wait 时间、K8s Pod restart count 训练 LightGBM 模型。在预发布环境模拟内存泄漏场景时,模型提前 23 分钟预警 OOM 风险概率 89.4%,运维人员据此触发自动扩容与堆转储分析。

多活架构下的一致性挑战

华东-华北双活单元化改造中,用户余额更新出现跨单元写冲突。放弃最终一致性妥协方案,采用 TCC 模式重构资金服务:Try 阶段冻结异地单元额度并记录反向补偿事务 ID,Confirm 阶段仅操作本单元,Cancel 阶段通过 RocketMQ 事务消息驱动异地解冻。压测显示跨单元事务平均耗时稳定在 142ms±19ms。

专治系统慢、卡、耗资源,让服务飞起来。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注