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.ctrl
或cn.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
标志。
小结
总之,双机热备状态下可以恢复处于“稳定”状态的通话,如桥接的通话或会议,但正在接续的电话可能无法正常恢复(不过由于没有正常接通,应该也可以接受)。
另外,为了防止从头重复放音(playback
、speak
、say
等),放音会中断,这时,业务侧应该感知到主备切换并重新放音。如果应用希望恢复后重放所有声音,则可以设置通道变量recovery_skip_announcement_type_applications=false
。