分布式理论,架构设计(二) ---- 分布式架构网络通信

文章内容输出来源:拉勾教育Java高薪训练营。
本篇文章是 分布式理论 学习课程中的一部分笔记。

分布式架构网络通信

在分布式服务框架中,一个最基础的问题就是远程服务是怎么通讯的,在Java领域中有很多可实现远程通讯的技
术,例如:RMI、Hessian、SOAP、ESB和JMS等,它们背后到底是基于什么原理实现的呢

基本原理: 要实现网络机器间的通讯,首先得来看看计算机系统网络通信的基本原理,在底层层面去看,网络通信需要做的就是将流从一台计算机传输到另外一台计算机,基于传输协议和网络IO来实现,其中传输协议比较出名的有tcp、

udp等等,tcp、udp都是在基于Socket概念上为某类应用场景而扩展出的传输协议,网络IO,主要有bio、nio、
aio三种方式,所有的分布式应用通讯都基于这个原理而实现,只是为了应用的易用,各种语言通常都会提供一些
更为贴近应用易用的应用层协议

RPC

  • RPC描述

    • RPC全称为remote procedure call,即远程过程调用。
      借助RPC可以做到像本地调用一样调用远程服务,是一种进程间的通信方式
  • 架构

    • 客户端(Client),服务的调用方。
    • 客户端存根(Client Stub),存放服务端的地址消息,再将客户端的请求参数打包成网络消息,然后通过网络远程发送给服务方。
    • 服务端(Server),真正的服务提供者。
    • 服务端存根(Server Stub),接收客户端发送过来的消息,将消息解包,并调用本地的方法。
  • 过程

    (1) 客户端(client)以本地调用方式(即以接口的方式)调用服务;
    (2) 客户端存根(client stub)接收到调用后,负责将方法、参数等组装成能够进行网络传输的消息体(将消息体对
    象序列化为二进制);
    (3) 客户端通过sockets将消息发送到服务端;
    (4) 服务端存根( server stub)收到消息后进行解码(将消息对象反序列化);
    (5) 服务端存根( server stub)根据解码结果调用本地的服务;
    (6) 本地服务执行并将结果返回给服务端存根( server stub);
    (7) 服务端存根( server stub)将返回结果打包成消息(将结果消息对象序列化);
    (8) 服务端(server)通过sockets将消息发送到客户端;
    (9) 客户端存根(client stub)接收到结果消息,并进行解码(将结果消息发序列化);
    (10) 客户端(client)得到最终结果。
    RPC的目标是要把2、3、4、7、8、9这些步骤都封装起来

RMI远程调用方法

  • 简介:Java RMI 指的是远程方法调用 (Remote Method Invocation),是java原生支持的远程调用 ,采用JRMP(Java Remote Messageing protocol)作为通信协议,可以认为是纯java版本的分布式远程调用解决方案, RMI主要用于不同虚拟机之间的通信,这些虚拟机可以在不同的主机上、也可以在同一个主机上,这里的通信可以理解为一个虚拟机上的对象调用另一个虚拟机上对象的方法。

  • 组成

    • 客户端

      • 1)存根/桩(Stub):远程对象在客户端上的代理;
      • 2)远程引用层(Remote Reference Layer):解析并执行远程引用协议;
      • 3)传输层(Transport):发送调用、传递远程方法参数、接收远程方法执行结果。
    • 服务端

      • 1)骨架(Skeleton):读取客户端传递的方法参数,调用服务器方的实际对象方法,并接收方法执行后的返回值;
      • 2)远程引用层(Remote Reference Layer):处理远程引用后向骨架发送远程方法调用;
      • 3)传输层(Transport):监听客户端的入站连接,接收并转发调用到远程引用层。
    • 注册表(Registry):以URL形式注册远程对象,并向客户端回复对远程对象的引用。

  • 远程调用过程

    1)客户端从远程服务器的注册表中查询并获取远程对象引用。

    2)桩对象与远程对象具有相同的接口和方法列表,当客户端调用远程对象时,实际上是由相应的桩对象代理完成的。

    3 )远程引用层在将桩的本地引用转换为服务器上对象的远程引用后,再将调用传递给传输层(Transport),由传输层通
    过TCP协议发送调用;

    4)在服务器端,传输层监听入站连接,它一旦接收到客户端远程调用后,就将这个引用转发给其上层的远程引用层;

    5)服务器端的远程引用层将客户端发送的远程应用转换为本地虚拟机的引用后,再将请求传递给骨架(Skeleton);

    6)骨架读取参数,又将请求传递给服务器,最后由服务器进行实际的方法调用。

BIO,NIO,AIO

  • 同步异步,阻塞非阻塞

    • IO的两个阶段:查看数据是否就绪,进行数据拷贝(内核将数据拷贝到用户线程)

    • 同步与异步

      • 同步和异步的概念描述的是用户线程与内核的交互方式: 同步是指用户线程发起IO请求后需要等待或者轮询内核IO操作完成后才能继续执行;而异步是指用户线程发起IO请求后仍继续执行,当内核IO操作完成后会通知用户线程,或者调用用户线程注册的回调函数。
        同步IO和异步IO的关键区别反映在数据拷贝阶段是由用户线程完成还是内核完成。所以说异步IO必须要有操作系统的底层支持。同步IO数据拷贝由用户线程完成,异步则为内核线程完成。
    • 阻塞与非阻塞

      • 阻塞和非阻塞的概念描述的是用户线程调用内核IO操作的方式: 阻塞是指IO操作需要彻底完成后才返回到用户空间;而非阻塞是指IO操作被调用后立即返回给用户一个状态值,无需等到IO操作彻底完成。阻塞与非阻塞主要是从CPU的消耗上来说的,阻塞就是CPU停下来等待一个慢的操作完成以后,CPU才接着完成其他工作。非阻塞就是在这个慢的操作执行时,CPU去做其他工作,等着这个慢的操作完成时,CP再接着完成后续的操作。阻塞IO和非阻塞IO是反映在IO操作的第一个阶段,在查看数据是否就绪时是如何处理的

