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

Redis緩存更新策略詳解

 更新時間:2026年03月25日 10:35:33   作者:彭于晏Yan  
本文介紹了4種核心緩存更新策略(Cache-Aside、Write-Through、Write-Behind、Refresh-Ahead),并討論了3種補(bǔ)充策略(Read-Through、最終一致性、過期淘汰),感興趣的朋友跟隨小編一起看看吧

1. 主動更新-4種核心緩存更新策略

核心原則:根據(jù)業(yè)務(wù)的 “讀寫比例”“一致性要求”“性能要求” 選擇策略,優(yōu)先保證數(shù)據(jù)一致性,其次優(yōu)化性能。

1.1. Cache-Aside(旁路緩存)

這是最常用、最經(jīng)典的策略,也叫 “先查緩存,再查數(shù)據(jù)庫,更新時先更庫再刪緩存”。

  1. 讀取數(shù)據(jù):先查詢緩存,命中則直接返回;未命中則查詢數(shù)據(jù)庫,將結(jié)果寫入緩存并返回。
  2. 更新數(shù)據(jù):先更新數(shù)據(jù)庫,再刪除緩存(而非更新緩存)。
@Service
public class UserServiceCacheAside {
    @Resource
    private UserMapper userMapper;
    @Resource
    private RedisTemplate<String, Object> redisTemplate;
    public User getUserById(Long userId) {
        String cacheKey = "user:" + userId;
        // 1. 查詢緩存
        User user = (User) redisTemplate.opsForValue().get(cacheKey);
        if (user != null) {
            return user;  //緩存命中,直接返回
        }
        // 2. 緩存未命中,查詢數(shù)據(jù)庫
        user = userMapper.selectById(id);
        if (user != null) {
            // 3. 將數(shù)據(jù)庫結(jié)果寫入緩存(設(shè)置過期時間)
            redisTemplate.opsForValue().set(cacheKey, user, 30, TimeUnit.MINUTES);
        }
        return user;
    }
    public void updateUser(User user) {
        // 1. 先更新數(shù)據(jù)庫
        userMapper.updateById(user);
        // 2. 再刪除緩存(而非更新緩存,避免并發(fā)問題)
        String cacheKey = "user:" + user.getId();
        redisTemplate.delete(cacheKey);
    }
}
  • 優(yōu)點
    • 邏輯簡單,易于實現(xiàn);適合讀多寫少的業(yè)務(wù)場景;
    • 避免 “更新緩存” 帶來的并發(fā)數(shù)據(jù)不一致問題(比如兩個線程同時更新,緩存可能存舊值)。
  • 缺點
    • 緩存未命中時會有 “緩存穿透” 的風(fēng)險(可通過布隆過濾器解決);
    • 數(shù)據(jù)庫更新后、緩存刪除前,若有讀請求,可能讀到舊值(概率極低,可接受)。

1.2. Write-Through(寫穿透)

更新時 “先更緩存,再更數(shù)據(jù)庫”,讀取時只查緩存(緩存一定有最新數(shù)據(jù))。

  1. 讀取數(shù)據(jù):直接從緩存讀取,緩存必然命中(因為更新時同步寫緩存);
  2. 更新數(shù)據(jù):先更新緩存;再由緩存同步更新數(shù)據(jù)庫(通常是緩存框架自動完成)。
