缓存概念入门

本文为读书笔记,<<深入分布式缓存:从原理到实践>>
缓存是一种通用设计模式

1. 什么是缓存?

缓存:存储在计算机上的一个原始数据复制集,以便于访问。

原本出现在计算机主存到CPU之间的那层存储设备,现在,已经远远突破了原有的概念

缓存是一种通用的设计模式

这种模式利用增加存储空间的方式,实现低速部件与高速部件之间的解耦。换而言之,只要实现了解耦的地方,就有存在缓存的可能,既然解耦是计算机系统架构设计中最常用的手段,那么缓存就必将在计算机系统中无处不在。

缓存用空间换时间的方式解决问题,而空间不可能无限使用,使用缓存时我们通常会考虑如何选择存储方式、如何使用多级缓存节省空间、如何有效提高缓存的命中率、如何确定有效的更新策略等问题,这些思考会有相当普遍的适用性。

2.缓存淘汰算法(替代)

在这里插入图片描述

当缓存没有命中时,并且缓存容量已经满了,就需要在缓存中去除一条旧数据,然后加入一条新数据,而到底应该去除哪些数据,就是由替代策略决定的。

(1)Least-Recently-Used(LRU)

替换掉最近被请求最少的对象,这种传统策略在实际中应用最广。在CPU缓存淘汰和虚拟内存系统中效果很好。然而在直接应用与代理缓存中效果欠佳,因为Web访问的时间局部性常常变化很大。

浏览器就一般使用了LRU作为缓存算法。新的对象会被放在缓存的顶部,当缓存达到了容量极限,底部的对象被去除,方法就是把最新被访问的缓存对象放到缓存池的顶部。

(2)Least-Frequently-Used(LFU)

替换掉访问次数最少的缓存,这一策略意图是保留最常用的、最流行的对象,替换掉很少使用的那些数据。然而,有的文档可能有很高的使用频率,但之后再也不会用到。传统的LFU策略没有提供任何移除这类文件的机制,因此会导致“缓存污染”,即一个先前流行的缓存对象会在缓存中驻留很长时间,这样,就阻碍了新进来可能会流行的对象对它的替代。

(3)First in First out(FIFO)

FIFO通过一个队列去跟踪所有的缓存对象,最近最常用的缓存对象放在后面,而更早的缓存对象放在前面,当缓存容量满时,排在前面的缓存对象会被踢走,然后把新的缓存对象加进去。

(4)Random Cache

随机缓存就是随意的替换缓存数据,比FIFO机制好,在某些情况下,甚至比LRU好,但是通常LRU都会比随机缓存更好些。

(5) Least Recently Used 2

LRU的变种,把被两次访问过的对象放入缓存池,当缓存池满了之后,会把有两次最少使用的缓存对象去除。因为需要跟踪对象2次,访问负载就会随着缓存池的增加而增加。
基于缓存池的实现又可实现很多变种,比如二级缓存,缓存队列

(5)LRU-k

LRU-K中的K代表最近使用的次数,因此LRU可以认为是LRU-1。
LRU-K的主要目的是为了解决LRU算法“缓存污染”的问题,其核心思想是将“最近使用过1次”的判断标准扩展为“最近使用过K次”。
相比LRU, LRU-K需要多维护一个队列,用于记录所有缓存数据被访问的历史。只有当数据的访问次数达到K次的时候,才将数据放入缓存。当需要淘汰数据时,LRU-K会淘汰第K次访问时间距当前时间最大的数据。
在这里插入图片描述

3. 缓存替换实现原理(LRU)

基于弱引用:只要被垃圾回收线程扫描到就会回收弱引用对象

我们通过维护entry的列表,在get、put时维护entry列表实现,使最少访问的键值对维持在entry列表的最尾部。 在数据量超过缓存容量需要做LRU淘汰时,我们通过删除链表尾部的数据,来实现简单的LRU数据淘汰机制

在这里插入图片描述

1)新数据插入到链表头部;2)每当缓存命中(即缓存数据被访问),则将数据移到链表头部;3)当链表满的时候,将链表尾部的数据丢弃。

缺点:
当存在热点数据时,LRU的效率很好,但偶发性的、周期性的批量操作会导致LRU命中率急剧下降,缓存污染情况比较严重。

4. 缓存框架的SPI机制

(1)什么是SPI

SPI的全名为Service Provider Interface,是JDK内置的一种服务提供发现机制,在Java.util.ServiceLoader的文档里有比较详细的介绍。

