跳至主要内容

快取使用範例

位置: app-server 模組 目的: 展示如何在實際 Spring Boot 應用程式中使用 AppFuse Cache

📋 目錄

快取配置

配置類別

位置: io.leandev.app.config.CacheConfig

@Configuration
public class CacheConfig {

@Bean
public CacheManager cacheManager() {
return CacheManagerBuilder
.newCacheManager()
.withPersistence(Paths.get("./data/cache")) // 支援持久化
.build();
}

// 定義各種快取 Bean...
}

已定義的快取

快取名稱類型用途過期策略
sessionsDualLayerCacheSession 管理快速層 30 分鐘 TTL
usersCache使用者資訊30 分鐘 TTL
rateLimitCacheAPI 限流5 分鐘 TTI
verificationCodesCache驗證碼5 分鐘 TTL
configsCache系統配置永不過期
tokenBlacklistCacheToken 黑名單15 分鐘 TTL
loginAttemptsCacheLogin Lockout30 分鐘 TTI

Session 快取

使用場景

展示如何使用雙層快取管理 Session,提供高效能與容錯能力。

服務類別

位置: io.leandev.app.service.cache.SessionCacheService

使用範例

@RestController
@RequestMapping("/api/sessions")
@RequiredArgsConstructor
public class SessionController {

private final SessionCacheService sessionCacheService;

// 建立 Session
@PostMapping("/login")
public String login(@RequestBody LoginRequest request) {
String userId = authenticateUser(request);
String sessionId = sessionCacheService.createSession(userId);
return sessionId;
}

// 取得 Session(自動降級)
@GetMapping("/{sessionId}")
public Optional<String> getSession(@PathVariable String sessionId) {
return sessionCacheService.getSession(sessionId);
}

// 延長 Session
@PostMapping("/{sessionId}/touch")
public void touchSession(@PathVariable String sessionId) {
sessionCacheService.touchSession(sessionId);
}

// 登出
@DeleteMapping("/{sessionId}")
public void logout(@PathVariable String sessionId) {
sessionCacheService.removeSession(sessionId);
}
}

關鍵特性

1. 雙層架構

// 快速層:存放熱資料,30 分鐘過期
// 持久層:存放所有資料,永不過期
sessionCache.put(sessionId, userId); // 同時寫入兩層

2. 自動降級

// 優先從快速層讀取,若不存在則自動降級到持久層
String userId = sessionCache.get(sessionId);

3. 不降級模式

// 只從快速層讀取,用於確認 session 是否仍在有效期內
String userId = sessionCache.get(sessionId, false);

4. 統計監控

CacheStatistics stats = sessionCacheService.getStatistics();
System.out.println("Hit ratio: " + stats.getHitRatio());
System.out.println("Fallback count: " + sessionCacheService.getFallbackCount());

使用者快取

使用場景

展示如何使用標準快取實作 Cache-Aside Pattern,減少資料庫查詢。

服務類別

位置: io.leandev.app.service.cache.UserCacheService

使用範例

@Service
@RequiredArgsConstructor
public class UserService {

private final UserCacheService userCacheService;
private final UserRepository userRepository;

// Cache-Aside Pattern
public Optional<User> getUser(Long userId) {
return userCacheService.getUser(userId,
// 資料庫查詢 Lambda
id -> userRepository.findById(id)
.map(User::toJson)
);
}

// Write-Through Pattern
public void updateUser(Long userId, String userData) {
userCacheService.updateUser(userId, userData,
// 資料庫更新 Lambda
(id, data) -> {
User user = User.fromJson(data);
userRepository.save(user);
}
);
}

// 預熱快取
@PostConstruct
public void warmUpCache() {
List<Long> hotUserIds = userRepository.findHotUserIds();
userCacheService.warmUp(hotUserIds,
id -> userRepository.findById(id).map(User::toJson)
);
}
}

快取模式

1. Cache-Aside(旁路快取)

// 1. 先查快取
String user = cache.get(userId);

// 2. 若 miss,查資料庫
if (user == null) {
user = database.findById(userId);
cache.put(userId, user); // 3. 寫入快取
}

