Orleans 解決并發(fā)之痛(一):單線程

程序在運行過程中有時會莫名其妙出現(xiàn)代碼的某些約束或者執(zhí)行結(jié)果和理想狀況不一樣,正常邏輯怎么會出現(xiàn)這樣的情況?到底發(fā)生了什么?好像見了鬼!瞬間好無助。

誰來救救我

大多數(shù)出現(xiàn)正常邏輯很難解釋的時候,我們可能會想到并發(fā)問題,因為好像只有并發(fā)才會能說服自己。為了驗證和解決這個問題,我們可能會嘗試一些方案,在并發(fā)的情況下我相信很多人都使用過鎖,鎖確實也能幫忙我們解決問題,不然它干嘛存在。

但隨著業(yè)務邏輯的持續(xù)復雜,鎖的使用可能無處不在。首先大家都知道鎖本身的機制很耗性能;然后鎖本身不涉及什么編程模式,所以在業(yè)務代碼中融入大量鎖對代碼本身的穩(wěn)定性也有一定影響。

經(jīng)過查找資料,因為本身的項目是基于 .NET,所以發(fā)現(xiàn) Microsoft Orleans 好像可以比較好的滿足解決并發(fā)的需求。

Orleans 之前,先來扯一扯 Actor 模型

  1. Actor 是以單線程存在的,所有消息都是順序到達的,每次收到消息后,就放入隊列,而它每次也從隊列中取出消息體來處理;

  2. 每一個 Actor 有一個 Id 和它對應,一個 Id 對應的 Actor 只會在 集群中 存在一個,使用者只需要通過 Id 就能隨時訪問不需要關注該 Actor 在集群的什么位置;

  3. 每一個 Actor 看作是一個獨立的實體,擁有自己獨立的狀態(tài)。Actor 與 Actor 之間可以進行消息通知;

注:有狀態(tài)的 Actor 在集群中一個 Id 只會存在一個實例,無狀態(tài)的可配置為根據(jù)流量存在多個,無狀態(tài)的情況看具體業(yè)務需求。

Actor System

再來扯一扯 Orleans 框架

Orleans 提供了一個簡單的方法來構(gòu)建大規(guī)模、高并發(fā)、分布式應用程序,被認為是 Actor 模型的分布式版本,是一種改進的 Actor 模型。在 Orleans 中,Actors 被稱作 Grains,采用接口來表示,Actors 的消息用異步方法來接收,方法返回值必須是 Task or Task<T>。

Orleans幾個核心角色:

Grains(Actors)

Grains 是 Orleans 應用程序的業(yè)務邏輯實現(xiàn)與抽象,Grains 是彼此孤立的原子單位,分布的,持久的。 一個典型的 Grain 是有狀態(tài)和行為的一個單實例。

Silo

Silo 是一個主機服務,里面主要用于執(zhí)行 Grains,也就是說 Grains 開發(fā)完成后需要注冊到 Silo 中,然后等待調(diào)用。它監(jiān)聽一個端口,用來監(jiān)聽從 Silo 到 Silo 的消息或者從客戶端到 Silo 的消息的,典型的 Silo 就是,每臺機器運行一個 Silo,會對外暴露網(wǎng)關地址供調(diào)用。

Cluster(集群搭建的時候會具體介紹)

大量的 Silo 同時在一起工作就形成了 Orleans 的集群,Orleans 運行完全自動化的集群管理。

Client

具體的應用客戶端,可以是控制臺、Web 應用程序、WPF 等一切 .NET 端技術。


開始接觸 Orleans Sample 的時候,第一感覺項目結(jié)構(gòu)和 gRPC 還挺像的,如果你之前有接觸,一定感覺很親切:

  1. 定義一個接口(Interfaces)
  2. 實現(xiàn)接口(Grains) -- 添加引用Interfaces
  3. 啟動服務端(Silo)-- 添加引用Interfaces,Grains
  4. 啟動客戶端 (Client)-- 添加引用Interfaces

