XCC API

XSwitch 集群

本章讨论 XSwitch 集群组网。


来话处理

来话处理如下图所示。假设中继侧来话可以以一定算法分配置到 3 个不同的 XSwitch Node 节点。XNode 收到呼叫后,向 NATS 广播来话消息(Event.Channel(state = START)),Ctrl 收到后进行处理。

所有 XNode 都订阅至少两个 Subject:

  • cn.xswitch.node:所有 XNode 都以同一个 Queue 订阅,发往该 Subject 的消息只有一个 Node 能收到,也就是说接收消息是互斥的。
  • cn.xswitch.node.n:其中n = 1,2,3 ...,每个 Node 独立订阅,发往该 Subject 的消息只有一个能收到,也就是说,可以通过它定向发消息。

同理,所有 Ctrl 都订阅两个 Subject:

  • cn.xswitch.ctrl:以同一个 Queue 订阅,互斥接收。
  • cn.xswitch.ctrl.n:其中n = 1,2,3 ...,每个 Ctrl 独立订阅。

当有来话时,不失一般性,假设该来话分发到了node.1,则它会构造一个消息,通过 NATS 发送到cn.xswitch.ctrl上,右侧两个 Ctrl 都有可能收到该消息,但只有一个收到。不失一般性,假设ctrl.1收到了这个消息,这时候,它从消息的内容中取出node_uuid,知道了该消息是来自node.1,因此,它往node.1上回复一个XNode.Accept指令,表示它想接管这一路呼叫。node.1收到后,向ctrl.1回复200 OK,表示指令执行成功。至此,Node 和 Ctrl 通过 NATS 建立了一个“虚连接”(一对一对应关系),在这路通话生存期间都是如此。

时序图如下所示:

去话处理

去话逻辑与来话差不多,区别只是控制首先从 Ctrl 发起。假设ctrl.1发起呼叫,它首先将XNode.Dial请求发到cn.xswitch.node这个 Subject 上,不失一般性,假设node.1收到了这个请求,在后续的返回结果中,它会告诉ctrl.1这个呼叫是在node.1上处理了,因而也可以建立虚连接。

如果 Ctrl 想将 Dial 请求发到指定的 Node 上,也可以直接指定 Node 对应的 Subject,如cn.xswitch.node.1。至于 Ctrl 如何知道有哪几个 Node,参见下一节节点管理。

节点管理

  • 每个 Node 在上线时都会主动发Event.NodeRegister消息,Ctrl 侧可以订阅该消息以便感知节点上线。
  • 每个 Node 在下线时会发Event.NodeUnregister消息,请求注销。
  • 为了防止 Node 崩溃时来不及发送注销消息,Node 每 20 秒发一个Event.NodeUpdate消息,该消息可以做为心跳保活消息使用。此外,该消息还携带节点当前的活动 Channel 数以及负载信息。Ctrl 可以根据该信息向“负载最轻”的 Node 发送新任务。

注意:防止优先级反转。分布式系统的一个难点或者说一个误区就是无条件地往“负载最轻”的节点上发消息。假设我们做了一个自动外呼系统,有三个 Ctrl 和三个 Node。在某一时间,三个 Ctrl 都发现node.1负载最轻,然后分别向它发送了10个外呼任务,node.1就会在同一时刻收到 30 个外呼任务,有可能立即成为“负载最重”的节点,甚至会过载。

总之,分布式系统从来都不是很简单就可以实现的,在实际使用时需要考虑各种边界情况。在实际使用时,简单的轮循算法基于就可以做到“足够好”,如果能配合一些反馈补偿机制,就能做到“更好”。

这些消息默认会发到cn.xswitch.ctrl上,但是在 ctrl 侧存在多个实例的情况下,由于cn.xswitch.ctrl一般是队列方式订阅的,会导致一个消息只有其中一个节点能收到。因此,在这种情况下应该开启status-ctrl-subject参数,它的默认值是cn.xswitch.node.status

弹性伸缩

XCC 协议支持横向弹性伸缩。一般来说,伸比较容易,只需要加入一个新节点,然后启动就行了,缩需要一些额外的工作。

扩容

扩容一般包含以下步骤:

  • 启动新节点
  • 如果有来话,将来话分配到新节点(可以在 SIP Proxy 上自动或手动实现)
  • Ctrl 侧将收到新节点加入的消息(Event.NodeRegister),并持续收 Node 的心跳消息(Event.NodeUpdate
  • Ctrl 侧发送的新的呼叫请求,如果是发到以队列方式订阅的节点名上(如cn.xswitch.node),则会自动被分配到新节点上
  • Ctrl 也可以将新的呼叫请求按策略直接发送到新节点上

缩容

缩容比较复杂,但我们也尽量把它作得简单。

  • 准备缩容。
  • 如果有 SIP 代理,先在 SIP 代理上停止向该节点分发呼叫。
  • 在后台执行fsctl shutdown elegant优雅停机。

相关解释:

  • 优雅关机不会立即停止,而是会等待当前所有通话线束后再停止。
  • 考虑到安全原因,该命令只能在后台以管理员身份执行。
  • 优雅关机时,将拒绝后续的 SIP 呼入请求,但会继续允许外呼(如呼叫转接等)。
  • 优雅关机时,将取消所有以队列方式订阅的 Subject,因此,后许 Ctrl 侧的呼叫请求将不会被分配到该节点上。
    • 如果有多个相同的节点,则其他节点会负责相应的呼叫。
    • 如果只有这一个节点,很显然,就不能继续外呼了。
  • 优雅关机时,非队列方式订阅的 Subject 不会被取消,因此,Ctrl 侧可以继续向该节点发送呼叫控制命令。

Ctrl 侧行为

为配合优雅关机,Ctrl 侧应该确保:

  • 外呼的呼叫请求只发送以队列方式订阅的节点名上(如cn.switch.node),而不是具体的节点名(如cn.xswitch.node.<uuid>)。
  • 其他控制命令,如XNode.Status等,也本应该发送到特定的节点名上而不是以队列方式订阅的节点名上。

Ctrl 侧可以具有集群感知能力,下以纯外呼应用为例,有两种实现方式:

1)Ctrl 侧永远向以队列方式订阅的节点名发送请求。

  • Node 侧以队列方式订阅,如cn.xswitch.node,如果有多个 Node,消息请在多个节点间自动负载均衡。
  • Ctrl 侧无需要特殊处理。如果最后一个 Node 下载,Ctrl 侧的请求将会由于没有消费者而出错,或超时。

