P153
經過改變程序搜索數據的方式,並使用 Redis 來減小絕大部分基於單詞或者關鍵字進行的內容搜索操做的執行時間。 P154
git
P154
倒排索引 (inverted indexes) 是互聯網上絕大部分搜索引擎使用的底層結構,它相似於書本末尾的索引。倒排索引從每一個被索引的文檔裏面提取一些單詞,並記錄包含每一個單詞的文檔集合。 P154
github
示例redis
假設有三個文檔:網絡
咱們就能獲得下面的倒排索引集合:數據結構
{2}
{2}
{0, 1, 2}
{0, 1, 2}
{0, 1}
檢索的條件 "what", "is" 和 "it" 將對應這個集合:{0,1} ∩ {0,1,2} ∩ {0,1,2} = {0,1}
異步
能夠發現 Redis 的集合和有序集合很是適合處理倒排索引。ide
基本索引操做函數
從文檔裏面提取單詞的過程一般被成爲語法分析 (parsing) 和標記化 (tokenization) ,這個過程能夠產生一系列用於表示文檔的標記 (token) ,有時又被成爲單詞 (word) 。 P155
性能
標記化的一個常見的附加步驟就是移除非用詞 (stop word) 。非用詞就是那些在文檔中頻繁出現卻沒有提供相應信息量的單詞,對這些單詞進行搜索將返回大量無用的結果。 P155
學習
本書中實現方向索引的邏輯很是簡單:
若是須要支持中文等,就不能簡單進行英文分詞,須要分詞器進行處理。第一次接觸倒排索引是在 Elasticsearch
中,感興趣的能夠了解 Elasticsearch
中倒排索引的實現以及 IK
中文分詞器。
基本搜索操做
在索引裏面查找一個單詞是很是容易的,只須要獲取單詞集合裏面的全部文檔便可。根據多個單詞查找文檔時,就須要根據條件處理對應的集合,再從最終集合中獲取全部文檔。 P156
可使用 Redis 的集合操做完成對不一樣條件的處理:
SINTER
/ SINTERSTORE
: 找出同時包含全部指定單詞的文檔集合SUNION
/ SUNIONSTORE
: 找出至少包含一個指定單詞的文檔集合SDIFF
/ SDIFFSTORE
: 找出包含某個單詞且包含其餘某些單詞的文檔集合經過以上三類命令,咱們基本能實現條件大部分的與或非操做。
分析並執行搜索
咱們使用的查詢語句進行分詞後具備如下特徵:
即: "connect +connection chat -proxy -proxies" 表示查詢的文檔須要包含 "connect" 或 "connection" ,同時也要包含 "chat" ,而且不能包含 "proxy" 和 "proxies" 。
實際處理時,先對同義詞組分別取並集,而後與須要查詢的單詞一塊兒取交集,最後與不但願包含的單詞取差集,這樣所獲得的集合就是用戶查詢的結果集。
P160
上述搜索功能以及可以搜索出用戶查詢的全部文檔惟一標識的集合,如今咱們將根據這個文檔惟一標識集合以及每一個文檔的具體信息進行排序分頁。
{1, 2, 276}
HASH
, 以 doc_{id}
爲鍵,內部存儲對應文檔的相關信息,例如: "doc:276": {"id": 276, "created": 1324114412, "updated": 132562777, "title": "Troubleshooting...", ...}
對於這種狀況咱們可使用 Redis 的 SORT
命令對文檔惟一標識集合經過引用每一個文檔的具體信息進行排序分頁。 (05. Redis 其餘命令簡介)
P162
上面介紹了使用 Redis 進行搜索,並經過引用存儲在 HASH
裏面的數據對搜索結果進行排序分頁。接下來將介紹利用集合和有序集合實現基於多個分值的複合排序操做,它能提供比 SORT
命令更高的靈活性。 P162
P162
假設咱們目前須要根據文檔對更新時間和得票數進行排序,爲此咱們須要用兩個有序集合存儲相關信息。這兩個有序集合的成員都是文檔惟一標識,成員的分值則分別是文檔的更新時間和得票數。
設通過搜索後知足搜索條件的文檔惟一標識集合爲 filtered_doc_ids
,文檔惟一標識及其更新時間對應的有序集合爲 doc_ids_with_update
,文檔惟一 標識及其得票數對應的有序集合爲 doc_ids_with_votes
。那麼能夠經過 ZINTERSTORE
命令對這三個集合求交集,最後得出的知足搜索條件的文檔惟一標識及其排序分值對應的有序集合,再使用 ZRANGE
, ZREVRANGE
進行分頁獲取便可。 P162
示例: ZINTERSTORE filtered_doc_ids_with_sort_score 3 filtered_doc_ids doc_ids_with_update doc_ids_with_votes WEIGHTS 0 {update_weight} {vote_weight}
其中:
filtered_doc_ids_with_sort_score
爲結果有序集合filtered_doc_ids
的權重爲 0
,僅用作篩選結果,不用於排序doc_ids_with_update
, doc_ids_with_votes
的權重能夠進行設置,爲 0
時表示不用於排序;爲其餘數時,表示對應字段對最終排序分所佔的權重,正數至關於該字段須要正序排序,負數至關於該字段須要倒序排序。所思
這種利用分值的方法很巧妙,基本能夠實現多字段排序,可是優先級可能難以掌控,難以作到先按照某一字段排序,再按照另外一字段排序的形式。由於每一個字段對應的分值的數量級可能差異比較小,這個時候若是須要有排序字段的優先級,那麼可能須要對每一個權重進行精巧地設計才行。
P164
上面介紹了使用有序集合對多個數值字段進行排序,因爲有序集合的分值只能是浮點數,因此非數值字段不能直接用於排序,須要轉換成對應的浮點數。但因爲雙精度浮點數只有 64 個二進制位,實際能使用 63 個二進制位,因此能表示的字符串並很少,只能使用字符串的前幾個字符進行分值估計,不足指定字符數的須要補齊到指定字符數。固然若是字符集縮小的話,能夠從新進行編碼計算,進而能夠對更長的字符串進行分值估計。 P165
當這個分值特別大的時候,可能會引起最終計算的分值溢出而出錯的問題。
P166
接下來將介紹使用集合和有序集合構建出一個幾乎完整的廣告服務平臺 (ad-serving platform) 。 P166
P167
針對廣告的索引操做和針對其餘內容的索引操做並無太大的不一樣,被索引的的廣告一般都擁有像位置、年齡和性別這類必需的定向參數,而且每每只會返回單個廣告。 P167
廣告的價格 P167
爲了儘量簡化廣告價格的計算方式,將對全部類型的廣告進行轉換,使得它們的價格能夠基於每千次展現進行計算,產生出一個估算 CPM (estimated CPM, eCPM) 。 P168
將廣告插入倒排索引 P169
咱們基本能夠複用上面提到的搜索功能,除了會將廣告的關鍵詞插入倒排索引,還會將廣告的定向參數(位置、年齡和性別等)插入倒排索引中,並記錄廣告的類型、基本價格和 eCPM 價格。 P169
P170
當系統收到廣告定向請求的時候,它要作的就是在匹配用戶定向參數的一系列廣告裏面,找出 eCPM 最高的那一個廣告。同時,程序還會記錄頁面內容與廣告內容的匹配程度,以及不一樣匹配程度對廣告點擊經過率的影響等統計數據。經過使用這些統計數據,廣告中與頁面相匹配的那些內容就會做爲附加值被計入 CPC 和 CPA 的 eCPM 價格,使得那些包含了匹配內容的廣告可以更多地被展現出來。 P170
計算附加值
計算附加值就是基於頁面內容和廣告內容二者之間匹配的單詞,計算出應該給廣告的 eCPM 價格加上多少增量。每一個單詞都有一個有序集合,成員爲廣告 id ,成員的分值爲當前單詞對這則廣告的 eCPM 的附加值。 P171
在尋找合適的廣告時,咱們首先會過濾出匹配定位位置且至少包含一個頁面單詞的廣告,而後經過計算附加值的方法替代搜索,以便實現每次投放價值最高的廣告,並可以根據用戶的行爲學習。同時因爲每一個廣告匹配的內容不一樣,最優方式應該是使用加權平均值來計算單詞部分的附加值,但限於 Redis 自己的命令,咱們最終採起 (max + min) / 2 的形式計算單詞部分的附加值(max 表示全部匹配單詞的最大附加值, min 表示全部匹配單詞的最小附加值),採用以下命令便可: ZUNIONSTORE final_score 3 base max min WEIGHTS 1 0.5 0.5
。
從用戶行爲中學習 P175
首先須要存儲用戶的瀏覽記錄,包括三部分:(每 100 次就主動更新一次 eCPM ) P175
其次須要存儲用戶的點擊和動做記錄,用於計算 點擊經過率 = 點擊量或動做次數 / 廣告展現次數。(每次都更新 eCPM) P176
最後就是更新 eCPM ,包括兩部分:
P179
RescaleItemViewedNum
函數進行按期下降廣告的展現次數和點擊次數(或者動做執行次數)P180
接下來將使用集合和有序集合實現職位搜索功能,並根據求職者擁有技能來爲他們尋找合適的職位。 P180
P180
第一反應確定是直接對每個求職者搜索全部的崗位,從而找到求職者合適的崗位。但這種方法效率極低(大部分崗位確定是技能對不上的),並且沒法進行性能擴展。 P181
P181
使用相似上面提到的附加值形式,每次添加一個崗位時,在對應的技能集合中添加這個崗位的 id (SADD idx:skill:{skill} {job_id}
),再在崗位有序集合中進行添加,成員爲崗位 id ,成員的分值爲所需的技能數量 (ZADD job_required_skill_count {job_id} {required_skill_count}
)。搜索的時候就先對求職者全部技能對應的集合使用 ZUNIONSTORE
操做計算每一個公司匹配的技能數量 (ZUNIONSTORE matched {n} idx:skill:{skill} ... WEIGHTS 1 ...
),而後再與崗位有序集合求交集,並讓公司有序集合的權重爲 -1 (ZINTERSTORE result 2 job_required_skill_count matched WEIGHTS -1 1
),最後獲取分值爲 0 的全部崗位便可完成搜索。 P181
所思
書上的這個方法比較麻煩,其實可使用文章最開始的無序倒排索引,崗位至關於要搜索的文檔,崗位所需的技能至關於單詞。
本文首發於公衆號:滿賦諸機(點擊查看原文) 開源在 GitHub :reading-notes/redis-in-action