ET介紹——分散式Actor模型

来源:https://www.cnblogs.com/flamesky/archive/2023/05/19/17414884.html
-Advertisement-
Play Games

Actor模型 Actor介紹 在討論Actor模型之前先要討論下ET的架構,游戲伺服器為了利用多核一般有兩種架構,單線程多進程跟單進程多線程架構。兩種架構本質上其實區別不大,因為游戲邏輯開發都需要用單線程,即使是單進程多線程架構,也要用一定的方法保證單線程開發邏輯。ET採用的是單線程多進程的架構, ...


Actor模型

Actor介紹

在討論Actor模型之前先要討論下ET的架構,游戲伺服器為了利用多核一般有兩種架構,單線程多進程跟單進程多線程架構。兩種架構本質上其實區別不大,因為游戲邏輯開發都需要用單線程,即使是單進程多線程架構,也要用一定的方法保證單線程開發邏輯。ET採用的是單線程多進程的架構,而傳統Actor模型一般是單進程多線程的架構,這點是比較大的區別,不能說誰更好,只能說各有優勢。優劣如下:

  1. 邏輯需要單線程這點都是一樣的,erlang進程邏輯是單線程的,skynet lua虛擬機也是單線程的。ET中一個進程其實相當於一個erlang進程,一個skynet lua虛擬機。
  2. 採用單線程多進程不需要自己再寫一套profiler工具,可以利用很多現成的profiler工具,例如查看記憶體,cpu占用直接用top命令,這點erlang跟skynet都需要自己另外搞一套工具。
  3. 多進程單線程架構還有個好處,單台物理機跟多台物理機是沒有區別的,單進程多線程還需要考慮多台物理機的處理。
  4. 多進程單線程架構一點缺陷是消息跨進程需要進行序列化反序列化,占用一點資源。另外發送網路消息會有幾毫秒延時。一般這些影響可以忽略。

最開始Actor模型是給單進程多線程架構使用的,這是有原因的,因為多線程架構開發者很容易隨意的訪問共用變數,比方說一個變數a, 線程1能訪問,線程2也能訪問,這樣兩個線程在訪問變數a的時候都需要加鎖,共用變數多了之後鎖到處都是,會變得無法維護,框架肯定不能出現到處是線程共用變數的情況。為了保證多線程架構不出問題,必須提供一種開發模型保證多線程開發簡單又安全。erlang語言的併發機制就是actor模型。erlang虛擬機使用多線程來利用多核。erlang設計了一種機制,它在虛擬機之上設計了自己的進程。最簡單的,每個erlang進程都管理自己的變數,每個erlang進程的邏輯都跑在一個線程上,erlang進程跟進程之間邏輯完全隔離,這樣就不存在兩個線程訪問同一變數的情況了也就不存在多線程競爭的問題。接下來問題又出現了,既然每個erlang進程都有自己的數據,邏輯完全是隔離的,兩個erlang進程之間應該怎麼進行通信呢?這時Actor模型就登場了。erlang設計了一種消息機制:一個進程可以向其它進程發送消息,erlang進程之間通過消息來進行通信,看到這會不會感覺很熟悉?這不就是操作系統進程間通信用的消息隊列嗎?沒錯,其實是類似的。erlang裡面拿到進程的id就能給這個進程發送消息。

如果消息只發給進程其實還是有點不方便。比如拿一個erlang進程做moba戰隊進程,戰鬥進程中有10個玩家,如果使用erlang的actor消息,消息只能發送給戰鬥進程,但是很多時候消息是需要發送給一個玩家的,這時erlang需要根據消息中的玩家Id,把消息再次分發給具體的玩家,這樣其實多繞了一圈。

ET的Actor

