本文属于机器翻译版本。若本译文内容与英语原文存在差异,则一律以英文原文为准。
Memcached 的缓存策略
在以下主题中,您可以找到填充和维护 Memcached 缓存的策略。
为填充并维护缓存而执行的策略取决于要缓存的数据以及针对这些数据的访问模式。例如,您可能不想对游戏站点上排名前 10 排行榜和趋势新闻报道执行相同的策略。在本节的剩余内容中,我们将讨论常见缓存维护策略及其优点和缺点。
延迟加载
顾名思义,延迟加载是一种仅在需要时将数据加载到缓存中的缓存策略。它的工作原理如下。
Amazon ElastiCache 是一种内存键值存储,位于您的应用程序和它访问的数据存储(数据库)之间。每当您的应用程序请求数据时,它都会首先向 ElastiCache 缓存发出请求。如果数据存在于缓存中并且是最新的,则ElastiCache 会将数据返回到您的应用程序。如果数据不在缓存中或已过期,则应用程序会请求数据存储中的数据。然后,数据存储将数据返回到应用程序。之后,应用程序会将从存储接收的数据写入缓存。这样就可以在下次请求时更快地检索这些数据。
当数据位于缓存中且未过期时,就会发生缓存命中:
应用程序请求缓存中的数据。
缓存将数据返回给应用程序。
当数据不在缓存中或已过期时,就会发生缓存未命中:
应用程序请求缓存中的数据。
缓存不具有所请求的数据,因此返回了
null
。应用程序请求数据库中的数据并收到数据。
应用程序使用新数据更新缓存。
延迟加载的优点和缺点
延迟加载的优点如下:
-
仅对请求的数据进行缓存。
由于大部分数据从未被请求过,因此延迟加载避免了向缓存中填入未请求的数据。
-
节点故障对应用程序来说并不致命。
当某个节点发生故障并由新的空节点替换时,应用程序会继续运行,但延迟会增加。向新节点发出请求时,每次缓存未命中都会导致在数据库中进行查询。同时会将数据副本添加到缓存中,以便后续请求从缓存中进行检索。
延迟加载的缺点如下:
延迟加载伪代码示例
以下代码是延迟加载逻辑的伪代码示例。
// *****************************************
// 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)
直写
直写策略会在将数据写入数据库时在缓存中添加或更新数据。
直写的优点和缺点
直写的优点如下:
-
缓存中的数据永不过时。
由于每次将缓存中的数据写入数据库时都会更新这些数据,因此缓存中的数据始终为最新数据。
-
直写性能损失与读取性能损失比较。
每次写入都涉及两次往返:
对缓存进行写入
对数据库进行写入
这将增加流程的延迟。即便如此,与检索数据时的延迟相比,最终用户通常更能容忍更新数据时的延迟。有一个内在的意义,即更新的工作量更大,因而花费的时间会更长。
直写的缺点如下:
-
缺失的数据。
如果启动新节点(无论是由于节点故障还是横向扩展),都会出现数据缺失。此数据将持续保持丢失状态直到将其添加或更新到数据库。您可以通过实现延迟加载和使用直写来最大限度减少此情况。
-
缓存扰动。
大多数数据从不会被读取,这是一种资源浪费。通过添加 time to live (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) 值,您可以充分利用每种策略的优势。同时,您可以并在很大程度上避免多余数据混淆缓存。
Time to live (TTL) 是一个整数值,它指定距离密钥过期的秒数。Valkey 或 Redis OSS 可以为此值指定秒或毫秒。Memcached 指定此值(以秒为单位)。当应用程序尝试读取过期密钥时,其处理方式是当做未找到该密钥。应用程序会在数据库中查询该密钥并更新缓存。这种方法不能保证值不会过时。不过,其可以防止数据过时太久,并要求不时从数据库中刷新缓存中的值。
有关更多信息,请参阅 Valkey 和 Redis OSS 命令或 Memcac 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)