国际搜索离线系统优化之一 —— 全局排序优化

标签: 分布式技术 Hadoop 全局排序 | 发表时间:2014-03-04 16:18 | 作者:梦翔
出处:http://www.searchtb.com

总觉得阶段性的总结是个好习惯,很多自己做的事情,如果不及时总结一下,过一段时间就忘记了,当要用到时,又需要花费较多的时间去重新熟悉。于是决定抽点时间总结一下以前对国际搜索离线系统做的一些优化(这里说的国际搜索,主要指AE、SC和SC店铺,AE即AliExpress,SC即Sourcing,这些优化对这几个应用都是通用的),不仅起到一个备忘的作用,如果能给读者带来一些启发,想必也是极好的。

既然是搜索离线系统相关,我们就先看一下国际搜索全量流程的几个主要环节,如图1所示。

全量流程

图1. 全量流程

1)dump,将数据从数据库读出来,写入hbase,只有做大全量的时候才会全量dump数据库,一般情况下每天只需跑一次小全量,数据库中数据的更新会以增量的方式更新hbase。

2)join,读取hbase,做多表join,生成一条条doc,一条doc包含了一条产品的全部字段。

3)global sort,即全局排序,按产品全局分global_score对产品进行全局排序,生成的单个文件内部并不要求有序。

4)abuild,读取全局排序后生成的文件,构建索引,生成的索引会存储在HDFS上。

5)dispatch,将索引从HDFS上分发到对应的search机器上。

6)switch,切换索引、程序、配置和算法词典,新索引上线,对外提供服务。

这次先总结一下全局排序优化,任何项目或需求都有相应的背景,我们的离线计算中为何要做全局排序?

说到这个,又引出了分层检索,早些时候,国际站搜索引擎对外提供服务时,在处理每个搜索请求时,都会查询所有的segment,但其实对于每个请求,都只需返回一定数量的结果集,因此,查询所有的segment并非必要,只会带来性能上的损失。于是,分层检索就在千呼万唤中出来了。

何谓分层检索,顾名思义,就是只查询一定数量的segment,当结果集够了就不再继续查询,这对搜索引擎查询性能的优化是显而易见的。

但这里存在一个问题,就是对于卖家发布的产品,质量是良莠不齐的,我们需要把质量好的优先搜索出来,所以前面segment的产品质量要高于后面的segment,否则一些质量高的展品就没有展示机会了。比如,我们有3个segment,seg_1, seg_2, seg_3,那么seg_1中的产品质量就要比seg_2中的产品质量高,seg_2中的产品质量要比seg_3中的产品质量高,在每个segment内部并不做要求。

判断产品质量好坏的标准是什么呢?我们引入了一个全局分global_score,每条产品的global_score都是离线计算好的,以此作为分层检索的依据。

如图1所示,在搜索引擎的离线计算中,有个多表join的环节,在多表join的过程中会有一些业务逻辑的计算,global_score就是在这个阶段计算出来的。有了global_score,我们就可以对产品做全局排序了。假如排序之后我们生成3个文件,part_1, part_2, part_3,就要求part_1中每条doc的global_score要高于part_2中的每条doc,part_2之于part_3亦如此,但每个part内部并不要求有序。在后面建索引的过程中,会有一个保序逻辑,以此保证多个segment之间的有序。

全局排序怎么做呢?由于数据量大,我们各个应用的离线计算任务基本上都是运行在hadoop集群上的,全局排序亦如此。要达到上述的效果,即各个partition之间是按global_score有序的,我们采用的方案是:首先对数据进行采样,按global_score进行分区,将定义分区的键写入_partitions文件,再实现自定义的TotalOrderPartitioner(这里实现自定义的TotalOrderPartitioner是为了在输出的单个文件内部将同一家公司的产品聚合在一起,即按company_id聚合,从而大大提高输出文件的压缩比,显著缩短了后面abuild构建索引的运行时间),进行全局排序。采样的核心思想是只查看一小部分键,获得键的近似分布,并由此构建分区。

这里有必要先提一下列的概念,由于单台search能承载的索引量有限,所以数据量大时,需要对数据进行分列,使所有数据尽量均匀分布到不同的列上。比如SC有19列,采用的做法就是根据product_id % 19将全部数据分布到19列上。在做多表join的之后,数据的分列就已经做好了。因此全局排序是对多列的数据分别进行全局排序。

在分层检索项目上线到SC BT集群(预发布环境)时,全局排序需要80min才能运行完成,经分析,大部分的时间耗在采样上面。看了代码,发现每列的全局排序都对应一个job,SC有19列数据,就跑19个job分别对每列数据进行全局排序。排序之前先采样,采样器是在客户端运行的,因此,限制分片的下载数量以加速采样器的运行就显得尤为重要。在优化之前的代码实现中,每个job都是读取对应列的数据,自己独立采样的,而且多个job是串行采样。因此,一个可行的优化方案就是多个job并行采样,但由于我们的产品数据是分列存储的,每一列的数据量也足够大。比如SC现在3.6亿的数据量,单列的数据就接近2千万,因此其实每一列产品global_score的分布是基本一致的,所以,我们是否可以只对一列数据进行采样,然后所有job都共享这一个样本呢?这样就不仅能大大缩短采样时间,而且也不会引入并行的复杂性。答案是可行的。

