分享几个Redis入门级常见面试过程中遇到的题目!
你项目中哪里使用到了redis?可以讲一讲嘛
这个题目无论是大公司还是小公司都经常考,建议大家根据自己的项目做总结
redis的几种基础数据结构
redis为什么那么快?
1.基于内存实现:我们都知道内存读写是比磁盘读写快很多的。
2.合理的数据结构,他支持的数据结构如字符串、列表、集合等都是非常接近底层的,这意味着对数据的操作几乎是即时的。
3.redis的读写是单线程的。
4.Redis 使用非阻塞 I/O 模型,即事件驱动模型。这使得 Redis 能高效地处理来自客户端的多个连接和请求,即使在大量连接的情况下也能保持高性能。
从 Redis 6.0 开始,Redis 引入了对多线程的支持,但这种多线程的支持方式与传统的多线程数据库服务器有所不同。在 Redis 6.0 中,多线程主要用于处理网络 I/O,而不是数据的读写操作。
Redis 6.0 的多线程模型是一个有限的多线程模型,专注于优化网络 I/O 处理,而非数据操作。这种设计旨在提高 Redis 在多核 CPU 环境下处理大量并发网络请求的能力,同时保持其单线程执行命令的特性,确保操作的原子性和一致性。因此,尽管 Redis 6.0 支持多线程,但它的核心数据处理逻辑仍然是单线程的。
简述redis哨兵模式,缓存雪崩,缓存穿透,缓存击穿?
Redis 哨兵模式和缓存相关的几个问题(缓存雪崩、缓存穿透和缓存击穿)是分布式系统设计中常见的概念和问题。下面简要介绍每个概念:
Redis 哨兵模式(Sentinel)
Redis 哨兵模式是 Redis 的高可用性解决方案。在这种模式下,有一个或多个哨兵实例对 Redis 服务器(master 以及其 replicas)进行监控。哨兵的主要任务包括:
- 监控:哨兵会检测 Redis 主服务器和从服务器是否正常工作。
- 通知:当某个 Redis 实例出现问题时,哨兵可以通过 API 向管理员或其他应用程序发送通知。
- 自动故障转移:如果主服务器不可用,哨兵会自动将一个从服务器升级为新的主服务器,并让其他从服务器指向新的主服务器。
- 配置提供者:客户端可以询问哨兵当前哪个 Redis 实例是主服务器,并进行连接。
缓存雪崩
缓存雪崩是指在某一个时间点,由于大量的缓存同时失效,导致大量的请求直接落到数据库上,造成数据库压力骤增,甚至引发数据库宕机的现象。缓存雪崩可能因为缓存同一时间设置的过期时间导致的。
解决策略:
- 设置不同的过期时间,避免同时大量缓存过期。
- 使用持久化存储作为备份,如 Redis 的 RDB 或 AOF。
- 限流降级策略,避免数据库被打垮。
缓存穿透
缓存穿透是指查询一个数据库中不存在的数据。由于缓存是不命中的,每次查询都要到数据库查询,导致数据库压力增大。
解决策略:
- 对查询结果为空的情况也进行缓存,避免对同一数据的重复查询打到数据库上。
- 使用布隆过滤器,将所有可能查询的数据哈希到一个足够大的位数组中,一个不存在的数据会被这个布隆过滤器拦截掉,从而避免对数据库的查询。
缓存击穿
缓存击穿是指一个热点的 key 在缓存中过期的瞬间,同时有大量的请求查询这个 key。这时,这些请求都会落到数据库上,造成数据库短时间内大量的请求压力。
解决策略:
- 设置热点数据永不过期,或者用互斥锁控制对这个 key 的数据库访问请求,确保即使缓存失效,对数据库的访问也是串行的。
- 使用更细粒度的锁,例如分布式锁,来控制对这个热点 key 的访问。
理解这些概念和相应的解决策略对于设计高效、可靠的缓存系统至关重要,可以有效地提升应用的性能和稳定性。
Redis的缓存淘汰算法
FIFO(先进先出): 根据缓存被储存的时间,离当前最远的数据优先被淘汰
LRU(最近最少使用): 根据最近被使用的时间,离当前最远的数据优先被淘汰
LFU(最不经常使用): 在一段时间内,缓存数据被使用次数最少的会被淘汰
LRU 缓存实现
如果碰到这种题⽬先不要慌张,现在脑海⾥回忆⼀遍 LRU 的基本概念:LRU(Least Recently Used,最近最少使⽤)是⼀种缓存算法,其核⼼思想是将最近最少使⽤的缓存项移除,以便为更常 ⽤的缓存项腾出空间。
适⽤场景:
- 频繁访问:LRU 算法适⽤于那些有频繁访问的数据,⽐如缓存、⻚⾯置换等场景。
- 有局部性:当访问模式具有局部性,即近期访问的数据更可能在未来被再次访问时,LRU 算法 能够有较好的表现。
- 数据访问分布均匀:如果数据的访问分布较为均匀,没有出现热点数据或周期性访问模式, LRU 算法的命中率较⾼。
- 缓存容ᰁ适中:LRU 算法适⽤于缓存容ᰁ适中的场景,过⼤的缓存可能导致淘汰开销增⼤,⽽过⼩的缓存则可能导致频繁缓存失效。
在 Java 中,可以使⽤ LinkedHashMap 来实现 LRU 缓存。使⽤LinkedHashMap实现 LRU 缓存可 以极⼤地简化代码,因为LinkedHashMap已经内置了按照访问顺序排序的功能。所以使⽤LinkedHashMap 确实可以避免⼿动实现双向链表和节点的逻辑。
为了使⽤ LinkedHashMap 来实现 LRU 缓存,在创建 LinkedHashMap 对象时设置它的访问顺序为 true,这样元素将按照访问顺序进⾏排序。然后,我们可以重写它的 removeEldestEntry ⽅法来控制 是否移除最⽼的数据。
import java.util.LinkedHashMap;
import java.util.Map;
public class LRUCache<K, V> extends LinkedHashMap<K, V> {
private int capacity; public LRUCache(int capacity) {
super(capacity, 0.75f, true);
this.capacity = capacity;
}
public int get(int key) {
return super.getOrDefault(key, -1);
}
public void put(int key, int value) {
super.put(key, value);
}
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
// 当缓存元素个数超过容ᰁ时,移除最⽼的元素
return size() > capacity;
}
public static void main(String[] args) {
// 创建⼀个容ᰁ为3的LRU缓存
LRUCache<Integer, String> lruCache = new LRUCache<>(3);
// 添加数据
lruCache.put(1, "One");
lruCache.put(2, "Two");
lruCache.put(3, "Three");
// 此时缓存为:{1=One, 2=Two, 3=Three}
// 访问某个元素,使其成为最近访问的元素
String value = lruCache.get(2);
// 此时缓存为:{1=One, 3=Three, 2=Two}
// 添加新的数据,触发淘汰
lruCache.put(4, "Four");
// 此时缓存为:{3=Three, 2=Two, 4=Four}
// 元素1被淘汰,因为它是最近最少访问的元素
}
}
在上⾯的代码中, removeEldestEntry ⽅法是⽤于控制是否移除最⽼的数据。当缓存⼤⼩超过指定容 ᰁ时, removeEldestEntry 会返回 true,表示需要移除最⽼的数据。这样,通过 LinkedHashMap 和 重写 removeEldestEntry ⽅法,实现了⼀个简单的 LRU 缓存。
对于⾯试时遇到 LRU 实现的问题,如果不限制使⽤特定的数据结构,可以直接采⽤上述⽅案来进 ⾏简单实现。