拥有多年互联网和银行系统性能测试开发经验,对性能瓶颈诊断定位和优化领域有较多研究。 重回互联网行业,性能测试开发、自动化测试开发、Java开发

深入分析 Java I/O 的工作机制(网络 I/O 优化)

上一篇 / 下一篇  2013-01-05 16:38:32 / 个人分类:Java开发

网络 I/O 优化

  网络 I/O 优化通常有一些基本处理原则:

  1、一个是减少网络交互的次数:要减少网络交互的次数通常我们在需要网络交互的两端会设置缓存,比如Oracle的 JDBC 驱动程序,就提供了对查询的SQL结果的缓存,在客户端和数据库端都有,可以有效的减少对数据库的访问。关于 Oracle JDBC 的内存管理可以参考《 Oracle JDBC 内存管理》。除了设置缓存还有一个办法是,合并访问请求:如在查询数据库时,我们要查 10 个 id,我可以每次查一个 id,也可以一次查 10 个 id。再比如在访问一个页面时通过会有多个 js 或 css 的文件,我们可以将多个 js 文件合并在一个 HTTP 链接中,每个文件用逗号隔开,然后发送到后端 Web 服务器根据这个 URL 链接,再拆分出各个文件,然后打包再一并发回给前端浏览器。这些都是常用的减少网络 I/O 的办法。

  2、减少网络传输数据量的大小:减少网络数据量的办法通常是将数据压缩后再传输,如 HTTP 请求中,通常 Web 服务器将请求的 Web 页面 gzip 压缩后在传输给浏览器。还有就是通过设计简单的协议,尽量通过读取协议头来获取有用的价值信息。比如在代理程序设计时,有 4 层代理和 7 层代理都是来尽量避免要读取整个通信数据来取得需要的信息。

  3、尽量减少编码:通常在网络 I/O 中数据传输都是以字节形式的,也就是通常要序列化。但是我们发送要传输的数据都是字符形式的,从字符到字节必须编码。但是这个编码过程是比较耗时的,所以在要经过网络 I/O 传输时,尽量直接以字节形式发送。也就是尽量提前将字符转化为字节,或者减少字符到字节的转化过程。

  4、根据应用场景设计合适的交互方式:所谓的交互场景主要包括同步与异步阻塞与非阻塞方式,下面将详细介绍。

  同步与异步

  所谓同步就是一个任务的完成需要依赖另外一个任务时,只有等待被依赖的任务完成后,依赖的任务才能算完成,这是一种可靠的任务序列。要么成功都成功,失败都失败,两个任务的状态可以保持一致。而异步是不需要等待被依赖的任务完成,只是通知被依赖的任务要完成什么工作,依赖的任务也立即执行,只要自己完成了整个任务就算完成了。至于被依赖的任务最终是否真正完成,依赖它的任务无法确定,所以它是不可靠的任务序列。我们可以用打电话和发短信来很好的比喻同步与异步操作。

  在设计到 IO 处理时通常都会遇到一个是同步还是异步的处理方式的选择问题。因为同步与异步的 I/O 处理方式对调用者的影响很大,在数据库产品中都会遇到这个问题。因为 I/O 操作通常是一个非常耗时的操作,在一个任务序列中 I/O 通常都是性能瓶颈。但是同步与异步的处理方式对程序的可靠性影响非常大,同步能够保证程序的可靠性,而异步可以提升程序的性能,必须在可靠性和性能之间做个平衡,没有完美的解决办法。

  阻塞与非阻塞

  阻塞与非阻塞主要是从 CPU 的消耗上来说的,阻塞就是 CPU 停下来等待一个慢的操作完成 CPU 才接着完成其它的事。非阻塞就是在这个慢的操作在执行时 CPU 去干其它别的事,等这个慢的操作完成时,CPU 再接着完成后续的操作。虽然表面上看非阻塞的方式可以明显的提高 CPU 的利用率,但是也带了另外一种后果就是系统的线程切换增加。增加的 CPU 使用时间能不能补偿系统的切换成本需要好好评估。

  两种的方式的组合

  组合的方式可以由四种,分别是:同步阻塞、同步非阻塞、异步阻塞、异步非阻塞,这四种方式都对 I/O 性能有影响。下面给出分析,并有一些常用的设计用例参考。

  表 3. 四种组合方式

