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; } } }
在示例中,程序调用的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 }
这个类的源码与上一篇反向代理文章中所讲的核心区别是 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功能的实现是直接引用作者 寒空飞箭 的代码,对此向 寒空飞箭 表示感谢
项目源码git:https://github.com/niuniu007/DispatchProxyAop
欢迎访问个人博客 blog.nnwk.net