最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Redis 旁路緩存深度解析

 更新時間:2026年04月13日 08:25:10   作者:014-code  
旁路緩存是 Redis 最常用的緩存策略,本文主要介紹了Redis旁路緩存深度解析,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧

旁路緩存(Cache-Aside Pattern)是 Redis 最常用的緩存策略,通過"先查緩存,后查數(shù)據(jù)庫"的讀寫模式,顯著提升系統(tǒng)讀取性能

為什么需要緩存

在互聯(lián)網(wǎng)應(yīng)用中,數(shù)據(jù)庫通常是系統(tǒng)性能的瓶頸。當(dāng)面對高并發(fā)讀取請求時,直接查詢數(shù)據(jù)庫會造成:

  • 數(shù)據(jù)庫壓力過大:大量并發(fā)查詢會導(dǎo)致數(shù)據(jù)庫連接耗盡
  • 響應(yīng)延遲高:復(fù)雜查詢可能需要數(shù)十甚至數(shù)百毫秒
  • 擴展困難:通過增加數(shù)據(jù)庫實例來提升性能成本高昂

緩存的出現(xiàn)正是為了解決這一問題。通過將熱點數(shù)據(jù)存儲在內(nèi)存中,緩存能夠提供微秒級的訪問速度。

旁路緩存核心原理

旁路緩存(Cache-Aside Pattern)是最經(jīng)典的緩存應(yīng)用模式,其核心思想是:應(yīng)用層主動管理緩存,數(shù)據(jù)庫為主,緩存為從。

讀操作流程

public Product getProduct(Long id) {
    // 1. 先查緩存
    Product cachedProduct = redis.get("product:" + id);
    if (cachedProduct != null) {
        // 緩存命中,直接返回
        return cachedProduct;
    }
    // 2. 緩存未命中,查詢數(shù)據(jù)庫
    Product product = productMapper.findById(id);
    if (product != null) {
        // 3. 將數(shù)據(jù)寫入緩存,設(shè)置過期時間
        redis.set("product:" + id, product, 3600);
    }
    return product;
}

讀操作的關(guān)鍵步驟:

  1. 先從 Redis 讀取數(shù)據(jù)
  2. 緩存命中則直接返回
  3. 緩存未命中則查詢數(shù)據(jù)庫
  4. 將查詢結(jié)果寫入緩存并設(shè)置過期時間

寫操作流程

public void updateProduct(Product product) {
    // 1. 先更新數(shù)據(jù)庫
    productMapper.update(product);
    // 2. 再刪除緩存
    redis.del("product:" + product.getId());
}

寫操作采用先更新數(shù)據(jù)庫,后刪除緩存的策略,原因如下:

  • 保證數(shù)據(jù)一致性:即使緩存刪除失敗,數(shù)據(jù)庫數(shù)據(jù)仍是最新的
  • 避免并發(fā)問題:相比更新緩存,刪除緩存更能保證數(shù)據(jù)一致性
  • 簡化流程:不需要維護緩存與數(shù)據(jù)庫的精確同步

緩存三大經(jīng)典問題

1. 緩存穿透

問題描述:查詢一個不存在的數(shù)據(jù),由于緩存和數(shù)據(jù)庫都沒有,每次請求都會打到數(shù)據(jù)庫。

危害:惡意用戶可能利用此漏洞,大量請求不存在的數(shù)據(jù)導(dǎo)致數(shù)據(jù)庫崩潰。

解決方案

public Product getProduct(Long id) {
    // 參數(shù)校驗
    if (id == null || id <= 0) {
        return null;
    }
    // 1. 先查緩存
    Product cachedProduct = redis.get("product:" + id);
    if (cachedProduct != null) {
        return cachedProduct;
    }
    // 2. 緩存未命中,查詢數(shù)據(jù)庫
    Product product = productMapper.findById(id);
    if (product == null) {
        // 3. 緩存空值,防止穿透
        redis.set("product:" + id, null, 60); // 短過期時間
        return null;
    }
    // 4. 寫入緩存
    redis.set("product:" + id, product, 3600);
    return product;
}

