缓存策略 - Amazon ElastiCache for Redis
Amazon Web Services 文档中描述的 Amazon Web Services 服务或功能可能因区域而异。要查看适用于中国区域的差异,请参阅中国的 Amazon Web Services 服务入门

本文属于机器翻译版本。若本译文内容与英语原文存在差异,则一律以英文原文为准。

缓存策略

在以下主题中,您可以找到用于填充和维护缓存的策略。

为填充并维护缓存而执行的策略取决于要进行缓存的数据以及针对这些数据的访问模式。例如,您可能不想对游戏站点上排名前 10 排行榜和趋势新闻报道采用相同的策略。在本部分的剩余内容中,我们将讨论常见缓存维护策略及其优点和缺点。

延迟加载

顾名思义,延迟加载是一种仅在需要时将数据加载到缓存中的缓存策略。它的工作原理如下。

Amazon ElastiCache是一种内存中键值存储,位于应用程序和它访问的数据存储(数据库)之间。当应用程序请求数据时,它会先向 ElastiCache 缓存发出请求。如果数据在缓存中且最新,则 ElastiCache 会将数据返回到应用程序。如果数据不在缓存中或已过期,则应用程序会从数据存储中请求数据。然后,数据存储将数据返回到应用程序。接下来,应用程序会将从存储接收的数据写入缓存。这样,可以在下次请求时更快地检索它。

A缓存命中结果当数据在缓存中且未过期时会发生:

  1. 应用程序从缓存中请求数据。

  2. 缓存将数据返回到应用程序。

A缓存未命中当数据不在缓存中或已过期时会发生:

  1. 应用程序从缓存中请求数据。

  2. 缓存没有请求的数据,因此返回了null.

  3. 应用程序从数据库中请求并接收数据。

  4. 应用程序使用新数据更新缓存。

延迟加载的优点和缺点

延迟加载的优点如下:

  • 仅对请求的数据进行缓存。

    由于大部分数据从未被请求,因此延迟加载避免了向缓存中填入未请求的数据。

  • 节点故障对您的应用程序来说并不是致命的。

    当某个节点发生故障并由新的空节点替换时,应用程序将继续运行,但延迟会增加。当向新节点发出请求时,每次缓存未命中都会导致对数据库进行查询。同时,数据副本会添加到缓存中,以便从缓存中检索后续请求。

延迟加载的缺点如下所示:

  • 缓存未命中会导致性能损失。每次缓存未命中会导致三次往返:

    1. 初次从缓存中请求数据

    2. 查询数据库中的数据

    3. 将数据写入缓存

    这些未命中会导致在数据到达应用程序时出现显著延迟。

  • 过时数据。

    如果仅在缓存未命中时将数据写入缓存,则缓存中的数据会变得过时。出现此结果的原因是,在数据库中更改数据时没有对缓存进行更新。要解决此问题,您可以使用直写添加 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 // ***************************************** 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)

直写

直写策略会在将数据写入数据库时在缓存中添加或更新数据。

写入的优点和缺点

写入的优点如下:

  • 缓存中的数据永不过时。

    由于每次将缓存中的数据写入数据库时都会更新这些数据,因此缓存中的数据始终保持最新。

  • 写入性能损失与

    每次写入都涉及两次往返:

    1. 对缓存进行写入

    2. 对数据库进行写入

    这将增加流程的延迟。即便如此,与检索数据时的延迟相比,最终用户通常更能容忍更新数据时的延迟。有一个内在的意义,即更新的工作量更大,因而花费的时间会更长。

写入的缺点如下:

  • 缺失的数据。

    如果启动新节点,无论是因为节点故障还是横向扩展,则缺少数据。在数据库上添加或更新之前,此数据将继续丢失。您可以通过实现延迟加载使用直写。

  • 缓存扰动。

    大部分数据从未被读取,这是一种资源浪费。通过添加生存时间 (TTL) 值,则可以最大程度地减少此情况。

穿透伪代码示例

以下是写入逻辑的伪代码示例。

// ***************************************** // 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

延迟加载允许过时数据,但不会失败并产生空节点。直写可确保数据始终最新,但可能会失败并产生空节点,并且可以向缓存填充过多的数据。通过向每次写入添加生存时间 (TTL),您可以获得每种策略的优点。同时,您可以并且在很大程度上避免使用额外数据混淆缓存。

生存时间 (TTL)是一个整数值,指定秒数直到密钥过期。Redis 可以为此值指定秒或毫秒。当应用程序尝试读取过期密钥时,它将被视为未找到该密钥。将查询数据库中的密钥并更新高速缓存。这种方法不能保证值不会过时。但是,它可以防止数据变得太陈旧,并且要求不时从数据库刷新缓存中的值。

有关更多信息,请参阅 Redis set 命令

TTL 伪代码示例

以下是利用 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)

相关主题