zoukankan      html  css  js  c++  java
  • 简单说下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 体积更小,
    对于高并发、大数据量和多语言的环境更有优势。

    引用:

  • 相关阅读:
    Openstack API 开发 快速入门
    virtualBox虚拟机到vmware虚拟机转换
    使用Blogilo 发布博客到cnblogs
    Openstack Troubleshooting
    hdoj 1051 Wooden Sticks(上升子序列个数问题)
    sdut 2430 pillars (dp)
    hdoj 1058 Humble Numbers(dp)
    uva 10815 Andy's First Dictionary(快排、字符串)
    sdut 2317 Homogeneous squares
    hdoj 1025 Constructing Roads In JGShining's Kingdom(最长上升子序列+二分)
  • 原文地址:https://www.cnblogs.com/inyu/p/15412578.html
Copyright © 2011-2022 走看看