額外防護措施

// 使用布隆過濾器
private BloomFilter<Long> bloomFilter;
public Product getProduct(Long id) {
    // 布隆過濾器檢查
    if (!bloomFilter.mightContain(id)) {
        return null; // 一定不存在
    }
    // 正常查詢流程
    Product product = getProductFromCacheOrDB(id);
    return product;
}

2. 緩存擊穿

問題描述:某個熱點數(shù)據(jù)過期的瞬間,大量并發(fā)請求同時發(fā)現(xiàn)緩存失效,全部涌入數(shù)據(jù)庫查詢。

解決方案

方案一:互斥鎖

private boolean lock = false;
public Product getProduct(Long id) {
    Product product = redis.get("product:" + id);
    if (product != null) {
        return product;
    }
    // 獲取鎖
    if (tryLock("lock:product:" + id)) {
        try {
            // Double Check
            product = redis.get("product:" + id);
            if (product != null) {
                return product;
            }
            // 查詢數(shù)據(jù)庫
            product = productMapper.findById(id);
            redis.set("product:" + id, product, 3600);
        } finally {
            unlock("lock:product:" + id);
        }
    } else {
        // 等待后重試
        Thread.sleep(100);
        return getProduct(id);
    }
    return product;
}

方案二:邏輯過期

public Product getProduct(Long id) {
    // 1. 查緩存
    Product product = redis.get("product:" + id);
    if (product == null) {
        // 緩存為空,嘗試獲取鎖重建緩存
        if (tryLock("lock:product:" + id)) {
            Product newProduct = productMapper.findById(id);
            redis.set("product:" + id, newProduct, 3600);
            unlock("lock:product:" + id);
            return newProduct;
        }
        // 等待后重試
        Thread.sleep(100);
        return getProduct(id);
    }
    // 2. 檢查是否邏輯過期
    if (isLogicalExpired(product)) {
        // 異步重建緩存,不阻塞請求
        if (tryLock("lock:product:" + id)) {
            threadPool.execute(() -> {
                Product newProduct = productMapper.findById(id);
                redis.set("product:" + id, newProduct, 3600);
                unlock("lock:product:" + id);
            });
        }
    }
    return product;
}

3. 緩存雪崩

問題描述:大量緩存數(shù)據(jù)在同一時間過期,導(dǎo)致大量請求同時打到數(shù)據(jù)庫。

解決方案

方案一:隨機過期時間

// 設(shè)置過期時間添加隨機值
int baseExpire = 3600;
int randomExpire = ThreadLocalRandom.current().nextInt(300);
redis.set("product:" + id, product, baseExpire + randomExpire);

方案二:多級緩存

public Product getProduct(Long id) {
    // 1. 先查本地緩存
    Product product = localCache.get(id);
    if (product != null) {
        return product;
    }
    // 2. 查 Redis
    product = redis.get("product:" + id);
    if (product != null) {
        // 回填本地緩存
        localCache.put(id, product, 300);
        return product;
    }
    // 3. 查數(shù)據(jù)庫
    product = productMapper.findById(id);
    redis.set("product:" + id, product, 3600);
    return product;
}

方案三:服務(wù)降級

public Product getProduct(Long id) {
    try {
        // 1. 查緩存
        Product product = redis.get("product:" + id);
        if (product != null) {
            return product;
        }
        // 2. 緩存未命中,降級處理
        return getProductFromBackup(id);
    } catch (Exception e) {
        // Redis 異常,降級到數(shù)據(jù)庫
        log.error("Redis error, fallback to DB", e);
        return productMapper.findById(id);
    }
}

數(shù)據(jù)一致性方案

延遲雙刪

