近幾年,基于WebRTC的電話終端工具在通訊行業(yè)中越來(lái)越流行,客戶服務(wù)可以直接通過(guò)瀏覽器撥打電話來(lái)實(shí)現(xiàn)。目前業(yè)內(nèi)大多數(shù)Web電話工具僅支持單個(gè)頁(yè)面使用,無(wú)法支撐美團(tuán)多業(yè)務(wù)復(fù)雜的外呼場(chǎng)景,美團(tuán)在WebRTC領(lǐng)域不斷探索,實(shí)現(xiàn)了多頁(yè)面多域名共用的Web端電話SDK。在 RTE 2020 實(shí)時(shí)互聯(lián)網(wǎng)大會(huì)上,美團(tuán)前端技術(shù)專家楊尚林分享了美團(tuán)是如何通過(guò)共享線程來(lái)解決多頁(yè)面多域名下共享通話狀態(tài)的業(yè)界難題的。
??點(diǎn)擊「閱讀原文」可觀看視頻回放,獲取 PPT
以下為演講實(shí)錄:
大家好我是來(lái)自美團(tuán)網(wǎng)的楊尚林,很高興來(lái)參加今年的實(shí)時(shí)互聯(lián)網(wǎng)大會(huì),在這里我將也會(huì)跟大家交流一些我在美團(tuán)這邊一些日常的工作。
今天我主要跟大家分享的題目是美團(tuán)的webRTC電話終端工具實(shí)踐。下面開(kāi)始我今天的分享,我今天的分享將會(huì)進(jìn)行五個(gè)部分講解,分別是工具介紹、項(xiàng)目背景、項(xiàng)目目標(biāo)、實(shí)現(xiàn)方案以及最后的項(xiàng)目成果的展示,希望跟大家一起進(jìn)行探討。
首先我會(huì)先介紹一下我們的終端工具到底是個(gè)什么樣的東西,大家首先可以看到一個(gè)demo的展示,那么相信在很多做通信公司里面都有的,很多公司都會(huì)做自己內(nèi)部電話的系統(tǒng),它其實(shí)本身是包括外呼、接聽(tīng)、轉(zhuǎn)接、掛斷、呼叫保持、通話計(jì)時(shí)、通話質(zhì)量檢測(cè),最后其實(shí)還包括一個(gè)很重要的多頁(yè)面的使用,我不知道各位公司里面會(huì)不會(huì)有這樣的功能??赐炅怂鞘裁粗螅覀儊?lái)看一下它的項(xiàng)目背景是什么,首先目前來(lái)說(shuō)各個(gè)目前各大公司其實(shí)都有自己的云呼叫中心,或者有自己內(nèi)部自建的通信系統(tǒng),那么它本身是需要提供一些基礎(chǔ)的通話能力和坐席能力,比如說(shuō)最基本的語(yǔ)音通話能力、坐席、技能、隊(duì)列等等能力,包括智能IVR、機(jī)器人的能力,同時(shí)它還會(huì)提供一套客戶關(guān)系管理或者工單系統(tǒng),那么會(huì)兼容其他的數(shù)據(jù)報(bào)表等系統(tǒng),包括知識(shí)庫(kù)。
其實(shí)我們可以看到語(yǔ)音通信能力是語(yǔ)音呼叫中心最基礎(chǔ)最基本的能力,語(yǔ)音通話能力一般情況下怎么樣去做呢。
現(xiàn)在的做法是說(shuō)去實(shí)現(xiàn)一個(gè)桌面的軟件化,比如說(shuō)我們平時(shí)經(jīng)常用的ESPhone/X-Lite,在PC端的情況下,那么在Web端我們一般依賴于WebRTC,比如說(shuō)我們會(huì)實(shí)現(xiàn)一個(gè)電話條的工具或者一個(gè)單獨(dú)的頁(yè)面,給我們業(yè)務(wù)方去提供使用。
那么Web電話一般的使用場(chǎng)景是什么呢,首先我們會(huì)用一種電話條UI組建的方式去嵌入業(yè)務(wù)方的頁(yè)面中,這個(gè)頁(yè)面可能包括工單系統(tǒng)CRM等,它的功能包括電銷、回訪、客服這種常見(jiàn)的功能,因?yàn)樗枰獫M足我們正常的通話操作。比如說(shuō)外呼、撥號(hào)、掛斷等等,它還需要去保持我們的通話狀態(tài),我們的坐席是需要實(shí)時(shí)的感知到電話的狀態(tài)的,比如說(shuō)通話中、掛斷、忙線等。其實(shí)還有一些非常必要的功能。
目前來(lái)說(shuō)我發(fā)現(xiàn)市面上其實(shí)很多的電話功能是不具備的,比如說(shuō)多頁(yè)面、多系統(tǒng)、同狀態(tài),這三者分別是什么意思呢,第一點(diǎn)就是其實(shí)我們的業(yè)務(wù)方式需要在多個(gè)瀏覽器標(biāo)簽里進(jìn)行外呼和掛斷的,其實(shí)目前來(lái)說(shuō)大多數(shù)的這種電話工具都只是在單個(gè)tab頁(yè)里去發(fā)起外呼和接聽(tīng),或者只支持單個(gè)tab頁(yè)的注冊(cè)動(dòng)作。這個(gè)需求是重點(diǎn),就是說(shuō)業(yè)務(wù)方其實(shí)是不關(guān)心自己的tab頁(yè)的行為是什么樣的,用戶也不需要關(guān)心自己所做的操作是什么,他們所關(guān)心的其實(shí)只是日常的工作,比如說(shuō)他們接打電話、處理工單、回訪用戶等一系列。同時(shí)這個(gè)業(yè)務(wù)方還可能使用多個(gè)系統(tǒng),那么他們?cè)谡嬲尤氲臅r(shí)候在對(duì)于同一個(gè)業(yè)務(wù)方的兩個(gè)系統(tǒng)來(lái)說(shuō),一個(gè)是工單系統(tǒng),一個(gè)是CRM系統(tǒng),他們所需要的接入信息是一樣的,因?yàn)檫@個(gè)坐席它可能是同時(shí)使用兩個(gè)系統(tǒng)的,那么當(dāng)一通電話呼入的時(shí)候它不可能說(shuō)只是某一個(gè)頁(yè)面進(jìn)行響應(yīng),或者是兩個(gè)同時(shí)響應(yīng),它是需要同時(shí)兩個(gè)響應(yīng)的話接起任意一個(gè),所以說(shuō)業(yè)務(wù)方式可能使用多個(gè)系統(tǒng)的,每個(gè)系統(tǒng)都需要具備外呼的能力,在這種狀態(tài)下這種同頁(yè)面多系統(tǒng)狀態(tài)下的,每一個(gè)標(biāo)簽頁(yè)都需要保持統(tǒng)一的狀態(tài)。
比如說(shuō)我們?cè)贏頁(yè)面進(jìn)行外呼的時(shí)候,我們這時(shí)候切換到B頁(yè)面把它掛斷到,其實(shí)電話所做的操作是一樣的,A、B頁(yè)面的電話都會(huì)被掛斷掉。
像這里就是展示了一個(gè)我們平時(shí)內(nèi)部的一個(gè)demo頁(yè),我們可以在多個(gè)tab頁(yè)內(nèi)保持通話狀態(tài),并且在多個(gè)tab業(yè)同時(shí)進(jìn)行通話。本次這個(gè)項(xiàng)目目標(biāo)我們是針對(duì)前面我們前面我們需要的功能去做一些分析。
第一個(gè)我們必須支持多頁(yè)面的使用,當(dāng)然除了基本的電話功能之外,那么它支持多頁(yè)面多系統(tǒng)同時(shí)注冊(cè)和使用,并且使用的注冊(cè)信息是相同的,比如說(shuō)我們使用同一個(gè)分機(jī)號(hào)去進(jìn)行注冊(cè)。
第二個(gè)多頁(yè)面、多系統(tǒng)的時(shí)候狀態(tài)是需要完全同步的。
第三個(gè)它的業(yè)務(wù)接入是需要非常簡(jiǎn)單,因?yàn)槲覀兊臉I(yè)務(wù)方它僅需要引入我們的組件就可以接入我們的工具了,而不是要關(guān)注我們?yōu)槎囗?yè)面多系統(tǒng)開(kāi)發(fā)的成本所帶來(lái)的變化。比如說(shuō)它不需要為我們支持他們進(jìn)行多頁(yè)面而進(jìn)行任何的適配工作。
第四個(gè)就是我們的用戶體驗(yàn)必須是良好的,首先保持良好的UI和交互,要有完美的錯(cuò)誤回調(diào)和說(shuō)明,也要提供很好的質(zhì)量檢測(cè)與設(shè)備探測(cè)功能。
我們?cè)趯?shí)現(xiàn)這個(gè)電話條中所經(jīng)歷的哪些過(guò)程,首先這個(gè)電話工具它其實(shí)是有三個(gè)難點(diǎn)的,第一個(gè)就是多Tab業(yè)注冊(cè)是很難的,第二個(gè)多連接管理是非常難的,第三個(gè)我們會(huì)給大家介紹一個(gè)新的方案,引入這個(gè)新的方案我們帶來(lái)了很多相關(guān)的問(wèn)題。
首先我們看一下多Tab業(yè)為什么注冊(cè)難,我想這個(gè)問(wèn)題大家都是非常清楚的,因?yàn)槲覀兌际怯泄残缘?,我們知道在WebRTC SDP交換過(guò)程中,首先我們是需要對(duì)端的地址和端口號(hào)的,我們的媒體服務(wù)器其實(shí)會(huì)為每一個(gè)注冊(cè)的動(dòng)作去事先分配好一個(gè)IP+端口號(hào)去用于日后的傳輸。如果說(shuō)我們有多個(gè)Tab頁(yè)的時(shí)候,比如說(shuō)當(dāng)Tab A已經(jīng)完成了注冊(cè)并且已經(jīng)完成了SDP的交互,并且在建立語(yǔ)音的情況下,就是說(shuō)我們的媒體服務(wù)器已經(jīng)為他分配好了一個(gè)端口號(hào)去進(jìn)行UDP的傳輸,我們的Tab B又發(fā)起了注冊(cè),這個(gè)時(shí)候我們的媒體服務(wù)器又會(huì)為T(mén)ab B重新分配一個(gè)端口號(hào)去用于后續(xù)的傳輸,其實(shí)這個(gè)時(shí)候我們媒體服務(wù)器用于中轉(zhuǎn)語(yǔ)音流的端口其實(shí)是已經(jīng)變化了,就會(huì)導(dǎo)致當(dāng)前的通話直接被斷掉。
在我們?nèi)粘?chǎng)景中實(shí)際上是面臨這個(gè)問(wèn)題,我理解其實(shí)我們?nèi)粘I罟ぷ髦兴媾R的問(wèn)題是大同小異的,所以說(shuō)其實(shí)我們的結(jié)論在我們美團(tuán)內(nèi)部的結(jié)論是顯而易見(jiàn)的,多個(gè)Tab頁(yè)其實(shí)我們只能為他進(jìn)行一次的注冊(cè)動(dòng)作。
那么其實(shí)我們?cè)趺礃幼尪鄠€(gè)Tab只進(jìn)行一次注冊(cè)動(dòng)作呢,其實(shí)我們?cè)谶@之間去引用sharedWorker,那首先我們先不去sharedWorker是什么,我們首先來(lái)看一下shared Worker在其中起到的作用是什么,我們目前來(lái)說(shuō),假如說(shuō)我們有兩個(gè)Tab頁(yè)的話那么shared Worker在中間起到一個(gè)橋梁的作用,它會(huì)溝通兩個(gè)Tab頁(yè),它會(huì)為兩個(gè)Tab頁(yè)之間建立一定的聯(lián)系,并且我們?cè)赥ab頁(yè)中間只保持一個(gè)sharedWorker,我們僅通過(guò)這個(gè)sharedWorker在我們的遠(yuǎn)端發(fā)起單個(gè)連接,可以理解為T(mén)abA和TabB它們兩個(gè)共用了同一個(gè)連接。
這個(gè)時(shí)候這樣的話我們的注冊(cè)命令其實(shí)就很好實(shí)現(xiàn),因?yàn)樵谖覀兛磥?lái)我們的電話工具其實(shí)是只有一個(gè),我們其實(shí)不需要關(guān)心我們有幾個(gè)Tab頁(yè),Tab A和Tab B目前來(lái)說(shuō)對(duì)我們來(lái)說(shuō)都已經(jīng)只是一個(gè)頁(yè)面了,我們只需要通過(guò)sharedWorker去發(fā)起同步的操作就可以了。
那么sharedWorker是什么呢,它是一個(gè)共享多線程,它本身代表了一種特定類型的Worker,可以從瀏覽器的上下文中訪問(wèn),比如說(shuō)在幾個(gè)窗口內(nèi)iFrame或者其他的Worker中,它本身是有自己的全局作用域的,并且它還使用Post Message進(jìn)行通信的。
它其實(shí)是有很多限制,比如說(shuō)同源限制,那么我們其實(shí)規(guī)定加載的Worker腳本必須與我們的宿主頁(yè)面是同源的,并且shared Worker在連接不同頁(yè)面的時(shí)候,這些頁(yè)面也必須是同源的,就是說(shuō)天然規(guī)定的我們所接觸的業(yè)務(wù)方它的域名必須是同源的,請(qǐng)注意我們之前提到過(guò),我們是支持業(yè)務(wù)方使用多系統(tǒng)的,并且這些系統(tǒng)可能是不同域名的,后面會(huì)介紹我們?cè)趺慈ヌ幚磉@種現(xiàn)象。
第三個(gè)就是作用域是無(wú)法訪問(wèn)DOM的,并且它無(wú)法在共享線程中使用任何WebRTC的相關(guān)API。7
下面有一個(gè)sharedWorker使用的例子。它的兼容性其實(shí)在我們看來(lái)是非常好的,目前來(lái)說(shuō)在不考慮IE的情況下它的兼容性基本上與WebRTC相關(guān)的生態(tài)是同步的,并且國(guó)產(chǎn)瀏覽器同樣是支持,在國(guó)內(nèi)環(huán)境中。唯一的問(wèn)題是隱身模式是無(wú)法使用的,但是這個(gè)其實(shí)不是問(wèn)題,因?yàn)樗蚻ocalStorage這些特性其實(shí)是一樣的。
首先我們來(lái)看同源限制,為什么我們需要去考慮這個(gè)同源限制呢,因?yàn)楫吘刮覀冏鳛槠脚_(tái)方的話我們所有的資源腳本都是要去給業(yè)務(wù)方引用的,所以我們勢(shì)必會(huì)被為業(yè)務(wù)方提供一個(gè)npm包或是一個(gè)JS的腳本,這個(gè)時(shí)候勢(shì)必會(huì)與業(yè)務(wù)方的域起沖突,所以我們一定要想辦法解決這個(gè)同域的限制,我們這里研究出來(lái)兩個(gè)方法,分別一個(gè)是Data URL,一個(gè)是iFrame。
Data URL其實(shí)我們之前用過(guò),比如說(shuō)我們?cè)谝肂ase64的時(shí)候其實(shí)都是去使用過(guò)的。我們?cè)趺礃尤ナ褂盟?,我在下面也給出了一個(gè)例子,是通過(guò)本身的importScripts API去引入官方域下的sharedWorker腳本。
iFrame方案其實(shí)通過(guò)iFrame代理去進(jìn)入sharedWorker腳本。舉個(gè)例子,我有兩個(gè)域名,分別是A域名和B域名,我其實(shí)是無(wú)法再兩個(gè)域名中間去建立使用同一個(gè)shared Worker的,但是我可以在A和B兩個(gè)域名中引用同樣域的iFrame,我們使用同樣域的iFrame去引入同樣的sharedWorker,那這個(gè)時(shí)候其實(shí)我們就間接的讓A和B共同的使用了shared Worker的腳本,之后我們?cè)倥浜蟨ost Message進(jìn)行消息中轉(zhuǎn)就可以了,相當(dāng)于iFrame在中間起到代理層的作用。
我們看一下架構(gòu)圖,針對(duì)單個(gè)域名的時(shí)候sharedWorker就是一個(gè)代理,我們是通過(guò)Data URL這種方案去引用的,意思就是我們使用sharedWorker的腳本地址是一個(gè)Data URL地址,之后它會(huì)為我們?nèi)ソ⑦B接,跟我們之前說(shuō)的流程是完全一樣的。
其實(shí)很多人會(huì)擔(dān)心這種Data URL方式會(huì)不會(huì)更多的是一種hack,其實(shí)我們之前也是有擔(dān)心的,就是在我們比如嘗試摸索出Data URL這種方式之后,其實(shí)我們是專門(mén)去查證的原碼,最后是發(fā)現(xiàn)chromium是專門(mén)放開(kāi)了Data URL這種跨域的限制,原碼也在這里,大家可以放心去使用的,因?yàn)檫@條commit是可以加上去的。
那么iFrame方案其實(shí)更多的是針對(duì)多個(gè)域名的情況,比如說(shuō)像之前講到的我們存在兩個(gè)域名,我們可以同時(shí)引入相同域的官方iFrame腳本,并且引入同樣的sharedWorker去達(dá)到我們不同的域名不同頁(yè)面使用同一個(gè)sharedWorker的目的,并且通過(guò)這個(gè)sharedWorker幫我們做資源的分發(fā)連接,那么這里面其實(shí)與Data URL相比iFrame,我們?yōu)槭裁磿?huì)有兩個(gè)方案呢。就是因?yàn)閕Frame其實(shí)大家都是比較清楚的,它相對(duì)來(lái)說(shuō)耗費(fèi)的資源要相對(duì)多一點(diǎn),它的速度要相對(duì)慢一點(diǎn),但是在絕對(duì)速度上來(lái)說(shuō)其實(shí)也是在毫秒級(jí)別的,所以說(shuō)業(yè)務(wù)方其實(shí)可以是自行選擇去采用多域名或者單域名的配置,我們僅僅是通過(guò)一個(gè)配置項(xiàng)就可以給他們提供到。這里面相對(duì)來(lái)說(shuō)我們?nèi)绻f(shuō)業(yè)務(wù)方只有單個(gè)域名的話使用Data URL這種方案可能會(huì)稍微快一點(diǎn)。
其實(shí)在這個(gè)方案的背后我們是有很多的問(wèn)題的,這些問(wèn)題其實(shí)是非常頭疼也是非常必要的,我們現(xiàn)在看一下我們?yōu)榱酥С治覀兊亩囗?yè)面會(huì)引入哪些問(wèn)題,并且我們是如何解決這些問(wèn)題。
我目前總結(jié)的這些問(wèn)題應(yīng)該是支持多頁(yè)面里面并且是sharedWorker方案里面應(yīng)該是最棘手的問(wèn)題了,底下分成三部分去講解。
首先我們就會(huì)面臨一個(gè)通話頁(yè)異常關(guān)閉的現(xiàn)象,那么它的現(xiàn)象是什么呢,就是因?yàn)槲覀兊膕haredWorker,我們之前講過(guò)它其實(shí)是無(wú)法使用WebRTC相關(guān)生態(tài)里的任何對(duì)象的,比如說(shuō)MediaDevices,也就是說(shuō)明我們的WebRTC相關(guān)設(shè)備連接邏輯必須是存在于我們業(yè)務(wù)方系統(tǒng)的某一個(gè)頁(yè)面內(nèi)。所以我們勢(shì)必會(huì)出現(xiàn),當(dāng)我們把那個(gè)頁(yè)面關(guān)閉掉的時(shí)候,如果業(yè)務(wù)方使用多個(gè)頁(yè)面,那其實(shí)多個(gè)頁(yè)面的整個(gè)這種呼叫都會(huì)被掛斷掉。
比如說(shuō)我們關(guān)閉了發(fā)起外乎或者接聽(tīng)的這種頁(yè)面的話,頁(yè)面的流就會(huì)被斷掉,下面有一個(gè)示意圖,其實(shí)我們?nèi)魏蔚倪@種多頁(yè)面的情況下,每時(shí)每刻都只有一個(gè)頁(yè)面的Web RTC其實(shí)真正的邏輯是在工作中的。它的原因是很明顯的,比如說(shuō)我們?cè)谶M(jìn)行呼入的時(shí)候其實(shí)我們會(huì)在直接外呼的頁(yè)面上建立peerConnection的連接。比如說(shuō)我們存在呼入的時(shí)候其實(shí)我們會(huì)在接聽(tīng)的那個(gè)頁(yè)面上去進(jìn)行peerConnection的連接。
下面是有例子的,比如說(shuō)我有三個(gè)訂單頁(yè),其實(shí)當(dāng)我的訂單A是真正的WebRTC承載頁(yè),那如果說(shuō)我不小心把它關(guān)掉了,我們大家想一下真實(shí)邏輯的處理流程,比如說(shuō)我是一名客服,我要處理三個(gè)訂單,我很容易在跟一個(gè)用戶咨詢完電話之后我不小心,或者說(shuō)在中途中,其實(shí)我切換了好多頁(yè)面,我是不小心把某個(gè)頁(yè)面關(guān)掉了,但是恰好這個(gè)頁(yè)面是我真正WebRTC承載的頁(yè)面,這個(gè)時(shí)候其實(shí)整個(gè)電話都會(huì)掛斷掉。這個(gè)只是我個(gè)人一個(gè)不小心的行為會(huì)導(dǎo)致整個(gè)頁(yè)面掛掉,但是我們不能把這個(gè)問(wèn)題去歸咎去讓客服同學(xué)注意這種行為。
其實(shí)我們是需要給一定解決方案的,這個(gè)解決方案其實(shí)是比較簡(jiǎn)單的,我們只需要去給到我們的開(kāi)發(fā)同學(xué)一個(gè)正在通話的狀態(tài)就好了,我們讓他根據(jù)這個(gè)通話狀態(tài)去進(jìn)行一個(gè)提示,比如說(shuō)通話中我們不允許他關(guān)閉頁(yè)面,當(dāng)然這種方法是非常簡(jiǎn)單非常高效的。但是其實(shí)我們有另外一種方法去完全規(guī)避這種開(kāi)發(fā)的問(wèn)題,這個(gè)時(shí)候我們會(huì)增加一個(gè)設(shè)備頁(yè)面,設(shè)備頁(yè)面它其實(shí)是用來(lái)創(chuàng)建WebRTC實(shí)例,并且建立peerConnection連接的頁(yè)面,其實(shí)這個(gè)設(shè)備頁(yè)面在我們提出這個(gè)方案之前它是存在一個(gè)業(yè)務(wù)方的邏輯里面的,就是業(yè)務(wù)方的頁(yè)面里面的,雖然跟我們的SDK在一起,最終我們是希望我們?cè)黾舆@個(gè)設(shè)備頁(yè)面去始終通過(guò)這個(gè)設(shè)備頁(yè)面去管理我們的語(yǔ)音,并且承載我們的通話。
我們看打開(kāi)這個(gè)設(shè)備頁(yè)面的邏輯,設(shè)備頁(yè)面建立的時(shí)機(jī)是什么呢,比如說(shuō)我們有呼入或者來(lái)電的時(shí)候,我們首先會(huì)去檢查設(shè)備頁(yè)面是否存在了,如果沒(méi)存在我們會(huì)把設(shè)備頁(yè)面拉起并且建立起來(lái),并且建立它的WebRTC相關(guān)的邏輯。
他打開(kāi)的策略是這個(gè)設(shè)備頁(yè)面會(huì)以最小的窗口進(jìn)行打開(kāi),比方說(shuō)我們給它高100像素的一個(gè)大小,并且它會(huì)顯示電話的狀態(tài),并且它會(huì)置于所有的tab頁(yè)之后,平時(shí)是不可見(jiàn)的。我們還會(huì)做一個(gè)邏輯,比如說(shuō)當(dāng)我們的業(yè)務(wù)頁(yè)面全部關(guān)閉的時(shí)候,我們會(huì)自動(dòng)觸發(fā)關(guān)閉這個(gè)tab頁(yè)的操作,其實(shí)用戶他不會(huì)感知到我們這個(gè)設(shè)備頁(yè)面的存在。就好比他是運(yùn)行在所有tab頁(yè)之后的一個(gè)幽靈一樣。所有的操作同學(xué)他其實(shí)可以自由的使用他的頁(yè)面,關(guān)閉任何一個(gè)頁(yè)面都不會(huì)影響到設(shè)備頁(yè)面,只有說(shuō)當(dāng)我們把所有的頁(yè)面全部關(guān)閉之后才會(huì)對(duì)設(shè)備頁(yè)面進(jìn)行關(guān)閉的操作。
那么現(xiàn)在我們其實(shí)可以看一下整個(gè)的架構(gòu),因?yàn)槲覀兤鋵?shí)是引入了sharedWorker,其實(shí)正是因?yàn)槲覀冇衧haredWorker我們才得以去實(shí)現(xiàn)這種設(shè)備頁(yè)面的這種操作,設(shè)備頁(yè)面其實(shí)與業(yè)務(wù)頁(yè)面他們之間都是通過(guò)sharedWorker去進(jìn)行共享電話的狀態(tài)和操作的,比如說(shuō)業(yè)務(wù)頁(yè)面進(jìn)行電話的這種狀態(tài)的展示,設(shè)備頁(yè)面去執(zhí)行語(yǔ)音的動(dòng)作,其實(shí)我們可以理解為每一個(gè)頁(yè)面包括設(shè)備頁(yè)面包括業(yè)務(wù)頁(yè)面他們都是可以從sharedWorker中獲取一定信息的。
當(dāng)然只是說(shuō)他們執(zhí)行不同的動(dòng)作而已,而只有設(shè)備頁(yè)面去承載語(yǔ)音的質(zhì)量,而且他是唯一承載語(yǔ)音質(zhì)量的頁(yè)面。
那么我們還會(huì)面臨版本升級(jí)混亂的問(wèn)題,怎么去理解這個(gè)問(wèn)題呢,其實(shí)可能多數(shù)沒(méi)有使用過(guò)sharedWorker同學(xué)完全想象不到這個(gè)問(wèn)題的,但是這個(gè)問(wèn)題在我們的升級(jí)開(kāi)發(fā)中是非常非常嚴(yán)重的問(wèn)題,因?yàn)樗麜?huì)導(dǎo)致我們整個(gè)電話系統(tǒng)不可用,并且每次當(dāng)我們發(fā)布新版本的時(shí)候都會(huì)存在這樣的問(wèn)題,就是我們升級(jí)一個(gè)新版本,業(yè)務(wù)方卻完全不可用了,它是怎么回事呢,當(dāng)我們的sharedWorker腳本進(jìn)行升級(jí)的時(shí)候,也就是對(duì)我們的SDK進(jìn)行升級(jí)的時(shí)候,我們是需要對(duì)版本進(jìn)行區(qū)分的。
因?yàn)閟haredWorker它原理是跟webWorker一樣的,就是我們?nèi)绻胍獙?duì)它進(jìn)行替換的話,我們需要去改變它的URL,所以我們需要用不同的版本對(duì)它的資源加以區(qū)分才可以替換。
比如說(shuō)我們想把0.0.1的Worker去換掉0.0.2的Worker,我們真正期望的是多個(gè)頁(yè)面共享的sharedWorker,它其實(shí)能與正常的前端發(fā)布一樣,用戶是不需要關(guān)注頁(yè)面刷新或者說(shuō)資源發(fā)布時(shí)機(jī)的,它可以正常的使用,完全無(wú)憂無(wú)慮的去用,我們來(lái)看一下這個(gè)問(wèn)題是怎么造成的呢,其實(shí)在升級(jí)版本之前用戶的版本都是統(tǒng)一的。
比如說(shuō)Worker的狀態(tài)其實(shí)也可以共享一例,所有的狀態(tài)都是可以同步的,比如說(shuō)我們可以看一下左圖,當(dāng)我們沒(méi)有升級(jí)版本之前大家的版本都是0.0.1,包括sharedWorker它本身的腳本也是0.0.1。那我們升級(jí)版本之后用戶刷新了某個(gè)頁(yè)面會(huì)導(dǎo)致這個(gè)頁(yè)面版本升級(jí),從而引入新的Worker腳本,這樣會(huì)導(dǎo)致多個(gè)頁(yè)面間狀態(tài)是無(wú)法同步的。
因?yàn)槲覀兊讓映霈F(xiàn)了兩個(gè)Worker腳本,它會(huì)與我們的遠(yuǎn)端進(jìn)行兩個(gè)連接,并且他們的上層所有這些業(yè)務(wù)頁(yè)面是無(wú)法再進(jìn)行通信了,相當(dāng)于我們又開(kāi)辟出來(lái)一個(gè)新的業(yè)務(wù)頁(yè)面,他們之間再也沒(méi)有關(guān)系了。這個(gè)例子大家是可以理解的。
比如說(shuō)我們?nèi)齻€(gè)頁(yè)面中某一個(gè)頁(yè)面刷新了它變成0.0.2了,它勢(shì)必會(huì)引入新的shared Worker的資源,也變成0.0.2,這個(gè)時(shí)候其實(shí)我們底層的shared Worker會(huì)創(chuàng)建兩個(gè)事例,因?yàn)樗鼈儾辉偈峭瑯拥腢RL了。
我們?cè)趺慈ソ鉀Q這個(gè)問(wèn)題呢,其實(shí)我們需要引入一套shared Worker這種本身的升級(jí)機(jī)制的,去避免這種多頁(yè)面版本升級(jí)時(shí)和這種刷新動(dòng)作帶來(lái)的這種不可用的現(xiàn)象,所以說(shuō)我們可以通過(guò)我們總結(jié)出來(lái)的流程,去實(shí)現(xiàn)Worker的這種自我檢測(cè)和自我升級(jí)的。首先其實(shí)我們?cè)谶\(yùn)行過(guò)程中,我們是需要時(shí)時(shí)刻刻知道我們當(dāng)前的版本是什么。
比如說(shuō)我們?cè)谖覀兇虬倪^(guò)程中,把我們當(dāng)前的版本給打入到我們的包里面去,在我們運(yùn)行的過(guò)程中,其實(shí)我們?cè)诩虞d的初期是時(shí)刻需要對(duì)我們的Worker版本進(jìn)行探測(cè)的。首先我們需要感知到當(dāng)前在活著所有的Worker它是一個(gè)什么樣的版本,而我們刷新之后我們新引入我們希望升級(jí)的Worker是一個(gè)什么樣的版本,其實(shí)我們?cè)趺礃釉谖覀兯⑿马?yè)面的時(shí)候,知道我們當(dāng)前活著的就是其他頁(yè)面引用的Worker是什么呢。
因?yàn)檫@個(gè)時(shí)候我們還沒(méi)有建立這個(gè)shared Worker連接,其實(shí)我們是無(wú)法進(jìn)行交互的,所以我們是需要先引入原先的shared Worker首先進(jìn)行一個(gè)探測(cè),去探測(cè)我們其他的頁(yè)面,目前的shared Worker是一個(gè)什么樣的狀態(tài),同時(shí)我們需要把這個(gè)狀態(tài)去存入我們的localStorage之中。就好比是,如果我們進(jìn)來(lái)之后發(fā)現(xiàn)我們當(dāng)前已經(jīng)使用了localStorage里面某一個(gè)版本的版本號(hào)的話,那么我們會(huì)跟自己的狀態(tài)自身版本號(hào)進(jìn)行對(duì)比,自身版本號(hào)發(fā)現(xiàn)不一致的情況下,我們會(huì)給它進(jìn)行一個(gè)升級(jí)并且替換,這個(gè)升級(jí)替換規(guī)則是什么呢。
比如說(shuō)我當(dāng)前是0.0.1我需要把自己替換成0.0.2,首先我需要同樣先引入0.0.1的這個(gè)sharedWorker,這樣的話我才能跟其他的頁(yè)面引入的sharedWorker的版本是一致的,我們大家都引入0.0.1的版本,這個(gè)時(shí)候sharedWorker是保持一列的,之后我再傳入一系列命令,去告訴我的sharedWorker你要把你自己替換成0.0.2的版本,然后shared Worker會(huì)先所有業(yè)務(wù)頁(yè)面同步狀態(tài),所有的業(yè)務(wù)頁(yè)面都會(huì)把自己對(duì)0.0.1版本的shared Worker的引用銷毀掉,并且把自己升級(jí)成0.0.2的版本。我們目前建立一種shared Worker自更新自升級(jí)的機(jī)制。
如果其實(shí)想實(shí)現(xiàn)一個(gè)Web電話工具,我們是需要有其他優(yōu)化內(nèi)容的,比如說(shuō)我們需要去提供一些懶加載機(jī)制,避免業(yè)務(wù)方自身的懶加載。
第二我們需要一系列設(shè)備檢測(cè),幫助我們客戶快速的定位問(wèn)題,去檢測(cè)他們的設(shè)備當(dāng)前是否可用的,語(yǔ)音質(zhì)量是如何的。
第三個(gè),我們需要去提供一些網(wǎng)絡(luò)檢測(cè)的工具或者說(shuō)頁(yè)面,或者最好把它集成到我們的電話工具里面去,還需要對(duì)我們?cè)票O(jiān)控質(zhì)量進(jìn)行一個(gè)通信的大盤(pán),還需要給用戶在通話過(guò)程中進(jìn)行一個(gè)Notification的提示,包括可視化的一個(gè)質(zhì)量信號(hào)實(shí)時(shí)的抖動(dòng)。
還有我們每通電話它通信過(guò)程中發(fā)生的一些問(wèn)題,如何快速定位問(wèn)題,進(jìn)行一個(gè)可視化的鏈路的監(jiān)控。
如果說(shuō)在這個(gè)鏈路監(jiān)控中出現(xiàn)問(wèn)題的時(shí)候,我們還需要對(duì)我們的操作人員或者說(shuō)我們的開(kāi)發(fā)人員進(jìn)行一個(gè)實(shí)時(shí)的告警。
這里其實(shí)為大家展示了一個(gè)整體的架構(gòu)(如上圖所示),它其實(shí)會(huì)包括很多的東西,因?yàn)閟hared Worker是我們整個(gè)方案的核心,整個(gè)方案里面不光是有sharedWorker,其實(shí)它包含了很多內(nèi)容,所以工程化的能力其實(shí)也是需要去思考的,這里面也有我們的架構(gòu)圖可以給大家展示一下,最終我們其實(shí)是達(dá)成了一些成果的,有些成果是在我們意料之內(nèi),有些成果其實(shí)是在我們完成之后又去發(fā)現(xiàn)的,首先我們的業(yè)務(wù)收益其實(shí)是目前支撐了我們美團(tuán)數(shù)千名坐席的日常電話服務(wù)。包括電銷、面試、銷售等各種各樣場(chǎng)景。
其實(shí)它是同時(shí)滿足業(yè)務(wù)方快速接入的,業(yè)務(wù)方其實(shí)只需要引入我們的資源就可以了,并且它執(zhí)行一個(gè)初始化的方法,他是不需要關(guān)心多個(gè)頁(yè)面帶來(lái)的問(wèn)題的,接入是非常簡(jiǎn)單的。
其實(shí)它是有一些技術(shù)收益的,比如說(shuō)我們?nèi)ヒ雜haredWorker這個(gè)方案之后,大家知道我們是保持單個(gè)連接,單個(gè)連接大大的降低了我們后端系統(tǒng)復(fù)雜度的,比如說(shuō)我們不需要再用MQ去同步機(jī)器之間的狀態(tài),我們不需要再去維護(hù)多個(gè)鏈接同步狀態(tài)去進(jìn)行復(fù)雜的邏輯。
第二個(gè)我們是大大降低了并發(fā)的,不光是前端的連接,包括后端需要同步狀態(tài),MQ連接,包括數(shù)據(jù)庫(kù)查詢連接,包括我們Redis的連接等各個(gè)查詢連接,我們都是降低了很多并發(fā)流量的。
第三點(diǎn)我們知道,我們本次的方案是多個(gè)tab頁(yè)我們只引用了一個(gè)連接,其實(shí)我們的日志是只需要打印一路就可以了,多個(gè)tab頁(yè)其實(shí)對(duì)于我們來(lái)說(shuō)更像是只存在一個(gè)電話,所以我們的日志也只需要一路了,否則的話我們需要打印多路日志,這些日志其實(shí)是有大量的冗余的,并且我們查起來(lái)其實(shí)所有日志的事件是交錯(cuò)在一起的。
以上就是我本次的分享,本次分享里我也介紹了我們美團(tuán)在電話業(yè)務(wù)上的一些實(shí)踐,為大家介紹了sharedWorker這種方案,其實(shí)sharedWorker這種方案不光是在電話的場(chǎng)景中,其實(shí)在日常工作中,比如IM系統(tǒng)中也是可以去使用的。
標(biāo)簽:迪慶
鞍山
咸寧
游戲
內(nèi)蒙古