Netty入门(1) Netty背景

Netty入门(1) Netty背景

netty是什么?

  • Netty是基于Java NIO的网络应用框架,是一个NIO client-server(客户端服务器)框架,使用Netty可以快速开发网络应用,例如服务器和客户端协议。Netty提供了一种新的方式来使开发网络应用程序,这种新的方式使得它很容易使用和有很强的扩展性。Netty的内部实现时很复杂的,但是Netty提供了简单易用的api从网络处理代码中解耦业务逻辑。Netty是完全基于NIO实现的,所以整个Netty都是异步的。
  • 网络应用程序通常需要有较高的可扩展性,无论是Netty还是其他的基于Java NIO的框架,都会提供可扩展性的解决方案。
    Netty中一个关键组成部分是它的异步特性,本章将讨论同步(阻塞)和异步(非阻塞)的IO来说明为什么使用异步代码来解决扩展性
    问题以及如何使用异步。

netty的异步设计

整个Netty的API都是异步的,异步处理不是一个新的机制,这个机制出来已经有一些时间了。对网络应用来说,IO一般是性能的瓶颈,使用异步IO可以较大程度上提高程序性能,因为异步变的越来越重要。但是它是如何工作的呢?以及有哪些不同的模式可用呢?异步处理提倡更有效的使用资源,它允许你创建一个任务,当有事件发生时将获得通知并等待事件完成。这样就不会阻塞,不管事件完成与否都会及时返回,资源利用率更高,程序可以利用剩余的资源做一些其他的事情。

两个常见的异步设计 1 – 回调callback

通过一个简单的例子了解下回调的设计:
首先定义两个接口,一个是我们的实际使用的服务接口,另一个是callback接口:
服务接口:

public interface SomePart {
    String saHello (SomePartCallback somePartCallback);
}

callback接口:

public interface SomePartCallback {
    void onExecuteResult(String yourname) throws Exception;
    void onUnKnownError(Throwable cause);
}

然后分别实现两个接口并启动测试:


public class MySomePart implements SomePart {
    final String yourname;

    public MySomePart(String yourname){
        this.yourname = yourname;
    }

    @Override
    public String saHello(SomePartCallback somePartCallback) {
        try{
            somePartCallback.onExecuteResult(yourname);
        }catch (Exception e){
            somePartCallback.onUnKnownError(e);
        }
        return null;
    }
}

callback 实现和启动测试:


public class WorkerTestCase {
    public void doExecute(){
        SomePart somePart = new MySomePart("test Name");
        somePart.saHello(new SomePartCallback() {
            @Override
            public void onExecuteResult(String yourname) throws Exception {
                System.out.println("catch your name :" + yourname);
            }

            @Override
            public void onUnKnownError(Throwable cause) {
                System.out.println("An error occur : " + cause);
            }
        });

    }

    // application start

    public static void main(String[] args) {
        WorkerTestCase workerTestCase = new WorkerTestCase();
        workerTestCase.doExecute();
    }
}

测试结果如下:

catch your name :test Name

上面的例子只是一个简单的模拟回调,当出现错误时就会callback
因为可以将这些方法的执行从"caller"线程移动到其他的线程执行;但也不会保证Callback的每个方法都会被执行。回调过程有个问题就是当你使用链式调用,很多不同的方法会导致线性代码;有些人认为这种链式调用方法会导致代码难以阅读,但是我认为这是一种风格和习惯问题。例如,基于Javascript的Node.js越来越受欢迎,它使用了大量的回调,许多人都认为它的这种方式利于阅读和编写。

两个常见的异步设计 2 – Future模式

第二种技术是使用Futures。Futures是一个抽象的概念,它表示一个值,该值可能在某一点变得可用。一个Future要么获得计算完的结果,要么获得计算失败后的异常。Java在java.util.concurrent包中附带了Future接口,它使用Executor异步执行。例
如下面的代码,每传递一个Runnable对象到ExecutorService.submit()方法就会得到一个回调的Future,你能使用它检测是否执行完成。

