淘寶二面: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
  • GoF之工廠模式 @目錄GoF之工廠模式每博一文案1. 簡單說明“23種設計模式”1.2 介紹工廠模式的三種形態1.3 簡單工廠模式(靜態工廠模式)1.3.1 簡單工廠模式的優缺點:1.4 工廠方法模式1.4.1 工廠方法模式的優缺點:1.5 抽象工廠模式1.6 抽象工廠模式的優缺點:2. 總結:3 ...
  • 新改進提供的Taurus Rpc 功能,可以簡化微服務間的調用,同時可以不用再手動輸出模塊名稱,或調用路徑,包括負載均衡,這一切,由框架實現並提供了。新的Taurus Rpc 功能,將使得服務間的調用,更加輕鬆、簡約、高效。 ...
  • 本章將和大家分享ES的數據同步方案和ES集群相關知識。廢話不多說,下麵我們直接進入主題。 一、ES數據同步 1、數據同步問題 Elasticsearch中的酒店數據來自於mysql資料庫,因此mysql數據發生改變時,Elasticsearch也必須跟著改變,這個就是Elasticsearch與my ...
  • 引言 在我們之前的文章中介紹過使用Bogus生成模擬測試數據,今天來講解一下功能更加強大自動生成測試數據的工具的庫"AutoFixture"。 什麼是AutoFixture? AutoFixture 是一個針對 .NET 的開源庫,旨在最大程度地減少單元測試中的“安排(Arrange)”階段,以提高 ...
  • 經過前面幾個部分學習,相信學過的同學已經能夠掌握 .NET Emit 這種中間語言,並能使得它來編寫一些應用,以提高程式的性能。隨著 IL 指令篇的結束,本系列也已經接近尾聲,在這接近結束的最後,會提供幾個可供直接使用的示例,以供大伙分析或使用在項目中。 ...
  • 當從不同來源導入Excel數據時,可能存在重覆的記錄。為了確保數據的準確性,通常需要刪除這些重覆的行。手動查找並刪除可能會非常耗費時間,而通過編程腳本則可以實現在短時間內處理大量數據。本文將提供一個使用C# 快速查找並刪除Excel重覆項的免費解決方案。 以下是實現步驟: 1. 首先安裝免費.NET ...
  • C++ 異常處理 C++ 異常處理機制允許程式在運行時處理錯誤或意外情況。它提供了捕獲和處理錯誤的一種結構化方式,使程式更加健壯和可靠。 異常處理的基本概念: 異常: 程式在運行時發生的錯誤或意外情況。 拋出異常: 使用 throw 關鍵字將異常傳遞給調用堆棧。 捕獲異常: 使用 try-catch ...
  • 優秀且經驗豐富的Java開發人員的特征之一是對API的廣泛瞭解,包括JDK和第三方庫。 我花了很多時間來學習API,尤其是在閱讀了Effective Java 3rd Edition之後 ,Joshua Bloch建議在Java 3rd Edition中使用現有的API進行開發,而不是為常見的東西編 ...
  • 框架 · 使用laravel框架,原因:tp的框架路由和orm沒有laravel好用 · 使用強制路由,方便介面多時,分多版本,分文件夾等操作 介面 · 介面開發註意欄位類型,欄位是int,查詢成功失敗都要返回int(對接java等強類型語言方便) · 查詢介面用GET、其他用POST 代碼 · 所 ...
  • 正文 下午找企業的人去鎮上做貸後。 車上聽同事跟那個司機對罵,火星子都快出來了。司機跟那同事更熟一些,連我在內一共就三個人,同事那一手指桑罵槐給我都聽愣了。司機也是老社會人了,馬上聽出來了,為那個無辜的企業經辦人辯護,實際上是為自己辯護。 “這個事情你不能怪企業。”“但他們總不能讓銀行的人全權負責, ...