⏰ 快取過期與失效:TTL、LRU、主動清除
📌 TTL(Time To Live)存活時間
每個 Redis 鍵都可以設定「到期時間」,時間到了自動刪除。
# 設定鍵並指定 TTL(秒)
SET product:1001 "{...}" EX 300 # 300 秒後過期
# 設定鍵並指定 TTL(毫秒)
SET product:1001 "{...}" PX 300000 # 300000 毫秒後過期
# 對已存在的鍵設定 TTL
EXPIRE product:1001 300
PEXPIRE product:1001 300000
# 查看剩餘 TTL
TTL product:1001 # 返回剩餘秒數,-1 表示永不過期,-2 表示不存在
PTTL product:1001 # 毫秒精度
# 移除 TTL(變成永不過期)
PERSIST product:1001
📌 .NET 中設定過期時間
使用 StackExchange.Redis
var db = redis.GetDatabase();
// 設定帶 TTL 的鍵
await db.StringSetAsync("session:abc", "user-data",
TimeSpan.FromMinutes(30));
// 對已存在的鍵設定過期
await db.KeyExpireAsync("product:1001", TimeSpan.FromMinutes(5));
// 查看剩餘時間
TimeSpan? ttl = await db.KeyTimeToLiveAsync("product:1001");
Console.WriteLine($"剩餘: {ttl?.TotalSeconds} 秒");
// 移除過期(永不過期)
await db.KeyPersistAsync("product:1001");
使用 IDistributedCache
var options = new DistributedCacheEntryOptions();
// 絕對過期:從現在起 5 分鐘後過期(不管有沒有人存取)
options.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5);
// 滑動過期:2 分鐘沒人存取就過期(每次存取會重設計時器)
options.SlidingExpiration = TimeSpan.FromMinutes(2);
// 兩者可以同時使用!
// 例:滑動 2 分鐘 + 絕對 5 分鐘
// → 持續被存取最多活 5 分鐘,2 分鐘沒人用就過期
options.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5);
options.SlidingExpiration = TimeSpan.FromMinutes(2);
await _cache.SetStringAsync("product:1001", json, options);
📌 AbsoluteExpiration vs SlidingExpiration
絕對過期(Absolute):
├── 設定 ──── 5 分鐘後 ──── 過期(不管有沒有人用)
滑動過期(Sliding):
├── 設定 ── 存取 ── 存取 ── 2分鐘沒人用 ── 過期
│ ↑ 重設 ↑ 重設
同時使用:
├── 設定 ── 存取 ── 存取 ── 存取 ── 5分鐘到 ── 過期
│ ↑重設 ↑重設 ↑重設 (絕對上限)
| 類型 | 行為 | 適用場景 |
|---|---|---|
| Absolute | 固定時間後過期 | 商品資料、設定檔 |
| Sliding | 一段時間沒存取才過期 | Session、使用者暫存 |
| 兩者合用 | Sliding + 絕對上限 | 最佳實踐 |
📌 記憶體淘汰策略
當 Redis 記憶體用滿時,會根據設定的策略淘汰舊資料。
# 設定最大記憶體
maxmemory 256mb
# 設定淘汰策略
maxmemory-policy allkeys-lru
八種淘汰策略
| 策略 | 說明 | 適用場景 |
|---|---|---|
noeviction |
不淘汰,滿了就報錯 | 不能丟資料 |
allkeys-lru |
所有 key 中淘汰最近最少使用的 | 最常用 |
allkeys-lfu |
所有 key 中淘汰最不常使用的 | 熱點資料場景 |
allkeys-random |
隨機淘汰 | 隨意 |
volatile-lru |
有設 TTL 的 key 中淘汰 LRU | 混合持久/快取 |
volatile-lfu |
有設 TTL 的 key 中淘汰 LFU | 混合場景 |
volatile-random |
有設 TTL 的 key 中隨機淘汰 | 混合場景 |
volatile-ttl |
淘汰即將過期的 key | TTL 管理嚴格 |
推薦: 純快取用
allkeys-lru,混合用途用volatile-lru。
📌 主動清除快取的時機與方法
public class CacheInvalidationService
{
private readonly IDatabase _redis;
// 1. 更新資料時清除
public async Task OnProductUpdated(int productId)
{
await _redis.KeyDeleteAsync($"product:{productId}");
}
// 2. 批量清除某個前綴的所有快取
public async Task ClearProductCache()
{
var server = _redis.Multiplexer.GetServer("localhost:6379");
var keys = server.Keys(pattern: "product:*").ToArray();
if (keys.Length > 0)
await _redis.KeyDeleteAsync(keys);
Console.WriteLine($"已清除 {keys.Length} 筆商品快取");
}
// 3. 使用 Pub/Sub 通知其他實例清除快取
public async Task PublishCacheInvalidation(string key)
{
var sub = _redis.Multiplexer.GetSubscriber();
await sub.PublishAsync("cache:invalidate", key);
}
}
📌 範例:商品快取 + 30 秒 TTL
public class ProductCacheService
{
private readonly IDistributedCache _cache;
private readonly AppDbContext _db;
private static readonly DistributedCacheEntryOptions _cacheOptions = new()
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(30),
SlidingExpiration = TimeSpan.FromSeconds(10)
};
public async Task<Product?> GetProductAsync(int id)
{
var key = $"product:{id}";
var cached = await _cache.GetStringAsync(key);
if (cached != null)
return JsonSerializer.Deserialize<Product>(cached);
var product = await _db.Products.FindAsync(id);
if (product != null)
{
await _cache.SetStringAsync(key,
JsonSerializer.Serialize(product), _cacheOptions);
}
return product;
}
}
🔑 重點整理
- TTL 是快取過期的基礎,用 EX/PX 或 EXPIRE 設定
- AbsoluteExpiration 固定時間過期,SlidingExpiration 閒置才過期
- 最佳實踐是 兩者合用,Sliding + 絕對上限
- 記憶體滿時,allkeys-lru 是最常用的淘汰策略
- 資料更新後要 主動清除快取,保持一致性