首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

基於 Spring AOP 的網站緩存實時更新實踐:設計模式與併發考量

     編輯:Java 相關知識

高性能網站離不開多層緩存:首頁排行榜、熱門話題、最新註冊/登錄用戶等「讀多寫少」數據,若每次查數據庫將成為瓶頸。本文展示一種零侵入、聲明式、實時一致性的緩存更新方案:領域模型封裝緩存邏輯 + Spring AOP 切面攔截寫操作自動更新緩存,並深入分析高併發下的數據結構選型與一致性權衡。

架構分層設計

┌─────────────────────────────────────┐
│         Controller / Service        │  業務邏輯純淨,無緩存代碼
├─────────────────────────────────────┤
│         Spring AOP 切面層           │  @AfterReturning 攔截寫方法
├─────────────────────────────────────┤
│         Cache Manager (單例)        │  線程安全容器 + 排序策略
├─────────────────────────────────────┤
│         Domain Model (POJO)         │  實現 Comparable/Comparator
└─────────────────────────────────────┘

核心代碼實現

1. 緩存容器:線程安全 + 可排序

public final class WebCacheUtils {
    // 使用 CopyOnWriteArrayList 實現讀多寫少場景的高併發安全遍歷
    private static final List RECENT_REGISTERED = new CopyOnWriteArrayList<>();
    private static final List RECENT_LOGINED = new CopyOnWriteArrayList<>();
    private static final List HOT_TOPICS = new CopyOnWriteArrayList<>();

    // 熱門話題排序:權重 4:6 (贊同數:評論數)
    private static final Comparator HOT_COMPARATOR = (t1, t2) -> {
        double s1 = t1.getAgreeCount() * 0.4 + t1.getCommentCount() * 0.6;
        double s2 = t2.getAgreeCount() * 0.4 + t2.getCommentCount() * 0.6;
        return Double.compare(s2, s1); // 降序
    };

    public static void addRecentRegistered(Login user) {
        if (user == null) return;
        RECENT_REGISTERED.remove(user); // 去重
        RECENT_REGISTERED.add(0, user); // 新增到頭部
        trim(RECENT_REGISTERED, 50);    // 保留 Top 50
    }

    public static void addRecentLogined(Login user) {
        if (user == null) return;
        RECENT_LOGINED.remove(user);
        RECENT_LOGINED.add(0, user);
        trim(RECENT_LOGINED, 100);
    }

    public static void updateHotTopic(Topic topic) {
        if (topic == null) return;
        HOT_TOPICS.remove(topic);
        HOT_TOPICS.add(topic);
        HOT_TOPICS.sort(HOT_COMPARATOR);
        trim(HOT_TOPICS, 20);
    }

    private static  void trim(List list, int maxSize) {
        while (list.size() > maxSize) list.remove(list.size() - 1);
    }

    // 只讀獲取方法...
}

2. 切面類:聲明式攔截寫操作

@Aspect
@Component
@Slf4j
public class CacheUpdateAspect {

    @AfterReturning(pointcut = "execution(* com.example.service.UserService.checkUserLogin(..)) && args(..) && returning(result)", 
                    returning = "result")
    public void onUserLogin(JoinPoint jp, Object result) {
        if (result instanceof Login login) {
            log.debug("更新最近登錄緩存: {}", login.getUsername());
            WebCacheUtils.addRecentLogined(login);
        }
    }

    @AfterReturning(pointcut = "execution(* com.example.service.UserService.addUser(..)) && args(user)", 
                    argNames = "user")
    public void onUserRegister(Login user) {
        if (user != null) {
            log.debug("更新最新註冊緩存: {}", user.getUsername());
            WebCacheUtils.addRecentRegistered(user);
        }
    }

    @AfterReturning(pointcut = "execution(* com.example.service.TopicService.publishTopic(..)) && args(topic)", 
                    argNames = "topic")
    public void onTopicPublish(Topic topic) {
        if (topic != null) {
            WebCacheUtils.updateHotTopic(topic);
        }
    }
}

3. 業務服務保持純淨

@Service
@Transactional
public class UserServiceImpl implements UserService {
    @Override
    public Login checkUserLogin(String username, String password) {
        // 純業務邏輯:驗證密碼、更新最後登錄時間、返回 Login 對象
        return loginDao.authenticate(username, password);
    }

    @Override
    public void addUser(Login user) {
        userDao.insert(user);
    }
}

高併發場景深度分析

維度現狀/風險優化建議
數據結構CopyOnWriteArrayList 寫入複製整個數組,寫入頻繁時 GC 壓力大熱點緩存改用 ConcurrentSkipListSet (自帶排序) 或 ConcurrentHashMap + 定時重排
一致性最終一致性:DB 提交後異步更新緩存,極端情況下緩存比 DB 滯後幾毫秒引入 @TransactionalEventListener(phase = AFTER_COMMIT) 保證事務提交後再更新緩存
分佈式擴展單機內存緩存,集群間不一致遷移至 Redis Sorted Set (ZSET) 存儲排行榜,本地緩存僅作 L2
失效策略無主動失效,依賴容量裁剪加入 TTL、定時全量重建、關鍵業務變更主動刷新
異常處理切面異常不影響主流程,但緩存可能不更新切面內 try-catch 記錄錯誤,引入補償定時任務糾錯

進階演進:從本地緩存到多級緩存架構

請求 → [Nginx 本地緩存] → [應用層 Caffeine/Guava L1] → [Redis Cluster L2] → [MySQL]
                    ↑              ↑                    ↑
               靜態資源        熱點數據(毫秒級)      排行榜/會話/分佈式鎖
  • Caffeine:高性能本地緩存,支持 W-TinyLFU 淘汰、異步加載、統計指標
  • Redis ZSET:ZINCRBY score member 原子更新分數,ZREVRANGE 獲取 Top N,完美替代手工排序邏輯
  • Cache Aside 模式:讀時先查 L1→L2→DB,寫時先更新 DB 再異步失效/更新 L1/L2

總結

Spring AOP + 域模型封裝緩存邏輯,實現了業務代碼零侵入、緩存更新邏輯集中管理、實時性與一致性可控。單機階段 CopyOnWriteArrayList 足夠應付中低併發;隨著流量增長,應及時引入 Caffeine 本地緩存 + Redis 分佈式有序集合,並建立完善的緩存監控(命中率、延遲、一致性延遲)與降級預案。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved