偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

用好緩存的十條軍規(guī)

開發(fā) 前端
緩存不是存儲數(shù)據(jù)的垃圾桶,需要根據(jù)數(shù)據(jù)訪問頻率、讀寫比例、數(shù)據(jù)一致性要求進(jìn)行分級管理。大對象緩存會導(dǎo)致內(nèi)存碎片化,甚至觸發(fā)Full GC。

前言

"蘇工!首頁崩了!"

幾年前的一天晚上,我接到電話時,我正夢見自己成了緩存之神。

打開監(jiān)控一看:

緩存命中率:0%  
數(shù)據(jù)庫QPS:10萬+  
線程阻塞數(shù):2000+

根本原因竟是之前有同事寫的這段代碼:

public Product getProduct(Long id) {  
    return productDao.findById(id); 
}

直連數(shù)據(jù)庫,未加緩存。

這一刻我意識到:不會用緩存的程序員,就像不會剎車的賽車手。

今天這篇文章跟大家一起聊聊使用緩存的10條軍規(guī),希望對你會有所幫助。

圖片圖片

軍規(guī)1: 避免大key

反例場景:

@Cacheable(value = "user", key = "#id")  
public User getUser(Long id) {  
    return userDao.findWithAllRelations(id); 
}

這里一次查詢出了用戶及其所有關(guān)聯(lián)對象,然后添加到內(nèi)存緩存中。

如果通過id查詢用戶信息的請求量非常大,會導(dǎo)致頻繁的GC。

正確實(shí)踐:

@Cacheable(value = "user_base", key = "#id")  
public UserBase getBaseInfo(Long id) { /*...*/ }  

@Cacheable(value = "user_detail", key = "#id")  
public UserDetail getDetailInfo(Long id) { /*...*/ }

這種情況,需要拆分緩存對象,比如:將用戶基本信息和用戶詳細(xì)信息分開緩存。

緩存不是存儲數(shù)據(jù)的垃圾桶,需要根據(jù)數(shù)據(jù)訪問頻率、讀寫比例、數(shù)據(jù)一致性要求進(jìn)行分級管理。

大對象緩存會導(dǎo)致內(nèi)存碎片化,甚至觸發(fā)Full GC。

建議將基礎(chǔ)信息(如用戶ID、名稱)與擴(kuò)展信息(如訂單記錄)分離存儲。

軍規(guī)2: 永遠(yuǎn)設(shè)置過期時間

血淚案例:某系統(tǒng)將配置信息緩存設(shè)置為永不過期,導(dǎo)致修改配置后三天才生效。

正確配置:

@Cacheable(value = "config", key = "#key",  
           unless = "#result == null",  
           cacheManager = "redisCacheManager")  
public String getConfig(String key) {  
    return configDao.get(key);  
}

Redis配置如下:

spring.cache.redis.time-to-live=300000 // 5分鐘  
spring.cache.redis.cache-null-values=false

需要指定key的存活時間,比如:time-to-live設(shè)置成5分鐘。

TTL設(shè)置公式:

最優(yōu)TTL = 平均數(shù)據(jù)變更周期 × 0.3

深層思考:過期時間過短會導(dǎo)致緩存穿透風(fēng)險(xiǎn),過長會導(dǎo)致數(shù)據(jù)不一致。

建議采用動態(tài)TTL策略。

例如電商商品詳情頁可設(shè)置30分鐘基礎(chǔ)TTL+隨機(jī)5分鐘抖動。

軍規(guī)3: 避免批量失效

典型事故:所有緩存設(shè)置相同TTL,導(dǎo)致每天凌晨集中失效,數(shù)據(jù)庫瞬時被打爆。

解決方案:

使用基礎(chǔ)TTL + 隨機(jī)抖動的方案:

public long randomTtl(long baseTtl) {  
    return baseTtl + new Random().nextInt(300); 
}

TTL增加0-5分鐘隨機(jī)值。

使用示例

redisTemplate.opsForValue().set(key, value, randomTtl(1800), TimeUnit.SECONDS);

失效時間分布:

圖片圖片

軍規(guī)4: 需要增加熔斷降級

我們在使用緩存的時候,需要增加熔斷降級策略,防止萬一緩存掛了,不能影響整個服務(wù)的可用性。

