1. 程式人生 > >netcore 中的動態代理與RPC實現(微服務專題)

netcore 中的動態代理與RPC實現(微服務專題)

一、關於RPC的呼叫

  1. 呼叫者(客戶端Client)以本地呼叫的方式發起呼叫;
  2. Client stub(客戶端存根)收到呼叫後,負責將被呼叫的方法名、引數等打包編碼成特定格式的能進行網路傳輸的訊息體;
  3. Client stub將訊息體通過網路傳送給服務端;
  4. Server stub(服務端存根)收到通過網路接收到訊息後按照相應格式進行拆包解碼,獲取方法名和引數;
  5. Server stub根據方法名和引數進行本地呼叫;
  6. 被呼叫者(Server)本地呼叫執行後將結果返回給server stub;
  7. Server stub將返回值打包編碼成訊息,並通過網路傳送給客戶端;

  8. Client stub收到訊息後,進行拆包解碼,返回給Client;
  9. Client得到本次RPC呼叫的最終結果。

  參考https://www.cnblogs.com/FG123/p/10261676.html

  參考https://www.jianshu.com/p/bb9beca7f7bc 第四節

 

二、關於RPC呼叫方式的思考(為什麼要用代理類)

RPC的方便之處我們已經看到了,

假設在系統中要呼叫多個服務,如果寫一個函式,每次將這個服務的名字,引數,和其他資訊通過一個方法來呼叫遠端服務,假設這個方法叫做getService(methodname,object[],引數3,引數4)    

我們在各個消費類中來呼叫這個方法似乎也能得到結果。

在每個呼叫遠端服務的地方都要反射出 類的方法名稱,引數等其他資訊以能傳給getService 是不是很麻煩?

要知道遠端服務每個服務返回的結果不會是一樣的型別,那我們在客戶端還要每次都轉換getService的結果,是不是很麻煩?

有沒有好的解決方案?

  --請使用代理類,我們在代理類中反射代理介面得到這個方法的各種屬性(名稱&引數&其他),遠端呼叫傳遞給遠端服務,並轉換得到的結果。看起來這種方法和上文的getService 差不多嘛!那我們為什麼要使用代理類呢?(我也不知道,但看起來很吊的樣子。)這看起來並沒有很好的樣子,況且如果有多個類要呼叫遠端服務,那豈不是要寫很多代理類?

思考:呼叫getService 後每次都要在消費類中轉換結果,使用代理類後將這個轉換過程放入了代理類中,這樣消費類就不用關注RPC的呼叫結果的型別的轉換了。

 

於是人們發明了動態代理   --來自《魯迅先生說革命》

 

人們發現每個類都要寫個代理。現在小明要在專案中寫1000個代理類,直接氣炸了,對!炸了!。

經過了N代的小明客戶鑽研和發現,總結了一套可以很省力氣的方法。--動態代理