@Service
public class UserWriteThroughService {
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    @Autowired
    private UserMapper userMapper;
    /**
     * 新增用戶(Write Through:先寫緩存,再寫數(shù)據(jù)庫)
     * 加事務(wù)保證緩存和數(shù)據(jù)庫要么都成功,要么都失敗
     */
    @Transactional(rollbackFor = Exception.class)
    public void addUser(User user) {
        // 1. 先寫緩存(設(shè)置過期時間,兜底)
        String cacheKey = "user:write_through:" + user.getId();
        redisTemplate.opsForValue().set(cacheKey, user, 1, TimeUnit.HOURS);
        // 2. 同步寫數(shù)據(jù)庫(若數(shù)據(jù)庫寫入失敗,事務(wù)回滾,緩存也會被刪除)
        int insertCount = userMapper.insertUser(user);
        if (insertCount <= 0) {
            // 數(shù)據(jù)庫寫入失敗,主動刪除緩存,避免臟數(shù)據(jù)
            redisTemplate.delete(cacheKey);
            throw new RuntimeException("新增用戶到數(shù)據(jù)庫失敗");
        }
    }
    /**
     * 更新用戶(Write Through:先更新緩存,再更新數(shù)據(jù)庫)
     */
    @Transactional(rollbackFor = Exception.class)
    public void updateUser(User user) {
        String cacheKey = "user:write_through:" + user.getId();
        // 1. 先更新緩存(若緩存不存在,先查數(shù)據(jù)庫再更新,保證緩存有數(shù)據(jù))
        User oldUser = (User) redisTemplate.opsForValue().get(cacheKey);
        if (oldUser == null) {
            oldUser = userMapper.selectUserById(user.getId());
            if (oldUser == null) {
                throw new RuntimeException("用戶不存在,ID: " + user.getId());
            }
        }
        redisTemplate.opsForValue().set(cacheKey, user, 1, TimeUnit.HOURS);
        // 2. 同步更新數(shù)據(jù)庫
        int updateCount = userMapper.updateUser(user);
        if (updateCount <= 0) {
            // 數(shù)據(jù)庫更新失敗,回滾緩存(恢復(fù)舊值)
            redisTemplate.opsForValue().set(cacheKey, oldUser, 1, TimeUnit.HOURS);
            throw new RuntimeException("更新用戶到數(shù)據(jù)庫失敗,ID: " + user.getId());
        }
    }
    /**
     * 讀取用戶(Write Through:只查緩存,不查數(shù)據(jù)庫)
     */
    public User getUserById(Long userId) {
        String cacheKey = "user:write_through:" + userId;
        User user = (User) redisTemplate.opsForValue().get(cacheKey);
        if (user == null) {
            // 理論上 Write Through 策略下緩存一定有數(shù)據(jù),此處僅做異常兜底
            user = userMapper.selectUserById(userId);
            if (user != null) {
                redisTemplate.opsForValue().set(cacheKey, user, 1, TimeUnit.HOURS);
            }
        }
        return user;
    }
}
  • 優(yōu)點
    • 讀取性能極高,無需訪問數(shù)據(jù)庫;數(shù)據(jù)一致性強(qiáng),緩存與數(shù)據(jù)庫同步更新。
  • 缺點
    • 寫入性能低(每次寫都要操作緩存 + 數(shù)據(jù)庫);
    • 數(shù)據(jù)庫寫入失敗會導(dǎo)致緩存與數(shù)據(jù)庫不一致(需加事務(wù) / 重試)。

1.3. Write-Behind(寫回)

也叫 “延遲更新”,更新時只更緩存,不立即更數(shù)據(jù)庫,而是等緩存過期 / 淘汰時,再批量同步到數(shù)據(jù)庫。

  1. 更新流程:更新緩存,并標(biāo)記緩存為 “臟數(shù)據(jù)”;緩存過期 / 被淘汰時,異步將 “臟數(shù)據(jù)” 批量寫入數(shù)據(jù)庫。
  2. 讀取流程:與 Cache Aside 一致(先查緩存,未命中查庫)。
/**
 * 業(yè)務(wù)場景:用戶點贊數(shù)(寫多讀少,允許短時間緩存與數(shù)據(jù)庫不一致)
 */
