java

位置:IT落伍者 >> java >> 浏览文章

分析 Java I/O 的工作机制


发布日期:2020年11月13日
 
分析 Java I/O 的工作机制

网络 I/O 优化

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

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

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

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

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

同步与异步

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

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

阻塞与非阻塞

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

两种的方式的组合

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

四种组合方式

组合方式

性能分析

同步阻塞

最常用的一种用法使用也是最简单的但是 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 中要查询数据通常会往多个数据节点发送查询命令但是要检查每个节点返回数据的完整性所以需要一个异步查询同步结果的应用场景部分代码如下

清单 异步查询同步结果

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_ = SystemcurrentTimeMillis()

}

/*** 检查需要的数据是否已经返回如果没有返回阻塞 */

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 (SystemcurrentTimeMillis() startTime_)

if(overall_timeout > )// 设置等待超时的时间

bVal = condition_await(overall_timeout TimeUnitMILLISECONDS)

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_ = responsegetMessageBody()// 设置返回的数据

done_set(true)

condition_signal()// 唤醒阻塞的线程

}

}finally{lock_unlock()}

}

}

总结

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

               

上一篇:JAVA WEB开发中路径问题的总结

下一篇:Java堆栈溢出的机制与原理