淘寶二面:MySQL里有2000萬條數據,但是Redis中只存20萬的數據,如何保證redis中的數據都是熱點數據?

来源:https://www.cnblogs.com/coderacademy/p/18135195
-Advertisement-
Play Games

本文介紹瞭如何結合LFU淘汰策略與訪問頻率優化,實現在電商平臺等業務場景下,精準管理Redis中20萬熱點數據。 ...


引言

在當今互聯網領域,尤其在大型電商平臺如淘寶這樣的複雜分散式系統中,數據的高效管理和快速訪問至關重要。面對數以千萬計的商品、交易記錄以及其他各類業務數據,如何在MySQL等傳統關係型資料庫之外,藉助記憶體資料庫Redis的力量,對部分高頻訪問數據進行高效的緩存處理,是提升整個系統性能的關鍵一環。

比如淘寶,京東,拼多多等電商系統每日處理的訂單量級龐大,其資料庫中存儲的商品、用戶信息及相關交易數據可達數千萬條。為了降低資料庫查詢的壓力,加速數據讀取,Redis常被用於搭建二級緩存系統,以容納部分最為活躍的“熱點數據”。然而,在資源有限的情況下,如何確保僅有的20萬條緩存數據精準匹配到系統中的熱點數據,避免頻繁的冷數據替換熱數據導致的緩存失效,這就涉及到了一套精密的數據管理策略和緩存淘汰機制的設計。

本文將圍繞這一實戰場景展開討論:在MySQL擁有2000萬條數據的前提下,如何確保Redis僅緩存的20萬條數據全都是系統中的熱點數據,從而最大程度上發揮緩存的優勢,提高系統的響應速度和併發能力,進而提升用戶的購物體驗和服務質量。通過對Redis內部機制的深入理解以及對業務場景的精細分析,我們將揭示一套綜合運用各種技術手段來確保Redis中熱點數據準確有效的管理方案。

image.png

技術背景

在探討如何確保Redis中存儲的20萬數據均為熱點數據之前,首先需要明確MySQL與Redis在實際業務環境中的互補關係以及Redis自身的記憶體管理和數據淘汰機制。

MySQL與Redis的關係及應用場景

MySQL作為一種成熟的關係型資料庫管理系統,適用於存儲大量持久化且具有複雜關係的數據,其強大的事務處理能力和安全性保障了數據的一致性和完整性。但在大規模併發環境下,尤其是對那些讀多寫少、訪問頻次極高的熱點數據,直接從MySQL中讀取可能會成為系統性能瓶頸。

Redis則是一種高性能的記憶體鍵值資料庫,以其極快的速度和靈活的數據結構著稱。在淘寶這類大型電商平臺中,Redis主要用於緩存頻繁訪問的數據,例如熱門商品信息、用戶購物車、會話狀態等,以此減輕主資料庫的壓力,提高響應速度,增強系統的可擴展性和容錯性。

對於Redis高性能原理,請參考:京東二面:Redis為什麼快?我說Redis是純記憶體操作的,然後他對我笑了笑。
對於Redis的使用的業務場景,請參考:美團一面:項目中使用過Redis嗎?我說用Redis做緩存。他對我哦了一聲

Redis記憶體管理和數據淘汰機制簡介

Redis的所有數據都存儲在記憶體中,這意味著它的容量相較於磁碟存儲更為有限。為瞭解決記憶體容量不足的問題,Redis提供了多種數據淘汰策略。其中,與保證熱點數據密切相關的是LFU(Least Frequently Used)策略,它能夠根據數據對象的訪問頻次,將訪問次數最少(即最不常用)的數據淘汰出記憶體,以便為新的數據騰出空間。

對於Redis高性能的一方面原因就是Redis高效的管理記憶體,具體請參考:京東二面:Redis為什麼快?我說Redis是純記憶體操作的,然後他對我笑了笑。

此外,Redis允許用戶根據自身需求選擇不同的淘汰策略,例如“volatile-lfu”只針對設置了過期時間的key採用LFU演算法,“allkeys-lfu”則對所有key都執行LFU淘汰規則。

熱點數據定義及其識別方法

