搭建基于云端的中间层以支持跨平台的智能视觉服务
不斷演進(jìn)的應(yīng)用場(chǎng)景
初級(jí)應(yīng)用場(chǎng)景—宅在家里
場(chǎng)景:Bob同學(xué)有一天在網(wǎng)上看到了一張建筑物的圖片,大發(fā)感慨:"好漂亮啊!這是哪里?我要去親眼看看!"Bob同學(xué)不想問別人,可笑的自尊心讓他覺得這肯定是個(gè)著名的建筑,如果自己不知道多丟臉!怎么解決Bob同學(xué)的煩惱呢?
?
我們看看微軟認(rèn)知服務(wù)是否能幫助到Bob同學(xué),打開這個(gè)鏈接:
https://azure.microsoft.com/zh-cn/services/cognitive-services/computer-vision/
向下卷滾屏幕,到"識(shí)別名人和地標(biāo)"部分,在"圖像URL"編輯框里輸入了這張圖片的網(wǎng)絡(luò)地址,然后點(diǎn)擊"提交",一兩秒后,就能看到關(guān)于這張圖片的文字信息了(見下圖),原來這個(gè)建筑叫做"Space Needle"!但是呢,不太人性化,因?yàn)槭荍SON文件格式的,幸好Bob同學(xué)是個(gè)程序員,Bob同學(xué)想把這個(gè)場(chǎng)景做成一個(gè)實(shí)際的應(yīng)用,以幫助他人解決類似問題。
?
Bob同學(xué)剛學(xué)習(xí)了微軟認(rèn)知服務(wù)的應(yīng)用教程,于是打開Windows 10 PC,啟動(dòng)VS2017,安裝了Visual Studio Tools for AI后,先在Server Explorer->AI Tools->Azure Cognitive Services上點(diǎn)擊鼠標(biāo)右鍵,Create New Cognitive Service,API Type選擇ComputerVision (如果已經(jīng)有了就不需要重復(fù)申請(qǐng)了),得到了Key和Endpoint,按照《漫畫翻譯篇》教程所講述的過程,照貓畫虎,花了一兩個(gè)小時(shí),就把應(yīng)用做好了。
開發(fā)技術(shù)文檔在這個(gè)鏈接里面。
目前Bob的同學(xué)的應(yīng)用架構(gòu)是這樣的:
(上圖中右側(cè)的框圖內(nèi)的文字是“地標(biāo)識(shí)別”,下同)?
中級(jí)應(yīng)用場(chǎng)景—出門在外
Bob同學(xué)很滿意地試著自己的作品,長(zhǎng)城,天安門,故宮……都能認(rèn)出來!但是,Bob同學(xué)忽然想到,如果出門在外遇到一個(gè)漂亮建筑,沒有PC,只有手機(jī)怎么辦?于是Bob同學(xué)又啟動(dòng)了VS2017,創(chuàng)建了一個(gè)Xamarin項(xiàng)目,重用了PC上的code,把這個(gè)場(chǎng)景搞定了:拿起Android或者iOS手機(jī),對(duì)著建筑物一框,幾秒后就會(huì)有結(jié)果返回,告訴用戶眼前的這個(gè)建筑叫什么名字。太方便啦!
所以,Bob同學(xué)的應(yīng)用架構(gòu)進(jìn)化了一些:
高級(jí)應(yīng)用場(chǎng)景—擴(kuò)展信息
Bob同學(xué)用手機(jī)給很多同學(xué)們安裝后顯擺了幾天,有人問他:"Space Needle是啥?"
"這個(gè)……這個(gè)……哦!你可以在Bing上搜索一下啊!"
"你的程序能不能順便幫我們搜索一下呢?"
"嗯……啊……當(dāng)然啦!"硬著頭皮說了這句話后,Bob同學(xué)趕緊回去查微軟認(rèn)知服務(wù)的網(wǎng)站了。Bingo! 在這里了:
https://azure.microsoft.com/zh-cn/services/cognitive-services/bing-entity-search-api/
與前面的教程里描述的類似,申請(qǐng)了搜索服務(wù)后,也得到了Endpoint和Key,照貓畫虎地把客戶端改了一下,增加了搜索服務(wù)的功能,銜接到了地標(biāo)識(shí)別邏輯的后面,也就是把地標(biāo)識(shí)別的結(jié)果"Space Needle"作為關(guān)鍵字傳送給實(shí)體搜索服務(wù),然后再把結(jié)果展示出來。
注意這里要申請(qǐng)的API在Bing.Search.v7里面,技術(shù)文檔在這個(gè)鏈接里面。
于是Bob同學(xué)的應(yīng)用架構(gòu)變成了這個(gè)樣子:
(上圖中右側(cè)的框圖內(nèi)的文字是“實(shí)體搜索”,下同)
這個(gè)圖的連接線看著好奇怪,黃色的線為什么不連接到左側(cè)的客戶端上呢?這里特意這樣畫,為了表示黃色的連接(REST API調(diào)用)是接在藍(lán)色的連接之后的,有依賴關(guān)系。在下一個(gè)場(chǎng)景里,大家會(huì)看到更復(fù)雜的例子。
終級(jí)的應(yīng)用場(chǎng)景—并發(fā)處理
在一陣手忙腳亂的部署之后,所有的同學(xué)的手機(jī)都可以使用這個(gè)新App了,Bob同學(xué)很自豪。這時(shí),學(xué)習(xí)委員走過來了(也是體育課代表),問Bob:"出門旅游的機(jī)會(huì)不多,我想用這個(gè)App做更多的日常的事情,比如掃一張照片,就能知道這個(gè)明星的名字和背景資料,或者是照一件衣服就能知道在哪里買,還有看到一個(gè)電話號(hào)碼后,想用手機(jī)掃一下就能記錄下來……這些能辦到嗎?"
Bob同學(xué)邊聽邊鎮(zhèn)靜地點(diǎn)頭,其實(shí)后背都濕透了,嘴上不能服軟:"我回去想想辦法吧!"
Bob同學(xué)翻閱了微軟認(rèn)知服務(wù)的所有技能,在紙上畫了一個(gè)草圖,來解決學(xué)習(xí)委員的問題:
(上圖中右側(cè)的框圖內(nèi)的文字是“名人識(shí)別”,下同)
同時(shí)有三根藍(lán)線都從同一個(gè)客戶端連接到不同的認(rèn)知服務(wù)上,是因?yàn)榭蛻舳顺绦虿⒉恢酪R(shí)別的物體是建筑物呢,還是人臉呢,或是電話號(hào)碼呢?需要一個(gè)個(gè)的去嘗試調(diào)用三個(gè)API,如果返回有效的結(jié)果,就代表識(shí)別出了該實(shí)體的類型。
畫完圖后,本來以為會(huì)輕松的Bob同學(xué),忽然發(fā)現(xiàn)他需要不斷更新三個(gè)客戶端的代碼:PC,Android,iOS,來滿足更多的學(xué)習(xí)委員的需要(如同右側(cè)那個(gè)上下方向的箭頭一樣是可擴(kuò)充的),然后再分別發(fā)布出去!并且他意識(shí)到了另外一個(gè)問題:每個(gè)客戶端需要訪問認(rèn)知服務(wù)四次才能完成這個(gè)場(chǎng)景!不但網(wǎng)絡(luò)速度對(duì)用戶體驗(yàn)造成了影響,而且流量就是錢啊!如果將來需要支持更多的識(shí)別類型,連接線的增長(zhǎng)速率將會(huì)是幾何級(jí)別的!
My Omnipotent God!Tell Me How!
重構(gòu)
Bob同學(xué)想起了剛買到的《構(gòu)建之法》第三版,仔細(xì)閱讀了第9,10,11三章,明白了一些基本的概念:
需求是不斷演進(jìn)的,任何一個(gè)軟件都需要不斷迭代
定位典型用戶(學(xué)習(xí)委員)和常用場(chǎng)景(出門旅游還是宅在家里)
在需求分析階段,要搞清楚在現(xiàn)實(shí)世界里,都有哪些實(shí)體,如何抽象出我們真正關(guān)心的屬性和方法
PM/用戶提出的需求,程序員需要認(rèn)真理解,深入到實(shí)際問題中進(jìn)行抽象,找到實(shí)體和屬性/方法在軟件系統(tǒng)中的表現(xiàn),構(gòu)建框架,然后再編碼(想明白了再動(dòng)手,不能頭疼醫(yī)頭,腳疼醫(yī)腳)
"我要重構(gòu)!"房間里響起了Bob同學(xué)的吶喊聲,把隔壁鄰居嚇了一跳:"這小伙子是不是又失戀了?"
小提示:需求的"演進(jìn)"與"變化"是兩回事兒,不要混為一談來掩蓋項(xiàng)目經(jīng)理對(duì)需求的分析與把握的不足。簡(jiǎn)單地舉例來說,當(dāng)項(xiàng)目經(jīng)理說"地標(biāo)識(shí)別看上去很少有人用,廢掉吧,咱們做個(gè)名人識(shí)別",這個(gè)屬于需求變化。
認(rèn)知服務(wù)應(yīng)用構(gòu)建方式
兩種構(gòu)建方式的比較
微軟認(rèn)知服務(wù)應(yīng)用方式有兩大類:
用客戶端直接訪問認(rèn)知服務(wù)
客戶端通過中間服務(wù)層訪問認(rèn)知服務(wù)
第一種模式很好理解:微軟認(rèn)知服務(wù)7x24小時(shí)在云端提供服務(wù),開發(fā)者在智能手機(jī)或者PC上編寫客戶端應(yīng)用程序,調(diào)用REST API直接訪問云端。但是這種模式有一些潛在的問題,如:
客戶端代碼量大邏輯復(fù)雜
客戶端需要密集發(fā)布并持續(xù)維護(hù)
客戶端與服務(wù)器端耦合度高
客戶端多次訪問服務(wù)器
網(wǎng)絡(luò)安全性低
無論客戶端有多少,依賴的認(rèn)知服務(wù)有多少,其實(shí)還是下圖所示的模式:
目前Bob同學(xué)就是使用這種方式,來不斷演進(jìn)他的應(yīng)用,終于遇到了棘手的問題。
為什么呢?因?yàn)榭蛻舳艘坏┌l(fā)布到用戶手里,對(duì)發(fā)布者來說就比較被動(dòng)了,需要非常小心地維護(hù)升級(jí),每次都要全面測(cè)試,測(cè)試點(diǎn)多而復(fù)雜。即使有應(yīng)用商店可以幫助發(fā)布,但要把所有用戶都升級(jí)到最新版本,還是需要很長(zhǎng)時(shí)間的,這意味著你還需要向后兼容。
第二種模式可以用簡(jiǎn)單的圖來表示:
有規(guī)模的商業(yè)化應(yīng)用,一般都采用這種模式搭建應(yīng)用架構(gòu),以便得到以下好處:
客戶端代碼量小邏輯簡(jiǎn)單
客戶端不需要密集發(fā)布和維護(hù)
客戶端與認(rèn)知服務(wù)的耦合度低
客戶端單次訪問服務(wù)器
網(wǎng)絡(luò)安全性高
拉個(gè)表格,一目了然:
直接訪問模式 | 中間服務(wù)層模式 | |
客戶端代碼 | 量大,邏輯復(fù)雜 | 量小,邏輯簡(jiǎn)單 |
發(fā)布與維護(hù) | 密集,改一點(diǎn)兒東西都需要重新發(fā)布新版本 | 中間層服務(wù)能屏蔽大量邏輯,不需要在客戶端代碼中體現(xiàn) |
客戶端與認(rèn)知服務(wù)的耦合度 | 極高 | 很低 |
客戶端與認(rèn)知服務(wù)的通信量 | 頻繁,多次 | 單次 |
對(duì)認(rèn)知服務(wù)密鑰的保護(hù) | 低,用Fiddler就可以"看到"認(rèn)知服務(wù)密鑰 | 高,把費(fèi)德勒叫來也不行 |
服務(wù)器端代碼 | 無 | 有 |
多種客戶端支持 | 復(fù)雜 | 簡(jiǎn)單 |
如果有了中間服務(wù)層,客戶端的工作就簡(jiǎn)化到只做與中間服務(wù)層通信,提交請(qǐng)求,接收數(shù)據(jù),用戶交互等等,而復(fù)雜的商業(yè)邏輯,可以在中間服務(wù)層實(shí)現(xiàn)。而且在更新業(yè)務(wù)邏輯的時(shí)候,大多數(shù)情況下,只需要修改中間服務(wù)層的代碼,無需更新客戶端。
對(duì)于多種客戶端的支持問題,用微軟VS2017提供的跨平臺(tái)Xamarin架構(gòu)可以解決,開發(fā)者只需要寫C#程序,就可以把應(yīng)用部署在Windows/Android/iOS設(shè)備上,一套代碼搞定。
中間層服務(wù)也不是十全十美,帶來的問題有二:1)需要云端支持,要花錢的;2)圖片傳輸?shù)倪^程會(huì)發(fā)生兩次,第一次是從客戶端到中間層,第二次是中間層到微軟認(rèn)知服務(wù),這樣會(huì)增加網(wǎng)絡(luò)時(shí)間上的開銷。但是有個(gè)好消息是第二次傳輸所花費(fèi)的時(shí)間要比第一次小一個(gè)數(shù)量級(jí),因?yàn)槭欠?wù)器對(duì)服務(wù)器的通信,如果你自己的服務(wù)器也放在Azure上,那么和微軟認(rèn)知服務(wù)的服務(wù)器就可能在一個(gè)大機(jī)房里了,局域網(wǎng)的速度!并且,這個(gè)開銷與客戶端多次訪問服務(wù)器相比,也是占優(yōu)的選擇,所以大家可以在有條件的情況下盡量使用第二種方式做商業(yè)應(yīng)用。
另外一種分類方式
如果關(guān)注于對(duì)認(rèn)知服務(wù)的使用,也可以用另外一種分類方式:
單獨(dú)使用某個(gè)服務(wù)
串行使用兩個(gè)以上的服務(wù)
并行使用兩個(gè)以上的服務(wù)
串并行混合使用三個(gè)以上的服務(wù)
比如上面的最后的場(chǎng)景,實(shí)際上是第四種方式:先并行使用了地標(biāo)識(shí)別、名人識(shí)別、OCR,然后又串行使用了實(shí)體搜索服務(wù)。
合理的應(yīng)用架構(gòu)
我們來幫助Bob同學(xué)重新設(shè)計(jì)一下他的應(yīng)用架構(gòu):
上圖只是個(gè)粗略的架構(gòu),中間服務(wù)層具體如何實(shí)現(xiàn)呢?
我們常聽到的一句話是"這個(gè)問題你只要充值就能解決了" 沒錯(cuò),做信仰充值:先安裝Visual Studio 2017 and Tools for AI,再接著往下看。
從零開始構(gòu)建中間服務(wù)層
環(huán)境要求與基本步驟
環(huán)境要求:
強(qiáng)烈建議使用Windows 10 較新的版本(筆者使用的是Version 1803)。使用Windows 7也應(yīng)該可以,但是筆者沒有做過具體測(cè)試。
至少8G內(nèi)存。只有4G的話可能會(huì)比較吃力。
CPU主頻2.5GHz以上,最好是i7。1.9GHz + i5的配置比較吃力。
可以訪問互聯(lián)網(wǎng)上的微軟認(rèn)知服務(wù)
基本步驟:
安裝Visual Studio 2017 Community或以上版本,注意要安裝服務(wù)器開發(fā)包,否則找不到第4步的模板。
下載安裝Microsoft Visual Studio Tools for AI擴(kuò)展包,安裝完后重啟VS2017。
在Server Explorer中的AI Tools->Azure Cognitive Services菜單上,點(diǎn)擊鼠標(biāo)右鍵,申請(qǐng)兩個(gè)認(rèn)知服務(wù):Bing.Search.V7和ComputerVision。關(guān)于如何申請(qǐng)服務(wù),請(qǐng)看本系列文章的上一篇。
在VS2017中創(chuàng)建一個(gè)ASP.NET Core Web Application,在里面編寫中間服務(wù)層的邏輯代碼。
利用簡(jiǎn)單的客戶端進(jìn)行測(cè)試。
下面我們展開第4步做詳細(xì)說明。
創(chuàng)建應(yīng)用服務(wù)
在VS2017中創(chuàng)建一個(gè)新項(xiàng)目,選擇Web->ASP.NET Core Web Application,如下圖:
給項(xiàng)目取個(gè)名字叫做"CognitiveMiddlewareService",Location自己隨便選,然后點(diǎn)擊OK進(jìn)入下圖:
在上圖中選擇"API",不要?jiǎng)悠渌魏芜x項(xiàng),點(diǎn)擊OK,VS一陣忙碌之后,就會(huì)生成下圖的解決方案:
這是一個(gè)最基本的ASP.NET Core Web App的框架代碼,我們將會(huì)在這個(gè)基礎(chǔ)上增加我們自己的邏輯。在寫代碼之前,我們先一起搞清楚兩個(gè)關(guān)于ASP.NET Core框架的基本概念。
ASP.NET Core的兩個(gè)基本概念
依賴注入
ASP.NET Core?支持依賴關(guān)系注入?(DI)?軟件設(shè)計(jì)模式,這是一種在類及其依賴關(guān)系之間實(shí)現(xiàn)控制反轉(zhuǎn)?(IoC)?的技術(shù),原文鏈接在這里。簡(jiǎn)單的說就是:
定義一個(gè)接口
定義一個(gè)類實(shí)現(xiàn)這個(gè)接口
在框架代碼Startup.cs中注冊(cè)這個(gè)接口和類
在要用到這個(gè)服務(wù)的類(使用者)的構(gòu)造函數(shù)中引入該接口,并保存到成員變量中
在使用者中直接使用該成員變量->方法名稱
我們?cè)诤竺娴拇a中會(huì)有進(jìn)一步的說明。
發(fā)起HTTP請(qǐng)求
框架提供了一種機(jī)制,可以通過注冊(cè)IHttpClientFactory用于創(chuàng)建HttpClient實(shí)例,這種方式帶來以下好處:
提供一個(gè)集中位置,用于命名和配置HttpClient實(shí)例
通過委托HttpClient中的處理程序來提供中間層服務(wù)
管理基礎(chǔ)HttpClientMessageHandler實(shí)例的池和生存期,避免在手動(dòng)管理HttpClient生存期時(shí)出現(xiàn)常見的DNS問題
添加可配置的記錄體驗(yàn),以處理HttpClientFactory創(chuàng)建的客戶端發(fā)送的所有請(qǐng)求
以上是原文提供的解釋,鏈接在這里??赡鼙容^難理解,但坊間一直流傳著HttpClient不能釋放的問題,所以用IHttpClientFactory應(yīng)該至少可以解決這個(gè)問題。
但是在使用它之前,我們需要安裝一個(gè)NuGet包。在解決方案的名字上點(diǎn)擊鼠標(biāo)右鍵,在出現(xiàn)的菜單中選擇"Manage NuGet Packages…",在出現(xiàn)的如下窗口中,輸入"Microsoft.extensions.http",然后安裝Microsoft.Extensions.Http包:
安裝完畢后,需要在Startup.cs文件里增加依賴注入:services.AddHttpClient()。
文件目錄組織方式和層次關(guān)系
先在生成好的框架代碼的基礎(chǔ)上,建立下圖所示的文件夾:
CognitiveServices
MiddlewareService
Processors
Controllers是基礎(chǔ)框架帶的文件夾,不需要自己創(chuàng)建。
創(chuàng)建這些文件夾的目的,是讓我們自己能夠縷清邏輯,寫代碼時(shí)注意調(diào)用和被調(diào)用的關(guān)系,用必要的層次來體現(xiàn)軟件的抽象。以本案例來說,模塊劃分與層次抽象應(yīng)該如下圖所示(下圖中帶箭頭的實(shí)線表示調(diào)用關(guān)系):
基礎(chǔ)服務(wù)層
藍(lán)色的層,也就是CognitiveServices文件夾,包含了兩個(gè)訪問認(rèn)知服務(wù)的基礎(chǔ)功能:VisionService和EntitySearchService。
它們返回了最底層的結(jié)果:VisionResult和EntityResult。這一層的每個(gè)服務(wù),只專注于自己的網(wǎng)絡(luò)請(qǐng)求與接收結(jié)果的任務(wù),不管其它的事情。如果認(rèn)知服務(wù)編程接口有變化,只修改這一層的代碼。
集成服務(wù)層
黃色的層,也就是MiddlewareService文件夾,是我們自己包裝認(rèn)知服務(wù)的邏輯層,在這個(gè)層中的代碼,每一個(gè)服務(wù)都是用串行方式訪問認(rèn)知服務(wù)的:在用第一個(gè)輸入(假設(shè)是圖片)得到第一個(gè)認(rèn)知服務(wù)的返回結(jié)果后(假設(shè)是文字),再把這個(gè)返回結(jié)果輸入到第二個(gè)認(rèn)知服務(wù)中去,得到內(nèi)容更豐富的結(jié)果。
它們返回了集成后的結(jié)果:LandmarkResult和CelebrityResult,這兩個(gè)結(jié)果的定義已經(jīng)對(duì)認(rèn)知服務(wù)返回的結(jié)果進(jìn)行了進(jìn)一步的抽象和隔離,其目的是讓后面的邏輯代碼只針對(duì)這一層的抽象進(jìn)行處理,不必考慮更底層的數(shù)據(jù)結(jié)構(gòu)。
任務(wù)調(diào)度層
綠色的層,也就是Processors文件夾,是包裝業(yè)務(wù)邏輯的代碼,在本層中做任務(wù)分發(fā),用并行方式同時(shí)訪問兩個(gè)以上的認(rèn)知服務(wù),將返回的結(jié)果聚合在一起,并根據(jù)需要進(jìn)行排序,最后生成要返回的結(jié)果AggregatedResult。
CognitiveServices文件夾
在這個(gè)文件夾中,我們需要添加以下文件:
IVisionService.cs
VisionService.cs
VisionResult.cs
IEntitySearchService.cs
EntitySearchService.cs
EntityResult.cs
Helper.cs
?
IVisionService.cs - 訪問影像服務(wù)的接口定義,需要依賴注入
VisionService.cs - 訪問影像服務(wù)的邏輯代碼
小提示:上面的代碼中的Key1/Key2是不可用的,請(qǐng)用自己申請(qǐng)的Key和對(duì)應(yīng)的Endpoint來代替。
VisionResult.cs – 認(rèn)知服務(wù)返回的結(jié)果類,用于反序列化
IEntitySearchService.cs – 訪問實(shí)體搜索服務(wù)的接口定義,需要依賴注入
EntitySearchService.cs – 訪問實(shí)體搜索服務(wù)的邏輯代碼
小提示:上面的代碼中的Key1/Key2是不可用的,請(qǐng)用自己申請(qǐng)的Key和對(duì)應(yīng)的Endpoint來代替。
?
EntityResult.cs – 認(rèn)知服務(wù)返回的結(jié)果類,用于反序列化
Helper.cs – 幫助函數(shù)
MiddlewareService文件夾
在這個(gè)文件夾中,我們需要添加以下文件:
ICelebrityService.cs
CelebrityService.cs
CelebrityResult.cs
ILandmarkService.cs
LandmarkService.cs
LandmarkResult.cs
?
ICelebrityService.cs – 包裝多個(gè)串行的認(rèn)知服務(wù)來實(shí)現(xiàn)名人識(shí)別的中間服務(wù)層的接口定義,需要依賴注入
CelebrityService.cs?– 包裝多個(gè)串行的認(rèn)知服務(wù)來實(shí)現(xiàn)名人識(shí)別中間服務(wù)層的邏輯代碼
小提示:上面的代碼中,用CelebrityResult接管了實(shí)體搜索結(jié)果和名人識(shí)別結(jié)果的部分有效字段,以達(dá)到解耦/隔離的作用,后面的代碼只關(guān)心CelebrityResult如何定義的即可。
?
CelebrityResult.cs – 抽象出來的名人識(shí)別服務(wù)的返回結(jié)果
ILandmarkService.cs – 包裝多個(gè)串行的認(rèn)知服務(wù)來實(shí)現(xiàn)地標(biāo)識(shí)別的中間服務(wù)層的接口定義,需要依賴注入
LandmarkService.cs – 包裝多個(gè)串行的認(rèn)知服務(wù)來實(shí)現(xiàn)地標(biāo)識(shí)別的中間服務(wù)層的邏輯代碼
小提示:上面的代碼中,用LandmarkResult接管了實(shí)體搜索結(jié)果和地標(biāo)識(shí)別結(jié)果的部分有效字段,以達(dá)到解耦/隔離的作用,后面的代碼只關(guān)心LandmarkResult如何定義的即可。
?
LandmarkResult.cs – 抽象出來的地標(biāo)識(shí)別服務(wù)的返回結(jié)果
Processors文件夾
在這個(gè)文件夾中,我們需要添加以下文件:
IProcessService.cs
ProcessService.cs
AggregatedResult.cs
?
IProcessService.cs – 任務(wù)調(diào)度層服務(wù)的接口定義,需要依賴注入
ProcessService.cs – 任務(wù)調(diào)度層服務(wù)的邏輯代碼
小提示:大家可以看到上面這個(gè)文件中有很多綠色的注釋,帶有todo文字的,對(duì)于一個(gè)更復(fù)雜的系統(tǒng),可以用這些todo中的描述來設(shè)計(jì)獨(dú)立的模塊。
AggregatedResult.cs – 任務(wù)調(diào)度層服務(wù)的最終聚合結(jié)果定義
其他文件的修改
ValuesControllers.cs 注意Post的參數(shù)從[FromBody]變成了[FromForm],以便接收上傳的圖片流數(shù)據(jù)
Startup.cs
除了第一行的services.AddMvc()以外,后面所有的行都是我們需要增加的依賴注入代碼。
層次關(guān)系總結(jié)
總結(jié)一下,從調(diào)用關(guān)系上看,是這個(gè)次序:
Controller -> ProcessService -> LandmarkService/CelebrityService -> VisionService/EntitySearchService
其中:
·???????????Controller是個(gè)Endpoint
·???????????ProcessService負(fù)責(zé)任務(wù)調(diào)度
·???????????LandmarkService/CelebrityService是個(gè)集成服務(wù),封裝了串行調(diào)用底層服務(wù)的邏輯
·???????????VisionService/EntitySearchService是基礎(chǔ)服務(wù),相當(dāng)于最底層的原子操作
從數(shù)據(jù)結(jié)構(gòu)上看,進(jìn)化的順序是這樣的:
VisionResult/EntityResult -> CelebrityResult/LandmarkResult -> AggregatedResult
其中:
·???????????VisionResult/EntityResult是最底層返回的原始結(jié)果,主要用于反序列化
·???????????CelebrityResult/LandmarkResult是集成了多個(gè)原始結(jié)果后的抽象結(jié)果,好處是隔離了原始結(jié)果中的一些噪音,解耦,只返回我們需要的字段
·???????????AggregatedResult是聚合在一起的結(jié)果,主要用于排序和生成返回JSON數(shù)據(jù)
?
完整的中間服務(wù)層系統(tǒng)棧
有的人會(huì)問了:有必要搞這么復(fù)雜嗎?這幾個(gè)調(diào)用在一個(gè)幫助函數(shù)里不就可以搞定了嗎?
確實(shí)是這樣,如果不考慮應(yīng)用擴(kuò)展什么的,那就用一個(gè)幫助函數(shù)搞定;如果想玩兒點(diǎn)大的,那么下面這張圖就是一個(gè)完整系統(tǒng)的Stack圖,這個(gè)系統(tǒng)通過組合調(diào)用多種微軟認(rèn)知服務(wù)/微軟地圖服務(wù)/微軟實(shí)體服務(wù)等,能夠提供給用戶的智能設(shè)備豐富的視覺對(duì)象識(shí)別體驗(yàn)。
上圖包含了以下層次:
·???????????Endpoints
?? 兩個(gè)Endpoint,一個(gè)處理圖片輸入,另一個(gè)處理文本輸入
·???????????Processing and Classifier
?? 包含圖像/文字的預(yù)處理/預(yù)分類
·???????????Task Dispatcher
?? 并行調(diào)用多種服務(wù)并協(xié)調(diào)同步關(guān)系
·???????????API agent and Recognizer
?? 組合調(diào)用各種API,內(nèi)置的識(shí)別器(比如正則表達(dá)式)
·???????????APIs
?? 各種認(rèn)知服務(wù)API
·???????????Processors
?? 隔離層/聚合層/排序器的組合稱呼
·???????????Adaptive Card Generator
?? 生成微軟最新推出的Adaptive Card技術(shù)的數(shù)據(jù),供跨平臺(tái)客戶端接收并渲染
·???????????Assistant Component
?? 其它輔助組件
對(duì)中間服務(wù)層的測(cè)試
基本概念與環(huán)境搭建
做好了一個(gè)中間層服務(wù),不是說簡(jiǎn)單地向Azure上一部署就算完事兒了。任何一個(gè)商用的軟件,都需要嚴(yán)格的測(cè)試,對(duì)于普通的手機(jī)/客戶端軟件的測(cè)試,相信很多人都知道,覆蓋功能點(diǎn),各種條件輸入,等等等等。對(duì)于中間層服務(wù),除了功能點(diǎn)外,性能方面的測(cè)試尤其重要。
如何進(jìn)行測(cè)試呢?工欲善其事必先利其器,先看工具:
ASP.NET Core Web API有一套測(cè)試工具,請(qǐng)看這個(gè)鏈接:https://docs.microsoft.com/en-us/aspnet/core/test/?view=aspnetcore-2.1,它講述了一些列的方法,我們不再贅述,本文所要描述的是三種面向場(chǎng)景的測(cè)試方法:負(fù)載(較重的壓力)測(cè)試,(較輕的壓力)性能測(cè)試,(中等的壓力)穩(wěn)定性測(cè)試。不是以show code為主,而是以講理念為主,懂得了理念,code容易寫啦。
對(duì)于一個(gè)普通的App,我們用界面交互的方式進(jìn)行測(cè)試。對(duì)于一個(gè)service,它的界面就相當(dāng)于REST API,我們可以從客戶端發(fā)起測(cè)試,自動(dòng)化程度較高。
在Visual Studio 2017,有專門的Load Test工具可以幫助我們完成在客戶端編寫測(cè)試代碼,調(diào)整各種測(cè)試參數(shù),然后發(fā)起測(cè)試,具體的鏈接在這里。
有了工具,再看方法和理念:
在本文中,我們主要從概念上講解一下針對(duì)含有認(rèn)知服務(wù)的中間服務(wù)層的測(cè)試方法,因?yàn)檎J(rèn)知服務(wù)本身如果訪問量大的話,是要收取費(fèi)用的!
小提示:各個(gè)認(rèn)知服務(wù)的費(fèi)用標(biāo)準(zhǔn)不同,請(qǐng)仔細(xì)閱讀相關(guān)網(wǎng)頁,以免在進(jìn)行大量的測(cè)試時(shí)引起不必要的費(fèi)用發(fā)生。
負(fù)載測(cè)試 Load Test
測(cè)試目的
模擬多個(gè)并發(fā)用戶訪問中間層服務(wù),集中發(fā)生在一個(gè)持續(xù)的時(shí)間段內(nèi),以衡量服務(wù)質(zhì)量。負(fù)載測(cè)試不斷的發(fā)展下去,負(fù)載越來越大,就會(huì)變成極限測(cè)試,最終把機(jī)器跑癱為止。這種測(cè)試可以幫助開發(fā)者知道在單機(jī)環(huán)境下能支持多少用戶,進(jìn)而決定在Azure上要申請(qǐng)多少機(jī)器。
測(cè)試環(huán)境
注意!我們不是在測(cè)試認(rèn)知服務(wù)的性能,是要測(cè)試自己的中間層服務(wù)的性能,所以如下圖所示:
?
?
?
?
要把認(rèn)知服務(wù)用一個(gè)模擬的mock up service來代替,這個(gè)mock up service可以自己簡(jiǎn)單地用ASP.NET搭建一個(gè),接收請(qǐng)求后,不做任何邏輯處理,直接返回JSON字符串,但是中間需要模擬認(rèn)知服務(wù)的處理時(shí)間,故意延遲2~3秒。
另外一個(gè)原因是,認(rèn)知服務(wù)比較復(fù)雜,可能不能滿足很高的QPS的要求,而用自己的模擬服務(wù)可以到達(dá)極高的QPS,這樣就不會(huì)正在測(cè)試中產(chǎn)生瓶頸。
網(wǎng)絡(luò)環(huán)境為局域網(wǎng)內(nèi)部,亦即客戶端、中間層、模擬服務(wù)都在局域網(wǎng)內(nèi)部即可,這樣可以避免網(wǎng)絡(luò)延遲帶來的干擾。
測(cè)試方法與結(jié)果
在本例中,我們測(cè)試了8輪,每輪都模擬不同的并發(fā)用戶數(shù)持續(xù)運(yùn)行一小時(shí),最終結(jié)果如下:
concurrent users? | Idle? | 1 user? | 3 users? | 5 users? | 10 users? | 25 users? | 50 users? | 75 users? | 100 users? |
CPU? | 0%? | <1%? | <1%? | 1%? | 2.5%? | 6%? | 12%? | 17%? | 21%? |
Memory(MB)? | 110? | 116? | 150? | 158? | 164? | 176? | 260? | 301? | 335? |
Latency(s)? | 0? | 2.61? | 2.61? | 2.61? | 2.62? | 2.63? | 2.64? | 2.67? | 2.7? |
Total Req.? | 0? | 1,377? | 4,124? | 6,885? | 13,666? | 34,221? | 67,976? | 100,948? | 132,894? |
Failed Req.? | 0? | 0? | 0? | 0? | 0? | 0? | 0? | 0? | 0? |
QPS? | 0.00? | 0.38? | 1.15? | 1.91? | 3.80? | 9.51? | 18.88? | 28.04? | 36.92? |
從圖表可以看出,CPU/Memory/QPS都是線性增長(zhǎng)的,意味著是可以預(yù)測(cè)的。延遲(Latency)是平緩的,不會(huì)因?yàn)椴l(fā)用戶變多而變慢,很健康。
?
可靠性測(cè)試 Stability Test
測(cè)試目的
在一個(gè)足夠長(zhǎng)的時(shí)間內(nèi)持續(xù)測(cè)試服務(wù),中等負(fù)載,以檢查其可靠性。"足夠長(zhǎng)"一般定義為12小時(shí)、48小時(shí)、72小時(shí)等等??梢哉J(rèn)為,被測(cè)對(duì)象只要跑夠了預(yù)定的時(shí)長(zhǎng),就算是穩(wěn)定性過關(guān)了。
測(cè)試環(huán)境
同理,我們要測(cè)試的是中間層服務(wù),而不是認(rèn)知服務(wù)。測(cè)試環(huán)境與上面相同,也是使用模擬的認(rèn)知服務(wù),因?yàn)?2小時(shí)的測(cè)試時(shí)間,會(huì)發(fā)送大量的請(qǐng)求,很可能超出了當(dāng)月限額而收取費(fèi)用。
?
?
?
?
網(wǎng)絡(luò)環(huán)境仍然使用局域網(wǎng)。
測(cè)試方法與結(jié)果
模擬10個(gè)并發(fā)用戶,持續(xù)向中間層服務(wù)發(fā)請(qǐng)求12小時(shí),測(cè)試結(jié)果如下表:
Sample point? | CPU? | Memory? | Latency? | Total Request? | Failed? | QPS? |
1:00:00? | 2.5%? | 140M? | 2.63 second? | 13,730? | 0? | 3.81? |
2:00:00? | 2.5%? | 160M? | 2.61 second? | 13,741? | 0? | 3.82? |
3:00:00? | 2.5%? | 150M? | 2.62 second? | 13,728? | 0? | 3.81? |
…...? | ||||||
Total/Average? | 2.5%? | 150M? | 2.62? | 164,772? | 0? | 3.81? |
從CPU/Memory/Latency/QPS上來看,在12個(gè)小時(shí)內(nèi),都保持得非常穩(wěn)定,說明服務(wù)器不會(huì)因?yàn)殚L(zhǎng)時(shí)間運(yùn)行而變得不穩(wěn)定。
性能測(cè)試 Performance Test
測(cè)試目的
測(cè)試端對(duì)端(e2e)的請(qǐng)求/響應(yīng)時(shí)間。這是針對(duì)某個(gè)服務(wù)場(chǎng)景的測(cè)試,想得到具體的數(shù)值,所以不需要很大的負(fù)載壓力。
測(cè)試環(huán)境
?
?
?
這次我們需要使用真實(shí)的認(rèn)知服務(wù),網(wǎng)絡(luò)環(huán)境也使用真實(shí)的互聯(lián)網(wǎng)環(huán)境。亦即需要把中間服務(wù)層部署到互聯(lián)網(wǎng)上后進(jìn)行測(cè)試,因?yàn)橛媚M環(huán)境和局域網(wǎng)測(cè)試出來的數(shù)據(jù)不能代表實(shí)際的用戶使用情況。?
測(cè)試方法與結(jié)果
模擬1個(gè)用戶,持續(xù)向中間服務(wù)層發(fā)送請(qǐng)求1小時(shí)。然后模擬3個(gè)并發(fā)用戶,持續(xù)向中間服務(wù)層發(fā)送請(qǐng)求10分鐘。這兩種方法都不會(huì)對(duì)認(rèn)知服務(wù)帶來很大的壓力。
在得到了一系列的數(shù)據(jù)以后,每組數(shù)據(jù)都會(huì)有響應(yīng)時(shí)間,我們把它們按照從長(zhǎng)(慢)到短(快)的順序排列,得到下圖(其中橫坐標(biāo)是用戶數(shù),縱坐標(biāo)是響應(yīng)時(shí)間):
?
?
?
?
?
?
?
一般來說,我們要考察幾個(gè)點(diǎn),P90/P95/P99,比如P90的含義是:有90%的用戶的響應(yīng)時(shí)間小于等于2449ms。這意味著如果有極個(gè)別用戶響應(yīng)時(shí)間在10秒以上時(shí),是一種正常的情況;如果很多用戶(比如>5%)都在10秒以上就不正常了,需要立刻檢查服務(wù)器的運(yùn)行狀態(tài)。
最后得到的結(jié)果如下表,亦即性能指標(biāo):
Percentage? | P90? | P95? | P99? | Average? |
KPI? | <3000ms? | <3250? | <4000? | N/A? |
Server-side processing time? | 2449ms? | 2652ms? | 3571ms? | 1675ms? |
Test client e2e latency? | 3160ms? | 3368ms? | 4369ms? | 2317ms? |
?Server-side processing time: 服務(wù)器從接收到請(qǐng)求到發(fā)送回結(jié)果所花費(fèi)的時(shí)間
Test client e2e latency: 客戶端從發(fā)送請(qǐng)求到接收響應(yīng)所經(jīng)歷的時(shí)間
習(xí)題與進(jìn)階學(xué)習(xí)
增加OCR服務(wù)以提供識(shí)別文字的功能
在集成服務(wù)層增加可以識(shí)別具有標(biāo)準(zhǔn)模式的文字的服務(wù),比如電話號(hào)碼、網(wǎng)絡(luò)地址、郵件地址,這需要同時(shí)在基礎(chǔ)服務(wù)層增加OCR底層服務(wù),并在任務(wù)調(diào)度層增加一個(gè)并行任務(wù)。
部署到實(shí)際的Azure環(huán)境提供真實(shí)服務(wù)
在本地測(cè)試好服務(wù)器的基本功能后,部署到Azure上去,看看代碼在實(shí)際環(huán)境中運(yùn)行會(huì)有什么問題。因?yàn)槲覀儾荒軐?shí)時(shí)地監(jiān)控服務(wù)器,所以需要在服務(wù)層上增加log功能。
開發(fā)Android/iOS應(yīng)用來提供影像/視覺感知
可以選擇像Bob同學(xué)那樣,先用第一種方式直接訪問微軟認(rèn)知服務(wù),然后一步步演進(jìn)到中間層服務(wù)模式。建議使用VS2017 + Xamarin利器來實(shí)現(xiàn)跨平臺(tái)應(yīng)用。
圖像基本分類
在任務(wù)調(diào)度層,增加一個(gè)本地的圖像分類器,如同"todo"里的preprocess,能夠把輸入圖片分類成"有人臉"、"有地標(biāo)"、"有文字"等,然后再根據(jù)信心指數(shù)調(diào)用名人服務(wù)或地標(biāo)服務(wù),以減輕服務(wù)器的負(fù)擔(dān),節(jié)省費(fèi)用。比如,當(dāng)"有地標(biāo)"的信心指數(shù)小于0.5時(shí),就終止后面的調(diào)用。這需要訓(xùn)練一個(gè)圖片分類器,導(dǎo)出模型,再用Tools for AI做本地推理代碼。
原文地址:?https://www.cnblogs.com/ms-uap/p/9482470.html
.NET社區(qū)新聞,深度好文,歡迎訪問公眾號(hào)文章匯總 http://www.csharpkit.com
總結(jié)
以上是生活随笔為你收集整理的搭建基于云端的中间层以支持跨平台的智能视觉服务的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: CAP带你轻松玩转ASP.NETCore
- 下一篇: Microsoft宣布正式发布Linux