【Java手写RPC框架系列-1】—— 基础知识准备:RPC+Netty

代码随想录知识星球介绍
https://articles.zsxq.com/id_m76jd72243bi.html
基于Netty手写实现RPC
https://www.cnblogs.com/mic112/p/15565795.html

项目背景与介绍

  • RPC:
    • 远程过程调用协议:客户端在不知道调用细节的情况下,调用存在于远程计算机上的某个对象,就像调用本地应用程序中的对象一样;
    • 要点
      • RPC是协议
      • 网络协议或者网络IO模型对其透明;RPC客户端认为自己在调用本地对象;不关心传输层协议
      • 信息格式透明:参数以某种信息格式传递给网络上另一台计算机,信息格式是怎么构成的,调用者不关心
      • 跨语言能力:不清楚远程服务器的应用程序是使用什么语言运行;对调用方来说,无论服务器方使用什么语言,本次调用都应该成功;返回值也应该按照调用方程序语言所能理解的形式进行描述。
  • 常用RPC技术或框架
    • 应用级
    • 远程通信协议:RMI、Socket、SOAP(HTTP XML)、REST(HTTP JSON)
    • 通信框架:MINA和Netty
  • 目的:仿照市场主流的RPC框架设计思想,使用java手动实现一个高性能、高可用性的RPC框架

业内主流RPC

  1. Thrift:thrift是一个软件框架,用来进行可扩展且跨语言的服务的开发。它结合了功能强大的软件堆栈和代码生成引擎,以构建在 C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, JavaScript, Node.js, Smalltalk, and OCaml 这些编程语言间无缝结合的、高效的服务。
  2. Dubbo:Dubbo是一个分布式服务框架,以及SOA治理方案。其功能主要包括:高性能NIO通讯及多协议集成,服务动态寻址与路由,软负载均衡与容错,依赖分析与降级等。 Dubbo是阿里巴巴内部的SOA服务化治理方案的核心框架,Dubbo自2011年开源后,已被许多非阿里系公司使用。

基础需求

  • 场景:
    服务端B:有一个用户表
    1.UserService 里有一个功能: getUserByUserId(Integer id)
    2.UserServiceImpl 实现了UserService接口和方法
    客户端A
    • 调用getUserByUserId方法, 内部传一个Id给服务端,服务端查询到User对象返回给客户端
  • 实现过程:
    • 客户端A

      • 调用getUserByUserId方法时,内部将调用信息处理后发送给服务端B,告诉B我要获取User
      • 外部调用方法,内部进行其它的处理——这种场景我们可以使用动态代理的方式,改写原本方法的处理逻辑。比如,当我们正常调用getUserByUserId方法,代码逻辑是去数据库中找user,但是在当前远程调用的场景下肯定不能走这样的逻辑;我们通过动态代理的方式,绕过【去数据库查询】这原始的逻辑,改成封装信息发送到B中请求调用服务
    • 服务器B:

      • 监听A请求,接收A的调用信息,并根据信息得到A想调用的服务与方法
      • 根据信息找到对应的服务,进行调用后将结果发送回给A
    • A和B之间通信

      • Java的Socket网络编程通信
      • 为了方便A,B直接对信息进行处理,将请求信息和返回信息封装成统一的消息格式

网络IO通信

  • 传统BIO模式/同步阻塞IO:客户端向服务端发起一个数据读取请求,客户端在收到服务端返回数据之前,一直处于阻塞状态,直到服务端返回数据后完成本次会话。
    • 一个连接一个线程
    • 客户端有连接请求时,服务器端启动一个线程进行处理
    • 适合场景:HTTP请求
    • 阻塞点:
      • 服务端接收客户端连接时的阻塞;
      • 客户端和服务端IO通信时,数据未就绪情况下阻塞;
  • NIO/非阻塞IO:客户端向服务端发起请求时,如果服务端的数据未就绪的情况下, 客户端请求不会被阻塞,而是直接返回。
    • 客户端只能通过轮询的方式来获得请求结果
    • NIO仍然有一个弊端,就是轮询过程中会有很多空轮询,而这个轮询会存在大量的系统调用(发起内核指令从网卡缓冲区中加载数据,用户空间到内核空间的切换),随着连接数量的增加,会导致性能问题。
  • 多路复用机制
    • 单个进程监视多个文件描述符(fd)
    • 一旦某个描述符就绪(一般是读就绪或写就绪),能够通知程序进行相应的读写操作
    • 常见多路复用方式:
      • 【select】:进程可以通过把一个或者多个fd传递给select系统调用,进程会阻塞在select操作上,这样select可以帮我们检测多个fd是否处于就绪状态,这个模式有两个缺点
        • 当前进程需要线性轮询所有的fd,也就是监听的fd越多,性能开销越大
        • 在单个进程中能打开的fd是有限制的,默认是1024
      • 【poll】:
      • 【epoll】:inux还提供了epoll的系统调用,epoll是基于事件驱动方式来代替顺序扫描,因此性能相对来说更高
        • 当被监听的fd中,有fd就绪时,会告知当前进程具体哪一个fd就绪,那么当前进程只需要去从指定的fd上读取数据即可,另外,epoll所能支持的fd上线是操作系统的最大文件句柄,这个数字要远远大于1024

什么是fd:在linux中,内核把所有的外部设备都当成是一个文件来操作,对一个文件的读写会调用内核提供的系统命令,返回一个fd(文件描述符)。而对于一个socket的读写也会有相应的文件描述符,成为socketfd。

【使用NIO的api来完成多路复用机制,实现伪异步IO。】
【Netty的I/O模型是基于非阻塞IO实现的,底层依赖的是JDK NIO框架的多路复用器Selector来实现。一个多路复用器Selector可以同时轮询多个Channel,采用epoll模式后,只需要一个线程负责Selector的轮询,就可以接入成千上万个客户端连接。】



  • 异步IO
    • 数据就绪后,客户端不需要发送内核指令从内核空间读取数据,而是系统会异步把这个数据直接拷贝到用户空间,应用程序只需要直接使用该数据即可;

Netty —— 高性能通信框架

Netty提供了上述三种Reactor模型的支持,我们可以通过Netty封装好的API来快速完成不同Reactor模型的开发,这也是为什么大家都选择Netty的原因之一,除此之外,Netty相比于NIO原生API,它有以下特点:

  • 提供了高效的I/O模型、线程模型和时间处理机制
  • 提供了非常简单易用的API
  • 对数据协议和序列化提供了很好的支持
  • 稳定性,Netty修复了JDK NIO较多的问题
  • 可扩展性在同类型的框架中都是做的非常好的
  • 性能层面的优化
    • 对象池复用
    • 零拷贝技术
posted @ 2024-08-13 23:20  哆啦**  阅读(68)  评论(0编辑  收藏  举报