@Service
public class LikeCountWriteBackService {
    // Redis Key前綴:用戶點贊數(shù)緩存
    private static final String CACHE_LIKE_COUNT_KEY = "like:count:";
    // Redis Key:臟數(shù)據(jù)標(biāo)記(記錄需要同步到數(shù)據(jù)庫的用戶ID)
    private static final String DIRTY_DATA_SET_KEY = "like:dirty:user:ids";
    // 緩存過期時間(兜底,避免臟數(shù)據(jù)永久不刷新)
    private static final long CACHE_EXPIRE_TIME = 24 * 60 * 60;
    @Resource
    private RedisTemplate<String, Object> redisTemplate;
    @Resource
    private LikeCountMapper likeCountMapper;
    /**
     * 核心操作:更新用戶點贊數(shù)(只更緩存,標(biāo)記臟數(shù)據(jù))
     * @param userId 用戶ID
     * @param increment 增加的點贊數(shù)(正數(shù))
     */
    public void updateLikeCount(Long userId, int increment) {
        String cacheKey = CACHE_LIKE_COUNT_KEY + userId;
        try {
            // 1. 原子更新Redis緩存中的點贊數(shù)(避免并發(fā)問題)
            redisTemplate.opsForValue().increment(cacheKey, increment);
            // 設(shè)置緩存過期時間(兜底)
            redisTemplate.expire(cacheKey, CACHE_EXPIRE_TIME, TimeUnit.SECONDS);
            // 2. 將用戶ID加入臟數(shù)據(jù)集(標(biāo)記為需要同步到數(shù)據(jù)庫)
            // 使用ZSet存儲,score為當(dāng)前時間戳,便于后續(xù)按時間篩選
            redisTemplate.opsForZSet().add(DIRTY_DATA_SET_KEY, userId, System.currentTimeMillis());
        } catch (Exception e) {
            // 異常時降級:直接更新數(shù)據(jù)庫(避免數(shù)據(jù)丟失)
            fallbackUpdateDb(userId, increment);
        }
    }
    /**
     * 讀取用戶點贊數(shù)(先查緩存,未命中查庫并回填緩存)
     * @param userId 用戶ID
     * @return 最新點贊數(shù)
     */
    public Long getLikeCount(Long userId) {
        String cacheKey = CACHE_LIKE_COUNT_KEY + userId;
        // 1. 先查緩存
        Object cacheValue = redisTemplate.opsForValue().get(cacheKey);
        if (cacheValue != null) {
            return Long.parseLong(cacheValue.toString());
        }
        // 2. 緩存未命中:查數(shù)據(jù)庫
        Long dbCount = likeCountMapper.selectLikeCountByUserId(userId);
        if (dbCount == null) {
            dbCount = 0L;
        }
        // 3. 回填緩存(并標(biāo)記為臟數(shù)據(jù),避免后續(xù)同步時覆蓋)
        redisTemplate.opsForValue().set(cacheKey, dbCount);
        redisTemplate.expire(cacheKey, CACHE_EXPIRE_TIME, TimeUnit.SECONDS);
        redisTemplate.opsForZSet().add(DIRTY_DATA_SET_KEY, userId, System.currentTimeMillis());
        return dbCount;
    }
    /**
     * 核心異步任務(wù):定時將臟數(shù)據(jù)同步到數(shù)據(jù)庫(Write Back核心)
     * 定時規(guī)則:每5分鐘執(zhí)行一次(可根據(jù)業(yè)務(wù)調(diào)整)
     */
    @Scheduled(cron = "0 */5 * * * ?")
    @Transactional(rollbackFor = Exception.class)
    public void syncDirtyDataToDb() {
        ZSetOperations<String, Object> zSetOps = redisTemplate.opsForZSet();
        // 1. 批量獲取臟數(shù)據(jù)集中的用戶ID(最多取1000條,避免單次同步過多)
        Set<Object> dirtyUserIds = zSetOps.range(DIRTY_DATA_SET_KEY, 0, 999);
        if (dirtyUserIds == null || dirtyUserIds.isEmpty()) {
            return;
        }
        // 2. 遍歷臟數(shù)據(jù),同步到數(shù)據(jù)庫
        List<Long> failUserIds = new ArrayList<>(); // 記錄同步失敗的用戶ID
        for (Object userIdObj : dirtyUserIds) {
            Long userId = Long.parseLong(userIdObj.toString());
            String cacheKey = CACHE_LIKE_COUNT_KEY + userId;
            try {
                // 2.1 獲取緩存中的最新點贊數(shù)
                Object cacheCountObj = redisTemplate.opsForValue().get(cacheKey);
                if (cacheCountObj == null) {
                    zSetOps.remove(DIRTY_DATA_SET_KEY, userId); // 移除臟數(shù)據(jù)標(biāo)記
                    continue;
                }
                Long cacheCount = Long.parseLong(cacheCountObj.toString());
                // 2.2 更新數(shù)據(jù)庫
                likeCountMapper.updateLikeCountByUserId(userId, cacheCount);
                // 2.3 同步成功:移除臟數(shù)據(jù)標(biāo)記
                zSetOps.remove(DIRTY_DATA_SET_KEY, userId);
            } catch (Exception e) {
                failUserIds.add(userId); // 記錄失敗ID,后續(xù)重試
            }
        }
        // 3. 處理同步失敗的用戶ID(簡單重試:重新加入臟數(shù)據(jù)集)
        if (!failUserIds.isEmpty()) {
            for (Long failUserId : failUserIds) {
                zSetOps.add(DIRTY_DATA_SET_KEY, failUserId, System.currentTimeMillis());
            }
        }
    }
    /**
     * 降級策略:緩存更新失敗時,直接更新數(shù)據(jù)庫
     */
    private void fallbackUpdateDb(Long userId, int increment) {
        try {
            Long currentCount = likeCountMapper.selectLikeCountByUserId(userId);
            if (currentCount == null) {
                currentCount = 0L;
            }
            likeCountMapper.updateLikeCountByUserId(userId, currentCount + increment);
        } catch (Exception e) {
            // 可進(jìn)一步接入消息隊列/告警,保證數(shù)據(jù)不丟失
        }
    }
}
  • 優(yōu)點:
    • 寫入性能極高(只需操作緩存,數(shù)據(jù)庫異步批量更新);
    • 適合寫多讀少的場景(如計數(shù)器、點贊數(shù))。
  • 缺點
    • 數(shù)據(jù)一致性差(緩存未同步到數(shù)據(jù)庫時,服務(wù)宕機(jī)會丟失數(shù)據(jù));
    • 實現(xiàn)復(fù)雜(需處理臟數(shù)據(jù)標(biāo)記、異步同步、數(shù)據(jù)恢復(fù))。