简单的说,全局排序优化的基本思想,就是根据数据的分布特点,使多列数据的多个全局排序job共享同一个样本。

下面我们来看一下优化后的代码实现:

Vector vecRunningJob = new Vector(build_num);
Vector vecJobClient = new Vector(build_num);
for (int j = 0; j < build_num; j++) {
    job.setJobName("Doc Sort job" + String.valueOf(j));
    job.setInt("dc.sort.jobindex", j);

    Vector vecInput = fileGenerator.getInPutFiles(j, build_num);
    JobConf newjob = makeJob(job, inputPath, vecInput, outputPath + "/" + j, aggregateField); // Make a job for each column

    JobClient jc = new JobClient(newjob);
    vecJobClient.add(jc);
    vecRunningJob.add(jc.submitJob(newjob));
}

其中build_num表示列数,从上面的代码可以看出,对每列数据都会调用makeJob方法,然后提交任务进行全局排序。注意这里调用makeJob方法和提交任务是串行的,不过任务提交后是并行跑的。

​我们再看一下makeJob方法的实现:

private static JobConf makeJob(JobConf basejob, String inputPath,
        Vector vecInPutFile, String outPutPath, String aggregateField) throws Exception {
    JobConf conf = new JobConf(basejob);
    conf.setJarByClass(DCSortMain.class);

    for (int i = 0; i < vecInPutFile.size(); i++) {
        FileInputFormat.addInputPath(conf, new Path(vecInPutFile.get(i)));
    }

    Path outputDir = new Path(outPutPath);
    FileOutputFormat.setOutputPath(conf, outputDir);

    conf.setMapOutputKeyClass(DCText.class);
    conf.setMapOutputValueClass(DCText.class);

    conf.setOutputKeyClass(DCText.class);
    conf.setOutputValueClass(DCText.class);

    conf.setMapperClass(IdentityMapper.class);
    conf.setReducerClass(IdentityReducer.class);

    conf.setInputFormat(DCTextInputFormat.class);
    conf.setOutputFormat(DCTextOutputFormat.class);

    conf.set("mapred.output.compress", "true");
    conf.set("mapred.output.compression.codec", "org.apache.hadoop.io.compress.GzipCodec");

    conf.setOutputKeyComparatorClass(DCText.AggregateFieldComparator.class); // Sort numbericly by desc

    conf.setNumReduceTasks(conf.getInt("dc.sort.reduce_num", 1));

    sample(conf, inputPath); // Sample before global sort

    return conf;
}

可见,在做好相关设置后,makeJob中会调用sample方法进行采样,也就是说,其实针对每一列的makeJob都会调用sample方法。

再来看看sample方法的实现:

private static void sample(JobConf conf, String inputPath) throws IOException, URISyntaxException {
    int jobIndex = 0;
    Path partitionFile = new Path(inputPath, jobIndex + "_partitions");

    conf.setPartitionerClass(MyTotalOrderPartitioner.class);
    conf.set("total.order.partitioner.natural.order", "false");
    MyTotalOrderPartitioner.setPartitionFile(conf, partitionFile);

    if (!sampleDone) {
        LOG.info("sample start ...");
        MyInputSampler.Sampler<LongWritable, DCText> sampler =
            new MyInputSampler.RandomSampler<LongWritable, DCText>(1, 20000, 10);
        MyInputSampler.writePartitionFile(conf, sampler);
        LOG.info("sample end ...");

        sampleDone = true;
    }

    // Add to DistributedCache
    URI partitionUri = new URI(partitionFile.toString() + "#" + jobIndex + "_partitions");
    DistributedCache.addCacheFile(partitionUri, conf);
    DistributedCache.createSymlink(conf);
} 

可以看出,我们引入了一个布尔变量sampleDone对采样进行了控制,只在第1次调用makeJob方法时才执行采样操作,后面的创建的job都不再进行采样,而是与第1个job共享同一个_partitions文件,载入到自己使用的分布式缓存中,供后面的全局排序使用。sampleDone定义如下:

private static boolean sampleDone = false; 

顺便提一下采样操作,hadoop内置的采样器有3个:

1)RandomSampler,以指定的采样率均匀地从一个数据集中选择样本;

2)SplitSampler,只采样一个分片中的前n个记录;

3)IntervalSampler,以一定的间隔定期从划分中选择键,对于已排好序的数据来说是一个更好的选择。

RandomSampler是优秀的通用采样器,我们最终也是选择RandomSampler,因为虽然使用另外两个采用器,采样时间更短,但最终数据分布却很不均匀,只有RandomSampler才能达到预期效果。同时,我们将采样率设置为1,最大样本数设置为20000,最大分区设置为10。最大样本数和最大分区只需满足其一,即停止采样。可以通过调整RandomSampler的这些参数达到不同的采样效果。

优化版本上线SC BT之后,全局排序的运行时间从80min降到了30min,缩短了50min。正式环境由于hadoop集群更加强大,全局排序的运行时间更短。