public void updateProduct(Product product) {
    // 1. 刪除緩存
    redis.del("product:" + product.getId());
    // 2. 更新數(shù)據(jù)庫
    productMapper.update(product);
    // 3. 延遲刪除緩存
    threadPool.execute(() -> {
        try {
            Thread.sleep(1000);
            redis.del("product:" + product.getId());
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    });
}

延遲雙刪的適用場景:寫操作非常頻繁,且對一致性要求較高。

訂閱 Binlog + Canal

// Canal 配置
@CanalMessageListener(topic = "product_db.product")
public void onMessage(CanalEntry.Entry entry) {
    CanalEntry.RowChange rowChange = CanalEntry.RowChange.parseFrom(entry.getStoreValue());
    for (CanalEntry.RowData rowData : rowChange.getRowDatasList()) {
        if (rowChange.getEventType() == CanalEntry.EventType.UPDATE) {
            // 更新操作
            for (Column column : rowData.getBeforeColumnsList()) {
                if ("id".equals(column.getName())) {
                    Long id = Long.parseLong(column.getValue());
                    redis.del("product:" + id);
                }
            }
        }
    }
}

優(yōu)勢

  • 異步更新,不影響主流程
  • 保證最終一致性
  • 適用于大型分布式系統(tǒng)

緩存策略最佳實踐

緩存 key 設(shè)計

// 好的設(shè)計
String key = "product:info:" + categoryId + ":" + productId;
String key = "user:profile:" + userId;
String key = "order:summary:" + dateStr;
// 避免的設(shè)計
String key = "product_" + productId;           // 缺少命名空間
String key = getComplexKey(product);            // 復(fù)雜計算
String key = "temp:" + System.currentTimeMillis(); // 時效性數(shù)據(jù)

緩存 Value 設(shè)計

// 序列化配置
@Cacheable(value = "products", key = "#id", unless = "#result == null")
public Product getProduct(Long id) {
    return productMapper.findById(id);
}
// JSON 序列化
@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);
        Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper mapper = new ObjectMapper();
        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        mapper.activateDefaultTyping(mapper.getPolymorphicTypeValidator());
        serializer.setObjectMapper(mapper);
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(serializer);
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(serializer);
        return template;
    }
}

過期時間策略

數(shù)據(jù)類型過期時間原因
熱點商品24 小時數(shù)據(jù)相對穩(wěn)定
用戶會話30 分鐘安全性考慮
排行榜數(shù)據(jù)5 分鐘更新頻繁
配置信息1 小時變更不頻繁
計數(shù)器不過期需要持久化

容量規(guī)劃

// 預(yù)估緩存容量
// 假設(shè)每秒 10000 次查詢,緩存 10000 條數(shù)據(jù)
// 每條數(shù)據(jù) 1KB
// 需要的內(nèi)存 = 10000 * 1KB = 10MB

// 實際規(guī)劃需要預(yù)留 20-30% 冗余
// 還需要考慮 Redis 本身的內(nèi)存開銷

監(jiān)控告警

# 監(jiān)控指標(biāo)
- alert: RedisHighMemoryUsage
  expr: redis_memory_used_bytes / redis_memory_max_bytes > 0.8
  for: 5m
  labels:
    severity: warning
  annotations:
    summary: "Redis 內(nèi)存使用率過高"
- alert: RedisHighHitMissRatio
  expr: redis_keyspace_hits_total / (redis_keyspace_hits_total + redis_keyspace_misses_total) < 0.8
  for: 5m
  labels:
    severity: warning
  annotations:
    summary: "Redis 緩存命中率過低"

性能優(yōu)化技巧

批量操作

// 批量查詢
public Map<Long, Product> getProducts(List<Long> ids) {
    List<String> keys = ids.stream()
        .map(id -> "product:" + id)
        .collect(Collectors.toList());
    List<Product> products = redis.mGet(keys);
    Map<Long, Product> result = new HashMap<>();
    for (int i = 0; i < ids.size(); i++) {
        if (products.get(i) != null) {
            result.put(ids.get(i), products.get(i));
        }
    }
    // 批量回補緩存
    result.forEach((id, product) -> 
        redis.set("product:" + id, product, 3600)
    );
    return result;
}

