本文討論ActiveMQ傳輸檔案的幾種方法的原理及其利弊,作為訊息發送、直接傳輸檔案、使用ftp或http中轉。最後介紹擴充ActiveMQ實現自訂檔案傳輸方式,討論如何?高效的檔案傳輸。by kimmking
作為訊息發送
按照JMS規範,為了保證可靠性,所有的訊息都應該是發送到broker,然後交由broker來投遞的。也即是說其實JMS是不建議或不支援傳輸檔案的。
對於比較小的檔案,簡單的處理方式是先讀取所有的檔案成byte[],然後使用ByteMessage,把檔案資料發送到broker,像正常的message一樣處理。對於大檔案,例如1GB以上的檔案,這麼搞直接把client或是broker給oom掉了。
這種方式僅僅適用於小檔案的傳輸。特別是如果broker端使用資料庫作為儲存,message序列化以後存放於blob欄位,檔案傳輸頻繁或是稍微有點大,寫入效率極低。
直接傳輸檔案
為瞭解決傳輸大檔案的問題,ActiveMQ在jms規範之外引入了jms streams的概念。PTP模式下,連到同一個destination的兩端,可以通過broker中轉來傳輸大檔案。
發送端使用connection.createOutputStream開啟一個輸出資料流,往流裡寫檔案。
OutputStream out =connection.createOutputStream(destination);
接收端則簡單的使用connection.createInputStream拿到一個輸入資料流,從中讀取檔案資料即可。
InputStream in = connection.createInputStream(destination)
詳見:http://activemq.apache.org/jms-streams.html
使用非常簡單。ActiveMQ在中間做了什麼事情呢?
其實過程蠻曲折的,發送端拿到檔案後,首先分區,預設64K檔案資料為一個byte message,然後依次把所有的message發送到broker,broker轉寄給接收端,最後發送一個空訊息作為結束符。
connection上提供了兩個建立OutputStream的方法,一個是createOutputStream建立的是持久化的訊息集合,這些資料會寫到磁碟或是資料庫(對大檔案來說慢消費也是一件可怕的事兒);一個是createNonPersistOutputStream建立的是非持久化訊息集合,不會寫到磁碟上,如果沒有及時消費掉就慘了。
檔案片段的byte message的TTL設定為0,就是不會逾時進入DLQ。
優勢:簡單直接,處理非常小(不大於64K)的檔案非常方便。
劣勢:對大檔案,簡直就是噩夢。
檔案中轉方式
使用訊息的方式來傳遞大檔案,明顯不是一個有效率的辦法。檔案應該就是按檔案的方式去處理。
自己處理中轉
如果自己處理檔案的話,一個簡單方式是使用共用或ftp、dfs等方式,先把檔案發送到一個大家都可以拿到的地方,然後發送message,payload或properties中包含檔案的路徑資訊。這樣,consumer拿到檔案路徑後去指定的地方,按照給定的方式去擷取檔案資料即可。
優勢:這種方式可以用來處理大資料,並且不需要client或broker在記憶體中持有檔案資料本身,非常的節省資源。而且檔案是通過額外的方式處理,跟ActiveMQ本身無關,所以符合jms協議、處理的效率也相對比較高。
劣勢:需要自己處理很多檔案相關的操作。
BlobMessage對檔案中轉的封裝
幸運的是,ActiveMQ把上面繁複的檔案處理工作進行了封裝,屏蔽掉檔案中轉的整個處理過程,使得我們可以使用類似jms規範的API來簡單操作檔案傳輸。
舉個例子來說,典型的使用步驟:
發送端:
1. 啟動ActiveMQ時,也啟動jetty(即activemq.xml中有import jetty.xml),此時jetty中運行了一個ActiveMQ內建的http檔案伺服器
2. 使用tcp://localhost:61616?jms.blobTransferPolicy.defaultUploadUrl=http://localhost:8161/fileserver/建立connection,然後建立session和producer
3. 使用如下代碼傳送檔案:
BlobMessageblobMessage = session.createBlobMessage(file);
blobMessage.setStringProperty("FILE.NAME",file.getName());
blobMessage.setLongProperty("FILE.SIZE",file.length());
producer.send(blobMessage);
接收端比較簡單,正常的使用jms接收到訊息:
InputStream inputStream = blobMessage.getInputStream();
然後直接讀取檔案資料即可。檔案名稱和檔案大小可以從message的屬性中拿到。
這個過程中ActiveMQ做了什麼呢?
發送端:producer.send的時候,把檔案通過http協議的PUT方法發到jetty中的fileserver(預設128K走http的chunk分區傳輸)。然後把http的url寫入訊息中。再把訊息發送到broker。
接收端:接收到訊息以後,發現是BlobMessage,拿到url,直接使用GET方法擷取檔案資料。處理完畢後,使用DELETE方法從fileserver刪除檔案。
BlobMessage支援3種檔案中轉方式:
FILE
要求client和broker在同一個機器或者使用同一個共用儲存。傳送檔案的時候,把檔案從本地寫入到指定路徑。接收檔案的時候,把檔案從此路徑讀出來。
HTTP
使用http的fileserver,PUT/GET/DELETE方法。ActiveMQ內建了簡單的實現。就是前面情境中使用的方式。
FTP
使用一個獨立的ftpserver作為檔案中轉方式。傳送檔案的時候,把檔案發送到ftp伺服器。接收檔案的時候,從ftp把檔案讀取下來。
詳見:http://activemq.apache.org/blob-messages.html
優勢:訊息處理與檔案處理傳輸分開,極大的提高了檔案傳輸的效率。而且可以使用類似jms協議的方式來處理檔案發送。
劣勢:FILE方式不太實用。HTTP和FTP方式都需要額外的fileserver。
自訂檔案傳輸方式
ActiveMQ實現BlobMessage的三種檔案中轉方式時,使用了Façade和Strategy模式。ActiveMQBlobMessage需要在發送訊息時使用blobUploader上傳檔案、接收訊息時使用blobDownloader下載檔案。每種中轉方式的這兩個操作分別使用BlobUploadStrategy和BlobDownloadStrategy封裝。
所以,我們也可以根據ActiveMQBlobMessage檔案傳送的原理,實現自己的定製方式:
1、 給ActiveMQBlobMessage添加自己的blobDownloader和blobUploader來實現檔案的處理。
2、 擴充這個檔案中轉機制,實現BlobUploadStrategy和BlobDownloadStrategy。
一個更高效且不需要額外fileserver的實現思路是:broker上再開啟一個tcp的監聽連接埠,用來接收和轉寄檔案,當發送和接收端都在時,broker僅僅作為一個傳輸代理。接收端不在時,broker把資料存為本地臨時檔案,處理完畢後刪除掉。之所以使用一個新的連接埠來傳輸檔案資料而不是已有的transport,是為了避免jms streams這種命令和資料混合的模式。可以參考ftp協議,作為一種高效的檔案傳輸通訊協定,它有一個很大的特點就是命令的處理,和資料轉送的處理使用不同的連接埠和串連。而ActiveMQ使用的openwire協議其實就是一個個的操作命令。檔案分區、封裝、序列化,到另一頭再反向這個過程,無疑是效率很低下的。
大家有什麼想法都可以跟我交流:kimmking@163.com