1-
2-
31<!-- @import "[TOC]" {cmd="toc" depthFrom=1 depthTo=6 orderedList=false} -->
42
53<!-- code_chunk_output -->
@@ -121,39 +119,56 @@ Cache Aside Pattern 中服务端需要同时维系 DB 和 cache,并且是以 D
121119
122120你仅仅了解了上面这些内容的话是远远不够的,我们还要搞懂其中的原理。
123121
124- 比如说面试官很可能会追问:“** 在写数据的过程中,可以先删除缓存,后更新数据库么 ?** ”
122+ 比如说面试官很可能会追问:“** 在写数据的过程中,可以先删除 cache ,后更新 DB 么 ?** ”
125123
126- ** 答案:** 那肯定是不行的!因为这样可能会造成** 数据库和缓存数据不一致 ** 的问题。为什么呢?比如说请求1 先写数据A,请求2随后读数据A的话就很有可能产生数据不一致性的问题。这个过程可以简单描述为:
124+ ** 答案:** 那肯定是不行的!因为这样可能会造成** 数据库(DB)和缓存(Cache)数据不一致 ** 的问题。为什么呢?比如说请求1 先写数据A,请求2随后读数据A的话就很有可能产生数据不一致性的问题。这个过程可以简单描述为:
127125
128- > 请求1先把缓存中的A数据删除 -> 请求2从数据库中读取数据->请求1再把数据库中的A数据更新 。
126+ > 请求1先把cache中的A数据删除 -> 请求2从DB中读取数据->请求1再把DB中的A数据更新 。
129127
130- 当你这样回答之后,面试官可能会紧接着就追问:“** 在写数据的过程中,先更新数据库,后删除缓存就没有问题了么 ?** ”
128+ 当你这样回答之后,面试官可能会紧接着就追问:“** 在写数据的过程中,先更新DB,后删除cache就没有问题了么 ?** ”
131129
132130** 答案:** 理论上来说还是可能会出现数据不一致性的问题,不过概率非常小,因为缓存的写入速度是比数据库的写入速度快很多!
133131
134- 比如请求1先读数据 A,请求2随后写数据A,并且数据库A不在缓存中的话也有可能产生数据不一致性的问题 。这个过程可以简单描述为:
132+ 比如请求1先读数据 A,请求2随后写数据A,并且数据A不在缓存中的话也有可能产生数据不一致性的问题 。这个过程可以简单描述为:
135133
136- > 请求1从DB读数据A->请求2写更新数据 A 到数据库并把删除缓存中的A数据->请求1将数据库A写入缓存 。
134+ > 请求1从DB读数据A->请求2写更新数据 A 到数据库并把删除cache中的A数据->请求1将数据A写入cache 。
137135
138136现在我们再来分析一下 ** Cache Aside Pattern 的缺陷** 。
139137
140138** 缺陷1:首次请求数据一定不在 cache 的问题**
141139
142- 解决办法:可以将热点数据可以提前放入缓存中 。
140+ 解决办法:可以将热点数据可以提前放入cache 中 。
143141
144- ** 缺陷2:写操作比较频繁的话导致缓存中的数据会被频繁被删除 ,这样会影响缓存命中率 。**
142+ ** 缺陷2:写操作比较频繁的话导致cache中的数据会被频繁被删除 ,这样会影响缓存命中率 。**
145143
146144解决办法:
147145
148- - 数据库和缓存数据强一致场景 :更新数据的时候同样更新缓存 ,不过我们需要加一个锁/分布式锁来保证更新缓存的时候不存在线程安全问题 。
149- - 可以短暂地允许数据库和缓存数据不一致的场景 :更新数据的时候同样更新缓存 ,但是给缓存加一个比较短的过期时间,这样的话就可以保证即使数据不一致的话影响也比较小。
146+ - 数据库和缓存数据强一致场景 :更新DB的时候同样更新cache ,不过我们需要加一个锁/分布式锁来保证更新cache的时候不存在线程安全问题 。
147+ - 可以短暂地允许数据库和缓存数据不一致的场景 :更新DB的时候同样更新cache ,但是给缓存加一个比较短的过期时间,这样的话就可以保证即使数据不一致的话影响也比较小。
150148
151149#### 5.2. Read/Write Through Pattern(读写穿透)
152150
153- Read/Write Through 套路是:服务端把 cache 视为主要数据存储,从中读取数据并将数据写入其中。cache 服务负责将此数据读取和写入 DB,从而减轻了应用程序的职责。
151+ Read/Write Through Pattern 中服务端把 cache 视为主要数据存储,从中读取数据并将数据写入其中。cache 服务负责将此数据读取和写入 DB,从而减轻了应用程序的职责。
152+
153+ 这种缓存读写策略小伙伴们应该也发现了在平时在开发过程中非常少见。抛去性能方面的影响,大概率是因为我们经常使用的分布式缓存 Redis 并没有提供 cache 将数据写入DB的功能。
154+
155+ ** 写(Write Through):**
156+
157+ - 先查 cache,cache 中不存在,直接更新 DB。
158+ - cache 中存在,则先更新 cache,然后 cache 服务自己更新 DB(** 同步更新 cache 和 DB** )。
159+
160+ 简单画了一张图帮助大家理解写的步骤。
161+
162+ ![ write-through] ( images/缓存读写策略/write-through.png )
163+
164+ ** 读(Read Through):**
154165
155- 1 . 写(Write Through):先查 cache,cache 中不存在,直接更新 DB。 cache 中存在,则先更新 cache,然后 cache 服务自己更新 DB(** 同步更新 cache 和 DB** )。
156- 2 . 读(Read Through): 从 cache 中读取数据,读取到就直接返回 。读取不到的话,先从 DB 加载,写入到 cache 后返回响应。
166+ - 从 cache 中读取数据,读取到就直接返回 。
167+ - 读取不到的话,先从 DB 加载,写入到 cache 后返回响应。
168+
169+ 简单画了一张图帮助大家理解读的步骤。
170+
171+ ![ read-through] ( images/缓存读写策略/read-through.png )
157172
158173Read-Through Pattern 实际只是在 Cache-Aside Pattern 之上进行了封装。在 Cache-Aside Pattern 下,发生读请求的时候,如果 cache 中不存在对应的数据,是由客户端自己负责把数据写入 cache,而 Read Through Pattern 则是 cache 服务自己来写入缓存的,这对客户端是透明的。
159174
@@ -165,6 +180,13 @@ Write Behind Pattern 和 Read/Write Through Pattern 很相似,两者都是由
165180
166181但是,两个又有很大的不同:** Read/Write Through 是同步更新 cache 和 DB,而 Write Behind Caching 则是只更新缓存,不直接更新 DB,而是改为异步批量的方式来更新 DB。**
167182
168- ** Write Behind Pattern 下 DB 的写性能非常高,尤其适合一些数据经常变化的业务场景比如说一篇文章的点赞数量、阅读数量。** 往常一篇文章被点赞 500 次的话,需要重复修改 500 次 DB,但是在 Write Behind Pattern 下可能只需要修改一次 DB 就可以了。
183+ 很明显,这种方式对数据一致性带来了更大的挑战,比如cache数据可能还没异步更新DB的话,cache服务可能就就挂掉了。
184+
185+ 这种策略在我们平时开发过程中也非常非常少见,但是不代表它的应用场景少,比如消息队列中消息的异步写入磁盘、MySQL 的 InnoDB Buffer Pool 机制都用到了这种策略。
186+
187+ Write Behind Pattern 下 DB 的写性能非常高,非常适合一些数据经常变化又对数据一致性要求没那么高的场景,比如浏览量、点赞量。
188+
189+
190+
191+
169192
170- 但是,这种模式同样也给 DB 和 Cache 一致性带来了新的考验,很多时候如果数据还没异步更新到 DB 的话,Cache 服务宕机就 gg 了。
0 commit comments