Rails 批量数据该怎么处理才能减少耗时 (如何用 EventMachine 发送多 http 请求)

yutian · 发布于 2014年06月06日 · 最后由 mystery 回复于 2014年06月10日 · 1759 次阅读
6253

要处理业务系统一张表里面的数据,需要把每一个记录都遍历一次才能任务job,大概有10w数据,通过前台发送一个请求后台使用find_in_batches来处理,因为job任务处理有些耗时,处理这些数据的时候服务器不能接受其他请求,想把任务转为后台处理,看到网上有说线程,也有说用进程的,试了试sidekiq但是效果好些没有什么改进,就采用下面的方法:但是任务处理的很慢

User.find_in_batches(:batch_size => 2000) do |users|
  sleep(50)
  users.each { |user|
    job(usreport)
  }
end

请教各位改怎么改进才能让任务转为后台处理或者加快处理?thx! 后来觉得没有必要采用sidekiq,现在想只用rack来完成。

追问:(附job主要完成的任务) (1)判断数据库每个记录对应的两个文件pdf和png是否存在 (2)如果不存在要调用Http请求去生成对应的文件,并且必须有pdf才能生成png

为了完成上面的任务,(经过@nickcen @mystery @martin @liwei78 的指导)首先采用rack 任务,把文件不存在的查找一遍,并且标记文件文件是否存在(大概500s完成)。 之后再去遍历这个记录表,根据文件查找结果记录去请求url生成pdf和png;

目前遇到的问题: 每次发送Http请求需要等待500 - 700ms才能执行完请求生成文件,随着程序执行时间越来越长(1000ms以上),请求会花费更长时间。但是记录里面还有4w个文件没有生成的,如果一次性执行这么多请求会出现两个问题:(1)4G内存可能爆掉,请求终端;(2)总的执行时间太长,大概8-10h完成 (服务器用的是Unicorn,多进程服务器)目前采用同步发送请求的方式断断续续的生成了2w多pdf文件(内存溢出,程序经常中断,花费好几天)

理想的程序执行结果: 采用采用多线程异步异步发送Http请求缩短程序总的发送请求时间,发送完成后接收方继续执行即可(缩短总的文件生成时间,数分钟即可执行完成); 如果请求失败再次执行请求,直到生成为止;

于是找到了EventMachine 和EM-HTTP-Request 这样的异步请求处理工具: 采用如下处理过程: 先将pdf没有生成的记录获取出来,并构造出请求url放在urls数组中, 然后用EventMachine 发送请求,每次取出25个,发送完成后执行下一组url请求

代码如下:


desc 'use slice to perform  http request '
task gpdf: :environment do
  urls = []
  a = Time.now.to_i
  p 'generate     use event machine' << a.to_s
  report_ids = UsLog.find_by_sql("select distinct id  ,xml from  us_logs where  pdf ='false'  ")

  #store  all  url in urls
  report_ids.each_with_index do |rep, index|
    id = rep.id
    xml = rep.xml
    gpdf_url =URI("#{HOSTURI}/create_pdf?uuid=#{xml}")
    urls  << gpdf_url

    #record time
    if (index % 1000 == 0)
      b = Time.now.to_i
      c = b - a
      puts index
      puts c
    end
  end

  EventMachine.run {
    multi = EventMachine::MultiRequest.new
    urls.each_slice(25).with_index  do |bulk_urls ,arr_index|
      EM.next_tick do
        #bulk_urls.each {|url| multi.add(url,EventMachine::HttpRequest.new(url).get(:timeout => 5))}
        bulk_urls.each_with_index  {
            |url,index|

          # abc is the unique key of multi http request
          abc  = arr_index* 25 + index

          # add multiple requests to the multi-handler
          multi.add(abc,EventMachine::HttpRequest.new(url).get(:timeout => 5))

        }
      end
    end

    multi.callback {
      p multi.responses[:succeeded]
      p multi.responses[:failed]
      EventMachine.stop
    }
  }
end

程序执行后出现堆栈调用异常,如下:

