軟件架構方法論及
SOA推導
前面講了很多中間件產品中常用的關鍵技術。相對更大層面的軟件架構來說,這些只是局部的技術點。用于某個行業領域的中間件產品往往會非常深度地決定這個行業領域應用軟件所采用的軟件架構。
軟件系統規模比較小的時候,我們很少用架構這個詞。所以早期有種說法:
算法+數據結構=程序
這里的程序指的是解決特定的具體問題,一般不涉及大范圍網絡數據交換的獨立軟件。互聯網讓軟件應用從小范圍專業領域變成覆蓋全球的信息系統,軟件系統也從簡單的程序演變出很多復雜的架構。
目前在汽車軟件領域也正經歷著類似的變化,由于智能網聯與自動駕駛的需求,汽車軟件的復雜度也以指數形式上升,同時由于以太網的引入,很多之前在互聯網上可以使用的軟件架構經過一些變換后也可以用到汽車軟件上。典型的就是現在大家常說的SOA。
SOA是什么?更專業的說法,SOA是一種軟件架構風格。車載中間件產品也會有其軟件架構及架構風格,SOA 目前看來會是一個主流的趨勢。
什么是軟件架構,什么是架構風格,需要一個清晰的定義,這一章先從這里開始。
3.1 軟件架構組成與架構風格
在這里,我先引用兩篇論文,
1、軟件架構研究基礎(Foundations for the Study of Software Architecture [24])
2、架構風格與基于網絡的軟件架構設計Architectural Styles and the Design of Network-based SoftwareArchitectures [23]
第一篇是1992年的論文,提出了軟件架構的基礎模型和架構風格的概念。第二篇的作者Roy Thomas Fielding 是 HTTP1.0/1.1 規范的主要制定者,這篇文章是他2000年的博士論文,在Web發展史上,這是一篇極其重要的經典文獻,奠定了現代 Web 架構的基礎。這都是20-30年前的文章,但是其對軟件架構的闡述絲毫沒有過時,一樣在理論上指導著軟件架構的設計。
很多汽車相關企業都在推進SOA化,但其架構風格背后的推理邏輯其實并不是顯而易見的。只看到具體的技術點,而不知其由來,就很難準確理解并使用它。尤其是現代的汽車電子電器架構就是基于多種車載網絡體系來構建,汽車軟件已經成了典型的基于網絡的分布式軟件系統。原來基于網絡的軟件架構設計原理對汽車軟件一樣有非常重要的參考作用。
這一節盡量以易于理解的方式,用較短的篇幅將這兩篇文章中關于軟件架構和架構風格的闡述做一個綜述。為后續的討論做一個理論基礎。
3.1.1 軟件架構研究方法論
圖3. 1以 UML 類圖的表示了組成軟件架構的基本概念。 注: 簡單的 UML 符號語意。
表示“泛化(抽象)”概念,也就是邏輯上一般化與具體的關系,程序語言的繼承。箭頭所指父類,即比較“抽象”的概念,另一端是該概念的具體化呈現。
表示“組成”關系,也就是整體與部分的關系。箭頭所指為整體,另一端為組成整體的各個部分。
表示遵循某個“規約”,程序語言中代表接口實現。箭頭所指為具體的規約規則。
圖3. 1軟件架構組成 軟件架構由三個方面組成,架構元素,架構的組成形式,和一些形成架構的基本原則。
架構元素有三種:
處理元素:執行實際的功能性運算與數據轉換;是“計算和狀態的所在地”;是在運行時執行某種功能的軟件單元。
數據元素:承載著被使用和轉換的信息。
連接元素:將架構的不同部分結合在一起的粘合劑。 我們用四個不同的領域概念類比來理解這些架構元素的含義。
域 | 處理元素 | 數據元素 | 連接元素 |
計算機硬件架構 | CPU, Memory | 指令、數據 | 總線系統 |
計算機 OS | 進程、線程 | 堆、棧,函數參數 | IPC機制,系統調用 |
汽車 EE 架構 | 各種 ECU | 消息,總線信號 | Can, Lin,ETH 總線 |
自動駕駛軟件 | 視覺/Lidar/Radar感知算法,感知融合算法,車輛控制模型 | 攝像頭圖像數據,點云數據 | RPC ,消息發布與訂閱 |
架構的組成形式中包括“配置關系”與“約束屬性”。“配置關系”是在系統的運行期間處理元素、連接元素和數據元素之間的關系結構。“約束屬性”用于約束架構元素的選擇。它于將架構元素約束到系統需求所需的程度。對應的實例如下表。
領域 | 配置關系 | 約束屬性 |
計算機硬件架構 | 總線拓撲,對稱多處理(SMP),MPP | 芯片主頻、總線帶寬,功耗 |
計算機 OS | 內核組成模式,進程調度策略 | 地址空間大小,線程棧空間,I/O速率 |
汽車 EE 架構 | EE 架構拓撲 | 總線帶寬,實時性要求,功能安全要求 |
自動駕駛軟件 | 算法流程的前后依賴關系。 各模塊的部署模式 | 數據傳輸帶寬,算法執行幀率,控制實時性要求,功能安全要求 |
架構的一個潛在但不可或缺的部分是在定義架構時做出的各種選擇的一些基本原則。在軟件架構中,基本原則解釋了如何滿足系統約束。這些約束是由從基本功能方面到各種非功能方面的考慮因素決定的,例如經濟性、性能、和可靠性等。
結合上面所述,我們可以把設計一個軟件架構描述為:
根據我們需要構建的軟件系統的約束需求,我們選擇一組基本的原則,在這組原則的指導下,選擇合適(約束符合)的架構元素(處理元素、數據元素連接元素)組成一個集合,并設計各種架構元素的關系結構。
不同的基本原則選擇方式,會讓軟件架構呈現出不同的風格(Style),我們稱之為架構風格。一種架構風格是一組協作的架構約束,這些約束限制了架構元素的角色和功能,以及架構元素之間的關系。
當我們談及某種形式的軟件架構時,實際上往往討論的是架構風格,比如說 SOA。
每個架構設計決策可以被看作是對一種風格的應用,而一個軟件架構往往會混用多種風格。
3.1.2 軟件架構的評估方法
一種架構風格是一組協作的架構約束,但是經常會出現一種情況,一種約束的效果可能會抵消一些其它的約束所帶來的好處。沒有完美的設計,獲得某種優勢的同時,可能需要在另一方面付出代價。所以我們需要一種評估機制去從多個方面去評估一個軟件架構的特性,以便我們在不同的可能性之間進行權衡。
性能
性能往往是軟件架構首先要考慮的方面,軟件架構需要滿足應用的性能需求。
對于 I/O 性能,我們關注的一般是總吞吐量和平均的傳輸延遲。對于計算性能我們關注的是計算單元的總利用率以及計算任務的響應延遲。一個是衡量系統的總效率,一個是衡量系統的單次響應能力,這個會影響到用戶可察覺的性能。
這兩者有時是有沖突的。好的架構風格要能在滿足響應性要求的情況下,盡可能支持系統能夠達到的較高的總效率。
I/O (存儲或網絡) | 計算 (CPU/GPU/NPU) | |
總效率 | 總吞吐量 | 總利用率 |
響應性 | 平均傳輸延遲 | 實時性 |
性能也受成本的約束,在移動平臺或車載平臺,性能還受功耗的約束。
可伸縮性
可伸縮性要求架構能支持從小規模到大規模的平滑擴展。架構需要能夠支持大量的組件以及這些組件之間交互的能力。可伸縮性能夠通過以下方法來改善:
簡化組件
將服務分布到很多組件(分散交互)
根據監視的結果對組件之間的交互進行動態控制
風格可以通過確定應用狀態的位置、分布的范圍以及組件之間的耦合度,來影響這些因素。
簡單性
如果分配給單獨組件的功能足夠簡單,那么它們就更容易被理解和實現,也方便進行測試。越簡單的組件也越能夠被重復使用。架構要能夠支持將復雜的功能分解為很多簡單的組件,同時還要能夠交互協同以完成預期的功能。就是要拆得開,還能合得起來。
可修改性
需求也會隨時間發生變化,可修改性是對于應用的架構所作的修改的容易程度。可修改性能夠被進一步分解為在下面所描述的可進化性、可擴展性、可定制性、可配置性和可重用性。
可進化性:
一個組件實現能夠被改變而不會對其它組件產生負面影響的程度。
可擴展性:
將功能添加到一個系統中的能力。動態可擴展性意味著功能能夠被添加到一個已部署的系統中,而不會影響到系統的其它部分。提高可擴展性的方法是在一個架構中減少組件之間的耦合,比如基于事件或消息進行交互。
可定制性:
指組件可以為一個客戶進行定制化擴展,而不會對該組件的其它客戶產生影響。支持可定制性的風格也可能會提高簡單性和可擴展性,這是因為通過僅僅直接實現最常用的服務,允許客戶端來定義不常用的服務,服務組件的尺寸和復雜性將會降低。
可配置性
指在部署后對于組件,或者對于組件配置的修改,這樣組件能夠使用新的服務或者新的數據元素類型。管道/過濾器風格和按需代碼風格就是典型的例子。
可重用性
一個應用的架構中的處理元素、連接元素或數據元素能夠在不做修改的情況下在其它應用中重用。在架構風格中提高可重用性的主要方法就是是降低組件之間的耦合(對于其它組件的標識的了解)和強制使用通用的組件接口。
可見性
指對組件之間的交互進行監視或仲裁的能力。可以通過以下方式提高可見性:交互的共享緩存、通過分層服務提供可伸縮性、通過反射式監視(reflective monitoring)提供可靠性、以及通過允許中間組件(例如,網絡防火墻)對交互做檢查提供安全性。風格能夠通過限制必須使用通用性的接口,或者提供訪問監視功能的方法,來影響基于網絡的應用中交互的可見性。比如在自動駕駛應用中,我們強制每個算法組件報告它接收數據、處理數據、發送數據的幀率。
可移植性
如果軟件能夠在不同的環境下運行,軟件就是可移植的。標準的通訊協議,標準化的API接口都可以提高軟件的可移植性。SOME/IP 協議只管通訊的數據交換格式,可以兼容不同的通訊庫的實現。AdaptiveAutoSAR 標準將應用程序可以使用的系統調用限制為 POSIX PSE51 標準(參見 4.3.1.1),方便移植到不同的 OS(Linux/QNX/VxWorks) ;同時提供標準的應用API接口,支持基于Adaptive AutoSAR的應用在不同的 Adaptive AutoSAR實現之間移植。
可靠性
從應用的架構角度來說,可靠性可以被看作當在處理元素、連接元素或數據之中出現部分故障時,一個架構容易受到系統層面故障影響的程度。架構風格能夠通過以下方法提高可靠性:避免單點故障、增加冗余、允許監視、以及用可恢復的動作來縮小故障的范圍。車載應用還有更高的功能安全要求。
3.2 常見基于網絡應用的架構風格
圖3. 2列舉出了大多數基于網絡的應用架構風格。有一些與網絡應用相關性不大的其它架構風格沒有放進來。
圖3. 2常見的軟件架構風格之間的關系 圖中偏左側黑色粗框標識的是幾個基礎風格,包括“客戶-服務器,管道和過濾器、多副本,分層系統,虛擬機/解釋器;基于事件的集成”等。其它風格是對這些基礎風格的繼承(或稱為擴展),有的風格是繼承自多個基礎風格。
每個風格上部以淡粉色標注的標簽表示正面的評估結果,如改進“網絡性能、可伸縮性、可靠性等”,下部以淡青色標注的標簽表示負面的評估結果。這里我們不會逐一介紹每個風格,而是在下文對SOA 風格的推導中敘述涉及到的風格。
3.3 面向服務(SOA)的架構風格推導
汽車軟件最近開始了向 SOA 轉型的趨勢。從軟件架構角度看,SOA是一組軟件架構風格的統稱。嚴格來說,SOA并不是一個單一的軟件架構風格,而是一系列各具特點的軟件架構風格的綜合運用,其中每一種架構風格都推崇架構元素之間的一種特定的交互類型。
我們從一個空的架構風格開始,逐步增加新的約束,從而推導出 SOA 的架構風格,并結合車載軟件和自動駕駛軟件的特點來做進一步的說明。
圖3. 3空風格
3.3.1 “客戶-服務器”風格
首先我們加入到我們約束集合中的是“客戶-服務器”風格。客戶-服務器約束背后的原則是關注點分離。“關注點分離”是軟件設計思想中的一個關鍵概念,幾乎可以用在軟件設計從架構到具體實現的各個方面。這個概念比較抽象,簡單理解,可以認為“一個軟件單元(架構組件,軟件模塊,接口等),其關注的范圍盡可能小,聚焦在某一個特定的領域范圍(關注點)。”一個關注點可以看作是“功能,行為,數據”等,很難有一個通用準確的概括。不同的關注點由不同的軟件單元來處理,軟件的耦合程度就會降低,會帶來架構和實現上的各種便利。
“客戶-服務器”風格首先分離了“功能實現”與“用戶接口”兩個關注點。“功能實現”一般包括對數據的處理、計算、存儲,“用戶接口”是用戶提供數據和獲取結果的界面。這兩者的分離可以讓“功能實現”部分單獨進化,而不影響用戶的使用。在用戶接口不變的情況下,“功能實現”可以采用更新的算法,更快的存儲,更大的部署規模,或者移植到不同的技術平臺,而這些對用戶都是透明的。
對復雜的軟件系統而言,把整個系統拆解成多個服務器程序,每個服務器程序關注特定的功能。這種拆分本身也是關注點分離思想的應用。
現代車載軟件以及自動駕駛系統極為復雜,需要很多家不同供應商開發不同的軟件組件。客戶-服務器風格以服務界定功能邊界,不同供應商開發按照預定義的接口實現特定的服務,為其它組件提供服務的同時也使用其它供應商開發的服務組件,只要接口定義好,多個不同的供應商的軟件組件就可以協同工作。
圖3. 4 SOA:客戶-服務器
3.3.2 狀態分離與局部化
3.3.2.1程序“狀態”的含義
“狀態”這個詞被用在很多地方,其含義往往有很多細微差別,容易被混淆。這里所謂的狀態是指“某個軟件組件內部包含的數據信息,這個數據信息會影響外部對這個軟件組件發出請求的響應結果”。
假設有一個加法器A,提供了兩個接口: 1、設置初始值 2、增加 n 并返回結果 我們給加法器A設置初始值0,然后每次加1 ,返回的結果是不一樣的。
對另一加法器 B,提供如下接口: 輸入兩個操作數,返回其加法結果 對加法器B而言,只要每次給出相同的輸入數據,返回的結果是一樣的,不依賴于加法器B的內部數據。
加法器A內部就保存了程序狀態,假如多個客戶端并發進行訪問,取得的結果就會互相干擾。加法器B就是我們常說的無狀態服務器,所有狀態數據保存在客戶端的請求中,多個客戶端并發調用互不影響。這樣就允許我們復制部署多個加法器B,分擔承接大量并發請求。
圖3. 5描述了多種軟件架構風格在“狀態分布”和“交互耦合程度”上的分布情況。這里我們先關注狀態分布。圖中縱軸的上部表示狀態偏向在服務端保存,下端表示狀態偏向在客戶端保存。
圖3. 5不同架構風格的狀態與交互耦合程度的分布
狀態偏向服務端的極端案例是“遠程會話”風格,每個客戶端在服務器上啟動一個會話,然后調用服務器的一系列服務接口,最后退出會話。應用狀態被完全保存在服務器上。如:FTP服務,Telnet服務等。
狀態偏向客戶端的極端案例是“客戶-無狀態-服務器”風格,從客戶端發到服務器的每個請求必須包含用于理解請求所必需的全部信息,不能利用任何保存在服務器上的上下文(context),會話狀態全部保存在客戶端。
其它設計風格的“狀態分布”模式處于兩個極端中間。加入緩存機制的設計風格部分狀態保存在客戶與服務器之間的緩存機制上。分布式對象為基礎的設計風格,狀態主要保存在遠程對象中,偏向服務器。“管道和過濾器”風格和“基于事件集成”風格沒有明顯的客戶和服務器端,狀態保存在各自組件中。
3.3.2.2 SOA服務的狀態分布選擇
前面說的是程序狀態分布的通用概念。現在回到車載軟件SOA風格。我們新增一條約束,“分離強狀態服務與無狀態服務,并控制狀態在局部范圍”。
這個約束的核心含義有兩點:
1、一個是無狀態服務與強狀態服務要分離在不同的服務中
2、每一個服務要么是無狀態,要么是強狀態,避免中間路線。
無狀態服務會顯著改善服務的可見性、可靠性和可伸縮性。改善了可見性是因為監視系統僅僅只需要對單個請求進行分析就能得到其全部特質,不需要關心其它請求。改善了可靠性是因為它讓從局部故障中恢復所需要做的工作減少了。改善了可伸縮性是因為服務器不必在多個請求之間保存狀態,請求結束就可以迅速釋放資源。服務器的實現得到簡化,負載均衡也容易實現[23]5.1.3。
車載軟件對于可見性和可靠性的要求是顯而易見的。而對于可伸縮性的要求不高,因為車載軟件高并發的場景并不多。但是只需要關注單個請求的實現,并迅速釋放資源,依然會讓服務器的實現簡化很多,同時也會促進可靠性的提高。
對自動駕駛軟件來說,單個請求的獨立性,也意味著附著在請求上的功能性和非功能性約束也更為清晰明確。功能性約束體現在請求的參數和響應結果的數據形式上,因為一次服務只需要一次請求響應,約定好請求響應的數據規范就能界定服務的功能邊界。非功能性約束往往體現在響應時間(或幀率),數據傳遞的 QoS 要求上,服務越簡單,這些非功能性約束也就越容易明確。一方面可見性的提高能對這些非功能性約束做更好的監控,另一方面服務實現上滿足這些非功能性約束也會越容易。比如,對響應時間的約束滿足體現在任務調度機制上,無狀態帶來的簡單話意味著實現良好任務調度機制就容易許多。
雖然無狀態帶來了諸多好處,但是在應用中狀態依然是存在的。某些自動駕駛功能其狀態往往需要用復雜的“有限狀態機(FSM)”來定義。那如何來設計這些對狀態強依賴的服務。解決的辦法是:
1、在服務劃分上分離無狀態服務與有狀態服務
2、將狀態限制在服務的局部范圍,即少量特定的SOA服務
在服務劃分上,我們應該盡量把能夠進行的無狀態化處理的服務識別出來,并按照無狀態的方式去定義其服務接口并實現。而把涉及到復雜狀態轉換的部分集中在一個獨立的服務中。不同的有狀態服務之間,其各自涉及的狀態范圍應該是正交的,即不同服務的狀態相互無關。各自服務將狀態限制在自己服務本地,甚至還可以對外呈現出一定的無狀態特征。
例如,對于一個 ACC 應用,涉及的服務可以簡化的分解為如圖3. 6所示的多個服務(只是簡化表示)。我們可以把ACC狀態機集中在一個ACC 會話服務中。它所依賴的其它服務是無狀態的,只是根據輸入產生對應的輸出。
實際情況會更復雜一些,比如“前視算法服務”中并不是完全無狀態,如果需要做多幀融合或者目標跟蹤,其結果跟多幀的數據相關。這多幀的歷史數據就是狀態。解決的辦法是進一步拆解成更小的粒度。目標跟蹤算法做成單獨的服務,輸入是所有關聯幀的數據。
SOA服務劃分的無狀態和有狀態的分離,在形式上與函數式編程范式中的純函數與副作用的分離相對應,只是描述的是不同粒度上的架構問題。所以也可以在前視算法服務內部再做更細粒度的狀態分離。
也就是說,向“前視算法服務”這樣的輕量級狀態可以通過內部或外部的進一步分解來做到真正的無狀態。服務劃分得過于零碎,會導致服務部署配置的難度和額外的通訊開銷,但這可以通過其它的技術優化手段來解決,后文會詳述。
?
圖3. 6參與 ACC 功能的服務(簡化圖)
圖中的“ACC會話服務”的狀態就復雜的多。當用戶啟動ACC 功能,從功能激活到退出是一個完整的會話過程,會話的狀態細節由狀態機進行控制。這是典型的“遠程會話”架構風格,這與一個Telnet 會話其實是非常相似的,都有一個會話生命周期過程。只不過在一輛車上,一個 ACC 會話同一時間只會出現一次,單輛車上不會出現同時多個ACC 會話實例。這個會話服務未必就沒有辦法是無法拆解成無狀態的形式,但是會導致大量的狀態數據在每次請求中傳輸,同時實現上沒有狀態機形式更自然,徒增復雜性。
設計某一個具體的車載SOA服務時,對服務狀態分布的選擇最好在強狀態的“遠程會話”和“無狀態”兩個極端風格中二選一。應該避免在客戶端和服務端都維護狀態數據。
從“關注點分離”的視角看,“無狀態”化設計分離了狀態“數據的存儲與傳輸”和“狀態數據的處理”兩個關注點。
圖3. 7 SOA:客戶-服務器-狀態分布
3.3.3 服務發現
復雜的軟件系統被分解為大量小規模的服務后,服務之間也會有很多依賴關系,某個服務同時也會作為客戶端訪問其它服務。一個客戶端訪問另一個服務,需要知道該服務的訪問點,對于TCP/IP 協議棧來說,至少包含IP和Port信息。同時,還需要知道該服務是否可用。因為服務可能還未啟動,或者在啟動中,或者因為某種原因停止了服務。
服務的訪問點是可以通過配置文件靜態配置的,如果系統中只有幾個服務靜態配置難度還不大,如果服務數量上升到幾十個甚至更多,靜態配置的維護難度就非常大。
某個服務啟動時,為了它所依賴的服務已經就緒,就需要對服務的啟動順序進行管理。這對大量服務并存的系統也是很難做到的。
因此,我們給 SOA 架構風格增加一條約束“每個服務具備能被其它服務發現的能力,也能查找需要使用的其它服務”。
所謂實現被其它服務發現的能力,意味著該服務應該至少具備一下兩個能力:
1、服務可用性狀態發生變化時能通知其它服務
2、響應對服務可用性的查詢
第1條是事件發生時的主動通知,不關心誰接收。第2條是主動響應對本服務可用狀態的查詢。這也意味著每個服務需要維護自己的可用性狀態。
圖3. 8 SOA:客戶-服務器-狀態分布-服務發現
除了事件性的通知機制,“服務發現”也需要包括主動查詢服務可用性的能力。上圖顯示了在 SOA架構風格上增加“服務發現”后的圖示和約束。
相對于靜態配置,“服務發現”實際上提供了動態配置的能力,提高了系統的可維護性和可配置性。因為服務不是靜態配置的,當一個服務失效時,可以很快的用另一個相同功能的服務替換掉它,新服務的訪問點信息會很快在系統中被其它服務獲取,系統可以很快能從服務失效引起的錯誤中恢復,提高了系統的可靠性。當某個服務需要被升級時,也可以采用類似的方式進行,對系統的可進化性也有顯著幫助。
3.3.4 基于“事件/消息”發布訂閱
我們再增加一條約束,“服務之間支持基于‘事件/消息’的發布訂閱機制,以降低服務之間的耦合性。”
關于這個約束有很多稱呼方式,含義接近但又各有側重點,如:事件總線(EventBus), 消息通訊,發布訂閱模式等。
“事件總線”的稱呼,關注點在于系統中事件的觸發,比如UI程序中的用戶交互,或者OS內核的中斷。事件發生后“廣播”出來,由感興趣的軟件模塊去處理。事件產生源不關心事件的處理者是誰。但是對于本地程序來說事件的觸發到事件的處理可能是在一個線程里同步執行的。
“消息通訊”關注點在于數據的傳輸方式以及隱含的消息的異步處理語意。意味著發送者發出“消息”后,就不再擁有消息數據的內存所有權(“潑出去的水”)。發送者和消息接收者對消息數據的處理是異步的,發送者不用等待接收者確認。
“事件總線”和“消息通訊”都可以實現“發布/訂閱”模式。這里發布者和訂閱者之間只共享“事件名稱”,或稱作“消息主題”。發布者按主題發布消息,不關心誰會收到;訂閱者按主題接收消息,不關心消息從哪里來。
這種特性讓軟件模塊的測試也變得非常方便,我們可以在非生產環境中發送模擬的消息來測試軟件的功能。可以錄制生產環境的消息然后線下回放來做仿真測試。
圖3. 9 SOA:客戶-服務器-服務發現-發布訂閱
“發布/訂閱”風格顯著降低了系統各組件之間的耦合度。添加訂閱某個主題消息件的新組件變得非常容易(可擴展性)、只要組件接收或發送消息格式(接口)確定,該組件就可以被用在任何支持這個消息格式的場合(可重用性);允許組件被替換而不會影響其它組件的接口(可進化性)。發布訂閱風格為可擴展性、可重用性和可進化性提供了強有力的支持。
發布/訂閱的一個缺點是:難以預料一個事件將會產生什么樣的響應(缺乏可理解性),事件通知并不適合交換大粒度的數據,而且也不支持從局部故障中恢復。
上圖為我們的 SOA 架構增加了基于“事件/消息”發布訂閱風格。多個服務之間有相互交互的方式,交互方式有基于RPC的“請求/響應”,也有基于“事件/消息”的發布訂閱方式。
從“關注點分離”的視角看,發布訂閱分離了數據的“生產者”和“消費者”兩個關注點。
3.3.5 服務代理
車載軟件發展了幾十年,有大量的穩定成熟的既有代碼。車內廣泛使用的網絡總線也有Can、 Lin、 FlexRay 等很多種,連接在這些網絡上的ECU 很難去支持服務發現、發布訂閱等機制。對于這些成熟的既有系統,可以為它們增加一個代理服務。代理服務仍然按照原有的方式(如:Can 總線)跟既有系統進行通訊。但是代理服務對外可以以獨立SOA服務的方式呈現,提供標準的訪問接口, 接口暴露既有系統可以開放的部分能力。下圖在SOA架構風格上增加了服務代理風格。
圖3. 10客戶-服務器-狀態分布-服務發現-發布訂閱-代理
服務代理還帶來另一個好處。被代理的軟件模塊被隱藏在代理服務后面,可以單獨進化。比如采用不同的技術路線網絡總線重新實現。
但是服務代理作為額外的間接層會降低效率和用戶可察覺的性能。所以服務代理暴露出哪些原有軟件模塊的功能需要仔細選擇。比如,被代理的模塊是一個實時性要求很高動力系統ECU,那就沒必要把該ECU的高實時要求的控制信號暴露出來,只應該暴露出實時性要求不高的狀態發布的等信息接口。
3.3.6 服務裝配
在進行服務劃分的時候,我們希望把每個服務設計得盡可能功能單一,這樣服務簡單,方便開發、測試和復用。但是會造成服務數量變大。
在服務可以執行之前,它必須被加載到應用程序(操作系統進程)的地址空間中。如果每個服務一個進程,如果有上百個服務,就會造成操作系統中運行著上百個進程,爭搶系統資源。相當與把服務調度的工作交給了操作系統,讓操作系統的進程調度代為執行服務的調度。我們知道,操作系統的進程切換是開銷非常大的操作,也無法保證調度的精確性。比如,我們希望一個服務每秒鐘執行30次(軟實時),當有上百個繁忙的進程在系統中執行時,操作系統的進程調度策略是無法保證這個服務的調度要求的。
我們可以把多個相關的服務裝配到同一個服務容器進程中,由服務容器來對這些服務進行調度。這樣可以在用戶空間而不是內核空間進行服務切換,避免了大部分進程切換的系統開銷。同時可以自定義調度算法以滿足服務的需要的調度要求。
如果服務裝配策略(哪些服務裝配到一個進程里)是在開發早期就做出了決策,這個時間開發人員往往并不知道服務搭配或部署的最佳方式,一旦決策有誤,再變更難度就很大。此外,對“最佳”配置的定義,很可能會隨著計算環境的變化而變化。 如果服務的實現與其初始配置緊密耦合,則修改服務可能會對其它服務產生不利影響,比如會導致其它服務需要被重新編譯和部署。
解決問題較好的辦法是動態服務裝配機制。每個服務開發時并不是被預設為單獨的進程,而是一個可以被動態加載的模塊。(如:動態鏈接庫Windows 上的DLL或Linux 上的 SO 文件)。在服務部署時才決定哪些服務被裝配到同一個進程中。也可以在運行時才根據需要的加載服務,并在利用完成后卸載。甚至可以讓服務在不同進程、不同操作系統中遷移(從一個進程中卸載,在另一個進程中裝載)。
圖3. 11客戶-服務器-狀態分布-服務發現-發布訂閱-代理-裝配
每個服務聲明自己的調度要求(執行的頻次,要求完成的時間等),由服務容器的調度算法來滿足,不能滿足時也能獲知并收到告警。圖3. 11將服務裝配加入了我們的SOA架構風格。
“服務容器”本身也可以被設計成SOA服務,提供服務管理接口,用于加載、管理其它服務。所以圖中“服務容器”繼承自“服務器”,多個“服務器”又可以裝配到“服務容器”。
從“關注點分離”的角度看,服務裝配分離了“服務實現”與“服務部署”兩個關注點。“服務實現”時優先關注功能的定義與實現,而部署決策可以被延遲指定。
服務裝配還帶來另一個優點,就是為服務之間的數據交互提供了優化的空間。雖然我們默認采用通過網絡進行數據交換。但是當兩個服務部署在一個進程內時,顯然有更合適的數據交換通道。后文會進一步討論這個問題。
3.3.7 服務監督
對系統中的服務進行監督管理是必要的。比如,Linux 系統的系統管理守護進程“Systemd”就是用于對Linux 系統服務進行監督管理。它會根據預定的配置在合適時間啟動服務,并監督服務進程,如果進程消失,會自動重新啟動。Systemctl 命令就是用來與 Systemd 服務進行交互的命令行接口。
對SOA服務而言,我們需要對服務的“生命周期”、服務的“健康狀態”還有“服務質量”進行監督管理。
?
圖3. 12 SOA:客戶-服務器-狀態分布-服務發現-發布訂閱-代理-裝配-監督
管理服務的“生命周期”是指要決定什么時候加載、啟動服務,什么時候關閉、卸載服務。當系統中只有少數服務的時候這個問題可能不是很嚴重,簡單的系統就是啟動時所有服務都起來。但是當部署了幾十上百個互相依賴的服務后,服務的“生命周期”問題就很重要了。
尤其車載ECU需要對功耗進行控制,當前場景不需要的服務應該盡可能不啟用。比如,泊車場景需要使用環視攝像頭的圖像識別車位線,當判斷車輛行駛在高速公路上是,車位線識別的算法服務顯然沒必要加載。當車輛到達導航的目的地附近時,車位線識別服務可以被預先加載,但是不激活(執行算法識別),當用戶啟用泊車功能時,算法服務開始工作,泊車過程結束,算法服務停止計算。
服務“健康狀態”的監督包括確定服務是否異常退出,服務所依賴的資源是否不可用而導致服務狀態不正確。尤其是多個服務相互依賴時,服務的“健康狀態”問題會順著依賴鏈進行傳播。在車載系統中,這跟故障診斷和功能安全密切相關。
即便服務在持續工作,但是它的“服務質量”是否滿足要求也是需要被監督的。服務質量包括其響應時間,系統資源占用等等。對于檢測出來的問題,“監督服務”需要決定處置措施,比如:重啟、告警、功能降級等等。
圖3. 12是在我們的SOA架構風格中增加的“服務監督”風格。監督服務本身也是一個SOA服務,只是有自己定義的標準化服務監督接口。所以圖中“監督服務”繼承自“服務器”。監督服務與服務容器和其它SOA服務通過監督接口進行交互。
3.3.8 RESTful API
對于互聯網行業的開發人員來說,REST 以及 RESTful API 是司空見慣的事情。REST設計風格以及基于REST的HTTP協議是互聯網軟件架構基礎。REST 是一個龐大話題,參考資料[23]中有詳述,這里不多介紹。RESTful API 是基于REST 的原理,基于Http 協議實現的API 設計原則,遵循這些原則,可以設計出清晰簡潔易于維護的API接口。
更為重要的是,各種異構系統,如果能夠對外提供基于 HTTP 實現的RESTful API,就可以在更大范圍內做應用系統的集成。比如各大云服務提供商(AWS,阿里,百度等),都為它們的各種云基礎設施提供了 RESTful API接口,我們就可以很方便的使用程序去管理我們的云端資源(如創建一臺云主機,讀取或更新數據庫)。
圖3. 13 SOA:客戶-服務器-狀態分布-服務發現-發布訂閱-代理-裝配-監督-Restful
現代智能網聯汽車會與互聯網有非常多的數據交互,這些交互不像車內通訊要求有很高的實時性,但是外部系統確有復雜的多樣性,我們可以為某些服務提供RESTful API,以便能更好的與外部系統集成。圖3. 12在 SOA架構風格中增加了RESTful API 約束。
RESTful API 設計有一些指導原則,可以參見 MicrosoftREST API Guidelines。這里做一些簡要的說明。 設計RESTful API 首先要做好URI 的規劃,需要把服務中的概念映射成合適的URI。比如我們給娛樂系統的音量設計一個 URI 來表示,那就可以對這個 URI使用 HTTP 的GET 和 PUT 方法來讀取和設置音量值。
HTTP協議的操作方法很少,只有9個。RESTful API 最常用的方法主要是 GET、PUT、POST、DELETE,使用這幾個方法就可以完成常見的CURD操作(create,update,read, delete)。這些操作方法如何映射到 SOA 服務的方法有一些基本的原則,這里結合SOME/IP 來做一些說明。
HTTP 的GET方法有一個要求,就是它不應該改變被調用的服務的狀態,它只是讀取一個URI的值,而不會改變它。PUT 方法有一個特點,其任意多次執行所產生的影響均與一次執行的影響相同,數學上管這個叫“冪等”(GET/PUT/DELETE 方法都是“冪等”的,但只有 GET不改變狀態)。SOME/IP 協議中與之對應有同樣特性的是Field。所以對于服務中的 Field 字段應該映射為 RESTful 的GET/PUT 方法進行操作。
SOME/IP 中的 Method 應該映射為對某個 URI 的POST 操作。RESTful推薦用良好的 URI 規劃來更準確的表達領域的語義。所以比較合適的方式是每個Method有其URI,Method的參數和返回值體現在 POST 方法的提交數據和響應結果上。
SOME/IP 的 Event 映射為RESTful API 時比較麻煩,因為HTTP1.1協議是不支持向客戶端主動通知的,不過有變通的WebSocket 方案,HTTP/2 是可以支持的,都需要由客戶端向服務器發起GET 請求,服務器有需要向下通知的數據時就返回數據內容。
這些就是SOA 服務轉換為 RESTfulAPI 的基本映射方式。一般來說并不需要為所有服務設計RESTful API,只為需要與外部系統集成的服務提供RESTful API即可。
3.3.8 可選的其它風格
“虛擬機/解釋器”風格
這里的“虛擬機”指的是受控的代碼執行環境,比如 JavaScript 虛擬機,Lua腳本解釋器等。服務器向客戶端下發一段代碼,客戶端在嚴格受控的執行環境中執行代碼。這個受控的環境只能訪問指定的資源,對資源的訪問權限被限制在預定義的范圍內。
對車載應用來說,對這種方式的需求往往出現在與云端有交互的場景。因為“虛擬器/解釋器”可以先部署到車上,易變的需求可以后續由云端下發代碼來滿足,這在車載娛樂系統中會很常見。我們舉一個為自動駕駛服務的數據采集場景來說明。
自動駕駛的很多算法以及測試場景非常依賴對數據的收集,相對于專業的采集車,量產汽車可以提供更為真實的數據案例,更廣的覆蓋范圍。采集并上傳哪些數據需要一些規則進行控制,否則沒有針對性的大量數據上傳會對帶寬占用、數據存儲、數據分析帶來不利的影響。
可以在車輛量產時內置數據采集和上傳的能力,以及檢查采集規則的規則引擎。具體的采集規則由云端根據需要下發。比如視覺算法需要改進對雨霧天氣的識別效果,就對出現雨霧天氣的區域車輛下發采集規則的更新。車輛數據采集服務接收規則本地執行,觸發數據采集事件。這樣采集的數據內容可以根據需要隨時調整,帶來了較好的靈活性。這時規則引擎就相當與一個受限的解釋器,下發的規則內容就是被執行的代碼。
“遠程求值”風格
“遠程求值”風格跟“虛擬機/解釋器”風格正好相反,是客戶端把代碼送到服務端執行。同樣,這種方式的需求也出現在與云端有交互的場景。之所以把代碼送到服務端執行,是因為執行所需要的數據在服務端。這些數據或者是因為數據量大不便傳輸,或者是因為數據安全或數據隱私的原因,不能被下發給客戶端。客戶端可以將代碼發送到服務端執行,利用數據,取回結果。
這種方式在智能網聯汽車的“車路云協同”上是有應用場景的。根據需要,聯網的路側單元至少可以保存道路沿線一定距離內的道路、車輛等信息,云端的服務器可以保存更大范圍內的交通狀況數據。這些數據都不方便直接發送給行駛中的車輛。當然路側單元和云端服務器都可以根據自己保存的數據提供一些預定義的服務,供車端調用。但是更靈活的方式,是開放執行環境,由車端上傳代碼來決定如何利用數據。當然被執行代碼的權限也會被限制,執行環境也會是一個受限的沙箱。
這種方式優點是能夠定制服務器組件的服務,這改善了可擴展性和可定制性;代碼直接在服務端執行,減少了服務器與客戶端的交互能夠得到更好的效率。由于客戶端發送代碼而不是標準化的查詢,因此缺乏可見性。服務器如何信任客戶端,如何控制執行環境的安全性也需要考慮。這會對服務的部署帶來難度。
3.3.9 小結
這一節通過對SOA架構風格的推導,闡述了車載軟件的SOA 風格并不是一個單一的架構風格,是一系列軟件架構風格的組合。
對于車載軟件,我們首先考慮的是如何降低其復雜性,劃分為依賴性盡可能小的多個服務,是一種化整為零的方法。為了讓服務盡可能簡單,需要考慮服務的狀態分布,強狀態依賴的功能集中在特定服務,讓其它服務以盡量以無狀態的方式設計,以利于整體系統的開發、測試、復用。服務發現用來簡化大量服務的配置,基于事件的發布訂閱讓服務之間的通訊偶合性降低。服務裝配用于更好管理服務的部署,服務監督讓服務的可靠性得到保障。
RESRful API 增強車內服務與外部系統的互操作性。 這些軟件架構風格很多都是在各個領域得到了廣泛應用,以各種不同的形式存在。針對特定的應用場景,選擇不同風格的組合,發揮各自的優勢,往往能產生1+1>2 的效果。
3.4 SOA 的架構元素
前文3.1.1節提到,軟件架構由三個方面組成,架構元素,架構的組成形式,和一些形成架構的基本原則。前文SOA推導時每一步都對此三個方面有一些體現。這里我們再從整體上來對架構元素的區分以及其組成形式做一些分析。 架構元素分為“處理元素”、“數據元素”、“連接元素”。
SOA 的“數據元素”比較容易識別,無論是RPC調用還是消息的發布訂閱,都是數據在不同服務之間傳遞。深入理解這一點最好的方式是與面向對象的分布式系統做對比。面向對象的核心概念之一是“封裝”,其關鍵含義在于將數據以及操作數據的方法封裝在對象實例中,對象私有數據對外不可見。這可以理解為將“數據元素”與“處理元素”封裝在了一起。面向對象的軟件設計就更關注如何將領域概念轉換成合適的對象模型,定義對象的行為和操作,以及對象之間的組成結構。
與面向對象的方法對比,SOA 架構中“數據元素”和“處理元素”的耦合度就低很多。基于消息的發布訂閱完全是從數據的視角看世界,基本不關心數據如何被處理,只關注數據之間的供需關系。RPC請求可以被理解為“一對一”的數據供給與需求關系。
尤其在無狀態服務中,多個RPC請求之前沒有狀態上的關聯性,SOA服務的數據處理能力就更為“純粹”(函數式編程的中的概念,也稱作無副作用)。大型系統在設計時,考慮問題的視角就是如何尋找合適的數據邊界以界定服務邊界,而不是對領域進行對象建模,這與分布式對象系統是完全不同的設計理念。與分布式對象的對比,在3.5.1節中還有進一步的討論。
開發SOA架構中的“處理元素”是某個具體SOA服務的開發人員的職責,我們希望開發人員專注于這個具體數據處理的實現,比如某個AI算法,某個數據集的MapReduce過程。而數據從哪來,到哪去,開發人員不需要關心,這由SOA的“連接元素”來負責。
在實際工程實踐中,SOA服務的開發者并不會完成全部的從數據處理到數據通訊的全部工作,而是要借助分布式中間件系統來實現。中間件提供Runtime庫,IDL規范,程序語言特定的代碼生成工具。使用IDL定義出數據通訊協議后,再用工具根據IDL生成代碼。用戶編寫“處理元素”,使用IDL生成的代碼與外部通訊。這時候,IDL生成的代碼和中間件Runtime 就扮演了“連接元素”的角色。它從通訊通道接收數據傳遞給“處理元素”,將處理的結果發送給需求者。
一個SOA系統在整體設計時,架構師要關注“數據元素的定義”,“處理元素”的劃分, 以及選擇合適的“連接元素”將它們組合在起來。但當將某個明確的數據處理要求委托給某個團隊(如:內部的某個算法團隊,或者外部的供應商)開發時,架構師希望連接元素對與該團隊是透明的。
也就是說數據的處理不應該依賴于特定的連接方式。架構師甚至希望只需要提供給開發團隊模擬的連接方式并回放預先錄制的數據,開發團隊基于這些來實現其數據“處理元素”。“處理元素”的完成品可以被架構師集成到真實的應用環境中,那時使用的可能是不同的連接元素。
中間件Runtime能夠使用SOME/IP、DDS、共享內存等各種不同連接通道;工具根據IDL生成的代碼完成用戶代碼和中間件Runtime的連接;服務發現讓服務的位置不是固定的而是可以被配置、可動態發現的,這些都是在發揮“連接元素”的作用。
從“關注點分離”的視角看,SOA架構在三類主要的架構元素上實現了關注點分離,這也是它適合用于復雜系統集成的重要原因之一。
3.5 SOA相關其它問題討論
3.5.1 SOA vs 分布式對象
分析 SOA,如果跟分布式對象做一些比較,可以更好的理解SOA的意義。我們在3.2 節提到的架構風格中有“分布式對象”風格。前文也提到CORBA 和 ZeroC ICE 都是分布對象的實現。
SOA 和分布式對象都是分布式網絡架構的實現形式。只不過一個是 Service Oriented ,一個是 Object-Oriented。我們來看看他們有什么異同,為什么車載軟件選擇 Service-Oriented 而不是Object-Oriented。
面向對象(Object-Oriented)是非常重要的軟件設計思想。當軟件規模進一步復雜后,“結構化編程”方法也體現出一定的不足。面向對象的設計思想是解決這些問題的方法論之一。
從C++開始,大多數程序設計語言都支持面向對象的方法論。它把數據和處理數據的方法封裝在一個對象中,可以用來與具體的現實事物或抽象的語義概念進行對應。提供了對現實問題或語義概念進行建模的可能,用來描述復雜的軟件語義。
程序語言創建的對象是本地對象,即訪問對象的內部數據和接口方法都是當前進程內的行為,不涉及網絡通訊。當面向對象的設計理念在本地編程獲得成功后,人們很自然的會想到,在分布式領域中是不是可以使用類似的方法。一個對象封裝了數據和操作方法,部署在某個服務器上,客戶程序通過網絡進行訪問。
在客戶端也提供本地化的API接口,使用這些API時跟訪問本地接口一樣,但是請求會被自動代理到服務器上的某個對象。這就是分布式對象的由來。 CORBA 標準建立之初,人們曾經認為這將是未來主流的分布式技術。但實際上世界上最大的分布式系統萬維網(WWW),并沒有采用分布式對象。車載軟件的分布式化選擇了SOA,也沒有選擇分布式對象。我們從幾個角度來探究其背后的原因。
強“狀態相關”的服務不利于可擴展性
3.3.2 節討論了程序狀態在客戶端和服務器端的分布情況對軟件架構的影響。我們傾向于將服務設計成無狀態的。這在WWW 的架構中尤為重要,這是WWW 世界能成為超大規模系統的關鍵原因之一。
分布式對象將數據與操作接口封裝在一起,也意味著它將狀態留在了服務器端。從這種意義上看分布式對象架構風格與遠程會話風格有點接近,每一個遠程的分布式對象自身就是一個小的會話。對于同一個遠程對象的多次方法調用,都要精準的找到這個對象所在的位置。
對于 WWW 來說,這種方式會嚴重影響其可伸縮性。WWW 中,每一次請求的無狀態特性可以讓一個負載集群系統中的任何一個空閑的服務器都可以處理任何請求,而不需要一定讓多個請求必須綁定到同一個服務器。
對于車載軟件來說,雖然整體系統很復雜,但是到單個具體的服務點,其功能往往是很單一的,處理邏輯的復雜度遠遠小于電子商務等系統。很多時候就是接收數據,做簡單處理,然后發送數據。面向對象的建模對這些簡單功能來說帶來了不必要的復雜度。無狀態的服務設計方式能讓系統大大簡化。
不同對象的接口差異大
面向對象的設計方法很重要一點是對行業領域進行建模,分析出各種對象類型以及它們之間的關系。不同類型的對象就包含不同的狀態數據以及不同的操作接口。
對象類型多、接口各不相同對于開發本地應用來說不是大問題,因為一般應用程序也就幾十的對象類型,即便幾百個也是在可以處理的數量級內。但是對于WWW 系統,可能會面臨幾千萬甚至更高數量級的對象類型,沒人能處理這么復雜的系統互操作。
WWW 采用的 REST 架構的一個關鍵設計約束是統一接口,實際只有GET, PUT, POST, DELETE等有限幾個操作方法。僅僅這幾個操作方法就能完成 WWW上各種復雜的功能,非常的神奇。奧妙在于WWW 使用URI(統一資源標識)重新定義世界。
WWW上的每一個事物(簡單的文件、圖片;或者訂單、人等各種語義概念)都可以使用一個 URI去表示。世界的復雜性從對象模型的關系,轉換到了樹形的URI表示,從而采用簡單的操作方法完成所有可能的操作需求,如果不能滿足,就再拆解出一個合適的URI形式。
對于車載軟件來說,服務的類型數量也是在一個可控的數量級,并不需要URI機制去簡化接口形式。但是如前文所說,當汽車軟件需要與云端或者互聯網體系進行數據交換時,可以把車載軟件的部分服務包裝成RESTful 接口遵循WWW系統的架構風格。
對象生命周期管理復雜
無論是本地對象還是分布式遠程對象,都會有一創建、初始化、工作、失效、銷毀的完整生命周期。對于分布式對象來說,其生命周期管理非常復雜。比如,當一個客戶端請求某個對象的服務是,這個對象可能不存在,那么這種情況下如何處理也需要被考慮。
一般來說這種對象生命周期管理能力是由中間件系統來提供,CORBA,J2EE規范中都有對應的內容。會導致中間件實現的復雜度。
分布式對象系統,當然是希望以統一的方式管理大量的對象,比如成千上萬的訂單。只有這樣才值得在開發復雜中間件實現時的投入。然而對于車載軟件,很多軟件模塊恐怕只有一個對象需要被管理,比如上文的ACC 會話。一輛車同一時間內是不可能產生兩個及以上ACC會話的。即便某些服務需要多個會話實例(相當于要管理多個對象的生命周期),那這個復雜性由服務自身去處理。
比如某個服務內部確實需要保存多個不同對象(或會話)的實例,那么可以提供類似“createXXX”的接口并返回對象的句柄,然后在其它的接口方法參數中帶上這個句柄。服務實現時根據這個傳入的句柄去找到對應的對象進行操作。至于對象的生命周期,服務開發者自己想辦法維護。而不需要在中間件這一層提供架構級別的解決方案,因為投入與收獲不匹配,并且帶來不必要的復雜度。
3.5.2 RPC消息通訊管道 的技術關聯
RPC 是“客戶-服務器”架構風格實現請求響應的典型方式。“基于消息的通訊”(或者叫基于事件的集成、發布訂閱模式)及“管道和過濾器”本身也是常用的架構風格。這三者是架構組件之間數據通訊的方式。從架構風格的組件耦合程度看,RPC 耦合度最高,管道模式次之,發布訂閱耦合度最低。但在具體實現上,這三者之間很大程度上可以互為實現。
基于RPC 實現發布/訂閱
如果我們實現了“客戶-服務器”之間的RPC 機制,我們可以利用它來構建發布訂閱系統。ZeroC ICE 支持的發布訂閱服務IceStorm就是這么實現的。但是這個發布訂閱是通過中心節點進行中轉的。
圖3. 14基于RPC 實現發布/訂閱
如圖3. 14所示,“接口A”定義了一個 RPC 接口,在普通的基于RPC的“客戶-服務器”系統中,客戶端直接調用接口A,服務器獲取數據進行處理。當轉換成“發布/訂閱”模式時,引入中轉服務:
1、中轉服務提供基于主題(Topic,可以是一個字符串名稱)的Publish和 Subscribe 接口。中轉服務對每一個主題維護訂閱者列表。
2、訂閱者通過調用中轉服務的Subscribe接口將自己注冊到中轉服務中,以訂閱特定主題。實質上就是告訴中轉服務自己對哪個主題感興趣,告訴它回調自己的訪問點(IP,端口等)。
3、發布者按主題發布數據,實際是對接口A的一次RPC調用,但是帶上了主題名稱,方便中轉服務識別。
4、中轉服務并不識別并執行發布者的請求,只是根據主題名稱將請求轉發給訂閱者。接口的適配(參數定義,版本匹配)由發布者和訂閱者自己保證。
以上實現“發布/定義”方式的缺點是有中轉服務,一方面存在單點失敗的可能,一方面兩次數據傳輸有額外的網絡性能開銷。另外只適合沒有返回值的RPC調用。優點也很明顯,可以方便的把 RPC 服務轉換成“發布/訂閱”模式,能夠方便的達到解耦和發布者和訂閱者的目的。中轉服務是與特定接口無關的,也就是說任何RPC接口都可以這么轉換。因為中轉服務不需要識別接口內容,只是單純的做數據報文的轉發,不做序列化和反序列化動作,所以可以適用于任何單向RPC接口。
基于發布訂閱實現RPC
反過來,我們也可以基于“發布/訂閱”的消息通訊實現 RPC機制。“發布/訂閱”邏輯上是實現一個“多播”機制。多個軟件組件可以訂閱某一個主題的消息,不關心誰發送;多個組件也可以發出某個主題的消息而不關心誰會接收。既然可以“多播”,當然也可以“單播”,我們完全可以給用戶層一個RPC的API,而在實現的時候把RPC調用轉化成單播的消息通訊,比如利用DDS來實現。
基于“發布/訂閱”來實現管道
在管道和過濾器風格中,每個組件(過濾器)從其輸入端讀取數據流,對數據進行處理后在其輸出端產生數據流。每個過濾器必須完全獨立于其它的過濾器(零耦合),它不能與其它過濾器在其上行和下行數據流接口分享狀態、控制線程或其它資源。“管道和過濾器”有非常好的可配置性,可擴展性。
如果我們把管道中每一個過濾器的輸入和輸出數據流定義成“發布/訂閱”的消息,那么也可以實現管道和過濾器的風格形式。實際很多基于ROS/ROS2 實現的系統就是這么用的。
---- 以上幾個是架構風格在實現層面是交叉支持的具體形式,其實還可以有更多。如前文所述,架構風格定義的是軟件組件之間結構關系的約束形式。每一個架構風格當然有其最優的原生實現形式,但是也不排除在具體實現技術上基于其它風格實現。
3.5.3車載以太網助力 SOA架構
SOA 在分布式系統中實際上已經被應用很多。不過在以太網用于汽車之前,車載軟件其實是不怎么提 SOA的。SOA的相關設計約束并不是說一定要基于以太網,只是在車載軟件上,以太網能讓SOA 更好的實現并發揮作用。對此我們做一些說明。
先明確討論中“以太網”的概念。當我們談及“以太網”的時候,根據上下文其實往往有“狹義”和“廣義”兩種含義。
狹義的以太網重點關注的是以太網的物理層和鏈路層。這時候我們指的以太網是符合 100BASE-T、1000BASE-T等標準的有線網絡,采用總線型拓撲,或者基于交換機實現星型拓撲。使用CSMA/CD(Carrier Sense Multiple Access/Collision Detection,即載波多重訪問/碰撞偵測)的總線技術來解決通訊沖突。在這個語義下,WiFi 不是以太網,它是無線通訊技術,有另一套標準(IEEE 802.11)。
廣義的“以太網”包含了常用的通訊協議,最核心的是對IP 層協議的支持。ISO/OSI 定義的七層網絡模型中,物理層和鏈路層之上是 IP 層(網絡層)。傳輸層協議(TCP,UDP)也都是基于 IP 層。IP成定義了數據報文進行地址和傳輸的協議,無論下面兩層如何實現,只要有IP層的支持,不同網絡就是互通的。在這個語義下,WiFi 也是以太網,因為它也支持 IP 協議。我們還可以實現 IP over USB, 那就是基于 USB 的以太網。
下面討論中的“以太網”指的是廣義的以太網,即具有IP協議支持的網絡。
Can 總線在汽車中的到廣泛的運用,Can總線只有物理層和數據鏈路層([27]3.3)。使用 Can 總線的應用需要在這個基礎的數據鏈路層協議上去定義自己的數據格式,一般以一個DBC 格式文件描述。Lin總線成本更低, FlexRay 總線提供了比 Can 高得多的數據傳輸帶寬,但是他們也跟 Can 總線一樣,只有物理層和數據鏈路層,應用層協議各自為政。
這意味著這幾個網絡是無法互通的。汽車電子電器架構設計的時候,解決互通問題的辦法就是兩個網絡中間加網關。網關同時支持兩個以上網絡并來回搬運數據。即使是兩條Can 總線想要互通也需要加網關,而且網關的數據搬運代碼都需要單獨定制,因為每條Can的應用層協議都不一樣。
設想一下,在這種網絡環境下做SOA服務是什么效果。假如我們基于 Can 協議實現了一個 SOA 服務,我們沒法進行RPC請求,因為 Can 協議沒有尋址的概念,請求不知道發給誰。不過好消息是Can 本質上也是發布訂閱的機制,我們可以放棄單播通訊,只做事件廣播。但 Can 一個消息只有8個字節,沒有地方放更多的頭信息,我們在設計服務發現機制的時候會遇到巨大挑戰。
就算我們設計出了服務發現,對不起,這個服務在別的網段中不會被發現,因為各個網段的服務是不能互通的。我們就需要開發網關程序跨網段搬運數據。但是每增加一個服務,或者對服務的數據格式做些修改,網關程序都要重新修改適配。
就算這些都完成了,我們想讓一個開發好的服務在另一個項目中復用幾乎不可能,因為對應的Can 協議,網關程序全部都要重新適配。
引入以太網技術帶來的 IP 層是解決這些問題的關鍵。不管各段網絡的物理層和鏈路層是什么樣的,只要支持 IP層協議,IP報文就可以在不同網段之間傳輸。IP 協議是可以支持廣播和多播(一次數據發送,多個目標接收)的。而且廣播和多播是可以跨網段的,有成熟的協議支持。廣播和多播可被用于SOA 的“服務發現”和服務之間的數據發布訂閱。
以太網比大多數其它車載網絡提供了更高的帶寬,目前常用的車載以太網系統基本都可以達到1000Mbps。將來升級到萬兆甚至更高的光纖以太網也是指日可待。而這種升級在軟件架構上幾乎不需要做太多變化就可以利用網速提高帶來的好處。這樣在數據報文設計上就不用像設計Can報文一樣精打細算的利用好每一個 bit。必要的情況下可以增加更多的頭信息支持更多的功能。也可以用來傳輸圖像等多媒體數據。擴大了SOA的服務能力范圍。
TCP/IP 協議發展了很多年,是互聯網的技術基礎。衍生出了無數成熟的網絡技術,這些都可以適當調整后運用到車載軟件。比如基于發布訂閱的DDS技術,提供了豐富的 QoS能力,可以用于支持服務組件之間的通訊;與外部系統集成可以使用基于HTTP相關的技術。這些技術的有效利用可以很快的提供更多豐富的功能。
引入以太網也有一些其它問題需要解決。以太網的工作模式就是多個聯網節點上的多個應用爭搶網絡帶寬。某個應用的高帶寬占用可能會導致另一個應用的緊急數據不能及時傳輸,影響車內服務之間通訊的實時性。TSN (時間敏感網絡)相關協議的就是用來解決這些問題。
總而言之,在車載軟件中,以太網技術是支持車載SOA架構風格的關鍵技術基礎。
3.5.4 SOA 與 微服務
在汽車軟件開始向 SOA 架構轉型時,大型互聯網服務基本上都已經轉向了微服務架構風格。那么,SOA與微服務的異同點是什么?汽車軟件也會走向微服務架構嗎?
規模引起的量變到質變
首先SOA與微服務在架構風格的邏輯上是一脈相承的,前文對SOA的論述對微服務同樣有效。但是微服務在架構風格上更進一步,可以理解為至少增加了以下幾個約束:
1、服務粒度盡可能小
2、服務之間的依賴性更小
3、更徹底的去中心化
服務粒度盡可能小可以理解為比SOA更小的服務拆分,強調的重點是業務系統徹底的組件化和服務化,原有的單個業務系統會拆分為多個可以獨立開發,設計,運行和運維的小應用,這些小應用之間通過服務完成交互和集成。每個服務完全不依賴中心化的資源,比如不依賴中心化的數據庫,每個服務有自己的數據存儲機制。粒度越小、相互之間依賴越小,無中心化,讓開發、測試、部署的獨立性越強,越容易使用負載均衡技術支持高度的并發訪問。
相對于SOA ,微服務是一個量變引起質變的過程。目的是為了支持更大型的互聯網服務體系,應對高并發、高可用要求、高業務復雜性的挑戰,同時要求開發迭代更為敏捷迅速,部署簡單并高度自動化。像電商的秒殺系統,12306 購票系統,都是極限并發的典型案例,微服務架構在應對這些場景的時候有很好的表現。
微服務可以認為是SOA架構向更深度的發展進化。但是互聯網的微服務架構和車載的SOA架構,應用場景有很大的差別。雖然汽車軟件也是分布式架構,而且車載以太網技術得到應用后,很多車載分布式技術與互聯網的分布式技術有非常多的共通之處。但是車載軟件的分布式規模與互聯網服務完全不在一個量級。
“分布”與“集中”
大型互聯網服務部署規模可能涉及上百臺服務器,每秒百萬級的并發。車載的分布式服務只在一臺車的局部系統中,并發要求低但實時性要求高。與互聯網的極度分布式趨勢不同,車載系統是反過來,目前是向集中式發展。原來電子電氣架構中大量小控制器實現的功能,逐步向幾個主要的高性能域控制器集中。
車載SOA架構在功能劃分上盡量切分成多個服務,但是部署的時候實際是集中化的,同時通過一系列技術手段優化通訊延遲。有意思的是,這個“集中”其實是與“分布式”并存的。
圖3. 15物理的集中與邏輯的分布
圖3. 15是一個理想的域控制器內的服務部署。高性能的多核心SoC被虛擬化成了多個虛擬機,VM1和VM2分別是Linux和QNX系統,而且都支持容器化(Docker Enable)。每個虛擬機內又有多個容器(Docker Container,不是前文服務裝配中的服務容器 )。每個容器是一個獨立的 OS系統,里面再部署SOA服務進程,進程內有多個SOA服務。不同虛擬機內、不同容器內的服務通過網絡進行通訊。
我們可以看到,整體的域控制器中的軟件在物理上是“集中”部署到了同一個域控制器主機,但是邏輯上又“分布”到了不同的操作系統實例。這樣的好處是可以使用虛擬機或Docker容器作為供應商的邊界。一個供應商提供一個虛擬機或容器內的全部服務,與其它供應商互不影響,責任邊界也容易確定。為了優化通訊我們可以在虛擬化這一層提供PCIe總線的虛擬化來支持跨虛擬機的共享內存通訊。
虛擬化技術和容器技術在云端計算中心已經被大規模使用,車載系統只是在復刻這個過程。差別仍然在于規模,現實應用中微服務架構基于的虛擬機和容器數量比車載系統至少高出兩個數量級。而且虛擬化對微服務架構是完全透明的,微服務架構中的服務認為自己運行在獨立的OS中,在微服務架構中,只有極度的“分布式”,沒有“集中”部署的含義。
技術實現上的側重點不同
兩者的應用場景不同決定了分布式規模不同,導致在具體的設計和實現上的側重點也有很大差別。
車載SOA首要解決的是服務能夠被拆分,拆分之后能夠通訊,然后解決如何優化通訊的效率,尤其是一定的實時性保證。 微服務架構因為徹底的分布式帶來的大量微服務組件,需要在更大的規模上去解決“負載均衡、服務發現、認證授權、監控追蹤、流量控制、服務部署、分布式事務、分布式存儲”等等問題。
以目前最先進的微服務架構Service Mesh來說,2017 年1月發布的Service Mesh產品Linkerd,所有的請求都通過 Service Mesh 轉發,不提供直連方式,它掌控所有的流量。2017 年 5 月, Google、IBM、Lyft 聯手發布了 Istio,它與第一代 Service Mesh 相比,增加了控制平面,它具備遠超第一代的控制能力。
通過集中式控制面板,加上所有流量均會通過 Service Mesh 轉發,通過 Service Mesh 的控制面板,就可以控制所有整個系統。Service Mesh管控的內容出來服務注冊和服務發現外,還包括負載均衡機制,彈性的流量控制能力(熔斷、限流、降級、超時、重試等)。而轉發引起的消息延遲在互聯網業務中并不是最重要的關注點。
車載軟件架構會走向“微服務”嗎
車載SOA架構是實際上跟互聯網技術上的SOA架構也是有很大差別的,加入了很多為車載場景定制的協議和優化機制。微服務架構作為SOA的進一步演進,已經在互聯網領域體現了其價值所在。車載的SOA架構也不會一成不變,也會進一步的演化,很自然的也會從微服務架構中吸取優秀思想。但不會是直接使用互聯網微服務的產品。
目前來說車載軟件的當務之急還是先實現在業務功能層面SOA化,然后在服務部署上應用虛擬化和容器化,以支持不同粒度上的服務部署,并建立供應商的責任邊界。進一步的改進可以根據現實問題來做響應。有一個現成微服務架構作為參照,也為后續架構改進的思路提供了技術儲備。
審核編輯:劉清
-
UML
+關注
關注
0文章
122瀏覽量
30858 -
SOA
+關注
關注
1文章
288瀏覽量
27466 -
自動駕駛
+關注
關注
784文章
13794瀏覽量
166413
原文標題:自動駕駛軟件架構之:中間件與SOA(二)
文章出處:【微信號:阿寶1990,微信公眾號:阿寶1990】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論