一個信息可視化Demo的設計(一):系統架構

一個信息可視化Demo的設計(一):系統架構

 

我可是要成爲架構師的男人啊~

                                   ----寫在前面

 

    北京兩個月的實習結束,迴歸武漢校園,期間完成了一個信息可視化的Demo,回顧這一歷程有喜悅有收穫,現分系統架構、Index & Search、算法設計和前端MVC架構四部分總結。因實習內容屬於公司內部機密,本博文只做設計思路上的記錄和探討,並不會涉及任何與公司利益相關的工作內容。

 

一、前瞻

    所謂信息可視化(Information Visualization)旨在研究大規模非數值型信息資源的視覺呈現,以及利用圖形圖像方面的技術與方法,幫助人們理解和分析數據。說白了,就是有些信息,其結構、類型比較複雜,信息量較大,各種數據之間的關係也很是糾結,無法或者很難直觀地去用人工分析和理解信息所表達的含義。因爲人對文字、數字類的東東並不敏感,但對圖像、圖表這些看的見的東東比較敏感,藉助於信息可視化的技術,充分適當地對信息進行組織整理,使用表格、圖形,圖像展現數據、信息和知識,甚至提供足夠好的交互,使得用戶能夠通過所見即所得(WYSIWYG)的方式分析、理解信息,洞察、定位隱含的漏洞或問題,發現形形色色的關係,理解在人工分析情況下不易發覺的事情,從而追根溯源地挖掘信息背景的問題與深層次原因。

    信息可視化日益成爲不同領域方向的關鍵要素:如科學技術研究工作,數字圖書館、數據挖掘、財務數據分析和市場研究、生產投靠過程的控制等。反觀現在的互聯網行業,如果說過去10年是搜索技術大行其道的10年,那麼基於互聯網應用所產生的海量數據、海量信息進行數據挖掘,以及大規模機器學習,從而分析、理解用戶行爲、用戶喜好,並提供個性化、精確化的服務將成爲未來10年最重要的技術和研究熱潮之一。以數據和信息作爲驅動而發展的時代,數據和信息將取代技術成爲新的門檻和瓶頸。

    然而,海量的信息是無法直觀的使用人工進行甄別或分析,如何更爲迅速快捷的定位和發現問題,信息可視化恰恰成爲了處理這一尷尬的切入點。如機器學習過程中,訓練出的模型即使能在樣本集上跑出很好的效果,在真實數據集面前也未必能夠達到理想的狀態,這裏面的原因一方面可能是由於樣本集的選取不具備代表性,不能夠客觀地表徵數據信息的分佈,使得訓練出來的模型泛化能力不足;另一方面可能是由於訓練過程出現了過擬合現象,爲了得到一致假設而使模型構造的極爲精細和複雜,在樣本集上進行良好,但切換到真實數據集上會就出現很多的問題。然而一個模型的訓練需要依賴的信息量是海量的,同時訓練過程大多很複雜。據我的瞭解,如某公司廣告競價排名訓練所需的數據量爲100T,訓練時長爲10小時左右,往往一個很瑣碎的細節將導致整個訓練過程的失敗。即使在訓練過程會輸出很多的Log也無法很快地分析和發現問題的原因,如果能夠提供一種可視化的方式,提供很多的輔助統計分析信息,則比人工直接去觀察數據要高效的多。

 

二、需求

    本文中與公司利益相關工作內容全部略去,抽象描述爲:現有數據原料若干,類型各異、結構複雜,數據原料爲機器學習算法的輸入/輸出,主要包括:特徵描述信息Features File、輸入數據信息Input File、人工標註樣本信息HRS File和輸出數據信息Decision Tree File。

    訓練過程概述:Features File & Input File & HRS File --> Training --> Model  --> Decision Tree File,即我們將訓練得到的模型以一種特徵的格式,寫入到叫做Decision Tree的文件中,Decision Tree所描述是實際上是一種樹形的結構。

    需求:用信息可視化的技術,實現各種類型輔助信息的圖形化展示,如(Features的分佈情況、使用率)、Training相關數據統計分析、基於HRS的訓練過程、Decision Tree的結構、不符合人工標註的結果等;提供良好的交互方式,如圖形化的拖拽、縮放、點擊,支持表達式級別的檢索。

 

