成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

多級緩存架構:深度解析與實踐

開發 架構
常見的多級緩存架構為本地緩存 + 分布式緩存(Redis)+ 數據庫(DB),足以滿足大部分場景;若需支撐更高量級查詢,可將緩存進一步前置。

一、多級緩存架構概述

  • 在互聯網電商場景中,由于讀、寫請求量級大且響應時間(RT)要求低,多級緩存架構旨在提升讀請求性能。
  • 緩存本質是數據冗余,將數據逐層冗余放置在離用戶更近、速度更快但容量更小、價格更貴的存儲系統上,以提高系統訪問性能。
  • 常見的多級緩存架構為本地緩存 + 分布式緩存(Redis)+ 數據庫(DB),足以滿足大部分場景;若需支撐更高量級查詢,可將緩存進一步前置。

性能瓶頸

  • DB層:性能瓶頸在于磁盤讀取的磁盤IO,將數據移至內存中的Redis可提升性能。
  • Redis層:性能受限于網絡IO,JVM應用請求Redis時需發起IO請求,使用JVM本地緩存可減少此消耗。
  • JVM本地緩存層:性能受限于Tomcat服務器,單個服務器處理并發請求有限,可通過增加Tomcat服務數量(如K8s多Pod部署)或把緩存前置到Nginx提升性能。
  • Nginx層:Nginx是高性能Web服務器,單機處理并發請求量可達數萬,可選擇在Nginx本地內存存儲部分數據或通過Lua腳本訪問Redis數據;使用兩層Nginx架構,接入層Nginx負責流量分發,應用層Nginx處理業務邏輯和熱點緩存讀取。
  • 緩存層次并非越多越好,需根據實際情況選擇,互聯網常用多級緩存為JVM本地緩存 + Redis緩存,對于極熱點數據可采用Nginx + JVM本地緩存 + Redis緩存三級架構。
  • JVM緩存存放熱點數據,避免其使Redis分片崩潰,但僅在查詢Redis數據時將數據放入本地緩存會導致命中率不高,通常需要熱點探測系統將熱點數據放入JVM本地緩存,許多大廠都有自研熱點探測系統,如得物的Burning、京東的hotkey。

二、多級緩存請求流程

圖片圖片

  • 用戶請求進入接入層Nginx后,會被負載均衡算法分發到各應用層Nginx。
  • 在應用層Nginx上,先讀取本地緩存,減少對后端服務沖擊;
  • 若未命中,則讀取Redis緩存,以減輕Tomcat集群壓力;
  • 若Redis緩存未命中,進入JVM應用層,先讀取JVM本地內存;
  • 若JVM本地內存未命中,訪問Redis集群;
  • 若Redis集群未命中,最后訪問DB。

多級緩存架構應用可根據實際情況逐步完善,初始使用DB,DB有瓶頸時添加Redis,Redis無法支撐時將熱數據放至JVM本地內存,JVM內存也無法支撐時,將緩存前置到Nginx層。

簡單示例:

public class MultiLevelCache {
    private Cache<String, String> jvmCache;
    private Jedis redis;

    public MultiLevelCache() {
        jvmCache = Caffeine.newBuilder()
             .maximumSize(100)
             .build();
        redis = new Jedis("localhost", 6379);
    }

    public String getProductName(String productId) {
        // 首先嘗試從 JVM 本地緩存獲取
        String productName = jvmCache.getIfPresent("product:" + productId + ":name");
        if (productName == null) {
            // 若 JVM 本地緩存未命中,嘗試從 Redis 緩存獲取
            productName = redis.get("product:" + productId + ":name");
            if (productName!= null) {
                // 將 Redis 中命中的數據更新到 JVM 本地緩存
                jvmCache.put("product:" + productId + ":name", productName);
            } else {
                // 若 Redis 緩存未命中,從數據庫獲取
                productName = fetchFromDatabase(productId);
                if (productName!= null) {
                    // 將數據存儲到 Redis 緩存
                    redis.set("product:" + productId + ":name", productName);
                    // 同時存儲到 JVM 本地緩存
                    jvmCache.put("product:" + productId + ":name", productName);
                }
            }
        }
        return productName;
    }

