自己動手,寫一個分布式系統(附c#代碼示例)


分布式系統有很多成熟的解決方案。如:微軟的WCF。WCF太過於復雜,配置也麻煩。其實可以自己動手設計一個小的分布式系統。系統的原理完全在自己掌握之中,可以根據業務隨機而變。這里展示遠程調用最核心最基本的處理邏輯,其實遠程調用並不復雜神秘。

分布式系統其實是數據流的交換。數據必須快速的從一段傳送到另一端,否則系統性能就大打折扣。對於.net,本人設計一個非常優化易於使用的網絡庫(EasyNetMessage)。使用該庫,不需要關心底層細節,所有處理對象是string、byte;發送時,不需要處理分包(幾十M的數據也可以一次發送);收包時,不需要處理粘包。本文所示例代碼,就是基於該網絡庫。本文章實例代碼見底部。

實現目標

實現一個非常簡單的應用   internal int AddCall(int value1, int value2);就是兩個整數相加。調用過程和本地完全一樣,只是執行加法操作是在遠程服務器實現的。操作非常簡單,但是揭示了遠程調用的核心處理流程。

處理過程:

a) 數據發送

  每次函數調用必須有一個唯一標識CallId,這個也是發包的唯一標識;服務端處理完后,返回的結果也帶有此標識。通過此標識,將發送端數據和返回數據關聯起來了。

  發送完數據后,客戶端線程要掛起,等待服務器端的處理結果。線程掛起使用ManualResetEvent。並建立起CallId與ManualResetEvent的對應關系。當數據返回時,就能找到對應的ManualResetEvent。

  //callid與事件關聯
        Dictionary<int, ManualResetEvent> _callEventGroup = new Dictionary<int, ManualResetEvent>();
        //callId與返回結果關聯
        Dictionary<int, NetCallAddAck> _callResultGroup = new Dictionary<int, NetCallAddAck>();
        internal int AddCall(int value1, int value2)
        {
            //組織發送包
            NetCallAdd add = new NetCallAdd();
            add.Value1 = value1.ToString();
            add.Value2 = value2.ToString();

            MonitorClient client = GetCurAppClient();
            if (client == null)
                throw new Exception("socket未連接!");

            //生成線程事件,並與CallId關聯
            ManualResetEvent callEvent = new ManualResetEvent(false);
            lock (_callEventGroup)
            {
                _callEventGroup.Add(add.CallId, callEvent);
            }

            //發送數據
            EN_SendDataResult result = _netServer.SendData(client.ClientSocket, add.ToEasyMessage().ToNetPacket());
            if (result != EN_SendDataResult.ok)
            {
                lock (_callEventGroup)
                {
                    _callEventGroup.Remove(add.CallId);
                }
                throw new Exception("網絡發送異常!");
            }

            //等待結果
            callEvent.WaitOne(3000);
            _callEventGroup.Remove(add.CallId);

            //查看結果集
            lock (_callResultGroup)
            {
                if (_callResultGroup.ContainsKey(add.CallId))
                {
                    NetCallAddAck ack = _callResultGroup[add.CallId];
                    return int.Parse(ack.Result);
                }
            }

            throw new Exception("沒有返回結果!");
        }

b)數據返回

  數據返回后的處理是在另一個線程。數據返回后,先根據CallId查找對應的ManualResetEvent;如果找不到,有可能服務器處理太慢,超時了。

  先將返回結果存儲到哈希數組中,key為CallId。再調用ManualResetEvent的Set函數,喚醒調用端線程。調用端線程根據CallId到哈希表中獲取結果。

        internal void OnRcvAck(NetCallAddAck addAck)
        {
            //根據callid找到事件
            ManualResetEvent callEvent = null;
            lock (_callEventGroup)
            {
                if (!_callEventGroup.ContainsKey(addAck.CallId))
                    return;
                callEvent =_callEventGroup[addAck.CallId];
            }

            //結果存放到哈希表中
            lock (_callResultGroup)
            {
                _callResultGroup.Remove(addAck.CallId);
                _callResultGroup.Add(addAck.CallId, addAck);
            }
            //設置事件為有信號,調用方掛起的線程可以繼續執行
            callEvent.Set();
        }

進一步說明:可以在此基礎上,進一步擴展。開發出類似Redis的內存庫。客戶端的調用也不一定是同步 ,可以采用異步回調的方式處理。其實如果知道處理的原理,可以根據自己的業務做裁剪。只有知其所以然,才能開發出最符合自己業務的系統,才可能進一步優化。

實例代碼 https://download.csdn.net/download/qq_29939347/10684858 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM