什么是RPC

RPC (Remote Procedure Call Protocol), 远程过程调用,通俗的解释就是:客户端在不知道调用细节的情况下,调用存在于远程计算机上的某个对象,就像调用本地应用程序中的对象一样,不需要了解底层网络技术的协议。

简单的整体工作流程

请求端发送一个调用的数据包,该包中包含有调用标识,参数等协议要求的参数。当响应端接收到这个数据包,对应的程序被调起,然后返回结果数据包,返回的数据包含了和请求的数据包中同样的请求标识,结果等。

性能影响因素

  1. 利用的网络协议。可以使用应用层协议,例如HTTP或者HTTP/2协议;也可以利用传输层协议,例如TCP协议,但是主流的RPC还没有采用UDP传输协议。
  2. 消息封装格式。选择或设计一种协议来封装信息进行组装发送。比如,dubbo中消息体数据包含dubbo版本号、接口名称、接口版本、方法名称、参数类型列表、参数、附加信息等。
  3. 序列化。信息在网络传输中要以二进制格式进行传输。序列化和反序列化,是对象到而二进制数据的转换。常见的序列化方法有JSON、Hessian、Protostuff等。
  4. 网络IO模型。可以采用非阻塞式同步IO,也可以在服务器上实现对多路IO模型的支持。
  5. 线程管理方式。在高并发请求下,可以使用单个线程运行服务的具体实现,但是会出现请求阻塞等待现象。也可以为每一个RPC具体服务的实现开启一个独立的线程运行,最大线程数有限制,可以使用线程池来管理多个线程的分配和调度。

第一版RPC

第一个版本简单实现了RPC的最基本功能,即服务信息的发送与接收序列化方式动态代理等。

项目利用Springboot来实现依赖注入与参数配置,使用netty实现NIO方式的数据传输,使用Hessian来实现对象序列化。

动态代理

这里要提到代理模式,它的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。代理类与委托类之间通常会存在关联关系。

根据创建代理类的时间点,又可以分为静态代理和动态代理。

在以往的静态代理中需要手动为每一个目标编写对应的代理类。如果系统已经有了成百上千个类,工作量太大了。

静态代理由程序员创建或特定工具自动生成源代码,也就是在编译时就已经将接口与被代理类,代理类等确定下来。在程序运行之前,代理类的.class文件就已经生成。

代理类在程序运行时创建的代理方式被称为代理模式。在静态代理中,代理类是自己定义好的,在运行之前就已经编译完成了。而在动态代理中,可以很方便地对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。可以通过InvocationHandler接口来实现。

客户端的动态代理

public class ProxyFactory {
public static <T> T create(Class<T> interfaceClass) throws Exception {
return (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class<?>[]{interfaceClass}, new LwRpcClientDynamicProxy<T>(interfaceClass));
}
}
@Slf4j
public class LwRpcClientDynamicProxy<T> implements InvocationHandler {
private Class<T> clazz;
public LwRpcClientDynamicProxy(Class<T> clazz) throws Exception {
this.clazz = clazz;
} @Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
LwRequest lwRequest = new LwRequest();
String requestId = UUID.randomUUID().toString();
String className = method.getDeclaringClass().getName();
String methodName = method.getName();
Class<?>[] parameterTypes = method.getParameterTypes(); lwRequest.setRequestId(requestId);
lwRequest.setClassName(className);
lwRequest.setMethodName(methodName);
lwRequest.setParameterTypes(parameterTypes);
lwRequest.setParameters(args);
NettyClient nettyClient = new NettyClient("127.0.0.1", 8888);
log.info("开始连接服务器端:{}", new Date());
LwResponse send = nettyClient.send(lwRequest);
log.info("请求后返回的结果:{}", send.getResult());
return send.getResult();
}
}

在服务端会利用在客户端获取到的类名。参数等信息利用反射机制进行调用。

Class<?>[] parameterTypes = request.getParameterTypes();
Object[] paramethers = request.getParameters();
// 使用CGLIB 反射
FastClass fastClass = FastClass.create(serviceClass);
FastMethod fastMethod = fastClass.getMethod(methodName, parameterTypes);
return fastMethod.invoke(serviceBean, paramethers);

Netty客户端