public class FutureExample {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService executorService = Executors.newCachedThreadPool();
        Runnable task1 = new Runnable() {
            @Override
            public void run() {
                System.out.println("I am task 1");
            }
        };
        Callable<Integer> task2 = new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                return new Integer(100);
            }
        };

        Future<?> f1 = executorService.submit(task1);
        Future<Integer> f2 = executorService.submit(task2);
        System.out.println("task1 is completed? " + f1.isDone());
        System.out.println("task2 is completed? " + f2.isDone());
        //waiting task1 completed
        while (f1.isDone()) {
            System.out.println("task1 completed.");
            break;
        }
        //waiting task2 completed
        while (f2.isDone()) {
            System.out.println("return value by task2: " + f2.get());
            break;
        }
    }

}

执行结果为:

I am task 1
task1 is completed? false
task2 is completed? true
task1 completed.
return value by task2: 100

有时候使用Future感觉很丑陋,因为你需要间隔检查Future是否已完成,而使用回调会直接收到返回通知。这两个常用的异步执行技术没有明确的好与坏。事实上,在Netty中,这两类回调方式都会使用到。

NIO的问题和Netty中是如何解决这些问题的

Java的NIO相对老的IO APIs有着非常大的进步,但是使用NIO是受限制的。这些问题往往是设计的问题,有些是缺陷知道的。

跨平台和兼容性问题
  • NIO是一个比较底层的APIs,它依赖于操作系统的IO APIs。Java实现了统一的接口来操作IO,其在所有操作系统中的工作行为是一样的,这是很伟大的。使用NIO会经常发现代码在Linux上正常运行,但在Windows上就会出现问题,这里其实和底层操作系统的设计相关。你如果使用NIO编写程序,就应该在所有的操作系统上进行测试来支持,使程序可以在任何操作系统上正常运行;即使在所有的Linux系统上都测试通过了,也要在其他的操作系统上进行测试;若不验证,以后就可能会出问题。
  • 虽然,NIO看起来很理想,但是NIO只支持Jdk1.7+,若你的程序在Java1.6上运行,则无法使用NIO2。另外,Java7的NIO2中没有提供DatagramSocket的支持,所以NIO2只支持TCP程序,不支持UDP程序。
  • Netty提供一个统一的接口,同一语义无论在Java6还是Java7的环境下都是可以运行的,开发者无需关心底层APIs就可以轻松实现相关功能。
扩展ByteBuffer
  • ByteBuffer是一个数据容器,但是可惜的是JDK没有开发ByteBuffer实现的源码,ByteBuffer允许包装一个byte[]来获得一个实例,如果你希望尽量减少内存拷贝,那么这种方式是非常有用的。同时,因为ByteBuffer的构造函数是私有的,所以它不能被扩展。Netty提供了自己的ByteBuffer实现,Netty通过一些简单的APIs对ByteBuffer进行构造、使用和操作,以此来解决NIO中的一些限制。
NIO对缓冲区的聚合和分散操作可能会操作内存泄露
  • 很多Channel的实现支持Gather和Scatter。这个功能允许从从多个ByteBuffer中读入或写入到过个ByteBuffer,这样做可以提供性能。操作系统底层知道如何处理这些被写入/读出,并且能以最有效的方式处理。如果要分割的数据再多个不同的ByteBuffer中,使用Gather/Scatter是比较好的方式。
    下图显示的是Scatter(分散),将ScatteringByteBuffer中的数据分散读取到多个ByteBuffer中:
    在这里插入图片描述
    而Gather(聚合),则是将多个ByteBuffer的数据写入到GatheringByteChannel:
    在这里插入图片描述
Linux 等系统中selector和epoll的缺陷

Linux-like OSs的选择器使用的是epoll-IO事件通知工具。这是一个在操作系统以异步方式工作的网络栈。即使是现在,著名的epoll-bug也可能会导致无效的状态选择和100%的CPU利用率。要解决epoll-bug的唯一方法是回收旧的选择器,将先前注册的通道实例转移到新创建的选择器上。
那看下epoll的bug具体是怎么样的?

while (true) {
	int selected = selector.select();
	Set<SelectedKeys> readyKeys = selector.selectedKeys();
	Iterator iterator = readyKeys.iterator();
	while (iterator.hasNext()) {
		......
		} 
	} 
..
}
//这段代码的作用是while循环消耗CPU:
...
while (true) {
} 
..

参考书籍:

Netty in Action

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值