zoukankan      html  css  js  c++  java
  • Earlybird: Twitter的实时搜索引擎 Searcher's Log

    Earlybird: Twitter的实时搜索引擎 - Searcher's Log

        Earlybird: Twitter的实时搜索引擎

        /* -*- author: Tan Menglong; email: tanmenglong_at_gmail; twitter/weibo: @crackcell; 转载请注明出处 -*- */
        Table of Contents

            1 初识Earlybird
            2 总体架构
            3 索引结构
                3.1 Term字典
                3.2 分段索引
                    3.2.1 活动分段
                    3.2.2 只读分段
            4 小结

        1 初识Earlybird

        Earlybird是Twitter的实时搜索引擎,它目前服务于Twitter的tweet的搜索。它在建立之初,Twitter的工程师就为它定下几大目标:

            低延迟高吞吐的检索能力
            低延迟高处理能力的索引构建能力。特别地,由于Twitter上的经常会有“信息爆发”,索引系统需要能够处理突发的峰值。
            索引的并发读写能力
            时效性为中心的设计:在排序算法中,发布时间是一个决定性的特征。时间越近,权值越高。进而要求,设计索引的时候要以“按时间顺序从新到旧遍历”为中心优化。

        Earlybird还有一个特点,每个用户得到的搜索结果都是“个性化”的。结果排序会根据用户的本地关系图等特征进行调整。总的来看,earlybird需要处理如下3种数据(Twitter的工程师称之为信号,signal):

            静态信号(Static Signals):在初次建立索引的时候被建入,例如:tweet的语言
            共鸣信号(Resonance Signals):随着时间的推移,需要不断动态更新的信息,例如:tweet被转发的次数
            搜索用户的信息:和用户相关的信息,在发起搜索时被用来进行个性化排序。例如,本地关系图等

        2 总体架构

        Earlybird的架构图1如下:

            图的上半部分是tweet的索引构建阶段,下半部分是检索阶段。Earlybird服务是整个系统的核心,它负责建立、检索并维护着倒排索引数据。
            索引构建过程如下:
                用户发布的新tweet会被发送到一个队列(Ingestion Pipeline)里面。在这里,tweet的文本被分词,并被加上静态信号。
                按照hash分割,tweet被分发到各个Earlybird服务上。Earlybird将tweet实时地建立索引
                同时,另外有个一个Updater服务,它推送共鸣信号到Earlybird服务,动态地更新索引。
            查询过程如下:
                用户搜索请求搜先到达Blender服务(搜索前端服务器),Blender解析请求,并将搜索用户的本地社交图谱(Local Social Graph)合并到搜索请求中,往下发送到Earlybird服务。
                Earlybird服务执行相关性计算并排序。并将排序好的tweet列表返回给Blender。
                Blender合并各个Earlybird返回的列表,并执行一些重排序(Reranking),然后返回给用户

        3 索引结构

        简要来说,一个典型的搜索引擎查询过程是:

            对query分词成term
            对每个term查询term字典,获得倒排索引拉链的指针
            获取倒排索引拉链
            合并每个term的倒排索引拉链 5)返回给用户

        当然,这中间省略了可能穿插在各个环节的不同赋权和排序的操作。这一节,我们来一起看看Earlybird索引结构中主要的2个部分: Term字典 和 倒排索引 。
        3.1 Term字典

        Term字典一般是Hash-based或者Tree-based的。Earlybird不支持需要计算query中term顺序 以及term区间 等复杂查询,所以hash-based就足够了。简单来讲,它的term字典就是一个大的hashtable。特别地,Java默认的Hashmap使用的是拉链法,不是GC友好的数据结构。所以Earlybird自己实现了开放地址的Hashtable。为了节约空间,每个Term被分配了一个唯一且单调递增的id做为key,value的数据包含:

            Term对应的倒排索引数据长度
            指向位置倒排索引数据末尾的指针

        下图是Earlybird索引结构的一个结构图:
        这里有2个小问题:

            Q: 保存倒排索引长度的目的?
                A: 在多Term的拉链归并的时候,能按照索引长度进行排序,使得长度小的先先被合并,减少不必要的索引扫描
            Q: 为什么保存的是倒排索引末尾的指针,而不是头部?
                A: 如初识Earlybird中提到,Earlybird有对发布时间做优化,即,新的tweet有较高权值,那么它理应被先遍历到。

        3.2 分段索引

        为了实现高吞吐、低延时地并发索引更新和检索服务,Earlybird采取了将分离索引的读和写的策略:每个实例维护了多个索引分段(目前是12个1),每个分段保存相对较少量的tweet(目前是223~840万1)。新增加的tweet首先被放到同一个索引分段中。这样,在任意时间,只有一个分段是在发生写操作的(我们称之为“活动分段”),而其它分段处于只读状态(称之为“只读分段”)。
        3.2.1 活动分段

        上图中,除了Term字典以外,其它3个区域都是活动分段中的部分。每个分段中有4个pool,每个pool里面有若干个slice,每个slice保存一个term的postinglist,每个postinglist里面存储着一个term的posting信息,每个posting是一个32-bit的整数:24-bit用作文档id;8-bit用作位置id,保存term在tweet中出现的位置。因为tweet有140个字符的限制,所以8-bit足够用了。slice的大小由所在pool的规格决定,4个pool分别对应21、24、27、211。建立索引时,先尝试填满21pool中的slice,如果填满,就转到24的pool的slice,以此类推。同时,term dict保留着term对应的最后一个pool的中postinglist胃部的指针,这样做的目的是,Twitter的检索顺序是按照时间顺序,从新到久查找。数据的新旧程度在排序算法中占了很大比重。 所以查询的步骤是这样的:

            对Query进行分词成term
            对每个term,查询Term dict中对应的postlinglist的指针
            通过指针,遍历最多4个pool中的slice数据,获得整个postinglist

        另外,为了加快处理速度,处于活动状态的分段数据是不会被压缩的。
        3.2.2 只读分段

        当一个活动分段写满之后,它就会被转换成一个只读分段,转换过程中,一系列优化会被执行,以提高效率:一份新的索引数据会被创建,原数据不会发生改变。当新数据创建完毕之后,原数据会被优化过的新数据替换掉。 postinglist会以1000为阈值划分为“长”和“短”的两类。短的,posting保持原样(24-bit文档id加上8-bit的位置信息),但是posting会按照时间逆序排列。对于长的postinglist,引入基于块(block-based)的来源于PForDelta2和Simple93的压缩算法。postings数据被存放在256字节定长的block中。block的最开头4个字节是未压缩的,接下来的4个字节存储了block的头信息,剩下248字节(1984bits)用来存储编码过后的变长的posting数据。原始的posting数据被转换成{d-gap, position}的对。头信息中保存着,后面的变长数据中存储了多少个{d-gap, position}对。这个n的计算方法如下:
        n(⌈(log2gapmax)⌉+⌈(log2posmax)⌉)≤1984
        因为上面n的取值范围在一个相对较小的区间,可以使用预先定义好的bit操作来加速编码解码。

        4 小结

        Earlybird给我们展示了实时搜索引擎开发中一系列工程实践的方法。其中2点我个人比较赞同:

            利用已有开源代码:开发一个新项目的时候,借鉴同领域成熟解决方案,对其进行定制。不到万不得已,不要自己重新写一套全新代码。Earlybird就是一个典型的在旧方案基础上,做了一系列tradeoff,最终达到目标的一个例子。
            从需求出发做设计:Earlybird需求阶段即明确了时间在排序中的决定性因素,进而在后续的设计中,围绕这一目标做优化。咋一看似乎绑定太紧,后续需求变动会牵一发而动全身。互联网产品应该坚持“小步快跑”的原则,在不断迭代中优化架构。

        Footnotes:

        1 Michael Busch, Krishna Gade, Brian Larson, Patrick Lok, Samuel Luckenbill, and Jimmy Lin. Earlybird: Real-Time Search at Twitter.

        2 梁斌. "PForDelta索引压缩算法的实现"

        3 V. N. Anh and A. Moffat. Inverted Index Compression using Word-Aligned Binary Codes.
  • 相关阅读:
    Java基础——clone()方法浅析
    Unity shader error: “Too many texture interpolators would be used for ForwardBase pass”
    ar 解压一个.a文件报错: xxx.a is a fat file (use libtool(1) or lipo(1) and ar(1) on it)
    How to set up "lldb_codesign" certificate!
    Unity-iPhone has Conflicting Provisioning Settings
    ETC1/DXT1 compressed textures are not supported when publishing to iPhone
    Xcode 提交APP时遇到 “has one iOS Distribution certificate but its private key is not installed”
    XCode iOS之应用程序标题本地化
    苹果电脑(Mac mini或Macbook或iMac)恢复出厂设置
    Unity 4.7 导出工程在XCode10.1上编译报错
  • 原文地址:https://www.cnblogs.com/lexus/p/2963867.html
Copyright © 2011-2022 走看看