@Slf4j
public class NettyClient {
private String host;
private Integer port;
private LwResponse response;
private EventLoopGroup group;
private ChannelFuture future = null;
private Object obj = new Object();
private NettyClientHandler nettyClientHandler;
public NettyClient(String host, Integer port) {
this.host = host;
this.port = port;
} public LwResponse send(LwRequest request) throws Exception{
nettyClientHandler = new NettyClientHandler(request);
group = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group)
.channel(NioSocketChannel.class)
.option(ChannelOption.SO_KEEPALIVE, true)
.option(ChannelOption.TCP_NODELAY, true)
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
pipeline.addLast(new LengthFieldBasedFrameDecoder(65535, 0, 4));
pipeline.addLast(new LwRpcEncoder(LwRequest.class, new HessianSerializer()));
pipeline.addLast(new LwRpcDecoder(LwResponse.class, new HessianSerializer()));
pipeline.addLast(nettyClientHandler);
}
});
future = bootstrap.connect(host, port).sync();
nettyClientHandler.getCountDownLatch().await();
this.response = nettyClientHandler.getLwResponse();
return this.response;
} @PreDestroy
public void close() {
group.shutdownGracefully();
future.channel().closeFuture().syncUninterruptibly();
} }
@Slf4j
public class NettyClientHandler extends ChannelInboundHandlerAdapter {
private final CountDownLatch countDownLatch = new CountDownLatch(1);
private LwResponse response = null;
private LwRequest request; public NettyClientHandler(LwRequest request) {
this.request = request;
} public CountDownLatch getCountDownLatch() {
return countDownLatch;
} public LwResponse getLwResponse() {
return this.response;
}
@Override
public void channelActive(ChannelHandlerContext ctx) {
log.info("客户端向客户端发送消息");
ctx.writeAndFlush(request);
log.info("客户端请求成功");
} @Override
public void channelRead(ChannelHandlerContext ctx, Object msg)
throws Exception {
LwResponse lwResponse = (LwResponse) msg;
log.info("收到服务端的信息:{}", lwResponse.getResult());
this.response = lwResponse;
this.countDownLatch.countDown();
} @Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.close();
} @Override
public void exceptionCaught(ChannelHandlerContext ctx,
Throwable cause) {
cause.printStackTrace();
ctx.close();
} }

在客户端发送服务信息时,用LwQuest类进行封装,返回的结果用LwResponse进行封装,当客户端读取到服务器端返回的响应时,在NettyClientHandler中进行处理,并利用CountDownLatch进行线程的阻塞和运行。

Netty服务端

@Component
@Slf4j
public class NettyServer {
private EventLoopGroup boss = null;
private EventLoopGroup worker = null;
@Autowired
private ServerHandler serverHandler;
@Value("${server.address}")
private String address;
public void start() throws Exception {
log.info("成功");
boss = new NioEventLoopGroup();
worker = new NioEventLoopGroup();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(boss, worker)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
.childOption(ChannelOption.SO_KEEPALIVE, true)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
pipeline.addLast(new LengthFieldBasedFrameDecoder(65535, 0, 4));
pipeline.addLast(new LwRpcEncoder(LwResponse.class, new HessianSerializer()));
pipeline.addLast(new LwRpcDecoder(LwRequest.class, new HessianSerializer()));
pipeline.addLast(serverHandler);
}
});
String[] strs = address.split(":");
String addr = strs[0];
int port = Integer.valueOf(strs[1]);
ChannelFuture future = serverBootstrap.bind(addr, port).sync();
future.channel().closeFuture().sync();
} finally {
worker.shutdownGracefully();
boss.shutdownGracefully();
}
} @PreDestroy
public void destory() throws InterruptedException {
boss.shutdownGracefully().sync();
worker.shutdownGracefully().sync();
log.info("关闭netty");
}
}
@Component
@Slf4j
@ChannelHandler.Sharable
public class ServerHandler extends SimpleChannelInboundHandler<LwRequest> implements ApplicationContextAware {
private ApplicationContext applicationContext; @Override
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
} @Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, LwRequest msg) throws Exception {
LwResponse lwResponse = new LwResponse();
lwResponse.setRequestId(msg.getRequestId());
log.info("从客户端接收到请求信息:{}", msg);
try {
Object result = handler(msg);
lwResponse.setResult(result);
} catch (Throwable throwable) {
lwResponse.setCause(throwable);
throwable.printStackTrace(); }
channelHandlerContext.writeAndFlush(lwResponse);
} private Object handler(LwRequest request) throws ClassNotFoundException, InvocationTargetException { Class<?> clazz = Class.forName(request.getClassName());
Object serviceBean = applicationContext.getBean(clazz);
Class<?> serviceClass = serviceBean.getClass();
String methodName = request.getMethodName();
log.info("获取到的服务类:{}", serviceBean);
Class<?>[] parameterTypes = request.getParameterTypes();
Object[] paramethers = request.getParameters();
// 使用CGLIB 反射
FastClass fastClass = FastClass.create(serviceClass);
FastMethod fastMethod = fastClass.getMethod(methodName, parameterTypes);
return fastMethod.invoke(serviceBean, paramethers);
}
}

