Spark通信框架RPC介绍

内容安排:

1、RPC原理

2、nio操作

3、netty简单的api

4、自定义RPC框架

  1. RPC原理学习

    1. 什么是RPC

RPC(Remote Procedure Call Protocol)——远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。RPC协议假定某些传输协议的存在,如TCP或UDP,为通信程序之间携带信息数据。在OSI网络通信模型中,RPC跨越了传输层和应用层。RPC使得开发包括网络分布式多程序在内的应用程序更加容易。

RPC采用客户机/服务器模式。请求程序就是一个客户机,而服务提供程序就是一个服务器。首先,客户机调用进程发送一个有进程参数的调用信息到服务进程,然后等待应答信息。在服务器端,进程保持睡眠状态直到调用信息到达为止。当一个调用信息到达,服务器获得进程参数,计算结果,发送答复信息,然后等待下一个调用信息,最后,客户端调用进程接收答复信息,获得进程结果,然后调用执行继续进行。

  1. RPC原理

运行时,一次客户机对服务器的RPC调用,其内部操作大致有如下十步:

1.调用客户端句柄;执行传送参数

2.调用本地系统内核发送网络消息

3.消息传送到远程主机

4.服务器句柄得到消息并取得参数

5.执行远程过程

6.执行的过程将结果返回服务器句柄

7.服务器句柄返回结果,调用远程系统内核

8.消息传回本地主机

9.客户句柄由内核接收消息

10.客户接收句柄返回的数据

  1. nio原理

    1. 简介

nio 是New IO 的简称,在jdk1.4 里提供的新api 。Sun 官方标榜的特性如下: 为所有的原始类型提供(Buffer)缓存支持。字符集编码解码解决方案。 Channel :一个新的原始I/O 抽象。 支持锁和内存映射文件的文件访问接口。 提供多路(non-bloking) 非阻塞式的高伸缩性网络I/O 。

  1. 传统的I/O

使用传统的I/O程序读取文件内容, 并写入到另一个文件(或Socket), 如下程序:

File.read(fileDesc, buf, len);

Socket.send(socket, buf, len);

会有较大的性能开销, 主要表现在一下两方面:

1. 上下文切换(context switch), 此处有4次用户态和内核态的切换

2. Buffer内存开销, 一个是应用程序buffer, 另一个是系统读取buffer以及socket buffer

其运行示意图如下

1) 先将文件内容从磁盘中拷贝到操作系统buffer

2) 再从操作系统buffer拷贝到程序应用buffer

3) 从程序buffer拷贝到socket buffer

4) 从socket buffer拷贝到协议引擎.

  1. NIO

NIO技术省去了将操作系统的read buffer拷贝到程序的buffer, 以及从程序buffer拷贝到socket buffer的步骤, 直接将 read buffer 拷贝到 socket buffer. java 的 FileChannel.transferTo() 方法就是这样的实现, 这个实现是依赖于操作系统底层的sendFile()实现的.

publicvoid transferTo(long position, long count, WritableByteChannel target);

他的底层调用的是系统调用sendFile()方法

sendfile(int out_fd, int in_fd, off_t *offset, size_t count);

如下图

  1. netty常用API

    1. netty简介

Netty是基于Java NIO的网络应用框架.

Netty是一个NIO client-server(客户端服务器)框架,使用Netty可以快速开发网络应用,例如服务器和客户端协议。Netty提供了一种新的方式来使开发网络应用程序,这种新的方式使得它很容易使用和有很强的扩展性。Netty的内部实现时很复杂的,但是Netty提供了简单易用的api从网络处理代码中解耦业务逻辑。Netty是完全基于NIO实现的,所以整个Netty都是异步的。

