什么是缓存、为什么要用缓存、缓存分类、缓存&测试、缓存更新、缓存设计考虑点、缓存测试点
一、缓存
缓存是一种将数据存储在高速缓存中的技术,它可以提高应用程序的性能和响应速度。
二、 为什么要用缓存
1. 高性能(主要目的)
查询耗时,但变化少,又有很多读请求情况下,可以将查询结果放到缓存中。减少对数据库的压力,提升响应速度。
2. 高并发
Mysql对高并发支持不好,单机撑到2kQPS容易告警,所以对于1s上万个请求,会让mysql宕机。缓存功能简单,说白了就是 key-value 式操作,单机支撑的并发量一秒可达几万十几万,单机承载并发量是 mysql 单机的几十倍。
三、缓存分类
本地缓存
定义:直接运行在应用程序本地的缓存组件
优点:应用程序和cache是在同一个进程内部,请求缓存非常快速,没有过多的网络开销等,在单应用不需要集群支持或者集群情况下各节点无需互相通知的场景下使用本地缓存较合适。
缺点:缓存跟应用程序耦合,多个应用程序无法直接的共享缓存,各应用或集群的各节点都需要维护自己的单独缓存,对内存是一种浪费。
分布式缓存
定义:分布式缓存是指独立的缓存服务,不和任何一个具体的应用耦合,可以独立运行并搭建缓存集群。类似数据库,所有的应用程序都可以连接同一个缓存服务以获取相同的缓存数据。
优点:自身就是一个独立的应用,与本地应用隔离,多个应用可直接的共享缓存。
缺点:优点也就是缺点,因为自身是一个独立的应用,本地节点都需要与其进行通信,导致依赖网络,同时如果缓存服务崩溃可能会影响所有依赖节点(缓存雪崩)。
四、缓存异常&测试方法
1.缓存雪崩
定义:大量缓存数据在同一时间过期(失效)或者 Redis 故障宕机。调用时这些接口查询缓存时无数据,去查询数据库,这些请求都指向数据库,数据库压力增大,耗时增加。
解决方案:
- 均匀设置过期时间:避免同一时间失效
- 读数据库加互斥锁:如果发现访问的数据不在 Redis 里,就加个互斥锁,保证同一时间内只有一个请求来构建缓存(从数据库读取数据,再将数据更新到 Redis 里),当缓存构建完成后,再释放锁。未能获取互斥锁的请求,要么等待锁释放后重新读取缓存,要么就返回空值或者默认值。
实现互斥锁的时候,最好设置超时时间,不然第一个请求拿到了锁,然后这个请求发生了某种意外而一直阻塞,一直不释放锁,这时其他请求也一直拿不到锁,整个系统就会出现无响应的现象。
后台更新缓存:不设置过期时间
模拟测试方法:对多个使用到缓存的接口进行并发调用,设置这些缓存时间已过期(即删除缓存),调用时这些接口查询缓存时无数据,去查询数据库,这些请求都指向数据库,数据库压力增大,耗时增加。
测试通过标准:每个缓存失效的数据都只执行了一次数据库查询并设置缓存,之后请求都命中了缓存。
2.缓存击穿
定义:热点数据过期,此时大量的请求访问了该热点数据,就无法从缓存中读取,直接访问数据库,数据库很容易就被高并发的请求冲垮。
解决方案:
- 设置热点数据永远不过期。
- 读数据库时加互斥锁,写入缓存,其他等待线程就可以从缓存读取
模拟测试方法:对某个 Key 有大量的并发请求,这时从缓存中删除这个 key,再并发访问。
测试通过标准:并发调用接口,缓存失效的那个数据只有一个请求执行了数据库查询并设置缓存,其他查询则命中缓存。其他请求都命中了缓存。
3.缓存穿透
定义:不断发起查询缓存和数据库中都没有的数据,导致压力全部落在数据库,导致数据库压力过大。
解决方案:当数据库查询为空时,将缓存赋值默认值,后续查询都走缓存,减少数据库压力。
模拟测试方法:查询一个根本不存在的数据,缓存层和存储层都不会命中。
测试通过标准:每个不存在的数据都只执行了一次数据库查询并设置缓存,之后请求都命中了缓存,有效防止了缓存穿透问题。
五、 缓存更新方式
有三种更新方式:
- 惰性加载(Lazy Loading/Expiration):这是最懒的更新方式。通过设置缓存有效期,让缓存失效后通过新的请求自动创建新的缓存。
**优点:**实现简单,数据总是最新的,只有在数据被请求时才会更新,节省资源。
缺点:在缓存失效后的第一个请求可能会遇到延迟,因为需要重新从数据源加载数据。此外,如果缓存中存储的是热点数据,可能会导致缓存击穿问题。 - 缓存失效(Cache Invalidation):在更新db数据后,直接删除缓存,通过新的请求自动创建新的缓存。
优点:简单直接,保证了下一次请求一定会得到最新数据。
缺点:可能导致数据在缓存被删除和新缓存被创建之间的短暂时间内不一致。同时,如果删除操作和数据库更新操作之间有任何延迟,也会导致缓存和数据库之间的数据不一致。 - 重新设置缓存(Write-Through/Update Cache):在更新db数据后,直接重新设置缓存。
优点:缓存始终保持最新状态,避免了因缓存失效而导致的延迟,也减少了数据不一致的风险。
缺点:每次数据库更新时都需要更新缓存,这可能会导致额外的开销,特别是在写操作频繁的场景下。
六、 缓存更新策略
1. 先删除缓存后更新数据库(会造成数据不一致)
在删除缓存未更新数据库前,有读请求更新缓存,从而导致数据库和缓存不一致。
2. Cache Aside(更新数据库后缓存失效)旁路换存储策略
最常用策略,应用程序直接与「数据库、缓存」交互,并负责对缓存的维护
- 失效:应用程序先从cache取数据,没有得到,则从数据库中取数据,成功后,放到缓存中。
- 命中:应用程序从cache中取数据,取到后返回。
- 更新:先把数据存到数据库中,成功后,再让缓存失效。
也会发生数据不一致问题,但出现概率不高,原因是缓存的写入通常远快于比数据库写入。实际很难出现B更新完数据库删了缓存,A才更新完缓存清空。
如果业务对缓存命中率有严格的要求,那么可以考虑两种解决方案:
- 一种做法是在更新数据时也更新缓存,只是在更新缓存前先加一个分布式锁,因为这样在同一时间只允许一个线程更新缓存,就不会产生并发问题了。当然这么做对于写入的性能会有一些影响;
- 另一种做法同样也是在更新数据时更新缓存,只是给缓存加一个较短的过期时间,这样即使出现缓存不一致的情况,缓存的数据也会很快过期,对业务的影响也是可以接受。
3. Read/Write Through(读穿 / 写穿)策略
该模式把更新DB操作由Cache自己代理,对开发人员更简便。应用认为后端就是个单一存储,而存储自己维护自己的Cache。服务器只和 Cache 沟通,Cache 负责去沟通 DB,把数据持久化。
业界典型代表:Redis(可理解为 Redis 里包含了一个 Cache 和一个 DB)
Read Through 策略
先查询缓存中数据是否存在,如果存在则直接返回,如果不存在,则由缓存组件负责从数据库查询数据,并将结果写入到缓存组件,最后缓存组件将数据返回给应用。
Write Through 策略
- 当有数据更新的时候,先查询要写入的数据在缓存中是否已经存在:如果缓存中数据已经存在,则更新缓存中的数据,并且由缓存组件同步更新到数据库中,然后缓存组件告知应用程序更新完成。
- 如果缓存中数据不存在,直接更新数据库,然后返回;
4. Write Behind Caching
Write Back(写回)策略在更新数据的时候,只更新缓存,同时将缓存数据设置为脏的,然后立马返回,并不会更新数据库。对于数据库的更新,会通过批量异步更新的方式进行。
优点
- 让数据的I/O操作飞快(直接操作内存 )
- 因为异步,write back还可合并对同一个数据的多次操作,对性能提高相当可观
缺点 - 数据非强一致性,可能丢失(Linux非正常关机会导致数据丢失)
- 实现逻辑复杂,因为需track哪些数据是被更新的,待刷到DB
- os的write back会在仅当该cache需失效时,才会被真正持久化,如内存不够或进程退出等情况,这又叫lazy write
七、缓存设计考虑点
1.缓存时间设置合理性。
缓存时间设置,需要根据数据更新的频次合理设置;缓存时间太长会导致用户访问到的数据一直是老的,缓存设置时间太短对数据库访问会比较频繁。所以最好调研清楚实际数据更新的频次,再去设置缓存时间
2.存储逻辑合理性。
①服务端或数据库返回数据正确性。返回异常,不应该缓存;返回数据正常,才需要缓存;
②在缓存数据时,需要考虑查询条件的选择,这通常取决于业务需求和数据访问模式。例如,如果一个系统允许通过歌名或歌手来检索歌曲信息,那么可能需要为每种查询类型分别缓存数据。
3. 缓存读取逻辑合理性。以下是比较合理的逻辑:
有缓存,优先读取缓存;
无缓存,请求接口或查数据库获取数据;并存储缓存;
注意:缓存不命中也需要结合具体业务,比如上面的例子中,如果三位查不到缓存,大多数情况就是没有数据,在我们看来是一种正常的情况,会直接返回空结果,而不会去查数据库,避免造成缓存穿透。
4.缓存更新逻辑
缓存失效后是否会更新缓存的内容
注意:关注过期时间和更新时间的临界点,会不会出现异常情况,比如
5.缓存内容
redis缓存具体内容是否正确、格式(list、string)是否合理、实际每次缓存的数据数是否与需求一致
6.缓存数据重复
同样的数据触发保存缓存逻辑之后,应该只有一条在redis缓存中可以查到,重复缓存会浪费资源
八、缓存测试点
基本功能:
- 缓存增加:缓存与数据库的数据一致性检测。
- 缓存读取:缓存和数据库中均存在;缓存无,数据库有数据情况; 缓存和数据库中均不存在
- 缓存过期:验证数据在缓存中的过期机制是否按预期工作,包括时间过期和条件过期。再次请求,缓存是否正确写入。
- 缓存更新:当后端数据更新时,缓存应该相应地更新或失效。需要测试缓存更新机制是否正确执行。
- DB事务性导致回滚,缓存是否回滚,有没有产生脏数据。(可通过延迟写入解决)
- 缓存预热:验证缓存预热策略是否能够有效地将热点数据加载到缓存中,以避免系统启动初期的高延迟。
特殊场景
- 缓存读取时超时:校验缓存查询达到超时时间后,未返回指定的数据,对系统的影响。
(可退化为查库) - 缓存存储时超时:注意处理方式(比如重试)
- 特殊场景:缓存穿透、缓存雪崩、缓存击穿场景的解决方案
- 缓存上限:校验缓存淘汰参数配置与预期一致:增加缓存至达到 maxmemory 限制时(可修改 redis.conf 配置文件中配置的最大可用内存值),再次请求查询,数据返回正确,同时,检查被淘汰的数据确实是依照所配置的淘汰策略被移除的。
监控线上的稳定性 - 监控缓存的命中率:评估缓存的设计是否达到预期;
- 监控中间件:CPU、内存是否异常;
- 监控是否有某个 key 过大;
- 监控是否存在缓存的频繁更新。
参考:https://mp.weixin.qq.com/s/9YXstvCin7pkmWlk4JI-fA
https://blog.csdn.net/qq_40685200/article/details/124861245
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!