在Netty服务端中,会利用``serverHandler`来处理从客户端中接收的信息,并利用反射的思想调用本地的方法,并将处理的结构封装在LwResponse中。

LwRequestLwRespnse要想在网络中进行传输,需要转化为二进制转换。具体方法如下:

public class HessianSerializer implements Serializer {
@Override
public byte[] serialize(Object object) throws IOException {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
Hessian2Output output = new Hessian2Output(byteArrayOutputStream);
output.writeObject(object);
output.flush();
return byteArrayOutputStream.toByteArray();
} public <T> T deserialize(Class<T> clazz, byte[] bytes) throws IOException {
Hessian2Input input = new Hessian2Input(new ByteArrayInputStream(bytes));
return (T) input.readObject(clazz);
}
}
public class LwRpcDecoder extends ByteToMessageDecoder {

    private Class<?> clazz;
private Serializer serializer; public LwRpcDecoder(Class<?> clazz, Serializer serializer) {
this.clazz = clazz;
this.serializer = serializer;
} @Override
protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
if (byteBuf.readableBytes() < 4)
return;
byteBuf.markReaderIndex();
int dataLength = byteBuf.readInt();
if (dataLength < 0) {
channelHandlerContext.close();
}
if (byteBuf.readableBytes() < dataLength) {
byteBuf.resetReaderIndex();
}
byte[] data = new byte[dataLength];
byteBuf.readBytes(data); Object obj = serializer.deserialize(clazz, data);
list.add(obj);
}
}
public class LwRpcEncoder extends MessageToByteEncoder<Object> {
private Class<?> clazz;
private Serializer serializer; public LwRpcEncoder(Class<?> clazz, Serializer serializer) {
this.clazz = clazz;
this.serializer = serializer;
} @Override
protected void encode(ChannelHandlerContext ctx, Object in, ByteBuf out) throws Exception {
if (clazz.isInstance(in)) {
byte[] data = serializer.serialize(in);
out.writeInt(data.length);
out.writeBytes(data);
} } }

轻量级RPC设计与实现第一版的更多相关文章

  1. 轻量级RPC设计与实现第五版(最终版)

    在最近一段时间里,通过搜集有关资料加上自己的理解,设计了一款轻量级RPC,起了一个名字lightWeightRPC.它拥有一个RPC常见的基本功能.主要功能和特点如下: 利用Spring实现依赖注入与 ...

  2. 轻量级RPC设计与实现第三版

    在前两个版本中,每次发起请求一次就新建一个netty的channel连接,如果在高并发情况下就会造成资源的浪费,这时实现异步请求就十分重要,当有多个请求线程时,需要设计一个线程池来进行管理.除此之外, ...

  3. 轻量级RPC设计与实现第四版

    在本版本中引入了SPI机制,关于Java的SPI机制与Dubbo的SPI机制在以前的文章中介绍过. 传送门:Dubbo的SPI机制与JDK机制的不同及原理分析 因为设计的RPC框架是基于Spring的 ...

  4. 轻量级RPC设计与实现第二版

    在上一个版本中利用netty实现了简单的一对一的RPC,需要手动设置服务地址,限制性较大. 在本文中,利用zookeeper作为服务注册中心,在服务端启动时将本地的服务信息注册到zookeeper中, ...

  5. 微博轻量级RPC框架Motan

    Motan 是微博技术团队研发的基于 Java 的轻量级 RPC 框架,已在微博内部大规模应用多年,每天稳定支撑微博上亿次的内部调用.Motan 基于微博的高并发和高负载场景优化,成为一套简单.易用. ...

  6. C# 的轻量级 RPC 框架

    Redola.Rpc 的一个小目标 Redola.Rpc 的一个小目标 Redola.Rpc 的一个小目标:20000 tps. Concurrency level: 8 threads Comple ...

  7. 微博轻量级RPC框架Motan正式开源:支撑千亿调用

    支撑微博千亿调用的轻量级 RPC 框架 Motan 正式开源了,项目地址为https://github.com/weibocom/motan. 微博轻量级RPC框架Motan正式开源 Motan 是微 ...

  8. 轻量级RPC框架开发

    nio和传统io之间工作机制的差别 自定义rpc框架的设计思路 rpc框架的代码运行流程 第2天 轻量级RPC框架开发 今天内容安排: 1.掌握RPC原理 2.掌握nio操作 3.掌握netty简单的 ...

  9. 拥抱.NET Core,跨平台的轻量级RPC:Rabbit.Rpc

    不久前发布了一篇博文".NET轻量级RPC框架:Rabbit.Rpc",当初只实现了非常简单的功能,也罗列了之后的计划,经过几天的不断努力又为Rabbit.Rpc增加了一大波新特性 ...

随机推荐

  1. Codeforces 1188B Count Pairs (同余+分离变量)

    题意: 给一个3e5的数组,求(i,j)对数,使得$(a_i+a_j)(a_i^2+a_j^2)\equiv k\ mod\ p$ 思路: 化简$(a_i^4-a_j^4)\equiv k(a_i-a ...

  2. 使用 TF-IDF 加权的空间向量模型实现句子相似度计算

    使用 TF-IDF 加权的空间向量模型实现句子相似度计算 字符匹配层次计算句子相似度 计算两个句子相似度的算法有很多种,但是对于从未了解过这方面算法的人来说,可能最容易想到的就是使用字符串匹配相关的算 ...

  3. mybatis级联查询,多对一查询问题

    在使用Mybatis进行多表级联查询时遇到了一个问题:查询结果只有一项,但正确结果是两项.经测试,SQL语句本身没有问题. 在SQL映射文件(XML)中: <!-- 级联查询数据 --> ...

  4. spring cloud微服务快速教程之(七) Spring Cloud Alibaba--nacos(一)、服务注册发现

    0.前言 什么是Spring Cloud Alibaba? Spring Cloud Alibaba 是阿里开源的,致力于提供微服务开发的一站式解决方案.此项目包含开发分布式应用微服务的必需组件,方便 ...

  5. Flutter报错 Waiting for another flutter command to release the startup lock...

    Waiting for another flutter command to release the startup lock… 异常解决 平时我们在开发flutter过程中,在执行flutter p ...

  6. 安装 Cacti 监控

    简介:                Cacti是一套基于PHP,MySQL,SNMP及RRDTool开发的网络流量监测图形分析工具.         Cacti是通过 snmpget来获取数据,使用 ...

  7. 手动使用I2C协议写入24C02C

    刚尝试用AT89C52单片机使用IIC总线协议读写AT24C02C,我忽然想能否用手动调整开关的方式写入AT24C02C?于是,便尝试了一下,结果果然成功了. 关于IIC总线,这篇文章写的很详细:ht ...

  8. 代理模式-jdk动态代理

    IDB package com.bjpowernode.proxy; /** * 代理类和目标类都必须使用同一个接口. */ public interface IDB { int insert(); ...

  9. 封装dropdown模块(使用到之前写好的动画组件,封装下拉菜单)

    用 showhide 改写dropdown 模块: 1.首先在 css中新增动画相关样式 /*showhide组件的样式*/ .fadeOut{ opacity:; visibility: hidde ...

  10. leetcode面试题 02.06. 回文链表,解题心路

    目录 leetcode面试题 02.06. 回文链表,解题心路 1.题目描述 2.java语言题解一 3.java语言题解二 4.C语言题解一 leetcode面试题 02.06. 回文链表,解题心路 ...