ET根據自己架構得特點,沒有完全照搬erlang的Actor模型,而是提供了Entity對象級別的Actor模型。這點跟erlang甚至傳統的Actor機制不一樣。ET中,Actor是Entity對象,Entity掛上一個MailboxComponent組件就是一個Actor了。只需要知道Entity的InstanceId就可以發消息給這個Entity了。其實erlang的Actor模型不過是ET中的一種特例,比如給ET服務端Game.Scene當做一個Actor,這樣就可以變成進程級別的Actor。Actor本質就是一種消息機制,這種消息機制不用關心位置,只需要知道對方的InstanceId(ET)或者進程的Pid(erlang)就能發給對方。

語言ETErlangSkynet
架構 單線程多進程 單進程多線程 單進程多線程
Actor Entity erlang進程 lua虛擬機
ActorId Entity.InstanceId erlang進程Id 服務地址

ET的Actor的使用

普通的Actor,我們可以參照Gate Session。map中一個Unit,Unit身上保存了這個玩家對應的gate session。這樣,map中的消息如果需要發給客戶端,只需要把消息發送給gate session,gate session在收到消息的時候轉發給客戶端即可。map進程發送消息給gate session就是典型的actor模型。它不需要知道gate session的位置,只需要知道它的InstanceId即可。MessageHelper.cs中,通過GateSessionActorId獲取一個ActorMessageSender,然後發送。

// 從Game.Scene上獲取ActorSenderComponent,然後通過InstanceId獲取ActorMessageSender
ActorSenderComponent actorSenderComponent = Game.Scene.GetComponent<ActorSenderComponent>();
ActorMessageSender actorMessageSender = actorSenderComponent.Get(unitGateComponent.GateSessionActorId);
// send
actorMessageSender.Send(message);

// rpc
var response = actorMessageSender.Call(message);

 

問題是map中怎麼才能知道gate session的InstanceId呢?這就是你需要想方設法傳過去了,比如ET中,玩家在登錄gate的時候,gate session掛上一個信箱MailBoxComponent,C2G_LoginGateHandler.cs中

session.AddComponent<MailBoxComponent, string>(MailboxType.GateSession);

 

玩家登錄map進程的時候會把這個gate session的InstanceId帶進map中去,C2G_EnterMapHandler.cs中

M2G_CreateUnit createUnit = (M2G_CreateUnit)await mapSession.Call(new G2M_CreateUnit() { PlayerId = player.Id, GateSessionId = session.InstanceId });

 

Actor消息的處理

首先,消息到達MailboxComponent,MailboxComponent是有類型的,不同的類型郵箱可以做不同的處理。目前有兩種郵箱類型GateSession跟MessageDispatcher。GateSession郵箱在收到消息的時候會立即轉發給客戶端,MessageDispatcher類型會再次對Actor消息進行分發到具體的Handler處理,預設的MailboxComponent類型是MessageDispatcher。自定義一個郵箱類型也很簡單,繼承IMailboxHandler介面,加上MailboxHandler標簽即可。那麼為什麼需要加這麼個功能呢,在其它的actor模型中是不存在這個特點的,一般是收到消息就進行分發處理了。原因是GateSession的設計,並不需要進行分發處理,因此我在這裡加上了郵箱類型這種設計。MessageDispatcher的處理方式有兩種一種是處理對方Send過來的消息,一種是rpc消息

    // 處理Send的消息, 需要繼承AMActorHandler抽象類,抽象類第一個泛型參數是Actor的類型,第二個參數是消息的類型
    [ActorMessageHandler(AppType.Map)]
    public class Actor_TestHandler : AMActorHandler<Unit, Actor_Test>
    {
        protected override ETTask Run(Unit unit, Actor_Test message)
        {
            Log.Debug(message.Info);
        }
    }

    // 處理Rpc消息, 需要繼承AMActorRpcHandler抽象類,抽象類第一個泛型參數是Actor的類型,第二個參數是消息的類型,第三個參數是返回消息的類型
    [ActorMessageHandler(AppType.Map)]
    public class Actor_TransferHandler : AMActorRpcHandler<Unit, Actor_TransferRequest, Actor_TransferResponse>
    {
        protected override async ETTask Run(Unit unit, Actor_TransferRequest message, Action<Actor_TransferResponse> reply)
        {
            Actor_TransferResponse response = new Actor_TransferResponse();

            try
            {
                reply(response);
            }
            catch (Exception e)
            {
                ReplyError(response, e, reply);
            }
        }
    }

 