组合方式

性能分析

同步阻塞

最常用的一种用法,使用也是最简单的,但是 I/O 性能一般很差,CPU 大部分在空闲状态。

同步非阻塞

提升 I/O 性能的常用手段,就是将 I/O 的阻塞改成非阻塞方式,尤其在网络 I/O 是长连接,同时传输数据也不是很多的情况下,提升性能非常有效。
这种方式通常能提升 I/O 性能,但是会增加 CPU 消耗,要考虑增加的 I/O 性能能不能补偿 CPU 的消耗,也就是系统的瓶颈是在 I/O 还是在 CPU 上。

异步阻塞

这种方式在分布式数据库中经常用到,例如在网一个分布式数据库中写一条记录,通常会有一份是同步阻塞的记录,而还有两至三份是备份记录会写到其它机器上,这些备份记录通常都是采用异步阻塞的方式写 I/O。
异步阻塞对网络 I/O 能够提升效率,尤其像上面这种同时写多份相同数据的情况。

异步非阻塞

这种组合方式用起来比较复杂,只有在一些非常复杂的分布式情况下使用,像集群之间的消息同步机制一般用这种 I/O 组合方式。如 Cassandra 的Gossip 通信机制就是采用异步非阻塞的方式。
它适合同时要传多份相同的数据到集群中不同的机器,同时数据的传输量虽然不大,但是却非常频繁。这种网络 I/O 用这个方式性能能达到最高。

  虽然异步和非阻塞能够提升 I/O 的性能,但是也会带来一些额外的性能成本,例如会增加线程数量从而增加 CPU 的消耗,同时也会导致程序设计的复杂度上升。如果设计的不合理的话反而会导致性能下降。在实际设计时要根据应用场景综合评估一下。

下面举一些异步和阻塞的操作实例:

  在 Cassandra 中要查询数据通常会往多个数据节点发送查询命令,但是要检查每个节点返回数据的完整性,所以需要一个异步查询同步结果的应用场景,部分代码如下:

  清单 3.异步查询同步结果

class AsyncResult implements IAsyncResult{
private byte[] result_;
private AtomicBoolean done_ = new AtomicBoolean(false);
private Lock lock_ = new ReentrantLock();
private Condition condition_;
private long startTime_;
public AsyncResult(){
condition_ = lock_.newCondition();// 创建一个锁
startTime_ = System.currentTimeMillis();
}
/*** 检查需要的数据是否已经返回,如果没有返回阻塞 */
public byte[] get(){
lock_.lock();
try{
if (!done_.get()){condition_.await();}
}catch (InterruptedException ex){
throw new AssertionError(ex);
}finally{lock_.unlock();}
return result_;
}
/*** 检查需要的数据是否已经返回 */
public boolean isDone(){return done_.get();}
/*** 检查在指定的时间内需要的数据是否已经返回,如果没有返回抛出超时异常 */
public byte[] get(long timeout, TimeUnit tu) throws TimeoutException{
lock_.lock();
try{ boolean bVal = true;
try{
if ( !done_.get() ){
long overall_timeout = timeout - (System.currentTimeMillis() - startTime_);
if(overall_timeout > 0)// 设置等待超时的时间
bVal = condition_.await(overall_timeout, TimeUnit.MILLISECONDS);
else bVal = false;
}
}catch (InterruptedException ex){
throw new AssertionError(ex);
}
if ( !bVal && !done_.get() ){// 抛出超时异常
throw new TimeoutException("Operation timed out.");
}
}finally{lock_.unlock(); }
return result_;
}
/*** 该函数拱另外一个线程设置要返回的数据,并唤醒在阻塞的线程 */
public void result(Message response){
try{
lock_.lock();
if ( !done_.get() ){
result_ = response.getMessageBody();// 设置返回的数据
done_.set(true);
condition_.signal();// 唤醒阻塞的线程
}
}finally{lock_.unlock();}
}
}

  总结

  本文阐述的内容较多,从 Java 基本 I/O 类库结构开始说起,主要介绍了磁盘 I/O 和网络 I/O 的基本工作方式,最后介绍了关于 I/O 调优的一些方法。


TAG:

 

评分:0

我来说两句

Open Toolbar