就处理速度而言,CPU > 内存 > 磁盘IO。另外,磁盘的读写是系统调用,会从用户态切换到内核态,频繁的切换会导致性能的损失。为了能够提升性能,需要避免磁盘IO成为瓶颈。整体思想是:
1.减少磁盘IO;
2.减少系统调用;
在写入文件的时候,可以在写入较大批量后再flush到磁盘,而不是没写几个字节就flush一次。每次flush都会产生一次系统调用,一次IO,对性能影响比较明显。但是要注意的是,这种情况容易造成数据的丢失。在写入大批量数据还没来得及flush的时候,如果应用程序崩溃,那么数据是不会丢失的,但是如果操作系统崩溃了,那么就会造成数据的丢失。使用这种方式的时候,一定要考虑是否能够接收这种丢失或者有其他手段保证数据能够恢复。
内存的读写速度明显高于磁盘读写,所以缓存是减少磁盘IO很有效的方式。我们可以建一个数据的缓存,当有写入时,直接更新缓存,更新完成后就可以返回继续处理其他事情了。把缓存刷新到磁盘的工作可以交给其他线程批量来做。这种方式还能带来的一个好处是,合并更新。比如,应用程序更新了缓存的第一个值4 -> 5,之后又将这一个值继续更新5 -> 6。之后在刷新时,只需直接将6刷新到磁盘。由于缓存的存在,数据的读取如果能够在缓存中命中,那么性能也能够提升很多。这种方式同样要注意数据丢失的问题。如果缓存中的脏数据还没来得及更新到磁盘,程序就崩溃了,那么就会导致数据的丢失。为了避免这种情况,在数据库的实现中,采用WAL,即先写日志,再更新缓存。这样即使缓存中的数据丢失了,也可以通过日志来恢复数据。
zero-copy主要应对的是应用程序不需要对文件内容进行处理,直接转发的情况。比如一个日志采集系统,它只需要读取日志文件,然后上传到日志服务即可,不需要对日志文件做处理。
在一般场景下,应用程序调用系统命令读取文件,之后从用户态切换到内核态,CPU向DMS发起读取文件的命令,DMS就会把数据准备好,放到内核缓存中,之后通知CPU,CPU再将数据拷贝到应用缓存,这时应用就拿到数据了。如果应用需要发送这些数据给日志服务器,那么需要再将数据拷贝到Socket的缓存,之后数据才会被发送。
在zero-copy场景下,数据不会返回到应用缓存,而是直接在内核中从内核缓存拷贝到Socket缓存进行发送。减少了一次数据的拷贝。
在新版本的linux操作系统中,利用文件描述符从内核缓存拷贝到Socket缓存的步骤也被省略了,极大的提升了性能。
可以把文件直接映射到内存,这样对这个文件的读写可以直接以操作内存的方式实现,不需要再进行系统的调用,也就是减少了用户态内核态的切换。另外,这还是一种jvm之间进行内存映射的方式,建一个磁盘文件,分别映射到两个jvm中,这样在一个jvm的写可以被另一个jvm看到。
谨记
优化的针对点一定是瓶颈点,不然就是浪费工作量。
更多相关技术内容咨询欢迎前往并持续关注六星社区了解详情。
关注下方微信公众号:java圈子,获取价值999元全套java入门到进阶的学习资料以及教程,还有java技术交流群一起交流学习哦。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!