愛奇藝平臺的架構設計與演進之路

近年來愛奇藝快速發展,優質內容層出不窮,愛奇藝廣告也隨之發展和壯大,廣告在線服務同時服務於品牌、中小、DSP 等不一樣客戶,造成了能夠知足不一樣需求類型的較爲完善的商業廣告變現佈局,廣告庫存涵蓋視頻、信息流、泡泡社交(愛奇藝的社交平臺)和開機屏等多種場景。愛奇藝效果廣告是 2015 年開始全新搭建的一個廣告投放平臺,隨着信息流業務的增加,整個投放平臺也經歷了一次大的架構調整和屢次重要的升級優化。前端

愛奇藝廣告投放平臺的概要架構以下圖所示。本文主要介紹在線服務相關的內容,在線投放服務即圖中虛線所框出的部分,主要包括在線的投放和計費服務。算法

愛奇藝平臺的架構設計與演進之路

架構背後的業務需求編程

架構確定是爲業務需求而生的,先來看看咱們面對的業務需求及其特色。性能優化

愛奇藝效果廣告投放平臺目前採用代理商模式,平臺主要知足兩大類業務需求:面向代理商(廣告主)的和麪向產品及運營團隊的需求。具體來看看。服務器

一、面向代理商的需求: 本質上是要幫助代理商下降轉化成本網絡

  • 支持多種廣告位:貼片、暫停、浮層、信息流、視頻關聯位和推薦位等架構

  • 支持多種結算類型:支持 CPC、CPM 和 CPV 等廣告結算類型,oCPC 結算方式在規劃中併發

  • 豐富的定向功能:經常使用定向維度(平臺、地域等)及人羣精準定向(地域定向 - 支持區縣級別、人羣屬性定向和 DMP 人羣定向),關鍵詞定向app

  • 靈活的排期及預算設置:支持分鐘粒度的排期設置,支持日預算的任意增減框架

  • 特殊的業務功能:廣告去重功能、動態創意、創意優選和平滑消耗等,都是爲了提高廣告的轉化效果

  • 頻次控制:避免對相同用戶短期的大量曝光

二、面向產品及運營團隊:主要是提高產品控制能力,促進總體系統的良好運轉

  • 流量控制:經過黑白名單控制某些流量上不能夠 / 能夠投放哪些廣告

  • AB 測試功能:影響較大的功能全量發佈以前須要進行 AB 測試以確認效果符合預期

  • 計費相關:延遲曝光不計費,曝光、點擊異常檢測及過濾

  • 負反饋:根據用戶反饋自動調整廣告投放策略優化用戶體驗,同時也是對廣告主的一種制約

從上面描述的業務需求能夠看出,業務的特色有:

  1. 業務邏輯複雜:流程包括不少環節(場景信息獲取,廣告召回,預算控制,頻次控制,點擊率預估,創意優選,平滑消耗,廣告去重,結果排序,結果篩選,機率投放,AB 測試);下圖中綠框的部分僅展現投放服務的主要流程:

  2. 業務變動很是快:平均每週 5 次的系統功能變動;

愛奇藝平臺的架構設計與演進之路

  1.  
  2. 廣告主數量多,訂單量大,訂單平均預算較小,而且訂單設置會頻繁變化。

系統架構

愛奇藝效果廣告於 2016 年正式上線。起步伊始,業務邏輯簡單,廣告和訂單數量較少,總體架構相對比較簡單。爲了快速完成系統的搭建和上線應用,複用了品牌廣告投放平臺的架構,並作了剪裁,系統架構圖以下:

接入層 包括 QLB(iQiYi Load Balance)、Nginx 前端機,主要作流量的反向代理和總體的限流與降級功能。

流量分發層:包括策略服務和流量平臺服務;策略服務支持公司層面的策略控制和平常的運營需求;流量平臺服務主要控制流量在各投放平臺上的分配和請求邏輯,投放平臺包括品牌廣告投放平臺,效果廣告投放平臺和外部 DSP。

投放服務:前文介紹的業務邏輯都包含在這裏,由單一的模塊來實現。

日誌收集:接收曝光點擊等日誌,主要完成計費、頻控和去重等業務邏輯,也是由單一的模塊來實現。

計費系統:利用 Redis 主從同步機制把訂單的實時消耗數據同步到投放服務。

