高并发系统:缓存使用-读写策略

Posted 蜗码

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了高并发系统:缓存使用-读写策略相关的知识,希望对你有一定的参考价值。

我们在选择缓存读写策略时需要考虑诸多的因素,比如说,缓存中是否有可能被写入脏数据,策略的读写性能如何,是否存在缓存命中率下降的情况等等。我就以标准的“缓存 + 数据库”的场景为例,剖析经典的缓存读写策略以及它们适用的场景。这样一来,你就可以在日常的工作中根据不同的场景选择不同的读写策略。

一、Cache Aside(旁路缓存)策略

这个策略就是我们使用缓存最常见的策略,Cache Aside 策略(也叫旁路缓存策略),这个策略数据以数据库中的数据为准,缓存中的数据是按需加载的。

它可以分为读策略和写策略
其中读策略的步骤是:

  • 从缓存中读取数据;

  • 如果缓存命中,则直接返回数据;

  • 如果缓存不命中,则从数据库中查询数据;

  • 查询到数据后,将数据写入到缓存中,并且返回给用户。

写策略的步骤是:

  • 更新数据库中的记录;

  • 删除缓存记录。

高并发系统:缓存使用-读写策略

Cache Aside 策略是我们日常开发中最经常使用的缓存策略,不过我们在使用时也要学会依情况而变。比如说当新注册一个用户,按照这个更新策略,你要写数据库,然后清理缓存(当然缓存中没有数据给你清理)。可当我注册用户后立即读取用户信息,并且数据库主从分离时,会出现因为主从延迟所以读不到用户信息的情况。
而解决这个问题的办法恰恰是在插入新数据到数据库之后写入缓存,这样后续的读请求就会从缓存中读到数据了。并且因为是新注册的用户,所以不会出现并发更新用户信息的情况。

Cache Aside 存在的最大的问题是当写入比较频繁时,缓存中的数据会被频繁地清理,这样会对缓存的命中率有一些影响。如果你的业务对缓存命中率有严格的要求,那么可以考虑两种解决方案:

  1. 一种做法是在更新数据时也更新缓存,只是在更新缓存前先加一个分布式锁,因为这样在同一时间只允许一个线程更新缓存,就不会产生并发问题了。当然这么做对于写入的性能会有一些影响;

  2. 另一种做法同样也是在更新数据时更新缓存,只是给缓存加一个较短的过期时间,这样即使出现缓存不一致的情况,缓存的数据也会很快过期,对业务的影响也是可以接受。


二、Read/Write Through(读穿 / 写穿)策略

这个策略的核心原则是用户只与缓存打交道,由缓存和数据库通信,写入或者读取数据。

1、Write Through 策略

先查询要写入的数据在缓存中是否已经存在

  • 如果已经存在,则更新缓存中的数据,并且由缓存组件同步更新到数据库中

  • 如果缓存中数据不存在,我们把这种情况叫做“Write Miss(写失效)”

我们可以选择两种“Write Miss”方式:

Write Allocate(按写分配):做法是写入缓存相应位置,再由缓存组件同步更新到数据库中;
No-write allocate(不按写分配):做法是不写入缓存中,而是直接更新到数据库中。

在 Write Through 策略中,我们一般选择“No-write allocate”方式,原因是无论采用哪种“Write Miss”方式,我们都需要同步将数据更新到数据库中,而“No-write allocate”方式相比“Write Allocate”还减少了一次缓存的写入,能够提升写入的性能。

2、Read Through 策略

先查询缓存中数据是否存在

  • 如果存在则直接返回

  • 如果不存在,则由缓存组件负责从数据库中同步加载数据。

高并发系统:缓存使用-读写策略

Read Through/Write Through 策略的特点是由缓存节点而非用户来和数据库打交道,在我们开发过程中相比 Cache Aside 策略要少见一些,原因是我们经常使用的分布式缓存组件,无论是 Memcached 还是 Redis 都不提供写入数据库,或者自动加载数据库中的数据的功能。而我们在使用本地缓存的时候可以考虑使用这种策略,比如说在上一节中提到的本地缓存 Guava Cache 中的 Loading Cache 就有 Read Through 策略的影子。


三、Write Back(写回)策略

这个策略的核心思想是在写入数据时只写入缓存,并且把缓存块儿标记为“脏”的。而脏块儿只有被再次使用时才会将其中的数据写入到后端存储中。

这种策略不能被应用到我们常用的数据库和缓存的场景中,它是计算机体系结构中的设计,比如我们在向磁盘中写数据时采用的就是这种策略。无论是操作系统层面的 Page Cache,还是日志的异步刷盘,亦或是消息队列中消息的异步写入磁盘,大多采用了这种策略。因为这个策略在性能上的优势毋庸置疑,它避免了直接写磁盘造成的随机写问题,毕竟写内存和写磁盘的随机 I/O 的延迟相差了几个数量级呢。


当然,你依然可以在一些场景下使用这个策略,在使用时,我想给你的落地建议是:你在向低速设备写入数据的时候,可以在内存里先暂存一段时间的数据,甚至做一些统计汇总,然后定时地刷新到低速设备上。比如说,你在统计你的接口响应时间的时候,需要将每次请求的响应时间打印到日志中,然后监控系统收集日志后再做统计。但是如果每次请求都打印日志无疑会增加磁盘 I/O,那么不如把一段时间的响应时间暂存起来,经过简单的统计平均耗时,每个耗时区间的请求数量等等,然后定时地,批量地打印到日志中。

总结

1.Cache Aside 是我们在使用分布式缓存时最常用的策略,你可以在实际工作中直接拿来使用。
2.Read/Write Through 和 Write Back 策略需要缓存组件的支持,所以比较适合你在实现本地缓存组件的时候使用;
3.Write Back 策略是计算机体系结构中的策略,不过写入策略中的只写缓存,异步写入后端存储的策略倒是有很多的应用场景。


以上是关于高并发系统:缓存使用-读写策略的主要内容,如果未能解决你的问题,请参考以下文章

并发编程高并发相关技术

高并发场景下的限流策略

在高并发环境下该如何构建应用级缓存

大型高并发高负载网站的系统架构

你的系统是怎样支持高并发的?-多级缓存架构

高并发缓存设计策略