f42cb438000-7f42cb45a000 r-xp 00000000 08:01 269491                     /lib/x86_64-linux-gnu/ld-2.15.so
7f42cb53b000-7f42cb641000 rw-p 00000000 00:00 0 
7f42cb64c000-7f42cb64d000 rw-p 00000000 00:00 0 
7f42cb64d000-7f42cb654000 r--s 00000000 08:01 3809707                    /usr/lib/x86_64-linux-gnu/gconv/gconv-modules.cache
7f42cb654000-7f42cb655000 ---p 00000000 00:00 0 
7f42cb655000-7f42cb65a000 rw-p 00000000 00:00 0                          [stack:26307]
7f42cb65a000-7f42cb65b000 r--p 00022000 08:01 269491                     /lib/x86_64-linux-gnu/ld-2.15.so
7f42cb65b000-7f42cb65d000 rw-p 00023000 08:01 269491                     /lib/x86_64-linux-gnu/ld-2.15.so
7ffff0940000-7ffff0962000 rw-p 00000000 00:00 0 
7ffff09b2000-7ffff09b3000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]


[NOTE]
You may have encountered a bug in the Ruby interpreter or extension libraries.
Bug reports are welcome.
For details: http://www.ruby-lang.org/bugreport.html

不知为何会出现上面的错误,并且IDEshell中和系统的终端执行结果也不一样; Ubuntu自带系统终端中没有错误输出,最终callback中打印的信息为null IDE(rubymine)的shell输出如上,提示段转储错误

最终请求没有发送成功,接收方的server没有任何日志输出,请教做过类似多请求并发处理的大大们给些指导建议,再次感谢! @hooopo @pzgz @skandhas @luikore @zw963

共收到 15 条回复
1105

@yutian 改成异步就可以了

SomeWorker. perform_async
3469

sleep 去掉。

10w的数据,应该用sql了。

我是用 resque + resque-state 来做大数据任务,可以看到完成度。

6253

#1楼 @martin 看过sidekiq的文档后,我用的是

HardWorker.perform_async(user.id) 

来处理,效果不够显著,设置了参数后一直在retry,执行了一夜也没执行完,有种虐心的感觉。。

6253

#2楼 @liwei78 担心内存溢出看文档上有sleep就比葫芦画瓢的加上了, 因为 遍历的时候还要执行http请求好像没法用sql直接处理 (http://api.rubyonrails.org/v2.3.8/classes/ActiveRecord/Batches/ClassMethods.html

1105

@yutian 你的用户查询没必要放在controller里面的,在worker里面遍历用户即可,controller仅仅是调起worker,这个压根不会花时间。

然后你的worker可以多跑几个threads。至于job本身的优化,这个只能根据你的业务优化了。

6253

#5楼 @martin 原来sidekiq是这样用的 一语中的 改下试试 😄谢谢

96

你应该先说明你的job想要干什么,依赖哪些数据列。没准直接把表dump成csv来处理更快了。

6253

#7楼 @nickcen 这边job主要用来判断数据库每个记录对应的两个文件pdf和png是否存在,如果不存在要调用http请求去生成对应的文件,并且必须有pdf才能生成png

96

如何判断pdf是否存在,是基于某个数据列存不存在?如果是,那就直接把这一列查出来就好了。不要用ActiveRecord生成对象。

6253

#9楼 @nickcen 是根据磁盘存储文件来判断文件是否存在,若仅仅是根据数据列就非常容易实现了

22014d

#10楼 @yutian 那你应该给是否生成了pdf 加个一个字段来记录,再写个脚本再更新一下现有记录,这样一劳永逸呀亲!

6253

#11楼 @mystery 因为工程遗留问题文件丢失了一部分,只好把所有的记录再读一遍,用File.exists?(filename) 来判断文件存在

96

@yutian 那就把确定路径所需要的字段取出来就好了,不要生成ActiveRecord记录,这样慢。

22014d

#12楼 @yutian 先写个rake 查找一下 filename 然后记录下来, 这样就不用每次 File.exists?(filename) 时间(IO)上会节约好多, 以后再处理 job 逻辑上也好优化。

6253

#13楼 @nickcen #14楼 @mystery
谢谢两位给的指导 正在改进

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册