攜程客服機(jī)器人ASR引擎的負(fù)載均衡實(shí)踐
作者簡(jiǎn)介
玉修,攜程技術(shù)專家,專注于電話音視頻通信、智能客服機(jī)器人等領(lǐng)域。
一、前言
攜程擁有龐大的呼叫中心,涉及上萬(wàn)客服人員,覆蓋機(jī)票、酒店、火車票、度假等產(chǎn)線的售前售后業(yè)務(wù),每天的電話業(yè)務(wù)量超百萬(wàn)通。近年來(lái),通信技術(shù)、人工智能技術(shù)和智能終端等都在不斷革新,我們也一直在思考如何去做更智能化、自動(dòng)化的呼叫中心,為未來(lái)海量的客戶需求提供穩(wěn)定和優(yōu)質(zhì)的服務(wù)。
攜程呼叫中心的智能化包含多個(gè)方面:
- 用戶側(cè):智能在線聊天機(jī)器人(IM)、智能語(yǔ)音導(dǎo)航/智能語(yǔ)音客服機(jī)器人/智能邀評(píng)插件(電話)
- 客服側(cè):智能工單和排班系統(tǒng)、智能質(zhì)檢系統(tǒng)、智能客戶資源管理系統(tǒng)、服務(wù)渠道智能化
- 系統(tǒng)基建:平臺(tái)部署智能化、業(yè)務(wù)監(jiān)控智能化
本文旨在探討攜程實(shí)現(xiàn)呼叫中心電話智能語(yǔ)音客服機(jī)器人的基建服務(wù)——語(yǔ)音識(shí)別服務(wù)(即ASR)的負(fù)責(zé)均衡的演進(jìn)歷程,以及最佳實(shí)踐。
二、背景
隨著人工智能技術(shù)的發(fā)展,在呼叫中心業(yè)務(wù)中,傳統(tǒng)的 IVR(交互式語(yǔ)音應(yīng)答)按鍵導(dǎo)航模式逐步向IVR智能客服機(jī)器人轉(zhuǎn)變(客戶與IVR機(jī)器人進(jìn)行語(yǔ)音對(duì)話的方式來(lái)辦理業(yè)務(wù))。攜程呼叫中心系統(tǒng)下的IVR業(yè)務(wù)也在不斷地向電話智能語(yǔ)音機(jī)器人轉(zhuǎn)變,目前攜程酒店、機(jī)票、火車票的國(guó)內(nèi)IVR呼入業(yè)務(wù),以及IBU國(guó)際英語(yǔ)機(jī)票的IVR呼入業(yè)務(wù),已經(jīng)全部由電話智能語(yǔ)音機(jī)器人來(lái)為客戶提供自助服務(wù)。
下圖是國(guó)內(nèi)酒店業(yè)務(wù)場(chǎng)景中,客戶撥打攜程服務(wù)熱線后,客戶與電話機(jī)器人通過(guò)語(yǔ)音溝通的記錄??梢钥闯?,客戶順利完成了“取消訂單”的業(yè)務(wù)辦理。
智能客服機(jī)器人要想實(shí)現(xiàn)上圖的交互效果,離不開(kāi)ASR服務(wù)的使用,以及功能完善且穩(wěn)定的呼叫中心系統(tǒng)的支撐。攜程呼叫中心的整個(gè)平臺(tái)依賴了眾多組件,底層包括CC-Gateway(語(yǔ)音網(wǎng)關(guān))、SBC(會(huì)話邊際控制服務(wù))、REG(分機(jī)注冊(cè)服務(wù))、SM(會(huì)話管理服務(wù))、RS(呼叫路由服務(wù))、CM(呼叫管理服務(wù),基于FreeSWITCH)、ASR(語(yǔ)音識(shí)別服務(wù))等系列服務(wù)。
下圖是攜程呼叫中心,客戶呼入到智能客服機(jī)器人場(chǎng)景,進(jìn)行語(yǔ)音自助業(yè)務(wù)辦理時(shí)所涉及的部分核心組件架構(gòu)圖。
從上圖可以看出,攜程呼叫中心系統(tǒng)底層(如FreeSWITCH)調(diào)用實(shí)時(shí)ASR完成語(yǔ)音識(shí)別是基于MRCP協(xié)議來(lái)實(shí)現(xiàn)的。我們將上圖中涉及ASR使用部分的組件交互進(jìn)行簡(jiǎn)化,得出其包含下面3種組件:
- MRCP客戶端:發(fā)送RTP和SIP/MRCP的發(fā)起者,如FreeSWITCH(下文簡(jiǎn)稱FS
- MRCP服務(wù)端:處理MRCP/SIP信令,接收并轉(zhuǎn)發(fā)RTP
- ASR引擎 :解析RTP,將語(yǔ)音轉(zhuǎn)換成文本,并返回給MRCP Server
可以發(fā)現(xiàn),對(duì)于呼叫中心ASR調(diào)用者而言,只需要關(guān)心怎么對(duì)接MRCP Server即可,無(wú)需關(guān)注ASR Engine部分。在實(shí)際使用過(guò)程中,如果你采購(gòu)第三方ASR系統(tǒng)進(jìn)行私有化部署的話(比如科大訊飛ASR、百度ASR),通常MRCP Server和ASR Engine是打包在一起,并部署在同一機(jī)器上。
但無(wú)論你采購(gòu)哪家的ASR產(chǎn)品進(jìn)行集群化部署,廠商都沒(méi)有提供ASR的負(fù)載均衡解決方案,需要客戶自行解決。攜程為了讓ASR引擎具備更高的可用性,采用了多集群、多IDC、多供應(yīng)商的ASR產(chǎn)品(如攜程自研、百度、阿里、微軟等)來(lái)提供服務(wù)。針對(duì)這么多的集群和ASR產(chǎn)品,設(shè)計(jì)出一個(gè)調(diào)度策略和負(fù)載均衡方案來(lái)合理有效地利用ASR資源就變得極為重要了。
三、方案探索
目標(biāo)已經(jīng)理清,接下來(lái)深入分析調(diào)用ASR涉及的技術(shù)點(diǎn),看看如何實(shí)現(xiàn)目標(biāo)。
調(diào)用MRCP Server包含SIP(UDP/TCP)、MRCP(TCP)、RTP(UDP)三部分,MRCP和RTP的服務(wù)端地址是由SIP INVITE的響應(yīng) 200 OK中SDP指定(如下圖),所以只要完成對(duì)SIP的負(fù)載均衡就能解決另外兩個(gè),要給MRCP Server做負(fù)載均衡就變成了給 SIP(UDP/TCP)做負(fù)載均衡。
我們期望負(fù)載均衡的效果是:只要MRCP-Server服務(wù)端集群下有多臺(tái)機(jī)器,即使客戶端只有一個(gè),負(fù)載均衡設(shè)備也能將請(qǐng)求均勻分發(fā)給服務(wù)端的每一個(gè)成員。
常規(guī)的負(fù)載均衡方案,無(wú)外乎基于硬件負(fù)載均衡設(shè)備實(shí)現(xiàn),如A10(即AX)、F5、NetScaler等;或者基于軟負(fù)載實(shí)現(xiàn),如LVS、Nginx等。但這些常規(guī)方法,都無(wú)法真正做到給MRCP Server實(shí)現(xiàn)負(fù)載均衡。
攜程的基建服務(wù)中,恰好有AX、Netscaler、TDLB相關(guān)負(fù)載均衡服務(wù),所以我們基于這幾種基建服務(wù)都進(jìn)行過(guò)驗(yàn)證性測(cè)試,可惜最終效果都不盡人意。
以FS作為MRCP Client,AX作為負(fù)載均衡設(shè)備為例。假如只有1臺(tái)FS設(shè)備,1臺(tái)AX設(shè)備,4臺(tái)MRCP-Server設(shè)備。從FS依次發(fā)起4次請(qǐng)求,或者同時(shí)發(fā)起4次請(qǐng)求,最終使ASR駐留并發(fā)達(dá)到4個(gè)。
上圖是左側(cè)“賣家秀”是我們想要達(dá)到的預(yù)期效果,右側(cè)“買家秀”是我們實(shí)驗(yàn)所得的實(shí)際效果,所有的請(qǐng)求都被分配到了同一臺(tái)MRCP-Server機(jī)器上,沒(méi)能均勻的分配給集群下的各成員。理想很豐滿,但現(xiàn)實(shí)太骨感。
那么,AX設(shè)備沒(méi)能做到均勻分配的原因是什么呢?FS基于AX來(lái)給MRCP做負(fù)載又存在什么弊端呢?
首先,對(duì)于FS和AX設(shè)備相對(duì)固定的情況下,SIP請(qǐng)求的IP四元組(Source IP、Source port、Destination IP、Destination port)不會(huì)發(fā)生變化,因?yàn)镕S對(duì)接MRCP Server時(shí),會(huì)在MRCP配置文件中指定客戶端和服務(wù)端的IP/Port,所以AX每次分配給FS的MRCP Server都是同一臺(tái),這顯然不符合負(fù)載均衡的預(yù)期。
其次,電話場(chǎng)景,在收到200 OK后,可能長(zhǎng)達(dá)半小時(shí)不會(huì)再有SIP交互,期間的MRCP和RTP都是MRCP-Client和MRCP-Server之間進(jìn)行直連交互,根本不經(jīng)過(guò)AX設(shè)備,而AX設(shè)備默認(rèn)的會(huì)話保持時(shí)長(zhǎng)為120秒,超過(guò)這個(gè)時(shí)間,SIP通道會(huì)被AX關(guān)閉,這會(huì)導(dǎo)致后續(xù)的SIP無(wú)法送達(dá)。
既然此路不通,我們要考慮其他解決方案,經(jīng)過(guò)深入研究和各種嘗試,認(rèn)為下面這兩種解決方案比較適合,但各有優(yōu)缺點(diǎn):
- 方案A:通過(guò)FreeSWITCH的distributor模塊實(shí)現(xiàn)
- 方案B:通過(guò)OpenSIPs實(shí)現(xiàn)
優(yōu)點(diǎn) | 缺點(diǎn) | |
方案A | 1、無(wú)需依賴第三方負(fù)載均衡組件 | 1、配置繁瑣復(fù)雜 2、MRCP Server節(jié)點(diǎn)增刪,都需要調(diào)整FS配置文件,而且得在無(wú)ASR業(yè)務(wù)時(shí),才能加載生效 3、端口數(shù)量消耗大(每個(gè)MRCP Server都需要單獨(dú)分配端口段) 4、負(fù)載均衡策略相對(duì)單一,只支持按比例分配。而且單機(jī)所占有的最小比例不能小于0 |
方案B | 1、配置簡(jiǎn)單 2、MRCP Server節(jié)點(diǎn)增刪,只需調(diào)整OpenSIPs的DB即可,有ASR調(diào)用時(shí),也可更改,實(shí)時(shí)生效 3、端口數(shù)量消耗小(只需要配置一個(gè)MRCP Profile文件,多個(gè)MRCP Server共用端口段) 4、負(fù)載均衡方案多種多樣,支持按比例、輪詢等多種方式 | 1、需要依賴第三方負(fù)載均衡組件OpenSIPs |
我們最終將兩個(gè)方案結(jié)合,來(lái)實(shí)現(xiàn)負(fù)載均衡,F(xiàn)S上使用distributor模塊來(lái)實(shí)現(xiàn)對(duì) OpenSIPs做負(fù)載均衡,OpenSIPs上再對(duì)MRCP-Server做負(fù)載均衡,效果如下:
1)FS、OpenSIPs、MRCP-Server三個(gè)組件之間實(shí)現(xiàn)了IDC優(yōu)先就近訪問(wèn)(如上所述,F(xiàn)S未能做到100%的就近訪問(wèn))。
2)當(dāng)相同IDC下的下游服務(wù)全部不可用時(shí),則自動(dòng)將流量分配到其他IDC下,如下圖,IDC-A 下 FS的ASR請(qǐng)求,優(yōu)先請(qǐng)求到 IDC-A 的OpenSIPs,然后IDC-A的OpenSIPs再根據(jù)分配策略,將請(qǐng)求優(yōu)先分配給 IDC-A下的MRCP-Server,如果IDC-A下的MRCP-Server全宕機(jī)了,會(huì)自動(dòng)分配給IDC-B下的MRCP-Server。
3)負(fù)載均衡服務(wù)可自動(dòng)檢測(cè)下游集群各成員的狀態(tài),當(dāng)某成員服務(wù)不可用時(shí)自動(dòng)拉出,服務(wù)狀態(tài)恢復(fù)后,再自動(dòng)拉入。FS和OpenSIPs都是通過(guò)發(fā)送SIP OPTION 來(lái)自動(dòng)探測(cè)下游服務(wù)的狀態(tài)。
四、方案實(shí)踐
接下來(lái),我們?cè)敿?xì)看看每種方案的具體實(shí)現(xiàn)方式。以下方案運(yùn)行環(huán)境為:CentOS 7.6、FreeSWITCH 1.6.20、OpenSIPs 2.4.2。
本篇文章中,我們不詳細(xì)講解每種方式的實(shí)現(xiàn)原理,只介紹解決方法,有興趣的同學(xué)可以自行學(xué)習(xí)FS和OpenSIPs的相關(guān)功能點(diǎn),這里給出幾個(gè)鏈接:
- mod_unimrcp
- mod_distributor
- mod_dptools: play_and_detect_speech
- Load Balancer Module
- Dispatcher Module
- Dialplan Module
假設(shè)我們只有一臺(tái)FS作為MRCP 客戶端,并且MRCP Server 集群中有兩臺(tái)服務(wù)器,分別是 mrcp1 和 mrcp2,希望FS針對(duì)每一通電話執(zhí)行ASR命令時(shí),請(qǐng)求可均勻分配給兩個(gè)MRCP Server。
4.1 基于FS的distributor模塊實(shí)現(xiàn)MRCP Server的LB
該方案的核心思路如下:
1)FS直接與MRCP Server對(duì)接,為MRCP Server集群下每一個(gè)成員配置一個(gè)profile
2)將MRCP Server集群下的所有成員配置成 FS 的網(wǎng)關(guān),并開(kāi)啟網(wǎng)關(guān)的SIP OPTION探測(cè)功能,同時(shí)確保gateway的name要與mrcp_profile文件中profile的name一致
3)通過(guò)FS的distributor 模塊為這些MRCP網(wǎng)關(guān)配置負(fù)載均衡策略
4)最后,實(shí)際執(zhí)行ASR命令時(shí),先通過(guò) expand eval ${distributor mrcp ${sofia profile external gwlist down}} 負(fù)載均衡分配得到一個(gè)可用的 MRCP Server Profile的名稱,然后用該MRCP Profile的名稱作為FS play_and_detect_speech ASR命令的參數(shù)即可。
詳細(xì)的配置步驟如下所示:
第一步:FS與MRCP Server對(duì)接
在 /usr/local/freeswitch/conf/mrcp_profiles/下配置FS對(duì)接MRCP Server的文件
tree /usr/local/freeswitch/conf/mrcp_profiles
├── mrcp1.xml
└── mrcp2.xml
下面只給出mrcp1.xml的部分核心配置,只需要確保mrcp1.xml和mrcp2.xml里client-port、rtp-port-min、rtp-port-max配置不同即可。
這里可以看到,如果MRCP Server集群有很多機(jī)器,那么這里的RTP端口段可能不夠用,一通電話進(jìn)行ASR解析需要2個(gè)端口,一個(gè)用來(lái)傳輸RTP、一個(gè)傳RTCP。
<include>
<profile name="mrcp1" versinotallow="2"> 【每個(gè)MRCP Server這里配置的名稱都不一樣,但一定有一個(gè)相同名稱的網(wǎng)關(guān)】
<param name="client-ip" value="192.168.1.99"/>
<param name="client-port" value="client-port-1"/>
<param name="server-ip" value="server-ip-1"/>
<param name="server-port" value="8060"/>
<param name="sip-transport" value="tcp"/> 【也可以是UDP哦】
<param name="rtp-ip" value="192.168.1.99"/>
<param name="rtp-port-min" value="min-port-1"/>
<param name="rtp-port-max" value="max-port-1"/>
<param name="ua-name" value="FreeSWITCH"/>
</profile>
</include>
第二步:配置FS網(wǎng)關(guān)
在 /usr/local/freeswitch/conf/sip_profiles/external/下配置網(wǎng)關(guān)對(duì)接文件
/usr/local/freeswitch/conf/sip_profiles/external
├── mrcp1.xml
└── mrcp2.xml
mrcp1.xml 的詳細(xì)配置如下:
<gateway name="mrcp1"> 【gateway的name要與mrcp_profile文件中profile的name一致,或可以按照某種規(guī)則轉(zhuǎn)換】
<param name="username" value=""/>
<param name="proxy" value="mrcp1-server-ip:8060"/> 【當(dāng)然這里端口可能是其他值】
<param name="realm" value="mrcp1-server-ip"/>
<param name="register" value="false"/>
<param name="rtp-autofix-timing" value="false"/>
<param name="caller-id-in-from" value="true"/>
<param name="ping" value="10"/> 【FS給proxy對(duì)應(yīng)地址發(fā)送探測(cè)的周期】
<param name="ping-max" value="5"/>
<param name="ping-min" value="2"/>
</gateway>
第三步:配置FS的distributor模塊
vim /usr/local/freeswitch/conf/autoload_configs/distributor.conf.xml
<configuration name="distributor.conf" descriptinotallow="Distributor Configuration">
<lists>
<list name="mrcp"> 【權(quán)重配置成一樣,相當(dāng)于兩個(gè)MCRP server 按 1:1 分配】
<node name="mrcp1" weight="5"/> 【node name值與sip gateway 名稱相同】
<node name="mrcp2" weight="5"/>
</list>
</lists>
</configuration>
最后,來(lái)看看使用效果。按照上述配置,將mrcp2服務(wù)宕機(jī)后,執(zhí)行負(fù)載均衡的效果如下:
freeswitch@LPT0596> sofia profile external gwlist down 【獲取宕機(jī)的網(wǎng)關(guān)】
mrcp2
freeswitch@LPT0596> expand eval ${distributor mrcp ${sofia profile external gwlist down}} 【將宕機(jī)的網(wǎng)關(guān)排除在外后,獲取分配的SM節(jié)點(diǎn)】
mrcp1
freeswitch@LPT0596> expand eval ${distributor mrcp ${sofia profile external gwlist down}} 【如果mrcp2沒(méi)有宕機(jī),這里將返回mrcp2】
mrcp1
使用得到的MRCP Server Profile名稱執(zhí)行ASR命令:play_and_detect_speech
/usr/local/freeswitch/sounds/ivr_prompt_voice.wav detect:unimrcp:mrcp1
{start-input-timers=false,no-input-timeout=10000,recognition-timeout=10000}ahlt_ats
其他FS相關(guān)命令:
- reload mod_unimrcp : 修改FS與MRCP server對(duì)接的文件后,重新加載生效【只有當(dāng)前沒(méi)有正在執(zhí)行的ASR操作時(shí),才能重加載】
- sofia profile external rescan : 重新加載FS的網(wǎng)關(guān)配置
4.2 基于OpenSIPs實(shí)現(xiàn)MRCP Server的LB
4.2.1 核心思路
FS不直接與MRCP Server對(duì)接,而是與OpenSIPs進(jìn)行對(duì)接。對(duì)接方式是把OpenSIPs配置成一個(gè)MRCP profile,文件中的server-ip 和 server-port 地址配置成OpenSIPS 的服務(wù)地址即可。
FS執(zhí)行ASR命令時(shí),先將SIP請(qǐng)求發(fā)送給OpenSIPs,再由OpenSIPs負(fù)載均衡到MRCP Server集群中的成員,交互的時(shí)序圖如下:
4.2.2 方案分析
通過(guò)OpenSIPs來(lái)實(shí)現(xiàn)對(duì)MRCP的負(fù)載均衡需要解決下面幾個(gè)問(wèn)題:
問(wèn)題1、如何判斷收到的INVITE請(qǐng)求是要執(zhí)行ASR命令,還是普通呼叫命令?
問(wèn)題2、知道是執(zhí)行ASR命令后,如何選擇MRCP Server,進(jìn)行分配?
問(wèn)題3、如果有多套MRCP Server集群,比如一套百度MRCP,一套阿里MRCP,客戶端希望能指定引擎使用,該如何解決?
既然已經(jīng)明確了問(wèn)題點(diǎn),那咱就各個(gè)擊破即可,下面是各問(wèn)題點(diǎn)的解決方法:
- 問(wèn)題1的解決方法
我們來(lái)看一條FS發(fā)送給OpenSIPs,請(qǐng)求執(zhí)行MRCP負(fù)載均衡的SIP INVITE信息,其中 192.168.1.99是FS,192.168.1.18是OpenSIPs。
INVITE sip:192.168.1.18:5070 SIP/2.0
Via: SIP/2.0/UDP 192.168.1.99:5102;rport;branch=z9hG4bKQ21yZS46ytrgF
Max-Forwards: 70
From: <sip:192.168.1.99:5102>;tag=4B8SvQe66FNvc
To: <sip:192.168.1.18:5070>
Call-ID: ed9f5f6b-0673-123b-199a-fa163e72d95e
CSeq: 47770741 INVITE
Contact: <sip:192.168.1.99:5102>
User-Agent: FreeSWITCH
Allow: INVITE, ACK, BYE, CANCEL, OPTIONS, PRACK, MESSAGE, SUBSCRIBE, NOTIFY, REFER, UPDATE
Supported: timer, 100rel
Content-Type: application/sdp
Content-Disposition: session
Content-Length: 306
v=0
o=FreeSWITCH 2480643166757753319 6144298267054033408 IN IP4 192.168.1.99
s=-
c=IN IP4 192.168.1.99
t=0 0
m=application 9 TCP/MRCPv2 1
a=setup:active
a=connection:existing
a=resource:speechrecog
a=cmid:1
m=audio 31799 RTP/AVP 0 8
a=rtpmap:0 PCMU/8000
a=rtpmap:8 PCMA/8000
a=sendonly
a=mid:1
從上面信令可以看到,F(xiàn)S發(fā)起的INVITE中,沒(méi)有主被叫號(hào)碼信息,只有FS和OpenSIPs的IP和端口信息。如果我們的OpenSIPs只用來(lái)給MRCP Server做負(fù)載均衡,那么就很簡(jiǎn)單,收到INVITE請(qǐng)求,都認(rèn)為是請(qǐng)求執(zhí)行ASR命令,分配給MRCP Server即可。但是,OpenSIPs只給MRCP Server做負(fù)載豈不是大材小用了!
所以,實(shí)際我們不會(huì)這樣使用,OpenSIPs通常還會(huì)給其他呼叫中心組件做負(fù)載均衡,比如給FreeSWITCH、語(yǔ)音網(wǎng)關(guān)、分機(jī)注冊(cè)服務(wù)等做LB。這樣OpenSIPs就會(huì)收到來(lái)自各種組件的SIP INVITE請(qǐng)求。那么該如何判斷收到的 INVITE 是要執(zhí)行ASR命令,還是要做其他業(yè)務(wù)呢?
常規(guī)思路,自然是OpenSIPs分析INVITE的SIP消息頭,從中進(jìn)行判斷。可是由于FS的mod_unimrcp模塊的限制,F(xiàn)S執(zhí)行ASR命令時(shí),發(fā)送的SIP INVITE里不支持增加自定義SIP消息頭,所以只能從標(biāo)準(zhǔn) SIP 消息頭中進(jìn)行挖掘。
- 根據(jù)INVITE請(qǐng)求的源IP:不可行,因?yàn)橥粋€(gè)源IP可能發(fā)起多種請(qǐng)求的INVITE,比如FS可能是請(qǐng)求執(zhí)行ASR,也可能是請(qǐng)求呼叫手機(jī);此外,即使可行,源IP也不方便維護(hù)。
- 根據(jù)INVITE請(qǐng)求的目的IP:不可行,所有INVITE請(qǐng)求的該值都一樣
- 根據(jù)INVITE請(qǐng)求的User-Agent頭:可行,OpenSIPs通過(guò)$ua就能獲取該值。雖然不能針對(duì)每次INVITE自定義不同的UA頭,但FS對(duì)接MRCP Server的Profile中可以指定一個(gè)統(tǒng)一的User-Agent頭,默認(rèn)是FreeSWITCH。
- 根據(jù)INVITE請(qǐng)求SDP信息中的‘m’頭:可行,OpenSIPs通過(guò)$(rb{sdp.line,m})就能獲取該值。如 上面報(bào)文中“m=application 9 TCP/MRCPv2 1” 里面有MRCPv2,可根據(jù)這個(gè)判斷是執(zhí)行ASR。
建議使用User-Agent頭進(jìn)行區(qū)分,取值方便,效率高。所以,F(xiàn)S對(duì)接OpenSIPs時(shí),配置的MRCP Profile時(shí),指定一個(gè)特別的User-Agent,比如叫ASR_MRCP_CLIENT_FS,OpenSIPs收到INVITE請(qǐng)求,優(yōu)先判斷UA信息,如果是ASR_MRCP_CLIENT_FS,那么就是要執(zhí)行ASR命令。
- 問(wèn)題2的解決方法
可以使用OpenSIPS的load_balancer 或 dispatcher 模塊來(lái)實(shí)現(xiàn)對(duì) MRCP Server 服務(wù)端的負(fù)載均衡,兩種方式的特點(diǎn)如下:
如果MRCP-Server集群下的成員可支持的并發(fā)數(shù)不一樣,想做到哪臺(tái)機(jī)器剩余的可用資源最多,就優(yōu)先分配給誰(shuí),當(dāng)各成員可用資源數(shù)相同時(shí),在輪訓(xùn)分配,那么可以使用 load_balancer 模塊來(lái)實(shí)現(xiàn)負(fù)載均衡;
如果MRCP-Server集群下的成員可支持的并發(fā)數(shù)完全一樣,無(wú)差別,那么建議使用dispatcher模塊來(lái)試想負(fù)載均衡,可以做到均勻的將請(qǐng)求分配給每一臺(tái)服務(wù)器。
優(yōu)點(diǎn) | 缺點(diǎn) | |
load_balancer | 可控制每個(gè)MRCP Server的最大并發(fā)量 支持監(jiān)控分配給每個(gè)MRCP Server的實(shí)時(shí)并發(fā)量 | 分配策略單一:只支持空閑優(yōu)先策略分配和按比例分配兩種策略,無(wú)法支持記憶輪訓(xùn),這就導(dǎo)致但MRCP Server集群新增成員時(shí),會(huì)將流量全部分配給新增的機(jī)器,這種情況,新機(jī)器的突增壓力可能較大 |
dispatcher | 分配策略多種多樣:如支持記憶輪訓(xùn)、Hash分配等 | 不能控制每個(gè)MRCP Server的最大并發(fā)量,話務(wù)量暴漲時(shí),存在雪崩隱患 不能監(jiān)控分配給每個(gè)MRCP Server的實(shí)時(shí)并發(fā)量(但可以自行通過(guò)OpenSIPs其他模塊實(shí)現(xiàn)) |
- 問(wèn)題3的解決方法
在FS上為每一套MRCP Server集群,配置一個(gè)MRCP Profile并且都指向OpenSIPs,但User-Agent的值配置成不一樣,OpenSIPs根據(jù)UA的不同,來(lái)選擇該給哪個(gè)集群做LB。
baidu_mrcp_lb.xml 下面只給出特有配置,其他配置被省略了
<include>
<profile name="baidu_mrcp_lb" versinotallow="2"> 【阿里的配置,name為ali_mrcp_lb】
<param name="server-ip" value="opensips-ip"/>
<param name="ua-name" value="ASR_MRCP_CLIENT_FS_BAIDU"/> 【阿里的配置,ua-name為ASR_MRCP_CLIENT_FS_ALI】
<param name="sdp-origin" value="FS_MRCP"/>
</profile>
</include>
OpenSIPs給MRCP Server做負(fù)載均衡的處理流程圖如下:依賴dialplan模塊進(jìn)行選擇具體通過(guò)哪個(gè)模塊來(lái)執(zhí)行LB。
4.2.3 具體實(shí)現(xiàn)
如果OpenSIPs本身也是集群化部署,那么可以通過(guò)本文3.1章節(jié)的方法實(shí)現(xiàn)對(duì)OpenSIPs的負(fù)載均衡。
下面代碼涉及OpenSIPs對(duì)dialplan、dispatcher、load_balancer幾個(gè)模塊的使用,本文不講解這部分的使用方法。
- 數(shù)據(jù)庫(kù)初始化
說(shuō)明:
1)下方配置了百度、阿里兩個(gè)MRCP Server集群,并且每個(gè)集群都部署在了兩個(gè)IDC(IDC_A和IDC_B)
2)OpenSIPs根據(jù)dialplan撥號(hào)方案來(lái)為阿里和百度選擇負(fù)載均衡的方式,dialplan表中字段“attrs”配置邏輯是:[MRCP集群第一路由的集群ID:負(fù)載均衡實(shí)現(xiàn)方式:集群名稱],如“90:DS:ASR_MRCP_SERVER_CTRIP_ALI”代表,阿里MRCP第一路由的集群ID是90,采用dispacher模塊實(shí)現(xiàn)LB;"90:DS:ASR_MRCP_SERVER_CTRIP_ALI”代表,百度MRCP第一路由的集群ID是91,采用load_balancer模塊實(shí)現(xiàn)LB
3)無(wú)論是dispacher,還是load_balancer,都配置了單IDC下負(fù)載均衡的基礎(chǔ)上,增加了逃生路由的功能。集群ID為 90/91代表第一路由,10090/10091代表第二路由
dialplan的attrs字段被賦予了特殊用途
INSERT INTO `dialplan`(`dpid`,`pr`,`match_op`,`match_exp`,`match_flags`,`subst_exp`,`repl_exp`,`timerec`,`disabled`,`attrs`) VALUES
(90,1000,1,'^ASR_MRCP_CLIENT_CTRIP_FS_ALI$',0,NULL,NULL,NULL,0,'90:DS:ASR_MRCP_SERVER_CTRIP_ALI'),
(90,1000,1,'^ASR_MRCP_CLIENT_CTRIP_FS_BAIDU$',0,NULL,NULL,NULL,0,'91:LB:ASR_MRCP_SERVER_BAIDU');
dispatcher的attrs字段沒(méi)有實(shí)際作用
INSERT INTO `dispatcher` (`setid`, `destination`, `state`, `weight`, `priority`, `attrs`, `description`) VALUES
(90, 'sip:192.168.1.190:8060', 0, 1, 100, 'pstn=100', 'IDC_A:ASR_MRCP_SEVER_ALI'),
(90, 'sip:192.168.1.191:8060', 0, 1, 100, 'pstn=100', 'IDC_A:ASR_MRCP_SEVER_ALI'),
(10090, 'sip:192.168.2.198:8060', 0, 1, 100, 'pstn=100', 'IDC_B:ASR_MRCP_SEVER_ALI');
load_balancer的resources字段可以控制最大并發(fā)數(shù)
INSERT INTO `load_balancer`(`group_id`,`dst_uri`,`resources`,`probe_mode`,`description`) VALUES
(91,'sip:192.168.1.180:8060','pstn=50',2,'IDC_A:ASR_MRCP_SEVER_BAIDU'),
(91,'sip:192.168.1.181:8060','pstn=50',2,'IDC_A:ASR_MRCP_SEVER_BAIDU'),
(10091,'sip:192.168.2.188:8060','pstn=50',2,'IDC_B:ASR_MRCP_SEVER_BAIDU');
配置好后,可查看集群內(nèi)MRCP成員的狀態(tài):
sudo /usr/local/opensips/sbin/opensipsctl fifo lb_list
Destination:: sip:192.168.1.180:8060 id=1 group=90 enabled=yes auto-reenable=on
Resources::
Resource:: pstn max=50 load=50
Destination:: sip:192.168.1.180:8060 id=2 group=90 enabled=no auto-reenable=on
Resources::
Resource:: pstn max=50 load=0
Destination:: sip:192.168.2.188:8060 id=3 group=90 enabled=yes auto-reenable=on
Resources::
Resource:: pstn max=50 load=10
sudo /usr/local/opensips/sbin/opensipsctl fifo ds_list
PARTITION:: default
SET:: 90
URI:: sip:192.168.1.190:5080 state=Active first_hit_counter=8
attr:: pstn=500
URI:: sip:192.168.1.191:5080 state=Inactive first_hit_counter=0
attr:: pstn=500
SET:: 10090
URI:: sip:192.168.2.198:8060 state=Active first_hit_counter=0
attr:: pstn=100
- OpenSIPs代碼實(shí)現(xiàn)
route{
#省略N多代碼...
# check sip INVITE message source ip and port
if (is_method("INVITE")) {
xlog("ua = $ua , callid = $ci, fu = $fu , tu = $tu , ru = $ru , du =$du src:$si, $(rb{sdp.line,m}))");
$var(dlgPingTag) = "Pp";
if ( $ua == "ASR_MRCP_CLIENT_FS" ) { #to_asr_mrcp_server
$var(dlgPingTag) = ""; # ASR 的SIP通道不能做OPTION探測(cè)
}
if ( !create_dialog("$var(dlgPingTag)") ) {
route(PRINT_LOG, "create_dialog error : Internal Server Error");
send_reply("500","SM Internal Server Error");
exit();
}
if ( $ua =~ "^ASR_MRCP_CLIENT_CTRIP_FS*" ) { #to_asr_mrcp_server 【需要修改FS mrcp client配置文件,<param name="ua-name" value="ASR_MRCP_CLIENT_FS..."/>】
if ( dp_translate("90", "$ua/$avp(dest)", "$var(attrs)") ) { #撥號(hào)方案判斷
route(exeLb, $(var(attrs){s.int}), "pstn", $(var(attrs){s.select, 1,:}), $(var(attrs){s.select, 2,:}));
}
} else { #處理其他呼叫類型,如呼叫手機(jī)等
#省略N多代碼...
}
}
exit();
}
#usage : route(exeLb, lb_group_id, resource_type, node_type, lb_method)
#e.g. route(exeLb, 90, "pstn", "ASR_MRCP_SERVER_ALI", "LB")
#e.g. route(exeLb, 90, "pstn", "ASR_MRCP_SERVER_ALI", "DS")
route[exeLb]{
$var(lb_group_id) = $param(1);
$var(lb_group_id_bak) = $param(1) + 10000;
$var(resource_type) = $param(2);
$var(node_type) = $param(3);
$var(lb_method) = $param(4);
xlog("[$fU->$rU] Route $rU to '$var(node_type)' by load_balancer group_id : '$var(lb_group_id)' [back_group_id:'$var(lb_group_id_bak)'], resource_type : '$var(resource_type)', node_type : '$var(node_type)' [ci:$ci] [xcid:$hdr(X-CID)]");
$var(lbRst) = 0;
if( $var(lb_method) == "DS" ) {
$var(lbRst) = ds_select_dst("$var(lb_group_id)", "4");
if($var(lbRst) == -1) {
xlog("[exeLb4CM] [$fU->$rU] Failed --->lbRst=$var(lbRst) Route $rU to '$var(node_type)' by dispatcher group_id : '$var(lb_group_id)', resource_type : '$var(resource_type)' [ci:$ci]");
$var(lbRst) = ds_select_dst("$var(lb_group_id_bak)", "4");
if(!$var(lbRst)) {
xlog("[exeLb4CM] [$fU->$rU] Failed ===>lbRst=$var(lbRst) Route $rU to '$var(node_type)' by dispatcher [back_group_id:'$var(lb_group_id_bak)'], resource_type : '$var(resource_type)' [ci:$ci]");
}
}
} else {
$var(lbRst) = lb_start_or_next("$var(lb_group_id)", "$var(resource_type)", "s");
if( $var(lbRst) < 0) {
xlog("[$fU->$rU] Failed --->lbRst=$var(lbRst) Route $rU to '$var(node_type)' by load_balancer group_id : '$var(lb_group_id)', resource_type : '$var(resource_type)' [ci:$ci]");
$var(lbRst) = lb_start("$var(lb_group_id_bak)", "$var(resource_type)", "s");
if( $var(lbRst) < 0) {
xlog("[$fU->$rU] Failed ===>lbRst=$var(lbRst) Route $rU to '$var(node_type)' by load_balancer [back_group_id:'$var(lb_group_id_bak)'], resource_type : '$var(resource_type)' [ci:$ci]");
}
}
}
if ( $var(lbRst) > 0) {
if ( $rU == null ) { #對(duì)于FS 發(fā)起的 MRCP INVITE 請(qǐng)求, $rU 為 null, 而不設(shè)置 $rU 將導(dǎo)致 Load balancer 失敗,所以需要初始化一個(gè)值
xlog("[$fU->$rU] rU is null, then initialize to 'Null2Sm' [ci:$ci] [xcid:$hdr(X-CID)]");
#$rU = "Null2SM";
$ru = "sip:" + $(du{uri.host}) + ":" + $dp;
} else {
$ru = "sip:" + $rU + "@" + $(du{uri.host}) + ":" + $dp;
}
xlog("[$fU->$rU] Route to '$var(node_type)' --> [$du] [ci:$ci] [xcid:$hdr(X-CID)]");
route(relay);
} else {
xlog("[$fU->$rU] No available '$var(node_type)' now [ci:$ci] [xcid:$hdr(X-CID)]");
t_reply("480", "$var(node_type) Unavailable");
exit();
}
}
如果按照上面腳本執(zhí)行了 $ru = "sip:" + $rU + "@" + $(du{uri.host}) + ":" + $dp;,但是$rU== null 并且不設(shè)置 $rU="Null2SM"或者其他非空值,會(huì)報(bào)如下錯(cuò)誤:
Feb 12 22:27:35 fat5410 /usr/local/opensips/sbin/opensips[3710]: ERROR:core:parse_uri: bad char '@' in state 0 parsed: <sip:> (4) / <sip:@192.168.1.190:8060> (20)
Feb 12 22:27:35 fat5410 /usr/local/opensips/sbin/opensips[3710]: ERROR:core:parse_sip_msg_uri: bad uri <sip:@192.168.1.190:8060>
Feb 12 22:27:35 fat5410 /usr/local/opensips/sbin/opensips[3710]: ERROR:core:pv_get_ruri_attr: failed to parse the R-URI
解決辦法:
1)設(shè)置$rU 為一個(gè)非空值
2)直接不修改$ru 的值
3)修改 $ru = "sip:" + $(du{uri.host}) + ":" + $dp;
4.2.4 信令記錄:
- FS 發(fā)送INVITE給 OpenSIPs
2022-02-13 13:50:53 +0800 : 192.168.1.99:5221 -> 192.168.1.18:5070
INVITE sip:192.168.1.18:5070 SIP/2.0 你可以看到,這里沒(méi)有被叫號(hào)碼,所以到了OpenSIPs 后 $rU是null
Via: SIP/2.0/UDP 192.168.1.99:5221;rport;branch=z9hG4bKFUXeX5r0Q0gXp
Max-Forwards: 70
From: <sip:192.168.1.99:5221>;tag=3pU7FrrQBQ1NS
To: <sip:192.168.1.18:5070>
Call-ID: 5c97aeaf-64b4-123a-02b4-fa163ea03f01
CSeq: 38878534 INVITE
Contact: <sip:192.168.1.99:5221>
User-Agent: ASR_MRCP_CLIENT_FS_ALI
Allow: INVITE, ACK, BYE, CANCEL, OPTIONS, PRACK, MESSAGE, SUBSCRIBE, NOTIFY, REFER, UPDATE
Supported: timer, 100rel
Content-Type: application/sdp
Content-Disposition: session
Content-Length: 299
v=0
o=FS_MRCP 1321904497415698834 1659019553944433241 IN IP4 192.168.1.99
s=-
c=IN IP4 192.168.1.99
t=0 0
m=application 9 TCP/MRCPv2 1
a=setup:active
a=connection:new
a=resource:speechrecog
a=cmid:1
m=audio 16416 RTP/AVP 0 8
a=rtpmap:0 PCMU/8000
a=rtpmap:8 PCMA/8000
a=sendonly
a=mid:1
- OpenSIPS 轉(zhuǎn)發(fā)INVITE給 MRCP server
2022-02-13 13:50:53 +0800 : 192.168.1.18:5070 -> 192.168.1.190:8060
INVITE sip:192.168.1.18:5070 SIP/2.0 [如果修改$rU, 這里就是 INVITE sip:Null2SM@192.168.1.18:5070 SIP/2.0]
Record-Route: <sip:192.168.1.18:5070;lr;did=de7.2517abb1>
Via: SIP/2.0/UDP 192.168.1.18:5070;branch=z9hG4bK9443.c1265465.0
Via: SIP/2.0/UDP 192.168.1.99:5221;received=192.168.1.99;rport=5221;branch=z9hG4bKFUXeX5r0Q0gXp
Max-Forwards: 69
From: <sip:192.168.1.99:5221>;tag=3pU7FrrQBQ1NS
To: <sip:192.168.1.18:5070>
Call-ID: 5c97aeaf-64b4-123a-02b4-fa163ea03f01
CSeq: 38878534 INVITE
Contact: <sip:192.168.1.99:5221>
User-Agent:ASR_MRCP_CLIENT_FS_ALI
Allow: INVITE, ACK, BYE, CANCEL, OPTIONS, PRACK, MESSAGE, SUBSCRIBE, NOTIFY, REFER, UPDATE
Supported: timer, 100rel
Content-Type: application/sdp
Content-Disposition: session
Content-Length: 299
X-UUI: &XCID=0dc0196031626864653EXCIDEND
X-CID: 0dc0196031626864653EXCIDEND
v=0
o=FS_MRCP 1321904497415698834 1659019553944433241 IN IP4 192.168.1.99
s=-
c=IN IP4 192.168.1.99
t=0 0
m=application 9 TCP/MRCPv2 1
a=setup:active
a=connection:new
a=resource:speechrecog
a=cmid:1
m=audio 16416 RTP/AVP 0 8
a=rtpmap:0 PCMU/8000
a=rtpmap:8 PCMA/8000
a=sendonly
a=mid:1
- MRCP Server 回復(fù)200 OK,返回后續(xù)接收RTP的真實(shí)地址
2022-02-13 13:50:53 +0800 : 192.168.1.190:8060 -> 192.168.1.18:5070
SIP/2.0 200 OK
Via: SIP/2.0/UDP 192.168.1.18:5070;branch=z9hG4bK9443.c1265465.0
Via: SIP/2.0/UDP 192.168.1.99:5221;received=192.168.1.99;rport=5221;branch=z9hG4bKFUXeX5r0Q0gXp
Record-Route: <sip:192.168.1.18:5070;lr;did=de7.2517abb1>
From: <sip:192.168.1.99:5221>;tag=3pU7FrrQBQ1NS
To: <sip:192.168.1.18:5070>;tag=45D4K1DvpQQXK
Call-ID: 5c97aeaf-64b4-123a-02b4-fa163ea03f01
CSeq: 38878534 INVITE
Contact: <sip:192.168.1.190:8060>
User-Agent: BaiduSpeech SofiaSIP 1.5.0
Accept: application/sdp
Allow: INVITE, ACK, BYE, CANCEL, OPTIONS, PRACK, MESSAGE, SUBSCRIBE, NOTIFY, REFER, UPDATE
Supported: timer, 100rel
Session-Expires: 600;refresher=uac
Min-SE: 120
Content-Type: application/sdp
Content-Disposition: session
Content-Length: 303
v=0
o=BaiduSpeechServer 8512797916186481341 4497985761629564802 IN IP4 192.168.1.190 【接收RTP的IP】
s=-
c=IN IP4 192.168.1.190
t=0 0
m=application 1544 TCP/MRCPv2 1
a=setup:passive
a=connection:new
a=channel:b250b76cea1011eb@speechrecog
a=cmid:1
m=audio 18380 RTP/AVP 0 【接收RTP的端口】
a=rtpmap:0 PCMU/8000
a=recvonly
a=mid:1
- FS發(fā)送ACK給OpenSIPs
2022-02-13 13:50:53 +0800 : 192.168.1.99:5221 -> 192.168.1.18:5070
ACK sip:192.168.1.190:8060 SIP/2.0
Via: SIP/2.0/UDP 192.168.1.99:5221;rport;branch=z9hG4bKFUXeX5r0Q0gXp
Route: <sip:192.168.1.18:5070;lr;did=355.53f8e331>
Max-Forwards: 70
From: <sip:192.168.1.99:5221>;tag=3pU7FrrQBQ1NS
To: <sip:192.168.1.18:5070>
Call-ID: 5c97aeaf-64b4-123a-02b4-fa163ea03f01
CSeq: 38878534 ACK
Contact: <sip:192.168.1.99:5221>
Content-Length: 0
- 最后,OpenSIPs將ACK轉(zhuǎn)發(fā)給MRCP Server
五、結(jié)語(yǔ)
從上文中提到的攜程呼叫中心客戶呼入到智能客服機(jī)器人場(chǎng)景的核心組件架構(gòu)圖可以看出,ASR引擎的負(fù)載均衡只是攜程呼叫中心平臺(tái)各組件中很小的一個(gè)功能點(diǎn),但也是不可或缺的一部分。
正因?yàn)橛辛诉@個(gè)技術(shù)方案的實(shí)現(xiàn),使得多集群、多數(shù)據(jù)中心、多供應(yīng)商的ASR產(chǎn)品得以很好地整合,為攜程電話智能客服機(jī)器人業(yè)務(wù)的穩(wěn)定運(yùn)行提供了良好的技術(shù)保障,提升了攜程客戶的通話體驗(yàn)。
本文主要講解了對(duì)于ASR引擎做負(fù)載均衡的設(shè)計(jì)以及實(shí)現(xiàn)方案,希望能對(duì)從事智能呼叫中心領(lǐng)域工作或研究的同學(xué)們提供一些幫助。