1. 程式人生 > >Red5 流處理架構設計解析

Red5 流處理架構設計解析

                        前言        流處理是 Red5 容器的一個核心。本文是一個 Red5 流處理的設計文件,來自於 Red5 團隊的郵件列表,作者是 Steven Gong,起稿於 2006 年 4 月。本文的原文標題是《關於流處理架構設計的介紹》,原文可以點選這裡進行檢視。        雖然只是一個初始的設計構想,而且有些細節還沒有敲定,但通過對本文的學習,我們仍然可以深入領會到 Red5 流處理架構的執行機制。現在的最新的 Red5 的原始碼,關於流處理的整體架構也是基於本文進行設計。        終於涉及到 Red5 的架構設計了。雖然只是一個模組,但知識面以及個人水平所限,講解肯定有不當、膚淺之處,有些地方講的也很生硬,請各位大牛同學多多包涵。如果實在是看不下去了,也可以出來噴一下。        以下是郵件正文==========================================================================================================        大家好,        最近幾周的時間裡我一直在思考並設計關於 Red5 的新的流處理架構。這樣做的目的是使我們的流處理更加模組化,這樣程式碼才可以更好地進行設計、實現和進行獨立地測試。        我在建築設計方面做出了一些努力並盡力去找到最適合我們需要的模式。最終我找到了解決方案。我不知道這些模式是不是最好的但至少他們可以靈活而輕鬆地和我們遺留的流處理程式碼進行整合。在此我想對這個架構及其背後的設計依據做一個介紹。我們開始吧...        * 設計模式(Patterns)        事實上這個解決方案相當簡單。總之,就是
訊息傳遞(Messaging)模式
管道過濾器(Pipe-Filter)模式。在多數情況下訊息傳遞(Messaging)模式構建在管道過濾器(Pipe-Filter)模式之上,但也只使用了管道過濾器(Pipe-Filter)模式的一部分(我會詳細介紹他們是如何使用的)。我所做的就是將這些模式整合起來併為我所用。        * 背景(Background)        ** 訊息傳遞系統(Messaging Systems)        將 Red5 的流處理跟一個訊息傳遞系統相比,你會發現它們有很多類似之處:訊息提供者 --> 流釋出者,訊息消費者 --> 流播放者/檔案池,訊息路由器 --> 多匯聚節點,訊息 --> RTMP 訊息(RTMPMessage)等等。事實上 Red5 正是一個通訊伺服器,它從 RTMP 供應者那裡獲取資訊,將資訊進行過濾處理後傳送給 RTMP 消費者。所以訊息傳遞系統中的很多模式都可以直接整合到我們的設計中來。在
第一本書
中我們可以找到這些模式:訊息通道(messaging channels)、訊息路由器(messaging routers)和訊息轉換(messaging transformer),這些對我們都很有用。舉例,                - 我們可以使用頻寬濾器(Bandwidth Filter,基於訊息過濾器)來控制某個特殊客戶端的流量。                - 我們可以使用音訊/視訊過濾器(Audio/Video Filter,基於訊息過濾器)來控制是否釋出音訊或者視訊標籤。                - 我們可以使用訊息整合器(MessageCombiner,基於聚合器)來整合幾個實時播放源,然後使用混合器(MessageMixer,基於內容過濾器)再混合音訊標籤,進而得到一個單視訊多音訊的流。                - 我們可以使用播放列表釋出者(PlaylistPublisher,基於訊息佇列)來製作一個服務端偽線上釋出流(類似於 FMS 的服務端流類)。                - 我們可以使用切換流釋出者(SwitchStreamPublisher,基於訊息佇列)來讓客戶端選擇他們想要實時播放的流。        當然,你可能會從中找到更多有用的模型。畢竟我自己的想象力有限的很。;-)        ** 管道過濾器(Pipe-Filter)        管道過濾器(Pipe-Filter)模式有著很長的歷史了。最著名的實現就是執行在 unix shell 之下的管道(pipe)。Unix 上所有的程序在同時進行並以管道(pipe)相連。供應者將流新增到標準輸出裝置 stdout,stdout 將其轉發給輸入終端的管道(pipe),消費者從標準輸出裝置 stdin 得到流並將其轉發給終端的輸出管道(pipe)。由於這種管道(pipe)的非同步性質,應該維護一個快取來儲存臨時片段,當快取滿時阻止供應者,當快取為空時阻止消費者。        訊息傳遞系統也依賴於管道過濾器(Pipe-Filter)模式。當訊息供給者傳送訊息時,將其壓入管道(pipe,或者用 Messaging 模式的話說是通道 channel)。然後管道(pipe)隨之將訊息推送給消費者。多數情況下,消費者是基於事件驅動的。你可能會發現在 JMS 中有很多傳遞訊息的方法。事實上,這只是一個封裝內部事件驅動消費者的高層編碼。        但這只是管道過濾器(Pipe-Filter)模式的一部分而已。一個標準的管道過濾器(Pipe-Filter)模式包含四種管道(pipe):拉-拉(pull-pull),拉-推(pull-push),推-拉(push-pull)和推-推(push-push)。如果我們將它命名為 xxx-yyy,那麼供給者就是 xxx 方式而消費者則是 yyy 方式。例如,Unix 系統使用的就是推-拉(push-pull)管道(pipe)而訊息傳遞系統使用的是推-推(push-push)管道(pipe)。        * Red5 訊息傳遞架構(Red5 Messaging Framework)        現在讓我們回到我們的專案中來。無論是訊息傳遞系統還是類 Unix 系統都無法滿足我們的需求。對於前者我們有類似於 FileSourceStream 的被動消費者,它只按照要求“傳送”訊息。對於後者,讓每個元件都包含一個活躍的執行緒的做法太昂貴了。在 Red5 中,我們有時需要消費者觸發訊息傳遞,有時需要供應者觸發傳遞,甚至有時是混合。因此我們最好使用所有四種類型的管道(pipe)來使得我們的架構更加靈活。

        我們來看一下 Red5 通訊架構的類圖(MessagingFramework.png)。這些類只是根據我的個人分支(branches/dev_steveng),而且這些類並非架構的全部只是一部分,因為許多決定還是需要大家一起來做,但架構的整體已經設計完畢。主要分為五個部分:Provider, Consumer, Pipe,  Event/Listener 和 IMessage。