練習過程中對 NuGet 安裝 Orleans 相關依賴包可能會有一些模糊,這里說明一下我的具體步驟,希望盡快幫忙實現(xiàn)效果,所有程序集使用 .NET Framework 的版本都是 4.6:

程序集名稱 類型 NuGet 依賴包
Microsoft.Orleans.
引用
Interfaces 類庫 Core -
Grains 類庫 Core Interfaces
Silo 控制臺程序 Core
OrleansCodeGenerator
OrleansProviders
OrleansRuntime
Interfaces
Grains
Client 控制臺程序 Core
OrleansCodeGenerator
Interfaces

在 Silo 項目中添加配置文件 OrleansConfiguration.xml:

<?xml version="1.0" encoding="utf-8" ?>
<OrleansConfiguration xmlns="urn:orleans">
  <Globals>
    <SeedNode Address="localhost" Port="11111" />
  </Globals>
  <Defaults>
    <Networking Address="localhost" Port="11111" />
    <ProxyingGateway Address="localhost" Port="30000" />
  </Defaults>
</OrleansConfiguration>

SeedNode:集群中主 Silo 地址,生產(chǎn)環(huán)境下不要這么使用。以這種方式配置主 Silo 的情況下,其他 Silo 加入集群需要等主 Silo 先啟動。之后會介紹 SystemStore 來維護集群成員關系;
Networking:內(nèi)部 Silo 與 Silo 之間通信地址;
ProxyingGateway:客戶端調(diào)用的網(wǎng)關地址;

在 Client 項目中添加配置文件 ClientConfiguration.xml:

<?xml version="1.0" encoding="utf-8" ?>
<ClientConfiguration xmlns="urn:orleans">
  <Gateway Address="localhost" Port="30000"/>
</ClientConfiguration>

Gateway:配置 Silo 對外的網(wǎng)關地址;

集群下可以配置多個 Gateway 節(jié)點,如下:

<Gateway Address="gateway1" Port="30000"/>
<Gateway Address="gateway2" Port="30000"/>

注意:配置文件需要設置屬性 "復制到輸出目錄"

configuration

Grain 說明:

每個 Grain 都是單實例的,具有唯一標識。根據(jù)唯一標識獲取 Grain,這個標識可以是 GUID、String、Long、混合類型。

在 Grain 內(nèi)如果發(fā)送消息給其他 Grain,需要使用 this.GrainFactory.GetGrain,不能通過 GrainClient.GrainFactory.GetGrain。

var test = GrainClient.GrainFactory.GetGrain<ITest>(0); // long類型的primaryKey 0
public class TestGrain : Orleans.Grain, ITest
{
  private int num = 0;

  public Task AddCount()
  {
    num++;
    Console.WriteLine(num);
    return Task.CompletedTask;
  }
}

Client 說明:

同時啟動3個 Task,每個 Task 內(nèi)并行200次調(diào)用 AddCount 方法。如果沒有做特殊的處理,num 的結(jié)果肯定是亂的,并不會出現(xiàn)一直累加的效果。

private static void DoClientWork()
{
  var t1 = Task.Factory.StartNew(() =>
  {
    AddCount();
  });
  var t2 = Task.Factory.StartNew(() =>
  {
    AddCount();
  });
  var t3 = Task.Factory.StartNew(() =>
  {
    AddCount();
  });
  Task.WaitAll(t1, t2, t3);
}

static void AddCount()
{
  var test = GrainClient.GrainFactory.GetGrain<ITest>(0);

  Parallel.For(0, 200, (i) =>
  {
    test.AddCount();
  });
}

實際上執(zhí)行最終的結(jié)果是600,并不會出現(xiàn)不一致的變化效果,這足以說明同一個 Grain 內(nèi)部是單線程執(zhí)行。

Test Result

參考鏈接:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,321評論 6 543
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,559評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,442評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,835評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,581評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,922評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,931評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,096評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,639評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,374評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,591評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,104評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,789評論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,196評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,524評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,322評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,554評論 2 379

推薦閱讀更多精彩內(nèi)容