相关 [国际 搜索 离线] 推荐:

国际搜索离线系统优化之一 —— 全局排序优化

- - 搜索技术博客-淘宝
总觉得阶段性的总结是个好习惯,很多自己做的事情,如果不及时总结一下,过一段时间就忘记了,当要用到时,又需要花费较多的时间去重新熟悉. 于是决定抽点时间总结一下以前对国际搜索离线系统做的一些优化(这里说的国际搜索,主要指AE、SC和SC店铺,AE即AliExpress,SC即Sourcing,这些优化对这几个应用都是通用的),不仅起到一个备忘的作用,如果能给读者带来一些启发,想必也是极好的.

淘宝主搜索离线集群完成hadoop2.0升级

- - 搜索技术博客-淘宝
搜索离线dump集群(hadoop&hbase)2013进行了几次重大升级:. 第一阶段,主要是升级hdfs为2.0版本,mapreduce仍旧是1.0;同时hbase也进行了一次重大升级(0.94.5版本),hive升级到0.9.0;. 第二阶段,主要升级mapreduce到2.0版本即(YARN),hive升级到0.10.0,在13年年底的时候对hbase进行了一次小版本升级;.

如何用 Everything 实现离线搜索并找到对应储存设备

- - 小众软件 - Appinn
如果你有多块移动硬盘,想从中找到某个文件该怎么做呢. 不用这么麻烦,最新测试版的 Everything 已经实现了离线搜索并且可以快速找到文件所在硬盘. 感谢 xbeta 帮我向作者反馈的 此建议, 1.3 测试版的最新版本终于将文件列表名称添加进了排序栏,这样就实现了使用 Everything 离线搜索(不连接移动硬盘)多个移动硬盘文件, 并快速定位文件所在移动硬盘.

阿里如何实现秒级百万TPS?搜索离线大数据平台架构解读

- -
阿里妹导读:搜索离线数据处理是一个典型的海量数据批次/实时计算结合的场景,阿里搜索中台团队立足内部技术结合开源大数据存储和计算系统,针对自身业务和技术特点构建了搜索离线平台,提供复杂业务场景下单日批次处理千亿级数据,秒级实时百万TPS吞吐的计算能力. 一个典型的商品搜索架构如下图所示,本文将要重点介绍的就是下图中的离线数据处理系统(Offline System).

离线存储

- - 崔凯,前端开发
开发WebApp时,遇到一个问题:. 如果把页面配置到服务器上,当服务器挂掉或者用户离线的时候,那这个App也就没法工作了. 而当我把页面打包进App里面,又有一个新问题,更新不方便. 部门就此组织了一次《application cache》的相关讨论. 使用离线存储,来解决上述问题:. 这是一个打包进App的应用地址,阅读源代码可以看到,html标记上给了一个manifest配置文件.

[来自异次元] WikiTaxi 离线中英文维基百科数据库搜索阅读工具绿色免费版下载 (可装进U盘随处使用查询)

- Surflien - 异次元软件世界
        维基百科 (Wikipedia) 对很多人来说绝对是一个知识的宝库. 维基百科拥有海量权威的资料供我们查询,也许我们每个人都梦想着把维基百科下载下来实现离线查询. 甚至装在U盘里,以方便随时随地查询. 对于学习或是写论文等帮助极大,离线的维基百科不仅方便至极,还能大大节约时间.         WikiTaxi 是一款免费的离线维基百科阅读器.

深度搜索

- - 译言最新精选
译者: HorseHour 原文地址: streamhacker.com. 当我们准备发布 Weotta时,我们已经为如何描述它犯了难. 我们使用了机器学习和自然语言处理吗. 我们最终觉得“深度搜索”是对我们工作最贴切的描述,它是一个超越了基本文本搜索的复杂搜索系统的简洁描述. 无需赘言,不管怎么看,我们都不是这个领域唯一的一家公司;谷歌和很多其他公司都在对深度搜索的各个方面进行研究.

Java国际化:BreakIterator

- - 编程语言 - ITeye博客
【译自:http://tutorials.jenkov.com/java-internationalization/breakiterator.html , 不准确别怪我】. java.text.BreakIterator 类用来查找不同语言中的字符、单词和句子的边界. 因为不同的语言有不同的字、单词和句子的边界,所以只是查找空格、逗号、句号、分号和冒号是不够的.

搜索的未来

- Levi - 月光博客
笔者认为,未来的搜索有两个趋势:个性化,社会化. (注:本文给出的很多链接需要特殊方式才可以访问,请自行解决).   从google诞生的那一天起,google的搜索本质上并没有什么变化,依旧是:一个大大的搜索框,你敲进去几个词,google给出一些相关的网页. 不同的人对于同一个关键词所期待的搜索结果可能有很大差别啊.

google搜索技巧

- - ITeye博客
搜索的词语是网页中链接内包含的关键词(可使用多个关键词). 搜索的词语是网页标题中包含的关键词(可使用多个关键词). 所搜索的文件一个特定的格式. 搜索的词语是网页中链接内包含的关键词. 搜索的词语是网页内文包含的关键词. inurl:google.com 开源. 所进行的搜索在指定的域名或网站内.