熱點數據是指在一定時間內訪問頻率極高、對系統性能影響重大的數據集。在電商平臺中,這可能表現為熱銷商品詳情、活動頁面信息、用戶高頻查詢的搜索關鍵詞等。識別熱點數據主要依賴於對業務日誌、請求統計和系統性能監控工具的分析,通過收集和分析用戶行為數據,發現並量化哪些數據是系統訪問的熱點,以便有針對性地將它們緩存至Redis中。

實現方案

在實際應用中,確保Redis中存儲的數據為熱點數據,我們可以從以下幾個方案考慮實現。

LFU淘汰策略

Redis中的LFU(Least Frequently Used)淘汰策略是一種基於訪問頻率的記憶體管理機制。當Redis實例的記憶體使用量達到預先設定的最大記憶體限制(由maxmemory配置項指定)時,LFU策略會根據數據對象的訪問頻次,將訪問次數最少(即最不常用)的數據淘汰出記憶體,以便為新的數據騰出空間。

LFU演算法的核心思想是通過跟蹤每個鍵的訪問頻率來決定哪些鍵應當優先被淘汰。具體實現上,Redis並非實時精確地計算每個鍵的訪問頻率,而是採用了近似的LFU方法,它為每個鍵維護了一個訪問計數器(counter)。每當某個鍵被訪問時,它的計數器就會遞增。隨著時間推移,Redis會依據這些計數器的值來決定淘汰哪些鍵。

在Redis 4.0及其後續版本中,LFU策略可以通過設置maxmemory-policy配置項為allkeys-lfuvolatile-lfu來啟用。其中:

  • allkeys-lfu:適用於所有鍵,無論它們是否有過期時間,都會基於訪問頻率淘汰鍵。
  • volatile-lfu:僅針對設置了過期時間(TTL)的鍵,按照訪問頻率淘汰鍵。

Redis實現了自己的LFU演算法變體,它使用了一個基於訪問計數和老化時間的組合策略來更好地適應實際情況。這意味著不僅考慮訪問次數,還會考慮到鍵的訪問頻率隨時間的變化,防止長期未訪問但曾經很熱門的鍵占據大量記憶體空間而不被淘汰。在實現上,Redis使用了一種稱為“頻率跳錶(frequency sketch)”的數據結構來存儲鍵的訪問頻率,允許快速查找和更新計數器。為了避免長期未訪問但計數器較高的鍵永久保留,Redis會在一段時間後降低鍵的訪問計數,模擬訪問頻率隨時間衰減的效果。

在Redis中使用LFU淘汰策略,在配置文件redis.conf中找到maxmemory-policy選項,將其設置為LFU相關策略之一:

maxmemory-policy allkeys-lfu # 對所有鍵啟用LFU淘汰策略 
# 或者 
maxmemory-policy volatile-lfu # 對有過期時間的鍵啟用LFU淘汰策略

確保你也設置了Redis的最大記憶體使用量(maxmemory),只有當記憶體到達這個上限時,才會觸發淘汰策略:

maxmemory <size_in_bytes> # 指定Redis可以使用的最大記憶體大小

LFU策略旨在儘可能讓那些近期最不活躍的數據優先被淘汰,以此保持緩存中的數據相對活躍度更高,提高緩存命中率,從而提升系統的整體性能。(這也是我們面試中需要回答出來的答案)

LRU淘汰策略

Redis中的LRU(Least Recently Used)淘汰策略是一種用於在記憶體不足時自動刪除最近最少使用的數據以回收記憶體空間的方法。儘管Redis沒有完全精確地實現LRU演算法(因為這在O(1)時間內實現成本較高),但Redis確實提供了一種近似LRU的行為。

當我們配置了最大記憶體限制,如果記憶體超出這個限制時,Redis會選擇性地刪除一些鍵值對來騰出空間。Redis提供了幾種不同的淘汰策略,其中之一就是volatile-lruallkeys-lru,這兩種都試圖模擬LRU行為。

  • volatile-lru:僅針對設置了過期時間(TTL)的鍵,按照最近最少使用的原則來刪除鍵。
  • allkeys-lru:不論鍵是否設置過期時間,都會根據最近最少使用的原則來刪除鍵。

