携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第18天,点击查看活动详情
如何设计一个缓存策略,可以动态缓存热点数据呢?
我们一般将一部分热点数据放到缓存中,所以应该设计一个热点数据动态缓存的策略。
热点数据动态缓存的策略总体思路是:通过数据访问时间来做排名,并过滤掉不常访问的数据,只留下经常访问的数据。
就比如电商平台上的例子:
- 先通过缓存系统做一个排序队列,比如存放1000个商品,根据访问时间更新队列,最近访问的在前
- 定期过滤掉最后的200个,再从数据库中随机读200个加入队列
- 当请求到来,先从队列中获取商品ID,如果命中,再从另一个缓存数据结构中读取实际的信息,并返回
在 Redis 中可以用 zadd 方法和 zrange 方法来完成排序队列和获取 200 个商品的操作。
常见的缓存更新策略
常见的缓存更新策略有3种
- 旁路缓存策略(Cache aside)
- 读穿/写穿策略(Read/write through)
- 写回策略(Write back)
实际开发中,Redis 和 MySQL 的更新策略用的是 Cache Aside,另外两种策略应用不了。
?我的理解是旁路写回策略就是,更新操作先作用在redis上,然后给消息队列一个消息,让它异步去更新数据库,为啥说这里不行勒
旁路写回策略
应用程序直接与数据库、缓存交互,并负责对缓存的维护,该策略分为读策略、写策略
写策略:
先更新数据库种的数据,再删除缓存中的数据
读策略:
- 如果读取的数据命中,直接返回
- 没有命中,就从数据库中读取数据,然后写入缓存,再返回用户
这里需要的注意的是,写策略不能先删除缓存再更新数据库。
为什么?
原因是在「读+写」并发的时候,会出现缓存和数据库的数据不一致性的问题。
举个例子,假设某个用户的年龄是 20,请求 A 要更新用户年龄为 21,所以它会删除缓存中的内容。这时,另一个请求 B 要读取这个用户的年龄,它查询缓存发现未命中后,会从数据库中读取到年龄为 20,并且写入到缓存中,然后请求 A 继续更改数据库,将用户的年龄更新为 21。
最终,该用户年龄在缓存中是 20(旧值),在数据库中是 21(新值),缓存和数据库的数据不一致。
为什么“更新数据库再删除缓存”不会有数据不一致的问题?
假如某个用户数据在缓存中不存在,请求 A 读取数据时从数据库中查询到年龄为 20,在未写入缓存中时另一个请求 B 更新数据。它更新数据库中的年龄为 21,并且清空缓存。这时请求 A 把从数据库中读到的年龄为 20 的数据写入到缓存中。
最终,该用户年龄在缓存中是 20(旧值),在数据库中是 21(新值),缓存和数据库数据不一致。 从上面的理论上分析,先更新数据库,再删除缓存也是会出现数据不一致性的问题,但是在实际中,这个问题出现的概率并不高。
因为缓存的写入通常要远远快于数据库的写入,所以在实际中很难出现请求 B 已经更新了数据库并且删除了缓存,请求 A 才更新完缓存的情况。而一旦请求 A 早于请求 B 删除缓存之前更新了缓存,那么接下来的请求就会因为缓存不命中而从数据库中重新读取数据,所以不会出现这种不一致的情况。
旁路策略适合读多写少的场景。因为写入频繁,缓存会被频繁的清理,对命中率有影响。
如果对缓存命中率有要求,可以考虑以下两种解决方案:
- 更新数据时更新缓存,在更新缓存前加一个分布式锁,保证同一时间只有一个线程更新缓存,就不会有并发问题,但是对写入性能有影响
- 更新数据时更新缓存,只是给缓存加一个较短的过期时间,这样即使出现缓存不一致的情况,缓存的数据也会很快过期,对业务的影响也是可以接受。
读穿/写穿策略
应用程序只和缓存交互,不再和数据库交互,缓存来代理与数据库的交互。
读穿策略
先查询缓存是否存在,存在就返回。不存在由缓存组件复制从数据库查数据,写入到缓存,然后返回。
写穿策略
- 如果数据在缓存中存在,则先更新缓存,然后同步更新到数据库中,然后告知应用
- 如果不存在,直接更新数据库,然后返回
在开发过程中相比 Cache Aside 策略要少见一些,原因是我们经常使用的分布式缓存组件,无论是 Memcached 还是 Redis 都不提供写入数据库和自动加载数据库中的数据的功能。而我们在使用本地缓存的时候可以考虑使用这种策略。(需要再思考一下)
写回策略
在更新数据时,只更新缓存,同时将缓存数据设置为脏,然后返回,不更新数据库。对于数据库的更新,通过批量异步的方式跟新。
实际上,Write Back(写回)策略也不能应用到我们常用的数据库和缓存的场景中,因为 Redis 并没有异步更新数据库的功能。【有问题】
Write Back 是计算机体系结构中的设计,比如 CPU 的缓存、操作系统中文件系统的缓存都采用了 Write Back(写回)策略。
写回策略特别适合写多的场景,因为只需要更新缓存就能立马返回。
但是数据并不是强一致性,会有数据丢失的风险。因为缓存一般使用内存,内存断电就会丢失脏数据。
总结
设计热点数据的动态缓存策略:可以采用排序队列的方式,假如有1000个数据,每次删除最后200个不经常访问的数据,然后随机读取200个数据的数据写入队列。
另外有三种缓存更新策略:
- 旁路:直接与缓存与数据库交互
- 读穿、写穿:直接与缓存交互,不与数据库交互,缓存系统代理与数据库交互,同步跟新数据库
- 写回:直接与缓存交互,不与数据库交互,异步更新数据库