Pipeline 批量寫入

public void batchWriteProducts(List<Product> products) {
    redis.executePipelined((RedisCallback<Object>) connection -> {
        for (Product product : products) {
            String key = "product:" + product.getId();
            byte[] value = serializationUtils.serialize(product);
            connection.setEx(key.getBytes(), 3600, value);
        }
        return null;
    });
}

緩存預(yù)熱

@PostConstruct
public void warmupCache() {
    // 系統(tǒng)啟動時預(yù)加載熱點數(shù)據(jù)
    log.info("Start cache warmup...");
    List<Long> hotProductIds = productService.getHotProductIds();
    for (Long id : hotProductIds) {
        Product product = productMapper.findById(id);
        redis.set("product:" + id, product, 3600);
    }
    log.info("Cache warmup completed, {} products loaded", hotProductIds.size());
}

常見錯誤與規(guī)避

錯誤一:緩存與數(shù)據(jù)庫雙寫不一致

// 錯誤寫法:先更新緩存,再更新數(shù)據(jù)庫
public void updateProduct(Product product) {
    redis.set("product:" + product.getId(), product);  // 先更新緩存
    productMapper.update(product);                       // 后更新數(shù)據(jù)庫
    // 并發(fā)時可能緩存是舊數(shù)據(jù)
}
// 正確寫法:先刪緩存,再更新數(shù)據(jù)庫
public void updateProduct(Product product) {
    redis.del("product:" + product.getId());  // 先刪緩存
    productMapper.update(product);             // 后更新數(shù)據(jù)庫
}

錯誤二:緩存頻繁更新

// 錯誤寫法:每次訪問都更新緩存
public Product getProduct(Long id) {
    Product product = redis.get("product:" + id);
    if (product == null) {
        product = productMapper.findById(id);
    }
    // 每次都更新,浪費資源
    redis.set("product:" + id, product, 3600);
    return product;
}
// 正確寫法:只在緩存不存在時更新
public Product getProduct(Long id) {
    Product product = redis.get("product:" + id);
    if (product == null) {
        product = productMapper.findById(id);
        if (product != null) {
            redis.set("product:" + id, product, 3600);
        }
    }
    return product;
}

錯誤三:大對象緩存

// 錯誤寫法:緩存整個列表
public List<Product> getAllProducts() {
    List<Product> products = redis.get("all_products");
    if (products == null) {
        products = productMapper.findAll();
        redis.set("all_products", products, 300);
    }
    return products;
}
// 正確寫法:分頁緩存或使用壓縮
public List<Product> getProducts(int page, int size) {
    String key = "products:" + page + ":" + size;
    return redis.get(key);
}

總結(jié)

旁路緩存是提升系統(tǒng)讀取性能的利器,但在實際應(yīng)用中需要注意:

  1. 合理設(shè)計:根據(jù)業(yè)務(wù)特點選擇合適的緩存策略
  2. 一致性保障:根據(jù)一致性要求選擇延遲雙刪或 Binlog 方案
  3. 容錯機制:做好緩存穿透、擊穿、雪崩的防護
  4. 監(jiān)控告警:實時監(jiān)控緩存命中率、內(nèi)存使用等關(guān)鍵指標(biāo)
  5. 預(yù)熱與降級:系統(tǒng)啟動時預(yù)熱緩存,異常時做好降級

正確使用旁路緩存,能夠?qū)⑾到y(tǒng)性能提升一個數(shù)量級,是后端開發(fā)必備的核心技能。

