一、使用缓存的目的
- 加快计算机读取数据的速度,并有效的减少底层关键组件如核心应用、数据库的压力。
- 牺牲其他方面的优势:数据的强一致性(若数据出现了副本,如何保持副本与原有数据的一致性问题)
- 某些场景下对读取缓存的一致性要求并不是很高的情况下可以牺牲一定的一致性来换取高性能。
- 使用缓存原则:不但要看使用缓存能否提高性能,提高多少性能,还要看为了提高性能牺牲的一致性能否让用户接受。
二、缓存场景
1、适合使用缓存的场景
- 读密集型(海量数据)的应用
- 存在热数据(是需要被计算节点频繁访问的在线类数据)的应用
- 对响应的时间要求较高
- 对一致性要求不严格
- 需要实现分布式锁的时候
2、不适合使用缓存的场景
- 读少
- 更新频繁
- 对一致性要求严格
三、cpu缓存架构及性能
- 缓存在cpu内核中被分为L1/L2/L3三级缓存
- cpu在运行中首先使用自己的寄存器,然后使用速度更快的L1(L1D缓存数据、L1I缓存指令)缓存
- L1缓存和L2缓存同步数据,L2缓存和L3缓存同步数据,L3缓存和内存同步数据
- cpu寄存器的整体大小为2kb左右,因为内存和cpu缓存在性能上存在差异,所以对cpu的密集运算时越少访问内存越好。
四、cpu缓存运行过程
- 若核心访问(读或写)L1缓存时没有命中,则访问L2缓存和L3缓存,在L3缓存也没有命中时才访问内存。
- 缓存的最小单位是缓存单元(缓存行),每个缓存单元大小64bit(8Byte)
- 影响性能最大问题就是缓存未命中
- 缓存命中的情况:如果读操作命中缓存,则直接使用数据;如果写操作,则直接操写数据到缓存,而不是主动同步数据到内存(而是通过硬件级别的异步操作写回内存)。
- 整体缓存使用流程:1、cpu执行单元解析L1指令缓存中的指令来预测后续的执行指令;2、通过这些指令计算单元具体处理L1数据缓存和寄存器中的数据缓存。3、将计算结果更新写操作缓冲区再到L1数据缓存。
- 伪共享:多个线程同时读写同一个缓存行的不同变量时,尽管这些变量直接没有任何联系,但是多个线程直接仍然需要同步,从而导致性能下降。在多处理器系统中,伪共享是影响性能的主要因素之一。
- 解决伪共享方案:把热点变量隔离在不同的缓存行中,通过减少伪同步,在多核心cpu中能够极大地提高效率。
五、缓存穿透、缓存并发、缓存雪崩
缓存穿透、并发、雪崩时常见的由于并发量大而导致的缓存问题
- 缓存穿透
- 缓存穿透指的是使用不存在的key进行大量高并发查询,这导致缓存无法命中,每次请求都要穿透到后端数据库系统进行查询,使数据库压力过大,甚至使速记服务被压死。
- 解决方法:将空值缓存起来,再次接收到同样的查询请求时,若命中缓存并且为空,就会直接返回,不会穿透到数据库,避免缓存穿透。
- 缓存并发
- 缓存并发:高并发的情况下,当一个缓存key过期时,因为访问这个缓存的key请求量过大,多个请求同时发现缓存过期,因此多个请求会同时访问数据库来查询最新的数据,并且回写缓存,这样会造成应用和数据库的负载增加,性能降低,由于并发较高,甚至会导致数据库被压死。
- 解决方法:1、分布式锁:保证对于每个key同时只有一个线程去查询后端服务,其他线程没有获得分布式锁的权限时只需等待即可。2、本地锁 3、软过期:不使用缓存服务提供的过期时间,而是由业务层在数据中存储过期时间信息。
- 缓存雪崩
- 缓存雪崩:缓存服务器重启或大量缓存集中在某一个时间段内失效,给后端数据库造成瞬时的负载升高的压力,甚至压垮数据库。
- 解决方法:不同的数据使用不同的失效时间,甚至对相同的数据不同的请求使用不同的失效时间。