Java SPI机制的思想简单来说是:
在面向的对象的设计里,我们一般推荐模块之间基于接口编程,模块之间不对实现类进行硬编码。一旦代码里涉及具体的实现类,就违反了可拔插的原则,如果需要替换一种实现,就需要修改代码。

为了实现在模块装配的时候能不在程序里动态指明,这就需要一种服务发现机制

Java SPI就是提供了这样的一个机制,为某个接口寻找服务实现的机制。有点类似IoC的思想,就是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要。

(2)SPI的使用约定

当服务的提供者,提供了服务接口的一种实现之后,在jar包的META-INF/services/目录里同时创建一个以服务接口命名的文件。 该文件里就是实现该服务接口的具体实现类。**而当外部程序装配这个模块的时候,就能通过该jar包META-INF/services/里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。**基于这样一个约定就能很好地找到服务接口的实现类,而不需要在代码里指定。而在JDK里面提供服务查找工具类:java. util.ServiceLoader,如图:
在这里插入图片描述
具体实现例子:SpringBoot的Starter

5. 一致性Hash

可参考

6. 主流缓存中间件

在这里插入图片描述

1.EhCache (Java代码集成)
1)快速,简单。
2)多种缓存策略。
3)缓存数据有两级。
4)缓存数据会在虚拟机重启的过程中写入磁盘。
5)可以通过RMI、可插入API等方式进行分布式缓存。
6)具有缓存和缓存管理器的侦听接口。
7)提供Hibernate的缓存实现。

2.MemCache(服务器)

Memcached最主要的特征为:协议简单、基于libevent的事件处理、内置内存存储方式、客户端分布式。

3. Redis(服务器)

6.1 Redis简单介绍

Redis(REmote DIctionary Server)是一个key-value存储系统,由Salvatore Sanfilippo开发,使用ANSI C语言编写,遵守BSD协议。
Redis运行于独立的进程,通过网络协议和应用交互,将数据保存在内存中,并提供多种手段持久化内存数据。
Redis具备跨服务器的水平拆分、复制的分布式特性。Redis不同于Memcached将value视作黑盒,Redis的value本身具有结构化的特点,对于value提供了丰富的操作。基于内存存储的特点使得Redis与传统的关系型数据库相比,拥有极高的吞吐量和响应性能。

Redis 通用结构如下:
string、list、set、map、sorted-set
在这里插入图片描述
在这里插入图片描述

  • ype指的是前一节讲到的String、List等结构化类型;
  • encoding指的是这些结构化类型在具体的实现(承载)方式,同一个类型可以有多种实现,例如String可以用int来承载,也可以用封装的char[]来承载,List可以用ziplist或者链表来承载;
  • lru表示本对象的空转时长,用于有限内存下长久不访问的对象的清理;-
  • refcount是应用计数用于对象的垃圾回收;
  • ptr指向的就是以encoding方式实现这个对象的实际承载者的地址,例如string对象对应的sds地址;

7. 缓存穿透

缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。

解决办法:

  1. 可以将这个不存在的key预先设定一个值。比如key和“&&”。在返回这个&&值的时候,我们的应用就可以认为这是不存在的key,应用就可以决定是否需要查数据库。

  2. 采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被 这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。

8. 缓存雪崩

缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。

解决方案:

  1. 将缓存失效时间分散开。
  2. 如果缓存数据库是分布式部署,将热点数据均匀分布在不同搞得缓存数据库中。
  3. 设置热点数据永远不过期。

9. 缓存击穿

缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力
解决方案:

  1. 对缓存查询加锁,如果KEY不存在,就加锁,然后回源,将结果进行缓存,然后解锁;其他进程如果发现有锁就等待,然后等解锁后返回数据或者回源查询,利用锁的方式,会造成部分请求等待。
  2. 双key,主key生成一个附属key来标识数据修改到期时间,然后快到的时候去重新加载数据,如果觉得key多可以把结束时间放到主key中,附属key起到锁的功能。
  3. mutex的解决方案:
    1)热点key过期,则增加key_mutex;
    2)从数据库中load key的数据放入缓存;
    3)添加成功,则删除key_mutex;
    4)返回key的值给上层应用。

10. 数据一致性

最终一致性:(先更新数据库,再删缓存)
一种解决方案是通过更新数据库之后,把需要删除的key给缓存层,然后异步删除缓存数据的模式。
这样下一次get请求时,如果没有数据,则从数据库里查询同时更新到缓存集群。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值