三、思考

    架構師在系統架構設計階段,不應該去耦合某種具體的技術,架構的宏觀設計應以功能性爲出發點,從更高的抽象層次上考慮如何把一個系統的骨架結構給搭建起來。

    打個比方,比如說現在實現一個類QQ的IM通訊軟件,基本需求爲:

(1)用戶登入、登出;

(2)用戶之間互發消息;

(3)用戶之間傳送文件。

    作爲架構師看到的應該是C/S模式的架構,Client與Server,Client與Client之間可以互通消息,至於用何種技術實現消息的傳遞,他在這一階段的Design並不需要過分關心。

    如果是我,我會先考慮抽象,數據都應該是以一種格式進行傳遞,會有一個抽象的Message Class統一管理數據包,進而派生出多種不同類型的數據包,一種簡易的消息通訊數據包格式如下(僞碼):

//抽象的數據包基類
Abstract Class Message
{
    //XXXX…
}

//登入數據包
Class LoginMessage extends Message
{
    // XXXX…
}

//登出數據包
Class LogoutMessage extends Message
{
    // XXXX…
}

//用戶聊天數據包
Class ChatMessage extends Message
{
    // XXXX…
}

//文件傳輸數據包
Class FileTransMessage extends Message
{
    // XXXX…
}

    基於Abstract Class Message可以實現對數據包的統一發送管理。有了通訊協議,我會爲之設計一個抽象層次上消息傳遞接口,且只從功能性上考慮,並不涉及任何一種具體的通信技術。一個抽象的傳遞數據的接口(僞碼):

Interface MessageTrans
{
    long SendMessage(Message message);
    long Receive(Message message);
}

    這樣做的好處在於:接口統一,由於LoginMessage、LogoutMessage、ChatMessage以及FileTransMessage都是繼承至Message,所以我們可以藉助於這個接口發送各種不同類型的數據包。同時,你也可以根據業務需求,方便地擴展出更多類型的數據包,比如支持用戶之間發送表情、傳遞圖片等,而抽象層的接口統一使用了Message,不需要做任何修改。

    當這一層次的Design完成之後,我們就可以考慮要具體實現這個系統,應該藉助於哪種技術落地。比如剛纔提到的,Client與Server,Client與Client之間的消息通信,其本質你可以把它理解爲是兩個進程間的通信。進程間通信的方式相信大家都知道吧,我就不多說了,常見的有:管道、共享內存、消息隊列、Socket等。那麼,如果Server和Client在同一臺機器上,我們可以用管道來實現消息通信,如果Server和Client處於不同的機器上,我們可以使用Socket完成通信。很明顯的,基於某項具體技術實現的通信Class就誕生了:

//基於管道實現進程間的消息傳遞
Class PIPOMessageTrans extends MessageTrans
{
    long SendMessage(Message message)
{
    //XXXX…
}
long Receive(Message message)
{
    //XXXX…
}
}

//基於Socket實現進程間的消息傳遞
Class SocketMessageTrans extends MessageTrans
{
    long SendMessage(Message message)
{
    //XXXX…
}
long Receive(Message message)
{
    //XXXX…
}
}

    依據不同的需求,選擇與之對應的通信方式,我們把功能接口落地了,但這一切並不會影響和改變抽象層次上的Design。抽象層仍然是具備了足夠好的可擴展性,比如說你現在想使用消息隊列,繼承MessageTrans接口,派生相應的類實現就OK了。

    說了這麼多,無非表達一個觀點:架構應依賴於抽象,而不應依賴於技術

 

