西安网站seo服务,移动应用与开发是干什么的,销售渠道有哪几种,企业建站模板下载【摘要】本文介绍了Redis缓存原理、详细解析了缓存模型、缓存一致性和缓存异常场景。
【作者】李杰#xff0c;专注于Java虚拟机技术、云原生技术领域的探索与研究。
尽管#xff08;关系型#xff09;数据库系统 (SQL) 带来了许多出色的属性#xff0c;例如 ACID#x…【摘要】本文介绍了Redis缓存原理、详细解析了缓存模型、缓存一致性和缓存异常场景。
【作者】李杰专注于Java虚拟机技术、云原生技术领域的探索与研究。
尽管关系型数据库系统 (SQL) 带来了许多出色的属性例如 ACID但为了保持这些属性数据库的性能在“ 3 高” 条件环境下下往往显得捉襟见肘、苍白无力 。
为了解决这个问题我们往往需要在应用层即处理业务逻辑的后端代码和存储层即 SQL 数据库之间增加一个缓存层。该缓存层通常使用内存缓存来实现毕竟传统 SQL 数据库的性能瓶颈通常发生在二级存储即硬盘的 I/O 层面。随着主内存 (RAM) 的价格在过去十年中下降故将至少部分数据存储在主内存中以提高性能便是一种性价比较高的解决方案。基于当前的技术发展现状Redis 便成为当下一种较为流行的选择。 当然大多数系统只将所谓的“热数据”存储在缓存层即主内存中。基于帕累托原理也称为 80/20 法则对于大多数事件大约 80% 的影响来自 20% 的原因。为了节省成本我们只需要将这 20% 存储在缓存层中。为了识别“热数据”我们可以指定驱逐策略例如 LFU 或 LRU 来确定哪些数据将过期。 缓存概述
缓存是一种“预热”技术用于将经常访问的数据存储在临时存储器称为缓存中以减少硬盘驱动器的读/写。缓存无处不在基于此技术可以大大地提高 Web 应用程序的性能。
通常在最初的单体架构模型当用户向我们的服务发送一个消息请求时Web 服务器首先会读取或写入数据库再返回响应。在缓存的情况下服务器首先检查缓存副本是否存在如果存在则从缓存返回数据而不是询问数据库。它节省了时间和数据库的计算工作量。
下面简要介绍一下应用程序如何请求 Redis 此处主要基于 Master-Slave-Sentinel 模式的集群App 通过调用 Redis Client 例如Jedis、Lettuce 及 Redisson 等来与 Redis Sentinel 通信当 Redis Master 切换至 Slave 时Application 依旧能够正常工作如下为详细的时序图 缓存模型
在分布式系统中基于 CAP 定理指导根据业务需求和上下文选择这些策略通常可将其划分为常规模式和 Cache-Aside 模式。在开始之前让我们通过刷新缓存的方式来了解常用的缓存模式具体如下所 示 写模型
1、Write Through即“直写”。此模型为同步写入数据库后再缓存。这是安全的因为它首先写入数据库但比后写慢。与写无效相比它为先写后读场景提供了更好的性能。在这种写入策略中数据首先写入缓存然后写入数据库。缓存与数据库串联写入总是通过缓存到主数据库。 直写模式的算法是
1对于不可变操作读取
此策略不处理不可变操作。它应该与通读模式相结合。
2对于可变操作创建、更新、删除
客户端只需要在 Redis 中创建、更新或删除条目。缓存层必须以原子方式将此更改同步到 MySQL。
直写模式的缺点也很明显。首先许多缓存层本身并不支持这一点。其次Redis 是缓存而不是 RDBMS。它的设计并非具有弹性。因此更改在复制到 MySQL 之前可能会丢失。即使 Redis 现在已经支持 RDB 和 AOF 等持久化技术但仍然不推荐这种方式。
就其本身而言直写缓存似乎没有太大作用实际上它们会引入额外的写入延迟因为数据先写入缓存然后再写入主数据库。但是当与通读缓存配对时我们可以获得通读的所有好处并且我们还可以获得数据一致性保证使我们免于使用缓存失效技术。
DynamoDB Accelerator (DAX) 是读取/写入缓存的一个很好的例子。它与 DynamoDB 和应用程序内联。可以通过 DAX 对 DynamoDB 进行读取和写入。
2、Write Behind即“后写或回写”。基于此策略 应用程序将数据写入缓存缓存会立即确认并在延迟一段时间后将数据写回数据库 。这对于写入速度非常快如果将同一键上的多个写入合并为一次对数据库的写入则速度会更快。但是数据库长时间与缓存不一致如果在数据刷新到数据库之前进程崩溃可能会丢失数据。RAID 卡是这种模式的一个很好的例子为了避免数据丢失通常需要 RAID 卡上的电池备份单元将数据保存在缓存中但尚未登陆到磁盘。 Write Behind 模式的算法是
1对于不可变操作读取
此策略不处理不可变操作。它应该与通读模式相结合。
2对于可变操作创建、更新、删除
客户端只需要在 Redis 中创建、更新或删除条目。缓存层将更改保存到消息队列中并向客户端返回成功。更改会异步复制到 MySQL并且可能在 Redis 向客户端发送成功响应后发生。
后写模式与直写不同因为它异步地将更改复制到 MySQL。它提高了吞吐量因为客户端不必等待复制发生。具有高持久性的消息队列可能是一种可能的实现。Redis 流自 Redis 5.0 起受支持可能是一个不错的选择。为了进一步提高性能可以结合更改并批量更新 MySQL以节省查询次数。
Write Behind 模式的缺点是相似的。首先许多缓存层本身并不支持这一点。其次使用的消息队列必须是 FIFO先进先出。否则对 MySQL 的更新可能会乱序因此最终结果可能不正确。
回写缓存提高了写入性能适用于写入繁重的工作负载。与通读结合使用时它适用于混合工作负载其中最近更新和访问的数据始终在缓存中可用。
它对数据库故障具有弹性并且可以容忍一些数据库停机时间。如果支持批处理或合并它可以减少对数据库的总体写入从而减少负载并降低成本如果数据库提供程序按请求数量收费例如动态数据库。请记住DAX 是直写的因此如果应用程序写入繁重则不会看到任何成本降低。
一些开发人员将 Redis 用于缓存和回写以更好地吸收峰值负载期间的峰值。主要缺点是如果缓存失败数据可能会永久丢失。
大多数关系数据库存储引擎即 InnoDB在其内部默认启用回写缓存。查询首先写入内存并最终刷新到磁盘。
3、Write invalidate类似于直写先写入数据库然后使缓存无效。在并发更新的情况下这简化了缓存和数据库之间的一致性处理。我们不需要复杂的同步权衡是命中率较低因为我们总是使缓存无效并且下一次读取将始终未命中。
读模型
Read Through即“ 通读 ”。当读取未命中时需要从数据库中加载并保存到缓存中。这种模式的主要问题是基于某些特定的场景有时需要预热缓存。通读缓存与数据库保持一致。当缓存未命中时它会从数据库中加载丢失的数据填充缓存并将其返回给应用程序。 通读模式的算法是
1、对于不可变操作读取
客户端将始终简单地从缓存中读取。缓存命中或缓存未命中对客户端是透明的。如果是缓存未命中缓存应该具有自动从数据库中获取的能力。
2、对于可变操作创建、更新、删除
此策略不处理可变操作。它应该与直写或后写模式结合使用。
通读模式的一个主要缺点是许多缓存层可能不支持它。例如Redis 将无法自动从 MySQL 获取除非为 Redis 编写插件。
Cache-Aside 和 Read-Through 策略都是延迟加载数据即仅在第一次读取时加载。其适用 用例场景如下所示
虽然 Read- Through 和 Cache-Aside 非常相似但至少有两个关键区别
在缓存侧应用程序负责从数据库中获取数据并填充缓存。在通读中此逻辑通常由库或独立缓存提供程序支持。
与 Cache-Aside 不同Read-Through Cache 中的数据模型不能与数据库的数据模型不同。
当多次请求相同的数据时通读缓存最适合读取繁重的工作负载。例如一个新闻故事。缺点是当第一次请求数据时总是会导致缓存未命中并招致将数据加载到缓存中的额外惩罚。开发人员通过手动发出查询来“加热”或“预热”缓存来处理这个问题。就像 cache-aside 一样缓存和数据库之间的数据也有可能不一致解决方法在于写入策略我们将在下面看到。
不读或不写模型
Refresh ahead预测热点数据并自动刷新数据库中的缓存永不阻塞读取最适合小型只读数据集例如邮政编码列表缓存我们可以定期刷新整个缓存因为它很小并且是只读的。如果能够可以准确地预测最常读取哪些键那么还可以在此模式中预热这些键。最后如果数据在系统之外更新而系统无法收到通知可能必须使用此模式。
在大多数场景下我们通常使用通读和直写/后写/写无效等模型。针对 Refresh-ahead 模型其可以单独使用也可以作为一种优化来预测和预热读取以进行通读。由谁负责缓存维护调用者或专用层有两种实现模式。
1、Cache-Facade缓存层是一个库或服务委托写入数据库我们只与缓存层交谈。然后数据库对我们的应用程序是透明的。缓存层可以处理一致性和故障转移。例如许多数据库都有自己的缓存这是缓存外观的一个很好的例子。我们还可以编写一些进程内 DAO 层来读取/写入具有嵌入式缓存层的实体从调用者的角度来看这个小层也是一个缓存门面。
2、Cache-Aside我们的应用程序保持缓存一致性这意味着应用程序代码更复杂但这提供了更大的灵活性。例如像数据库查询缓存这样的缓存外观模式只能缓存行如果想缓存带有行的 Java POJO 或 Kotlin 数据类则将缓存放在一边要容易得多。但是它仍然可以使用缓存门面例如将 Spring 缓存作为门面库来缓存 POJO并在后台自动处理数据库中的 POJO。
当缓存不支持原生的读通和写通操作并且资源需求不可预测时我们使用这种缓存侧模式。
1读取尝试命中缓存。如果没有命中则从数据库中读取然后更新缓存。
2写入先写入数据库然后删除缓存条目。这里一个常见的陷阱是人们错误地用值更新了缓存高并发环境下的双写会使缓存变脏。
在这种模式下仍然有可能出现脏缓存。在满足这两种情况时会发生上述情况读取数据库并更新缓存、 更新数据库并删除缓存。 缓存一致性 缓存一致性模型参考图
如何保障缓存Redis与 数据存储数据库之间的数据一致性通常 有多种 设计实现策略本文重点针对 Cache Aside Pattern旁路缓存模式 进行简要解析此模型也是在实际的业务场景中使用较为广泛的。具体如下。
在 Cache Aside Pattern 模型中通常写请求场景基本流程主要为先更新 DB然后直接删除 Cache 。
在业务场景实现中如果更新数据库成功而进行缓存删除操作时出现失败的情况下简单地说通常主要有以下两个解决方案
1、缩短 Cache 失效时间我们让缓存数据的过期时间变短这样的话缓存就会从数据库中加载数据。另外这种解决办法对于先操作缓存后操作数据库的场景不适用。此方案在实际的业务场景中通常 不推荐本质上治标不治本。
2、增加 Cache 更新重试机制如果 Cache 服务当前不可用导致缓存删除失败的话我们就隔一段时间进行重试重试次数可以自己定。如果多次重试还是失败的话我们可以把当前更新失败的 Key 存入队列中等缓存服务可用之后再将缓存中对应的 Key 删除即可。可考虑使用消息队列。此方案算是一种 常用的解决策略能够满足绝大多数业务场景需要。
其实从本质上而言缓存方案的规划设计往往依赖于实际的业务场景需求毕竟技术是为业务服务的。可能有时我们引入缓存之后为了解决短期内的不一致性问题选择让系统设计变得更加复杂的话完全没必要。 缓存异常场景 缓存场景模型图
其实。在实际的场景中考虑到各种应用异常和业务故障通常不可能完全使用分布式缓存和数据库系统来实现线性一致性模型。每一种缓存模式都有其自身的局限性在某些情况下我们无法获得顺序一致性或者有时会在缓存和数据库之间获得意外延迟。对于笔者在本文中展示的所有的解决方案依据不同的业务需求总是会遇到高并发的极端情况。因此对此没有灵丹妙药在选择解决方案之前了解限制并定义特定的一致性要求。
如果想要实现线性一致性和容错性建议最好不要使用缓存策略可考虑其他的方案。以上为 Redis 缓存系统相关解析希望对大家有用。
活动时间~