

本文為英文版的機器翻譯版本，如內容有任何歧義或不一致之處，概以英文版為準。

# Memcached 的快取策略
<a name="Strategies"></a>

在下列主題中，您可以找到填入和維護 Memcached 快取的策略。

您實作用於填入和維護快取的策略，取決於您要快取的資料，以及該資料的存取模式。例如，您可能不會想對遊戲網站前十名排行榜和趨勢新聞報導使用相同的策略。在本節的其餘部分，我們會討論常見的快取維護策略，以及其優點和缺點。

**Topics**
+ [僅供讀取複本](#Strategies.ReadReplicas)
+ [延遲載入](#Strategies.LazyLoading)
+ [全部寫入](#Strategies.WriteThrough)
+ [新增 TTL](#Strategies.WithTTL)
+ [相關主題](#Strategies.SeeAlso)

## 僅供讀取複本
<a name="Strategies.ReadReplicas"></a>

您通常可以透過建立複本並從中讀取，而不是主要快取節點，大幅改善 ElastiCache 無伺服器快取的效能。如需更多資訊，請參閱[使用僅供讀取複本的最佳實務](ReadReplicas.md)。

## 延遲載入
<a name="Strategies.LazyLoading"></a>

如同名稱所隱含的意義，*延遲載入*是一項快取策略，只有在必要時才會將資料載入快取中。它的運作方式如下。

Amazon ElastiCache 是記憶體內鍵值存放區，位於應用程式與其存取的資料存放區 (資料庫) 之間。每當應用程式請求資料，它會先向 ElastiCache 快取進行請求。如果資料存在於快取且是最新的，ElastiCache 會將該資料傳回至您的應用程式。如果資料不存在於快取中或已過期，則應用程式會向資料存放區請求資料。接著資料存放區會將資料傳回至應用程式。。應用程式會將從存放區收到的資料寫入快取中。如此一來，下次收到請求時就能更快速擷取這些資料。

當資料存在於快取中且未過期，就會發生*快取命中*：

1. 應用程式向快取請求資料。

1. 快取將資料傳回給應用程式。

當資料不存在於快取中已過期，就會發生*快取未命中*：

1. 應用程式向快取請求資料。

1. 快取中沒有所請求的資料，因此傳回 `null`。

1. 應用程式請求並收到來自資料庫的資料。

1. 應用程式以新資料更新快取。

### 延遲載入的優點和缺點
<a name="Strategies.LazyLoading.Evaluation"></a>

延遲載入的優點如下：
+ 只會將請求的資料加以快取。

  因為大部分資料永遠不會被請求，延遲載入可避免將快取填滿未請求的資料。
+ 節點故障不會成為應用程式的嚴重問題。

  當節點故障並且由新的空白節點取代時，應用程式會繼續運作，只是會增加延遲。對新節點提出請求時，每次快取未命中都會產生一個資料庫查詢。同時會將資料複本新增至快取中，以便從快取擷取後續的請求。

延遲載入的缺點如下：
+ 快取遺漏會有懲罰。每次快取未命中都會產生 3 趟行程：

  1. 向快取初始請求資料

  1. 查詢資料庫來取得資料

  1. 將資料寫入快取

   這些未命中可能會造成應用程式取得資料的時間出現明顯的延遲。
+ 資料過時。

  如果只在快取未命中時將資料寫入快取，則快取中的資料可能變得過時。當資料庫中的資料變更時，並不會更新快取，因此會產生此結果。若要解決這個問題，您可以使用 [全部寫入](#Strategies.WriteThrough) 和 [新增 TTL](#Strategies.WithTTL) 策略。

### 延遲載入虛擬程式碼範例
<a name="Strategies.LazyLoading.CodeExample"></a>

以下是延遲載入邏輯的虛擬程式碼範例。

```
// *****************************************
// function that returns a customer's record.
// Attempts to retrieve the record from the cache.
// If it is retrieved, the record is returned to the application.
// If the record is not retrieved from the cache, it is
//    retrieved from the database, 
//    added to the cache, and 
//    returned to the application
// *****************************************
get_customer(customer_id)

    customer_record = cache.get(customer_id)
    if (customer_record == null)
    
        customer_record = db.query("SELECT * FROM Customers WHERE id = {0}", customer_id)
        cache.set(customer_id, customer_record)
    
    return customer_record
```

在此範例中，取得資料的應用程式程式碼如下。

```
customer_record = get_customer(12345)
```

## 全部寫入
<a name="Strategies.WriteThrough"></a>

每當有資料寫入資料庫，全部寫入 (write-through) 策略就會在快取中新增或更新資料。

### 全部寫入的優點和缺點
<a name="Strategies.WriteThrough.Evaluation"></a>

全部寫入的優點如下：
+ 快取中的資料絕不會過時。

  快取中的資料每次寫入資料庫時都會進行更新，因此快取中的資料永遠是最新的。
+ 寫入懲罰與讀取懲罰。

  每次寫入都牽涉到兩個來回行程：

  1. 一個寫入快取

  1. 一個寫入資料庫

   這會對程序增加延遲。也就是說，相較於擷取資料，最終使用者一般更能容忍更新資料時的延遲。固有的想法是更新需要的動作較多因此會花費較長時間。

全部寫入的缺點如下：
+ 遺漏資料。

  如果啟動新節點，無論是因為節點故障或水平擴展，都會發生資料未命中。這些資料會繼續處於未命中狀態，直到在資料庫上新增或更新為止。您可以藉由搭配全部寫入實作[延遲載入](#Strategies.LazyLoading)來盡可能減少此情形。
+ 快取流失。

  大部分資料都永遠不會被讀取，導致資源的浪費。您可以藉由[新增存留時間 (TTL) 值](#Strategies.WithTTL)，將浪費的空間降到最少。

### 全部寫入虛擬程式碼範例
<a name="Strategies.WriteThrough.CodeExample"></a>

下列是全部寫入邏輯的虛擬程式碼範例。

```
// *****************************************
// function that saves a customer's record.
// *****************************************
save_customer(customer_id, values)

    customer_record = db.query("UPDATE Customers WHERE id = {0}", customer_id, values)
    cache.set(customer_id, customer_record)
    return success
```

在此範例中，取得資料的應用程式程式碼如下。

```
save_customer(12345,{"address":"123 Main"})
```

## 新增 TTL
<a name="Strategies.WithTTL"></a>

延遲載入允許使用過時資料，但不會因空白節點而失敗。全部寫入可確保資料總是保持最新狀態，但可能因為空白節點而失敗，且可能對快取填入多餘的資料。對每個寫入新增存留時間 (TTL) 值，便可享受每個策略的優點。同時可以大幅避免將快取塞滿多餘的資料。

*存留時間 (TTL)* 是整數值，可指定金鑰過期前的秒數。Valkey 或 Redis OSS 可以為此值指定秒或毫秒。Memcached 需以秒為單位指定此值。應用程式嘗試讀取過期的索引鍵時，系統會視為找不到索引鍵。資料庫會查詢索引鍵並更新快取。這種方法並不能保證值不會過時。不過會確保資料不致於過時太久，且需要偶爾從資料庫重新整理快取中的值。

如需詳細資訊，請參閱 [Valkey 和 Redis OSS 命令](https://valkey.io/commands)或 [Memcached `set`命令](https://www.tutorialspoint.com/memcached/memcached_set_data.htm)。

### TTL 虛擬程式碼
<a name="Strategies.WithTTL.CodeExample"></a>

下列程式碼是使用 TTL 的全部寫入邏輯的虛擬程式碼範例。

```
// *****************************************
// function that saves a customer's record.
// The TTL value of 300 means that the record expires
//    300 seconds (5 minutes) after the set command 
//    and future reads will have to query the database.
// *****************************************
save_customer(customer_id, values)

    customer_record = db.query("UPDATE Customers WHERE id = {0}", customer_id, values)
    cache.set(customer_id, customer_record, {{300}})

    return success
```

下列程式碼是使用 TTL 的延遲載入邏輯的虛擬程式碼範例。

```
// *****************************************
// function that returns a customer's record.
// Attempts to retrieve the record from the cache.
// If it is retrieved, the record is returned to the application.
// If the record is not retrieved from the cache, it is 
//    retrieved from the database, 
//    added to the cache, and 
//    returned to the application.
// The TTL value of 300 means that the record expires
//    300 seconds (5 minutes) after the set command 
//    and subsequent reads will have to query the database.
// *****************************************
get_customer(customer_id)

    customer_record = cache.get(customer_id)
    
    if (customer_record != null)
        if (customer_record.TTL < 300)
            return customer_record        // return the record and exit function
            
    // do this only if the record did not exist in the cache OR
    //    the TTL was >= 300, i.e., the record in the cache had expired.
    customer_record = db.query("SELECT * FROM Customers WHERE id = {0}", customer_id)
    cache.set(customer_id, customer_record, {{300}})  // update the cache
    return customer_record                // return the newly retrieved record and exit function
```

在此範例中，取得資料的應用程式程式碼如下。

```
save_customer(12345,{"address":"123 Main"})
```

```
customer_record = get_customer(12345)
```

## 相關主題
<a name="Strategies.SeeAlso"></a>
+ [記憶體內資料存放區](elasticache-use-cases.md#elasticache-use-cases-data-store)
+ [選擇引擎和版本](SelectEngine.md)
+ [擴展 ElastiCache](Scaling.md)