Redis 三种高效缓存读写策略你了解吗?
引言:在某一天面试的时候,小 x 被问到 Redis 三种缓存读写的策略,他懵了,原因是简历上明明是写着熟悉 Redis,因此面试官可以随意向任何一个方向进行开火,大家要注意从小点切入,除非自己是完全能够掌握这门技术的,本文将带你去了解三种常用的缓存读写策略的优缺点和使用场景。
题目
Redis 三种高效缓存读写策略你了解吗?
推荐解析
旁路缓存模式(Cache Aside Pattern)
旁路缓存是最常见的缓存读写模式,适用于读多写少的使用经常。服务端以数据库比如 MySQL 为主,Redis 为辅,进行存储。
写操作流程
1)先更新数据库
2)删除 Redis 中的缓存
读操作流程
1)尝试从缓存中读取数据,读取到数据就直接返回
2)缓存中读取不到,从数据库中读取数据
3)读取完毕后,将数据放入缓存
缓存不一致可能的场景(先删后更)
假如先删除缓存,再更新数据库,大概率会造成缓存不一致。线程 1 把 Redis 中 x 数据删除,此时线程 2 发现缓存中没有数据,从数据库读取,而线程以此时又把数据库中的 x 数据更新,因此线程 2 读取到的就是旧数据,造成了缓存不一致的情况。
缓存不一致发生概率小
被推荐的作法,就是上文讲过的,先更新数据库,再删除缓存。
可能不一致的场景如下
1)缓存中 X(数据) 不存在(数据库 X = 1) 2)线程 1 读取数据库,得到旧值(X = 1) 3)线程 2 更新数据库(X = 2) 4)线程 2 删除缓存 5)线程 1 将旧值写入缓存(X = 1) 6)最终 X 的值在缓存中是 1(旧值),在数据库中是 2(新值),也发生不一致。
此场景需要满足:1)缓存失效 2) 读写请求同步对一个数据进行并发操作 3)更新数据库+删除缓存的时间大于读取和写入缓存的时间,也就是说写操作时间大于度操作时间,因为缓存这块可以不计入,理论发生概率是很小的。
旁路缓存优缺点
优点
1)提高数据访问速度
2)减少主存访问
3)提高并发性
缺点
1)存在缓存数据库不一致情况
2)首次请求数据一定不在缓存(可以缓存预热结合定时任务)
3)写操作频繁会导致缓存被频繁删除,影响缓存命中率。(可以加分布式锁,保证更新数据库同时更新缓存。或者直接设置一个较短的过期时间)
旁路缓存示例代码
import java.util.HashMap;
import java.util.Map;
public class CacheAsideExample {
// 模拟缓存
private static Map<String, String> cache = new HashMap<>();
// 模拟数据库或数据源
private static Map<String, String> dataSource = new HashMap<>();
// 从缓存中获取数据
public static String getDataFromCache(String key) {
return cache.get(key);
}
// 从数据源中获取数据
public static String getDataFromDataSource(String key) {
return dataSource.get(key);
}
// 将数据存入缓存
public static void putDataIntoCache(String key, String value) {
cache.put(key, value);
}
// 删除缓存中的数据
public static void deleteDataFromCache(String key) {
cache.remove(key);
}
// 从数据源中加载数据,并存入缓存
public static String loadData(String key) {
String data = getDataFromDataSource(key);
if (data != null) {
putDataIntoCache(key, data);
}
return data;
}
public static void main(String[] args) {
// 设置数据源
dataSource.put("key1", "value1");
dataSource.put("key2", "value2");
// 从缓存中获取数据,如果不存在则从数据源中加载
String data1 = getDataFromCache("key1");
if (data1 == null) {
data1 = loadData("key1");
}
System.out.println("Data1: " + data1);
// 从缓存中获取数据,如果不存在则从数据源中加载
String data2 = getDataFromCache("key2");
if (data2 == null) {
data2 = loadData("key2");
}
System.out.println("Data2: " + data2);
// 删除缓存中的数据
deleteDataFromCache("key1");
// 从缓存中获取数据,如果不存在则从数据源中加载
String data3 = getDataFromCache("key1");
if (data3 == null) {
data3 = loadData("key1");
}
System.out.println("Data3: " + data3);
}
}
读写穿透(Read/Write Through Pattern)
读写穿透策略将 Redis/Memcached 视为数据存储的主要地方,也就是说将缓存充当原本的数据库,利用 Cache 服务负责将数据读取并写入数据库(MySQL、Oracle等)。
写操作流程
1)先查询缓存,缓存不存在,更新数据库
2)缓存存在,先更新缓存,利用 Cache 服务同步更新数据库。
读操作流程
1)从缓存读取数据,读取到返回
2)缓存读取不到,从数据库加载后写入缓存并返回。
和旁路缓存相反,读写穿透,主缓存从数据库。
异步缓存写入(Write Behind Pattern)
只更新缓存,不利用 Cache 服务更新数据库,可以利用消息队列,先存放要消费的信息,然后可以异步批量的更新数据库,一般不使用,但数据库的缓冲池机制是这种策略的一个实现,
适用场景:数据经常变化,一致性要求不高(可以延时同步),比如 PV、UV、点赞量。
其他补充
鱼聪明 AI 的回答:
鱼聪明 AI 地址:https://www.yucongming.com/
1)旁路缓存(Cache Aside)
1.1)优点
简单易实现,适用于小规模系统。 缓存数据不会过期,不会出现缓存雪崩。
1.2)缺点
数据一致性问题,需要应用程序主动更新缓存。 缓存数据可能过期,导致缓存击穿。
1.3)使用场景
适用于读多写少的场景。 数据更新频率不高,对数据实时性要求不高的场景。
2)读写穿透(Cache Through)
2.1)优点
数据一致性较好,不会出现数据不一致的情况。 缓存数据不会过期,不会出现缓存雪崩。
2.2)缺点
需要保证数据源的可靠性和性能。 对于大规模系统,可能增加数据源的压力。
2.3)使用场景
适用于数据源更新频率高,对数据实时性要求高的场景。 数据源具有较好的性能和可靠性。
3)异步缓存写入(Write Behind)
3.1)优点
减少对数据源的频繁写入,提高性能。 可以缓解瞬时写入压力,提高系统稳定性。
3.2)缺点
数据一致性可能受影响,存在一定的数据丢失风险。 需要额外的机制来处理数据更新失败的情况。
3.3)使用场景
适用于写入频率高,对数据实时性要求不高的场景。 对数据丢失一定容忍度的场景。
在实际应用中,根据系统的特点和需求,可以选择合适的缓存读写策略来提高系统性能和稳定性。
欢迎交流
在阅读完本篇文章后,你应该对 Redis 的三种缓存读写策略有了一定了解,一般采用第一个策略进行读写,其他两种策略了解即可,在文末有三个问题将会检验本章的学习,欢迎在评论区发表意见。
1)旁路缓存策略中,如何解决缓存数据过期和缓存击穿的问题?
2)读写穿透策略中,如何确保数据源的可靠性和性能,以及如何处理数据源故障的情况?
3)在实际应用中,如何选择合适的缓存读写策略,考虑到系统的特点和需求?
点燃求职热情!每周持续更新,海量面试题等你挑战!赶紧关注面试鸭公众号,轻松备战春招和暑期实习!