頻次系統:使用 Couchbase 機羣來作用戶數據存儲。

數據同步層:這一層涉及的數據種類不少,其中相對較重要的有兩種:業務數據和日誌數據,業務數據主要包括廣告的定向、排期和預算等內容。

咱們利用業務數據作了兩方面的優化工做:

  1. 經過業務數據分發一些對時效性要求不高的數據給到投放服務,避免了一些網絡 IO;

  2. 在業務數據中進行空間換時間的優化,包括生成索引及一些投放服務所須要的數據的預計算,譬如提早計算計費系統中的 key 值。

隨着業務增加,架構也遇到了一些挑戰。

  1. 流量增加:系統上線以後很好地知足了廣告主對轉化效果的要求,這個正向的效果激發了廣告主對流量的需求,爲此產品和運營團隊不斷地開闢新的廣告位,同時愛奇藝的用戶數和流量也在持續增加,這些緣由共同爲效果廣告平臺帶來了巨大的流量。

  2. 廣告主數量和訂單數量增加:這個增加包括兩方面,一方面與流量增加相輔相成,相互促進;愛奇藝的優質流量和良好的轉化效果吸引了更多的廣告主;另外一方面,因爲商務政策上的緣由,廣告主和訂單量在季度末會有階段性的增加。

  3. 性能問題:流量和訂單量的增加使得系統的負載快速增長,由於訂單是全量召回的,當訂單量增加到必定數量以後,會使得長尾請求增多,影響總體服務性能,沒法經過水平擴容解決。

  4. 超投問題:因爲曝光和點擊的延遲,以及投放計費環路的延遲,不可避免的存在超投問題,這也是廣告系統的固有問題;品牌廣告是先簽定合同,投放量達到便可按照合同收款,超出部分不會對廣告主收費,品牌廣告預約量都很大,超投比率較小;和品牌廣告不一樣,效果廣告實時扣費,若是沿用品牌思路的話,超投部分會形成多餘的扣費,而中小廣告主對此很是敏感,也會增長技術團隊問題分析排查工做,同時由於效果廣告的預算少,預算調整變化很快,使得超投比率要比品牌廣告大;針對效果廣告的超投問題,技術團隊要作的事情分紅兩個層面,一是保證超投的部分不會計費,不給廣告主帶來損失,二是從根本上減小超投,即減小咱們本身的收入損失;分別稱爲 超投不計費 和 減小超投

針對上面的幾個狀況,咱們的架構作了調整:

愛奇藝平臺的架構設計與演進之路

對比上線伊始的架構,此階段架構調總體如今如下幾個方面:

  1. 投放服務性能優化 – 包括索引分片和增長粗排序模塊,主要解決了上述流量增加、廣告主數量訂單增加等方面帶來的性能問題

  2. 索引分片是把原來的一份索引拆分紅多份,對應的一個請求會被拆分紅多個子請求並行處理,這樣每一個請求的處理時間會減小,從而有效減小長尾請求數量。

  3. 粗排序:全量召回的好處是收益最大化,缺點是性能會隨着訂單量增長而線性降低;粗排序在召回階段過濾掉沒有競爭力的低價值的(ECPM 較低的)廣告,低價值廣告被投放的機率和產生轉化的機率很低,所以粗排序的過濾對總體收入影響很小,同時能有效減小進入後續核心計算邏輯(包括精排序及其餘的業務邏輯)的訂單數量,使得服務壓力不隨訂單量而線性增加。

  4. 計費服務架構優化 - 主要是提高系統的可擴展性和解決超投問題

可擴展性經過服務拆分來解決,把單一模塊的計費服務拆分紅三個模塊,拆分以後日誌收集模塊對外提供服務,主要職責是接收日誌請求並當即返回,保證極低的響應時間;而後對計費日誌和非計費日誌進行不一樣的處理;檢測過濾模塊主要職責是進行定向檢查和異常日誌識別。計費服務把有效計費數據更新到計費系統。拆分紅三個模塊以後,每一個模塊都很簡單,符合微服務基本原則之一:單一職責

關於超投, 先看第一個問題:超投不計費。

主要難點在於:

  1. 同一個廣告的計費請求是併發的;

  2. 計費系統是分佈式的,出於性能考慮,請求的處理流程須要是無鎖的。

咱們在計費系統中解決這個問題的思路以下:

