1 前言:为什么要用缓存?随着用户的增加,架构的演进,数据量的增加,我们开始考虑如何优化性能。性能优化第一定律是:优先使用缓存。2 缓
1.前言:为什么要用缓存?
随着用户的增加,架构的演进,数据量的增加,我们开始考虑如何优化性能。
性能优化第一定律是:优先使用缓存。
2.缓存的基本原理
2.1缓存的作用
1、加快数据访问速度;
2、减轻后端应用和数据存储的负载压力。
2.2缓存的特征
1、命中率:命中率=命中次数/请求次数。
这是衡量缓存有效性的一个重要指标。命中率越高,缓存的利用率越高。
2、最大元素(最大空间)。
一旦缓存中的元素数量超过这个值(或者缓存数据空间超过其最大支持空间),就会触发消除策略。
3、消除策略。
实际上我以前说过。
FIFO(先进先出)删除最早的数据。
判断存储时间,距离现在最远的数据会先被淘汰。
LRU(最近最少使用)排除最近最少使用的。
判断最近的使用时间,离现在最远的数据会先被淘汰。
LFU(最少使用)清除最近最少使用的数据。
在一段时间内,使用次数最少的数据会先被淘汰。
具体来说,我们可以看到LRU和LFU之间的区别,这是本文中常见的缓存剔除策略。
3.缓存的分类
缓存的主要手段有:浏览器缓存、CDN、反向代理、本地缓存、分布式缓存和数据库缓存。
在《大型网站技术架构》的解读中,其实已经说了。
一般来说,我们做性能优化时,指的是后三种:本地缓存、分布式缓存和数据库缓存。
前三种缓存策略属于网站前端。
从硬件介质的角度来看,缓存可以分为内存和硬盘。
但技术上可以分为内存、硬盘文件和数据库。
我们通常所说的缓存一般是基于内存的。
因为只有记忆够快。
数据库缓存通常基于内存,但是这种活动通常由DBA在配置数据库时设置。
对于大多数开发者来说,我们一般所说的缓存优化是基于本地缓存和远程缓存的。
现在,远程缓存这个词一般被分布式缓存代替,这是一种常见的方案。
4.何时使用缓存?
4.1缓存的使用判断
什么时候使用缓存的判断其实比较简单,把握两点就好:
1、是热门数据吗?
所谓热点一般遵循二八定律,即80%的访问量集中在20%的数据上。
字体你读的比写的多吗?
这个比例一般是2:1。
4.2什么时候我不应该使用缓存?
正好相反。
1、没有热数据就不使用缓存是没有意义的。
因为内存资源更珍贵。
2、不要对频繁修改的数据使用缓存。
因为写完之后还没读就可能失效或被淘汰,而且容易产生脏读。
4.3缓存的合理使用
最后,最重要的是确认是否需要缓存。
确定后,选择合适的缓存工具和使用缓存的方式。
5.缓存中的一些常见问题
使用缓存有很多优点,但也有一些常见的问题。双刃剑就看怎么用了。
列举一些我们工作中常见的缓存问题,至少给出一个解决方案。
5.1缓存更新导致的数据不一致和脏读
缓存更新的常见策略有:
1、在更新缓存之前更新数据库;
2、在删除缓存之前更新数据库;
3、首先删除缓存,然后更新数据库;
4、定期清理缓存;
5、当有访问数据的请求时,判断缓存是否过期,如果过期则从数据库刷新缓存。
在这些方案中,如果修改后的缓存和数据库不是一个东西,就会带来数据不一致和脏读的问题。
解决方案1:首先删除缓存,然后更新数据库,同样的事情。
方案二:缓存自动失效后,另一个异步线程更新缓存。
解决方案3:缓存更新应该考虑并发和分布式模式下的锁。redis天生单线程,有优势。
5.2如何进行缓存预热
缓存预热是指在用户可以访问服务之前将热数据加载到缓存中的操作,可以有效避免上线后瞬间大流量导致的系统不可用。
高速缓存预热的一般策略:
1、开发一个缓存刷新功能,手动刷新;
2、项目启动时自动加载(一般是字典表等数据量较小的数据);
3、设置定时器自动刷新缓存;
4、提前统计热点数据,提前批量加载到redis等缓存工具中。
5.3缓存重建
如果缓存失效后需要很长时间来重建热点缓存,重建过程中性能和负载都不好。
相应的方案:
1、正常情况下,缓存失效时间错开,以减轻缓存压力;
2、如果崩溃失败,可以使用具有持久化功能的缓存进行恢复,比如Redis
3、如果是MongoDB就不一样了。它使用mmap将数据文件映射到内存中,所以当MongoDB重启时,这些映射的内存不会被清除,也不需要进行缓存重建和预热。
5.4缓存雪崩和可用性
缓存雪崩:当缓存同时发生故障时,对数据库层的访问可能会导致数据库挂起和系统崩溃。
方案一:交错缓存失效时间或随机缓存失效时间。
方案二:Redis哨兵。
方案三:集群/水平分割(Redis集群,一致哈希)。
5.5缓存渗透率
缓存渗透:对不存在的键的持续高并发访问。
方案1:空缓存。
方案二:布隆滤镜位图。使用散列访问,用尽可能被访问的数据并将其放入位图。
5.6缓存故障
缓存崩溃:热键失败,高并发请求,直接命中数据库。
缓存击穿和缓存突破非常相似,但不同的是,真正的热数据是在缓存击穿之前被访问的,只是在某个时刻失效,造成了击穿效应。
这样看来,其实是缓存雪崩的一个特例。与雪崩不同的是,击穿是针对特定的热数据,而雪崩是所有数据。
对应方案:多级缓存和错时失效LRU消除算法。
对热点数据进行两级或多级缓存,对不同级别的缓存设置不同的失效时间,以缓解雪崩。
此外,LRU-K,LRU的一个变种算法,可以用来缓存数据。
5.7缓存降级
缓存降级是服务降级的一部分。
为了保证核心服务的可用性,防止缓存雪崩,可以在访问次数剧增导致服务出现问题时进行服务降级。
以redis为例,不查询数据库,直接向用户返回默认值的情况比较常见。
也可以根据日志级别设置缓存降级。
6.分布式缓存的选择
说了这么多缓存的原理和策略,下面来说说实际工作中如何选择缓存。
以下是几种常用的缓存工具。
6.1 Ehcache
Ehcache是一个纯Java的开源缓存框架,最早是从hibernate发展而来的。现在是springboot的官方缓存工具,集成很简单。特点如下:
快,针对大规模高并发系统的场景,对Ehcache的多线程机制进行了相应的优化和改进;
简单,小jar包简单配置就可以直接使用,单机场景下不需要太多其他服务依赖;
支持多种缓存策略,灵活多变;
数据缓存有两个级别:内存和磁盘。与一般的本地内存缓存相比,凭借磁盘的存储空间,它将能够支持更大数量的数据缓存需求;
有了缓存和缓存管理器的监听接口,监控和管理缓存实例更加容易和方便;
支持多个高速缓存管理器实例和一个实例的多个高速缓存区域。
6.2番石榴缓存
Guava Cache是Google开源Java重用工具集库中的一个缓存工具。其特点如下:
自动将入口节点加载到缓存结构中;
当缓存数据超过设定的最大值时,使用LRU算法将其移除;
具有到期机制,以根据最后一次访问或写入来计算入口节点;
缓存的键封装在WeakReference引用中;
缓存的值封装在WeakReference或SoftReference引用中;
统计缓存命中率、异常率、未命中率等统计。
6.3内存缓存
Memcache本身并不支持分布式,而是通过客户端的路由处理来达到分布式解决方案的目的。特点如下:
Memcache使用预先分配的内存池来管理内存;
所有数据都存储在物理内存中;
无阻塞IO复用模式,纯KV接入操作;
多线程,效率高,会遇到锁等上下文切换问题;
仅支持简单的KV数据类型;
数据不支持持久性。
6.4 Redis
Redis是主流的高性能内存数据库,多用于存储缓存数据,可以实现轻量级MQ功能。特点如下:
临时应用空间可能导致碎片化;
有VM机制,可以存储更多的数据,超过内存空间后会导致swap,降低效率;
非阻塞IO重用模型支持额外的CPU计算:排序和聚合会影响IO性能;
单线程、无锁、无上下文切换、单实例无法利用多核性能;
支持多种数据类型:字符串/哈希/列表/集合/排序集合;
数据支持持久性:AOF(语句增量)/RDB(叉总数量);
自然支持高可用的分布式方案sentinel
群集(故障转移群集)。
6.5建议
通常我们会使用Ehcache甚至java自带的concurrenthashmap来实现缓存。Redis一般用于发行。
声明本站所有作品图文均由用户自行上传分享,仅供网友学习交流。若您的权利被侵害,请联系我们