到此這篇關(guān)于Redis 旁路緩存深度解析的文章就介紹到這了,更多相關(guān)Redis 旁路緩存內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Redis中RPOP、BRPOP、LPOP和BLPOP使用示例代碼

    Redis中RPOP、BRPOP、LPOP和BLPOP使用示例代碼

    這篇文章主要介紹了Redis中RPOP、BRPOP、LPOP和BLPOP使用的相關(guān)資料,解釋了列表的方向,并詳細說明了每個命令的功能、返回值、使用場景,文中通過代碼介紹的非常詳細,需要的朋友可以參考下
    2026-05-05
  • Redis中TYPE命令的具體使用

    Redis中TYPE命令的具體使用

    本文主要介紹了Redis中TYPE命令的具體使用,它用于返回存儲在指定鍵中的值的數(shù)據(jù)類型,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2024-06-06
  • Redis優(yōu)雅地實現(xiàn)延遲隊列的方法分享

    Redis優(yōu)雅地實現(xiàn)延遲隊列的方法分享

    Redisson是Redis服務(wù)器上的分布式可伸縮Java數(shù)據(jù)結(jié)構(gòu),這篇文中主要為大家介紹了Redisson實現(xiàn)的優(yōu)雅的延遲隊列的方法,需要的可以參考一下
    2023-02-02
  • 淺談Redis哨兵模式高可用解決方案

    淺談Redis哨兵模式高可用解決方案

    Redis高可用有兩種模式:哨兵模式和集群模式,本文基于哨兵模式搭建一主兩從三哨兵Redis高可用服務(wù),感興趣的可以了解一下
    2022-03-03
  • Redis集群中節(jié)點更換IP后實現(xiàn)恢復(fù)集群且保留數(shù)據(jù)

    Redis集群中節(jié)點更換IP后實現(xiàn)恢復(fù)集群且保留數(shù)據(jù)

    本文介紹了如何在Redis集群中更改節(jié)點IP地址,并通過修改cluster-config-file文件中的IP地址并重啟所有節(jié)點,成功恢復(fù)集群
    2026-03-03
  • Redis數(shù)據(jù)庫的鍵管理示例詳解

    Redis數(shù)據(jù)庫的鍵管理示例詳解

    這篇文章主要為大家介紹了Redis數(shù)據(jù)庫的鍵管理示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-10-10
  • Redis?key-value亂碼的解決

    Redis?key-value亂碼的解決

    本文主要介紹了Redis?key-value亂碼的解決,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-06-06
  • Redis 熱 key 和大 key 問題小結(jié)

    Redis 熱 key 和大 key 問題小結(jié)

    這篇文章主要介紹了Redis 熱 key 和大 key 問題小結(jié),本文給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友參考下吧
    2025-04-04
  • 為Java項目添加Redis緩存的方法

    為Java項目添加Redis緩存的方法

    Redis一般有Linux和Windows兩種安裝方式,本文就這兩種方式給大家詳細介紹,對java項目添加redis緩存相關(guān)知識,感興趣的朋友一起看看吧
    2021-05-05
  • redis和redission分布式鎖原理及區(qū)別說明

    redis和redission分布式鎖原理及區(qū)別說明

    文章對比了synchronized、樂觀鎖、Redis分布式鎖及Redission鎖的原理與區(qū)別,指出在集群環(huán)境下synchronized失效,樂觀鎖存在數(shù)據(jù)庫性能瓶頸,而Redission通過watchdog自動續(xù)期和Lua原子操作解決Redis鎖的超時問題,推薦其在高并發(fā)場景下的可靠性與易用性
    2025-08-08

最新評論

湖州市| 永兴县| 香格里拉县| 彰武县| 永泰县| 海阳市| 呼伦贝尔市| 衡东县| 赣榆县| 兴隆县| 洛浦县| 华蓥市| 文登市| 梓潼县| 木里| 崇仁县| 登封市| 临桂县| 东平县| 吉安市| 台中市| 外汇| 盘锦市| 建宁县| 连江县| 吴江市| 伊金霍洛旗| 扎鲁特旗| 象州县| 枞阳县| 确山县| 江山市| 永城市| 沙坪坝区| 乐平市| 原平市| 萨嘎县| 电白县| 宁陵县| 隆尧县| 平江县|