首先,要嚴格準確地計費,就要對並行的請求進行串行處理,Redis 的單線程模型自然知足串行計費的需求,咱們決定基於 Redis 來實現這個架構,把計費的邏輯以腳本的形式在 Redis 線程中執行,避免了先讀後寫的邏輯,這樣兩個根本緣由都消除了。

接下來的任務就是設計一個基於 Redis 的高可用高性能的架構。咱們考慮了兩種可選方案。

方案 1:數據分片,架構中有多個主 Redis,每一個主 Redis 存儲一個分數分片,日誌收集服務處理有效計費請求時要更新主 Redis;每一個主 Redis 都有對應的只讀從 Redis,投放服務根據分片算法到對應的從 Redis 上獲取廣告的實時消耗數據。

該方案的優勢是可擴展性強,能夠經過擴容來解決性能問題;缺點是運維複雜,要知足高可用系統架構還要更復雜;

方案 2:數據不分片,全部的計費請求都匯聚到惟一的主 Redis,同時只讀從 Redis 能夠下沉到投放服務節點上,能夠減小網絡 IO,架構更加簡潔;但主 Redis 很容易成爲性能的瓶頸;

在實踐中咱們採用了第二種 不分片 的方案。主要基於如下考慮:

在業務層面,效果廣告中有很大比率的是 CPC 廣告,而點擊日誌的數量相對較少,基本不會對系統帶來性能壓力;對於剩下的 CPM 計費的廣告,系統會對計費日誌進行聚合以下降主 Redis 的壓力;由於從 Redis 是下沉到投放上的,能夠不作特殊的高可用設計;主 Redis 的高可用採用 Redis Sentinel 的方案能夠實現自動的主從切換,日誌收集服務經過 Sentinel 接口獲取最新的主 Redis 節點。

在串行計費的情形下,最後一個計費請求累加以後仍是可能會超出預算,這裏有一個小的優化技巧,調整最後一個計費請求的實際計費值使得消耗與預算恰好吻合。

關於超投的第二個問題 減小超投,這個問題不能完全解決,但能夠獲得緩解,即下降超投不計費的比率,把庫存損失降到最低;咱們的解決方案是在廣告的計費消耗接近廣告預算時執行按機率投放,消耗越接近預算投放的機率越小;該方法有一個弊端,就是沒有考慮到廣告的差別性,有些廣告的 ECPM 較低,自己的投放機率就很小,曝光(或點擊)延遲的影響也就很小;針對這一點,咱們又作了一次優化:基於歷史數據估算廣告的預算消耗速度和計費延遲的狀況,再利用這兩個數據來修正投放機率值。

這個方案的最大特色是實現簡單,在現有的系統中作簡單的開發便可實現,不須要增長額外的系統支持,不依賴於準確的業務場景預測(譬如曝光率,點擊率等),並且效果也還不錯;咱們還在嘗試不一樣的方式繼續進行優化超投比率,由於隨着收入的日漸增加,超投引發的收入損失仍是很可觀的。

關於微服務架構改造的思考

微服務架構如今已經被業界普遍接受和推廣實踐,咱們從最初就對這個架構思想有很強的認同感; 廣告在線服務在 2014 年完成了初版主要架構的搭建,那時的微觀架構(虛框表示一臺服務器)是這樣的:

愛奇藝平臺的架構設計與演進之路

在同一臺機器上部署多個服務,上游服務只請求本機的下游服務,服務之間使用 http 協議傳輸 protobuf 數據,每一個機器都是一個完備的投放系統。

這個架構有不少的優勢:結構清晰,運維簡單,網絡延遲最小化等。

固然也有一些缺點,同一臺機器上可部署的服務數量是有限的,於是會限制架構的增加,多個模塊混合部署不利於總體的性能優化,一個服務的異常會影響整個機器的服務質量;這個架構在微觀上知足了單一服務的原則,但在宏觀上還不是真正的微服務化,爲了解決上面的一些問題,按照天然的演進咱們必然走上微服務化這條路;咱們從 16 年中開始進行微服務化的實踐。

微服務化過程當中咱們也遇到了不少問題,分享一下咱們的解決方法及效果:

1. 技術選型問題