Redis實現LRU的方式並不是真正意義上的雙向鏈表加引用計數這樣的完整LRU結構,因為每個鍵值對的插入、刪除和訪問都需要維持這樣的數據結構會帶來額外的開銷。所以Redis實現LRU會採取以下方式進行:

  1. Redis內部為每個鍵值對維護了一個“空轉時間”(idle time)的欄位,它是在Redis實例啟動後最後一次被訪問或修改的時間戳。
  2. 當記憶體達到閾值並觸發淘汰時,Redis不會遍歷整個鍵空間找出絕對意義上的最近最少使用的鍵,而是隨機抽取一批鍵檢查它們的空轉時間,然後刪除這批鍵中最久未被訪問的那個。
    Redis在大多數情況下能較好地模擬LRU效果,有助於保持活躍數據在記憶體中,減少因頻繁換入換出帶來的性能損失。

記憶體淘汰策略通常是在Redis伺服器端的配置文件(如redis.conf)中設置,而不是在應用中配置。你需要在Redis伺服器端的配置中設置maxmemory-policy參數為allkeys-lru。(同LFU策略)

使用Redis的LRU淘汰策略實現熱點數據的方式,簡單易行,能較好地應對大部分情況下的熱點數據問題。但是若訪問模式複雜或數據訪問分佈不均勻,單純的LRU策略可能不夠精準,不能確保絕對的熱點數據留存。

結合訪問頻率設定過期時間

在實際應用中,除了依賴Redis的淘汰策略外,還可以結合業務邏輯,根據數據的訪問頻率動態設置Key的過期時間。例如,當某個Key被頻繁訪問時,延長其在Redis中的有效期,反之則縮短。

@Autowired
private RedisTemplate<String, Object> redisTemplate;

public void updateKeyTTL(String key, int ttlInSeconds) {
    redisTemplate.expire(key, ttlInSeconds, TimeUnit.SECONDS);
}

// 示例調用,當檢測到某個數據訪問增多時,增加其緩存過期時間
public void markAsHotSpot(String key) {
    updateKeyTTL(key, 3600); // 將熱點數據緩存時間延長至1小時
}

這種方式靈活性強,可根據實際訪問情況動態調整緩存策略。但是需要在應用程式中進行較多定製開發,以捕捉並響應數據訪問的變化。

基於時間視窗的緩存淘汰策略

在給定的時間視窗(如過去1小時、一天等)內,對每個數據項的訪問情況進行實時跟蹤和記錄,可以使用計數器或其他數據結構統計每條數據的訪問次數。到達時間視窗邊界時,計算每個數據項在該視窗內的訪問頻率,這可以是絕對訪問次數、相對訪問速率或者其他反映訪問熱度的指標。根據預先設定的閾值,將訪問次數超過閾值的數據項加入Redis緩存,或者將其緩存時間延長以確保其能在緩存中停留更久。而對於訪問次數低於閾值的數據項,要麼從緩存中移除,要麼縮短其緩存有效期,使其更容易被後續淘汰策略處理。

@Service
public class TimeWindowCacheEvictionService {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    private final Map<String, AtomicInteger> accessCounts = new ConcurrentHashMap<>();

    // 時間視窗長度(例如,1小時)
    private static final long TIME_WINDOW_MILLIS = TimeUnit.HOURS.toMillis(1);

    @Scheduled(fixedRate = TIME_WINDOW_MILLIS)
    public void evictBasedOnFrequency() {
        accessCounts.entrySet().forEach(entry -> {
            int accessCount = entry.getValue().get();
            if (accessCount > THRESHOLD) { // 假設THRESHOLD是訪問次數閾值
                // 將數據存入或更新到Redis緩存,並設置較長的過期時間
                redisTemplate.opsForValue().set(entry.getKey(), getDataFromDB(entry.getKey()), CACHE_EXPIRATION_TIME, TimeUnit.MINUTES);
            } else if (redisTemplate.hasKey(entry.getKey())) {
                // 訪問次數低,從緩存中移除或縮短過期時間
                redisTemplate.delete(entry.getKey());
            }
        });

        // 清零訪問計數器,準備下一個時間視窗
        accessCounts.clear();
    }