    private String fetchFromDatabase(String productId) {
        // 模擬從數據庫讀取數據,這里使用 MySQL 示例代碼
        MySQLDataAccess mySQLDataAccess = new MySQLDataAccess();
        return mySQLDataAccess.fetchProductName(productId);
    }

    public static void main(String[] args) {
        MultiLevelCache cache = new MultiLevelCache();
        String productName = cache.getProductName("1");
        System.out.println("Product Name: " + productName);
    }
}
class MySQLDataAccess {
    private static final String JDBC_URL = "jdbc:mysql://localhost:3306/your_database";
    private static final String JDBC_USER = "username";
    private static final String JDBC_PASSWORD = "password";

    public String fetchProductName(String productId) {
        String productName = null;
        try (Connection connection = DriverManager.getConnection(JDBC_URL, JDBC_USER, JDBC_PASSWORD)) {
            String query = "SELECT name FROM products WHERE id =?";
            try (PreparedStatement preparedStatement = connection.prepareStatement(query)) {
                preparedStatement.setInt(1, Integer.parseInt(productId));
                try (ResultSet resultSet = preparedStatement.executeQuery()) {
                    if (resultSet.next()) {
                        productName = resultSet.getString("name");
                    }
                }
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return productName;
    }
}

三、負載均衡算法的選擇

  • 常用負載均衡算法:

輪詢:優勢在于負載均衡,但相同請求會被轉發到不同節點,節點增多時緩存命中率會降低。

一致性哈希:相同請求會路由到同一臺機器,節點宕機僅會使少量緩存數據失效,但會導致大量請求集中在某臺機器。

  • 算法選擇依據:負載較低時,更追求緩存命中率,可使用一致性哈希;負載較高時,對熱點數據訪問多,更希望請求平均分散,不建議使用一致性哈希,可選擇輪詢,避免如Redis Cluster中使用一致性哈希時節點掛掉引發的雪崩問題。

四、應用層Nginx本地緩存實現

圖片圖片

  • 可使用Lua Shared Dict實現,這是OpenResty提供的功能,OpenResty集成了Nginx和Lua,可開發業務邏輯進行熱點數據的查詢和獲取。
  • 通過Nginx + Lua可從Nginx本地內存或遠程Redis獲取數據。
  • 應用層Nginx處理第一層數據并存儲熱點數據,會將請求上報到熱點發現系統進行統計,熱點數據還可放在JVM本地緩存。
  • 互聯網公司的熱點數據探測系統,如京東的hotkey,主要用于探測MySQL、Redis中頻繁訪問數據,避免因惡意攻擊、爬蟲請求、機器人導致的熱點數據使Redis分片癱瘓。以往使用JVM本地緩存 + Redis緩存的二級緩存方式,對于熱點數據命中率較低,因此需要統一熱點key探測方案,將熱點數據推送到JVM本地緩存。
  • 京東 hotkey:https://mp.weixin.qq.com/s/xOzEj5HtCeh_ezHDPHw6Jw
  • 得物 Burning:https://tech.dewu.com/article?id=23

簡單示例:

-- 在 Nginx 配置文件中
http {
    lua_shared_dict my_cache 10m;  -- 定義一個 10MB 的共享字典

    server {
        location /cache {
            content_by_lua_block {
                local cache = ngx.shared.my_cache
                local key = ngx.var.arg_key
                local value = cache:get(key)
                if value == nil then
                    -- 從 Redis 獲取數據
                    local redis = require "resty.redis"
                    local red = redis:new()
                    red:connect("localhost", 6379)
                    value = red:get(key)
                    red:close()
                    if value ~= nil then
                        cache:set(key, value)  -- 將數據存儲到本地緩存
                    end
                end
                ngx.say(value)
            }
        }
    }
}

五、數據緩存優化

  • 緩存數據過期時間:

設置過期時間:適合熱點、易更新數據,如庫存數據,可短時間允許不一致。

不設置過期時間:適合非熱點、長期訪問數據,如用戶信息、店鋪信息等。

  • 緩存數據淘汰:
  • 對于設置過期時間的數據到期自動刪除,不設置過期時間的數據,當緩存空間滿時,需通過淘汰策略刪除。
  • 淘汰策略有LRU(根據訪問時間淘汰最久未訪問數據,但大批量數據訪問會使命中率下降)、LFU(根據訪問頻率淘汰最不常訪問數據,數據訪問內容變化大時命中率會下降)、ARC(結合LRU和LFU優點)。
  • 緩存加載和更新:可使用緩存旁路模式,先寫數據庫,再寫緩存;更新時先更新數據庫,再刪除緩存。
  • 增量化緩存重建:對于復雜數據,緩存重建成本高,可通過維度劃分和根據增量數據變更僅重建對應維度緩存,降低重建成本,如對商品的不同維度信息(基礎信息、圖片等)進行劃分,按更新維度重建緩存。

六、緩存數據一致性保證

  • 多級緩存的特性差異:分布式緩存Redis中一份數據只存儲一次,可存儲較多數據;JVM緩存中熱點數據在每個節點存儲,且本地緩存容量小,存儲少量數據,因此更新策略不同。
  • Redis數據一致性:

同步刪除緩存數據 - 旁路緩存策略:讀場景:先從緩存讀取,命中則返回,未命中從數據庫讀取;寫場景:先更新數據庫,再失效緩存,能滿足大部分場景的數據一致性,但在極端情況(讀寫操作時序錯亂)下會出現數據不一致問題。

同步刪除緩存數據 - 延時雙刪:兩次刪除緩存,第一次快速達到最終一致性,第二次延時刪除可能的臟數據;需考慮延時時間設置(大于讀操作時間 + 數據庫主從同步延時時間),可使用異步線程執行第二次刪除,但存在延時時間不準確和性能消耗問題。

異步刪除緩存數據 - 基于binlog實現:如阿里巴巴的Canal監聽binlog實現緩存更新,Canal模擬MySQL從庫,接收主庫binlog,Server解析存儲,客戶端與Server通信獲取binlog;

Canal 1.1.1版本后支持將binlog投遞至MQ,可多客戶端消費,實現大量數據的緩存更新;但對于大多數場景,使用緩存旁路策略已足夠,引入Canal會使架構復雜且增加維護成本。

簡單示例:

public class RedisCacheConsistency {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("localhost", 6379);
        String key = "product:1:price";
        // 先更新數據庫(這里省略數據庫更新代碼)
        // 立即刪除緩存
        jedis.del(key);
        // 延時 500 毫秒后再次刪除緩存,確保臟數據清除
        new Thread(() -> {
            try {
                TimeUnit.MILLISECONDS.sleep(500);
                jedis.del(key);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
        jedis.close();
    }
}
  • 本地緩存數據一致性:

對于少量熱點數據,可采用主動更新 + 過期時間的方式刷新本地緩存。

對于不同更新頻率的數據,可設置不同過期時間并配合定時任務刷新,如不易變化的數據可設置過期時間為30min,配合1min更新一次的定時任務;變化頻繁的數據可設置過期時間為1min,配合30ms更新一次的定時任務。

可使用本地雙緩存策略,創建兩份過期時間不同的本地緩存,第一份寫后30min過期,第二份讀寫后40min過期;讀寫操作以第一份緩存為主,第一份過期后可從第二份讀取,同時更新第一份緩存,第二份作為備用。

             本地雙緩存刷新通過定時任務完成,檢查緩存數據失效后從遠程獲取數據并放入雙緩存。

  • 緩存最終一致性的保證:設置緩存過期時間,最終會刪除緩存,達到最終一致性;軟件工程中無法保證絕對一致性,如Linux的PageCache在服務器異常關機時會有數據丟失,軟件層面更難避免數據不一致。

簡單示例:

public class LocalCacheConsistency {
    // 本地緩存
    private Cache<String, String> cache;
    // 模擬的數據庫訪問服務
    private DatabaseService databaseService;
    // 定時任務執行器
    private ScheduledExecutorService executorService;

    public LocalCacheConsistency() {
        // 初始化本地緩存,設置最大容量和過期時間
        cache = Caffeine.newBuilder()
              .maximumSize(100)
              .expireAfterWrite(1, TimeUnit.MINUTES)
              .build();
        databaseService = new DatabaseService();
        // 創建定時任務執行器
        executorService = Executors.newSingleThreadScheduledExecutor();
        // 啟動定時刷新任務
        startCacheRefreshTask();
    }

    // 從本地緩存獲取數據
    public String get(String key) {
        return cache.getIfPresent(key);
    }

    // 存儲數據到本地緩存
    public void put(String key, String value) {
        cache.put(key, value);
    }

    // 從數據庫獲取數據并更新本地緩存
    public void refreshCache(String key) {
        String value = databaseService.fetchData(key);
        if (value!= null) {
            cache.put(key, value);
        }
    }

    // 啟動定時刷新緩存任務
    private void startCacheRefreshTask() {
        executorService.scheduleAtFixedRate(() -> {
            // 這里可以根據實際情況選擇要刷新的緩存鍵,例如全部刷新或只刷新部分熱點鍵
            // 這里假設我們要刷新所有緩存鍵,你可以修改為更具體的邏輯
            refreshAllCacheKeys();
        }, 0, 30, TimeUnit.SECONDS); // 每 30 秒刷新一次
    }

    // 刷新所有緩存鍵
    private void refreshAllCacheKeys() {
        // 假設緩存鍵的前綴是 "product:",這里可以根據具體業務進行修改
        for (int i = 1; i <= 100; i++) {
            String key = "product:" + i;
            refreshCache(key);
        }
    }

    // 模擬的數據庫服務
    private static class DatabaseService {
        public String fetchData(String key) {
            // 這里可以實現具體的數據庫查詢邏輯,這里僅作模擬
            System.out.println("Fetching data from database for key: " + key);
            return"Value for " + key;
        }
    }
}

七、最后

在實際使用中,需從業務場景、數據不一致時間、實現成本和維護成本等多方面評估并選擇合適方案,通過壓測和實驗對比優劣。

責任編輯:武曉燕 來源: 一安未來
相關推薦

2022-06-13 10:23:34

Helios緩存服務端

2024-08-30 09:53:17

Java 8編程集成

2024-09-19 08:49:13

2013-04-07 17:57:16

SDN網絡架構

2024-05-06 00:00:00

GAC代碼緩存

2023-12-04 16:18:30

2016-08-23 10:50:50

WebJavascript緩存

2010-11-25 09:37:14

MySQL查詢緩存機制

2024-07-08 07:30:47

2023-05-05 06:13:51

分布式多級緩存系統

2025-05-12 01:33:00

異步函數Promise

2019-10-21 09:32:48

緩存架構分層

2025-01-02 10:19:18

2025-05-07 03:22:00

2024-11-18 16:15:00

2024-12-24 14:01:10

2024-12-20 16:46:22

Spring三級緩存

2023-09-28 08:34:26

Docker微服務

2009-07-30 09:23:53

Java JNI

2025-02-25 10:21:15

點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 亚洲午夜久久久 | 中文字幕精品一区二区三区精品 | 日韩欧美三区 | 中文字幕精品视频在线观看 | 日韩在线视频免费观看 | 日韩精品中文字幕在线 | 欧美九九九 | 欧美日韩视频一区二区 | 欧美日韩在线免费观看 | 国产精品视频网 | 中文字幕久久精品 | 国产精品久久久久久福利一牛影视 | 男人天堂手机在线视频 | 羞羞视频网页 | 国产福利小视频 | 国产精品久久久久久久午夜 | 天天天天天天天干 | 国产精品二区三区 | 自拍在线| 亚欧午夜| 欧美一区二区在线 | 中文字幕免费在线 | 999国产精品视频免费 | 亚洲成人av一区二区 | 日中文字幕在线 | 三级黄片毛片 | 日韩在线国产精品 | 久久久久久久一区 | 久久久www成人免费无遮挡大片 | 日本一二三区在线观看 | 日本午夜网 | 久久精品视频在线播放 | 中文字字幕在线中文乱码范文 | 亚洲欧美国产一区二区三区 | 国产乱码一二三区精品 | 欧美一区二区三区视频 | 一区二区三区日本 | 亚洲黄色av | 欧美一级特黄aaa大片在线观看 | 久久av一区 | 农村黄性色生活片 |