Java在处理大数据的时候一些小技巧

转载自:http://soft.chinabyte.com/database/258/12609258.shtmlhtml

 

 

  众所周知,java在处理数据量比较大的时候,加载到内存必然会致使内存溢出,而在一些数据处理中咱们不得不去处理海量数据,在作数据处理中,咱们常见的手段是分解,压缩,并行,临时文件等方法;java

  例如,咱们要将数据库(不管是什么数据库)的数据导出到一个文件,通常是Excel或文本格式的CSV;对于Excel来说,对于POI和JXL的接口,你不少时候没有办法去控制内存何时向磁盘写入,很恶心,并且这些API在内存构造的对象大小将比数据原有的大小要大不少倍数,因此你不得不去拆分Excel,还好,POI开始意识到这个问题,在3.8.4的版本后,开始提供cache的行数,提供了SXSSFWorkbook的接口,能够设置在内存中的行数,不过惋惜的是,他当你超过这个行数,每添加一行,它就将相对行数前面的一行写入磁盘(如你设置2000行的话,当你写第20001行的时候,他会将第一行写入磁盘),其实这个时候他些的临时文件,以致于不消耗内存,不过这样你会发现,刷磁盘的频率会很是高,咱们的确不想这样,由于咱们想让他达到一个范围一次性将数据刷如磁盘,好比一次刷1M之类的作法,惋惜如今尚未这种API,很痛苦,我本身作过测试,经过写小的Excel比使用目前提供刷磁盘的API来写大文件,效率要高一些,并且这样若是访问的人稍微多一些磁盘IO可能会扛不住,由于IO资源是很是有限的,因此仍是拆文件才是上策;而当咱们写CSV,也就是文本类型的文件,咱们不少时候是能够本身控制的,不过你不要用CSV本身提供的API,也是不太可控的,CSV自己就是文本文件,你按照文本格式写入便可被CSV识别出来;如何写入呢?下面来讲说。。。node

  在处理数据层面,如从数据库中读取数据,生成本地文件,写代码为了方便,咱们未必要1M怎么来处理,这个交给底层的驱动程序去拆分,对于咱们的程序来说咱们认为它是连续写便可;咱们好比想将一个1000W数据的数据库表,导出到文件;此时,你要么进行分页,oracle固然用三层包装便可,mysql用limit,不过度页每次都会新的查询,并且随着翻页,会愈来愈慢,其实咱们想拿到一个句柄,而后向下游动,编译一部分数据(如10000行)将写文件一次(写文件细节很少说了,这个是最基本的),须要注意的时候每次buffer的数据,在用outputstream写入的时候,最好flush一下,将缓冲区清空下;接下来,执行一个没有where条件的SQL,会不会将内存撑爆?是的,这个问题咱们值得去思考下,经过API发现能够对SQL进行一些操做,例如,经过:PreparedStatement statement = connection.prepareStatement(sql),这是默认获得的预编译,还能够经过设置:mysql

  PreparedStatement statement = connection.prepareStatement(sql,ResultSet.TYPE_FORWARD_ONLY,ResultSet.CONCUR_READ_ONLY);nginx

  来设置游标的方式,以致于游标不是将数据直接cache到本地内存,而后经过设置statement.setFetchSize(200);设置游标每次遍历的大小;OK,这个其实我用过,oracle用了和没用没区别,由于oracle的jdbc API默认就是不会将数据cache到java的内存中的,而mysql里头设置根本无效,我上面说了一堆废话,呵呵,我只是想说,java提供的标准API也未必有效,不少时候要看厂商的实现机制,还有这个设置是不少网上说有效的,可是这纯属抄袭;对于oracle上面说了不用关心,他自己就不是cache到内存,因此java内存不会致使什么问题,若是是mysql,首先必须使用5以上的版本,而后在链接参数上加上useCursorFetch=true这个参数,至于游标大小能够经过链接参数上加上:defaultFetchSize=1000来设置,例如:算法

  jdbc:mysql://xxx.xxx.xxx.xxx:3306/abc?zeroDateTimeconvertToNull&useCursorFetch=true&defaultFetchSize=1000< /span>sql

  上次被这个问题纠结了好久(mysql的数据老致使程序内存膨胀,并行2个直接系统就宕了),还去看了不少源码才发现奇迹居然在这里,最后通过mysql文档的确认,而后进行测试,并行多个,并且数据量都是500W以上的,都不会致使内存膨胀,GC一切正常,这个问题终于完结了。数据库

  咱们再聊聊其余的,数据拆分和合并,当数据文件多的时候咱们想合并,当文件太大想要拆分,合并和拆分的过程也会遇到相似的问题,还好,这个在咱们可控制的范围内,若是文件中的数据最终是能够组织的,那么在拆分和合并的时候,此时就不要按照数据逻辑行数来作了,由于行数最终你须要解释数据自己来断定,可是只是作拆分是没有必要的,你须要的是作二进制处理,在这个二进制处理过程,你要注意了,和平时read文件不要使用同样的方式,平时大多对一个文件读取只是用一次read操做,若是对于大文件内存确定直接挂掉了,不用多说,你此时因该每次读取一个可控范围的数据,read方法提供了重载的offset和length的范围,这个在循环过程当中本身能够计算出来,写入大文件和上面同样,不要读取到必定程序就要经过写入流flush到磁盘;其实对于小数据量的处理在现代的NIO技术的中也有用到,例如多个终端同时请求一个大文件下载,例如视频下载吧,在常规的状况下,若是用java的容器来处理,通常会发生两种状况:缓存

  其一为内存溢出,由于每一个请求都要加载一个文件大小的内存甚至于更多,由于java包装的时候会产生不少其余的内存开销,若是使用二进制会产生得少一些,并且在通过输入输出流的过程当中还会经历几回内存拷贝,固然若是有你相似nginx之类的中间件,那么你能够经过send_file模式发送出去,可是若是你要用程序来处理的时候,内存除非你足够大,可是java内存再大也会有GC的时候,若是你内存真的很大,GC的时候死定了,固然这个地方也能够考虑本身经过直接内存的调用和释放来实现,不过要求剩余的物理内存也足够大才行,那么足够大是多大呢?这个很差说,要看文件自己的大小和访问的频率;服务器

  其二为假如内存足够大,无限制大,那么此时的限制就是线程,传统的IO模型是线程是一个请求一个线程,这个线程从主线程从线程池中分配后,就开始工做,通过你的Context包装、Filter、拦截器、业务代码各个层次和业务逻辑、访问数据库、访问文件、渲染结果等等,其实整个过程线程都是被挂住的,因此这部分资源很是有限,并且若是是大文件操做是属于IO密集型的操做,大量的CPU时间是空余的,方法最直接固然是增长线程数来控制,固然内存足够大也有足够的空间来申请线程池,不过通常来说一个进程的线程池通常会受到限制也不建议太多的,而在有限的系统资源下,要提升性能,咱们开始有了new IO技术,也就是NIO技术,新版的里面又有了AIO技术,NIO只能算是异步IO,可是在中间读写过程仍然是阻塞的(也就是在真正的读写过程,可是不会去关心中途的响应),还未作到真正的异步IO,在监听connect的时候他是不须要不少线程参与的,有单独的线程去处理,链接也又传统的socket变成了selector,对于不须要进行数据处理的是无需分配线程处理的;而AIO经过了一种所谓的回调注册来完成,固然还须要OS的支持,当会掉的时候会去分配线程,目前还不是很成熟,性能最多和NIO吃平,不过随着技术发展,AIO必然会超越NIO,目前谷歌V8虚拟机引擎所驱动的node.js就是相似的模式,有关这种技术不是本文的说明重点;

  将上面二者结合起来就是要解决大文件,还要并行度,最土的方法是将文件每次请求的大小下降到必定程度,如8K(这个大小是通过测试后网络传输较为适宜的大小,本地读取文件并不须要这么小),若是再作深刻一些,能够作必定程度的cache,将多个请求的同样的文件,cache在内存或分布式缓存中,你不用将整个文件cache在内存中,将近期使用的cache几秒左右便可,或你能够采用一些热点的算法来配合;相似迅雷下载的断点传送中(不过迅雷的网络协议不太同样),它在处理下载数据的时候未必是连续的,只要最终能合并便可,在服务器端能够反过来,谁正好须要这块的数据,就给它就能够;才用NIO后,能够支持很大的链接和并发,本地经过NIO作socket链接测试,100个终端同时请求一个线程的服务器,正常的WEB应用是第一个文件没有发送完成,第二个请求要么等待,要么超时,要么直接拒绝得不到链接,改为NIO后此时100个请求都能链接上服务器端,服务端只须要1个线程来处理数据就能够,将不少数据传递给这些链接请求资源,每次读取一部分数据传递出去,不过能够计算的是,在整体长链接传输过程当中整体效率并不会提高,只是相对相应和所开销的内存获得量化控制,这就是技术的魅力,也许不要太多的算法,不过你得懂他。

  相似的数据处理还有不少,有些时候还会将就效率问题,好比在HBase的文件拆分和合并过程当中,要不影响线上业务是比较难的事情,不少问题值得咱们去研究场景,由于不一样的场景有不一样的方法去解决,可是大同小异,明白思想和方法,明白内存和体系架构,明白你所面临的是沈阳的场景,只是细节上改变能够带来惊人的效果。

相关文章
相关标签/搜索