Netty

  • NIO缺点及Netty改进

    • nio缺点:

      • NIO 的类库和 API 繁杂,使用麻烦。你需要熟练掌握 Selector、ServerSocketChannel、SocketChannel、ByteBuffer 等.
      • 可靠性不强,开发工作量和难度都非常大
      • NIO 的 Bug。例如 Epoll Bug,它会导致 Selector 空轮询,最终导致 CPU 100%
    • netty优点

      • 对各种传输协议提供统一的 API

        • 高度可定制的线程模型——单线程、一个或多个线程池
      • 更好的吞吐量,更低的等待延迟

        • 更少的资源消耗

          • 最小化不必要的内存拷贝
  • 单线程模型

  • 线程池模型

  • Netty线程模型

    • netty模型

      • Netty 抽象出两组线程池, BossGroup 专门负责接收客 户端连接, WorkerGroup 专门负责网络读写操作。
        NioEventLoop 表示一个不断循环执行处理 任务的线程, 每个 NioEventLoop 都有一个 selector, 用于监听绑定在其上的 socket 网络通道。 NioEventLoop 内部采用串行化设计, 从消息的读取->解码->处理->编码->发送, 始终由 IO 线 程 NioEventLoop 负责。
  • Netty核心组件

    • ChannelHandler及其实现类

      • 我们经常需要自定义一个 Handler 类去继承 ChannelInboundHandlerAdapter, 然后通过 重写相应方法实现业务逻辑,
    • ChannelPipeline

      addFirst(ChannelHandler… handlers), 把一个业务处理类(handler) 添加到链中的第一 个位置

      addLast(ChannelHandler… handlers), 把一个业务处理类(handler) 添加到链中的最后 一个位置

      • 是一个 Handler 的集合, 它负责处理和拦截 inbound 或者 outbound 的事 件和操作, 相当于一个贯穿 Netty 的链。
    • ChannelHandlerContext

      • ChannelFuture close(), 关闭通道

      • ChannelOutboundInvoker flush(), 刷新

      • ChannelFuture writeAndFlush(Object msg) , 将 数 据 写 到 ChannelPipeline 中

      • ChannelHandler 的下一个 ChannelHandler 开始处理(出站)

        • 这 是 事 件 处 理 器 上 下 文 对 象 , Pipeline 链 中 的 实 际 处 理 节 点 。 每 个 处 理 节 点ChannelHandlerContext 中 包 含 一 个 具 体 的 事 件 处 理 器 ChannelHandler , 同 时
          ChannelHandlerContext 中也绑定了对应的 pipeline 和 Channel 的信息
    • ChannelFuture

      Channel channel(), 返回当前正在进行 IO 操作的通道

      ChannelFuture sync(), 等待异步操作执行完毕

      • 表示 Channel 中异步 I/O 操作的结果, 在 Netty 中所有的 I/O 操作都是异步的, I/O 的调 用会直接返回, 调用者
        并不能立刻获得结果, 但是可以通过 ChannelFuture 来获取 I/O 操作 的处理状态。
    • EventLoopGroup 和其实现类 NioEventLoopGroup

      • public NioEventLoopGroup(), 构造方法

      • public Future<?> shutdownGracefully(), 断开连接, 关闭线程

        • EventLoopGroup 是一组 EventLoop 的抽象, Netty 为了更好的利用多核 CPU 资源, 一般 会有多个 EventLoop
          同时工作, 每个 EventLoop 维护着一个 Selector 实例。 EventLoopGroup 提供 next 接口, 可以从组里面按照一
          定规则获取其中一个 EventLoop 来处理任务。 在 Netty 服务器端编程中, 我们一般都需要提供两个EventLoopGroup
    • ServerBootstrap 和 Bootstrap

      group(EventLoopGroup parentGroup, EventLoopGroup childGroup),该方法用于 服务器端, 用来设置两个 EventLoop

      • public B group(EventLoopGroup group) , 该方法用于客户端, 用来设置一个 EventLoop

      • public B channel(Class<? extends C> channelClass), 该方法用来设置一个服务器端的通道实现

      • public B option(ChannelOption option, T value), 用来给 ServerChannel 添加配置

      • public ServerBootstrap childOption(ChannelOption childOption, T value), 用来给接收到的
        通道添加配置

      • public ServerBootstrap childHandler(ChannelHandler childHandler), 该方法用来设置业务处理类(自定
        义的 handler)

      • public ChannelFuture bind(int inetPort) , 该方法用于服务器端, 用来设置占用的端口号

      • public ChannelFuture connect(String inetHost, int inetPort) 该方法用于客户端, 用来连接服务器端

        • ServerBootstrap 是 Netty 中的服务器端启动助手,通过它可以完成服务器端的各种配置; Bootstrap 是 Netty 中
          的客户端启动助手, 通过它可以完成客户端的各种配置