四、架構

    項目從設計之初到後期重構,再到Demo Show之前的靈感突發,一步步地改進,我先後給出了三種設計方案,篇幅關係,這裏只討論Version3.0的Design。從一開始講需求的時候,就知道,我們需要把一些結構複雜的文件,轉化成可展示、可交互的圖形、圖像、圖表等信息,同時支持必要的檢索功能。一個宏觀上的架構圖如下:


    整個系統由後臺(Backstage)和前臺(Visualization)兩部分組成,中間通信使用WCF完成,有關WCF的內容我會抽時間寫篇博文來介紹。

    後臺主要由若干服務組成,處於最底層的服務爲Data Loader & Parser & Writer Service(DLPWS),直接與磁盤文件或DB交互,以完成數據的I/O操作,其職責在於加載解析各種類型的文件,如Feature、Input、HRS文件等,DLPWS採用接口設計,以支持靈活方便的擴展。當前我們的數據存儲介質是基於磁盤文件的,一旦替換爲DB、甚至更改文件描述格式,則我們只需要擴展實現DLPWS的接口,抽象層的邏輯不需要任何改變。DLPWS存在的好處在於:使得之上的應用層服務Business Logic Service(BLS)不必要關心底層數據格式到底長什麼樣,是INI文件或XML文件;也不需要關心這些數據究竟持久化在哪裏,是磁盤文件或者DB中,它一點都不Care,它只需要關注其本身的業務邏輯。BLS只需要從DLPWS處接收到抽象的,可以直接被處理使用的內存數據即可。DLPWS對於BLS而言,相當於一個透明層,對BLS屏蔽了複雜的文件格式的細節,屬於應用層之下的基礎數據I/O層。

    應用層服務Business Logic Service(BLS)是直接用於處理業務邏輯的,爲用戶的請求提供數據分析、處理和計算的功能。在我們這個Demo中主要包含了圖形構建服務Shapes Service、數據分析服務Analysis Service和數據統計服務Statistics Service三個。切合需求本身主要是針對決策樹Decision Tree的信息做可視化,所以需要先在後臺把整個樹狀結構的圖形給構建起來,這是Shapes Service的職責。Tree的可視化不僅僅考慮節點,還需要節點之間的連線、節點與連線對接的端口等,同時,需要基於一定的渲染算法,以控制每個節點、每條線段具體擺放在哪個位置,這塊內容將在後續的Part3算法設計中給予介紹。Analysis Service和Statistics Service則是用於對某些數據或內容進行統計或分析的應用服務,具體內容涉及公司機密,細節不透露。

    爲了提升後臺服務的性能,我們引入了Cache層。爲了支持用戶的檢索行爲,我們設計和與具體應用相結合的索引結構,並提供了支持表達式級別的查詢服務Query Service,Index & Query的設計思路將在後續博文Part2中給予介紹。

    對於前端的Visualization展現,我們使用了Silverlight技術,將後臺拿到的數據與具體的Silverlight控件進行綁定,以控制整個圖形化界面的渲染。爲了提供系統的性能,前端也引入了Cache層。而對於檢索的需求,在Demo裏面分爲了兩類Query:基本的Tree/Feature Query檢索,查詢Decision Tree的基本信息;DSAT Query檢索,查詢與人工標註樣本差異化較大信息的Query。Query在到達服務器端之間會流經Query Simple Filter做簡易的字符串處理,如去掉空格、標點符號等。

 

五、剖析

    上述是系統的宏觀輪廓,具體的細節如下所示。

(1)後臺

   

    對於後臺而言,需要完成對數據的加載、解析、統計、分析、計算等任務,我們將之以Web Services的形式進行封裝,併爲前端提供必要的服務調用接口。考慮到後臺數據量可能會比較大,而Cache是放置在內存中的一塊區域,其容量是有限的,因此,我們不能一開始就把所有解析、統計或計算的結果全部存入Cache。於是,對於數據的獲取採用的是懶加載的模式(Lazy Fetch),即只有在真正用到這部分數據信息的時候纔會去喚醒相應的服務執行。這樣做的好處在於:一個系統中真正被頻繁使用到的數據只佔整個數據的20%,80%的數據幾乎很少或不被訪問(二/八定律),若根據系統運行時態,動態地去加載用戶所需要的數據,比一開始就把所有數據載入,會減少系統運行對資源的需求,並且這些數據可能很久都不會被訪問一次。而對於已經獲取到的結果數據,我們會將之緩存入Cache。

    結合我們的Demo實際的應用需求,對應於服務Shapes Service、Analysis Service和Statistics Service,其輸出結果將分別放置於Shapes Cache、Analysis Data Cache和Statistics Data Cache中。然而,Cache容量是有限的,越來越多數據的置入將導致Cache的飽和,我們引入了Cache Manager對Cache進行管理,實現Cache緩存數據的更新與替換。擴展性方面考慮,Cache Manager爲接口設計,並可擴展出最近最少使用(LRU)替換策略、最近未使用(NRU)替換策略等具體實現,採用策略設計模式來完成緩存替換模塊,在此只講思路,具體細節不做介紹。

    對於前端用戶請求而言,在經過WCF傳遞到後臺後,會被Cache層攔截,如果Cache層中緩存了對應的結果,則Cache命中,直接返回,而不會被傳遞到下層的應用服務層BLS;否則Cache未命中,Cache層將會被擊穿,請求將被傳遞至BLS層,驅動對應的服務調用DLPWS接口完成數據的I/O,進而實現統計、分析和計算。最終的生成結果會被經由Cache Manager執行緩存替換後載入Cache。

    關於Index & Search設計方案,我個人認爲是做的比較巧妙的一塊,也是整個工程中代碼最優雅的地方,哈哈~我會在本系列博文Part2中來介紹這塊內容,請大家持續關注本博客~

