Redis 在搜尋和廣告應用中扮演著重要的角色。其高效的資料結構和操作,能有效提升搜尋排序和廣告投放的效能。本文將著重於 ZSET 的排序功能,並結合實際案例,展示如何建構一個搜尋導向應用程式,以及如何利用 Redis 建立一個廣告投放平台。其中,ZSET 的 score 成為關鍵,它能根據投票數、更新時間等因素,對搜尋結果進行排序,同時也能應用於廣告的 eCPM 計算和排序。程式碼範例中,search_and_zsort
函式巧妙地結合了搜尋查詢和排序標準,傳回排序後的搜尋結果,並利用 zintersect
和 zunion
函式進行集合運算,提高效率。此外,文章也探討了非數值排序的處理方法,以及廣告投放中根據位置和內容的索引策略,並提供了 index_ad
和 target_ads
函式的實作細節,展示瞭如何利用 Redis 進行廣告的儲存、排序和選擇。
搜尋導向應用程式中的排序技術
在前面的章節中,我們已經探討瞭如何利用Redis的SET和ZSET資料結構來進行搜尋和排序操作。本章節將深入介紹如何結合ZSET的排序功能和搜尋結果,以實作更靈活和高效的搜尋導向應用程式。
使用ZSET進行排序和搜尋
在許多應用程式中,僅僅傳回搜尋結果是不夠的,還需要根據某些標準對結果進行排序。Redis的ZSET(有序集合)提供了一種非常有效的方式來進行排序和排名操作。
search_and_zsort() 函式實作
為了實作根據投票數和更新時間的搜尋結果排序,我們定義了search_and_zsort()
函式,如下所示:
def search_and_zsort(conn, query, id=None, ttl=300, update=1, vote=0,
start=0, num=20, desc=True):
if id and not conn.expire(id, ttl):
id = None
if not id:
id = parse_and_search(conn, query, ttl=ttl)
scored_search = {
id: 0,
'sort:update': update,
'sort:votes': vote
}
id = zintersect(conn, scored_search, ttl)
pipeline = conn.pipeline(True)
pipeline.zcard('idx:' + id)
if desc:
pipeline.zrevrange('idx:' + id, start, start + num - 1)
else:
pipeline.zrange('idx:' + id, start, start + num - 1)
results = pipeline.execute()
return results[0], results[1], id
#### 內容解密:
此函式的主要目的是根據搜尋查詢和指定的排序標準,傳回排序後的搜尋結果。它首先檢查是否有現有的結果ID,如果有且尚未過期,則重新整理其TTL(生存時間)。否則,它會執行新的搜尋並計算排序後的結果。排序是透過結合搜尋結果、更新時間和投票數來實作的。最終,函式傳回結果的總數、排序後的結果列表以及用於分頁的結果ID。
輔助函式:ZSET 交集和並集運算
為了支援search_and_zsort()
函式,我們定義了兩個輔助函式:zintersect()
和zunion()
,用於執行ZSET的交集和並集運算。
def _zset_common(conn, method, scores, ttl=30, **kw):
id = str(uuid.uuid4())
execute = kw.pop('_execute', True)
pipeline = conn.pipeline(True) if execute else conn
for key in scores.keys():
scores['idx:' + key] = scores.pop(key)
getattr(pipeline, method)('idx:' + id, scores, **kw)
pipeline.expire('idx:' + id, ttl)
if execute:
pipeline.execute()
return id
def zintersect(conn, items, ttl=30, **kw):
return _zset_common(conn, 'zinterstore', dict(items), ttl, **kw)
def zunion(conn, items, ttl=30, **kw):
return _zset_common(conn, 'zunionstore', dict(items), ttl, **kw)
#### 內容解密:
這些輔助函式透過建立一個新的臨時ID並在Redis中執行ZSET的交集或並集運算來工作。它們自動為輸入鍵新增'idx:'
字首,並設定結果的TTL。最終,它們傳回結果ZSET的ID,以便進一步處理。
非數值排序與ZSET
在某些情況下,我們需要對非數值資料進行排序,例如字串。由於ZSET的分數必須是浮點數,我們需要將非數值資料轉換為數值。以下是實作非數值排序的方法:
- 字串轉數值:透過將字串轉換為其對應的數值表示,可以實作根據字串字首的排序。
- IEEE754浮點數限制:由於Redis使用IEEE754雙精確度浮點數儲存分數,我們需要注意其儲存限制和精確度問題。
實踐練習:文章投票系統
在本章節中,我們學習瞭如何使用ZSET結合投票數和更新時間來計算文章的分數。現在,請嘗試更新第一章中的article_vote()
、post_article()
、get_articles()
和get_group_articles()
函式,以使用本章介紹的新方法。這樣,您就可以根據新的評分機制更新文章的分數。
使用 Redis 建構廣告投放平台
在眾多的網站上,我們經常看到以文字、圖片和影片形式呈現的廣告。這些廣告的存在是作為網站所有者的收入來源,無論是搜尋結果、旅遊資訊還是字典定義。
本章節將探討如何利用 Redis 的 SET 和 ZSET 資料結構來實作廣告投放引擎。當您完成本章節的閱讀後,您將對如何使用 Redis 建構廣告投放平台有基本的瞭解。雖然有許多方法可以在沒有 Redis 的情況下建構廣告投放引擎(例如使用 C++、Java 或 C# 編寫的自定義解決方案),但使用 Redis 是啟動廣告網路最快速的方法之一。
將字串轉換為數值
在開始之前,我們需要了解如何將字串轉換為數值。這是因為 Redis 的 ZSET 需要使用數值作為 score。我們的目標是將字串轉換為一個數值,使得具有相同字首的字串具有相近的數值。
def string_to_score(string, ignore_case=False):
if ignore_case:
string = string.lower()
pieces = map(ord, string[:6])
while len(pieces) < 6:
pieces.append(-1)
score = 0
for piece in pieces:
score = score * 257 + piece + 1
return score * 2 + (len(string) > 6)
內容解密:
- 將輸入字串轉換為小寫(如果需要忽略大小寫)。
- 將字串的前 6 個字元轉換為其 ASCII 值。如果字串長度不足 6 個字元,則使用 -1 作為填充值。
- 將這些值組合成一個整數 score。
- 將 score 乘以 2,並根據字串長度是否大於 6 個字元加 1。
這個函式的作用是將字串轉換為一個數值,使得具有相同字首的字串具有相近的數值。
使用 SET 和 ZSET 實作廣告投放引擎
現在我們已經瞭解瞭如何將字串轉換為數值,接下來我們將探討如何使用 Redis 的 SET 和 ZSET 來實作廣告投放引擎。
廣告投放引擎的核心是根據使用者的特徵(如地理位置、興趣愛好等)來選擇合適的廣告。我們可以使用 Redis 的 SET 來儲存廣告的特徵,使用 ZSET 來儲存廣告的排序資訊。
廣告投放流程
- 廣告儲存:將廣告的特徵儲存到 Redis 的 SET 中。
- 廣告排序:將廣告的排序資訊儲存到 Redis 的 ZSET 中。
- 廣告選擇:根據使用者的特徵,從 Redis 的 SET 和 ZSET 中選擇合適的廣告。
import redis
# 連線 Redis
redis_client = redis.Redis(host='localhost', port=6379, db=0)
# 儲存廣告特徵
def store_ad_features(ad_id, features):
for feature in features:
redis_client.sadd(f'ad:{ad_id}:features', feature)
# 儲存廣告排序資訊
def store_ad_sort_info(ad_id, score):
redis_client.zadd('ad:sort', {ad_id: score})
# 選擇合適的廣告
def select_ads(user_features):
# 根據使用者特徵選擇合適的廣告
ad_ids = []
for feature in user_features:
ad_ids.extend(redis_client.smembers(f'ad:*:features:{feature}'))
# 根據廣告排序資訊排序廣告
ad_ids = redis_client.zrange('ad:sort', 0, -1)
return ad_ids
內容解密:
- 將廣告的特徵儲存到 Redis 的 SET 中。
- 將廣告的排序資訊儲存到 Redis 的 ZSET 中。
- 根據使用者的特徵,從 Redis 的 SET 和 ZSET 中選擇合適的廣告。
練習題
- 自動完成:嘗試改寫
find_prefix_range()
和autocomplete_on_prefix()
函式,以使用ZRANGEBYSCORE
來實作自動完成。 - 更長的字串:嘗試設計一種方法,可以使用超過 6 個字元的字首來實作自動完成。
- 更複雜的廣告投放邏輯:可以根據使用者的更多特徵來選擇合適的廣告。
- 更高效的廣告儲存:可以使用更高效的資料結構來儲存廣告的特徵和排序資訊。
- 實時廣告投放:可以實作實時的廣告投放,以提高廣告的點選率和轉換率。
第7章:根據搜尋的應用程式 - 廣告伺服器建置
在我們開始建置廣告伺服器之前,讓我們先來談談什麼是廣告伺服器及其功能。
7.3.1 什麼是廣告伺服器?
當我們提到廣告伺服器時,我們實際上指的是有時很小但很複雜的技術。每當我們存取一個帶有廣告的網頁時,無論是網頁伺服器本身還是我們的網頁瀏覽器都會向遠端伺服器發出請求以取得該廣告。這個廣告伺服器將會被提供各種資訊,以找出可以透過點選、瀏覽或操作賺取最多錢的廣告。
廣告伺服器的運作原理
為了選擇特定的廣告,我們的伺服器必須被提供目標引數。伺服器通常至少會接收到有關觀眾位置的基本資訊(至少根據我們的IP地址,有時根據手機或電腦的GPS資訊)、我們正在使用的作業系統和網頁瀏覽器、我們所在的頁面內容,以及我們在當前網站上存取的最後幾個頁面。
目標引數的重要性
我們將重點放在建立一個具有觀眾位置和存取頁面內容基本資訊的廣告目標平台。在我們瞭解如何根據這些資訊選擇廣告後,我們可以稍後新增其他目標引數。
7.3.2 索引廣告
索引廣告的過程與索引其他內容的過程沒有太大的不同。主要區別在於,我們不是要傳回一系列廣告(或搜尋結果),而是要傳回單一廣告。還有一些次要的區別,例如廣告通常具有所需的目標引數,如位置、年齡或性別。
根據位置和內容的廣告索引
正如前面提到的,我們將僅根據位置和內容進行目標定位,因此本文將討論如何根據位置和內容索引廣告。當你瞭解如何根據位置和內容進行索引和目標定位後,根據年齡、性別或最近的行為進行目標定位應該是類別似的(至少在索引和目標定位方面)。
計算廣告價值
在網路頁面上顯示的三種主要廣告型別是:每千次瀏覽成本(CPM)、每次點選成本(CPC)和每次操作成本(CPA,也稱為每次取得成本)。CPM廣告按照廣告本身的固定費率每千次瀏覽支付。CPC廣告按照廣告本身的固定費率每次點選支付。CPA廣告按照在廣告目標網站上執行的操作的變動費率支付。
使廣告價值一致化
為了簡化計算特定廣告的價值,我們將把所有型別的廣告轉換為相對於每千次瀏覽的價值,生成所謂的預估CPM(eCPM)。CPM廣告最容易計算,因為它們的每千次瀏覽價值已經被提供,所以eCPM = CPM。但對於CPC和CPA廣告,我們必須計算eCPM。
計算CPC廣告的eCPM
如果我們有一個CPC廣告,我們從其每次點選成本開始,比如說0.25美元。然後,我們將該成本乘以廣告的點選率(CTR)。點選率是廣告收到的點選次數除以廣告收到的瀏覽次數。然後,我們將該結果乘以1000,以獲得該廣告的預估CPM。如果我們的廣告獲得0.2%的CTR(即0.002),那麼我們的計算結果如下:0.25 x 0.002 x 1000 = 0.5美元eCPM。
計算CPA廣告的eCPM
當我們有一個CPA廣告時,計算過程與CPC廣告的計算過程有些相似。我們從廣告的CTR開始,比如說0.2%。我們將其乘以使用者在廣告商的目標頁面上執行操作的機率,也許是10%或0.1。然後,我們將其乘以執行的操作的價值,並再次乘以1000,以獲得我們的預估CPM。如果我們的CPA是3美元,我們的計算結果如下:0.002 x 0.1 x 3 x 1000 = 0.6美元eCPM。
將CPC和CPA廣告轉換為eCPM的輔助函式
def cpc_to_ecpm(views, clicks, cpc):
return 1000. * cpc * clicks / views
def cpa_to_ecpm(views, actions, cpa):
return 1000. * cpa * actions / views
輔助函式的解釋
請注意,在我們的輔助函式中,我們直接使用了點選次數、瀏覽次數和操作次數,而不是計算出的CTR。這讓我們能夠直接在我們的會計系統中保留這些值,只在必要時計算eCPM。另外,請注意,對於我們的用途,CPC和CPA是相似的,主要區別在於,對於大多數廣告,操作次數明顯低於點選次數,但每次操作的價值通常遠高於每次點選的價值。
將廣告插入索引
在目標定位廣告時,我們將有一組可選和必需的目標引數。為了正確地目標定位廣告,我們的廣告索引必須反映目標引數。
索引廣告的準備
在我們能夠討論索引廣告之前,我們必須首先確定如何以一致的方式衡量廣告的價值。
#### 內容解密:
上述程式碼定義了兩個函式:cpc_to_ecpm
和 cpa_to_ecpm
,用於將CPC和CPA廣告轉換為eCPM。這兩個函式的邏輯相似,都是透過將廣告的價值乘以其相關的比率(點選率或操作率),然後乘以1000來計算eCPM。這樣做的目的是將不同型別的廣告轉換為相同的衡量標準,以便於比較和選擇最有價值的廣告進行顯示。
這些函式的引數包括瀏覽次數、點選次數或操作次數,以及CPC或CPA的價值。它們直接使用這些原始資料而不是預先計算的CTR或操作率,這使得系統能夠直接從會計系統中取得資料,只在需要時才計算eCPM。這種設計提高了系統的靈活性和效率。
在未來的發展中,可以考慮新增更多的目標引數,如使用者的興趣、行為等,以提高廣告的精準度和點選率。同時,也可以最佳化廣告的選擇演算法,以更好地平衡廣告的價值和使用者的體驗。
廣告伺服器架構圖
graph LR A[使用者請求] --> B[廣告伺服器] B --> C[目標引數選擇] C --> D[廣告索引] D --> E[eCPM計算] E --> F[廣告選擇] F --> G[廣告傳回使用者]
圖表翻譯:
此圖示展示了廣告伺服器的基本架構和運作流程。當使用者發出請求時,廣告伺服器接收請求並根據目標引數選擇合適的廣告。廣告索引模組根據廣告的特性和目標引數進行索引。eCPM計算模組計算每個廣告的預估CPM,以評估其價值。最後,廣告選擇模組根據eCPM選擇最有價值的廣告並傳回給使用者。
第7章:根據搜尋的應用程式 - 廣告投放
在前面的章節中,我們討論瞭如何使用Redis建立搜尋功能。在本章中,我們將進一步探討如何利用搜尋功能來實作廣告投放。廣告投放是根據使用者的位置和網頁內容來選擇最合適的廣告進行顯示。
7.3.1 廣告投放需求
廣告投放需要滿足兩個主要條件:位置和內容。位置是必須的,可以是城市、州或國家級別。內容則是可選的,如果廣告內容與網頁內容相符,則會獲得額外的加分。
7.3.2 建立廣告索引
為了實作廣告投放,我們需要建立廣告索引。廣告索引使用倒排索引(inverted index)來儲存廣告ID。我們使用SET和ZSET來儲存廣告ID。
TO_ECPM = {
'cpc': cpc_to_ecpm,
'cpa': cpa_to_ecpm,
'cpm': lambda *args: args[-1],
}
def index_ad(conn, id, locations, content, type, value):
pipeline = conn.pipeline(True)
for location in locations:
pipeline.sadd('idx:req:' + location, id)
words = tokenize(content)
for word in words:
pipeline.zadd('idx:' + word, {id: 0})
rvalue = TO_ECPM[type](1000, AVERAGE_PER_1K.get(type, 1), value)
pipeline.hset('type:', id, type)
pipeline.zadd('idx:ad:value:', {id: rvalue})
pipeline.zadd('ad:base_value:', {id: value})
pipeline.sadd('terms:' + id, *list(words))
pipeline.execute()
內容解密:
index_ad
函式:用於為廣告建立索引,引數包括廣告ID、目標位置、廣告內容、廣告型別和廣告價值。pipeline
物件:用於批次執行Redis命令,以提高效能。sadd
命令:將廣告ID加入到對應位置的SET中,用於位置定向。zadd
命令:將廣告ID和對應的分數加入到ZSET中,用於儲存廣告的eCPM值。tokenize
函式:用於將廣告內容分詞,提取關鍵字。TO_ECPM
字典:用於根據廣告型別計算eCPM值。
7.3.3 廣告投放
廣告投放的過程包括:首先根據使用者的位置找到符合的廣告,然後根據網頁內容對廣告進行加分,最後選擇eCPM值最高的廣告進行顯示。
def target_ads(conn, locations, content):
pipeline = conn.pipeline(True)
matched_ads, base_ecpm = match_location(pipeline, locations)
words, targeted_ads = finish_scoring(pipeline, matched_ads, base_ecpm, content)
pipeline.incr('ads:served:')
pipeline.zrevrange('idx:' + targeted_ads, 0, 0)
target_id, targeted_ad = pipeline.execute()[-2:]
內容解密:
target_ads
函式:用於根據使用者的位置和網頁內容選擇最合適的廣告。match_location
函式:用於根據使用者的位置找到符合的廣告。finish_scoring
函式:用於根據網頁內容對廣告進行加分。incr
命令:用於增加廣告服務的計數器。zrevrange
命令:用於取得eCPM值最高的廣告ID。
廣告投放系統可以進一步最佳化,例如可以使用機器學習演算法來提高廣告投放的準確性。此外,可以增加更多的定向條件,例如使用者的興趣愛好、行為特徵等,以提高廣告投放的效果。
graph LR A[使用者請求] --> B[廣告投放系統] B --> C[根據位置找到符合的廣告] C --> D[根據網頁內容對廣告進行加分] D --> E[選擇eCPM值最高的廣告] E --> F[顯示廣告]
圖表翻譯: 此圖示展示了廣告投放系統的工作流程,包括根據使用者的位置和網頁內容選擇最合適的廣告進行顯示。