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 识别出来;如何写入呢?下面来说说。。。
在处理数据层面,如从数据库中读取数据,生成本地文件,写代码为了 方便,我们未必要 1M 怎么来处理,这个交给底层的驱动程序去拆分, 对于我们的程序来讲我们认为它是连续写即可;我们比如想将一个 1000W 数据的数据库表,导出到文件;此时,你要么进行分页,oracle 当然用三层包装即可,mysql 用 limit,不过分页每次都会新的查询,而 且随着翻页,会越来越慢,其实我们想拿到一个句柄,然后向下游动, 编译一部分数据(如 10000 行)将写文件一次(写文件细节不多说了, 这个是最基本的),需要注意的时候每次 buffer 的数据,在用 outputstream 写入的时候,最好 flush 一下,将缓冲区清空下;接下来, 执行一个没有 where 条件的 SQL,会不会将内存撑爆?是的,这个问 题我们值得去思考下,通过 API 发现可以对 SQL 进行一些操作,例如, 通过:PreparedStatement statement = connection.prepareStatement(sql),这是默认得到的预编译,还可以通 过设置:PreparedStatement statement = connection.prepareStatement(sql , ResultSet.TYPE_FORWARD_ONLY , ResultSet.CONCUR_READ_ONLY); 来设置游标的方式, 以至于游标不是将数据直接 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?zeroDateTimeBehavior=conv ertToNull&useCursorFetch=true&defaultFetchSize=1000 上次被这个问题纠结了很久(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 的文件拆分和合并过程中,要不影响线上业务是比较难的事情,很多问 题值得我们去研究场景,因为不同的场景有不同的方法去解决,但是大 同小异,明白思想和方法,明白内存和体系架构,明白你所面临的是沈 阳的场景,只是细节上改变可以带来惊人的效果

0 个评论

要回复文章请先登录注册