Redis缓存策略

261 阅读6分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 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个数据的数据写入队列。

另外有三种缓存更新策略:

  • 旁路:直接与缓存与数据库交互
  • 读穿、写穿:直接与缓存交互,不与数据库交互,缓存系统代理与数据库交互,同步跟新数据库
  • 写回:直接与缓存交互,不与数据库交互,异步更新数据库