1.4. 刷新過期(Refresh-Ahead)

本質(zhì)是 Cache-Aside(旁路緩存)的優(yōu)化 / 增強(qiáng)版

  1. 更新流程:與 Cache Aside 一致(先更新數(shù)據(jù)庫,再刪除緩存)。
  2. 讀取流程:先查詢緩存,未命中則查詢數(shù)據(jù)庫,將結(jié)果寫入緩存并返回;命中則檢查緩存剩余過期時間,若剩余過期時間 ≥ 閾值:直接返回緩存中的舊值;若剩余過期時間 < 閾值:異步觸發(fā)緩存刷新(后臺查數(shù)據(jù)庫最新數(shù)據(jù) → 重寫緩存并重置 TTL),當(dāng)前請求仍返回緩存舊值。
/**
 * Refresh-Ahead(提前刷新)策略實現(xiàn)示例
 * 核心邏輯:訪問緩存時檢查剩余過期時間,若小于閾值則異步刷新緩存,當(dāng)前請求仍返回舊值
 */
@Service
public class RefreshAheadCacheService {
    // 緩存過期時間(示例:30分鐘)
    private static final long CACHE_TTL_SECONDS = 30 * 60;
    // Refresh-Ahead 觸發(fā)閾值(過期時間剩余10%時觸發(fā),示例:3分鐘)
    private static final long REFRESH_THRESHOLD_SECONDS = CACHE_TTL_SECONDS / 10;
    @Resource
    private RedisTemplate<String, Object> redisTemplate;
    @Resource
    private ProductCategoryMapper productCategoryMapper;
    /**
     * 獲取商品分類數(shù)據(jù)(核心Refresh-Ahead邏輯)
     */
    public ProductCategory getCategoryWithRefreshAhead(Long categoryId) {
        String cacheKey = "category:" + categoryId;
        ValueOperations<String, Object> valueOps = redisTemplate.opsForValue();
        // 1. 先查緩存
        ProductCategory category = (ProductCategory) valueOps.get(cacheKey);
        if (category == null) {
            // 緩存未命中:查庫 + 寫入緩存(常規(guī)Cache Aside邏輯)
            category = productCategoryMapper.selectById(categoryId);
            if (category != null) {
                redisTemplate.opsForValue().set(cacheKey, category, CACHE_TTL_SECONDS, TimeUnit.SECONDS);
            }
            return category;
        }
        // 2. 緩存命中:檢查剩余過期時間,判斷是否觸發(fā)Refresh-Ahead
        Long remainExpireSeconds = redisTemplate.getExpire(cacheKey, TimeUnit.SECONDS);
        // 剩余時間小于閾值 且 緩存未過期(避免已過期的情況)
        if (remainExpireSeconds != null && remainExpireSeconds > 0 
                && remainExpireSeconds < REFRESH_THRESHOLD_SECONDS) {
            // 3. 異步刷新緩存(不阻塞當(dāng)前請求)
            asyncRefreshCategoryCache(categoryId, cacheKey);
        }
        // 當(dāng)前請求仍返回舊值,異步刷新不影響響應(yīng)速度
        return category;
    }
    /**
     * 異步刷新緩存(核心:不阻塞主線程)
     */
    @Async("refreshExecutor") // 指定自定義異步線程池(避免用默認(rèn)線程池)
    public void asyncRefreshCategoryCache(Long categoryId, String cacheKey) {
        try {
            // 1. 從數(shù)據(jù)庫查詢最新數(shù)據(jù)
            ProductCategory latestCategory = productCategoryMapper.selectById(categoryId);
            if (latestCategory != null) {
                // 2. 重新設(shè)置緩存(覆蓋舊值 + 重置過期時間)
                redisTemplate.opsForValue().set(cacheKey, latestCategory, CACHE_TTL_SECONDS, TimeUnit.SECONDS);
            }
        } catch (Exception e) {
            System.err.println("Refresh-Ahead刷新緩存失?。篕ey=" + cacheKey + ",原因:" + e.getMessage());
        }
    }
}