    public void trackDataAccess(String dataId) {
        accessCounts.computeIfAbsent(dataId, k -> new AtomicInteger()).incrementAndGet();
    }
}

關於@Scheduled是Springboot中實現定時任務的一種方式,對於其他幾種方式,請參考:玩轉SpringBoot:SpringBoot的幾種定時任務實現方式

通過這種方法,系統能夠基於實際訪問情況動態調整緩存內容,確保Redis緩存中存放的總是具有一定熱度的數據。當然,這種方法需要與實際業務場景緊密結合,並結合其他緩存策略共同作用,以實現最優效果。同時,需要註意此種策略可能帶來的額外計算和存儲成本。

手動緩存控制

針對已識別的熱點數據,可以通過監聽資料庫變更或業務邏輯觸發器主動將數據更新到Redis中。例如,當商品銷量劇增變為熱點商品時,立即更新Redis緩存。

這種方式可以確保熱點數據及時更新,提高了緩存命中率。

利用數據結構優化

使用Sorted Set等數據結構可以進一步精細化熱點數據管理。例如,記錄每個商品最近的訪問的活躍時間,並據此決定緩存哪些商品數據。

// 商品訪問活躍時更新其在Redis中的排序
String goodsActivityKey = "goods_activity";
redisTemplate.opsForZSet().add(goodsActivityKey, sku, System.currentTimeMillis());

// 定時清除較早的非熱點商品數據
@Scheduled(cron = "0 0 3 * * ?") // 每天凌晨3點清理前一天的數據
public void cleanInactiveUsers() {
    long yesterdayTimestamp = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1);
    redisTemplate.opsForZSet().removeRangeByScore(goodsActivityKey, 0, yesterdayTimestamp);
}

這種方式能夠充分利用Redis內建的數據結構優勢,實現複雜的數據淘汰邏輯。

實際業務中實踐方案

在例如淘寶這樣龐大的電商生態系統中,面對MySQL中海量的業務數據和Redis有限的記憶體空間,我們採用了多元化的策略以確保緩存的20萬數據是真正的熱點數據。

LFU策略的運用

自Redis 4.0起,我們可以通過配置Redis淘汰策略為近似的LFU(volatile-lfu 或 allkeys-lfu),使得Redis能夠自動根據數據訪問頻率進行淘汰決策。LFU策略基於數據的訪問次數,使得訪問越頻繁的數據越不容易被淘汰,從而更好地保持了熱點數據在緩存中的存在。

訪問頻率動態調整

除了依賴Redis內置的LFU淘汰策略,我們還可以實現應用層面的訪問頻率追蹤和響應式緩存管理。例如,每當商品被用戶訪問時,系統會更新該商品在Redis中的訪問次數,同時根據訪問頻率動態調整緩存過期時間,確保訪問頻率高的商品在緩存中的生存期得到延長。

@Service
public class ProductService {

    @Autowired
    private RedisTemplate<String, Product> redisTemplate;

    public void updateProductViewCount(String productId) {
        // 更新產品訪問次數
        redisTemplate.opsForValue().increment("product:view_count:" + productId);

        // 根據訪問次數調整緩存過期時間
        Long viewCount = redisTemplate.opsForValue().get("product:view_count:" + productId);
        if (viewCount > THRESHOLD_VIEW_COUNT) {
            redisTemplate.expire("product:info:" + productId, LONGER_CACHE_EXPIRATION, TimeUnit.MINUTES);
        }
    }
}

數據結構優化

我們還可以利用Redis豐富的數據結構,如有序集合(Sorted Sets)和哈希(Hashes),來實現商品熱度排行、用戶行為分析等功能。例如,通過Sorted Set存儲商品的瀏覽量,自動按照瀏覽量高低進行排序,並淘汰訪問量低的商品緩存。

// 更新商品瀏覽量並同步到Redis有序集合
public void updateProductRanking(String productId, long newViewCount) {
    redisTemplate.opsForZSet().add("product_ranking", productId, newViewCount);
    // 自動淘汰瀏覽量低的商品緩存
    redisTemplate.opsForZSet().removeRange("product_ranking", 0, -TOP_RANKED_PRODUCT_COUNT - 1);
}

