“一切都是訊息”--MSF(訊息服務框架)入門簡介
“一切都是訊息”--這是MSF(訊息服務框架)的設計哲學。
MSF的名字是 Message Service Framework 的簡稱,中文名稱:訊息服務框架,它是PDF.NET框架的一部分。
1,MSF誕生的背景
MSF最初來源於2009年,我們為某銀行開發的基金投資分析系統,由於銀行安全的原因並且這些投資資料屬於機密資料,規定必須使用郵件系統來發送這些資料,但是郵件的收發不是直接針對人,而是兩端的計算機程式。為了及時向客戶傳送這些投資資訊,我們使用WCF開發了基於郵件的通訊系統。後來,從這套系統中分離出來跟業務無關的“訊息推送框架”,這就是MSF的雛形。
2011年,我來到某線上拍賣的電商創業公司,公司要求在1個月內開發出一套拍賣客戶端軟體。大家都知道創業公司的工作節奏,這麼短時間要開發出一套類似炒股軟體的線上拍賣軟體是很難的,幸好有之前的“訊息推送框架”,買家通過競拍軟體,實時更新競拍價格,眾多買家和賣家線上完成拍賣,整點搶拍,“訊息推送框架”作為競拍軟體的基礎服務通訊框架,保證了整個軟體的研發成功。不幸的是當時很多客戶用的還是XP系統,還是深度克隆版,裝不上.NET框架,半年後客戶端產品放棄了,公司整體上轉向BS應用結構和移動APP軟體,“訊息推送框架”在公司也隨著客戶端產品的遺棄而不再有人提起。
2015年,“訊息推送框架”納入PDF.NET框架集合,正式命名為MSF,對外開源。MSF跟SOD框架一起,成為PDF.NET的企業框架集合的成員,其位置如下圖所示(WCF Message Service Framework):
![](https://img.796t.com/res/2022/04-29/02/1dbdf8d57be767af9bdeff14db4be609.png)
2,MSF的技術架構
- 基於WCF技術構建
- 成熟,穩定,安全可靠
- 極簡配置,拿來即用
- 基本上只需要配置一下監聽地址和埠號即可
- MSF Host-服務的容器
- 不需要再開發宿主程式,寫好的服務元件直接放入宿主程式即可使用,就像Web應用寄宿在IIS上面一樣。
- NetTcpBinding,雙工通訊
- 二進位制通訊,速度更快
- 請求-響應的模式
- 絕大多數RPC框架呼叫服務的方式
- 釋出-訂閱的推送模式
- 伺服器釋出服務,客戶端訂閱服務,伺服器向客戶端推送訊息
- 非同步通訊
- 內部基於雙工回撥實現非同步功能
下面是技術架構圖:
![](https://img.796t.com/res/2022/04-29/02/858eb0b9945e498cd4f84dc2e18a61a3.png)
3,MSF設計哲學
MSF:一切都是訊息
訊息不都是佇列暫存,也可以是實時的:
我們一看到訊息,容易想到訊息佇列的,訊息都儲存在訊息佇列中,但實際上,訊息也可以不存在訊息佇列中(資料庫,檔案都可以作為訊息佇列持久化的一種方式),訊息可以出現在函式的引數上,類或者方法中的變數上,作為實時訊息來處理。
命令是訊息,事件也是訊息:
在CQRS架構中,將操作分為命令和查詢,命令改變物件的狀態,查詢僅查詢物件的狀態。如果是基於分散式環境的CQRS架構,那麼這些命令和查詢,本質上還是客戶端與服務端的訊息通訊而已。
在另外一些架構中,物件的操作可能也會分為操作命令和操作結果引發的事件的概念,假如這些操作的物件相互是隔離的,物件之間的這種通訊還是基於訊息的,只不過是程序內的訊息。
每種不同的訊息可以看做是物件的不同方法:
如果我們要進行一個RPC呼叫,客戶端向伺服器端傳遞訊息,這種訊息最終是對映到伺服器端物件的不同方法的。比如常見的WebAPI這種RPC,我們跟伺服器通訊的就是一個個呼叫API的URL形式的訊息。
服務是訊息的生產者,客戶是訊息的消費者:
這裡說明的是服務端,客戶端與訊息3者的關係,服務端提供服務,客戶端使用服務,但是服務端提供什麼樣的服務,客戶端要呼叫那個服務,都需要訊息交換,比如一家餐廳提供餐飲服務,它要生產一條提供服務的訊息,比如在門口掛一個牌子,牌子上寫明本店賣什麼菜品;一個顧客經過這家餐廳,看到這個牌子,覺得正好有自己喜歡的菜品,於是進餐廳就餐,顧客的消費過程,其實首先消費的是這個牌子上寫的菜品訊息。
MSF:服務不是被動的,也可以是主動的
我們用慣了WebService,RPC等,可能習慣性的認為,服務都是被動請求然後提供服務的,但在實際生活中,商家這樣服務是越來越不行了,比如前面餐廳的例子,它提供餐飲服務的,競爭的人多了,老闆只好親自站在門口,問路過的小哥:
“帥哥想吃什麼?這裡啥都有。”
這個時候,餐飲資訊由之前的餐飲資訊牌子,變成老闆的吆喝聲音主動進入你的耳朵,而不是等著你去看那個牌子了。顯然,服務可以是主動的,並且主動服務效果更好。這個道理現實生活中如此,程式世界也是如此,我們需要將我們的服務及時的推送給客戶端,這樣客戶端就由主動呼叫服務變成了被動接受服務了。
MSF:非同步無處不在
世界的本質是非同步的,你永遠跟不上光的腳步!
愛因斯坦在頭腦中對光的思考,誕生了偉大的“相對論”理論,我想它不會反對我說的這句話:)
不過,只有一個人他一定會首先出來反對我,那就是 馬克斯·普朗克,他說“量子糾纏” 效應的發生,幾乎就是同步的!
普朗克的量子世界距離我們普通人的世界有點遠,對我們普通人而言,說“非同步無處不在”沒什麼大問題:)
舉個例子:
古時候,將軍在邊疆駐軍,皇帝要想對敵人發動攻擊,他要寫一道金牌,然後讓傳令兵八百里加急,晝夜馳騁將軍令送到將軍手中。將軍接到命令的時候,離皇帝下達命令,往往已經過去好幾天了,將軍接到命令與皇帝發出命令,在時間上總是有差異的,而這個時間差異可能敵情已經發生了變化,將軍需要根據實際情況來決定,是馬上發動攻擊還是暫時不執行命令,所以才有“將在外軍令有所不受”的說法。當然,現在有了電報電話,可以用這些先進手段來通訊,但還是有延遲,比如我們看到的電視直播一樣,主持人詢問現場記者情況,記者總是會等幾秒才能迴應。
在計算機通訊領域,我們來看同步和非同步的區別。
同步是指:傳送方發出資料後,等接收方發回響應以後才發下一個數據包的通訊方式。 非同步是指:傳送方發出資料後,不等接收方發回響應,接著傳送下個數據包的通訊方式。 同步是阻塞模式,非同步是非阻塞模式。所以,現在很多大型分散式系統都採用非同步通訊來提高系統的處理能力,隨著大資料雲端計算越來越流行,非同步處理變得越來越常見。
4,MSF的技術特點
- 無需WCF繁瑣的配置,無需學習WCF知識。
- MSF Host作為MSF服務元件的宿主程序,它是一個控制檯程式;同時,MSF Host也是服務的容器,它可以執行多個使用者開發的MSF服務元件。
- MSF內建快取服務,會話服務和身份驗證服務。
- MSF支援“服務叢集”功能,包含叢集監控管理和叢集節點排程,節點負載均衡。
- 任何業務類只需要繼承MSF的服務介面,就可以釋出為MSF的服務元件。
- 任何一個MSF服務類,都可以作為RPC模式或者服務推送模式使用,兩種模式都支援同步或者非同步呼叫,具體使用哪種方式僅僅取決於客戶端Proxy。
- 服務推送支援定時推送和觸發推送兩種方式,觸發推送可以將服務內部的業務事件作為分散式事件推送給其它服務節點或客戶端。
5,MSF與Actor程式設計模型
Actor模型是一個概念模型,用於處理併發計算。它定義了一系列系統元件應該如何動作和互動的通用規則,最著名的使用這套規則的程式語言是Erlang。這篇文章更關注模型本身而不是它在不同語言的實現。
一個Actor指的是一個最基本的計算單元。它能接收一個訊息並且基於其執行計算。 這個理念很像面嚮物件語言,一個物件接收一條訊息(方法呼叫),然後根據接收的訊息做事(呼叫了哪個方法)。
以上內容,來自《10 分鐘瞭解 Actor 模型》,更多內容請參考原文。
Actor模型作為一種重要的併發程式設計模型,它比作業系統原生的基於執行緒的變法程式設計模型,提供了更高的抽象,基於Scala語言開發的Akka,是JAVA虛擬機器JVM平臺上構建高併發、分散式和容錯應用的工具包和執行時。
Akka它處理併發的方法基於Actor模型。在Akka裡,Actor之間通訊的唯一機制就是訊息傳遞。Akka的流行使得Actor這種程式設計模型被人們討論的越來越多。
![](https://img.796t.com/res/2022/04-29/02/be8d3f9b37c057e1be249395ad9dea83.png)
MSF的設計哲學之一就是“一切都是訊息”,所以MSF跟Actor模型有一些共同之處:
- Actor模型=資料+行為+訊息
- Actor模型內部的狀態由自己的行為維護,外部執行緒不能直接呼叫物件的行為,必須通過訊息才能激發行為,這樣就保證Actor內部資料只有被自己修改。
- Remote Actor有Actor Path,例如:
- akka://[email protected]:2552/user/SomeActor
- MSF模型=服務+訊息
- MSF模型中服務的呼叫和服務的處理結果,都以訊息來表示,要改變服務的狀態,必須使用訊息
- MSF通過訂閱一個服務,建立一個服務的例項,這些例項相當於一些Actor,它可以通過訊息再呼叫別的Actor.
- MSF也有Service Path,例如:
- Service://Calculator/Add/System.Int32=1&System.Int32=2
6,有關MSF的疑問
我在向社群朋友們介紹MSF的時候,常常聽到下面這些疑問,我想看到今天這篇文章的讀者或許也有類似的疑問,所以很有必要在這裡先做一個問題釋疑,以便你在決定是否使用MSF的時候做一個根本性的判斷,比如你要求很高效能的RPC呼叫,那你不適合使用MSF,因為它基於WCF,高效能不是WCF的設計目標。
- MSF是訊息服務框架,但它不是訊息佇列;
- MSF的訊息不做持久化,都是實時的;
- MSF不是僅僅處理訊息的,重點在於服務,訊息是服務呼叫過程的抽象資料;
- MSF不是一個RPC框架,這只是其中一個功能;
- MSF不做B/S的訊息推送,而是C/S, S/S的通訊;
- MSF推送的不是訊息,而是服務;
- MSF基於WCF,所以不要苛求它RPC的高效能,而應該是通訊的成熟、穩定和可靠。
對於第2點,雖然MSF不做訊息的持久化,但你可以在訊息傳送後或者接收後自己做這種持久化功能;
對於第5點,如果需要做B/S的訊息推送,可以使用WebSocket,而在WebServer端,它可以跟業務伺服器之間使用MSF,訊息由業務伺服器推送到Web伺服器,最後再推送到瀏覽器;
對於第6點,MSF推送的不是訊息,而是服務,有一位技術總監跟我爭論了很久,他說服務端推送的不過是一些給客戶端的資料而已,不是訊息,更不是什麼服務。這位總監說得沒錯,但我說MSF推送的是服務,只不過是對他說法的一個更加高階的抽象而已。
我想,有一句廣告詞很適合來詮釋我和他的分歧:
沒錯,你喝的是汽水,我喝的是北冰洋!
我不是成心要在這裡給一種汽水做廣告,只是覺得這個廣告實在是很適合來說明我的問題。
7,獲取MSF
MSF現在是開源軟體,使用前,你需要遵守LGPL開源協議,LGPL對商業友好,你可以放心的使用,當然你可以聯絡我們獲得技術支援。
- 獲取原始碼,請點選 http://pwmis.codeplex.com ,未來將遷移到GitHub,敬請關注。
- 獲取程式包,請在程式包管理程式搜尋 PDF.NET.MSF,如下圖:
![](https://img.796t.com/res/2022/04-29/02/44cd9ea62f6002bbe31ccd7703b6aaec.png)
檢視資訊,請瀏覽網址:https://www.nuget.org/packages?q=PDF.NET.MSF 程式包分為客戶端、服務端和宿主環境,分別是:
- PDF.Net.MSF.Client
- PDF.Net.MSF.Service
- PDF.Net.MSF.Service.Host
8,MSF使用入門
在當前這個入門示例中,我們首先來演示下MSF的“訊息對話”功能,讓MSF的客戶端和服務宿主程式直接進行對話通訊。
示例步驟
1,建立一個MSFTest解決方案,新增一個控制檯專案MSFTest 2,包管理控制檯,選擇該專案,然後輸入:
Install-Package PDF.Net.MSF.Service.Host
3,在解決方案新增一個TestClient 控制檯專案
4,包管理控制檯,選擇該專案,然後輸入:
Install-Package PDF.Net.MSF.Client
此時解決方案資料夾如下圖:
![](https://img.796t.com/res/2022/04-29/02/8dbb8363e28acb8b3de243054b39e9cc.png)
5,在TestClient控制檯專案裡面,新增如下程式碼:
class Program
{
static void Main(string[] args)
{
Console.WriteLine("******** PDF.NET MSF 客戶端測試程式 *********");
Console.WriteLine();
Proxy client = new Proxy();
client.ErrorMessage += client_ErrorMessage;
Console.Write("請輸入伺服器的主機名或者IP地址(預設 127.0.0.1):");
string host = Console.ReadLine();
if (string.IsNullOrEmpty(host))
host = "127.0.0.1";
Console.WriteLine("服務地址:{0}",host);
Console.Write("請輸入服務的埠號(預設 8888):");
string port = Console.ReadLine();
if (string.IsNullOrEmpty(port))
port = "8888";
Console.WriteLine("服務埠號:{0}", port);
client.ServiceBaseUri = string.Format("net.tcp://{0}:{1}", host, port);
Console.WriteLine("當前客戶端代理的服務基礎地址是:{0}",client.ServiceBaseUri);
Console.WriteLine();
string repMsg = "你好!";
client.SubscribeTextMessage("我是客戶端", serverMessage => {
Console.WriteLine();
Console.WriteLine("[來自伺服器的訊息]::{0}", serverMessage);
});
while (repMsg != "")
{
Console.Write("回覆伺服器(輸入為空,則退出):>>");
repMsg = Console.ReadLine();
client.SendTextMessage(repMsg);
}
Console.WriteLine("測試完成,退出");
}
static void client_ErrorMessage(object sender, MessageSubscriber.MessageEventArgs e)
{
Console.WriteLine("---處理服務時錯誤:{0}",e.MessageText);
}
}
6,生成MSFTest專案 如果已經生成過,請右鍵選單,重新生成專案,這一步將自動啟動MSF Host。
7,執行TestClient專案 在服務端和客戶端隨意輸入文字內容,服務端可以將訊息推送給所有訂閱此訊息的客戶端。
![](https://img.796t.com/res/2022/04-29/02/ce59178bd373f797677ead5814a5dc81.png)
如何啟動MSF Host
在VS解決方案資源管理器上,選擇安裝過 nuget 程式包 PDF.Net.MSF.Service.Host 的專案,右鍵選單,“重新生成”命令,即可啟動MSF Host,它是在Nuget安裝程式包的時候,給專案的編譯前後添加了事件實現的:
copy /y "$(TargetDir)*.*" "$(SolutionDir)Host"
cd "$(SolutionDir)Host"
start "MessageService Host" "SucessCompiled.vbs"
為MSF Host新增防火牆規則
如果你需要讓MSF Host遠端訪問,可能需要管理防火牆規則,用管理員許可權開啟 CMD命令,執行下面的命令列:
netsh advfirewall firewall add rule name="PDF.NET.MSF.Host" dir=in action=allow protocol=TCP localport=8888
也可是指定程式路徑方式來新增防火牆規則:
netsh advfirewall firewall add rule name="PDF.NET.MSF.Host" dir=in action=allow program="D:MSFHostPdfNetEF.MessageServiceHost.exe"
訂閱和傳送文字訊息
MSF客戶端程式,可以直接訂閱MSF服務宿主的文字訊息服務,之後,就可以隨時向MSF服務宿主傳送文字訊息,並且能夠非同步的從MSF服務宿主接受訊息。
相關的程式碼如下:
Proxy client = new Proxy();
client.ServiceBaseUri = string.Format("net.tcp://{0}:{1}", host, port);
client.SubscribeTextMessage("我是客戶端", serverMessage => {
Console.WriteLine();
Console.WriteLine("[來自伺服器的訊息]::{0}", serverMessage);
});
while (repMsg != "")
{
Console.Write("回覆伺服器(輸入為空,則退出):>>");
repMsg = Console.ReadLine();
client.SendTextMessage(repMsg);
}
服務代理物件的SubscribeTextMessage 方法發起文字定義,並且接受一個非同步訊息的委託。訂閱之後,只要不關閉連線,之後隨時可以使用 SendTextMessage 傳送訊息。
這樣,一個簡單的MSF訊息通話示例就做好了,我們看到在伺服器端一行程式碼都沒有編寫。
如果要自定義我們的業務服務,就需要寫一點程式碼了,但也很簡單,下一篇再繼續,
或者你可以先看看網友寫的介紹:
SOD開源框架MSF(訊息服務框架)介紹
歡迎加入我們的QQ群討論MSF框架的使用,群號:敏思(PWMIS) .NET 18215717,加群請註明:PDF.NET技術交流,否則可能被拒。