问题很简单,我很惊讶它在搜索时没有立即弹出。
我有一个需要处理的CSV文件,可能非常大。应将每一行传递给处理器,直到处理完所有行。为了读取CSV文件,我将使用OpenCSV,它实质上提供了一个readNext()方法,它给了我下一行。如果没有更多行可用,则所有处理器都应终止。
为此,我创建了一个非常简单的groovy脚本,定义了一个同步的readNext()方法(因为读取下一行并不是非常耗时),然后创建了几个线程来读取下一行并处理它。它工作正常,但是......
不应该有我可以使用的内置解决方案吗?这不是gpars集合处理,因为它总是假设内存中存在一个现有集合。相反,我无法将其全部读入内存并进行处理,这将导致内存异常。
所以....有一个很好的模板,可以使用几个工作线程“逐行”处理CSV文件吗?
答案 0 :(得分:6)
同时访问文件可能不是一个好主意,GPars的fork / join-processing仅适用于内存数据(集合)。我的意思是按顺序将文件读入列表。当列表达到一定大小时,使用GPars同时处理列表中的条目,清除列表,然后继续阅读行。
答案 1 :(得分:5)
对于演员来说,这可能是一个很好的问题。同步读者actor可以将CSV行移交给并行处理器actor。例如:
@Grab(group='org.codehaus.gpars', module='gpars', version='0.12')
import groovyx.gpars.actor.DefaultActor
import groovyx.gpars.actor.Actor
class CsvReader extends DefaultActor {
void act() {
loop {
react {
reply readCsv()
}
}
}
}
class CsvProcessor extends DefaultActor {
Actor reader
void act() {
loop {
reader.send(null)
react {
if (it == null)
terminate()
else
processCsv(it)
}
}
}
}
def N_PROCESSORS = 10
def reader = new CsvReader().start()
(0..<N_PROCESSORS).collect { new CsvProcessor(reader: reader).start() }*.join()
答案 2 :(得分:2)
我只是在Grails中完成一个问题的实现(你没有指定你是使用grails,普通的hibernate,普通的JDBC还是其他东西)。
我知道没有任何开箱即用的东西。你可以看看与Spring Batch的集成,但是最后一次看到它时,它对我来说感觉非常沉重(而且不是非常时髦)。
如果您正在使用普通JDBC,那么做Christoph建议的可能是最容易的事情(读取N行并使用GPars同时旋转这些行)。
如果您正在使用grails或hibernate,并希望您的工作线程可以访问spring上下文以进行依赖注入,那么事情会变得复杂一些。
我解决它的方法是使用Grails Redis插件(免责声明:我是作者)和Jesque plugin,这是Resque的java实现。
Jesque插件允许您创建“作业”类,这些类具有“进程”方法,该方法具有用于处理在Jesque队列中排队的工作的任意参数。你可以根据需要增加工人数量。
我有一个文件上传,管理员用户可以将文件发布到,它将文件保存到磁盘并为我创建的ProducerJob排队作业。 ProducerJob旋转文件,对于每一行,它将消息队列一条消息,以便ConsumerJob接收。该消息只是从CSV文件中读取的值的映射。
ConsumerJob获取这些值并为其行创建适当的域对象并将其保存到数据库中。
我们已经在生产中使用Redis,因此使用它作为排队机制是有道理的。我们有一个旧的同步加载,连续运行文件加载。我目前正在使用一个生产者工人和4个消费者工作者,并且以这种方式加载东西的速度比旧负载快100倍(对最终用户有更好的进度反馈)。
我同意最初的问题,即这样的东西可能会被打包,因为这是一个相对常见的事情。
更新:我提出a blog post with a simple example doing imports with Redis + Jesque。