日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

美团广告实时索引的设计与实现

發(fā)布時(shí)間:2024/7/5 编程问答 47 豆豆
生活随笔 收集整理的這篇文章主要介紹了 美团广告实时索引的设计与实现 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

背景

在線廣告是互聯(lián)網(wǎng)行業(yè)常見的商業(yè)變現(xiàn)方式。從工程角度看,廣告索引的結(jié)構(gòu)和實(shí)現(xiàn)方式直接決定了整個(gè)系統(tǒng)的服務(wù)性能。本文以美團(tuán)的搜索廣告系統(tǒng)為藍(lán)本,與讀者一起探討廣告系統(tǒng)的工程奧秘。

領(lǐng)域問題

廣告索引需具備以下基本特性:

  • 層次化的索引結(jié)構(gòu)。
  • 實(shí)時(shí)化的索引更新。
  • 層次投放模型

    一般地,廣告系統(tǒng)可抽象為如下投放模型,并實(shí)現(xiàn)檢索、過濾等處理邏輯。

    該層次結(jié)構(gòu)的上下層之間是一對多的關(guān)系。一個(gè)廣告主通常創(chuàng)建若干個(gè)推廣計(jì)劃,每個(gè)計(jì)劃對應(yīng)一個(gè)較長周期的KPI,比如一個(gè)月的預(yù)算和投放地域。一個(gè)推廣計(jì)劃中的多個(gè)推廣單元分別用于更精細(xì)的投放控制,比如一次點(diǎn)擊的最高出價(jià)、每日預(yù)算、定向條件等。廣告創(chuàng)意是廣告曝光使用的素材,根據(jù)業(yè)務(wù)特點(diǎn),它可以從屬于廣告主或推廣計(jì)劃層級。

    實(shí)時(shí)更新機(jī)制

    層次結(jié)構(gòu)可以更準(zhǔn)確、更及時(shí)地反應(yīng)廣告主的投放控制需求。投放模型的每一層都會定義若干字段,用于實(shí)現(xiàn)各類投放控制。廣告系統(tǒng)的大部分字段需要支持實(shí)時(shí)更新,比如審核狀態(tài)、預(yù)算上下線狀態(tài)等。例如,當(dāng)一個(gè)推廣單元由可投放狀態(tài)變?yōu)闀和顟B(tài)時(shí),若該變更沒有在索引中及時(shí)生效,就會造成大量的無效投放。

    業(yè)界調(diào)研

    目前,生產(chǎn)化的開源索引系統(tǒng)大部分為通用搜索引擎設(shè)計(jì),基本無法同時(shí)滿足上述條件。

    • Apache Lucene
      • 全文檢索、支持動態(tài)腳本;實(shí)現(xiàn)為一個(gè)Library。
      • 支持實(shí)時(shí)索引,但不支持層次結(jié)構(gòu)。
    • Sphinx
      • 全文檢索;實(shí)現(xiàn)為一個(gè)完整的Binary,二次開發(fā)難度大。
      • 支持實(shí)時(shí)索引,但不支持層次結(jié)構(gòu)。

    因此,廣告業(yè)界要么基于開源方案進(jìn)行定制,要么從頭開發(fā)自己的閉源系統(tǒng)。在經(jīng)過再三考慮成本收益后,我們決定自行設(shè)計(jì)廣告系統(tǒng)的索引系統(tǒng)。

    索引設(shè)計(jì)

    工程實(shí)踐重點(diǎn)關(guān)注穩(wěn)定性、擴(kuò)展性、高性能等指標(biāo)。

    設(shè)計(jì)分解

    設(shè)計(jì)階段可分解為以下子需求。

    實(shí)時(shí)索引

    廣告場景的更新流,涉及索引字段和各類屬性的實(shí)時(shí)更新。特別是與上下線狀態(tài)相關(guān)的屬性字段,需要在若干毫秒內(nèi)完成更新,對實(shí)時(shí)性有較高要求。

    用于召回條件的索引字段,其更新可以滯后一些,如在幾秒鐘之內(nèi)完成更新。采用分而治之的策略,可極大降低系統(tǒng)復(fù)雜度。

    • 屬性字段的更新:直接修改正排表的字段值,可以保證毫秒級完成。
    • 索引字段的更新:涉及更新流實(shí)時(shí)計(jì)算、倒排索引等的處理過程,只需保證秒級完成。

    此外,通過定期切換全量索引并追加增量索引,由索引快照確保數(shù)據(jù)的正確性。

    層次結(jié)構(gòu)

    投放模型的主要實(shí)體是廣告主(Advertiser)、推廣計(jì)劃(Campaign)、廣告組(Adgroup)、創(chuàng)意(Creative)等。其中:

    • 廣告主和推廣計(jì)劃:定義用于控制廣告投放的各類狀態(tài)字段。
    • 廣告組:描述廣告相關(guān)屬性,例如競價(jià)關(guān)鍵詞、最高出價(jià)等。
    • 創(chuàng)意:與廣告的呈現(xiàn)、點(diǎn)擊等相關(guān)的字段,如標(biāo)題、創(chuàng)意地址、點(diǎn)擊地址等。

    一般地,廣告檢索、排序等均基于廣告組粒度,廣告的倒排索引也是建立在廣告組層面。借鑒關(guān)系數(shù)據(jù)庫的概念,可以把廣告組作為正排主表(即一個(gè)Adgroup是一個(gè)doc),并對其建立倒排索引;把廣告主、推廣計(jì)劃等作為輔表。主表與輔表之間通過外鍵關(guān)聯(lián)。

  • 通過查詢條件,從倒排索引中查找相關(guān)docID列表。
  • 對每個(gè)docID,可從主表獲取相關(guān)字段信息。
  • 使用外鍵字段,分別獲取對應(yīng)輔表的字段信息。
  • 檢索流程中實(shí)現(xiàn)對各類字段值的同步過濾。

    可靠高效

    廣告索引結(jié)構(gòu)相對穩(wěn)定且與具體業(yè)務(wù)場景耦合較弱,為避免Java虛擬機(jī)由于動態(tài)內(nèi)存管理和垃圾回收機(jī)制帶來的性能抖動,最終采用C++11作為開發(fā)語言。雖然Java可使用堆外內(nèi)存,但是堆外堆內(nèi)的數(shù)據(jù)拷貝對高并發(fā)訪問仍是較大開銷。項(xiàng)目嚴(yán)格遵循《Google C++ Style》,大幅降低了編程門檻。

    在“讀多寫少”的業(yè)務(wù)場景,需要優(yōu)先保證“讀”的性能。檢索是內(nèi)存查找過程,屬于計(jì)算密集型服務(wù),為保證CPU的高并發(fā),一般設(shè)計(jì)為無鎖結(jié)構(gòu)。可采用“一寫多讀”和延遲刪除等技術(shù),確保系統(tǒng)高效穩(wěn)定運(yùn)轉(zhuǎn)。此外,巧妙利用數(shù)組結(jié)構(gòu),也進(jìn)一步優(yōu)化了讀取性能。

    靈活擴(kuò)展

    正排表、主輔表間的關(guān)系等是相對穩(wěn)定的,而表內(nèi)的字段類型需要支持?jǐn)U展,比如用戶自定義數(shù)據(jù)類型。甚至,倒排表類型也需要支持?jǐn)U展,例如地理位置索引、關(guān)鍵詞索引、攜帶負(fù)載信息的倒排索引等。通過繼承接口,實(shí)現(xiàn)更多的定制化功能。

    邏輯結(jié)構(gòu)

    從功能角度,索引由Table和Index兩部分組成。如上圖所示,Index實(shí)現(xiàn)由Term到主表docID的轉(zhuǎn)換;Table實(shí)現(xiàn)正排數(shù)據(jù)的存儲,并通過docID實(shí)現(xiàn)主表與輔表的關(guān)聯(lián)。

    分層架構(gòu)

    索引庫分為三層:

  • 接口層:以API方式對外提供索引的構(gòu)建、更新、檢索、過濾等功能。
  • 能力層:實(shí)現(xiàn)基于倒排表和正排表的索引功能,是系統(tǒng)的核心。
  • 存儲層:索引數(shù)據(jù)的內(nèi)存布局和到文件的持久化存儲。
  • 索引實(shí)現(xiàn)

    本節(jié)將自底向上,從存儲層開始,逐一描述各層的設(shè)計(jì)細(xì)節(jié)和挑戰(zhàn)點(diǎn)。

    存儲層

    存儲層負(fù)責(zé)內(nèi)存分配以及數(shù)據(jù)的持久化,可使用mmap實(shí)現(xiàn)到虛擬內(nèi)存空間的映射,由操作系統(tǒng)實(shí)現(xiàn)內(nèi)存與文件的同步。此外,mmap也便于外部工具訪問或校驗(yàn)數(shù)據(jù)的正確性。

    將存儲層抽象為分配器(Allocator)。針對不同的內(nèi)存使用場景,如對內(nèi)存連續(xù)性的要求、內(nèi)存是否需要回收等,可定制實(shí)現(xiàn)不同的分配器。

    以下均為基于mmap的各類分配器,這里的“內(nèi)存”是指調(diào)用進(jìn)程的虛擬地址空間。實(shí)際的代碼邏輯還涉及復(fù)雜的Metadata管理,下文并未提及。

    簡單的分配策略

    • LinearAllocator

      • 分配連續(xù)地址空間的內(nèi)存,即一整塊大內(nèi)存;當(dāng)空間需要擴(kuò)展時(shí),會采用新的mmap文件映射,并延遲卸載舊的文件映射。
      • 新映射會導(dǎo)致頁表重新裝載,大塊內(nèi)存映射會導(dǎo)致由物理內(nèi)存裝載帶來的性能抖動。
      • 一般用于空間需求相對固定的場景,如HashMap的bucket數(shù)組。
    • SegmentAllocator

      • 為解決LinearAllocator在擴(kuò)展時(shí)的性能抖動問題,可將內(nèi)存區(qū)分段存儲,即每次擴(kuò)展只涉及一段,保證性能穩(wěn)定。
      • 分段導(dǎo)致內(nèi)存空間不連續(xù),但一般應(yīng)用場景,如倒排索引的存儲,很適合此法。
      • 默認(rèn)的段大小為64MB。

    集約的分配策略

    頻繁的增加、刪除、修改等數(shù)據(jù)操作將導(dǎo)致大量的外部碎片。采用壓縮操作,可以使占用的內(nèi)存更緊湊,但帶來的對象移動成本卻很難在性能和復(fù)雜度之間找到平衡點(diǎn)。在工程實(shí)踐中,借鑒Linux物理內(nèi)存的分配策略,自主實(shí)現(xiàn)了更適于業(yè)務(wù)場景的多個(gè)分配器。

    • PageAllocator
      • 頁的大小為4KB,使用伙伴系統(tǒng)(Buddy System)的思想實(shí)現(xiàn)頁的分配和回收。
      • 頁的分配基于SegmentAllocator,即先分段再分頁。

    在此簡要闡述伙伴分配器的處理過程,為有效管理空閑塊,每一級order持有一個(gè)空閑塊的FreeList。設(shè)定最大級別order=4,即從order=0開始,由低到高,每級order塊內(nèi)頁數(shù)分別為1、2、4、8、16等。分配時(shí)先找滿足條件的最小塊;若找不到則在上一級查找更大的塊,并將該塊分為兩個(gè)“伙伴”,其中一個(gè)分配使用,另一個(gè)置于低一級的FreeList。

    下圖呈現(xiàn)了分配一個(gè)頁大小的內(nèi)存塊前后的狀態(tài)變化,分配前,分配器由order=0開始查找FreeList,直到order=4才找到空閑塊。

    將該空閑塊分為頁數(shù)為8的2個(gè)伙伴,使用前一半,并將后一半掛載到order=3的FreeList;逐級重復(fù)此過程,直到返回所需的內(nèi)存塊,并將頁數(shù)為1的空閑塊掛在到order=0的FreeList。

    當(dāng)塊釋放時(shí),會及時(shí)查看其伙伴是否空閑,并盡可能將兩個(gè)空閑伙伴合并為更大的空閑塊。這是分配過程的逆過程,不再贅述。

    雖然PageAllocator有效地避免了外部碎片,卻無法解決內(nèi)部碎片的問題。為解決這類小對象的分配問題,實(shí)現(xiàn)了對象緩存分配器(SlabAllocator)。

    • SlabAllocator
      • 基于PageAllocator分配對象緩存,slab大小以頁為單位。
      • 空閑對象按內(nèi)存大小定義為多個(gè)SlabManager,每個(gè)SlabManager持有一個(gè)PartialFreeList,用于放置含有空閑對象的slab。

    對象的內(nèi)存分配過程,即從對應(yīng)的PartialFreeList獲取含有空閑對象的slab,并從該slab分配對象。反之,釋放過程為分配的逆過程。

    綜上,實(shí)時(shí)索引存儲結(jié)合了PageAllocator和SlabAllocator,有效地解決了內(nèi)存管理的外部碎片和內(nèi)部碎片問題,可確保系統(tǒng)高效穩(wěn)定地長期運(yùn)行。

    能力層

    能力層實(shí)現(xiàn)了正排表、倒排表等基礎(chǔ)的存儲能力,并支持索引能力的靈活擴(kuò)展。

    正向索引

    也稱為正排索引(Forward Index),即通過主鍵(Key)檢索到文檔(Doc)內(nèi)容,以下簡稱正排表或Table。不同于搜索引擎的正排表數(shù)據(jù)結(jié)構(gòu),Table也可以單獨(dú)用于NoSQL場景,類似于Kyoto Cabinet的哈希表。

    Table不僅提供按主鍵的增加、刪除、修改、查詢等操作,也配合倒排表實(shí)現(xiàn)檢索、過濾、讀取等功能。作為核心數(shù)據(jù)結(jié)構(gòu),Table必須支持頻繁的字段讀取和各類型的正排過濾,需要高效和緊湊的實(shí)現(xiàn)。

    為支持按docID的隨機(jī)訪問,把Table設(shè)計(jì)為一個(gè)大數(shù)組結(jié)構(gòu)(data區(qū))。每個(gè)doc是數(shù)組的一個(gè)元素且長度固定。變長字段存儲在擴(kuò)展區(qū)(ext區(qū)),僅在doc中存儲其在擴(kuò)展區(qū)的偏移量和長度。與大部分搜索引擎的列存儲不同,將data區(qū)按行存儲,這樣可針對業(yè)務(wù)場景,盡可能利用CPU與內(nèi)存之間的緩存來提高訪問效率。

    此外,針對NoSQL場景,可通過HashMap實(shí)現(xiàn)主鍵到docID的映射(idx文件),這樣就可支持主鍵到文檔的隨機(jī)訪問。由于倒排索引的docID列表可以直接訪問正排表,因此倒排檢索并不會使用該idx。

    反向索引

    也稱作倒排索引(Inverted Index),即通過關(guān)鍵詞(Keyword)檢索到文檔內(nèi)容。為支持復(fù)雜的業(yè)務(wù)場景,如遍歷索引表時(shí)的算法粗排邏輯,在此抽象了索引器接口Indexer。

    具體的Indexer僅需實(shí)現(xiàn)各接口方法,并將該類型注冊到IndexerFactory,可通過工廠的NewIndexer方法獲取Indexer實(shí)例,類圖如下:

    當(dāng)前實(shí)現(xiàn)了三種常用的索引器:

    • NoPayloadIndexer:最簡單的倒排索引,倒排表為單純的docID列表。
    • DefaultPayloadIndexer:除docID外,倒排表還存儲keyword在每個(gè)doc的負(fù)載信息。針對業(yè)務(wù)場景,可存儲POI在每個(gè)Node粒度的靜態(tài)質(zhì)量分或最高出價(jià)。這樣在訪問正排表之前,就可完成一定的倒排優(yōu)選過濾。
    • GEOHashIndexer:即基于地理位置的Hash索引。

    上述索引器的設(shè)計(jì)思路類似,僅闡述其共性的兩個(gè)特征:

    • 詞典文件term:存儲關(guān)鍵詞、簽名哈希、posting文件的偏移量和長度等。與Lucene采用的前綴壓縮的樹結(jié)構(gòu)不同,在此實(shí)現(xiàn)為哈希表,雖然空間有所浪費(fèi),但可保證穩(wěn)定的訪問性能。
    • 倒排表文件posting:存儲docID列表、Payload等信息。檢索操作是順序掃描倒排列表,并在掃描過程中做一些基于Payload的過濾或倒排鏈間的布爾運(yùn)算,如何充分利用高速緩存實(shí)現(xiàn)高性能的索引讀取是設(shè)計(jì)和實(shí)現(xiàn)需要考慮的重要因素。在此基于segmentAllocator實(shí)現(xiàn)分段的內(nèi)存分配,達(dá)到了效率和復(fù)雜度之間的微妙平衡。

    出于業(yè)務(wù)考慮,沒有采用Lucene的Skip list結(jié)構(gòu),因?yàn)閺V告場景的doc數(shù)量沒有搜索引擎多,且通常為單個(gè)倒排列表的操作。此外,若后續(xù)doc數(shù)量增長過快且索引變更頻繁,可考慮對倒排列表的元素構(gòu)建B+樹結(jié)構(gòu),實(shí)現(xiàn)倒排元素的快速定位和修改。

    接口層

    接口層通過API與外界交互,并屏蔽內(nèi)部的處理細(xì)節(jié),其核心功能是提供檢索和更新服務(wù)。

    配置文件

    配置文件用于描述整套索引的Schema,包括Metadata、Table、Index的定義,格式和內(nèi)容如下:

    可見,Index是構(gòu)建在Table中的,但不是必選項(xiàng);Table中各個(gè)字段的定義是Schema的核心。當(dāng)Schema變化時(shí),如增加字段、增加索引等,需要重新構(gòu)建索引。篇幅有限,此處不展開定義的細(xì)節(jié)。

    檢索接口

    檢索由查找和過濾組成,前者產(chǎn)出查找到的docID集合,后者逐個(gè)對doc做各類基礎(chǔ)過濾和業(yè)務(wù)過濾。

    • Search:返回正排過濾后的ResultSet,內(nèi)部組合了對DoSearch和DoFilter的調(diào)用。
    • DoSearch:查詢doc,返回原始的ResultSet,但并未對結(jié)果進(jìn)行正排過濾。
    • DoFilter:對DoSearch返回的ResultSet做正排過濾。

    一般僅需調(diào)用Search就可實(shí)現(xiàn)全部功能;DoSearch和DoFilter可用于實(shí)現(xiàn)更復(fù)雜的業(yè)務(wù)邏輯。

    以下為檢索的語法描述:

    /{table}/{indexer|keyfield}?query=xxxxxx&filter=xxxxx

    第一部分為路徑,用于指定表和索引。第二部分為參數(shù),多個(gè)參數(shù)由&分隔,與URI參數(shù)格式一致,支持query、filter、Payload_filter、index_filter等。

    由query參數(shù)定義對倒排索引的檢索規(guī)則。目前僅支持單類型索引的檢索,可通過index_filter實(shí)現(xiàn)組合索引的檢索。可支持AND、OR、NOT等布爾運(yùn)算,如下所示:

    query=(A&B|C|D)!E

    查詢語法樹基于Bison生成代碼。針對業(yè)務(wù)場景常用的多個(gè)term求docID并集操作,通過修改Bison文法規(guī)則,消除了用于存儲相鄰兩個(gè)term的doc合并結(jié)果的臨時(shí)存儲,直接將前一個(gè)term的doc并入當(dāng)前結(jié)果集。該優(yōu)化極大地減少了臨時(shí)對象開銷。

    由filter參數(shù)定義各類正排表字段值過濾,多個(gè)鍵值對由“;”分割,支持單值字段的關(guān)系運(yùn)算和多值字段的集合運(yùn)算。

    由Payload_filter參數(shù)定義Payload索引的過濾,目前僅支持單值字段的關(guān)系運(yùn)算,多個(gè)鍵值對由“;”分割。

    詳細(xì)的過濾語法如下:

    此外,由index_filter參數(shù)定義的索引過濾將直接操作倒排鏈。由于構(gòu)造檢索數(shù)據(jù)結(jié)構(gòu)比正排過濾更復(fù)雜,此參數(shù)僅適用于召回的docList特別長但通過索引過濾的docList很短的場景。

    結(jié)果集

    結(jié)果集ResultSet的實(shí)現(xiàn),參考了java.sql.ResultSet接口。通過cursor遍歷結(jié)果集,采用inline函數(shù)頻繁調(diào)用的開銷。

    實(shí)現(xiàn)為C++模板類,主要接口定義如下:

    • Next:移動cursor到下一個(gè)doc,成功返回true,否則返回false。若已經(jīng)是集合的最后一條記錄,則返回false。
    • GetValue:讀取單值字段的值,字段類型由泛型參數(shù)T指定。如果獲取失敗返回默認(rèn)值def_value。
    • GetMultiValue:讀取多值字段的值,返回指向值數(shù)組的指針,數(shù)組大小由size參數(shù)返回。讀取失敗返回null,size等于0。

    更新接口

    更新包括對doc的增加、修改、刪除等操作。參數(shù)類型Document,表示一條doc記錄,內(nèi)容為待更新的doc的字段內(nèi)容,key為字段名,value為對應(yīng)的字段值。操作成功返回0,失敗返回非0,可通過GetErrorString接口獲取錯(cuò)誤信息。

    • 增加接口Add:將新的doc添加到Table和Index中。
    • 修改接口Update:修改已存在的doc內(nèi)容,涉及Table和Index的變更。
    • 刪除接口Delete:刪除已存在的doc,涉及從Table和Index刪除數(shù)據(jù)。

    更新服務(wù)對接實(shí)時(shí)更新流,實(shí)現(xiàn)真正的廣告實(shí)時(shí)索引。

    更新系統(tǒng)

    除以上描述的索引實(shí)現(xiàn)機(jī)制,生產(chǎn)系統(tǒng)還需要打通在線投放引擎與商家端、預(yù)算控制、反作弊等的更新流。

    挑戰(zhàn)與目標(biāo)

    數(shù)據(jù)更新系統(tǒng)的主要工作是將原始多個(gè)維度的信息進(jìn)行聚合、平鋪、計(jì)算后,最終輸出線上檢索引擎需要的維度和內(nèi)容。

    業(yè)務(wù)場景導(dǎo)致上游觸發(fā)可能極不規(guī)律。為避免更新流出現(xiàn)的抖動,必須對實(shí)時(shí)更新的吞吐量做優(yōu)化,留出充足的性能余量來應(yīng)對觸發(fā)的尖峰。此外,更新系統(tǒng)涉及多對多的維度轉(zhuǎn)換,保持計(jì)算、更新觸發(fā)等邏輯的可維護(hù)性是系統(tǒng)面臨的主要挑戰(zhàn)。

    吞吐設(shè)計(jì)

    雖然更新系統(tǒng)需要大量的計(jì)算資源,但由于需要對幾十種外部數(shù)據(jù)源進(jìn)行查詢,因此仍屬于IO密集型應(yīng)用。優(yōu)化外部數(shù)據(jù)源訪問機(jī)制,是吞吐量優(yōu)化的主要目標(biāo)。

    在此,采取經(jīng)典的批量化方法,即集群內(nèi)部,對于可以批量查詢的一類數(shù)據(jù)源,全部收攏到一類特定的worker上來處理。在短時(shí)間內(nèi),worker聚合數(shù)據(jù)源并逐次返回給各個(gè)需要數(shù)據(jù)的數(shù)據(jù)流。處理一種數(shù)據(jù)源的worker可以有多個(gè),根據(jù)同類型的查詢匯集到同一個(gè)worker批量查詢后返回。在這個(gè)劃分后,就可以做一系列的邏輯優(yōu)化來提升吞吐量。

    分層抽象

    除生成商家端的投放模型數(shù)據(jù),更新系統(tǒng)還需處理針對各種業(yè)務(wù)場景的過濾,以及廣告呈現(xiàn)的各類專屬信息。業(yè)務(wù)變更可能涉及多個(gè)數(shù)據(jù)源的邏輯調(diào)整,只有簡潔清晰的分成抽象,才能應(yīng)對業(yè)務(wù)迭代的復(fù)雜度。

    工程實(shí)踐中,將外部數(shù)據(jù)源抽象為統(tǒng)一的Schema,既做到了數(shù)據(jù)源對業(yè)務(wù)邏輯透明,也可借助編譯器和類型系統(tǒng)來實(shí)現(xiàn)完整的校驗(yàn),將更多問題提前到編譯期解決。

    將數(shù)據(jù)定義為表(Table)、記錄(Record)、字段(Field)、值(Value)等抽象類型,并將其定義為Scala Path Dependent Type,方便編譯器對程序內(nèi)部的邏輯進(jìn)行校驗(yàn)。

    可復(fù)用設(shè)計(jì)

    多對多維度的計(jì)算場景中,每個(gè)字段的處理函數(shù)(DFP)應(yīng)該盡可能地簡單、可復(fù)用。例如,每個(gè)輸出字段(DF)的DFP只描述需要的源數(shù)據(jù)字段(SF)和該字段計(jì)算邏輯,并不描述所需的SF(1)到SF(n)之間的查詢或路由關(guān)系。

    此外,DFP也不與最終輸出的層級綁定。層級綁定在定義輸出消息包含的字段時(shí)完成,即定義消息的時(shí)候需要定義這個(gè)消息的主鍵在哪一個(gè)層級上,同時(shí)綁定一系列的DFP到消息上。

    這樣,DFP只需單純地描述字段內(nèi)容的生成邏輯。如果業(yè)務(wù)場景需要將同一個(gè)DF平鋪到不同層級,只要在該層級的輸出消息上引用同一個(gè)DFP即可。

    觸發(fā)機(jī)制

    更新系統(tǒng)需要接收數(shù)據(jù)源的狀態(tài)變動,判斷是否觸發(fā)更新,并需要更新哪些索引字段、最終生成更新消息。

    為實(shí)現(xiàn)數(shù)據(jù)源變動的自動觸發(fā)機(jī)制,需要描述以下信息:

    • 數(shù)據(jù)間的關(guān)聯(lián)關(guān)系:實(shí)現(xiàn)描述關(guān)聯(lián)關(guān)系的語法,即在描述外部數(shù)據(jù)源的同時(shí)就描述關(guān)聯(lián)關(guān)系,后續(xù)字段查詢時(shí)的路由將由框架處理。
    • DFP依賴的SF信息:僅對單子段處理的簡單DFP,可通過配置化方式,將依賴的SF固化在編譯期;對多種數(shù)據(jù)源的復(fù)雜DFP,可通過源碼分析來獲取該DFP依賴的SF,無需用戶維護(hù)依賴關(guān)系。

    生產(chǎn)實(shí)踐

    早期的搜索廣告是基于自然搜索的系統(tǒng)架構(gòu)建的,隨著業(yè)務(wù)的發(fā)展,需要根據(jù)廣告特點(diǎn)進(jìn)行系統(tǒng)改造。新的廣告索引實(shí)現(xiàn)了純粹的實(shí)時(shí)更新和層次化結(jié)構(gòu),已經(jīng)在美團(tuán)搜索廣告上線。該架構(gòu)也適用于各類非搜索的業(yè)務(wù)場景。

    系統(tǒng)架構(gòu)

    作為整個(gè)系統(tǒng)的核心,基于實(shí)時(shí)索引構(gòu)建的廣告檢索過濾服務(wù)(RS),承擔(dān)了廣告檢索和各類業(yè)務(wù)過濾功能。日常的業(yè)務(wù)迭代,均可通過升級索引配置完成。

    此外,為提升系統(tǒng)的吞吐量,多個(gè)模塊已實(shí)現(xiàn)服務(wù)端異步化。

    性能優(yōu)化

    以下為監(jiān)控系統(tǒng)的性能曲線,索引中的doc數(shù)量為百萬級別,時(shí)延的單位是毫秒。

    后續(xù)規(guī)劃

    為便于實(shí)時(shí)索引與其他生產(chǎn)系統(tǒng)的結(jié)合,除進(jìn)一步的性能優(yōu)化和功能擴(kuò)展外,我們還計(jì)劃完成多項(xiàng)功能支持。

    JNI

    通過JNI,將Table作為單獨(dú)的NoSQL,為Java提供本地緩存。如廣告系統(tǒng)的實(shí)時(shí)預(yù)估模塊,可使用Table存儲模型使用的廣告特征。

    SQL

    提供SQL語法,提供簡單的SQL支持,進(jìn)一步降低使用門檻。提供JDBC,進(jìn)一步簡化Java的調(diào)用。

    參考資料

    • Apache Lucene http://lucene.apache.org/
    • Sphinx http://sphinxsearch.com/
    • “Understanding the Linux Virtual Memory Manager” https://www.kernel.org/doc/gorman/html/understand/
    • Kyoto Cabinet http://fallabs.com/kyotocabinet/
    • GNU Bison https://www.gnu.org/software/bison/

    作者簡介

    • 倉魁:廣告平臺搜索廣告引擎組架構(gòu)師,主導(dǎo)實(shí)時(shí)廣告索引系統(tǒng)的設(shè)計(jì)與實(shí)現(xiàn)。擅長C++、Java等多種編程語言,對異步化系統(tǒng)、后臺服務(wù)調(diào)優(yōu)等有深入研究。
    • 曉暉:廣告平臺搜索廣告引擎組核心開發(fā),負(fù)責(zé)實(shí)時(shí)更新流的設(shè)計(jì)與實(shí)現(xiàn)。在廣告平臺率先嘗試Scala語言,并將其用于大規(guī)模工程實(shí)踐。
    • 劉錚:廣告平臺搜索廣告引擎組負(fù)責(zé)人,具有多年互聯(lián)網(wǎng)后臺開發(fā)經(jīng)驗(yàn),曾領(lǐng)導(dǎo)多次系統(tǒng)重構(gòu)。
    • 蔡平:廣告平臺搜索廣告引擎組點(diǎn)評側(cè)負(fù)責(zé)人,全面負(fù)責(zé)點(diǎn)評側(cè)系統(tǒng)的架構(gòu)和優(yōu)化。

    招聘信息

    有志于從事Linux后臺開發(fā),對計(jì)算廣告、高性能計(jì)算、分布式系統(tǒng)等有興趣的朋友,請通過郵箱與我們聯(lián)系。聯(lián)系郵箱:liuzheng04@meituan.com 。

    總結(jié)

    以上是生活随笔為你收集整理的美团广告实时索引的设计与实现的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。