简单说下Netty和RPC吧,大佬绕行

Netty是什么?

  1. Netty是一个高性能、一部驱动的NIO框架,同时也是基于jAVA NIO实现的;
  2. Netty作为异步NIO框架,可以提供对TCP、UDP和文件传输的支持;
  3. 基于Netty的的异步机制-Future-Listener,用户可以主动获取/消息通知 方式,来获取IO操作的结果

Netty的高性能?怎么个搞法?

在IO编程过程中,当需要同事处理多个客户端的接入需求时,可以利用多线程/IO多路复用来处理;
IO多路复用可以把多个请求阻塞复用到一个selector上,这样一个单线程selector就可以处理多个客户需求,降低开销,系统不需要每个需求都去创建线程,节省资源;

进入netty正题

  1. 多路复用的通信方式
    1.1 服务端通信序列图

    1.2 客户端通信序列图

Netty的IO线程NioEventLoop由于聚合了多路复用器selector,可以并发处理成百上千的客户端channel,且读操作书非阻塞的,效率更高;

  1. netty的异步通讯NIO

由于Netty采用了异步通信模式,一个IO线程可以并发处理多个客户端线程(读/写),从根本上解决传统的阻塞IO模IO链接和线程1VS1的问题,架构性能、弹性伸缩能力和可靠性都有很大提升;

  • 提前记录下两个点:直接内存,堆内存?
    NIO中缓冲区是数据传输的基础,Netty基于JDK原生的ByteBuffer构造了ByteBuf,并进行了大量的优化。

    • 堆内存: 堆内存是由JVM管理的,相对于方法区/栈,对象的实例,数组等分配都存在堆上,GC要地;
    • 直接内存: JVM可以使用native方法在堆外分配内存,之后使用DircetByterBuffer对象作为这块内存的引用进行操作,这样不会对JVM的堆造成影响;只有在JVM-GC的时候才会去顺便清理下直接内存的废弃对象;
  • 关键点
    2.1 零拷贝

  • Netty的发送嗯哼接收ByteBuffer采用Direct Buffers,使用堆外直接内存进行Socket的读写,不需要进行字节缓冲区的二次拷贝。传统的方式会先存入堆内存Heap Buffers进行Socket读写,JVM会将堆内存Buffer拷贝一份到直接内存中,然后进行Socket的读写,会多拷贝一次;

  • Netty提供了组合Buffer对象,可以聚合多个buffer对象,可以像操作一个buffer对象一样操作一组buffer对象;这样避免了传统操作中先将多个buffer对象合并的操作

  • Netty采用了TransferTo方法,它可以直接将文件缓冲区数据发送到目标的Channel,避免了传统的方式:通过循环write方法导致的内存拷贝问题;

2.2 内存池.

  • netty的内存池是不依赖于JVM的,基于内存池的缓冲区重用机制,【没找到相关资料】
    2.3 高效的reactor线程模型
    • reactor单线程模型
      所有的IO操作都在同一个NIO线程(异步非阻塞)上完成:Acceptor 接收客户端的TCP 连接请求消息,链路建立成功之后,通过Dispatch 将对应的ByteBuffer
      派发到指定的Handler 上进行消息解码。用户Handler 可以通过NIO 线程将消息发送给客户端

    • reactor多线程模型
      所有的IO操作都是由一组NIO线程池来完成的:有专门一个NIO 线程-Acceptor 线程用于监听服务端,接收客户端的TCP 连接请求; 网络IO 操作-读、写等由一个NIO 线程池负责,线程池可以采用标准的JDK 线程池实现,它包含一个任务队列和N个可用的线程,由这些NIO 线程负责消息的读取、解码、编码和发送

    • reactor主从多线程模型
      服务端用于接收客户端连接的不再是个1 个单独的NIO 线程,而是一个独立的NIO 线程池。
      Acceptor 接收到客户端TCP 连接请求处理完成后(可能包含接入认证等),将新创建的
      SocketChannel 注册到IO 线程池(sub reactor 线程池)的某个IO 线程上,由它负责
      SocketChannel 的读写和编解码工作。Acceptor 线程池仅仅只用于客户端的登陆、握手和安全
      认证,一旦链路建立成功,就将链路注册到后端subReactor 线程池的IO 线程上,由IO 线程负
      责后续的IO 操作