簡單的來說:動態建立代理類(https://www.cnblogs.com/netqq/p/11452374.html),這樣就不用給每個消費類都寫一個代理類了,是不很爽

 

三、動態代理與RPC

 在網上找到了一個簡單的RPC 示例,非常適合初學者學習  https://github.com/Coldairarrow/DotNettyRPC 

 下載專案後先執行 Server 專案,再執行client專案

 

看到再server的控制檯上輸出了hello 字串。這是客戶端程式呼叫了server的IHello.SayHello()的服務輸出的。

我們來看下作者的客戶端呼叫

 

RPCClientFactory原始碼如下

namespace Coldairarrow.DotNettyRPC
{
    /// <summary>
    /// 客戶端工廠
    /// </summary>
    public class RPCClientFactory
    {
        private static ConcurrentDictionary<string, object> _services { get; } = new ConcurrentDictionary<string, object>();

        /// <summary>
        /// 獲取客戶端
        /// 注:預設服務名為介面名
        /// </summary>
        /// <typeparam name="T">介面定義型別</typeparam>
        /// <param name="serverIp">遠端服務IP</param>
        /// <param name="port">遠端服務埠</param>
        /// <returns></returns>
        public static T GetClient<T>(string serverIp, int port) where T : class
        {
            return GetClient<T>(serverIp, port, typeof(T).Name);
        }

        /// <summary>
        /// 獲取客戶端
        /// 注:自定義服務名
        /// </summary>
        /// <typeparam name="T">介面定義型別</typeparam>
        /// <param name="serverIp">遠端服務IP</param>
        /// <param name="port">遠端服務埠</param>
        /// <param name="serviceName">服務名</param>
        /// <returns></returns>
        public static T GetClient<T>(string serverIp, int port, string serviceName) where T : class
        {
            T service = null;
            string key = $"{serviceName}-{serverIp}-{port}";
            try
            {
                service = (T)_services[key];
            }
            catch
            {
                var clientProxy = new RPCClientProxy
                {
                    ServerIp = serverIp,
                    ServerPort = port,
                    ServiceType = typeof(T),
                    ServiceName = serviceName
                };
                service = clientProxy.ActLike<T>();
                //動態代理?

                _services[key] = service;
            }

            return service;
        }
    }
}
View Code

 

 

 

在示例中,程式呼叫的GetClient 

 

 

 實際上也是動態生成的代理類,返回了IHello型別的物件。

我們先拋開該作者的程式,用我們自己的動態代理類來實現相同的效果。

在DotNettyRPC專案中新增ProxyDecorator<T> 類。   需要nuget下載System.Reflection.DispatchProxy.dll

在新增ProxyDecorator 和編譯的時候會遇到問題,我們將server、 client專案和DotNettyRPC  轉為NETCORE專案才能正常執行 ,因為 System.Reflection.DispatchProxy.dll 只NETCORE 類庫,不支援NET Framework專案

ProxyDecorator<T>  原始碼

  1   public class ProxyDecorator<T> : DispatchProxy
  2     {
  3         public string ServerIp { get; set; }
  4         public int ServerPort { get; set; }
  5         public string ServiceName { get; set; }
  6         static Bootstrap _bootstrap { get; }
  7         static ClientWait _clientWait { get; } = new ClientWait();
  8 
  9         static ProxyDecorator()
 10         {
 11             _bootstrap = new Bootstrap()
 12                 .Group(new MultithreadEventLoopGroup())
 13                 .Channel<TcpSocketChannel>()
 14                 .Option(ChannelOption.TcpNodelay, true)
 15                 .Handler(new ActionChannelInitializer<ISocketChannel>(channel =>
 16                 {
 17                     IChannelPipeline pipeline = channel.Pipeline;
 18                     pipeline.AddLast("framing-enc", new LengthFieldPrepender(8));
 19                     pipeline.AddLast("framing-dec", new LengthFieldBasedFrameDecoder(int.MaxValue, 0, 8, 0, 8));
 20 
 21                     pipeline.AddLast(new ClientHandler(_clientWait));
 22                 }));
 23         }
 24 
 25         public ProxyDecorator()
 26         {
 27 
 28         }
 29 
 30         ///// <summary>
 31         ///// 建立代理例項
 32         ///// </summary>
 33         ///// <param name="decorated">代理的介面型別</param>
 34         ///// <returns></returns>
 35         public T Create(string serverIp, int port, string serviceName)
 36         {
 37 
 38             object proxy = Create<T, ProxyDecorator<T>>();   //呼叫DispatchProxy 的Create  建立一個新的T
 39             ((ProxyDecorator<T>)proxy).ServerIp = serverIp;
 40             ((ProxyDecorator<T>)proxy).ServerPort = port;
 41             ((ProxyDecorator<T>)proxy).ServiceName = serviceName;
 42             return (T)proxy;
 43         }
 44 
 45         protected override object Invoke(MethodInfo targetMethod, object[] args)
 46         {
 47             if (targetMethod == null) throw new Exception("無效的方法");
 48 
 49             try
 50             {
 51 
 52                 ResponseModel response = null;
 53                 IChannel client = null;
 54                 try
 55                 {
 56                     client = AsyncHelpers.RunSync(() => _bootstrap.ConnectAsync($"{ServerIp}:{ServerPort}".ToIPEndPoint()));
 57                 }
 58                 catch
 59                 {
 60                     throw new Exception("連線到服務端失敗!");
 61                 }
 62                 if (client != null)
 63                 {
 64                     _clientWait.Start(client.Id.AsShortText());
 65                     RequestModel requestModel = new RequestModel
 66                     {
 67                         ServiceName = ServiceName,
 68                         MethodName = targetMethod.Name,
 69                         Paramters = args.ToList()
 70                     };
 71                     var sendBuffer = Unpooled.WrappedBuffer(requestModel.ToJson().ToBytes(Encoding.UTF8));
 72 
 73                     client.WriteAndFlushAsync(sendBuffer);
 74                     var responseStr = _clientWait.Wait(client.Id.AsShortText()).ResponseString;
 75                     response = responseStr.ToObject<ResponseModel>();
 76                 }
 77                 else
 78                 {
 79                     throw new Exception("連線到服務端失敗!");
 80                 }
 81 
 82                 if (response == null)
 83                     throw new Exception("伺服器超時未響應");
 84                 else if (response.Success)
 85                 {
 86                     Type returnType = targetMethod.ReturnType;
 87                     if (returnType == typeof(void))
 88                         return null;
 89                     else
 90                          return response.Data;
 91                 }
 92                 else
 93                     throw new Exception($"伺服器異常,錯誤訊息:{response.Msg}");
 94 
 95             }
 96             catch (Exception ex)
 97             {
 98                 if (ex is TargetInvocationException)
 99                 {
100                     LogException(ex.InnerException ?? ex, targetMethod);
101                     throw ex.InnerException ?? ex;
102                 }
103                 else
104                 {
105                     throw ex;
106                 }
107             }
108         }
109 
110 
111         /// <summary>
112         /// aop異常的處理
113         /// </summary>
114         /// <param name="exception"></param>
115         /// <param name="methodInfo"></param>
116         private void LogException(Exception exception, MethodInfo methodInfo = null)
117         {
118             try
119             {
120                 var errorMessage = new StringBuilder();
121                 errorMessage.AppendLine($"Class {methodInfo.IsAbstract.GetType().FullName}");
122                 errorMessage.AppendLine($"Method {methodInfo?.Name} threw exception");
123                 errorMessage.AppendLine(exception.Message);
124 
125                 //_logError?.Invoke(errorMessage.ToString());  記錄到檔案系統
126             }
127             catch (Exception)
128             {
129                 // ignored  
130                 //Method should return original exception  
131             }
132         }
View Code

 

這個類的原始碼與上一篇反向代理文章中所講的核心區別是 Invoke 的實現,上篇文章中其呼叫的是本地的一個類實體的方法,本文中其呼叫的是遠端服務中的類實體的方法

client呼叫程式碼如下

 static void Main(string[] args)
        {
            //IHello client = RPCClientFactory.GetClient<IHello>("127.0.0.1", 39999);
            var serviceProxy = new ProxyDecorator<IHello>();
            IHello client = serviceProxy.Create("127.0.0.1", 39999, "IHello");
            client.SayHello("Hello");
            Console.WriteLine("完成");
            Console.ReadLine();
        }

 重新啟動Server 和Client 執行效果如下

 

和原作者的執行結果一致,那麼我們換個介面來試試:建立IMail  和Mail兩個類,幷包含一個成員string  Send(string  name)//IMail和Mail的成員  

public string Send(string name)
        {
       Console.WriteLine(name); return $"你的名字是{name}"; }

 

 

Client端呼叫程式碼

        static void Main(string[] args)
        {
            //IHello client = RPCClientFactory.GetClient<IHello>("127.0.0.1", 39999);
            //var serviceProxy = new ProxyDecorator<IHello>();
            //IHello client = serviceProxy.Create("127.0.0.1", 39999, "IHello");
            var serviceProxy = new ProxyDecorator<IMail>();
            IMail client = serviceProxy.Create("127.0.0.1", 39999, "IMail");
            string   msg= client.Send("張三丰");
            Console.WriteLine(msg);
            Console.WriteLine("完成");
            Console.ReadLine();
        }

 

服務端新增服務監控

        static void Main(string[] args)
        {
            RPCServer rPCServer = new RPCServer(39999);
            rPCServer.RegisterService<IHello, Hello>();
            rPCServer.RegisterService<IMail, Mail>();
            rPCServer.Start();

            Console.ReadLine();
        }

 

 

預計客戶端輸出:

你的名字是張三丰

完成 

服務端輸出是:

張三丰

我們先後啟動server 和 client 兩個端來看看

 

 

至此動態代理的應用示例已經演示完畢。

在檢視   寒空飛箭   git 原始碼時候我們發現  RPCClientProxy 類和我們的ProxyDecorator<T> 類  實現了相同的效果,寒空飛箭的實現方式也是很令人振奮,獨闢蹊徑,非常值得學習。下篇文章將會分析他的用法。感興趣的可以自行檢視作者的原始碼。

參考文獻:

https://www.cnblogs.com/coldairarrow/p/10193765.html

 說明:

RPC功能的實現是直接引用作者 寒空飛箭 的程式碼,對此向 寒空飛箭 表示感謝

&n