需求:
分頁求出進三天的發布視頻的權重
熱度 = 權重 / 衰減時間
衰減時間 = 當前時間 - 視頻發布時間 小根堆來實現
這個公式可以很好的利用半衰期來進行解決
難點:
如果一次性加載太多到springBoot服務器里面會造成堆內存占用過多,
分頁又有可能造成深分頁問題,因此選擇使用主鍵(雪花id)作為游標的快速分頁算法
流程:
1:找出當前時間-三天的最大視頻id
2:利用視頻id作為游標每次選擇1000個視頻計算熱度
3:插入到小根堆當中去
@Scheduled(cron = "30 * * * * ?") // 每分鐘的第30秒執行public void findTopK() { //更新增量表//應該優化一下,選擇三天之內最小發布的String tag = "0";Double lambda = 0.001;int K = 15;PriorityQueue<VideoInfo> minHeap = new PriorityQueue<>(Comparator.comparingDouble(v -> calculateWeight(v, lambda)));List<VideoInfo> videoList = videoInfoMapper.selectByGreaterThanVideoIdLimit1000(tag);while(videoList != null && videoList.size() != 0){for (VideoInfo video : videoList) {minHeap.offer(video);if (minHeap.size() > K) {minHeap.poll(); // 移除權重最小的視頻}}tag = videoList.get(videoList.size() - 1).getVideoId();videoList = videoInfoMapper.selectByGreaterThanVideoIdLimit1000(tag);}List<VideoInfo> topK = new ArrayList<>(minHeap);topK.sort((a, b) -> Double.compare(calculateWeight(b, lambda), calculateWeight(a, lambda)));cacheVideo.setHotVideos(topK);}/*** 計算半衰期權重* 權重 = (播放量 + 點贊量) * e^(-λ * 時間差)*/private static double calculateWeight(VideoInfo video, double lambda) {long currentTime = System.currentTimeMillis();long createTime = video.getCreateTime().getTime();long timeDiffSeconds = (currentTime - createTime) / 1000; // 轉為秒double decayFactor = Math.exp(-lambda * timeDiffSeconds);return (video.getPlayCount() + video.getLikeCount()) * decayFactor;}
雪花id介紹:
Mysql使用索引和order by
注意:using index表示 使用到了索引 , 并且所取的數據完全在索引中就能拿到
返回Using where 說明用戶要的字段不完全覆蓋,server層要進行過濾,或者進行了回表
"Using where" 表示 MySQL 服務器層需要對存儲引擎返回的行進行額外的過濾檢查
這種檢查可能發生在兩種情況下:
a) 存儲引擎返回的行不完全符合 WHERE 條件(需要二次過濾)
b) 需要從存儲引擎獲取完整行數據(即回表)
沒有索引的動用都是using where
-- 假設有索引 (a, b)
EXPLAIN SELECT a, b FROM table ORDER BY a, b;
排序和索引使用的一樣,因此會使用索引,不會再進行排序
-- 假設有索引 (a, b)
EXPLAIN SELECT a, b FROM table ORDER BY b, a;
會顯示"Using index; Using filesort",因為排序順序與索引不完全匹配
-- 假設有索引 (a, b)
EXPLAIN SELECT a, b FROM table ORDER BY b, a;
會顯示"Using index; Using filesort",因為排序順序與索引不完全匹配
深分頁問題:
MySQL必須讀取并丟棄大量不需要的數據才能到達目標分頁位置。
SELECT * FROM table INNER JOIN (SELECT id FROM table ORDER BY id LIMIT 10000, 20
) AS tmp USING(id);
優化1:
SELECT * FROM table INNER JOIN (SELECT id FROM table ORDER BY id LIMIT 10000, 20
) AS tmp USING(id);
優化2:
-- 記住上一頁最后一條記錄的ID
SELECT * FROM table
WHERE id > 上一頁最后ID
ORDER BY id
LIMIT 20;
優化3:
索引覆蓋