免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 5376 | 回复: 3
打印 上一主题 下一主题

[文本处理] [原]如何使用 aria2c + taskset + mawk 实现并发多任务、多线程下载并快速分割XML [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2013-07-20 22:23 |只看该作者 |倒序浏览
本帖最后由 ailms 于 2013-07-22 10:32 编辑

原文链接 :http://blog.ailms.me/2013/07/20/ ... wk-for-xml-job.html

完整脚本 :http://static2.ailms.me/scripts/tuan-proc.tar.gz

受人之托写一个脚本,需求如下

1、定时从多个团购网站下载 XML (全量,有些超巨大)

2、要并发下载+实时处理。也就是不能一个下载完处理后,再下载第2个,再处理 (话说这个要求是楼主自己提的 ..)

3、对下载的 XML 按照每文件1000个商品的数量进行分割。XML 的格式如下 :<goods id=n></goods> 为一个商品

<?xml version=”1.0″ encoding=”UTF-8″?>
<data>
<apiversion><![CDATA[3.0]]></apiversion>
<site_name><![CDATA[满座网]]></site_name>
<goodsdata>
<goods id=”1″>
<xxxx>
<yyyy>
</goods>
<goods id=”2″>
<xxxx>
<yyyy>
</good>
….


针对第一点 :

如何快速下载。wget 是传统的单线程,所以楼主搜索了一下,有一个 aria2 ,看了一下 man ,非常满意。实际测试速度也很不错。

另外这个东西功能过于强大,强大到可以支持 session 和 daemon 模式,具体楼主还没研究,目前只是简单使用,其他等待以后吧。

另外有一个要注意的,因为不是无时无刻这些 XML 都在更新,所以并不需要每次都全量下载,所以楼主用了 HEAD 方法记录这些

XML 文件的 ETag、Mtime、Size 并存成文件,下次下载前检查一下这3项信息,并与磁盘上实际的文件大小比较,如果相同则不下载。

  1. declare -a aria2cOpts=()

  2. aria2cOpts+=("--log $tempDir/$i.log --log-level=info")

  3. aria2cOpts+=("--stop=$(($singleURLTimeout * 60)) --max-concurrent-downloads=10")

  4. aria2cOpts+=("--auto-file-renaming=false --continue")

  5. #aria2cOpts+=("--http-accept-gzip=true --enable-http-keep-alive=true")

  6. aria2cOpts+=("-d $tempDir/ -o ${i}.xml --quiet")

  7. aria2c ${aria2cOpts[*]} $url 2>$logDir/${i}/${i}.err
复制代码
针对第二点 :

后台多任务这个简单,但多少才是合适呢? 这个楼主做了测试,先是用 wget 下载一个 500MB 的 XML 文件,记录下耗时。

由于这个时候没有指定 CPU cores ,所以默认是bind 在任意一个 Cores 都可以。接下来再用 taskset 启动相同的 wget 命令,

但这次不同的是,只bind 到一个 core 。经过多次测试,发现时间节省了 50% ! 所以楼主用下面的代码实现了一个功能,就是

每一轮取出跟 cpu core 数量相同的下载任务,然后分配到不同的 cpu core 上,再并发多任务下载。

  1. cpuCoreNum=$(cat /proc/cpuinfo | grep -c '^processor[[:space:]]')

  2. batchDownloadTask=$(egrep -v '^#|^[[:space:]]* $1 | xargs -n $cpuCoreNum)

  3. count=0

  4. while read line ; do

  5.    (
  6.       <spawn arai2c job>

  7.     ) &

  8.    backgroundJobPid=$!

  9.    sleep 0.5

  10.    taskset -c -p $count $backgroundJobPid &>/dev/null

  11.    info_log "$i" "bind pid [$backgroundJobPid] to cpu #$(taskset -c -p $backgroundJobPid 2>/dev/null | awk -F '[ :]+' '{print $NF}')"

  12.   ((count++))

  13. done  <<< "$batchDownloadTask"
复制代码
针对第三点 :

【方法1】一开始是想用 grep + sed 的。就是找到所有 <goods id=n></goods> 行的行号,并把每个 pair 凑成一行。然后

            每1000个pair做成一个文件,再取出文件的第一个和最后一个数字,这2个数字就是代表1000个商品的范围,然

           后动态生成 sed  命令,每次取出指定的范围。但按照这个想法实现后,发现性能太差了,因为假设有10w 个商品,

           那么需要切割为10次, 意味着需要访问10次 XML 文件,性能自然不行了,实际测试也证明了这点:将近10分钟 ,

          所以不能接受

【方法2】因为第一种方法的瓶颈是在于 sed 多次访问XML 文件,所以这次的目的就是如何减少对 XML 文件访问的次数。前

             一种方法之所以需要多次访问,是因为没有找到标记“这里是第1000个商品”的类似信息,如果我们能提供这种信息

             ,那么就可以使用操作系统自带的 csplit 命令来一次性分割了。所以这次放弃 grep + sed 的组合,先用 awk 读取文

            件,每读取1000 个 </goods> 行就打印出一个标记行“—cut-here—“ 。在所有标记行打印完毕后,用 csplit 命令分

            割,分割之前需要 grep 检查一共有多少个标记行。

            

【方法3】第2种方法比起第1种是快了不少,但扔因为 cpslit 的使用而显得不够轻巧,因为 awk 已经可以识别出每1000 个

             </goods> 行的所在,所以直接把遇到第n*1000个 <\/goods> 行的内容放入字符串,当遇到第 n*1000个</goods>

            行时就直接 print 到文件,这样可以最大程度的减少 IO 次数。但很不幸,这个方法失败了,因为字符串太大了,导致

            mawk 执行非常久,而且 cpu 的消耗达到了几乎 100%

【方法4】既然方法3的字符串存储行不通,那么就直接输出到文件。每次打开一个文件,并将当前行输出到该文件,当遇到

             第n*1000个</goods> 行时,就关闭文件,同时设置一个新的文件名,继续上面的循环。这个方法经过测试是最

             快的,+500MB的文件,在30s内可以完成切割,而且几乎不占 cpu 和内存。

————————————————————— 中途休息 —————————————————————————-

楼主用的是 Ubuntu 12.04 ,默认安装的是 mawk 。但对方是 centOS 5.8 ,安装的是 gawk ,所以当楼主把脚本给到对方

测试时,发现很慢,通过检查发现是 awk 的版本不同。所以让对方安装了 mawk ,则速度一下子就提高了非常多。楼主当

即进行了自测,发现结果非常惊人,mawk 是30s 左右,而 gawk 竟然要1分27秒,相差了1.5倍左右。

mawk 是目前已知的性能最高的 awk的实现。怪不得 Ubuntu 12.04 默认安装的是 mawk

下面是用于切割 XML 文件的 shell 脚本完整代码,整个脚本由于比较大,不好贴出,请点击这里下载

  1. #!/bin/bash

  2. mawk -v _result_dir=$2  -v _flag_line="$3" -v _goods_per_file=$4 '

  3.   BEGIN { count=0 ; batch=0 ; }

  4.   {
  5.     output=_result_dir"/cut."sprintf("%03d",batch)

  6.     output_cmd="cat >>"output

  7.     if ( $0 !~ _flag_line ) {

  8.          print $0 | output_cmd

  9.     } else {

  10.          print $0 | output_cmd

  11.          count++

  12.          if ( count % _goods_per_file == 0 ) {

  13.               fflush(output_cmd)

  14.               close(output_cmd)

  15.               batch++
  16.          }

  17.    }

  18. }' $1

  19. cd $2

  20. lastCutFile=$(ls cut.??? | tail -n 1)

  21. for i in cut.000 $lastCutFile ; do

  22.   sed -r -n '/<goods id=/,/<\/goods>/p' $i > $i.new

  23.   mv $i.new $i

  24. done
复制代码
脚本整体输出如下(一共 2.3GB 的 XML ,5个) 。由于楼主的 VPS 是在米国,所以下载速度不如国内,如果是在国内执行,时间会比下面的小

论坛徽章:
15
2015年辞旧岁徽章
日期:2015-03-03 16:54:15双鱼座
日期:2015-01-15 17:29:44午马
日期:2015-01-06 17:06:51子鼠
日期:2014-11-24 10:11:13寅虎
日期:2014-08-18 07:10:55酉鸡
日期:2014-04-02 12:24:51双子座
日期:2014-04-02 12:19:44天秤座
日期:2014-03-17 11:43:36亥猪
日期:2014-03-13 08:13:51未羊
日期:2014-03-11 12:42:03白羊座
日期:2013-11-20 10:15:18CU大牛徽章
日期:2013-04-17 11:48:45
2 [报告]
发表于 2013-07-21 13:25 |只看该作者
谢谢分享。并发和先比较再下载的主意不错。

论坛徽章:
5
2015年辞旧岁徽章
日期:2015-03-03 16:54:152015年迎新春徽章
日期:2015-03-04 09:50:282015年亚洲杯之朝鲜
日期:2015-03-13 22:47:33IT运维版块每日发帖之星
日期:2016-01-09 06:20:00IT运维版块每周发帖之星
日期:2016-03-07 16:27:44
3 [报告]
发表于 2013-07-21 14:17 |只看该作者
回复 1# ailms


    不错,学习。

论坛徽章:
1
辰龙
日期:2014-05-22 11:38:58
4 [报告]
发表于 2013-07-22 08:54 |只看该作者
好,学习了
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP