| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> 网络协议 -> rpc 动态代理的核心原理介绍 -> 正文阅读 |
|
[网络协议]rpc 动态代理的核心原理介绍 |
rpc介绍
rpc的出现:随着项目越来越大,访问量越来越大,为了突破性能瓶颈,需要将项目拆分成多个部分,这样比起传统的项目都是本地内存调用,分布式的项目之间需要在网络间进行通信
服务之间的远程调用通常有两种方式,即基于TCP的远程调用和基于Http的远程调用
基于TCP的RPC实现
主要是服务提供方定义socket端口和提供的方法名称已经需要的参数结构,服务调用方通过连接服务方的socket端口,进而调用相关方法,并且将需要通信的数据作为参数传递,需要值得注意的是参数在传递的时候需要在服务调用端进行序列化然后在服务提供端进行反序列化,个人理解就行netty之间的通信方式,就是一种基于tcp的远程调用
基于HTTP的RPC实现
对于HTTP的RPC实现,本人觉得与现在的restful风格很类似,主要是在服务调用方通过标识请求,GET,POST然后通过url来定位到服务提供方提供的服务,数据通过xml或者json来传输,省去了TCP的序列化和反序列化
区别
RPC是基于socket通信,在协议层面处于较底层,优点是传输效率高,但是开发难度相对较高,而HTTP处于较高层面,开发难度相对较小,不用维护socket端口和数据序列化相关问题,但是传输效率比起TCP来低了一些
通常的rpc是说通过
基于TCP,有socket和netty两种实现方式,详见
手写带注册中心的rpc框架(Netty版和Socket版)?
手写带注册中心的rpc框架(Netty版和Socket版)_dearfulan的博客-CSDN博客_netty 注册中心
通信核心实现代码
客户端核心类,实现了InvocationHandler接口,用于把创建代理类,invoke方法里就是创建netty客户端并发送请求给从注册中心获取到的服务端
public class RpcInvocationHandler implements InvocationHandler {
????private String serviceName;
????private IServiceDiscovery serviceDiscovery;
????public RpcInvocationHandler(String serviceName, IServiceDiscovery serviceDiscovery) {
????????this.serviceName = serviceName;
????????this.serviceDiscovery = serviceDiscovery;
????}
????/**
?????* 增强的InvocationHandler,接口调用方法的时候实际是调用socket进行传输
?????*/
????public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
????????//将远程调用需要的接口类、方法名、参数信息封装成RPCRequest
????????RpcRequest rpcRequest = new RpcRequest();
????????rpcRequest.setArgs(args);
????????rpcRequest.setClassName(method.getDeclaringClass().getName());
????????rpcRequest.setMethodName(method.getName());
????????return handleNetty(rpcRequest);
????????//return handleSocket(rpcRequest);
????}
????private Object handleNetty(RpcRequest rpcRequest){
????????//创建客户端线程池
????????EventLoopGroup group = null;
????????final RpcClientHandler handler = new RpcClientHandler();
????????try{
????????????group = new NioEventLoopGroup();
????????????Bootstrap bootstrap = new Bootstrap();
????????????bootstrap.group(group).channel(NioSocketChannel.class);
????????????//添加客户端的处理器
????????????bootstrap.option(ChannelOption.TCP_NODELAY, true)
????????????????????.handler(new ChannelInitializer<SocketChannel>() {
????????????????????????@Override
????????????????????????protected void initChannel(SocketChannel socketChannel) throws Exception {
????????????????????????????socketChannel.pipeline()
????????????????????????????????????/** 入参有5个,如下
?????????????????????????????????????maxFrameLength:框架的最大长度。如果帧的长度大于此值,则将抛出TooLongFrameException。
?????????????????????????????????????lengthFieldOffset:长度字段的偏移量:即对应的长度字段在整个消息数据中的位置
?????????????????????????????????????lengthFieldLength:长度字段的长度:如:长度字段是int型表示,那么这个值就是4(long型就是8)
?????????????????????????????????????lengthAdjustment:要添加到长度字段值的补偿值
?????????????????????????????????????initialBytesToStrip:从解码帧中去除的第一个字节数
?????????????????????????????????????*/
????????????????????????????????????//自定义协议解码器
????????????????????????????????????.addLast("frameDecoder", new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4))
????????????????????????????????????//自定义协议编码器
????????????????????????????????????.addLast("frameEncoder", new LengthFieldPrepender(4))
????????????????????????????????????//对象参数类型编码器
????????????????????????????????????.addLast("encoder", new ObjectEncoder())
????????????????????????????????????// 对象参数类型解码器
????????????????????????????????????.addLast("decoder", new ObjectDecoder(Integer.MAX_VALUE, ClassResolvers.cacheDisabled(null)))
????????????????????????????????????.addLast(handler);
????????????????????????}
????????????????????});
????????????//通过service从zk获取服务端地址
????????????String address = serviceDiscovery.discover(serviceName);
????????????//绑定端口启动netty客户端
????????????String[] add = address.split(":");
????????????ChannelFuture future = bootstrap.connect(add[0], Integer.parseInt(add[1])).sync();
????????????//通过Netty发送??RPCRequest给服务端
????????????future.channel().writeAndFlush(rpcRequest).sync();
????????????future.channel().closeFuture().sync();
????????}catch (Exception e){
????????????e.printStackTrace();
????????}finally {
????????????group.shutdownGracefully();
????????}
????????//返回客户端获取的服务端输出
????????return handler.getResponse();
????}
????private Object handleSocket(RpcRequest rpcRequest) throws IOException, ClassNotFoundException {
????????String address = serviceDiscovery.discover(serviceName);
????????//绑定端口启动netty客户端
????????String[] add = address.split(":");
????????//通过socket发送RPCRequest给服务端并获取结果返回
????????Socket socket= new Socket(add[0],Integer.parseInt(add[1]));
????????ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
????????oos.writeObject(rpcRequest);
????????ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
????????Object result = ois.readObject();
????????return result;
????}
}
该链接核心内容就是说只要有通信需求,被调用方地址信息注册到注册中心,调用方查询注册中心里需要的地址,然后通过netty或者socket方式请求结果(请求过程是一个序列化请求参数和反序列化请求结果的过程
)
rpc的动态代理介绍
?
首先远程调用,然后jdkproxy代理,然后invokehandler找到要mock的方法,然后调用mock出来的接口,主要解决的是
实际开发中不可能为每一个远程调用Java接口都编写一个RPC客户端实现类
代理模式分为静态代理和动态代理:
(1)静态代理:在代码编写阶段由工程师提供代理类的源码,再编译成代理类。所谓静态,就是在程序运行前就已经存在代理类的字节码文件,代理类和被委托类的关系在运行前就确定了。
(2)动态代理:在代码编写阶段不用关心具体的代理实现类,而是在运行阶段直接获取具体的代理对象,代理实现类由JDK负责生成。
静态代理
静态代理只是对委托对象抽象出来
接口
静态代理的RPC实现类看上去是一堆冗余代码,发挥不了什么作用。为什么在这里一定要先介绍静态代理模式的RPC实现类呢?原因有以下两点:
(1)RPC实现类是出于演示目的而做了简化,对委托类并没有做任何扩展。而实际的远程调用代理类会对委托类进行很多扩展,比如
远程调用时的负载均衡、熔断、重试
等。
(2)RPC实现类是动态代理实现类的学习铺垫。Feign的RPC客户端实现类是一个JDK动态代理类,是在运行过程中动态生成的。大家知道,动态代理的知识对于很多读者来说不是太好理解,所以先介绍一下代理模式和静态代理的基础知识,作为下一步的学习铺垫
动态代理
package com.crazymaker.demo.proxy.FeignMock;
...
@RestController(value = TestConstants.DEMO_CLIENT_PATH)
public interface MockDemoClient
{ /**
*远程调用接口的方法,完成REST接口api/demo/hello/v1的远程调用
*REST接口功能:返回hello world
*@return JSON响应实例
*/
@GetMapping(name = "api/demo/hello/v1")
RestOut<JSONObject> hello();
/**
*远程调用接口的方法,完成REST接口api/demo/echo/{0}/v1的远程调用
*REST接口功能:回显输入的信息
*@return echo回显消息JSON响应实例
*/
@GetMapping(name = "api/demo/echo/{0}/v1")
RestOut<JSONObject> echo(String word);
}
通过动态代理模式实现模拟远程接口MockDemoClient的RPC调用,关键的类为调用处理器,调用处理器
DemoClientInocationHandler的代码如下:
package com.crazymaker.demo.proxy.basic;
//省略import
/**
*动态代理的调用处理器
*/
@Slf4j
public class DemoClientInocationHandler implements InvocationHandler
{
/**
*被代理的被委托类实例
*/
private MockDemoClient realClient;
public DemoClientInocationHandler(MockDemoClient realClient)
{
this.realClient = realClient;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
{
String name = method.getName();
log.info("{} 方法被调用", method.getName());
/**
*直接调用被委托类的方法:调用其hello方法
*/
if (name.equals("hello"))
{
return realClient.hello();
}
/**
*通过Java反射调用被委托类的方法:调用其echo方法
*/
if (name.equals("echo"))
{
return method.invoke(realClient, args);
}
/**
*通过Java反射调用被委托类的方法
*/
Object result = method.invoke(realClient, args);
return result;
}
}
//参数1:类装载器
ClassLoader classLoader = ProxyTester.class.getClassLoader();
//参数2:代理类和被委托类共同的抽象接口
Class[] clazz = new Class[]{MockDemoClient.class};
//参数3:动态代理的调用处理器
InvocationHandler invocationHandler = new DemoClientInocationHandler (realObject);
/**
*使用以上3个参数创建JDK动态代理类
*/
MockDemoClient proxy = (MockDemoClient)Proxy.newProxyInstance(classLoader, clazz, invocationHandler);
newProxyInstance三个参数介绍如下:
第一个参数为ClassLoader类加载器类型,此处的类加载器和被委托类的类加载器相同即可。
第二个参数为Class[]类型,代表动态代理类将会实现的抽象接口,此接口是被委托类所实现的接口。
第三个参数为InvocationHandler类型,它的调用处理器实例将作为JDK生成的动态代理对象的内部成员,在对动态代理对象进行方法调用时,该处理器的invoke(…)方法会被执行。
动态代理需要在
实现
InvocationHandler接口的前提下,实现
java.lang.reflect.Proxy类的newProxyInstance(…)该方法的第三个参数就是已实现的
InvocationHandler
动态代理与静态代理相反,不需要手工实现代理类,而是由JDK通过反射技术在执行阶段动态生成代理类,所以也叫动态代理。使用的时候可以直接获取动态代理的实例,获取动态代理实例大致需要如下3步:
(1)需要明确代理类和被委托类共同的抽象接口,JDK生成的动态代理类会实现该接口。
(2)构造一个调用处理器对象,该调用处理器要实现InvocationHandler接口,实现其唯一的抽象方法invoke(…)。而InvocationHandler接口由JDK定义,位于java.lang.reflect包中。
(3)通过java.lang.reflect.Proxy类的newProxyInstance(…)方法在运行阶段获取JDK生成的动态代理类的实例。注意,这一步获取的是对象而不是类。该方法需要三个参数,
其中的第一个参数为类装载器,第二个参数为抽象接口的class对象,第三个参数为调用处理器对象
测试代码
package com.crazymaker.demo.proxy.basic;
//省略import
@Slf4j
public class StaticProxyTester {
/**
*动态代理测试
*/
@Test
public void dynamicProxyTest() {
DemoClient client = new DemoClientImpl();
//参数1:类装载器
ClassLoader classLoader = StaticProxyTester.class.getClassLoader();
//参数2:被代理的实例类型
Class[] clazz = new Class[]{DemoClient.class};
//参数3:调用处理器
InvocationHandler invocationHandler =
new DemoClientInocationHandler(client);
//获取动态代理实例
DemoClient proxy = (DemoClient)
Proxy.newProxyInstance(classLoader, clazz, invocationHandler);
//执行RPC远程调用方法
Result<JSONObject> result1 = proxy.hello();
log.info("result1={}", result1.toString());
Result<JSONObject> result2 = proxy.echo("回显内容");
log.info("result2={}", result2.toString());
}
}
|
|
网络协议 最新文章 |
使用Easyswoole 搭建简单的Websoket服务 |
常见的数据通信方式有哪些? |
Openssl 1024bit RSA算法---公私钥获取和处 |
HTTPS协议的密钥交换流程 |
《小白WEB安全入门》03. 漏洞篇 |
HttpRunner4.x 安装与使用 |
2021-07-04 |
手写RPC学习笔记 |
K8S高可用版本部署 |
mySQL计算IP地址范围 |
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年12日历 | -2024/12/29 11:14:59- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |
数据统计 |