我們需要註意一下,Actor消息有死鎖的可能,比如A call消息給B,B call給C,C call給A。因為MailboxComponent本質上是一個消息隊列,它開啟了一個協程會一個一個消息處理,返回ETTask表示這個消息處理類會阻塞MailboxComponent隊列的其它消息。所以如果出現死鎖,我們就不希望某個消息處理阻塞掉MailboxComponent其它消息的處理,我們可以在消息處理類裡面新開一個協程來處理就行了。例如:

    [ActorMessageHandler(AppType.Map)]
    public class Actor_TestHandler : AMActorHandler<Unit, Actor_Test>
    {
        protected override ETTask Run(Unit unit, Actor_Test message)
        {
            RunAsync(unit, message).Coroutine();
        }

        public ETVoid RunAsync(Unit unit, Actor_Test message)
        {
            Log.Debug(message.Info);
        }
    }

 

相關資料可以谷歌一下Actor死鎖的問題。

ET開源地址地址:egametang/ET: Unity3D Client And C# Server Framework (github.com)   qq群:474643097


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Apache JMeter是Apache組織開發的基於Java的壓力測試工具。用於對軟體做壓力測試,它最初被設計用於Web應用測試,但後來擴展到其他測試領域。 它可以用於測試靜態和動態資源,例如靜態文件、Java 小服務程式、CGI 腳本、Java 對象、資料庫、FTP 伺服器, 等等。JMeter ...
  • 基於java的職工管理系統設計與實現,員工管理系統,企業員工管理系統,公司員工管理系統,企業人事管理系統,基於java職工管理系統,前後端分離,員工考勤管理系統,職工獎懲管理系統,職員合同管理,HR管理系統,人事HR管理系統。 ...
  • Author Alex Zhang Category SpreadJS Tags SpreadJS,前端電子錶格,實時數據,RealTime Data 前言 數據(包括股票、天氣和體育比分)在不斷更新為新信息時最為有用。SpreadJS是一個非常通用的 JavaScript 電子錶格組件,它還可以輕 ...
  • C語言教程——翁凱老師、赫斌 翁愷老師是土生土長的浙大碼農,從本科到博士都畢業於浙大電腦系,後來留校教書,一教就是20多年。 翁愷老師的c語言課程非常好,講解特別有趣,很適合初學者學習。 郝斌老師的思路是以初學者的思路來思考的,非常適合小白,你不理解的問題,基本上他都會詳細說一下。 C++——侯捷 ...
  • 前言: 自從使用了 AsyncLocal 後,就發現 AsyncLocal 變數像個臭蟲一樣,在有 AsyncLocal 變數的線程中啟動的 Task 、或者 Thread 都會附帶 AsyncLocal 變數。 在項目使用 AsyncLocal 實現了全局、局部 工作單元 ,但是就無法在後續作業中 ...
  • # 前言 SQLite是一種輕量級的關係型資料庫管理系統,支持跨平臺操作。它可以嵌入到程式中,無需單獨的伺服器進程或者配置文件,減少了資料庫維護的負擔和運行的複雜性。SQLite的數據存儲在單個文件中,方便備份、傳輸和分享,也容易進行版本管理。SQLite擁有良好的性能、可靠的穩定性和豐富的功能,成 ...
  • 類似魔獸世界,moba這種技能極其複雜,靈活性要求極高的技能系統,必須需要一套及其靈活的數值結構來搭配。數值結構設計好了,實現技能系統就會非常簡單,否則就是一場災難。比如魔獸世界,一個人物的數值屬性非常之多,移動速度,力量,怒氣,能量,集中值,魔法值,血量,最大血量,物理攻擊,物理防禦,法術攻擊,法 ...
  • Actor Location Actor模型只需要知道對方的InstanceId就能發送消息,十分方便,但是有時候我們可能無法知道對方的InstanceId,或者是一個Actor的InstanceId會發生變化。這種場景很常見,比如:很多游戲是分線的,一個玩家可能從1線換到2線,還有的游戲是分場景的 ...