网络应用程序通常需要有较高的可扩展性,无论是Netty还是其他的基于Java NIO的框架,都会提供可扩展性的解决方案。Netty中一个关键组成部分是它的异步特性.

  1. 轻量级RPC框架开发之netty的helloworld

  1. import io.netty.bootstrap.ServerBootstrap;
  2. import io.netty.channel.Channel;
  3. import io.netty.channel.ChannelFuture;
  4. import io.netty.channel.ChannelInitializer;
  5. import io.netty.channel.EventLoopGroup;
  6. import io.netty.channel.nio.NioEventLoopGroup;
  7. import io.netty.channel.socket.nio.NioServerSocketChannel;
  8.  
  9. /**
  10.  * • 配置服务器功能,如线程、端口 
  11.  * • 实现服务器处理程序,它包含业务逻辑,决定当有一个请求连接或接收数据时该做什么
  12.  */
  13. public class EchoServer {
  14.  
  15.  private final int port;
  16.  
  17.  public EchoServer(int port) {
  18.   this.port = port;
  19.  }
  20.  
  21.  public void start() throws Exception {
  22.   EventLoopGroup eventLoopGroup = null;
  23.   try {
  24.    //创建ServerBootstrap实例来引导绑定和启动服务器
  25.    ServerBootstrap serverBootstrap = new ServerBootstrap();
  26.    //创建NioEventLoopGroup对象来处理事件,如接受新连接、接收数据、写数据等等
  27.    eventLoopGroup = new NioEventLoopGroup();
  28.    //指定通道类型为NioServerSocketChannel,设置InetSocketAddress让服务器监听某个端口已等待客户端连接。
  29.    serverBootstrap.group(eventLoopGroup).channel(NioServerSocketChannel.class).localAddress("localhost",port).childHandler(new ChannelInitializer<Channel>() {
  30.     //设置childHandler执行所有的连接请求
  31.     @Override
  32.     protected void initChannel(Channel ch) throws Exception {
  33.      ch.pipeline().addLast(new EchoServerHandler());
  34.     }
  35.      });
  36.    // 最后绑定服务器等待直到绑定完成,调用sync()方法会阻塞直到服务器完成绑定,然后服务器等待通道关闭,因为使用sync(),所以关闭操作也会被阻塞。
  37.    ChannelFuture channelFuture = serverBootstrap.bind().sync();
  38.    System.out.println("开始监听,端口为:" + channelFuture.channel().localAddress());
  39.    channelFuture.channel().closeFuture().sync();
  40.   } finally {
  41.    eventLoopGroup.shutdownGracefully().sync();
  42.   }
  43.  }
  44.  
  45.  public static void main(String[] args) throws Exception {
  46.   new EchoServer(20000).start();
  47.  }
  48. }
  • 服务端回调方法
  1. package com.zhangjk;
  2.  
  3. import io.netty.buffer.ByteBuf;
  4. import io.netty.buffer.Unpooled;
  5. import io.netty.channel.ChannelFutureListener;
  6. import io.netty.channel.ChannelHandlerContext;
  7. import io.netty.channel.ChannelInboundHandlerAdapter;
  8.  
  9. import java.util.Date;
  10.  
  11. public class EchoServerHandler extends ChannelInboundHandlerAdapter {
  12.  
  13.     @Override
  14.     public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
  15.         System.out.println("server 读取数据……");
  16.         //读取数据
  17.         ByteBuf buf = (ByteBuf) msg;
  18.         byte[] req = new byte[buf.readableBytes()];
  19.         buf.readBytes(req);
  20.         String body = new String(req, "UTF-8");
  21.         System.out.println("接收客户端数据:" + body);
  22.         //向客户端写数据
  23.         System.out.println("server向client发送数据");
  24.         String currentTime = new Date(System.currentTimeMillis()).toString();
  25.         ByteBuf resp = Unpooled.copiedBuffer(currentTime.getBytes());
  26.         ctx.write(resp);
  27.     }
  28.  
  29.     @Override
  30.     public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
  31.         System.out.println("server 读取数据完毕..");
  32.         ctx.flush();//刷新后才将数据发出到SocketChannel
  33.     }
  34.  
  35.     @Override
  36.     public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
  37.         cause.printStackTrace();
  38.         ctx.close();
  39.     }
  40. }
  • 客户端启动类
  1. import io.netty.bootstrap.Bootstrap;
  2. import io.netty.channel.ChannelFuture;
  3. import io.netty.channel.ChannelInitializer;
  4. import io.netty.channel.EventLoopGroup;
  5. import io.netty.channel.nio.NioEventLoopGroup;
  6. import io.netty.channel.socket.SocketChannel;
  7. import io.netty.channel.socket.nio.NioSocketChannel;
  8.  
  9. import java.net.InetSocketAddress;
  10.  
  11. /**
  12.  * • 连接服务器 
  13.  * • 写数据到服务器 
  14.  * • 等待接受服务器返回相同的数据 
  15.  * • 关闭连接
  16.  */
  17. public class EchoClient {
  18.  
  19.     private final String host;
  20.     private final int port;
  21.  
  22.     public EchoClient(String host, int port) {
  23.         this.host = host;
  24.         this.port = port;
  25.     }
  26.  
  27.     public void start() throws Exception {
  28.         EventLoopGroup nioEventLoopGroup = null;
  29.         try {
  30.             //创建Bootstrap对象用来引导启动客户端
  31.             Bootstrap bootstrap = new Bootstrap();
  32.             //创建EventLoopGroup对象并设置到Bootstrap中,EventLoopGroup可以理解为是一个线程池,这个线程池用来处理连接、接受数据、发送数据
  33.             nioEventLoopGroup = new NioEventLoopGroup();
  34.             //创建InetSocketAddress并设置到Bootstrap中,InetSocketAddress是指定连接的服务器地址
  35.             bootstrap.group(nioEventLoopGroup).channel(NioSocketChannel.class).remoteAddress(new InetSocketAddress(host, port))
  36.                     .handler(new ChannelInitializer<SocketChannel>() {
  37.                         //添加一个ChannelHandler,客户端成功连接服务器后就会被执行
  38.                         @Override
  39.                         protected void initChannel(SocketChannel ch)
  40.                                 throws Exception {
  41.                             ch.pipeline().addLast(new EchoClientHandler());
  42.                         }
  43.                     });
  44.             //调用Bootstrap.connect()来连接服务器
  45.             ChannelFuture f = bootstrap.connect().sync();
  46.             //最后关闭EventLoopGroup来释放资源
  47.             f.channel().closeFuture().sync();
  48.         } finally {
  49.             nioEventLoopGroup.shutdownGracefully().sync();
  50.         }
  51.     }
  52.  
  53.     public static void main(String[] args) throws Exception {
  54.         new EchoClient("localhost", 20000).start();
  55.     }
  56. }
  • 客户端回调方法
  1. import io.netty.buffer.ByteBuf;
  2. import io.netty.buffer.ByteBufUtil;
  3. import io.netty.buffer.Unpooled;
  4. import io.netty.channel.ChannelHandlerContext;
  5. import io.netty.channel.SimpleChannelInboundHandler;
  6.  
  7. public class EchoClientHandler extends SimpleChannelInboundHandler<ByteBuf> {
  8.     //客户端连接服务器后被调用
  9.     @Override
  10.     public void channelActive(ChannelHandlerContext ctx) throws Exception {
  11.         System.out.println("客户端连接服务器,开始发送数据……");
  12.         byte[] req = "QUERY TIME ORDER".getBytes();
  13.         ByteBuf firstMessage = Unpooled.buffer(req.length);
  14.         firstMessage.writeBytes(req);
  15.         ctx.writeAndFlush(firstMessage);
  16.     }
  17.  
  18.     //从服务器接收到数据后调用
  19.     @Override
  20.     protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
  21.         System.out.println("client 读取server数据..");
  22.         //服务端返回消息后
  23.         ByteBuf buf = (ByteBuf) msg;
  24.         byte[] req = new byte[buf.readableBytes()];
  25.         buf.readBytes(req);
  26.         String body = new String(req, "UTF-8");
  27.         System.out.println("服务端数据为 :" + body);
  28.     }
  29.  
  30.     //发生异常时被调用
  31.     @Override
  32.     public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
  33.         System.out.println("client exceptionCaught..");
  34.         // 释放资源
  35.         ctx.close();
  36.     }
  37. }  
  1. netty中handler的执行顺序