2) Ctrl 侧自己维护一个节点列表,根据负载情况向负载最轻的节点发送呼叫请求。

  • Node 上线时,会发送 Event.NodeRegister 消息,Ctrl 侧收到后将该节点加入列表。
  • Node 在存续期间,每 20 秒发送 Event.NodeUpdate 消息,Ctrl 侧收到后更新该节点的负载信息。
  • Node 发送的 Event.NodeUpdate 消息中,有rack参数,为节点负载,取值范围为0 ~ 100,数值越小,代表负载越轻。
  • Ctrl 可以向负载最轻的节点发送呼叫请求。但要注意防止在多线程环境中所有线程都发现这个节点负载最轻,然后都向它发送呼叫请求,从而导致负载过重。

3)Ctrl 侧可以使用上述两种方式混合的方式发送呼叫请求。

在一般情况下,方式 1)就能工作的很好,但在极端情况下,可能出现不分配不均衡的情况(或分配虽然均衡,但一个节点碰巧大部分通话的时长都比较长),这时候,可以结合方式 2) 进行补偿。

双机热备

集群需要比较多的物理机或虚拟机。在规模不大的情况下,可以使用双机热备,只需要两台机器。

双机热备与集群不同,双机热备是两台当一台用,浮动 IP 绑在主用 XSwitch 节点上对外提供服务。如下图:

双机热备模式下,两个节点配置如下:

  • switch.conf.xml中配置的switch_name要一致。
  • XCC 节点名要一致,配置node-name参数(该参数如果不配则可选)。
  • cn.xswitch.node这个如果使用,则只能用普通订阅模式,而不能用队列订阅。
  • 如果不使用cn.xswitch.node这个通用主题,可以使用cn.xswitch.node.$node-name主题
  • 将备机配置为standby模式(目前仅支持命令切换xcc <active|standby>),在standby模式下,XCC 正常订阅消息,但不做处理。
  • 总之,主备两台机器都会订阅cn.xswitch.node.$node-name,但只有主机会处理。

上图中,node-name被配成了x,会影响事件消息中的node_uuid值(也等于x),时时 XCC Node 节点全名为cn.xswitch.node.x。在应用中可以直接对这个 Subject 发消息。

需要说明的是,双机热备也需要 Ctrl 侧的支持。下面是一些切换逻辑:

总体逻辑

当发生切换时,系统会向cn.xswitch.ctrlcn.xswitch.ctrl.status(可配置)上发送Event.Recover事件消息,消息中包含成功恢复的 Channel 数量。

当发生切换时,已经桥接的呼叫会继续通话。正在放音的呼叫会中断(业务侧应该重放),已进入会议的呼叫可以继续会议。

单腿通话

来话

当发生主备切换时,Ctrl 将重新收到START消息,里面有recovered: true参数;Ctrl 应先执行Accept,然后执行后面的动作。在 IVR 类应用中,为了避免重放以前的放音,Ctrl 侧应该是有状态的。

去话

当发生主备切换时,只能通过Event.Recover事件知道已发生了切换,这时,所有已发送的请求命令将会超时(因为 XNode 无法回应消息),在超时后,Ctrl 侧可以重试上一个命令(这时可能还没有收到Event.Recover消息),或全局等待收到Event.Recover消息后再重试。

会议

会议应该可以正常恢复。

桥接的通话

桥接的通话理论上不受影响,但是,主备切换后会丢失一些状态,桥接后的处理可能会不正常(如转 IVR、转评价等)。

主备切换后,如果是已桥接的通话,会重新收到BRIDGE消息,并带有recovered: true标志。如果 Ctrl 侧依赖于该事件处理逻辑,则应该检查recovered标志。

桥接的通话在解除桥接时(如一方挂机),也能收到UNBRIDGE消息,并带有recovered: true标志。

小结

总之,双机热备状态下可以恢复处于“稳定”状态的通话,如桥接的通话或会议,但正在接续的电话可能无法正常恢复(不过由于没有正常接通,应该也可以接受)。

另外,为了防止从头重复放音(playbackspeaksay等),放音会中断,这时,业务侧应该感知到主备切换并重新放音。如果应用希望恢复后重放所有声音,则可以设置通道变量recovery_skip_announcement_type_applications=false

配置文件