接觸Orleans 有一段時間了,之前也翻譯了一系列官網文檔,今天我們就來一個實際的例子,來看看到底如何用這個東西來開發項目,當然經典的也是醉人的,我們就從HelloWorld開始吧。
通過前面的知識准備我們知道Orleans 項目需要n個服務端(就是silohost),n個客戶端(就是調用方),然后就是提供的actors(在Orleans 中成為grain),廢話少說。
首先建立一個解決方案,叫做OrleansSamples
然后,增加一個模塊解決方案,叫做HelloWorlds,在解決方案下增加兩個類庫Sample.Interfaces,Sample.Implements,其中Sample.Implements引用Sample.Interfaces,
這兩個項目中引用Orleans的核心庫,你可以手動一個一個引用進來,但還是老老實實的用nuget吧。
nuget的引用兩種方式一種,通過圖形化的方式,另一種通過命令的方式,命令:Install-Package Microsoft.Orleans.Core (注:在這里可以找到所需的包http://dotnet.github.io/orleans/NuGets)
記得引用完后如果在nuget里有更新就更新一下,對於新版本,可能里面有些庫會沒有進來,否則就會報錯,反正我是這么做,做完這一切,項目結構如下:
在Sample.Interfaces中增加一個接口IUserService,並且繼承接口IGrainWithIntegerKey(關於這個接口有姊妹接口,關於這些接口后續會陸續講到)
代碼如下:
namespace Sample.Interfaces { public interface IUserService:IGrainWithIntegerKey { Task<bool> Exist(string mobileNumber); } }
在Sample.Implements項目中增加實現類UserService,並且繼承Grain(Grain這個基類同時也提供了相應的泛型實現Grain<>,關於他們的不同點,以及功能,后續會講到),且實現IUserService接口
代碼如下:
namespace Sample.Implements { public class UserService : Grain, IUserService { public Task<bool> Exist(string mobileNumber) { return Task.FromResult<bool>(mobileNumber=="18612478956"); } } }
好了到此為止,我們已經開發好actor雖然簡單,接下來我們接着增加服務啟動寄宿項(關於寄宿項,可以是控制台、windows服務、winfrom、asp.net ),這里我們采用控制台,下面我們創建一個服務控制台應用程序(Server)
引用上面創建兩個項目:Sample.Implements、Sample.Interfaces。(注:其實這兩個項目不一定要引用進來,只要在生成項目的目錄下存在他們的編譯好的dll即可,silo用來自動加載啟動這個他們)
引用orleans項目中服務端的類庫(使用nuget命令:Install-Package Microsoft.Orleans.Server
)
項目結構如下:
代碼如下:
namespace Server { class Program { static void Main(string[] args) { using (var host = new SiloHost("Default")) { host.InitializeOrleansSilo(); host.StartOrleansSilo(); Console.WriteLine("啟動成功!"); Console.ReadLine(); host.StopOrleansSilo(); } } } }
好一切准備就緒,我們F5吧,
當你看到這得時候是不是覺得成功了,真的成功了嗎,不一定吧!
哦對了怎么沒看到日志呢,好我們在項目目錄下看看日志:
果然有日志文件,一看文件名稱,直接告訴我們發生錯誤了,好讓我們打開看看吧。
從發生的異常看出,好像少了一個配置文件,在orleans 服務啟動時,需要一個配置文件,這個配置文件可以是OrleansConfiguration.xml或者orleans.config或者orleans.config.xml
好知道原因了,知道該怎么做了吧,在server根目錄下創建一個xml文件OrleansConfiguration.xml,將該文件的屬性“復制到輸出目錄”值更改為"如果較新則復制"
該文件中填充配置內容,如下(詳細配置請看配置一節,此處不解釋)

<?xml version="1.0" encoding="utf-8" ?> <OrleansConfiguration xmlns="urn:orleans"> <Globals> <StorageProviders> <Provider Type="Orleans.Storage.MemoryStorage" Name="MemoryStore" /> <Provider Type="Orleans.Storage.MemoryStorage" Name="Default" /> <!--<Provider Type="Orleans.Storage.AzureTableStorage" Name="AzureStore"/>--> </StorageProviders> <SeedNode Address="localhost" Port="22222"/> <Messaging ResponseTimeout="30s"/> </Globals> <Defaults> <Networking Address="localhost" Port="22222"/> <ProxyingGateway Address="localhost" Port="40000" /> <Tracing DefaultTraceLevel="Info" TraceToConsole="false" TraceToFile="{0}-{1}.log" PropagateActivityId="false" BulkMessageLimit="1000"> <TraceLevelOverride LogPrefix="Application" TraceLevel="Info" /> <!-- <TraceLevelOverride LogPrefix="Runtime.Dispatcher" TraceLevel="Verbose" /> <TraceLevelOverride LogPrefix="AssemblyLoader.Silo" TraceLevel="Warning" /> --> </Tracing> <Statistics MetricsTableWriteInterval="30s" PerfCounterWriteInterval="30s" LogWriteInterval="300s" WriteLogStatisticsToTable="true" StatisticsCollectionLevel="Info"/> </Defaults> </OrleansConfiguration>
再次啟動F5,當看到啟動成功的輸出時,我們再次看看生成的日志:
這次發現比之前生成的東西多了,但是當我們繼續往下瀏覽的時候發現有個異常:
Exc level 0: System.IO.FileNotFoundException: 未能加載文件或程序集“Microsoft.Extensions.DependencyInjection.Abstractions, Version=1.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60”或它的某一個依賴項。系統找不到指定的文件。
在 Orleans.Runtime.Startup.ConfigureStartupBuilder.ConfigureStartup(String startupTypeName)
在 Orleans.Runtime.Startup.ConfigureStartupBuilder.Orleans.Runtime.Startup.IStartupBuilder.ConfigureStartup(String startupTypeName)
在 Orleans.Runtime.Silo..ctor(String name, SiloType siloType, ClusterConfiguration config, ILocalDataStore keyStore)
原來是少了程序集:Microsoft.Extensions.DependencyInjection
知道原因了,我們通過nuget來引用這個類庫,引用成功,再次運行,然后查看日志,異常消失,但是有個問題,每次打開日志文件要看,是否有錯誤,或者一些關於服務的監控內容,這樣是不是很麻煩,其實我們可以更改一下配置信息,讓它輸出到控制台,這樣在開發過程中就方便多了,可以時時看到動態信息,如下:
打開OrleansConfiguration.xml 文件找到<Tracing DefaultTraceLevel="Info" TraceToConsole="false" TraceToFile="{0}-{1}.log" 這個節點,將TraceToConsole的值更改為true保存,再次運行,如下:
好了一切都完美了,接下來我們在繼續開發客戶端。
在解決方案下創建一個控制台應用程序Client,引用客戶端相關類庫:Install-Package Microsoft.Orleans.Client
引用項目:Sample.Interfaces
代碼如下:

namespace Client { class Program { static void Main(string[] args) { System.Threading.Thread.Sleep(15000); GrainClient.Initialize(); while (true) { Console.WriteLine("請輸入用戶手機號:"); var mobileNumber = Console.ReadLine(); //這里由於我們采用的grain繼承的是IGrainWithIntegerKey ,所以我們采用調用數值類型的key=10來創建這個grain, //可能有人會問key是干嘛的,他是唯一標識這個grain的,當你指定一個key的時候,Orleans 會創建一個,它首先到 //你的存儲介質中找(如果你配置了的話,默認采用內存存儲,這種方式適合開發期,生產環境需要保持狀態的,所以需要配置到能持久化存儲的地方去,比如sqlserver等) //如果找到了就直接返回,如果沒找到就根據你指定的這個key然后創建一個,這個就是grain的激活,具體詳細的,可以看官方問的關於Grain一章。 var userService = GrainClient.GrainFactory.GetGrain<IUserService>(10); //C#的一種新的表達式語法,這樣就方便多了,省的我們拼接字符串。 Console.WriteLine($"用戶{mobileNumber},{(userService.Exist(mobileNumber).Result?"已經存在":"不存在")}"); } } } }
在client項目下記得要創建配置文件,文件名稱叫做ClientConfiguration.xml
內容如下:

<?xml version="1.0" encoding="utf-8" ?> <ClientConfiguration xmlns="urn:orleans"> <Gateway Address="localhost" Port="40000"/> <!-- To turn tracing off, set DefaultTraceLevel="Off" and have no overrides. For the trace log file name, {0} is replaced by "Client" and {1} is the current time. --> <Tracing DefaultTraceLevel="Info" TraceToConsole="false" TraceToFile="{0}-{1}.log" BulkMessageLimit="1000"> <TraceLevelOverride LogPrefix="Runtime" TraceLevel="Info" /> <TraceLevelOverride LogPrefix="Application" TraceLevel="Info" /> <TraceLevelOverride LogPrefix="AssemblyLoader" TraceLevel="Warning" /> </Tracing> <Statistics MetricsTableWriteInterval="300s" PerfCounterWriteInterval="30s" LogWriteInterval="300s" WriteLogStatisticsToTable="true" StatisticsCollectionLevel="Info"/> <Messaging ResponseTimeout="30s" ClientSenderBuckets="8192" MaxResendCount="0"/> </ClientConfiguration>
注:要記得更改文件屬性哦
一切准備就緒,下來讓改一下啟動方式為多項目啟動,然后就F5等待飛吧!
終於看到勝利的果實了,哈哈!接下來我們接着說說另外一種開發方式以及發布方式。
上面的這種開發方式為了說明開發的那些具體步驟,需要引用那些類庫,以及客戶端如何去調用,步驟比較麻煩,尤其是服務端的開發、引用類庫,也沒有相應的單元測試,接下來我們看看另外一種服務端的開發方式。
跟上面的大體步驟一樣
1.創建接口類庫
2.創建實現類庫
3.開發測試服務寄宿程序
在開始之前首先要確認一下你是否安裝了Orleans的vs模版插件,如果安裝了那么如下圖:
如果沒有安裝,趕緊去下載一個吧地址在:http://dotnet.github.io/orleans/NuGets
找到這一節,如下圖:
點開里面有你想要的插件,然后安裝重啟vs
1.創建接口類庫
2.創建實現類庫
3.創建服務寄宿程序
服務創建完之后,發現下面有自動生成的一個類OrleansHostWrapper,並且在Program下自動生成了很多代碼,代碼的大體意思就是,將服務端啟動程序的邏輯封裝在OrleansHostWrapper,然后啟動是單獨創建一個應用程序域,增加一些測試例子代碼,方便多了吧,我們不需要寫任何服務端的服務啟動代碼,在實際開發過程中我們只需要關心業務模塊
即接口創建,接口實現,方便多了吧。
將相應的代碼貼入進去,還記得上面出現的那個異常嗎,記得要將Microsoft.Extensions.DependencyInjection類庫引用進來哦,F5吧。
一切如預期所料,成功!
接下來我們創建一個單元測試程序庫,方便服務端的程序單元測試Sample.Test
引用項目Sample.Implements、Sample.Interfaces
引用Orleans測試包 PM> Install-Package Microsoft.Orleans.TestingHost

我們創建一個測試類就叫UserServiceTest,繼承自Orleans 測試庫TestingSiloHost

[ClassCleanup] public static void ClassCleanup() { // Optional. // By default, the next test class which uses TestignSiloHost will // cause a fresh Orleans silo environment to be created. StopAllSilosIfRunning(); }
測試代碼如下:

namespace Sample.Test { [TestClass] public class UserServiceTest: TestingSiloHost { [ClassCleanup] public static void ClassCleanup() { // Optional. // By default, the next test class which uses TestignSiloHost will // cause a fresh Orleans silo environment to be created. StopAllSilosIfRunning(); } [TestMethod] public async void TestExist() { var grain = GrainFactory.GetGrain<IUserService>(10); bool bo = await grain.Exist("18612478956"); Assert.IsTrue(bo); } } }
記得增加兩個配置文件,
ClientConfigurationForTesting.xml

<?xml version="1.0" encoding="utf-8" ?> <ClientConfiguration xmlns="urn:orleans"> <Gateway Address="localhost" Port="40000"/> <!-- To turn tracing off, set DefaultTraceLevel="Off" and have no overrides. For the trace log file name, {0} is replaced by "Client" and {1} is the current time. --> <Tracing DefaultTraceLevel="Info" TraceToConsole="false" TraceToFile="{0}-{1}.log" BulkMessageLimit="1000"> <TraceLevelOverride LogPrefix="Runtime" TraceLevel="Info" /> <TraceLevelOverride LogPrefix="Application" TraceLevel="Info" /> <TraceLevelOverride LogPrefix="AssemblyLoader" TraceLevel="Warning" /> </Tracing> <Statistics MetricsTableWriteInterval="300s" PerfCounterWriteInterval="30s" LogWriteInterval="300s" WriteLogStatisticsToTable="true" StatisticsCollectionLevel="Info"/> <Messaging ResponseTimeout="30s" ClientSenderBuckets="8192" MaxResendCount="0"/> </ClientConfiguration>
OrleansConfigurationForTesting.xml

<?xml version="1.0" encoding="utf-8"?> <OrleansConfiguration xmlns="urn:orleans"> <Globals> <StorageProviders> <Provider Type="Orleans.Storage.MemoryStorage" Name="MemoryStore" /> <Provider Type="Orleans.Storage.MemoryStorage" Name="Default" /> <!--<Provider Type="Orleans.Storage.AzureTableStorage" Name="AzureStore"/>--> </StorageProviders> <SeedNode Address="localhost" Port="22222"/> <Messaging ResponseTimeout="30s"/> </Globals> <Defaults> <Networking Address="localhost" Port="22222"/> <ProxyingGateway Address="localhost" Port="40000" /> <Tracing DefaultTraceLevel="Info" TraceToConsole="false" TraceToFile="{0}-{1}.log" PropagateActivityId="false" BulkMessageLimit="1000"> <TraceLevelOverride LogPrefix="Application" TraceLevel="Info" /> <!-- <TraceLevelOverride LogPrefix="Runtime.Dispatcher" TraceLevel="Verbose" /> <TraceLevelOverride LogPrefix="AssemblyLoader.Silo" TraceLevel="Warning" /> --> </Tracing> <Statistics MetricsTableWriteInterval="30s" PerfCounterWriteInterval="30s" LogWriteInterval="300s" WriteLogStatisticsToTable="true" StatisticsCollectionLevel="Info"/> </Defaults> </OrleansConfiguration>
更改xml文件屬性為:如果較新則復制
測試項目結構大體如下:
好,測試編寫完成,如何操作你懂得,這里就不廢話了。
接下來說一下部署,部署吧,各有各的妙招,控制台、winform、windows服務等等,這里我說一個框架自帶的一個部署控制台怎么用
記得Orleans 里面有這么一個程序OrleansHost.exe,他是干什么用的呢,對了就是用來部署的。
我們來看看他的源碼,弄清楚他到底是做了一件什么事情
如下圖:
打開這個文件,可以發現這個文件跟上面我們通過模版創建的server中的文件OrleansHostWrapper很相似,對了,這個就是為了保持開發部署的一致性,所以這個就可以用來直接部署了
我們在server的Debug下找到相應的程序,將程序復制到某個盤符比如D:\demo下面
如下圖:
然后將配置文件拷貝進來OrleansConfiguration.xml
一切准備就緒,我們運行把,雙擊StartOrleans.cmd服務啟動
運行成功.
你會發現這個服務部署於我們的開發互不影響,當我們開發好一個grain的時候,直接編譯丟到這個部署目錄下,別的地方就可以訪問了,可以讓我們重點關注業務邏輯,而不需要關心那些復雜的配置或者服務的開啟關閉等等。
實例代碼