2.4 无锁设计、线程绑定
Netty采用无锁设计,串行操作。避免多线程的竞争;其实netty内部可以同时启动多个串行化线程

Neety的NIOEventLoop读取到消息后,直接调用ChannelPiple的FireChannelRead(msg),只要用户不主动切换线程。一直由NioEventLoop来处理用户的Handler,期间不会线程切换;

2.5 高性能的序列化框架
netty默认支持google protobuf,用户可以实现其他的高性能序列化框架,例如Thrift的压缩二进制编码框架

Netty的RPC实现

RPC(远程调用):Remote Producedure Call

3 关键技术

  • 服务的发布与订阅:zookeeper注册中心
  • 通信:使用Netty来做通信框架
  • Spring:使用Spring 来做beand加载配置
  • 动态处理:客户端使用代理模式,透明化服务调用
  • 消息编解码:使用pb序列化和反序列化

3.1 核心流程

3.2 通信流程

netty一般使用channel.writeAndFlush()方法来发送二进制串,请求书异步请求;
服务端接收到请求处理完后会发送给客户端;

问题:当有多个线程请求到服务端,服务端怎么保证消息返回的准确性?

  1. requestID:会生成一个AtomicLong 全局唯一,并存储回掉对象到全局的ConCurrentHashMap
  2. sync获取回调对象的锁并自旋wait:
    当线程调用channel.writeAndFlush()发送消息后,紧接着会执行callback的get方法,视图获取远程的返回结果,get()方法内部使用sync获取callback对象锁:获取成功-查询是否有结果-没有结果-wait后释放锁继续等待;
  3. 当收到消息找到callback的锁唤醒线程-获取结果
  4. 接收到服务端返回结果,response中包含requsetID,发送给客户端;
    客户端的socket上有专门线程监听线程收到消息,获取到requestID,从前面的ConCurrentHashMap中获取到callBack对象,在用sync获取callbakc的锁。将结果设置到callback对象中。在调用callback.notifyAll()唤醒处于等待的线程;
    3.3 RMI实现方式

JAVA RMI是java原生的远程调用编程接口,具体步骤如下

  1. 编写远程服务接口。实现Remote接口
  2. 实现类编写。继承UniccastRemoteObject
  3. 运行RMI编译器,创建客户端stub类和服务端skeleton类
  4. 启动RMI注册表,驻留这些服务
  5. RMI注册表中注册服务
  6. 客户端查找远程对象,并调用远程方法;
//1:创建远程接口,继承java.rmi.Remote 接口
public interface GreetService extends java.rmi.Remote {
    String sayHello(String name) throws RemoteException;
}


//2:实现远程接口,继承 java.rmi.server.UnicastRemoteObject 类
public class GreetServiceImpl extends java.rmi.server.UnicastRemoteObject
        implements GreetService {
    private static final long serialVersionUID = 3434060152387200042L;

    public GreetServiceImpl() throws RemoteException {
        super();
    }

    @Override
    public String sayHello(String name) throws RemoteException {
        return "Hello " + name;
    }
}

//3:生成Stub 和Skeleton;
//        4:执行rmiregistry 命令注册服务
//        5:启动服务
        LocateRegistry.createRegistry(1098);
        Naming.bind("rmi://10.108.1.138:1098/GreetService",new GreetServiceImpl());
//        6.客户端调用
        GreetService greetService=(GreetService)
        Naming.lookup("rmi://10.108.1.138:1098/GreetService");
        System.out.println(greetService.sayHello("Jobs"));

4 其他RPC对比
4.1 Protocol buffer

4.2 THrift

Apache Thrift,它采用接口描述语言定义并创建服务,支持可扩展的跨语言服务开发,所包含的代码
生成引擎可以在多种语言中,如 C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa,
Smalltalk 等创建高效的、无缝的服务,其传输数据采用二进制格式,相对 XML 和 JSON 体积更小,
对于高并发、大数据量和多语言的环境更有优势。

引用:

原文地址:https://www.cnblogs.com/inyu/p/15412578.html