(2)前臺

    在我們的前端設計中,同樣引入了一個Cache層,且Cache層的結構與後臺Cache層幾乎一一對應。引入前端Cache的好處有兩個:其一,用戶之前訪問的數據會被存入Cache,用戶同樣的訪問請求會被前端Cache命中後,直接攔截並返回結果,響應速度迅速,減少了用戶的等待時間;其二,用戶請求被Cache攔截後,Cache層並沒有被擊穿,用戶的請求不被通過WCF傳遞至後臺服務端,減輕了後臺服務器端的訪問壓力。不過由於前端一般是基於瀏覽器實現的,因此緩存的大小不能太大,結合Demo的實際需求,我們開設了2M的前端Cache,這對一個Demo項目而言是完全足夠了。前端同樣會存在一個Cache Manager用於管理Cache,實現緩存內容的替換和更新。

    大家可以看到,上圖中存在一條黑線,在黑線的左側,直到整個後臺部分,我們通過後臺把抽象圖形構建好,並把對應的信息統計、分析好,通過WCF傳到前端,存入Visualization Cache,而這一切並沒有涉及任何與表示層相關的技術,我們始終關注的都只是業務邏輯。接下來,我們需要藉助於一種展現交互方式,將圖形信息予以可視化的展示。我們選擇了Silverlight(原因很明顯,因爲我在微軟實習),並將Visualization Cache層中的圖形、圖像、圖表信息獲取到,與一些具體的Silverlight可視化控件綁定,將數據灌入控件中,並完成控件的渲染。這樣的話,Silverlight其實只相當於是一個代理(Proxy),一個外殼,所有的數據和行爲全部封裝在了抽象的圖形(Visualization Shapes)對象裏面,Silverlight只需要藉助Proxy的方式調用Visualization Shape的數據和行爲即可。這樣做的好處在於:我們可以非常方便地更換前端的展現方式,雖然現在我們是用Silverlight,如果你願意,我們還可以很快地將展現技術替換爲使用Flex、JavaScript或HTML5,而黑線左側,乃至整個後臺都不會有任何改動,真正意義上做到了視圖與模型的完全剝離。

    爲了改進用戶交互體驗,我們在將信息可視化的同時,加入了對控件拖拽、縮放、固定的支持,同時提供了特定的動畫效果,如當點擊Tree的節點時,則其子孫節點會以一種優雅的動畫效果漸變式地展現出來,這些行爲全部被封裝在了Zoom / Drag / Ping / Stretch Actions中。

    對於用戶的檢索詞條,前端會先經由一個過濾器(Query Simple Filter)做簡易的字符串處理,如去除空格、去除標點符號等等,後經WCF將Query請求發送到後臺服務器端。

(3)架構


    如果把前臺後臺的詳細流程放到一起,就形成了上圖所示的系統整體架構。大概的流程爲:用戶的請求會被前端Cache攔截,如果Cache命中則直接返回,並交由Silverlight控件渲染展現;否則Cache被擊穿,通過WCF傳遞至後臺;一旦請求被傳遞至後臺,首先會被後臺Cache攔截,如果Cache命中則將結果通過WCF直接傳遞至前臺,存入前端Cache中,同時將數據灌入Silverlight控件並顯示;否則後臺Cache被擊穿,說明該數據沒被訪問過或者最近很少被訪問;請求將驅動對應的應用服務;應用服務驅動DLPWS完成底層數據的I/O,並統計、分析和計算生成最終結果;最終結果會經由WCF傳遞至前臺Cache以完成圖形的渲染,同時副本會被存入後臺的Cache中。

    其中,有關Cache數據替換或更新的操作均由Cache Manager完成,用戶的檢索過程會在本系列博文Part2中單獨介紹。其他的,圖上畫的已經很清楚了,我就不多說了。

