这是缓存速成课程系列的第二篇。

分布式缓存将频繁访问的数据存储在多个节点的内存中。缓存数据被分区存储在许多节点上,每个节点只存储一部分缓存数据。节点以键值对的形式存储数据,每个键通过确定性方式被分配到一个特定的分区或分片。当客户端请求数据时,缓存系统从相应的节点检索数据,从而减轻后端存储的负载。

有不同的分片策略,包括取模、基于范围和一致性哈希。

取模分片(Modulus sharding)根据键的哈希值除以分片总数的余数,将键分配到一个分片。虽然这种策略很简单,但当分片数量增加或减少时,会导致大量缓存未命中。这是因为当池子调整大小时,大多数键会被重新分配到不同的分片。

取模分片示意图

范围分片(Range-based sharding)根据预定义的键范围将键分配到特定的分片。使用这种方法,系统可以将键空间划分为特定范围,然后将每个范围映射到特定的分片。范围分片对于某些业务场景很有用,例如数据按地理位置或特定客户细分自然分组的情况。

范围分片示意图

然而,这种方法扩展起来也可能具有挑战性,因为分片数量是预定义的,不容易更改。更改分片数量需要重新定义键范围并重新映射数据。

一致性哈希(Consistent hashing)是一种广泛使用的分片策略,与其他分片方法相比,它提供了更好的负载均衡和容错能力。使用一致性哈希时,键和节点都被映射到一个固定大小的环上,使用哈希函数为每个键和节点分配环上的一个位置。

一致性哈希示意图

当请求一个键时,系统使用相同的哈希函数将键映射到环上的一个位置。然后系统从该位置顺时针遍历环,直到到达第一个节点。该节点负责存储与该键关联的数据。向系统添加或移除节点只需要重新映射先前存储在被影响节点上的键,而不是重新分配所有键,这使得更改分片数量时只需要重新哈希少量数据。

在本节中,我们将深入分析各种缓存策略、它们的特性和适用场景。

缓存策略可以根据它们处理读写数据的方式进行分类:

  • 读策略:Cache-Aside 和 Read-Through
  • 写策略:Write-Around、Write-Through 和 Write-Back

Cache-Aside(也称为懒加载)是一种流行的缓存策略,应用程序直接与缓存和存储系统通信。它遵循以下工作流程来读取数据:

  1. 应用程序从缓存请求一个键
  2. 如果键在缓存中找到(缓存命中),数据返回给应用程序
  3. 如果键未在缓存中找到(缓存未命中),应用程序继续从存储请求该键
  4. 存储将数据返回给应用程序
  5. 应用程序将键和相应数据写入缓存,供将来读取使用

Cache-Aside 读策略

Cache-Aside 用途广泛,可以适应各种用例,并且很好地适应读密集型工作负载。

优点

  • 系统可以容忍缓存故障,因为仍然可以从存储读取
  • 缓存中的数据模型可以与存储中的数据模型不同,为各种用例提供灵活性

缺点

  • 应用程序必须管理缓存和存储,使代码复杂化
  • 由于缺乏对缓存和存储的原子操作,确保数据一致性具有挑战性

使用 Cache-Aside 时,必须考虑其他潜在的数据一致性问题。例如,如果一条数据被写入缓存,随后存储中的值被更新,应用程序只能从缓存读取旧值,直到它被驱逐。解决这个问题的一个方法是为每个缓存记录设置一个可接受的 TTL(Time-To-Live),确保旧数据在一定时间后失效。对于更严格的数据新鲜度要求,应用程序可以将 Cache-Aside 与下面讨论的写策略之一结合起来使用。

Read-Through 是另一种常见的缓存方法,其中缓存作为应用程序和存储系统之间的中介,处理所有读请求。这种策略通过将数据检索责任委托给缓存,简化了应用程序的角色。Read-Through 特别适合读密集型工作负载,常用于缓存库和一些独立缓存提供商。Read-Through 的基本工作流程如下:

  1. 应用程序请求从缓存读取一个键
  2. 如果键在缓存中找到(缓存命中),数据返回给应用程序
  3. 如果键未在缓存中找到(缓存未命中),缓存从存储请求该键
  4. 缓存从存储检索数据,将键和关联数据写入缓存,然后将数据返回给应用程序

Read-Through 读策略

本文为学习目的的个人翻译,译文仅供参考。

原文链接:A Crash Course in Caching - Part 2

版权归原作者或原刊登方所有。本文为非官方译本;如有不妥,请联系删除。