0x01 前言
提到缓存,那么缓存是什么,为什么需要缓存?
如果知道一点点计算机方面的知识就会知道,计算机的构造也是由多级缓存->硬盘一起来构造计算机的数据存储。当然这里不是来拨开计算机的神秘面纱来看透缓存的本质设计的,此篇想来讲讲我们在软件构造的过程中为了让系统能提高数据的读写效率,同时减少网络/IO延迟而进行的缓存设计。
0x02 缓存特征
命中率--缓存中取到数据的请求/总的数据请求次数
缓存空间--既然是缓存肯定缓存的数据存储量不够实际的存储系统(文件系统、数据库等)那么大,如何定义缓存的存储上限。
淘汰策略--既然缓存有命中率、最大数据存储空间,那就不可避免的引入了当存储空间接近最大存储空间的时候如何将一些缓存数据从缓存中清除出去,采用什么样的策略。
淘汰策略
FIFO 先进来的优先淘汰。应用场景:时效性要求比较高的场景,优先保障最新的数据能被缓存命中。
LFU(least frequently used) 最近最少频次使用的数据被优先淘汰。应用场景:在保障高频数据可用的场景可以选用这类策略。
LRU(least recently used) 最近最少使用的数据优先淘汰。应用场景:热点数据使用场景
0x03 缓存分类
存储介质分类
从硬件角度进行区分:内存、硬盘
从技术实现上进行区分:内存、硬盘文件、数据库
缓存实现分类
缓存从大类上可以分为本地缓存和分布式缓存。
本地缓存:本地缓存和应用在同一个进程里面,数据请求没有额外的网络开销,能快速得到响应,对于单应用没有集群支持的系统或者拥有集群的情况下但是集群中各个节点的缓存无需互通的场景下比较合适。缺点:应用进程和本地缓存是强绑定在一起的,对于多应用的情况下,每个应用程序都要维护一套单独的缓存,无法共享各自的缓存对内存资源是一种浪费。
分布式缓存:应用和缓存分离,缓存单独作为一个系统单独部署,多个应用可以共享的访问缓存。
本地缓存实现方式
本地缓存相信大家在进行编程的过程中会经常使用到。非常简单的场景下会直接通过Map结构在局部对象中直接构建缓存存储结构,局部变量实现缓存。
private ConcurrentHashMap<String, String> localCache = new ConcurrentHashMap<String, String>();
这种局部缓存只能在类自身的作用域中能访问到,而且这种简单的缓存数据结构无需关注存取、淘汰缓存等策略。
应用场景:在一次请求的过程中缓存此次请求中的重复数据请求,避免过多的无用请求、序列化反序列话对CPU造成的压力,请求结束后清空缓存。
静态变量实现缓存,基本实现思路和上面的局部缓存一样的原理,主要是将其可见度扩大到了本应用程序,在本引用程序都是可见的。通过静态变量一次获取所有的数据缓存起来避免频繁的I/O读取。
应用场景:一次缓存所有的数据,如应用配置信息、一些数据量不大但是应用需要频繁使用到的一些数据,通过开关推送的方式来refresh内存。
分布式缓存实现方式
这里通过redis作为缓存实现来探讨下分布式缓存。
根据应用对缓存的数据存储量的需求,可以通过redis单机或者集群的方式来实现缓存。下面我们主要是来聊聊通过redis集群的方式来实现分布式缓存:
统一接入层暴露统一的操作缓存的API,客户端通过API像和使用单机缓存一样透明的使用分布式缓存集群。
类似的分布式缓存实现方案还有memcache的集群方案,其实现思想和redis的实现思想大相径庭。memcache主要是通过客户端的的一致性Hash算法来实现集群实例选址操作。
0x04 Redis简介
持久化方案
Redis是一个内存存储系统,通常情况下都是被使用作为缓存系统,但是它本身是支持对内存中的数据进行持久化的操作的,其支持两种持久化的方式:
snapshot--save/bgsave两个命令可以对其进行持久化操作。save是主进程进行的操作会将所有的对外服务操作全部block掉;bgsave采用子线程写时复制技术进行持久化,主进程继续对外提供服务,但是写时复制技术在写入请求达到一个量级的过程中可能会出现内存瞬间翻倍的情况,所以在一般情况下Redis的内存使用量不能操作物理内存的二分之一,不然会存在潜在的数据丢失的风险。同时在进行持久化得过程中也会有大量的I/O操作会对系统造成负载压力大。
aof--在文件末尾追加对内存的修改操作。这也会存在个严重的问题:当count++循环100次的时候是不是在aof文件末尾追加了100条同样的+1的操作,其实可以用一个语句count+=100来进行替换。所以像这种情况下aof文件中的语句会出现逐渐庞大的情况,需要定期去rewrite aof文件。在rewrite aof文件的过程中,对内存的修改语句没法写入到aof文件,所以Redis会将rewrite期间对内存修改的命令写入缓存,最后统一写入aof文件中。
Redis的持久化方案使用了buffer I/O,即会使用物理内存的page cache。计算机在发现page cache不足的时候会对cache和硬盘进行swap的操作,在持久化的同时可能会导致系统的不稳定或者崩溃的现象,所以Redis需要实时针对内存的使用量进行监控告警。大多数的数据库存储系统会使用Direct I/O来绕过page cache并自行维护一个数据的cache。
主备同步方式
Redis主从同步方式中包含全量/增量数据同步。第3步ACK之前都是同步snapshot文件,通过snapshot文件快速构建一个和Master一直的内存数据,后面增量同步的过程中按照追加到aof文件中的内存修改命令进行同步修改Slave中的内存数据。
Key淘汰策略
Redis中会单独针对带有过期时间的Key进行统一存储,针对这些带有过期时间的Key有三种淘汰策略。
被动删除。对Key进行访问时,检查超时时间,如果超时则删除K-V。
主动删除。定期调用清理过期Key的方法。
a) 随机抽取100个Key
b) 删除过期Key
c) 若删除过期key数量>25则重复a)
整个主动删除是有时间限制限制的,否则时间太长会影响缓存对外服务的吞吐量。内存空间达到Maxmemory时进行清理。阻塞服务对Key进行过期删除操作,直到低于Maxmemory值,否则服务一直阻塞。
0x05 后记
spring提供了注解缓存的实现方案,目前还没有实践过,后面对spring进行深入学习其注解缓存实现方案后更新。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。