線程池配置

@Configuration
@EnableAsync // 開啟異步功能
public class ThreadPoolConfig {
    @Bean
    public ThreadPoolTaskExecutor refreshExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5);
        executor.setMaxPoolSize(20);
        executor.setQueueCapacity(100);
        executor.setThreadNamePrefix("cache-refresh-");
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return executor;
    }
}
  • 優(yōu)點
    • 保證數(shù)據(jù)一致性;避免 “緩存穿透” 的風(fēng)險。
    • 僅在 “緩存快過期且被訪問” 時刷新,比定時刷新更精準(zhǔn),避免無意義的全量刷新,節(jié)省資源;
  • 缺點
    • 需額外開發(fā) “過期時間預(yù)判”“異步刷新”“線程池管理” 邏輯,增加開發(fā)和維護(hù)成本;
    • 觸發(fā)刷新后、異步更新完成前,客戶端仍會讀取到舊值(窗口極短,通??山邮埽?/li>
    • 異步線程池耗盡、數(shù)據(jù)庫查詢失敗等,可能導(dǎo)致刷新失敗,需增加重試 / 日志監(jiān)控機(jī)制;
    • 刷新閾值(如總 TTL 的 10%)設(shè)置不合理時:過大→頻繁刷新浪費資源,過小→刷新完成前緩存已過期。

2. 3種補(bǔ)充策略

2.1. Read-Through(讀穿透)

Read-Through是Cache Aside的 “封裝版 / 框架版”,核心是封裝緩存讀取邏輯,讓業(yè)務(wù)層聚焦業(yè)務(wù)而非緩存操作。

只需要將Cache Aside是緩存的邏輯封裝,所有業(yè)務(wù)復(fù)用即可。

  • 優(yōu)點 :
    • 封裝性好,應(yīng)用代碼無需關(guān)心緩存邏輯
    • 集中處理緩存加載,減少冗余代碼
    • 適合只讀或讀多寫少的數(shù)據(jù)
  • 缺點:
    • 緩存未命中時引發(fā)數(shù)據(jù)庫請求,可能導(dǎo)致數(shù)據(jù)庫負(fù)載增加
    • 無法直接處理寫操作,需要與其他策略結(jié)合使用
    • 需要額外維護(hù)一個緩存管理層
  • 適用場景
    • 讀操作頻繁的業(yè)務(wù)系統(tǒng)
    • 需要集中管理緩存加載邏輯的應(yīng)用
    • 復(fù)雜的緩存預(yù)熱和加載場景

2.2. 最終一致性(Eventual Consistency)

最終一致性策略基于分布式事件系統(tǒng)實現(xiàn)數(shù)據(jù)同步:

  1. 數(shù)據(jù)變更時發(fā)布事件到消息隊列
  2. 緩存服務(wù)訂閱相關(guān)事件并更新緩存
  3. 即使某些操作暫時失敗,最終系統(tǒng)也會達(dá)到一致狀態(tài) 首先定義數(shù)據(jù)變更事件:
@Data
@AllArgsConstructor
public class DataChangeEvent {
    private String entityType;
    private String entityId;
    private String operation; // CREATE, UPDATE, DELETE
    private String payload;   // JSON格式的實體數(shù)據(jù)
}

實現(xiàn)事件發(fā)布者:

@Component
public class DataChangePublisher {
    @Autowired
    private KafkaTemplate<String, DataChangeEvent> kafkaTemplate;
    private static final String TOPIC = "data-changes";
    public void publishChange(String entityType, String entityId, String operation, Object entity) {
        try {
            // 將實體序列化為JSON
            String payload = new ObjectMapper().writeValueAsString(entity);
            // 創(chuàng)建事件
            DataChangeEvent event = new DataChangeEvent(entityType, entityId, operation, payload);
            // 發(fā)布到Kafka
            kafkaTemplate.send(TOPIC, entityId, event);
        } catch (Exception e) {
            log.error("Failed to publish data change event", e);
            throw new RuntimeException("Failed to publish event", e);
        }
    }
}

實現(xiàn)事件消費者更新緩存:

@Component
@Slf4j
public class CacheUpdateConsumer {
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    private static final long CACHE_EXPIRATION = 30;
    @KafkaListener(topics = "data-changes")
    public void handleDataChangeEvent(DataChangeEvent event) {
        try {
            String cacheKey = buildCacheKey(event.getEntityType(), event.getEntityId());
            switch (event.getOperation()) {
                case "CREATE":
                case "UPDATE":
                    // 解析JSON數(shù)據(jù)
                    Object entity = parseEntity(event.getPayload(), event.getEntityType());
                    // 更新緩存
                    redisTemplate.opsForValue().set(
                            cacheKey, entity, CACHE_EXPIRATION, TimeUnit.MINUTES);
                    log.info("Updated cache for {}: {}", cacheKey, event.getOperation());
                    break;
                case "DELETE":
                    // 刪除緩存
                    redisTemplate.delete(cacheKey);
                    log.info("Deleted cache for {}", cacheKey);
                    break;
                default:
                    log.warn("Unknown operation: {}", event.getOperation());
            }
        } catch (Exception e) {
            log.error("Error handling data change event: {}", e.getMessage(), e);
            // 失敗處理:可以將失敗事件放入死信隊列等
        }
    }
    private String buildCacheKey(String entityType, String entityId) {
        return entityType.toLowerCase() + ":" + entityId;
    }
    private Object parseEntity(String payload, String entityType) throws JsonProcessingException {
        // 根據(jù)實體類型選擇反序列化目標(biāo)類
        Class<?> targetClass = getClassForEntityType(entityType);
        return new ObjectMapper().readValue(payload, targetClass);
    }
    private Class<?> getClassForEntityType(String entityType) {
        switch (entityType) {
            case "User": return User.class;
            case "Product": return Product.class;
            // 其他實體類型
            default: throw new IllegalArgumentException("Unknown entity type: " + entityType);
        }
    }
}

使用示例:

@Service
@Transactional
public class UserServiceEventDriven {
    @Autowired
    private UserRepository userRepository;
    @Autowired
    private DataChangePublisher publisher;
    public User createUser(User user) {
        // 1. 保存用戶到數(shù)據(jù)庫
        User savedUser = userRepository.save(user);
        // 2. 發(fā)布創(chuàng)建事件
        publisher.publishChange("User", savedUser.getId().toString(), "CREATE", savedUser);
        return savedUser;
    }
    public User updateUser(User user) {
        // 1. 更新用戶到數(shù)據(jù)庫
        User updatedUser = userRepository.save(user);
        // 2. 發(fā)布更新事件
        publisher.publishChange("User", updatedUser.getId().toString(), "UPDATE", updatedUser);
        return updatedUser;
    }
    public void deleteUser(Long userId) {
        // 1. 從數(shù)據(jù)庫刪除用戶
        userRepository.deleteById(userId);
        // 2. 發(fā)布刪除事件
        publisher.publishChange("User", userId.toString(), "DELETE", null);
    }
}
  • 優(yōu)點 :
    • 支持分布式系統(tǒng)中的數(shù)據(jù)一致性
    • 削峰填谷,減輕系統(tǒng)負(fù)載峰值
    • 服務(wù)解耦,提高系統(tǒng)彈性和可擴(kuò)展性
  • 缺點:
    • 一致性延遲,只能保證最終一致性
    • 實現(xiàn)和維護(hù)更復(fù)雜,需要消息隊列基礎(chǔ)設(shè)施
    • 可能需要處理消息重復(fù)和亂序問題
  • 適用場景
    • 大型分布式系統(tǒng)
    • 可以接受短暫不一致的業(yè)務(wù)場景
    • 需要解耦數(shù)據(jù)源和緩存更新邏輯的系統(tǒng)

2.3. 過期淘汰(被動更新)

  • 本質(zhì):依賴 Redis 自身的過期策略(如 TTL 過期、LRU 淘汰)被動更新緩存,配合核心策略使用(比如 Cache Aside 中給緩存設(shè) TTL,到期自動淘汰舊數(shù)據(jù))。
  • 特點:不主動更新,而是 “被動清理舊數(shù)據(jù)”,是所有策略的基礎(chǔ)保障(避免緩存永久有效)。

簡單說:過期淘汰是 “策略目標(biāo)”(讓過期緩存被清理),惰性刪除 + 定期刪除是 “技術(shù)手段”。

2.3.1. 惰性刪除(Lazy Delete)

  • 邏輯:當(dāng)用戶訪問某個 key 時,Redis 先檢查該鍵是否過期,若過期則立即刪除,不返回值;
  • 定位:過期淘汰的核心實現(xiàn)手段之一,被動觸發(fā),節(jié)省 CPU 資源(不用輪詢所有 key)。

2.3.2. 定期刪除(Periodic Delete)

  • 邏輯:Redis 會啟動一個后臺線程,每隔一段時間(默認(rèn) 100ms) 隨機(jī)抽取一部分過期 key 檢查,刪除其中已過期的;為了不阻塞主線程,每次檢查的時間和數(shù)量都有限制。
  • 定位:補(bǔ)充惰性刪除的不足(避免過期 key 長期不被訪問,一直占用內(nèi)存),主動但輕量化。

2.3.3. 兩者結(jié)合的原因

  • 只靠惰性刪除:過期 key 若長期不被訪問,會一直占內(nèi)存;
  • 只靠定期刪除:輪詢所有 key 會消耗大量 CPU,影響性能;
  • 結(jié)合使用:既保證了過期 key 最終會被清理(定期刪除兜底),又避免了過度消耗 CPU(惰性刪除減少檢查),是 Redis 平衡性能和內(nèi)存的最優(yōu)方案。

3. 內(nèi)存淘汰

內(nèi)存淘汰屬于「兜底型緩存清理機(jī)制」,是指 Redis 達(dá)到最大內(nèi)存(maxmemory)時,按照預(yù)設(shè)規(guī)則(如 LRU、LFU、隨機(jī)等)自動淘汰部分緩存數(shù)據(jù),本質(zhì)是 “內(nèi)存管理手段”,而非 “保證數(shù)據(jù)一致性的更新策略”。

  • 典型行為:Redis 內(nèi)存占滿后,淘汰最少使用的 key(LRU 策略);
  • 核心目標(biāo):當(dāng) Redis 內(nèi)存達(dá)到 maxmemory 上限時,主動淘汰部分鍵,釋放內(nèi)存以保證 Redis 能繼續(xù)接收新寫入;
  • 核心定位:目的是避免 Redis 內(nèi)存溢出,而非保證緩存與數(shù)據(jù)庫的一致性 —— 淘汰的可能是最新的、也可能是舊的緩存數(shù)據(jù),完全不考慮業(yè)務(wù)邏輯;
  • 常見策略
    • volatile-lru:淘汰設(shè)置了過期時間的鍵中,最近最少使用的;
    • allkeys-lru:淘汰所有鍵中最近最少使用的;
    • volatile-ttl:淘汰設(shè)置了過期時間的鍵中,剩余過期時間最短的;
    • noeviction(默認(rèn)):不淘汰任何鍵,內(nèi)存滿時拒絕新寫入并返回錯誤;
  • 是否屬于:? 嚴(yán)格來說,不算 “業(yè)務(wù)層面的緩存更新策略”,而是 Redis 底層的內(nèi)存保護(hù)機(jī)制;但廣義上可視為 “被動清理緩存的補(bǔ)充手段”。

簡單記:主動更新是 “主動做事”,過期淘汰是 “被動兜底做事”,內(nèi)存淘汰是 “實在沒內(nèi)存了才清理”,前兩者屬于緩存更新策略范疇,后者是底層機(jī)制。

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

相關(guān)文章

  • Redis分布式鎖一定要避開的兩個坑

    Redis分布式鎖一定要避開的兩個坑

    這篇文章主要為大家詳細(xì)介紹了Redis中分布式鎖一定要避開的兩個坑以及對應(yīng)的解決方法,文中的示例代碼講解詳細(xì),希望對大家有所幫助
    2023-04-04
  • redis keys與scan命令的區(qū)別說明

    redis keys與scan命令的區(qū)別說明

    這篇文章主要介紹了redis keys與scan命令的區(qū)別說明,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-03-03
  • redis快照模式_動力節(jié)點Java學(xué)院整理

    redis快照模式_動力節(jié)點Java學(xué)院整理

    這篇文章主要為大家詳細(xì)介紹了redis快照模式的相關(guān)資料,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-08-08
  • Windows環(huán)境下安裝Redis并設(shè)置Redis開機(jī)自啟實踐

    Windows環(huán)境下安裝Redis并設(shè)置Redis開機(jī)自啟實踐

    文章介紹了如何在Windows系統(tǒng)上安裝和配置Redis,包括下載Windows版本的Redis、設(shè)置連接密碼、啟動Redis、設(shè)置開機(jī)自啟等步驟
    2025-12-12
  • Linux、Windows下Redis的安裝即Redis的基本使用詳解

    Linux、Windows下Redis的安裝即Redis的基本使用詳解

    Redis是一個基于內(nèi)存的key-value結(jié)構(gòu)數(shù)據(jù)庫,Redis 是互聯(lián)網(wǎng)技術(shù)領(lǐng)域使用最為廣泛的存儲中間件,這篇文章主要介紹了Linux、Windows下Redis的安裝即Redis的基本使用詳解,需要的朋友可以參考下
    2022-09-09
  • Redis SDS字符串與集合的底層實現(xiàn)原理解析

    Redis SDS字符串與集合的底層實現(xiàn)原理解析

    這篇文章給大家介紹了Redis SDS字符串與集合的底層實現(xiàn)原理解析,本文結(jié)合實例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友參考下吧
    2026-04-04
  • 基于Redis位圖實現(xiàn)用戶簽到功能

    基于Redis位圖實現(xiàn)用戶簽到功能

    這篇文章主要介紹了基于Redis位圖實現(xiàn)用戶簽到功能,本文通過實例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-05-05
  • Redis分析慢查詢操作的實例教程

    Redis分析慢查詢操作的實例教程

    這篇文章主要給大家介紹了關(guān)于Redis如何分析慢查詢操作的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2018-09-09
  • Redis獲取某個前綴的key腳本實例

    Redis獲取某個前綴的key腳本實例

    這篇文章主要給大家介紹了關(guān)于Redis獲取某個前綴的key腳本的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家學(xué)習(xí)或者使用Redis具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。
    2018-04-04
  • 深度剖析Redis雙寫一致性問題的解決方案

    深度剖析Redis雙寫一致性問題的解決方案

    在高并發(fā)場景下,緩存與數(shù)據(jù)庫的雙寫一致性是每個開發(fā)者必須直面的核心挑戰(zhàn),本文通過5大解決方案,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解下
    2025-09-09

最新評論

嵊州市| 会宁县| 景泰县| 滨海县| 阿克| 内乡县| 庆安县| 武陟县| 古蔺县| 招远市| 秦皇岛市| 南宁市| 双城市| 榆中县| 叶城县| 肥东县| 谢通门县| 百色市| 北票市| 贡山| 志丹县| 嘉定区| 临邑县| 三江| 临城县| 鄂伦春自治旗| 黄浦区| 大石桥市| 商丘市| 金寨县| 荣昌县| 望谟县| 富源县| 岗巴县| 治县。| 漯河市| 云和县| 彭水| 井研县| 杭锦旗| 若尔盖县|