引言:來自自創的小程序中熱點接口,本小程序專為在校學生自提點餐使用
一、功能描述
該功能作為一個推薦的職責,根據用戶最近行為給用戶推薦用戶可能喜歡去吃的店鋪,可能比較簡潔,但是需要設計的方面挺多的,需要有三個方面去走:
設計架構如下:
- 用戶行為采集
- 冷熱數據隔離
- 推薦店鋪緩存的預熱
- 猜你喜歡接口查詢(核心)
從這些設計上就能保證在使用接口查詢的時候,就不需要花很多時間去分析,保證了熱點接口的響應時間處于較低的一個狀態,不過對于一個熱點接口,不止要保證他的高性能,還需要保證他的高可用,這就需要涉及到降級策略,這些都是一個考量的標準。
二、接口功能實現
用戶行為采集
這里我使用到的技術棧有美團的mzt-Biz-Log框架做類似AOP的切面功能,保證用戶進店行為日志優雅的實現,加上redis進行統計猜你喜歡服務的用戶,使用rocketmq進行日志的統計,實現進店異步化,并且達到削峰限流的效果。
消息隊列的異步化,保證了用戶正常去訪問一個店鋪詳細的體驗,達到日志記錄無感化。
由于消息隊列是異步線程去處理的,傳遞用戶的消息過來很重要,更加方便去獲取到用戶線程id
- 這里使用到的就是美團的mzt-Biz-Log框架,進行優雅的日志記錄,后續可以新增一個實現類進行高內聚的做具體的日志完善邏輯
- 注意這里使用了全局唯一自增的id,后續在分表冷熱隔離中會被用到進行刪除數據的操作
使用類似AOP的思想對日志的完善,這里我對用戶數據分了16個表,保證熱點查詢不會聚集在單表中,具體的分表學習可以移步到我這篇文章進行學習:
SpringBoot中使用Sharding-JDBC實戰(實戰+版本兼容+Bug解決)_springboot shardingjdbc-CSDN博客文章瀏覽閱讀3.2k次,點贊23次,收藏37次。這里整理的是使用SpringBoot3.2.4和ShardingSphere-JDBC5.5.0進行分表操作,里面有遇到的bug并且解決的流程,還有結合自己之前做的秒殺博客進行測試分表,很詳細_springboot shardingjdbchttps://blog.csdn.net/qq_73440769/article/details/143992138?spm=1001.2014.3001.5502
冷熱數據隔離
這個功能主要由另外的一個博主進行實現,歡迎參考他的文章進行詳細學習:
優化:將針對單一日志表的冷熱數據分離類改造成通用類-CSDN博客文章瀏覽閱讀283次,點贊5次,收藏4次。文章介紹了店鋪推薦系統中日志數據存儲方案的優化過程。原方案將熱數據存入Elasticsearch,現改為存儲冷數據,并針對代碼冗余問題進行重構。通過引入泛型機制實現通用處理類,將固定代碼參數化,同時為不同日志類型提供定制化ES處理邏輯。優化后的方案提升了代碼復用性,支持多日志表并行處理,并通過線程池提高執行效率。文中詳細展示了改進后的代碼實現,包括通用日志處理方法、任務創建機制和重試策略等核心功能模塊。https://blog.csdn.net/2401_88959292/article/details/148619523?spm=1001.2014.3001.5502其實思想就是:使用定時任務進行隔離一個月之前的數據,將冷數據進行歸檔,保證熱數據才會被用來進行分析,這樣就能極大的削減db的承重,提升查詢效率、釋放 MySQL 壓力,這里隔離的數據庫可以不適用es,可以使用一些實時數倉比較合適。
- 每天凌晨執行,支持分頁游標 + 泛型處理;
- 支持所有進店日志的通用遷移;
- 使用多線程 + 重試機制處理批量插入,保障穩定性。
- 每類日志各跑一個線程任務,互不干擾
對于怎么不給時間加索引也能保證比較快速的查詢需要隔離的店鋪,首先id是自增的,查16個表中最靠近一個月的最小的id,比這個id小的就是一定需要進行刪除的,或者給時間字段加上索引也是可以的,實現也是使用類似的思想。
推薦店鋪緩存的預熱
目的:為了保證用戶的體驗,將復雜的分析過程放在了凌晨,使用定時任務進行分析,將分析好的結果放進redis,這樣子用戶體驗感就極佳,歡迎參考這位博主的思路:
優化日志分析店鋪推薦方案:用戶范圍的精確度以及ES與MySQL的查詢效率差異-CSDN博客文章瀏覽閱讀785次,點贊6次,收藏15次。本文針對原有店鋪推薦方案的兩個核心問題提出優化方案。問題一是用戶范圍不精確,通過Redis記錄用戶每月首次進店行為作為登錄標識,并統計熱點用戶(月登錄≥14天);問題二是數據處理效率低,改用MySQL存儲熱數據(建立用戶-店鋪映射),ES僅作冷備份。方案采用Lua腳本確保原子性操作,并優化了權重計算模型(店鋪50%、分類30%、分區20%)。最終實現通過分批并行處理用戶日志數據,結合個性化推薦與熱門店鋪補充機制,顯著提升了10萬級用戶日志場景下的推薦效率和精準度。https://blog.csdn.net/2401_88959292/article/details/148618437?spm=1001.2014.3001.5502
為避免推薦實時計算帶來性能瓶頸,我們采用“離線計算 + 緩存預熱”方式,利用定時任務每天構建用戶維度的推薦結果,緩存到 Redis 中。
- 用戶分批處理(活躍用戶訪問≥7天),每批200人,異步線程池分析;
- 推薦維度包括:訪問頻率、店鋪分類、分區,采用加權模型打分;
- 冷熱數據分離后,在構建推薦時能快速分析歷史行為;
- 最終結果緩存到 Redis 的
STORE_RECOMMEND_ONE:{userId}
列表中; - 未登錄用戶使用通用熱門推薦(Top 打分店鋪)。
這里講一下推薦維度的意義:
我們可以參考先成的推薦系統,比如抖音刷碎片,會有幾個考量:
- 比如你愛刷這個人視頻比較多,就會給你很多推薦;
- 比如你喜歡刷這類型視頻比較多,也會給你推薦很多類似的;
- 比如你比較喜歡刷當前地區的視頻,抖音也會給你推薦很多
參考上面的三點,就可以類比到我們的這些維度。我們會拿出日志,取出這些維度的數據,給每個維度進行賦分,各乘于一個百分比,最后相加得到最后得分,取出前四名的放進最后預熱的結果中。
這里大家可能會想,是給所有用戶進行分析嗎。回答是:不是。因為熱點分析的消耗成本是很大的,有些用戶不是小程序常駐,只是偶爾使用,甚至只是為了體驗一次,這些用戶的話推薦優先級就不是最高的,所以我們會在日志記錄的時候就記錄需要被推薦的用戶,這些用戶的考量標準是:一個月內最少使用一周,這里我使用了redis的自增id進行記錄有哪些用戶一個月內使用了多少次,用set集合保證一個用戶一天最多記錄一次就好,這里就可以得到我需要分析的用戶。
猜你喜歡接口查詢(核心)
這里是重中之重,這里需要保證高性能并且高可用,那就需要避免走db的路線,最好是走緩存。高可用就需要準備一些降級策略,避免服務掛掉。
這個是我對這個接口的設計流程圖,使用了多層的降級策略,保證了接口的高可用。
如果登錄的用戶沒有對應的推薦緩存或者沒有登錄的用戶,需要走降級策略:
1、第一層降級
這里會取出默認策略中推薦的緩存店鋪,如果有很特殊的情況導致這些緩存失效了,就需要繼續服務降級去完善默認推薦的緩存店鋪
2、第二層降級
這里如果沒有推薦店鋪就會去redis里面去隨機四個店鋪進行填充,這里為啥不用分布式鎖保證只有一個用戶更新默認推薦店鋪,可以這樣子理解,在一開始有預檢,一家很大程度的避免有不同推薦店鋪的行為,即使在短時間內有很多線程過了這個預檢測,我基于redis單線程的特性,指令在機器里面是單線程執行的特性,使用lua腳本保證各個線程執行指令的有序性,就能保證只有第一個線程的策略是實施的,后續線程的策略都不會成功,大家可能又會想,還是存在很多用戶推薦店鋪的數據不一致啊,再換個角度,我們這里的推薦店鋪都是一個隨機策略,哪怕是不同用戶的推薦不同又有什么問題呢嗎,下面給出lua腳本的實現:
其實大家會發現,這里使用了redis進行隨機,就可能極端情況下會出現緩存一致性的問題,出現這個問題,那就繼續服務降級。不過有個情況是不可能出現問題的,出現也會在及其短的時間內進行避免,可以關注下面這段代碼:
如果這個店鋪ids集合的數據量少于4,就會導致這些線程在不斷的更新默認店鋪的緩存,這豈不是災難性的,其實這個redis緩存和店鋪詳細緩存在小程序各個接口中多多少少都用到了,如果沒有了,點開小程序,也會啟動別的接口進行redis緩存的重構,對于我們目前小卡拉米小程序,還是不需要考慮這點帶來的影響,并發并沒美團那么高。
3、第三層降級
根據上一點講過的緩存不一致問題,也就是和db的不一致,redis中ids緩存和店鋪集合緩存不一致的問題,關于和db的不一致問題,很多方案都是加鎖,然后只用一個線程進行查db更新緩存,避免緩存擊穿;關于第二個不一致問題,我在那個流程圖里面有做了解釋,大家可以參考一下。
4、第四層降級
可以參考第三層降級里面,如果遇到了緩存穿透,就需要走空緩存的策略進一步一面,繼續服務降級,通過上面四層的降級,我覺得整個接口處于相對比較好的高可用狀態。
三、優化
可以看到上面第一版的方案中,我是拿到一個店鋪id就去redis查一個店鋪,這種就會帶來一個問題,無法充分利用redis的性能,對于傳輸網絡的開銷還是很大的,就會導致大部分時間浪費在網絡上了,所以我把多次用單個id去查redis改為一次用多個id去查redis。
1、優化方案實現
完善前:
完善后:
優化了多次網絡帶來的開銷:
第一次降級前代碼不變。
具體修改為多次查詢的邏輯:
具體使用:
默認策略修復:
2、性能測試
這里博主已經做了多次測試,下面的數據大致是均值的水平,所以只提供了一份測試數據(測試默認緩存帶來的優化)
(1)響應測試(ABtest):
優化前:
緩存重構的時候(203ms):
緩存不重構的時候(135ms):
優化后:
緩存重構的時候(155ms):
緩存不重構的時候(75ms):
在對推薦緩存邏輯進行優化后,系統整體響應時間得到了明顯改善。
- 在緩存不重構的場景下,響應時間從 135ms 降至 76ms,優化幅度約為 43.7%。
- 在緩存重構的場景中,響應時間從 203ms 降至 155ms,優化幅度約為 23.6%。
(2)jmeter測吞吐量
這里博主jmeter配置有些問題,雖然是異常,但是是正常走代碼邏輯的。
1000用戶qps1000測試(225.6/sec):
1用戶qps1000測試(654.9/sec):?
四、最后
歡迎大家給更多的建議,toc菜鳥希望可以得到更多好的方案進行學習,期待大家指點。
大家也可以關注一下這個博主,這個功能是由我們兩個共同進行一個實現和完善:
Yilena-CSDN博客Yilena擅長八股輕松學,業務場景方案分析以及優化方案,解決方案,等方面的知識https://blog.csdn.net/2401_88959292?type=blog