简介
缓存的工作机制是先从缓存中读取数据,如果没有,再从慢速设备上读取实际数据并同步到缓存。计算机系统里天然就存在多级缓存系统,这是由于不同的硬件设备的访问速度以及容量大小不一致引起的一个选择。比如,CPU到L1/L2/L3到内存到磁盘的访问方式就是一个典型的多级缓存的例子。当CPU需要数据的时候,它首先到L1里找,如果没有找到,则查找L2/L3,如果还是没有找到,则再到内存里找,如果还没有,再到磁盘里查找。不同层级的缓存的访问速度和容量大小各不相同,简要对比如下所示:
名称 | 访问速度 | 通常容量大小 |
L1 | 1.3纳秒 | 12组每组32KB数据加32KB代码 |
L2 | 3.92纳秒 | 32组每组256KB |
L3 | 11.11纳秒 | 30MB |
DDR4 内存 | 100纳秒 | 8GB,16GB,32GB,64GB,128GB |
NVMe SSD磁盘 | 120000纳秒 | 1TB, 2TB |
SATA SSD磁盘 | 400000纳秒 | 1TB,2TB |
机械磁盘 | 6毫秒 | 1TB,2TB |
缓存命中率
缓存命中率是从缓存中读取数据的次数与总读取次数的比率,命中率越高越好。这是一个非常重要的监控指标,它能反映缓存系统是否工作良好。
缓存回收策略
- 基于存储空间
基于存储空间的回收策略是指缓存设置了最大的存储空间,比如设500MB,当达到存储空间上限时,按照一定的回收算法移除旧数据。 - 基于容量大小
基于容量大小的回收策略是指缓存设置了最大的数据条目数大小,当缓存的条目超过最大值后,按照一定的回收算法移除旧数据。 - 基于时间
有两种典型的衡量时间的方式,如下:
TTL(Time To Live)即存活期,意思是缓存数据从创建开始直到到期的一个时间段(不管在这个时间内有没有被访问,缓存数据都将过期)。
TTI(Time To Idle)即空闲期,意思是缓存数据多久没被访问后移除缓存的时间。
缓存回收算法
使用基于存储空间和基于容量大小的缓存方式需要借助一定的回收算法策略去移除旧数据,常见的缓存回收算法有以下三种:
- FIFO(First In First Out)即先进先出算法,意思是先放入缓存的数据会被先移除。
- LRU(Least Recently Used)即最近最少使用算法,意思是使用时间距离现在最久的那个缓存数据会被移除。
- LFU(Least Frequently Used)即最不常用算法,意思是一定时间段内使用次数最少的那个缓存数据会被移除。
在实践中,使用LRU算法的缓存居多。
缓存的类型
按照缓存的可访问范围划分缓存数据的类型,系统内具有以下两类缓存:
- 全局共享缓存,系统内所有微服务应用均能访问的缓存数据。
- 本地专属缓存,只能被各个微服务应用自己访问的缓存数据。
缓存更新策略
系统采用先更新数据库,成功后,再让缓存失效的更新策略。采用这个策略,而不是采用“先删除缓存,再更新数据库”的策略,是为了避免并发操作下引发的访问脏数据的问题。即,当有两个并发操作,一个是更新操作,另一个是查询操作,当更新操作删除缓存后,查询操作没有命中缓存,它去将旧数据读取出来并放到缓存中去,然后更新操作更新了数据库。于是,在缓存中的数据还是 旧 的数据,导致缓存中的数据是脏的,而且还一直这样脏下去。
你可能已经注意到了,这里只是让缓存失效,并没有更新缓存,为什么不是写完数据库后更新缓存?这个选择主要是怕两个并发的写操作导致脏数据。这个策略有没有并发的问题?答案是有的。只是出现的概率比较低。比如,一个是读操作,但是没有命中缓存,然后就到数据库中取出数据,这时,来了一个写操作,写完数据库后,让缓存失效之后,之前的读操作再将旧的数据写入缓存,于是,造成了脏数据。说这个情况的出现概率比较小,是因为,它需要同时满足以下条件:需要发生在读缓存时缓存失效,并且并发着有一个写操作。而且读操作必须在写操作前进入数据库操作,且又要晚于写操作更新缓存。对一个数据库的操作来说,写操作会比读操作慢得多,而且还要锁表,要满足前述条件概率并不大。
缓存需要注意的场景
- 缓存穿透
缓存穿透是指查询的数据在数据库里是不存,那么在缓存中自然也没有,所以,在缓存中查不到就会回到数据库中做查询,如果此类并发请求很多,那么对数据库压力势必很大,很容易跑满数据库连接,进而使得系统性能急剧下降。 - 缓存击穿
缓存击穿是指对于某些热点的数据设置了过期时间,在缓存到期失效后,大量的并发请求会直接回数据库查询,进而引起很大的数据库访问压力。 - 缓存雪崩
缓存雪崩只指缓存不可用或者大量缓存数据由于超时时间相同在同一时间段失效,使得大量并发请求回数据库查询,数据库压力过大引起系统雪崩。