Handler在netty中,无疑占据着非常重要的地位。Handler与Servlet中的filter很像,通过Handler可以完成通讯报文的解码编码、拦截指定的报文、统一对日志错误进行处理、统一对请求进行计数、控制Handler执行与否。一句话,没有它做不到的只有你想不到的。

Netty中的所有handler都实现自ChannelHandler接口。按照输出输出来分,分为ChannelInboundHandler、ChannelOutboundHandler两大类。ChannelInboundHandler对从客户端发往服务器的报文进行处理,一般用来执行解码、读取客户端数据、进行业务处理等;ChannelOutboundHandler对从服务器发往客户端的报文进行处理,一般用来进行编码、发送报文到客户端。

Netty中,可以注册多个handler。ChannelInboundHandler按照注册的先后顺序执行;ChannelOutboundHandler按照注册的先后顺序逆序执行,如下图所示,按照注册的先后顺序对Handler进行排序,request进入Netty后的执行顺序为:

总结:

在使用Handler的过程中,需要注意:

1、ChannelInboundHandler之间的传递,通过调用 ctx.fireChannelRead(msg) 实现;调用ctx.write(msg) 将传递到ChannelOutboundHandler。

2、ctx.write()方法执行后,需要调用flush()方法才能令它立即执行。

3、ChannelOutboundHandler 在注册的时候需要放在最后一个ChannelInboundHandler之前,否则将无法传递到ChannelOutboundHandler。