(4)性能

    由於在前端和後臺均引入了兩級的Cache結構,因此,系統對用戶請求的響應是相當迅速,能夠保證在小於1S的情況下完成結果視圖的渲染;後臺的索引由於採用了BitMap做優化,因此總的數據量只有1~2M左右,可以完全載入內存;後臺Cache層在加載完所有的圖形信息、統計信息以及分析信息之後,大概有30M左右;而前端Cache我們設定的大小爲2M。

(5)擴展

    簡要說說我們在可擴展性方面的考慮:

    1. 數據獲取(DLPWS)接口設計,保證數據存儲的介質可以是磁盤文件、DB或者是Memory。

    2. 前端圖形的渲染工作託管給Shape Render,採用訪問者設計模式,這樣做的好處在於你可以方便地添加任何一種新的Shape,並添加與之配套的Render即可插拔式地完成圖形的渲染。

    3. Cache的替換採用策略設計模式,可以方便地更換爲LRU、NRU或FIFO等。

    4. 前端整個採用MVC架構,具體的展現技術(如Silverlight)只是一層代理(Proxy),這樣可以方便地更換這個“外殼”(如Flex、JavaScript或HTML5),這部分內容將在本系列博文Part4中單獨介紹。

 

六、心得

    不得不說的一點是,整個項目進展了大概持續了1個多月的時間,神馬C#、WCF、Silverlight完全都是第一次接觸,現學現用,特別對於WCF的部署經常出現各種各樣無比糾結詭異的問題,足足讓我們抓狂了一個多星期。誤用過單例,對於事件委託曾出現過一次請求觸發了兩次服務調用的詭異事件,然而當我們堅持地挺過去後又是一片嶄新的天地。這期間還對現有代碼做過一次大的重構,幾乎推翻了之前整個架構方案,並基於Version2.0的Design完成了全部的編碼。然而,就在Demo Show PPT製作過程中,我開始不斷的回顧、總結、思索整個項目的歷程,不知不覺間過度到了第三個階段,Version2.0的各種設計上不合理的弊端完全清晰瞭然於心,就有了Version3.0的架構Design。雖然Version3.0依然不是完美的,依然有很多可以改進的地方,但我覺得對於一個Demo性質的項目而言,它是足夠的,同時,這也不代表我不知道如何更進一步去ReDesign、去優化現有的結構。

    比如,從數據容災容錯、備份恢復等角度我已經可以大概給出Version4.0的構架;從應對大規模數據分析處理角度考慮,我可以給出Version5.0的分佈式架構;甚至爲了進一步提升系統運行時的性能,我想把某些複雜的計算過程從並行化上考慮,從而給出Version6.0的架構;再比如對於架構中所出現可複用的模塊,我想把它完全剝離於現有工程的框架,如Cache層管理、Index & Search等,擴展形成一個完全獨立、具備一定通用性的組件庫,能夠被其他工程所使用,從而實現組件的重用。

    有人會說你這叫過度設計,是的,你可以把它當作過度設計,但對於一個剛剛起步,夢想成爲架構師的孩子而言,這是一個必然經歷的過程,只有不斷的、反覆的實踐方能得出成本最適當、經濟效益最好的架構方案。於是,對於現在我的而言,過度設計比根本不知道有這種設計要好上太多。

 

七、總結

    我曾問過Li這樣一個問題:“爲什麼我總覺得老外那麼牛,寫出來的代碼那麼的優雅健壯,而我卻做不到?”

    Li是MSRA的Dev,南大應用數學畢業,之前在IBM做過2年的架構開發,一年半前來到MSRA。我當時做這個Demo時,他是帶我做開發的Dev Leader,我們用到了他寫的Visualization可視化的MVC框架,框架的源碼我花了兩天看懂了,感覺在設計上貌似有些不甚合理的地方,對於流程調用、跳轉繁瑣的糾結程度一直都有些耿耿於懷。

    他想都沒想告訴我:“寫的多了,當你不斷的重構、重構再重構的時候,你會驚奇地發現,你現在的代碼跟你最初給出的設計,早已不可同日而語了”。

    這句話引起了我強烈的共鳴,至少整個Demo做下來,這點我還是深有體會的。感謝Li,教會了我很多東西。或許我只是暫時還達不到某個高度,尚不能完全理解Li的那個Visualization MVC框架設計的某些細節,希望經過自身內力的不斷提升,下次再讀這份源碼時能夠找到這樣設計的理由。

    唯有不斷的閱讀、實踐並反覆思索、重構,方能交出一份優雅健壯的架構方案!共勉了各位~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章