RPC 選型,必須知足的條件是要支持 C++、protobuf 協議和異步編程模型。最初的可選項有 sofa-pbrpc、pbrpc 和 grpc,這三者中咱們選中了 grpc,主要看中了它通用(多語言、多平臺和支持代理)、流控、取消與超時等特性;在咱們選定 grpc 以後不久百度開源了它的高性能 rpc 框架 brpc,相比之下 brpc 更具備優點:健全的文檔,高性能,內置檢測服務等很是多的特性;爲此咱們果斷地拋棄了 grpc 和已經在上面投入的一些開發成本,快速地展開了 brpc 相關的基礎功能開發和各服務的改造。

名字服務選型,排除了 zookeeper,etcd 等,最終選定的是 consul+consul template 這個組合,它很完美地支持了咱們的業務需求;除服務註冊與發現外,還有健康檢查,服務列表本地備份,支持權重設置等功能,這些功能能夠有效地減小團隊成員的運維工做量,加強系統的可用性,成爲服務的標準配置。

2. 運維成本增長

這是微服務化帶來的問題之一,微服務化要作服務拆分,服務節點的類型和數量會增多,同時還要額外運維一些基礎服務(譬如,名字服務的 Agency)。考慮到大部分運維工做都是同一個任務在多個機器上重複執行,這樣的問題最適合交由機器來完成,因此咱們的解決方案就是自動化運維。咱們基於 Ansible 自研了一個可視化的自動運維繫統。其實研發這個系統最初目的並非爲了支持微服務化,而是爲了消除人工運維事故,由於人的狀態是不穩定的(有時甚至是不靠譜的),因此但願由機器來替代人來完成重複的標準動做;後來隨着微服務化的推動,這個系統很天然地就接管了相關的運維工做。如今這個系統完成了整個團隊 90% 以上的運維工做量。

自動運維繫統架構

1. 問題發現和分析定位

業界通用的方式是全鏈路追蹤系統(dapper & zipkip)和智能運維,咱們也在正在進行這方面的工做;除此以外,咱們還作了另外兩件事情:異常檢測和 Staging 環境建設;

  • 異常檢測:主要是從業務層面發現各類宏觀指標的異常,對於廣告投放系統、庫存量、曝光量、點擊率和計費率等都是很是受關注的業務指標;異常檢測系統能夠預測業務指標在當前時刻的合理範圍值,而後跟實時數據做對比;若是實時數據超出預測範圍就會發出報警並附帶分析數據輔助進行問題分析;這部分工做由在線服務和數據團隊共同完成,這個系統有效地提升了問題發現的效率。

  • Staging 環境建設:系統變動(包括運維和新功能發佈)是引發線上故障的主要緣由,因此咱們須要一個系統幫助咱們以很小的代價快速發現變動異常。

愛奇藝平臺的架構設計與演進之路

  •  

在功能發佈時你們都會採用梯度發佈的方法,譬如先升級 5% 的服務,而後觀察覈心指標的變化,沒有明顯異常就繼續推動直到全量;這個方法並非總能有效發現問題,假如一個新功能中的 bug 會致使 1% 的訂單曝光降低 50%,那麼在全量發佈以後系統的總體曝光量也只有 0.5% 的變化,也可能由於其餘訂單的填充使得總體曝光量沒有變化,因此僅經過總體曝光量很難發現這個問題。只有對全部訂單的曝光量進行對比分析才能準確地發現這個問題。

咱們在實踐中利用向量餘弦類似度來發現系統變動引發異常,即把一段時間內(5min)曝光的廣告數量轉換成向量並計算餘弦類似度。那麼如何獲得兩個向量呢?能夠按照梯度發佈的時間進行分割先後各生成一個向量,這個方法不夠健壯,不一樣時間的向量自己就有必定的差別。

咱們是這樣來解決的:部署一個獨立的投放環境(咱們稱爲 Staging 環境,相對的本來的投放環境稱爲 Base 環境)承載線上的小流量(譬如 3%),全部的系統變動都先在這裏進行;而後用 Staging 環境的向量與 Base 環境的向量進行類似度計算。

由於對差別很是敏感,使用餘弦類似度作監控會有誤報發生;不過這個並不難解決,經過一些 bad case 的分析,咱們定位並消除了兩個環境之間的差別(非 bug)因素;在正常狀況下兩個環境的類似度會保持在 95% 左右,並在遇到真正的異常時會有明顯的降低觸發報警。Staging 環境及類似度檢測功能在實踐中屢次幫助咱們發現系統異常。