4、Handler的消费处理放在最后一个处理。

  1. netty发送对象

Netty中,通讯的双方建立连接后,会把数据按照ByteBuf的方式进行传输,例如http协议中,就是通过HttpRequestDecoder对ByteBuf数据流进行处理,转换成http的对象。基于这个思路,自定义一种通讯协议:Server和客户端直接传输java对象。

实现的原理是通过Encoder把java对象转换成ByteBuf流进行传输,通过Decoder把ByteBuf转换成java对象进行处理,处理逻辑如下图所示:

Spark通信框架RPC介绍的更多相关文章

  1. RPC通信框架——RCF介绍

    现有的软件中用了大量的COM接口,导致无法跨平台,当然由于与Windows结合的太紧密,还有很多无法跨平台的地方.那么为了实现跨平台,支持Linux系统,以及后续的分布式,首要任务是去除COM接口. ...

  2. RPC通信框架——RCF介绍(替换COM)

    阅读目录 RPC通信框架 为什么选择RCF 简单的性能测试 参考资料 总结 现有的软件中用了大量的COM接口,导致无法跨平台,当然由于与Windows结合的太紧密,还有很多无法跨平台的地方.那么为了实 ...

  3. RPC通信框架——RCF介绍

    现有的软件中用了大量的COM接口,导致无法跨平台,当然由于与Windows结合的太紧密,还有很多无法跨平台的地方.那么为了实现跨平台,支持Linux系统,以及后续的分布式,首要任务是去除COM接口. ...

  4. 【Java】分布式RPC通信框架Apache Thrift 使用总结

    简介 Apache Thrift是Facebook开源的跨语言的RPC通信框架,目前已经捐献给Apache基金会管理,由于其跨语言特性和出色的性能,在很多互联网公司得到应用,有能力的公司甚至会基于th ...

  5. SpringBoot2+Netty打造通俗简版RPC通信框架(升级版)

    背景         上篇文章我简单的介绍了自己打造的通俗简版RPC通信框架,这篇是对简版的增强~         如果大家对此项目还感兴趣的话,可到码云上瞄瞄:Netty-RPC         上 ...

  6. SpringBoot2+Netty打造通俗简版RPC通信框架

    2019-07-19:完成基本RPC通信! 2019-07-22:优化此框架,实现单一长连接! 2019-07-24:继续优化此框架:1.增加服务提供注解(带版本号),然后利用Spring框架的在启动 ...

  7. AgileEAS.NET SOA 中间件平台.Net Socket通信框架-介绍

    一.前言 AgileEAS.NET SOA 中间件平台是一款基于基于敏捷并行开发思想和Microsoft .Net构件(组件)开发技术而构建的一个快速开发应用平台.用于帮助中小型软件企业建立一条适合市 ...

  8. 介绍开源的.net通信框架NetworkComms框架 源码分析

    原文网址: http://www.cnblogs.com/csdev Networkcomms 是一款C# 语言编写的TCP/UDP通信框架  作者是英国人  以前是收费的 售价249英镑 我曾经花了 ...

  9. 介绍开源的.net通信框架NetworkComms框架 源码分析(二)ConnectionInfo

    原文网址: http://www.cnblogs.com/csdev Networkcomms 是一款C# 语言编写的TCP/UDP通信框架  作者是英国人  以前是收费的 目前作者已经开源  许可是 ...

