在鏈接 http://www.dengb.com/Javabc/1409473.html 中發(fā)現(xiàn)了一道非常有趣的題目 --- “頭條文章向用戶推送避免重復(fù)推送的問題”,基于自己的思考提出一個簡單的設(shè)計,供大家吐槽,拋磚引玉。
解決這個問題,我首先做一些假設(shè):
- 頭條APP每次翻頁,會加載10篇文章 (主要是為了后面的一些定量的數(shù)字描述方便)
- 頭條后臺擁有海量文章,比用戶閱讀的歷史文章要多得多得多
基于以上的假設(shè),我提出了一個簡單的設(shè)計思路。
1. 最樸素,最基礎(chǔ)的查詢應(yīng)該如下:(當(dāng)然,where后面還有其他的條件,如分類等)
select * from 文章表 where 文章ID not in (閱讀過的ID列表) order by rand() limit 10
2. 首先我們想辦法優(yōu)化這個查詢
閱讀過的文章列表ID會越來越多,也就是in里面的條件越來越多,當(dāng)in的條件到一定程度后,可能會不走索引導(dǎo)致效率降低 (參考文檔2);另外,in 語句后面的參數(shù)可能會有限制 (參考文檔3)
優(yōu)化思路
因為我們有假設(shè)2) 文章海量,用戶閱讀歷史比較少,所以我們可以用下面的方法優(yōu)化查詢:
select 文章ID from 文章表 order by rand() limit 200; -- 第一步,先取20頁
select 文章ID from 閱讀歷史表 where userid=用戶ID and 文章ID in (第一步取到的文章ID) -- in語句條件可控
然后再將兩步文章ID的差集作為原始數(shù)據(jù)。
3. 查詢到的數(shù)據(jù)的使用
因為有第二步中有一些文章ID會被排除掉,所以不應(yīng)該用第二步的結(jié)果直接作為查詢結(jié)果返回給前端。我們可以:
- 把第二步中的文章ID緩存進(jìn)redis的list中,用prefix+用戶ID作為list的key
- 前端每次查詢走redis,做 lpop 10 ,拿出一頁數(shù)據(jù)
- 做完翻頁請求后,如果 list 中的數(shù)據(jù)總量少于兩頁,就在后臺啟動任務(wù)執(zhí)行步驟2,加載一批文章ID放進(jìn)緩存。
4. 緩存預(yù)熱
當(dāng)用戶首次使用APP時,我們只需要執(zhí)行以下SQL,并將文章ID放進(jìn)redis緩存,即可完成預(yù)熱。
select 文章ID from 文章表 order by rand() limit 200;
通過以上四步,即可做到:
- 保證用戶不會閱讀到重復(fù)的文章;
- 保證前端請求的查詢效率。
大家如果有其他的設(shè)計方法,歡迎交流切磋。