Redis:深入理解与实践

发表时间: 2021-02-24 22:19

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 是什么?都有哪些使用场景?

  • Redis是现在最常用的一种NoSQL数据库,基于内存运行,性能高效的key-value存储系统。
  • 支持数据的持久化,对数据采用copy-on-write技术,可以异步保存数据到磁盘上。
  • Redis的所有操作都是原子操作
  • 支持快速的数据备份,主从复制。

2. redis 有哪些优缺点?

  • 1)高性能,能够达到10万/秒的读写速度
  • 2)支持数据的持久化,使用copy-on-write技术,可以异步保存数据到磁盘上
  • 3)所有的操作都是原子操作
  • 4)快速备份数据,主从复制
  • 5)集群的使用,高性能,防止容灾,宕机

3. redis 和 memcache 有什么区别?

  • 1)redis支持5种数据类型,memcache只支持字符串
  • 2)如果是小数据量,Redis效率更快;大数据,memcache快一些
  • 3)Redis是单线程,基于非阻塞的IO多路复用机制;memcache是多线程,一个Master,多个worker线程
  • 4)Redis支持持久化操作;memcache数据都存储在内存中
  • 5)Redis自带cluster集群;memcache没有原生态的集群操作
  • 6)Redis宕机后可以通过aof恢复数据;memcache宕机后就无法恢复了

4. redis 为什么是单线程的?

Redis在6.X版本支持IO Thread,IO多线程,与外部数据的读写是多线程,但是Redis内部的操作还是单线程。

  • 1)单线程不需要进行线程切换,效率高
  • 2)不存在多线程争抢资源的情况,不需要考虑锁的问题
  • 3)基于内存操作,本身的效率就很高
  • 4)使用了非阻塞的IO多路复用功能
  • 5)数据存储进行了压缩优化
  • 6)使用了高性能的数据结构,如hash、跳表

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命令、配置文件配置(自动触发)

  • 1. save命令:会阻塞当前线程,执行save操作的时候,redis不能够执行其它的操作(串行操作),直到RDB操作结束。
  • 2. bgsave命令:不影响主进程,主进程会fork一个完全一样的子进程来处理RDB操作。期间主进程还是正常处理redis操作,子进程把数据写入到dump.rdb文件中,写完之后会将新的文件替换老文件。
  • 3. 在配置文件中配置save命令,save m n :在m秒内,有n个key发生改变就保存快照。

优点:

  • 1. 保存的文件紧凑,数据全面,适合用于数据备份与容灾恢复
  • 2. 在进行备份的时候,会fork一个子进程操作,主进程不会受到影响。
  • 3. 因为保存的是快照信息,用于恢复数据的时候非常快

缺点:

在进行RDB操作的时候,主进程执行的操作信息不会保存进这次的RDB文件中,这可能会导致数据丢失

2)AOF:记录Redis的操作信息,即日志信息:每收到一条写命令,就会通过write函数将命令添加到文件中

随着使用时间越来越长,AOF文件会越来越大,可以通过bgrewriteaof命令,对AOF文件进行压缩。

触发方式appendfsync:

  • 1)aways:同步执行,每执行修改命令后将命令添加到日志文件中
  • 2)everysec:异步执行,每秒钟同步一次,如果宕机会丢失一秒数据
  • 3)no:从不同步 (同步是指同步写入到磁盘中)

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 如何做内存优化?

  • 1)缩减键值对对象,key越短越好,value适当压缩
  • 2)共享对象池的使用
  • 3)尽可能使用散列表hash
  • 4)尽量减少key的个数

14. redis 淘汰策略有哪些?

  • 1) volatile-lru 从设置失效时间的key中淘汰最少使用的
  • 2) volatile-ttl 从设置失效时间的key中淘汰最早失效的
  • 3) volatile-random 从设置失效时间的key中随机淘汰
  • 4) allkeys-lru 从所有key中淘汰最少使用的
  • 5) allkeys-random 从所有key中随机淘汰
  • 6) noeviction(默认策略):内存使用完,新增对象的时候,直接返回错误
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部署再同一个局域网内