Hystrix實(shí)現(xiàn)示例:

@HystrixCommand(fallbackMethod = "getProductFallback",  
               commandProperties = {  
                   @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "20"),  
                   @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "5000")  
               })  
public Product getProduct(Long id) {  
    return productDao.findById(id);  
}  

public Product getProductFallback(Long id) {  
    return new Product().setDefault(); // 返回兜底數(shù)據(jù)  
}

熔斷狀態(tài)機(jī):

圖片圖片

軍規(guī)5: 空值緩存

在用戶請求并發(fā)量大的業(yè)務(wù)場景種,我們需要把空值緩存起來。

防止大批量在系統(tǒng)中不存在的用戶id,沒有命中緩存,而直接查詢數(shù)據(jù)庫的情況。

典型代碼:

public Product getProduct(Long id) {  
    String key = "product:" + id;  
    Product product = redis.get(key);  
    if (product != null) {  
        if (product.isEmpty()) { // 空對象標(biāo)識  
            returnnull;  
        }  
        return product;  
    }  

    product = productDao.findById(id);  
    if (product == null) {  
        redis.setex(key, 300, "empty"); // 緩存空值5分鐘  
        returnnull;  
    }  

    redis.setex(key, 3600, product);  
    return product;  
}

空值緩存原理:

圖片圖片

需要將數(shù)據(jù)庫中返回的空值,緩存起來。

后面如果有相同的key查詢數(shù)據(jù),則直接從緩存中返回空值。

而無需再查詢一次數(shù)據(jù)庫。

軍規(guī)6: 分布式鎖用Redisson

用Redis做分布式鎖的時候,可能會遇到很多問題。

建議大家使用Redisson做分布式鎖。

Redisson分布式鎖實(shí)現(xiàn):

public Product getProduct(Long id) {  
    String key = "product:" + id;  
    Product product = redis.get(key);  
    if (product == null) {  
        RLock lock = redisson.getLock("lock:" + key);  
        try {  
            if (lock.tryLock(3, 30, TimeUnit.SECONDS)) {  
                product = productDao.findById(id);  
                redis.setex(key, 3600, product);  
            }  
        } finally {  
            lock.unlock();  
        }  
    }  
    return product;  
}

鎖競爭流程圖:

圖片圖片

軍規(guī)7: 延遲雙刪策略

在保證數(shù)據(jù)庫和緩存雙寫數(shù)據(jù)一致性的業(yè)務(wù)場景種,可以使用延遲雙刪的策略。

例如:

@Transactional  
public void updateProduct(Product product) {  
    // 1. 先刪緩存  
    redis.delete("product:" + product.getId());  

    // 2. 更新數(shù)據(jù)庫  
    productDao.update(product);  

    // 3. 延時再刪  
    executor.schedule(() -> {  
        redis.delete("product:" + product.getId());  
    }, 500, TimeUnit.MILLISECONDS);  
}

軍規(guī)8: 最終一致性方案

延遲雙刪可能還有其他的問題。

我們可以使用最終一致性方案。

基于Binlog的方案:

圖片圖片

DB更新數(shù)據(jù)之后,Canal會自動監(jiān)聽數(shù)據(jù)的變化,它會解析數(shù)據(jù)事件,然后發(fā)送一條MQ消息。

在MQ消費(fèi)者中,刪除緩存。

軍規(guī)9: 熱點(diǎn)數(shù)據(jù)預(yù)加載

對于一些經(jīng)常使用的熱點(diǎn)數(shù)據(jù),我們可以提前做數(shù)據(jù)的預(yù)加載。

實(shí)時監(jiān)控方案:

// 使用Redis HyperLogLog統(tǒng)計(jì)訪問頻率  
public void recordAccess(Long productId) {  
    String key = "access:product:" + productId;  
    redis.pfadd(key, UUID.randomUUID().toString());  
    redis.expire(key, 60); // 統(tǒng)計(jì)最近60秒  
}  

// 定時任務(wù)檢測熱點(diǎn)  
@Scheduled(fixedRate = 10000)  
public void detectHotKeys() {  
    Set<String> keys = redis.keys("access:product:*");  
    keys.forEach(key -> {  
        long count = redis.pfcount(key);  
        if (count > 1000) { // 閾值  
            Long productId = extractId(key);  
            preloadProduct(productId);  
        }  
    });  
}

