缓存架构整理

缓存介绍

概念

用于存储数据的硬件和软件组成部分,以使得后续更快访问相应的数据,缓存中的数据可能是提前计算好的结果、数据的副本
作用

缓存是分布式系统中重要的组件:
1.主要解决高并发、大数据场景下,热点数据访问的问题,提供高性能的数据快速访问。
2.减少了冗余的数据传输和服务器的负担。
3.提高客户端的访问速度。

原理

将数据写入/读取速度更快的存储(设备)
将数据缓存离用户更近的位置

缓存分类

CDN缓存
CDN(Content Delivery Network 内容分发网络)的基本原理是广泛采用各种缓存服务器,将这些缓存服务器分布到用户访问相对集中的地区或网络中。
在用户访问网站时,利用全局负载技术将用户的访问指向距离最近的工作正常的缓存服务器上,由缓存服务器直接响应用户请求。

浏览器缓存

反向代理缓存
反向代理位于应用服务器机房,处理所有对WEB服务器的请求。
如果用户请求的页面在代理服务器上有缓冲的话,代理服务器直接将缓冲内容发送给用户。如果没有缓冲则先向WEB服务器发出请求,取回数据,本地缓存后再发送给用户。通过降低向WEB服务器的请求数,从而降低了WEB服务器的负载。

本地应用缓存
指的是在应用中的缓存组件(数据层),需要实现缓存机制,其最大的优点是应用和cache是在同一个进程内部,请求缓存非常快速,没有过多的网络开销等,在单应用不需要集群支持或者集群情况下各节点无需互相通知的场景下使用本地缓存较合适;
同时,它的缺点也是应为缓存跟应用程序耦合,多个应用程序无法直接的共享缓存,各应用或集群的各节点都需要维护自己的单独缓存,对内存是一种浪费。

  • 稳定性:应用会频繁重启更新,缓存易丢失,稳定性不佳
  • 扩展性:差,受限于进程的资源限制
  • 通用性:差,不同应用难以复用
  • 对代码的侵入性:代码侵入性小,无网络操作,只需要操作应用进程内存

本地单点缓存
缓存的数据只由单机处理,比如单个独立的缓存应用(redis、memcached等)

  • 稳定性不会频繁重启,稳定性一般,但有单点故障问题
  • 扩展性一般,受限于单服务器资源限制
  • 通用性一般,业务应用和缓存应用有强耦合
  • 对代码的侵入性:代码侵入性一般,需要引入对应的api通常有网络操作

分布式缓存
缓存的数据不能只由单机处理,分布式缓存一般都具有良好的水平扩展能力,对较大数据量的场景也能应付自如。缺点就是需要进行远程请求,性能不如本地缓存。

  • 稳定性:分布式系统,具备故障恢复功能,无单点故障问题,稳健性佳
  • 扩展性:好,支持水平扩展
  • 通用性:好,对业务层提供通用接口,后端具体的缓存应用对业务透明
  • 对代码的侵入性:代码侵入性一般,需要引入通用的api通常有网络操作

数据库缓存
比如mysql使用查询缓冲机制。将select语句和结果存在缓冲区。下次遇到相同select就直接从缓冲区拿数据
注:如果是写入操作很频繁的数据库。缓存会成为累赘。因为数据库更新缓存会占用大量性能.

多级缓存
多级缓存,也称多层缓存,为了平衡本地应用缓存与分布式缓存,实际业务中一般采用多级缓存,本地应用缓存只保存访问频率最高的部分热点数据,其他的热点数据放在分布式缓存中。
nginx缓存-->redis缓存-->tomcat 本地缓存 -->数据库缓存-->数据库.

缓存介质
硬盘缓存:将数据缓存到硬盘,读取时从硬盘读取,原理是直接读取本机文件,键时网络传输消耗,比网络读取数据库速度更快。适用对速度要求不高,但需要大量缓存存储的场景。
内存缓存:直接将数据存储到本机内存,通过程序直接维护缓存对象,是访问速度最快的方式。

缓存(淘汰)回收策略

  • 定时回收
  • 随机回收
  • LRU(最近最少使用)到期失效策略
  • 显式清除

以下原文链接

作者:肖张 

缓存更新机制

更新缓存的设计模式有三种:Cache aside,Read/Write Through, Write behind caching。