2. Write-Through(寫穿)

// 同步更新資料庫和快取
database.update(userId, userData);
cache.put(userId, userData);

3. Cache Invalidation(失效)

// 資料庫直接更新後,手動失效快取
database.update(userId, userData);
userCacheService.invalidate(userId);

API 限流

使用場景

展示如何使用 Time-to-Idle 快取實作 API 限流,防止濫用。

服務類別

位置: io.leandev.app.service.cache.RateLimitService

使用範例

@Component
@RequiredArgsConstructor
public class RateLimitInterceptor implements HandlerInterceptor {

private final RateLimitService rateLimitService;

@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {

String apiKey = request.getHeader("X-API-Key");

// 檢查限流
if (!rateLimitService.allowRequest(apiKey)) {
response.setStatus(429); // Too Many Requests
response.getWriter().write("Rate limit exceeded");
return false;
}

// 設定剩餘次數 Header
int remaining = rateLimitService.getRemainingRequests(apiKey);
response.setHeader("X-RateLimit-Remaining", String.valueOf(remaining));

return true;
}
}

自訂限制

// 不同等級的限制
public class ApiTier {
public static final int FREE_TIER = 100; // 免費:100 次/5分鐘
public static final int BASIC_TIER = 500; // 基礎:500 次/5分鐘
public static final int PREMIUM_TIER = 5000; // 進階:5000 次/5分鐘
}

// 使用
boolean allowed = rateLimitService.allowRequest(apiKey, ApiTier.PREMIUM_TIER);

Time-to-Idle 特性

  • 每次存取後重新計時:持續活動的用戶不會被清除
  • 5 分鐘無活動則過期:自動清理不活躍的記錄
  • 節省記憶體:只保留活躍用戶的記錄

監控與管理

快取統計

@GetMapping("/admin/cache/stats")
public Map<String, CacheStatistics> getCacheStatistics() {
return Map.of(
"sessions", sessionCacheService.getStatistics(),
"users", userCacheService.getStatistics(),
"rateLimit", rateLimitService.getStatistics()
);
}

統計資訊包含

  • hitCount: 命中次數
  • missCount: 未命中次數
  • hitRatio: 命中率(0.0 ~ 1.0)
  • size: 當前項目數
  • putCount: 寫入次數
  • removeCount: 移除次數
  • evictionCount: 淘汰次數

快取管理操作

// 停用快取(維護模式)
sessionCacheService.disableCache();

// 啟用快取
sessionCacheService.enableCache();

// 清空快取
userCacheService.clearAll();

// 手動失效
userCacheService.invalidate(userId);

雙層快取特殊操作

// 只清除快速層(保留持久層作為降級)
sessionCacheService.clearExpiredSessions();

// 查看降級次數
long fallbackCount = sessionCacheService.getFallbackCount();

// 查看統計(包含兩層的聚合統計)
CacheStatistics stats = sessionCacheService.getStatistics();

最佳實踐

1. 選擇合適的快取類型

場景推薦類型原因
Session 管理DualLayerCache需要降級能力
使用者資訊Cache (TTL)定期過期避免髒資料
API 限流Cache (TTI)自動清理不活躍記錄
系統配置Cache (NoExpiration)手動控制更新

2. 合理設定過期時間

// 短期資料:5-15 分鐘
.ttl(5)

// 中期資料:30-60 分鐘
.ttl(30)

// 永久資料:不過期
.noExpiration()

3. 監控快取效能

// 定期檢查命中率
CacheStatistics stats = cache.getStatistics();
if (stats.getHitRatio() < 0.8) {
log.warn("Low cache hit ratio: {}", stats.getHitRatio());
}

4. 處理快取穿透

// 快取空值,避免重複查詢不存在的資料
if (userFromDb.isEmpty()) {
cache.put(userId, "NULL"); // 使用特殊值表示不存在
}

5. 快取預熱

@PostConstruct
public void warmUp() {
// 在系統啟動時預先載入熱門資料
List<Long> hotIds = repository.findHotIds();
cacheService.warmUp(hotIds, repository::findById);
}

相關文檔