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> 源码
public class ProxyDecorator<T> : DispatchProxy
{
public string ServerIp { get; set; }
public int ServerPort { get; set; }
public string ServiceName { get; set; }
static Bootstrap _bootstrap { get; }
static ClientWait _clientWait { get; } = new ClientWait(); static ProxyDecorator()
{
_bootstrap = new Bootstrap()
.Group(new MultithreadEventLoopGroup())
.Channel<TcpSocketChannel>()
.Option(ChannelOption.TcpNodelay, true)
.Handler(new ActionChannelInitializer<ISocketChannel>(channel =>
{
IChannelPipeline pipeline = channel.Pipeline;
pipeline.AddLast("framing-enc", new LengthFieldPrepender());
pipeline.AddLast("framing-dec", new LengthFieldBasedFrameDecoder(int.MaxValue, , , , )); pipeline.AddLast(new ClientHandler(_clientWait));
}));
} public ProxyDecorator()
{ } ///// <summary>
///// 创建代理实例
///// </summary>
///// <param name="decorated">代理的接口类型</param>
///// <returns></returns>
public T Create(string serverIp, int port, string serviceName)
{ object proxy = Create<T, ProxyDecorator<T>>(); //调用DispatchProxy 的Create 创建一个新的T
((ProxyDecorator<T>)proxy).ServerIp = serverIp;
((ProxyDecorator<T>)proxy).ServerPort = port;
((ProxyDecorator<T>)proxy).ServiceName = serviceName;
return (T)proxy;
} protected override object Invoke(MethodInfo targetMethod, object[] args)
{
if (targetMethod == null) throw new Exception("无效的方法"); try
{ ResponseModel response = null;
IChannel client = null;
try
{
client = AsyncHelpers.RunSync(() => _bootstrap.ConnectAsync($"{ServerIp}:{ServerPort}".ToIPEndPoint()));
}
catch
{
throw new Exception("连接到服务端失败!");
}
if (client != null)
{
_clientWait.Start(client.Id.AsShortText());
RequestModel requestModel = new RequestModel
{
ServiceName = ServiceName,
MethodName = targetMethod.Name,
Paramters = args.ToList()
};
var sendBuffer = Unpooled.WrappedBuffer(requestModel.ToJson().ToBytes(Encoding.UTF8)); client.WriteAndFlushAsync(sendBuffer);
var responseStr = _clientWait.Wait(client.Id.AsShortText()).ResponseString;
response = responseStr.ToObject<ResponseModel>();
}
else
{
throw new Exception("连接到服务端失败!");
} if (response == null)
throw new Exception("服务器超时未响应");
else if (response.Success)
{
Type returnType = targetMethod.ReturnType;
if (returnType == typeof(void))
return null;
else
return response.Data;
}
else
throw new Exception($"服务器异常,错误消息:{response.Msg}"); }
catch (Exception ex)
{
if (ex is TargetInvocationException)
{
LogException(ex.InnerException ?? ex, targetMethod);
throw ex.InnerException ?? ex;
}
else
{
throw ex;
}
}
} /// <summary>
/// aop异常的处理
/// </summary>
/// <param name="exception"></param>
/// <param name="methodInfo"></param>
private void LogException(Exception exception, MethodInfo methodInfo = null)
{
try
{
var errorMessage = new StringBuilder();
errorMessage.AppendLine($"Class {methodInfo.IsAbstract.GetType().FullName}");
errorMessage.AppendLine($"Method {methodInfo?.Name} threw exception");
errorMessage.AppendLine(exception.Message); //_logError?.Invoke(errorMessage.ToString()); 记录到文件系统
}
catch (Exception)
{
// ignored
//Method should return original exception
}
}
这个类的源码与上一篇反向代理文章中所讲的核心区别是 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", , "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", , "IMail");
string msg= client.Send("张三丰");
Console.WriteLine(msg);
Console.WriteLine("完成");
Console.ReadLine();
}
服务端添加服务监控
static void Main(string[] args)
{
RPCServer rPCServer = new RPCServer();
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
netcore 中的动态代理与RPC实现(微服务专题)的更多相关文章
- JDK动态代理在RPC框架中的应用
RPC框架中一般都有3个角色:服务提供者.服务消费者和注册中心.服务提供者将服务注册到注册中心,服务消费者从注册中心拉取服务的地址,并根据服务地址向服务提供者发起RPC调用.动态代理在这个RPC调用的 ...
- netcore 之动态代理(微服务专题)
动态代理配合rpc技术调用远程服务,不用关注细节的实现,让程序就像在本地调用以用. 因此动态代理在微服务系统中是不可或缺的一个技术.网上看到大部分案例都是通过反射自己实现,且相当复杂.编写和调试相当不 ...
- 深度剖析java中JDK动态代理机制
https://www.jb51.net/article/110342.htm 本篇文章主要介绍了深度剖析java中JDK动态代理机制 ,动态代理避免了开发人员编写各个繁锁的静态代理类,只需简单地指定 ...
- 使用Java中的动态代理实现数据库连接池
2002 年 12 月 05 日 作者通过使用JAVA中的动态代理实现数据库连接池,使使用者可以以普通的jdbc连接的使用习惯来使用连接池. 数据库连接池在编写应用服务是经常需要用到的模块,太过频繁的 ...
- java中的动态代理机制
java中的动态代理机制 在java的动态代理机制中,有两个重要的类或接口,一个是 InvocationHandler(Interface).另一个则是 Proxy(Class),这一个类和接口是实现 ...
- Spring AOP中的动态代理
0 前言 1 动态代理 1.1 JDK动态代理 1.2 CGLIB动态代理 1.2.1 CGLIB的代理用法 1.2.2 CGLIB的过滤功能 2 Spring AOP中的动态代理机制 2.1 ...
- 转:Spring AOP中的动态代理
原文链接:Spring AOP中的动态代理 0 前言 1 动态代理 1.1 JDK动态代理 1.2 CGLIB动态代理 1.2.1 CGLIB的代理用法 1.2.2 CGLIB的过滤功能 2 S ...
- java反射中的动态代理机制(有实例)
在学习Spring的时候,我们知道Spring主要有两大思想,一个是IoC,另一个就是AOP,对于IoC,依赖注入就不用多说了,而对于Spring的核心AOP来说,我们不但要知道怎么通过AOP来满足的 ...
- 十分钟理解Java中的动态代理
十分钟理解 Java 中的动态代理 一.概述 1. 什么是代理 我们大家都知道微商代理,简单地说就是代替厂家卖商品,厂家“委托”代理为其销售商品.关于微商代理,首先我们从他们那里买东西时通常不知道 ...
随机推荐
- Go组件学习——cron定时器
1 前言 转到Go已经将近三个月,写业务代码又找到了属于Go的条件反射了. 后置声明和多参数返回这些Go风格代码写起来也不会那么蹩脚,甚至还有点小适应~ 反而,前几天在写Java的时候,发现Java怎 ...
- IntelliJ IDEA 2019.2最新解读:性能更好,体验更优,细节处理更完美!
idea 2019.2 准备 idea 2019.2正式版是在2019年7月24号发布的,本篇文章,我将根据官方博客以及自己的理解来进行说明,总体就是:性能更好,体验更优,细节处理更完美! 支持jdk ...
- web图形验证码逻辑
逻辑:前端生成一个UUID以URL方式发送给后端,后端准备Redis数据库缓存数据,后端拿到UUID后,调用captcha.generate_captcha()生成图片和图片的标签,Redis数据库保 ...
- NLP(十三)中文分词工具的使用尝试
本文将对三种中文分词工具进行使用尝试,这三种工具分别为哈工大的LTP,结巴分词以及北大的pkuseg. 首先我们先准备好环境,即需要安装三个模块:pyltp, jieba, pkuseg以及L ...
- JS和C#.NET获取客户端IP
我们经常在项目中会遇到这种需要获取客户端真实IP的需求,其实在网上也能随便就能查到各种获取的方法,我也是在网上查了加上了自己的实践,说一下自己在实践后的感受,基本上网上大部分都是用JS的方法来获取客户 ...
- Unity实现放大缩小以及相机位置平移实现拖拽效果
放大缩小功能是游戏开发中用到的功能,今天就来讲一下Unity中放大缩小怎么实现. 1.IDragHandler, IBeginDragHandler, IEndDragHandler这三个接口是Uni ...
- 【0805 | Day 8】Python进阶(二)
列表类型内置方法 一.列表类型内置方法(list) 用途:多个爱好.多个武器.多种化妆品 定义:[ ]内可以有多个任意类型的值,逗号分隔元素 # my_boy_friend = list(['jaso ...
- Javascript中,实现类与继承的方法和优缺点分析
Javascript是一种弱类型语言,不存在类的概念,但在js中可以模仿类似于JAVA中的类,实现类与继承 第一种方法:利用Javascript中的原型链 //首先定义一个父类 function An ...
- Spring参数的自解析--还在自己转换?你out了!
背景前段时间开发一个接口,因为调用我接口的同事脾气特别好,我也就不客气,我就直接把源代码发给他当接口定义了. 没想到同事看到我的代码问:要么 get a,b,c 要么 post [a,b,c]. ...
- 浅谈Http与Https
大家都知道,在客户端与服务器数据传输的过程中,http协议的传输是不安全的,也就是一般情况下http是明文传输的.但https协议的数据传输是安全的,也就是说https数据的传输是经过加密. 在客户端 ...