MessagingFramework.png

        顧名思義,IProvider 是訊息的製造者,而訊息的消費者當然就是 IConsumer 了。它們都是標識介面。然後是 IPullableProvider 和 IPushableConsumer。        你會發現 IPipe 介面有兩個自己附加的方法。它們用於管道(pipe)的事件模型。管道(pipe)充當事件源的角色,當有 provider 或者 consumer 連線到它時,注入進來的監聽者將會被以一個 PipeConnectionEvent 事件通知到。使用 addPipeConnectionListener 和 removePipeConnectionListener 方法可以顯式地新增/移除監聽者,在預設情況下(當有新的連線時)管道(pipe)會自動通知已經連線到它並且實現了 IPipeConnectionListener 介面的 provider 或者 consumer。這一行為使得連線到管道(pipe)的終端之間的很容易實現通訊。我會在下文中做詳細介紹。        我沒有定下來的是訊息模型,因為這取決於我們的需求。我們需要幾種型別的訊息?屬性是什麼?這可能需要在郵件中討論。        * 介面卡(Adapters)

        為了使這個框架生效,我寫了幾個臨時介面卡封裝了我們遺留的流處理類,使之能夠 VOD,實時流及其錄製。類圖(adapter.png)顯示了這些介面卡是如何工作在 Red5 通訊架構中的。

adapter.png

        ** 消費者(Consumers)        DownStreamAdapter 包裝好流之後,將 RTMP 包傳送給客戶端。它可以工作在兩種模式下,一種是拉模式另一種是推模式。在播放一個 VOD 時,它工作在拉模式,因為它主動地從管道(pipe)中獲取資訊所以我們稱之為主動消費者。但當它播放一個實時流的時候,它工作在推模式,我們稱之為被動消費者或者事件驅動消費者。        ** 供應者(Providers)        FileStreamSourceAdapter 將 FileStreamSource 封裝起來,是一個拉模式的供給者又稱為被動供給者。這個很容易理解因為我們在 VOD 使用到了它。        LieStreamSourceAdapter 將流封裝起來,是一個推模式的供給者又稱為主動供給者。它在 RTMP 連線中獲取 RTMP 包並將其推送給管道(pipe)。        ** 訊息(Message)        在這裡我將原來的 Message 類簡單地封裝為 RTMPMessage 類。        ** VOD,實時和錄製(VOD, live and recording)

        現在我們來看一下這些介面卡是如何組織提供 VOD,實時和播放服務的。(注:我基於遺留 API 開發了這些介面卡,但它們也可以比較容易地為以後的類所用)

PlayVOD.png

        請參考圖 PlayVOD.png。在 VOD 中,當客戶端發起一個播放請求時,Stream 類將會使用 FileStreamSourceAdapter、DownStreamSourceAdapter 和 InMemoryPullPullPipe 建立一個管線(pipeline)。在播放時,RTMPMessage 由消費者 DownStreamSourceAdapter 獲取(MINA 對它進行觸發)。

        請參考圖 PublishLive.png 和 PlayLive.png。在這裡 StreamManager 充當了一個為連線供給者和消費者的中心管道記賬人(pipe)的角色。我畫的只是先有釋出然後才有播放的情形。釋出者和線上播放者由 InMemoryPushPushPipe 進行連線 -- LiveStreamSourceAdapter 充當了這一過程的主動的供給者而 DownStreamAdapter 則在推模式下進行連線。事實上,如果線上播放者先提出申請的話,它將會等待,直到釋出者提出申請。所以我們不再需要 TemporaryStream 和 TemporaryStreamSink 兩個類了。

