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

引用:

  • netty内存分析[https://stor.51cto.com/art/201808/581286.htm]
  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
使用Netty模拟RPC调用需要先了解RPC的基本概念和原理,以及Netty框架的使用方法。 RPC(Remote Procedure Call)远程过程调用是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。它允许程序调用另一个地址空间(通常是共享网络的另一台机器上)的过程或函数,而不用显式地编写远程调用的代码。RPC通常基于客户端/服务器模型,客户端向服务器发送RPC请求,服务器响应请求并返回结果。 Netty是一个高性能的、异步的、事件驱动的网络编程框架,它可以轻松地实现RPC调用。 下面是一个简单的Java代码示例,演示如何使用Netty模拟RPC调用: 1. 首先需要定义一个接口,这个接口定义了要远程调用的方法: ```java public interface HelloService { String sayHello(String name); } ``` 2. 接下来创建一个实现类,实现HelloService接口: ```java public class HelloServiceImpl implements HelloService { @Override public String sayHello(String name) { return "Hello, " + name + "!"; } } ``` 3. 创建一个服务端程序,启动Netty服务端,并将HelloServiceImpl注册到服务端: ```java public class Server { public static void main(String[] args) throws Exception { EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast(new ObjectDecoder(ClassResolvers.cacheDisabled(null))); pipeline.addLast(new ObjectEncoder()); pipeline.addLast(new ServerHandler()); } }); ChannelFuture f = b.bind(8888).sync(); f.channel().closeFuture().sync(); } finally { workerGroup.shutdownGracefully(); bossGroup.shutdownGracefully(); } } private static class ServerHandler extends SimpleChannelInboundHandler<Object> { @Override protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception { if (msg instanceof RpcRequest) { RpcRequest request = (RpcRequest) msg; String className = request.getClassName(); String methodName = request.getMethodName(); Class<?>[] parameterTypes = request.getParameterTypes(); Object[] parameters = request.getParameters(); // 根据类名获取实现类 Class<?> clazz = Class.forName(className); Object service = clazz.newInstance(); // 根据方法名和参数类型获取方法 Method method = clazz.getMethod(methodName, parameterTypes); // 执行方法 Object result = method.invoke(service, parameters); // 返回结果 ctx.writeAndFlush(result); } } } } ``` 4. 创建一个客户端程序,通过Netty客户端向服务端发送RPC请求: ```java public class Client { public static void main(String[] args) throws Exception { EventLoopGroup group = new NioEventLoopGroup(); try { Bootstrap b = new Bootstrap(); b.group(group) .channel(NioSocketChannel.class) .handler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast(new ObjectEncoder()); pipeline.addLast(new ObjectDecoder(ClassResolvers.cacheDisabled(null))); pipeline.addLast(new ClientHandler()); } }); ChannelFuture f = b.connect("localhost", 8888).sync(); // 发送RPC请求 RpcRequest request = new RpcRequest(); request.setClassName("com.example.HelloServiceImpl"); request.setMethodName("sayHello"); request.setParameterTypes(new Class<?>[] { String.class }); request.setParameters(new Object[] { "world" }); f.channel().writeAndFlush(request); // 等待响应 f.channel().closeFuture().sync(); } finally { group.shutdownGracefully(); } } private static class ClientHandler extends SimpleChannelInboundHandler<Object> { @Override protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception { // 处理响应结果 System.out.println(msg); ctx.channel().close(); } } } ``` 这样,我们就通过Netty模拟了一次RPC调用。当客户端向服务端发送RPC请求时,服务端会根据请求参数调用相应的方法并返回结果,客户端收到响应结果后输出到控制台。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值