一周排行
    -Advertisement-
    Play Games
  • 前言 在我們開發過程中基本上不可或缺的用到一些敏感機密數據,比如SQL伺服器的連接串或者是OAuth2的Secret等,這些敏感數據在代碼中是不太安全的,我們不應該在源代碼中存儲密碼和其他的敏感數據,一種推薦的方式是通過Asp.Net Core的機密管理器。 機密管理器 在 ASP.NET Core ...
  • 新改進提供的Taurus Rpc 功能,可以簡化微服務間的調用,同時可以不用再手動輸出模塊名稱,或調用路徑,包括負載均衡,這一切,由框架實現並提供了。新的Taurus Rpc 功能,將使得服務間的調用,更加輕鬆、簡約、高效。 ...
  • 順序棧的介面程式 目錄順序棧的介面程式頭文件創建順序棧入棧出棧利用棧將10進位轉16進位數驗證 頭文件 #include <stdio.h> #include <stdbool.h> #include <stdlib.h> 創建順序棧 // 指的是順序棧中的元素的數據類型,用戶可以根據需要進行修改 ...
  • 前言 整理這個官方翻譯的系列,原因是網上大部分的 tomcat 版本比較舊,此版本為 v11 最新的版本。 開源項目 從零手寫實現 tomcat minicat 別稱【嗅虎】心有猛虎,輕嗅薔薇。 系列文章 web server apache tomcat11-01-官方文檔入門介紹 web serv ...
  • C總結與剖析:關鍵字篇 -- <<C語言深度解剖>> 目錄C總結與剖析:關鍵字篇 -- <<C語言深度解剖>>程式的本質:二進位文件變數1.變數:記憶體上的某個位置開闢的空間2.變數的初始化3.為什麼要有變數4.局部變數與全局變數5.變數的大小由類型決定6.任何一個變數,記憶體賦值都是從低地址開始往高地 ...
  • 如果讓你來做一個有狀態流式應用的故障恢復,你會如何來做呢? 單機和多機會遇到什麼不同的問題? Flink Checkpoint 是做什麼用的?原理是什麼? ...
  • C++ 多級繼承 多級繼承是一種面向對象編程(OOP)特性,允許一個類從多個基類繼承屬性和方法。它使代碼更易於組織和維護,並促進代碼重用。 多級繼承的語法 在 C++ 中,使用 : 符號來指定繼承關係。多級繼承的語法如下: class DerivedClass : public BaseClass1 ...
  • 前言 什麼是SpringCloud? Spring Cloud 是一系列框架的有序集合,它利用 Spring Boot 的開發便利性簡化了分散式系統的開發,比如服務註冊、服務發現、網關、路由、鏈路追蹤等。Spring Cloud 並不是重覆造輪子,而是將市面上開發得比較好的模塊集成進去,進行封裝,從 ...
  • class_template 類模板和函數模板的定義和使用類似,我們已經進行了介紹。有時,有兩個或多個類,其功能是相同的,僅僅是數據類型不同。類模板用於實現類所需數據的類型參數化 template<class NameType, class AgeType> class Person { publi ...
  • 目錄system v IPC簡介共用記憶體需要用到的函數介面shmget函數--獲取對象IDshmat函數--獲得映射空間shmctl函數--釋放資源共用記憶體實現思路註意 system v IPC簡介 消息隊列、共用記憶體和信號量統稱為system v IPC(進程間通信機制),V是羅馬數字5,是UNI ...