定時任務(wù)檢測熱點(diǎn),并且更新到緩存中。

軍規(guī)10: 根據(jù)場景選擇數(shù)據(jù)結(jié)構(gòu)

血淚案例:某社交平臺使用String類型存儲用戶信息。

錯誤用String存儲對象:

redis.set("user:123", JSON.toJSONString(user));

每次更新單個字段都需要反序列化整個對象。

導(dǎo)致問題:

  1. 序列化/反序列化開銷大
  2. 更新單個字段需讀寫整個對象
  3. 內(nèi)存占用高 正確實(shí)踐:
// 使用Hash存儲  
redis.opsForHash().putAll("user:123", userToMap(user));  

// 局部更新  
redis.opsForHash().put("user:123", "age", "25");

數(shù)據(jù)結(jié)構(gòu)選擇矩陣:

圖片圖片

各數(shù)據(jù)結(jié)構(gòu)最佳實(shí)踐:

1.String

計(jì)數(shù)器

redis.opsForValue().increment("article:123:views");

分布式鎖

redis.opsForValue().set("lock:order:456", "1", "NX", "EX", 30);

2.Hash

存儲商品信息

Map<String, String> productMap = new HashMap<>();  
productMap.put("name", "iPhone15");  
productMap.put("price", "7999");  
redis.opsForHash().putAll("product:789", productMap);

部分更新

redis.opsForHash().put("product:789", "stock", "100");

3.List

消息隊(duì)列

redis.opsForList().leftPush("queue:payment", orderJson);

最新N條記錄

redis.opsForList().trim("user:123:logs", 0, 99);

4.Set

標(biāo)簽系統(tǒng)

redis.opsForSet().add("article:123:tags", "科技", "數(shù)碼");

共同好友

redis.opsForSet().intersect("user:123:friends", "user:456:friends");

5.ZSet

排行榜

redis.opsForZSet().add("leaderboard", "player1", 2500);  
redis.opsForZSet().reverseRange("leaderboard", 0, 9);

延遲隊(duì)列

redis.opsForZSet().add("delay:queue", "task1", System.currentTimeMillis() + 5000);

總結(jié)

緩存治理黃金法則

問題類型

推薦方案

工具推薦

緩存穿透

空值緩存+布隆過濾器

Redisson BloomFilter

緩存雪崩

隨機(jī)TTL+熔斷降級

Hystrix/Sentinel

緩存擊穿

互斥鎖+熱點(diǎn)預(yù)加載

Redisson Lock

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

延遲雙刪+最終一致性

Canal+RocketMQ

圖片圖片

最后忠告:緩存是把雙刃劍,用得好是性能利器,用不好就是定時炸彈。

當(dāng)你準(zhǔn)備引入緩存時,先問自己三個問題:

  1. 真的需要緩存嗎?
  2. 緩存方案是否完整?
  3. 有沒有兜底措施?
責(zé)任編輯:武曉燕 來源: 蘇三說技術(shù)
相關(guān)推薦

2012-09-28 09:12:39

移動Web

2025-03-19 08:21:15

2025-05-15 20:55:38

2021-03-18 09:00:00

微服務(wù)架構(gòu)工具

2025-07-01 00:45:00

2009-01-15 09:57:00

2022-02-14 00:16:17

數(shù)據(jù)安全云安全

2021-02-04 11:55:45

Redis性能優(yōu)化

2020-04-30 09:35:41

物聯(lián)網(wǎng)安全物聯(lián)網(wǎng)IOT

2016-11-17 14:54:49

云計(jì)算安全性可用性

2012-05-15 01:38:18

編程編程技巧編程觀點(diǎn)

2012-03-06 16:01:04

項(xiàng)目管理

2012-08-02 09:14:13

編程戒律

2011-08-02 21:16:56

查詢SQL性能優(yōu)化

2011-04-14 11:43:47

2024-08-19 09:04:50

2022-09-09 16:27:09

微服務(wù)架構(gòu)數(shù)據(jù)存儲

2024-02-19 14:50:42

編碼原則軟件開發(fā)

2011-07-27 09:17:20

.NET設(shè)計(jì)架構(gòu)

2011-05-30 15:59:47

編程
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號