總結

本文詳細闡述了在電商平臺例如淘寶及其他類似場景下,如何結合LFU策略與訪問頻率調整,優化Redis中20萬熱點數據的管理。通過配置Redis近似的LFU淘汰策略,結合應用層面對訪問頻率的實時追蹤與響應式調整,以及利用多樣化的Redis數據結構如有序集合和哈希表,成功實現了熱點數據的精確緩存與淘汰。

通過電商平臺的一些實際業務實踐證明,這種綜合策略可以有效提升緩存命中率,降低資料庫訪問壓力,確保緩存資源始終服務於訪問最頻繁的數據。未來隨著數據挖掘與分析技術的進步,以及Redis或其他記憶體資料庫功能的拓展,預計將進一步細化和完善熱點數據的識別與管理機制。例如,探索更具前瞻性的預測性緩存策略,或是結合機器學習模型對用戶行為進行深度分析,以更精準地預判和存儲未來的熱點數據。

本文已收錄於我的個人博客:碼農Academy的博客,專註分享Java技術乾貨,包括Java基礎、Spring Boot、Spring Cloud、Mysql、Redis、Elasticsearch、中間件、架構設計、面試題、程式員攻略等


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 鏡像分層的簡單直觀體現 在執行docker pull時,會發現多個Pull complete 字樣,就能體現分層,如果是一個文件,只會有一個Pull complete 。 docker pull redis Using default tag: latest latest: Pulling from ...
  • 極簡概括 官網:https://www.docker.com 利用比虛擬機更加輕量級的容器化虛擬技術,能夠低成本的把當前環境快速打包或在新環境部署相同子環境的運維工具,基於Go語言實現,跨平臺(支持Linux、Windows、MacOS)。 通俗類比:無論什麼牌子什麼價位的主機,都可以利用同一個的W ...
  • ESP32 Arduino開發 MQTT 目錄ESP32 Arduino開發 MQTT1. 安裝程式庫2. 編寫相關程式2.1. 引入頭文件2.2. 定義MQTT相關參數2.3. 創建對象2.4. 連接網路2.5. 連接MQTT伺服器2.6. MQTT回調函數3. 完整的代碼常式4. MQTT連接測 ...
  • 參考 參考閃客的系列,將開機到執行shell的整個過程濃縮成本文。 https://github.com/dibingfa/flash-linux0.11-talk bootsect.s 當按下開機鍵的那一刻,在主板上提前寫死的固件程式 BIOS 會將硬碟中啟動區的 512 位元組的數據,原封不動複製 ...
  • 目錄 目錄目錄購買伺服器環境要求硬體配置CPU記憶體磁碟網路軟體環境JRE(Java Runtime Environment)MySQL(可選)Web 伺服器(可選)Wget(可選)VIM(可選)瀏覽器支持名詞解釋~(符號)運行包工作目錄購買功能變數名稱伺服器安裝配置遠程連接阿裡雲網頁連接Xshell程式連接 ...
  • 以Flink為主的計算引擎配合OLAP查詢分析引擎組合進而構建實時數倉**,其技術方案的選擇是我們在技術選型過程中最常見的問題之一。也是很多公司和業務支持過程中會實實在在遇到的問題。 很多人一提起實時數倉,就直接大談特談Hudi,Flink的流批一體等,但實際上,**實時數倉包括任何架構體系的構建如... ...
  • 在實際項目中,從Kafka到HDFS的數據是每天自動生成一個文件,按日期區分。而且Kafka在不斷生產數據,因此看看kettle是不是需要時刻運行?能不能按照每日自動生成數據文件? 為了測試實際項目中的海豚定時調度從Kafka到HDFS的Kettle任務情況,特地提前跑一下海豚定時調度這個任務,看看 ...
  • 在Centos7中使用的包管理工具是yum,當然使用包管理工具安裝也是最方便的。 本文操作內容需要在root用戶下,否則有些步驟無法成功執行。 系統環境信息展示 安裝 MySQL 提供的 RPM wget https://dev.mysql.com/get/mysql80-community-rel ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...