標籤:
- 廣告引擎
- 總體設計
- 檢索服務
- 廣告檢索流程
- 廣告定向
- 定向維度下的選項很少,可以枚舉,這類定向包括,性別,年齡段,網路,系統。
- 我們的系統現在是如何處理這類定向的
- 定向維度有級聯關係,省,市,區
- 定向維度是按照某個座標附近N公裡定向
- 檢索服務是資料庫的副本
- CTR計算
- 計算二價
- ADX廣告
- 曝光服務
- 計費服務
廣告引擎總體設計
我們的基本架構是用戶端請求API,然後由API發送RPC請求到我們的服務,服務通過註冊中心來管理
檢索服務,根據資料庫中的廣告建立索引,訂閱Redis Channel通過通知機制來使記憶體中的廣告與資料庫保持一致
曝光服務,接收曝光和點擊,彙總以後將彙總的結果推送到訊息佇列
計費服務,計費服務與支付系統進行互動,主要負責扣廣告主的錢,發起餘額不足,預算不足的下線
檢索服務廣告檢索流程
- 參數解析,主要是為了過濾掉一些非法請求
- 發起ADX廣告要求,這步首先對要向哪一家DSP發請求有個簡單的定向,然後再經過流量控制模組,確認可以發請求,會非同步發送HTTP請求,然後立刻返回,繼續下面的邏輯
- 廣告定向,對自己的廣告按照定向進行初篩,得到所有合格廣告ID
- 廣告過濾,根據一些邏輯規則,過濾掉不合格廣告ID
- CTR計算,並按照ECPM排序,這部對所有廣告的所有素材計算點擊率,最後按照ECPM排序
- 選擇各廣告位廣告,並計算二價
- 與ADX廣告競價,之前非同步發送的廣告要求,在這裡需要等待每個請求的返回結果
- 廣告內容格式化,按照不同類型的廣告格式化成用戶端的協議
- 廣告曝光點擊參數加密,對一些資訊進行加密,尤其是這次廣告的曝光價格和點擊價格
廣告定向
需要解決的問題
一個使用者本身有一些屬性,比如性別,年齡,網路環境,裝置類型,地區資訊等等,而廣告主想讓自己的廣告投放在特定的人群中,根據使用者的屬性,檢索出可用的廣告過程就完成了廣告定向
比如這個例子,廣告2就不滿足定向條件,廣告3,廣告1滿足
定向維度下的選項很少,可以枚舉,這類定向包括,性別,年齡段,網路,系統。
廣告定向表
| 廣告ID |
性別 |
年齡段 |
網路 |
作業系統 |
| 廣告1 |
不限 |
不限 |
不限 |
不限 |
| 廣告2 |
女 |
0-18 |
不限 |
不限 |
| 廣告3 |
男 |
0-18 |
不限 |
IOS |
根據以上這張定向表,看上去有點像資料庫,假設圖中小人的流量來了,換成資料庫的查詢語句,應該是
where (性別=男 or 性別=不限)
and (年齡=0-18 or 年齡=不限)
and (網路=wifi or 網路=不限)
and (作業系統=ios or 作業系統=不限)
如果沒有“不限”,那麼看上去複合式索引是最好的選擇,我們把 性別-年齡-網路-作業系統 組合成一個索引,這樣索引的空間是
- 2種性別 X 5段年齡 X 2種網路環境 X 2種作業系統 = 40種可能,每種可能對應一個ID的列表,為了用到複合式索引,必須把or語句去掉,可以把不限的廣告冗餘寫到所有的索引下,比如最極端的例子就是廣告1,所有都不限,那麼40種可能的組合下全部都有廣告1的冗餘
- 我們的系統最開始就是這樣來檢索,好處就是只需要查詢一個索引就能取出定向的廣告,不好的地方也顯而易見就是會增加被索引資料的冗餘
where語句只看性別一個維度,or左右兩邊是等值查詢肯定可以利用索引求出兩個集合再取並集,然後再和其他維度取交集
- 我們程式中也是類比這種先並再交的操作,資料中使用的btree來組織索引,我們程式中使用的是雜湊表,都是給定一個查詢的Key,返回一個被索引的內容
集合操作
如何取並集可以參考兩個有序數組的合并排序
如何取交集,一般有三種方法
- 通用方法,選擇小集合作為base在大集合裡做二分尋找
- 大集合交小集合,小集合如果可以放到記憶體可以使用把小集合在記憶體裡用構建hash索引,大集合作為base,在小集合裡雜湊尋找
- 大集合交大集合,讓兩個都有序之後再交集M+N的複雜度,找到一個集合的最小,然後在另一個集合skip比這個數還小的所有資料
我們的系統現在是如何處理這類定向的
對每個維度在記憶體中建立倒排雜湊索引,然後我們把某個維度下的不限做了冗餘,這樣就不需要做並集,每個維度用資料最少的維度資料做為驅動表,然後在其他並集的結果中做雜湊檢測,如果不存在就把這個廣告刪除掉
注意,我們的定向只做了一次adid記憶體拷貝到某個流量檢索的上下文,而且拷貝的是最小的集合,其他要做的是不斷的雜湊檢測,把這個集合根據其他維度定向再縮小範圍
以上這種定向方法主要使用了倒排索引,還有一些求交集的技巧,倒排索引在處理等值查詢和動態多維度組合的時候是非常適合的,但是在處理範圍查詢的時候就不太好使了,比如我們年齡要是支援任意年齡區間的定向,也就是要處理範圍查詢,一些有序結構比如平衡樹會是更好的選擇
定向維度有級聯關係,省,市,區
廣告在地區定向條件如下
廣告地區定向表
| 廣告ID |
省 |
市 |
區 |
| 廣告1 |
不限 |
不限 |
不限 |
| 廣告2 |
北京 |
北京市區 |
朝陽 |
| 廣告3 |
上海 |
上海市區 |
不限 |
假設一個使用者在北京,北京市區,朝陽要檢索廣告,SQL如下:
where (省=不限 and 市=不限 and 區=不限)
or (省=北京 and 市=北京市區 and 區=不限)
or (省=北京 and 市=北京市區 and 區=朝陽)
之前我們說可把不限這種情況冗餘到所有資料項目下面,這樣就避免了OR操作,省市區這種無法冗餘的原因有兩個
- 一個是不知道冗餘到哪裡,因為省市區的選項有可能都不固定
- 還有就是冗餘的話資料在定向是省市區都不限的時候冗餘量太大,要把全國每一個區縣都冗餘一遍
觀察這個SQL是先AND再OR,之前分析過多個AND適合複合式索引,所以假如我們可以把省-市-區看成一個索引中查詢的值,相當於我們查3次複合式索引,然後再取並集,得到的集合還需要和其他集合做交集,而且這個集合因為是OR出來的動態變化的所以不得不把廣告ID的列表拷貝出來,弄個暫存資料表。
如何最佳化暫存資料表,我想了兩種方法,但是目前還沒有做
- 對暫存資料表緩衝固定查詢條件緩衝OR以後的暫存資料表
- 根據集合運算定律A交(B 並 C) = (A交B) 並 (A交C),這樣把這個維度在最後運算可以首先保證A比較小,B,C是永遠不變的,做雜湊過濾就可以了。
定向維度是按照某個座標附近N公裡定向
首先座標可以轉成GEOHASH,然後N公裡可以再定向之後用過濾的方式計算
有幾點需要注意:
- GEOHASH的精度,最小位應該能覆蓋附近N公裡,大概4位的GEOHASH可以覆蓋20KM,3位可以覆蓋78KM
- GEOHASH因為只能定位一個大概,所以需要把廣告同時在附近的8個格子冗餘寫入廣告ID,這樣查詢的時候只需要查一個格子,不然就需要檢索9個格子
總的來說這種方式就是簡單的在索引中做等值查詢where geo=abcd 做一次初篩,然後再過濾
過濾並不一定很慢,在資料庫中用索引反而更慢的例子,這個CASE最適合的是過濾
檢索服務是資料庫的副本
我們的檢索服務在啟動的時候會全量載入資料庫中的廣告,構建廣告的正排資料和倒排索引資料,多個副本通過訊息通知維護與資料庫的一致
通過訂閱redis的channel來擷取訊息,訊息類型包括,下線廣告主的廣告,同步推廣計劃,同步廣告,同步素材,重新全量載入資料
在重新載入資料的時候,線上服務還要繼續運行,所以我們使用了引用替換的方式,為了保證全量載入是有足夠記憶體,記憶體只能使用1/2
訊息機制有可能會不可靠,每個小時檢索服務與資料庫重新全量同步一次
- 當記憶體中不足以裝下所有廣告的正倒排的時候,我們應該考慮的是兩反面,一個是壓縮儲存盡量裝載必要資訊,正排資料壓縮儲存;另一個是資料分區,比如按照地區分區,但是分區的時候還需要考慮到一些資料扭曲問題,目前我們還沒有碰到記憶體方面的平均。
CTR計算
每個廣告素材有一個CTR,所以CTR的計算量是很大的,我們的CTR計算採用非同步方式,當查詢一個廣告素材的CTR緩衝中沒有的時候,就返回預設CTR,然後非同步計算好這個素材的CTR填充到緩衝中
計算二價
計算方式 CPC廣告
- first.clickPrice = second.ecpm / first.quality / first.ctr + 0.01 * 1000
CPM廣告
- first.displayPrice = second.ecpm / first.quality 0.01
ADX廣告
面臨問題
ADX發送廣告要求是到外網,並且請求量大,在100ms以內返回,某一家DSP逾時不能對整體的廣告檢索有影響
我們針對這些問題做了如下幾方面的控制
- 流量控制,針對每家DSP有QPS的控制
- 使用NIO非同步發送請求
- 長串連,盡量使用HTTP1.1的KEEPALIVE特性,不要讓第三方使用HTTPS的競價連結
- 與每家DSP建立長串連的數量有最大值
- 逾時率大於40%折半降低流量,直到設定的QPS最小值,當請求成功率大於40%倍增流量,直到達到當前這家DSP這隻的QPS值
- 對逾時的控制,自己封裝了DspFuture使用HashwheelTimer定時器來控制逾時時間
針對ADX還需要加強的地方
- 連結的預熱,可以預先對每家DSP建立好長串連以後再對外提供服務
- 網路鏈路的選擇,與DSP之間的網路出現問題的時候,可以對某一家DSP嘗試使用不同的鏈路,或者優先使用最經濟的鏈路
曝光服務
接收到曝光點擊以後在記憶體中做彙總,每分鐘把彙總後資訊推送到訊息佇列
計費服務
計費服務從訊息佇列消費曝光服務彙總後的曝光點擊,其中曝光是每分鐘消費一次,點擊是即時消費。
曝光的消費是兩個線程協作,一個線程負責從訊息佇列拉資料然後彙總N台曝光服務推入的內容,一個線程負責消費。
- 當消費線程發生阻塞的時候,計費服務如果把彙總後的曝光都儲存在記憶體中有遺失資料的風險,所以我們隊計費服務從訊息佇列中拉取訊息的速度做了控制,最多隻可以彙總幾分鐘的資料
計費服務主備
計費服務一主一備,通過redis實現租約
redis中有一個叫lock的key,value是當前工作節點的名字,有一個到期時間。
主服務,每秒鐘先get(lock),然後判斷是否與自己當前節點名字一致,如果一致的話,使用expire方法進行續約
備服務,每秒鐘嘗先嘗試寫lock這個key為自己節點的名稱,使用redis的setex方法,因為key已經存在,所以會返回false
浮點數問題
因為計費經驗做一些錢方面的檢查,所以涉及浮點數精度問題
浮點數做減法使用BigDecimal.subtract()方法
浮點數做乘法使用BigDecimal.multiply()方法
浮點數做除法使用BigDecimal.divide()方法
廣告引擎解析