愛奇藝平臺的架構設計與演進之路

架構設計過程當中積累的經驗

最後分享幾點我在架構設計過程當中總結的經驗。

  • 深刻理解業務。 在架構設計方面,業務和架構是要互相配合的,架構在知足業務需求的同時,也能夠反過來給業務提需求甚至要求改變業務邏輯已達到系統的最優,這裏的關鍵就是充分理解業務。架構上很難解決的問題,可能在業務上作個微小的調整就搞定了,能有這樣的效果,何樂而不爲呢。在系統或者架構優化方面,優化理論和策略已經研究的很是充分,剩下的只是如何跟業務場景進行結合和利用。

  • 設計階段要追求完美,實踐階段要考慮性價比,採用分階段遞進的方式演進到完美的架構。** 在設計階段能夠暫時拋開實現成本或者其餘一些客觀條件的束縛,按照理想的狀況去作架構設計,這樣獲得的一個結果是咱們所追求的一個理想目標,這個目標暫時達不到不要緊,由於它的做用就是指明架構未來的發展或者演化的大方向;而後在結合實際的限制條件逐步調整這個完美的架構到一個可實際落地的程度,這個過程當中還能夠保留多箇中間版本,做爲架構演進升級過程的 Milestone。也能夠這樣理解,從現實出發,着眼於將來,隨着技術發展的速度愈來愈快,在設計之初遇到的限制和障礙很快就會被解決,避免被這些暫時的限制和障礙遮住了對將來的想象。

  • 監控先行。 監控信息是瞭解系統運行狀態的重要信息,大部分監控信息都要持久化用來作數據分析使用,它能夠作異常檢測也能夠輔助進行問題的分析和定位;作好監控工做是改善 TTA(Time To Detection)和 TTM(Time To Mitigation)指標的方法之一;這裏還要強調的是要在設計階段就考慮到相關的各類監控指標、統計粒度等細節內容,在開發階段就在系統中進行相關指標的計算和統計,在服務部署階段將這些指標同步到監控系統中;確保服務上線之初就有相應的監控「保駕護航」,避免裸奔。

  • 容錯能力。 這個世界是不完美的,不完美世界中的系統要面對各類各樣的問題;在一個系統的整個生命週期中,研發運維人員要花費大量的時間來應對和解決各類錯誤甚至是災難;兩個方面去考慮,即 Design By Failure 和災難演練(Netflex 已經開源了他們的相關工具)。我想談談本身的實際體會:

首先,在設計之初能夠先劃定系統的邊界,分出系統內部和系統外部;從成本的角度考慮,系統內部由於可控性強,能夠設定一些假設以減小相關的考量和系統容錯設計;其他的系統內部問題以及系統外部的問題,優先解決影響較大的問題(譬如,外部服務不可用,對外接口訪問量突增)和高頻發生的問題(硬盤故障,網絡割接),這樣的問題大部分都有可借鑑的方案,若是因業務場景特殊而不能複用已有方案,那就要考慮本身來實現;應對外部服務不可用進行熔斷並增長保底策略,訪問量突增作限流,專線故障時走外網,硬盤作 Raid;其餘的未考慮到問題在問題首次發生時要評估損失和應對成原本決定要否當即解決;

其次,災難演練的這個想法跟消防演練是同樣的,消防演練一方面能夠發現逃生流程上的缺陷,更重要的是培養參與者的逃生常識和實操經驗,在問題真正發生時能正確應對;災難演練同理,在作自動化的同時,也要安排專人(尤爲是新人)進行故障處理,要有老司機陪同進行 review 在必要時進行指導或者接管處理動做。這樣纔會使得團隊總體的容錯應急處理能力不斷地提高。這個世界註定是不完美的,所以纔會有也更須要完美主義者來讓這個世界變得完美,哪怕是隻有一點點。

在此我向你們推薦一個架構學習交流羣。交流學習羣號: 744642380, 裏面會分享一些資深架構師錄製的視頻錄像:有Spring,MyBatis,Netty源碼分析,高併發、高性能、分佈式、微服務架構的原理,JVM性能優化、分佈式架構等這些成爲架構師必備的知識體系。還能領取免費的學習資源,目前受益良

相關文章
相關標籤/搜索