PublishLive.png

PlayLive.png

        請參考圖 Recording.png。除了 StreamManager 同時建立了一個 FileStreamSinkAdapter 之外這個時序圖幾乎跟線上釋出相同。理論上,當另一個播放者提出線上播放請求時,這個時序圖就會和 PlayLive.png 一樣了,但這樣火狐會崩潰的... :-(

Recording.png

                > 通過上面的時序圖,我們有理由斷定本通訊架構可以使我們的程式碼更加模組化更加靈活。        * 管道連線事件模型(Pipe Connection Event Model)        提供者和消費者是怎樣進行排程的呢?比如說,當一個客戶端停止播放一個 VOD 並且關掉了 DownStreamAdapter(這個關閉由流觸發),FileStreamSourceAdapter 就不在需要了。我們需要通知前者是否也需要關掉 FileStreamSourceAdapter。所以當 DownStreamAdapter 關掉並且斷開與管道(pipe)的連線時,我們應該想到一個方法去通知 FileStreamSourceAdapter 或更多類似者通知想要關閉的供應者。很容易理解消費者指向供應者,就是說,DownStreamAdapter 擁有一個 FileStreamSourceAdapter 成員並且當自己關閉時通知檔案源,但這會造成耦合。如果供應者和消費者之間有過濾器的話事情會變得很糟,就是說,一個 SpeedControlFilter 定位於 DownStreamAdapter 和 FileStreamAdapter 之間。        為了解決這一問題,我們可以引進一個事件模型。無論什麼時候一個供應者或者消費者連線到管道(pipe)時,所有注入到管道(pipe)的監聽者會被通知到,如果供應者和消費者也是監聽者他們會被自動通知。這就是為什麼我們看到在介面卡的類圖上所有的介面卡都實現了 IPipeConnectionListener 的原因。

        通過事件模型機制,資源釋放就很容易實現,而且還鬆耦合。請參考圖 Dispose.png,時序圖表明當 DownStreamAdapter 消亡時,FileStreamSourceAdapter 也會相應地消亡。FileStreamSinkAdapter 同理。

Dispose.png

        在這裡,DownStreamAdapter 關閉並且登出(斷開)了和管道(pipe)的連線。另一端的供應者(FileStreamSourceAdapter)收到通知也相應地關閉。現在讓我們想像一下當有一個 SpeedControllerFilter 在二者之間時的情形。過濾器將會被通知進而自行關閉。然後 FileStreamSourceAdapter 也會被通知到。所以事件從 DownStreamAdapter 向下通過管道(pipe)傳遞給了 FileStreamSourceAdapter。        * 日程列表(TODO list)        ** 設計訊息模型(Design the message model)        首先,我們需要確定下來所有通訊類的體系。我已經將它設計成包含頭和體的模型了,就像傳統的 JMS 所坐的那樣。我不確定這樣是否可以。我們需要蒐集所有的 Red5 用到的通訊型別並且把它們進行建模。到目前為止我認為 RTMPMessage 和 RawByteMessage 是我們需要的。        ** 管線的生命週期管理(Lifecycle management of pipeline)        通訊框架並沒有提及管線(pipeline 由管道(pipe)連線起來的供應者、消費者和過濾器)是如何建立、管理和銷燬的,我們應該考慮一個靈活的方案去實現這些。比如說,管線(pipeline)可以通過一個 xml 檔案進行配置(就像 cocoon 所做的那樣)。管線(pipeline)可以通過一個良好設計的 API 由應用程式開發人員在執行時進行建立,這樣應用程式開發人員可以決定管線(pipeline)是如何進行建立的:它是否需要一個 SpeedControlFilter,或者它將支援多點傳送,或者它將需要一個基於客戶端的安全管理(SecurityFilter)。應用程式開發人員也可以在自己的程式執行的時候改變管線(pipeline),就是說,實時地替換或者移除掉一個過濾器。        ** 管理管線的 API(API that manages the pipeline)        上面已經提到過了。        ** 流程式碼的重構(Refactor the streaming code)        使用介面卡只是權宜之計。我們需要將遺留的流處理程式碼進行重構讓它變的基於元件,這樣子這些元件才可以自行包含、自行文件和可以進行單元測試。這樣我們才可以把它們連線起來執行在我們的系統中。==========================================================================================================原文連結:http://osflash.org/pipermail/red5devs_osflash.org/2006-April/000703.html。