引言:在某一天面试的时候,小 x 被问到 Redis 三种缓存读写的策略,他懵了,原因是简历上明明是写着熟悉 Redis,因此面试官可以随意向任何一个方向进行开火,大家要注意从小点切入,除非自己是完全能够掌握这门技术的,本文将带你去了解三种常用的缓存读写策略的优缺点和使用场景。
Redis 三种高效缓存读写策略你了解吗?
旁路缓存是最常见的缓存读写模式,适用于读多写少的使用经常。服务端以数据库比如 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);
}
}
读写穿透策略将 Redis/Memcached 视为数据存储的主要地方,也就是说将缓存充当原本的数据库,利用 Cache 服务负责将数据读取并写入数据库(MySQL、Oracle等)。
写操作流程
1)先查询缓存,缓存不存在,更新数据库
2)缓存存在,先更新缓存,利用 Cache 服务同步更新数据库。
读操作流程
1)从缓存读取数据,读取到返回
2)缓存读取不到,从数据库加载后写入缓存并返回。
和旁路缓存相反,读写穿透,主缓存从数据库。
只更新缓存,不利用 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)在实际应用中,如何选择合适的缓存读写策略,考虑到系统的特点和需求?
点燃求职热情!每周持续更新,海量面试题等你挑战!赶紧关注面试鸭公众号,轻松备战春招和暑期实习!