随机推荐

  1. C# 传不定参数

    1 public class MyClass 2 { 3 public static void UseParams(params int[] list) 4 { 5 for (int i = 0; i ...

  2. @Transactional注解真的有必要声明rollbackFor属性吗?

    @Transactional注解真的有必要声明rollbackFor属性吗? ​ 今天在看spring的事务底层源码时,想到一个问题,@Transactional注解真的有必要声明rollbackFo ...

  3. day10-习题

    习题 1.Homework01 (1) D -- 没有在别名上加引号(ps:别名的as可以省略) (2) B -- 判断null或非空不能用不等于号 (3) C 2.Homework02 写出查看de ...

  4. 解决在vue中设置的height: 100%没有效果

    在新的页面设置height无效果的时候.需要改动App这个文件的heigth 解决办法.给app这个盒子设置高度.默认情况下为0 设置高度100%时,div的高度会等同于其父元素的高度.而上面中id为 ...

  5. [WPF] 抄抄超强的苹果官网滚动文字特效实现

    1. 前言 今天 ChokCoco 大佬发布了一篇博客 超强的苹果官网滚动文字特效实现,iPhone 我是买不起的,但不妨碍我对抄特效感兴趣,正好我这周安排的工作已经完成了,于是有空练练手实现了一个 ...

  6. HashMap基本使用方法

    HashMap Map集合基于 键(key)/值(value)映射.每个键最多只能映射一个值.键可以是任何引用数据类型的值,不可重复:值可以是任何引用数据类型的值,可以重复:键值对存放无序. Hash ...

  7. mysql网上知识

    MySQL学习笔记 登录和退出MySQL服务器 # 登录MySQL $ mysql -u root -p12345612 # 退出MySQL数据库服务器 exit; 基本语法 -- 显示所有数据库 s ...

  8. 微信小程序canvas 证件照制作

    小程序制作证件照过程 利用canvas制作生活中常用的证件照,压缩图片,修改图片dpi.希望给大家带来方便. 证件照小程序制作要点 上传合适的图片,方便制作证件照 调用AI接口,将图像进行人像分割.这 ...

  9. .Net6新版本的AssemblyLoadContext 加载程序集和卸载程序集

    准备俩个项目 第一个是控制台 第二个项目是类库 类库项目中只有一个示例class 将类库的代码生成dll 并且设置属性为复制到输出目录 using System.Runtime.Loader; var ...

  10. WCF实现大文件上传

    一.文件服务接口 1.文件上传 2.文件传输(上传按钮) 3.文件传输停止 服务地址: 在客端添加服务器引用,从而实现客户端调用服务器的功能. 二.契约 服务契约[ServiceContract]:定 ...