1.为什么用缓存?
由于CPU处理速度快,磁盘读取速度慢导致的CPU利用率不高的问题,我们用缓存来解决。添加缓存后,不仅可以提高读写效率,还可以降低后端的负载(不用再读取数据库)。但是需要注意数据一致性的成本问题,考虑缓存带来的效益是否大于成本。
2.缓存更新策略
①内存淘汰
我写的第一篇Redis文章里展开讲了内存淘汰的几种方式。内存淘汰策略通常用于读多写少的场景,对缓存的写入性能要求不高,级一致性需求较低。
②超时剔除
为缓存数据设置一个过期时间(Time To Live, TTL),当数据达到这个时间限制后,缓存会自动删除,可以保证数据在一定时间后得到更新,适用于对数据实时性要求不是极高的场景。值得注意的是,可能会在数据过期的瞬间造成缓存击穿(大量请求同时打到数据库使其崩溃)。
③主动更新
当数据库中的数据发生变化时,主动更新缓存中的数据,可以保证缓存中的数据与数据库中的数据保持高度一致,适用于对数据一致性要求较高的场景。
3.主动更新缓存的方法
①人工编码
开发者手动编写代码来在更新数据库的同时更新缓存。这种方式虽然直接,但容易出错,且难以维护,尤其是当系统复杂度增加时。
②缓存与数据库整合成一个服务
缓存和数据库被封装在一个服务中来维护缓存和数据库之间的一致性,只需要与这个服务交互,不需要关心缓存和数据库的具体一致性问题。虽然简化了调用者的逻辑,但服务的维护和开发成本较高。
③只在Redis增删改查,由其他线程异步持久化到数据库
这种方法中,业务操作只与缓存交互,而缓存的更新则由一个单独的线程异步地持久化到数据库。优点是业务操作不需要等待数据库操作的完成,可以提高业务操作的性能。缺点是难以保证缓存和数据库之间的一致性,特别是在缓存宕机或者网络问题导致数据未能成功持久化到数据库时。
4.人工编码方法需要考虑的问题
①更新缓存还是删除缓存?
当遇到写多读少的情况,更新缓存是无效的。所以可以更新数据库时让缓存失效,查询时再更新。
②如何保存缓存与数据库更新操作的原子性?
单体系统-将缓存与数据库操作放在一个事务中(事务的原子性)。
分布式系统-TCC分布式操作(埋个伏笔)。
③缓存与数据库哪个先?
·先删缓存后更新数据库
第一种是正常情况,线程1先删除缓存后更新数据库,线程2查缓存时,此时能够成功请求到更新成功后的数据。
第二种是异常情况,线程1在删除缓存后更新数据库期间(时间相对长),线程2查缓存失败则去读取数据库(这个过程时间短),由于数据库还没有更新成功,最后线程2请求的数据还是老数据,且将老数据更新到了缓存。
·先更新数据库后删缓存
第一种是正常情况,线程2先更新数据库后删除缓存,然后线程1查缓存失败,则取读取数据库获得更新后的数据。
第二种是异常情况,恰好缓存失效时,线程1查缓存失败则查库,接着要将查的旧数据更新进缓存期间,线程2更新数据库并删缓存,导致结果是数据库更新了但是缓存还是旧数据。
由于查缓存失败读库到更新缓存时间短,难以在此期间实现更新数据库且删缓存操作,因此这种情况出现的可能性很小。
而前面说的异常情况比较常见,所以选择先更新数据库后删缓存更合理。