Cache aside更新模式
Cache aside策略

  • 读操作:先从cache取数据,取到就返回,没有得到,则从数据库中取出,然后放回cache。
  • 写操作:先把数据更新到数据库中,然后让cache失效。
  • 两个步骤:更新数据库,失效cache

Cache Aside 更新模式流程图

注意我们上面所提到的,缓存更新时先更新数据库,然后在让缓存失效。那么为什么不是直接更新缓存呢?这里有一些缓存更新的坑,我们需要避免入坑。

避坑指南一

先更新数据库,再更新缓存。这种做法最大的问题就是两个并发的写操作导致脏数据。如下图(以Redis和Mysql为例),两个并发更新操作,数据库先更新的反而后更新缓存,数据库后更新的反而先更新缓存。这样就会造成数据库和缓存中的数据不一致,应用程序中读取的都是脏数据。

mark

两个并发的写操作导致脏数据

避坑指南二

先删除缓存,再更新数据库。这个逻辑是错误的,因为两个并发的读和写操作导致脏数据。如下图(以Redis和Mysql为例)。假设更新操作先删除了缓存,此时正好有一个并发的读操作,没有命中缓存后从数据库中取出老数据并且更新回缓存,这个时候更新操作也完成了数据库更新。此时,数据库和缓存中的数据不一致,应用程序中读取的都是原来的数据(脏数据)。

mark

两个并发的读和写操作导致脏数据

避坑指南三

先更新数据库,再删除缓存。这种做法其实不能算是坑,在实际的系统中也推荐使用这种方式。但是这种方式理论上还是可能存在问题。如下图(以Redis和Mysql为例),查询操作没有命中缓存,然后查询出数据库的老数据。此时有一个并发的更新操作,更新操作在读操作之后更新了数据库中的数据并且删除了缓存中的数据。然而读操作将从数据库中读取出的老数据更新回了缓存。这样就会造成数据库和缓存中的数据不一致,应用程序中读取的都是原来的数据(脏数据)。

mark

但是,仔细想一想,这种并发的概率极低。因为这个条件需要发生在读缓存时缓存失效,而且有一个并发的写操作。实际上数据库的写操作会比读操作慢得多,而且还要加锁,而读操作必需在写操作前进入数据库操作,又要晚于写操作更新缓存,所有这些条件都具备的概率并不大。但是为了避免这种极端情况造成脏数据所产生的影响,我们还是要为缓存设置过期时间。

Read/Write Through 更新模式

Cache Aside策略中,我们的应用代码需要维护两个数据存储,一个是缓存(Cache),一个是数据库(Repository),而Read/Write Through策略是把读数据库(Repository)的操作由缓存自己代理了,所以,对于应用层来说,就简单很多了。可以理解为,应用认为后端就是一个单一的存储,而存储自己维护自己的Cache。
mark

  • Read Through:Read Through 模式就是在查询操作中更新缓存,也就是说,当缓存失效的时候,Cache Aside 模式是由调用方负责把数据加载入缓存,而 Read Through 则用缓存服务自己来加载。
  • Write Through:Write Through 模式和 Read Through 相仿,不过是在更新数据时发生。当有数据更新的时候,如果没有命中缓存,直接更新数据库,然后返回。如果命中了缓存,则更新缓存,然后由缓存自己更新数据库(这是一个同步操作)。

Write behind caching更新模式

Write Behind Caching 更新模式就是在更新数据的时候,只更新缓存,不更新数据库,而我们的缓存会异步地批量更新数据库。这个设计的好处就是直接操作内存速度快。因为异步,Write Behind Caching 更新模式还可以合并对同一个数据的多次操作到数据库,所以性能的提高是相当可观的。

但其带来的问题是,数据不是强一致性的,而且可能会丢失。另外,Write Behind Caching 更新模式实现逻辑比较复杂,因为它需要确认有哪些数据是被更新了的,哪些数据需要刷到持久层上。只有在缓存需要失效的时候,才会把它真正持久起来。

mark

常用方案

  • 写流程:
  • 第一步先删除缓存,删除之后再更新DB,之后再异步将数据刷回缓存
  • 读流程:
  • 第一步先读缓存,如果缓存没读到,则去读DB,之后再异步将数据刷回缓存

其它方案
https://www.cnblogs.com/-wenli/p/11474164.html

原文地址:https://www.cnblogs.com/-wenli/p/12605995.html