1. redis 是什么?都有哪些使用场景?
2. redis 有哪些功能?
3. redis 和 memcache 有什么区别?
4. redis 为什么是单线程的?
5. 什么是缓存穿透?怎么解决?
6. redis 支持的数据类型有哪些?
7. redis 支持的 java 客户端都有哪些?
8. jedis 和 redisson 有哪些区别?
9. 怎么保证缓存和数据库数据的一致性?
10. redis 持久化有几种方式?
11.redis 怎么实现分布式锁?
12. redis 分布式锁有什么缺陷?
13. redis 如何做内存优化?
14. redis 淘汰策略有哪些?
15. redis 常见的性能问题有哪些?该如何解决?
1. redis 是什么?都有哪些使用场景?
2. redis 有哪些优缺点?
3. redis 和 memcache 有什么区别?
4. redis 为什么是单线程的?
Redis在6.X版本支持IO Thread,IO多线程,与外部数据的读写是多线程,但是Redis内部的操作还是单线程。
5. 什么是缓存穿透?怎么解决?
缓存穿透:在Redis与数据库中都不存在的数据,被频繁的访问数据库,高并发下增加数据库的压力。
解决办法:
① 当查询Redis发现不存在数据的时候,会去查询物理库,如果物理库也不存在数据,在返回空的同时,在Redis中插入一条空数据,防止再次查询的时候去查询物理库。
这个操作会存在一个严重的问题,当查询的空数据越来越多,会导致Redis中数据越来越多,这时最好给Redis加一个失效时间,防止数据量过大。
② 添加一个布隆过滤器,在数据进行查询前先经过布隆过滤器去查询数据是否存在,如果不存在,直接返回空,如果存在则先查询Redis,Redis不存在再去查询物理库。
缺点:过滤数据不准确,布隆过滤器只可以增加数据,无法删除数据。
补充:
1. 什么是缓存雪崩?怎么解决?
缓存雪崩:在同一时间,大部分的缓存失效,导致大量的访问请求在同一时间访问物理库,增加物理机的压力。
场景:① Redis宕机 ② 大量设置了失效时间的key在同一时间失效
解决方法:① 搭建高可用的Redis集群(哨兵模式) ② Redis中key的失效时间不统一,设置不同的失效时间,防止key在同一时间失效。
2. 什么是缓存击穿?怎么解决?
缓存击穿: 热点数据在高并发的场景下,突然失效,导致大量请求访问物理库,导致物理库压力增大。
场景: ① 未被访问过的数据,在初始就被高并发访问。
② 热点key的失效时间达到,高并发请求直接访问物理库
解决方法:① 热点数据可以预初始化到Redis中,例如抢购。
② 当查询redis与查询数据库的时候,添加锁操作,只有当第一个查询数据库执行完成后,将数据存入redis中,才释放锁,允许后面的请求去查询redis
注:具体的操作还是需要看具体的使用场景。
9. 怎么保证缓存和数据库数据的一致性?
在进行缓存与数据库都存储数据的时候,就需要考虑双写时候数据一致性的问题。
极端情况,可以讲读操作与写操作串行执行,效率极低
经典情况是: 1)读取的时候先去读取缓存,如果缓存不存在数据,去数据量读取数据,读取到数据后更新缓存信息同时返回数据。 2) 进行写操作的时候,先删除缓存数据,然后再更新数据库数据。
为什么先删除缓存,然后再更新数据库就结束呢(需要根据实际场景设计)
在进行更新操作的时候,先删除数据库是为了防止在执行更新操作时,读取到老数据,如果更新数据库失败,那么redis中数据已经不存在了,再次查询也只会查询到老数据。 如果更新成功,是否需要写入redis中,需要看实际业务场景,如果更新的redis数据需要根据复杂的计算后才能够得到,那么可以不写入,如果redis对象的更新操作频繁,那等需要读取的时候再读取数据库再写入redis(相当于懒加载),尽量减少资源的浪费。
在上述情况会遇到一个比较常见的问题:当删除缓存中的数据,但是物理库还没有更新的时候,来了一个查询请求,发现redis中不存在数据,就会把物理库中当前的老数据写入到缓存中,会导致物理库更新了新值,但是缓存中数据是老数据。
解决方法:
① 延时双删操作 1)在更新数据库前先删除缓存数据 2)更新物理库数据 3)等待N毫秒后再次删除缓存数据。 该操作可能会存在N毫秒的脏数据。
② 在更新数据库数据前先将缓存中数据置为一个默认值 2)再更新物理库数据 3)如果查询的时候发现缓存中置为默认值,进行自循环等待直至数据库中数据为新值或者key被删除。 该操作需要等待,降低吞吐量。
10. redis 持久化有几种方式?
RDB与AOF
1)RDB:通过快照在指定的时间内对数据进行保存操作
三种触发方式: save命令、bgsave命令、配置文件配置(自动触发)
优点:
缺点:
在进行RDB操作的时候,主进程执行的操作信息不会保存进这次的RDB文件中,这可能会导致数据丢失
2)AOF:记录Redis的操作信息,即日志信息:每收到一条写命令,就会通过write函数将命令添加到文件中
随着使用时间越来越长,AOF文件会越来越大,可以通过bgrewriteaof命令,对AOF文件进行压缩。
触发方式appendfsync:
aways最安全,no效率最高
缺点:与RDB相比,使用AOF进行数据恢复的时候会比较慢。 但是如果执行了flushall命令,dump.rdb文件会被清空,AOF文件只需要删除最后的这条flushAll命令就可以执行恢复操作。
11.redis 怎么实现分布式锁?
获取锁与释放锁都需要保证操作的原子性
加锁: set key value nx|xx ex|px timeout
nx: if not exist xx:if exist
ex:秒 px:毫秒
解锁:
String script = " if redis.cell('get',key[1]) == avg then return redis.cell('del',key[1]) else return 0 end ";
jedis.eval(script);
执行lua命令,当get获取key的值等于目标值,则执行删除当前key,否则不操作
12. redis 分布式锁有什么缺陷?
jedis会产生的问题:1)业务逻辑较长,导致Redis锁自动释放。 2)会导致B锁被A释放的情况。
redisson的getLock方法会调用一个定时任务去监测这个锁,每个10秒检查锁是否存在,存在则延长过期时间,一般为30秒。 看门狗操作。
13. redis 如何做内存优化?
14. redis 淘汰策略有哪些?
LRU算法:最少使用的key被淘汰public class LRUCache { private int capacity; private int count = 0; Map<Integer,Node> nodeMap; Node head; Node last; public LRUCache(int capacity){ this.capacity = capacity; nodeMap = new HashMap<Integer,Node>(); head = new Node(1,1); last = new Node(1,1); head.next = last; last.pre = head; } public int get(int key){ /** * 1. 先判断这个key是否在nodeMap中,如果不存在,直接返回null * 2. 当存在nodeMap中,获取node信息,调用方法删除node节点的前置与后者节点信息,然后将node节点移到首位 * 3. 返回node节点的value值 */ } public void set(int key,int value){ /** * 1. 先判断key是否在nodeMap中,如果存在,获取node节点信息,然后删除当前node的位置,移动到首位。 * 2. 如果不存在,判断count是否已经等于capacity,如果等于,删除尾节点,将node节点添加到nodeMap中,并且添加到首位 * 3. 如果count小于capacity,直接在首位添加node节点,在nodeMap中添加node信息 */} class Node{ int key; int value; Node pre; Node next; public Node(int key,int value){ //设置node节点 this.key = key; this.value = value; } }}
LFU算法:访问次数最少的被淘汰class HitRate implements Comparable<HitRate> { int capacity;//存放key、valueMap<Integer, Object> map = new HashMap<Integer, Object>();//存放key、hitRate对象Map<Integer, HitRate> hitMap = new HashMap<Integer, HitRate>();public LFUCache(int capacity) { this.capacity = capacity;}//HitRate对象存放key,使用次数,最后更新时间 class HitRate implements Comparable<HitRate> { private int key; private int hitCount; private long lastTime; public HitRate(int key, int hitCount, long lastTime) { this.key = key; this.hitCount = hitCount; this.lastTime = lastTime; } @Override public int compareTo(HitRate o) { // 先比较访问次数,如果次数一样,比较最后访问时间 int compare = Integer.compare(this.hitCount, o.hitCount); return compare == 0 ? Long.compare(this.lastTime, o.lastTime) : compare; }}}
15. redis 常见的性能问题有哪些?该如何解决?
1. Redis master执行save命令保存快照的时候,会使用主进程来操作,会导致进程阻塞,建议使用bgsave命令,fork()出一个子进程来保存快照。
2. Master如果开启AOF日志功能,如果不重写AOF文件,影响比较小,但是随着系统的运行,AOF文件会越来越大,当Master重新启动的时候,AOF过大会影响重启的恢复速度。一般重启使用RDB快照来进行数据恢复。
3. Master开启了AOF功能,执行BGREWRITEAOF命令进行AOF文件重写的时候,AOF重写会占用大量的CPU和内存资源。
4. Redis的主从复制性能问题,为了Master与slave之间的性能与网络稳定性,最好将Master与slave部署再同一个局域网内