当前位置:首页 > 服务端 > 通过面试题学Redis--基础篇

通过面试题学Redis--基础篇

2022年09月17日 21:43:05服务端6

欢迎来我的个人网站,里面有最新的版本

这篇介绍了下单体Redis的知识,还会再写一篇介绍分布式集群下的Redis。

参考:https://blog.nowcoder.net/n/f62aef5a98804489a7e7bd27cfd7b542

​ https://www.cnblogs.com/ysocean/p/9080942.html

面试题

这篇博客会解决以下问题:

Redis是什么,用在哪?Redis 的缺点?

Redis常见数据类型

  • 用在什么场景
  • 底层数据结构是啥
  • Zset底层为什么要用两个数据结构

Redis的持久化

说一下 Redis 的数据淘汰策略

Redis和MySql的区别?

  • redis为什么不能代替mysql?
  • redis能存大量的数据呢为什么不能?说到了事务

Redis和memcached有什么区别?


以下内容放在另外一篇博客

Redis的并发竞争问题如何解决?
Redis的缓存穿透,缓存雪崩,缓存击穿?怎么解决?
怎么保证缓存和数据库数据的一致性?
Redis集群!集群是如何判断是否有某个节点挂掉?集群进入fail状态的必要条件?
Redis哨兵
Redis主从复制和一致性保证!
Redis热key问题

一、Redis简介

1.1 Redis是什么

redis是一种支持Key-Value等多种数据结构的存储系统。可用于缓存,事件发布或订阅,高速队列等场景。该数据库使用ANSI C语言编写,支持网络,提供字符串,哈希,列表,队列,集合结构直接存取,基于内存,可持久化。

1.2 常见数据类型

了解下Redis内部内存管理中是如何描述这些不同数据类型的

首先Redis内部使用一个redisObject对象来表示所有的key和value,redisObject最主要的信息如上图所示:

type代表一个value对象具体是何种数据类型,

encoding是不同数据类型在redis内部的存储方式

通过面试题学Redis--基础篇 _ JavaClub全栈架构师技术笔记


redis支持五种数据类型作为其Value,redis的Key都是字符串类型的。Redis支持string(字符串),hash(哈希),list(列表),set(集合)和zset(sorted set有序集合)

String

类型是二进制安全的,在传输数据时,保证二进制数据的信息安全,也就是不被篡改、破译等,如果被攻击,能够及时检测出来。

  • 实现方式: String在redis内部存储默认就是一个字符串,被redisObject所引用,当遇到incr,decr等操作时会转成数值型进行计算,此时redisObject的encoding字段为int

  • 应用场景:

    1. 常用于保存单个字符串或JSON字符串数据
    2. 因String是二进制安全的,可以把一个图片文件的内容作为字符串来存储
    3. 计数器(常规key-value缓存应用。常规计数: 微博数, 粉丝数)
    4. INCR等指令本身就具有原子操作的特性,所以我们完全可以利用redis的INCR、INCRBY、DECR、DECRBY等指令来实现原子计数的效果。实现业务上的统计计数需求。
  • 常用命令: set,get,decr,incr,mget 等

通过面试题学Redis--基础篇 _ JavaClub全栈架构师技术笔记

Hash

类是一个string类型的field和value的映射表,**hash特别适合用于存储对象。**Redis 中每个 hash 可以存储 232 - 1 键值对(40多亿),可以看成具有KEY和VALUE的MAP容器,该类型非常适合于存储值对象的信息, 如:uname,upass,age等

  • 实现方式:

    上面已经说到Redis Hash对应Value内部实际就是一个HashMap,实际这里会有2种不同实现,这个Hash的成员比较少时Redis为了节省内存会采用类似一维数组的方式来紧凑存储,而不会采用真正的HashMap结构,对应的value redisObject的encoding为zipmap,当成员数量增大时会自动转成真正的HashMap,此时encoding为ht

  • 应用场景:

    1. 常用于存储一个对象

      通过面试题学Redis--基础篇 _ JavaClub全栈架构师技术笔记
  • 常用命令:hget,hset,hgetall 等

通过面试题学Redis--基础篇 _ JavaClub全栈架构师技术笔记

为什么不用String存对象?

hash是最接近关系数据库结构的数据类型,可以将数据库一条记录或程序中一个对象转换成hashmap存放在redis中。
用户ID为查找的key,存储的value用户对象包含姓名,年龄,生日等信息,如果用普通的key/value结构来存储,主要有以下2种存储方式:
第一种方式将用户ID作为查找key,把其他信息封装成一个对象以序列化的方式存储,这种方式的缺点是,增加了序列化/反序列化的开销,并且在需要修改其中一项信息时,需要把整个对象取回,并且修改操作需要对并发进行保护,引入CAS等复杂问题。

通过面试题学Redis--基础篇 _ JavaClub全栈架构师技术笔记

第二种方法是这个用户信息对象有多少成员就存成多少个key-value对儿,用用户ID+对应属性的名称作为唯一标识来取得对应属性的值,虽然省去了序列化开销和并发问题,但是用户ID为重复存储,如果存在大量这样的数据,内存还是挺浪费的

通过面试题学Redis--基础篇 _ JavaClub全栈架构师技术笔记

List

Redis列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)一个列表最多可以包含 232 - 1 个元素 (4294967295, 每个列表超过40亿个元素) 类似JAVA中的LinkedList。

  • 实现方式:

    Redis list的实现为一个双向链表,即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销,Redis内部的很多实现,包括发送缓冲队列等也都是用的这个数据结构。

  • 应用场景

    1. 对数据量大的集合数据删减
      • 列表数据显示、关注列表、粉丝列表、留言评价等…分页、热点新闻(Top5)等
      • 利用LRANGE还可以很方便的实现分页的功能,在博客系统中,每片博文的评论也可以存入一个单独的list中。
    2. 任务队列
      • list通常用来实现一个消息队列,而且可以确保先后顺序。
  • 常用命令:lpush,rpush,lpop,rpop,lrange等。

通过面试题学Redis--基础篇 _ JavaClub全栈架构师技术笔记

Set

Redis 的 Set 是 String 类型的无序集合。 集合成员是唯一的,这就意味着集合中不能出现重复的数据。Redis 中集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。集合中最大的成员数为 232 - 1 。类似于JAVA中的 Hashtable集合
set的底层存储结构使用了intset和hashtable两种数据结构存储的,intset我们可以理解为数组,hashtable就是普通的哈希表(key为set的值,value为null)。
intset内部其实是一个数组(int8_t coentents[]数组),存储数据的时候是有序的,因为在查找数据的时候是通过二分查找来实现的。

  • 实现方式:

    set 的内部实现是一个 value永远为null的HashMap,实际就是通过计算hash的方式来快速排重的,这也是set能提供判断一个成员是否在集合内的原因。

  • 应用场景

    1. 对两个集合间的数据计算进行交集、并集、差集运算
    2. 以非常方便的实现如共同关注、共同喜好、二度好友等功能。
    3. 利用唯一性,可以统计访问网站的所有独立 IP
  • 常用命令:sadd,spop,smembers,sunion 等

通过面试题学Redis--基础篇 _ JavaClub全栈架构师技术笔记

Zset有序集合(sorted set)

1、 Redis 有序集合和集合一样也是string类型元素的集合,且不允许重复的成员。
2、不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。
3、集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。 集合中最大的成员数为 232 - 1
4、Redis的ZSet是有序、且不重复

  • 实现方式:

    Redis sorted set的内部使用HashMap和**跳跃表(SkipList)**来保证数据的存储和有序,HashMap里放的是成员到score的映射,而跳跃表里存放的是所有的成员,排序依据是HashMap里存的score,使用跳跃表的结构可以获得比较高的查找效率,并且在实现上比较简单。

  • 应用场景

    1. 排行榜:成绩排行
    2. 带权重的队列,让重要的任务优先执行。
  • 常用命令:zadd,zrange,zrem,zcard等

二、底层数据结构

参考https://segmentfault.com/a/1190000020770894?utm_source=tag-newest

2.1 简单动态字符串(SDS)

struct sdshdr{

    //等于 SDS 保存字符串的长度
    int len;
    //记录 buf 数组中未使用字节的数量
    int free;
    //字节数组,用于保存字符串
    char buf[];
}

通过面试题学Redis--基础篇 _ JavaClub全栈架构师技术笔记

不使用C语言字符串实现,而是使用 SDS的好处

  1. 常数复杂度获取字符串长度

由于 len 属性的存在,我们获取 SDS 字符串的长度只需要读取 len 属性,时间复杂度为 O(1)。而对于 C 语言,获取字符串的长度通常是经过遍历计数来实现的,时间复杂度为 O(n)。通过strlen key 命令可以获取 key 的字符串长度。

  1. 杜绝缓冲区溢出

我们知道在 C 语言中使用 strcat 函数来进行两个字符串的拼接,一旦没有分配足够长度的内存空间,就会造成缓冲区溢出。而对于 SDS 数据类型,在进行字符修改的时候,会首先根据记录的 len 属性检查内存空间是否满足需求,如果不满足,会进行相应的空间扩展,然后在进行修改操作,所以不会出现缓冲区溢出。

  1. 减少修改字符串的内存重新分配次数

C语言由于不记录字符串的长度,所以如果要修改字符串,必须要重新分配内存(先释放再申请),因为如果没有重新分配,字符串长度增大时会造成内存缓冲区溢出,字符串长度减小时会造成内存泄露。

而对于SDS,由于len属性和free属性的存在,对于修改字符串SDS实现了空间预分配和惰性空间释放两种策略:

1、空间预分配:对字符串进行空间扩展的时候,扩展的内存比实际需要的多,这样可以减少连续执行字符串增长操作所需的内存重分配次数。

2、惰性空间释放:对字符串进行缩短操作时,程序不立即使用内存重新分配来回收缩短后多余的字节,而是使用 free 属性将这些字节的数量记录下来,等待后续使用。(当然SDS也提供了相应的API,当我们有需要时,也可以手动释放这些未使用的空间。)

  1. 二进制安全

因为C字符串以空字符作为字符串结束的标识,而对于一些二进制文件(如图片等),内容可能包括空字符串,因此C字符串无法正确存取;而所有 SDS 的API 都是以处理二进制的方式来处理 buf 里面的元素,并且 SDS 不是以空字符串来判断是否结束,而是以 len 属性表示的长度来判断字符串是否结束。

  1. 兼容部分 C 字符串函数

虽然 SDS 是二进制安全的,但是一样遵从每个字符串都是以空字符串结尾的惯例,这样可以重用 C 语言库<string.h> 中的一部分函数。

通过面试题学Redis--基础篇 _ JavaClub全栈架构师技术笔记

2.2 双端链表

typedef  struct listNode{
       //前置节点
       struct listNode *prev;
       //后置节点
       struct listNode *next;
       //节点的值
       void *value;  
}listNode
    
typedef struct list{
     //表头节点
     listNode *head;
     //表尾节点
     listNode *tail;
     //链表所包含的节点数量
     unsigned long len;
     //节点值复制函数
     void (*free) (void *ptr);
     //节点值释放函数
     void (*free) (void *ptr);
     //节点值对比函数
     int (*match) (void *ptr,void *key);
}list;

通过面试题学Redis--基础篇 _ JavaClub全栈架构师技术笔记

Redis链表特性:

  1. 双端:链表具有前置节点和后置节点的引用,获取这两个节点时间复杂度都为O(1)。

  2. 无环:表头节点的 prev 指针和表尾节点的 next 指针都指向 NULL,对链表的访问都是以 NULL 结束。

  3. 带链表长度计数器:通过 len 属性获取链表长度的时间复杂度为 O(1)。

  4. 多态:链表节点使用 void* 指针来保存节点值,可以保存各种不同类型的值

2.3 字典

字典又称为符号表或者关联数组、或映射(map),是一种用于保存键值对的抽象数据结构。字典中的每一个键 key 都是唯一的,通过 key 可以对值来进行查找或修改。C 语言中没有内置这种数据结构的实现,所以字典依然是 Redis自己构建的。

Redis 的字典使用哈希表作为底层实现

// 哈希表结构
typedef struct dictht{
     //哈希表数组
     dictEntry **table;
     //哈希表大小
     unsigned long size;
     //哈希表大小掩码,用于计算索引值
     //总是等于 size-1
     unsigned long sizemask;
     //该哈希表已有节点的数量
     unsigned long used;
 
}dictht

// 哈希表是由数组 table 组成,table 中每个元素都是指向 dict.h/dictEntry 结构,dictEntry 结构定义如下:
typedef struct dictEntry{
     //键
     void *key;
     //值
     union{
          void *val;
          uint64_tu64;
          int64_ts64;
     }v;
 
     //指向下一个哈希表节点,形成链表
     struct dictEntry *next;
}dictEntry

哈希表最大的问题是存在哈希冲突,如何解决哈希冲突,有开放地址法和链地址法。这里采用的便是链地址法,通过next这个指针可以将多个哈希值相同的键值对连接在一起,用来解决哈希冲突

通过面试题学Redis--基础篇 _ JavaClub全栈架构师技术笔记

2.4 跳跃表

跳跃表(skiplist)是一种有序数据结构,它通过在每个节点中维持多个指向其它节点的指针,从而达到快速访问节点的目的。具有如下性质:

  1. 由很多层结构组成;

  2. 每一层都是一个有序的链表,排列顺序为由高层到底层,都至少包含两个链表节点,分别是前面的head节点和后面的nil节点;

  3. 最底层的链表包含了所有的元素;

  4. 如果一个元素出现在某一层的链表中,那么在该层之下的链表也全都会出现(上一层的元素是当前层的元素的子集);

  5. 链表中的每个节点都包含两个指针,一个指向同一层的下一个链表节点,另一个指向下一层的同一个链表节点;

通过面试题学Redis--基础篇 _ JavaClub全栈架构师技术笔记

Redis中跳跃表节点定义如下:

typedef struct zskiplistNode {
     //层
     struct zskiplistLevel{
           //前进指针
           struct zskiplistNode *forward;
           //跨度
           unsigned int span;
     }level[];
 
     //后退指针
     struct zskiplistNode *backward;
     //分值
     double score;
     //成员对象
     robj *obj;
 
} zskiplistNode

多个跳跃表节点构成一个跳跃表:

typedef struct zskiplist{
     //表头节点和表尾节点
     structz skiplistNode *header, *tail;
     //表中节点的数量
     unsigned long length;
     //表中层数最大的节点的层数
     int level;
 
}zskiplist;

通过面试题学Redis--基础篇 _ JavaClub全栈架构师技术笔记

①、搜索:从最高层的链表节点开始,如果比当前节点要大和比当前层的下一个节点要小,那么则往下找,也就是和当前层的下一层的节点的下一个节点进行比较,以此类推,一直找到最底层的最后一个节点,如果找到则返回,反之则返回空。

②、插入:首先确定插入的层数,有一种方法是假设抛一枚硬币,如果是正面就累加,直到遇见反面为止,最后记录正面的次数作为插入的层数。当确定插入的层数k后,则需要将新元素插入到从底层到k层。

③、删除:在各个层中找到包含指定值的节点,然后将节点从链表中删除即可,如果删除以后只剩下头尾两个节点,则删除这一层。

2.5 整数集合

整数集合(intset)是Redis用于保存整数值的集合抽象数据类型,它可以保存类型为int16_t、int32_t 或者int64_t 的整数值,并且保证集合中不会出现重复元素。

定义如下:

typedef struct intset{
     //编码方式
     uint32_t encoding;
     //集合包含的元素数量
     uint32_t length;
     //保存元素的数组
     int8_t contents[];
 
}intset;

整数集合的每个元素都是 contents 数组的一个数据项,它们按照从小到大的顺序排列,并且不包含任何重复项。

length 属性记录了 contents 数组的大小。

需要注意的是虽然 contents 数组声明为 int8_t 类型,但是实际上contents 数组并不保存任何 int8_t 类型的值,其真正类型有 encoding 来决定。

升级

当我们新增的元素类型比原集合元素类型的长度要大时,需要对整数集合进行升级,才能将新元素放入整数集合中。具体步骤:

  1. 根据新元素类型,扩展整数集合底层数组的大小,并为新元素分配空间。

  2. 将底层数组现有的所有元素都转成与新元素相同类型的元素,并将转换后的元素放到正确的位置,放置过程中,维持整个元素顺序都是有序的。

  3. 将新元素添加到整数集合中(保证有序)。

升级能极大地节省内存。

降级

整数集合不支持降级操作,一旦对数组进行了升级,编码就会一直保持升级后的状态。

2.6 压缩列表

压缩列表(ziplist)是Redis为了节省内存而开发的,是由一系列特殊编码的连续内存块组成的顺序型数据结构,一个压缩列表可以包含任意多个节点(entry),每个节点可以保存一个字节数组或者一个整数值。

压缩列表的原理:压缩列表并不是对数据利用某种算法进行压缩,而是将数据按照一定规则编码在一块连续的内存区域,目的是节省内存。

通过面试题学Redis--基础篇 _ JavaClub全栈架构师技术笔记

压缩列表的每个节点构成如下:

通过面试题学Redis--基础篇 _ JavaClub全栈架构师技术笔记

  • previous_entry_ength:记录压缩列表前一个字节的长度。previous_entry_ength的长度可能是1个字节或者是5个字节,如果上一个节点的长度小于254,则该节点只需要一个字节就可以表示前一个节点的长度了,如果前一个节点的长度大于等于254,则previous length的第一个字节为254,后面用四个字节表示当前节点前一个节点的长度。利用此原理即当前节点位置减去上一个节点的长度即得到上一个节点的起始位置,压缩列表可以从尾部向头部遍历。这么做很有效地减少了内存的浪费。

  • encoding:节点的encoding保存的是节点的content的内容类型以及长度,encoding类型一共有两种,一种字节数组一种是整数,encoding区域长度为1字节、2字节或者5字节长。

  • content:content区域用于保存节点的内容,节点内容类型和长度由encoding决定。

三、持久化机制

数据保存在内存中,高效但也容易发生丢失。于是需要一种持久化的机制,Redis提供了RDB(Redis DataBase)和AOF(Append Only File)。

3.1 数据持久化的过程

(1)客户端向服务端发送写操作(数据在客户端的内存中)。

(2)数据库服务端接收到写请求的数据(数据在服务端的内存中)。

(3)服务端调用write方法,将数据从系统内存的缓冲区往磁盘缓存中写。

(4)磁盘控制器将数据写到磁盘的物理介质中(数据真正落到磁盘上)。

3.2 RDB

RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘。也是默认的持久化方式,这种方式是就是将内存中数据以快照的方式写入到二进制文件中,默认的文件名为dump.rdb。

既然RDB机制是通过把某个时刻的所有数据生成一个快照来保存,那么就应该有一种触发机制,是实现这个过程。对于RDB来说,提供了三种机制:save、bgsave、自动化

3.2.1 save触发方式

该命令会阻塞当前Redis服务器,执行save命令期间,Redis不能处理其他命令,直到RDB过程完成为止。具体流程如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5TYhOnb8-1588592281716)(https://pics1.baidu.com/feed/e7cd7b899e510fb3aa8c05042b22c093d0430ca7.jpeg?token=7ed4cf784a82d04e60b8dc72cf7e3c24&s=EDBAA5565D1859C85444707E02005071)]

执行完成时候如果存在老的RDB文件,就把新的替代掉旧的。我们的客户端可能都是几万或者是几十万,这种方式显然不可取。

3.2.2 bgsave触发方式

执行该命令时,Redis会在后台异步进行快照操作,快照同时还可以响应客户端请求。具体流程如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-69xsJBED-1588592281718)(https://pics5.baidu.com/feed/023b5bb5c9ea15cefb035bc8431132f53b87b21e.jpeg?token=a72f072d65d2de548d71bb459cd0bf4f&s=05AAFE168FF04C8A10FD2DEE0300E032)]

具体操作是Redis进程执行fork操作创建子进程,RDB持久化过程由子进程负责,完成后自动结束。阻塞只发生在fork阶段,一般时间很短。基本上 Redis 内部所有的RDB操作都是采用 bgsave 命令。

3.2.3 save与bgsave对比

我们可以修改这些配置来实现我们想要的效果。因为第三种方式是配置的,所以我们对前两种进行一个对比:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QbLxRBus-1588592281722)(https://pics5.baidu.com/feed/1c950a7b02087bf43b4490d50ac25f2a11dfcf7e.jpeg?token=22f387ba78130c6115420059481b2393&s=EF48A15796784D8816E1D9EB03007024)]

3.2.4 自动触发

自动触发是在配置文件中配置bgsave相关操作,比如触发条件、失败停止写入等来完成的。

在redis.conf配置文件中,可以设置:

**①save:**这里是用来配置触发 Redis的 RDB 持久化条件,也就是什么时候将内存中的数据保存到硬盘。比如“save m n”。表示m秒内数据集存在n次修改时,自动触发bgsave

不需要持久化,那么你可以注释掉所有的 save 行来停用保存功能。

**②stop-writes-on-bgsave-error :**默认值为yes。当启用了RDB且最后一次后台保存数据失败,Redis是否停止接收数据。这会让用户意识到数据没有正确持久化到磁盘上,否则没有人会注意到灾难(disaster)发生了。如果Redis重启了,那么又可以重新开始接收数据了

**③rdbcompression ;**默认值是yes。对于存储到磁盘中的快照,可以设置是否进行压缩存储。

**④rdbchecksum :**默认值是yes。在存储快照后,我们还可以让redis使用CRC64算法来进行数据校验,但是这样做会增加大约10%的性能消耗,如果希望获取到最大的性能提升,可以关闭此功能。

**⑤dbfilename :**设置快照的文件名,默认是 dump.rdb

**⑥dir:**设置快照文件的存放路径,这个配置项一定是个目录,而不能是文件名。

3.2.5 RDB 的优势和劣势

①、优势

(1)RDB文件紧凑,全量备份,非常适合用于进行备份和灾难恢复。

(2)生成RDB文件的时候,redis主进程会fork()一个子进程来处理所有保存工作,主进程不需要进行任何磁盘IO操作。

(3)RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快。

②、劣势

当进行快照持久化时,会开启一个子进程专门负责快照持久化,子进程会拥有父进程的内存数据,父进程修改内存子进程不会反应出来,所以在快照持久化期间修改的数据不会被保存,可能丢失数据

3.3 AOF

全量备份总是耗时的,有时候我们提供一种更加高效的方式AOF,工作机制很简单,redis会将每一个收到的写命令都通过write函数追加到文件中。通俗的理解就是日志记录

3.3.1 持久化原理

他的原理看下面这张图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XjLov8p4-1588592281727)(https://pics3.baidu.com/feed/32fa828ba61ea8d3c2502e396b1b3848251f58b0.jpeg?token=394597ccd73bd15778c518b5c5be6998&s=2D62E7169D305F8A847546E20200B036)]

每当有一个写命令过来时,就直接保存在我们的AOF文件中。

3.3.2 文件重写原理

AOF的方式也同时带来了另一个问题。持久化文件会变的越来越大。为了压缩aof的持久化文件。redis提供了bgrewriteaof命令。将内存中的数据以命令的方式保存到临时文件中,同时会fork出一条新进程来将文件重写

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DvUHjZnv-1588592281729)(https://pics7.baidu.com/feed/09fa513d269759ee28454d2c4cea4b106c22dfd3.jpeg?token=86eda46b8bcd54a7a0e7d8a37d87bee8&s=EDB2A4579D317B824660D4DF0200E036)]

重写aof文件的操作,并没有读取旧的aof文件,而是将整个内存中的数据库内容用命令的方式重写了一个新的aof文件,这点和快照有点类似。

3.3.3 AOF也有三种触发机制

(1)always:同步持久化 每次发生数据变更会被立即记录到磁盘 性能较差但数据完整性比较好

(2)everysec:异步操作,每秒记录 如果一秒内宕机,有数据丢失

(3)no:从不同步

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WR4kfELp-1588592281736)(https://pics5.baidu.com/feed/b17eca8065380cd7df69859ba056a5325982816c.jpeg?token=a060f459d81c409c3d6c7208d2118888&s=AF4AA5574ED85CC841D04BE60300A036)]

3.3.4 优点

(1)AOF可以更好的保护数据不丢失,一般AOF会每隔1秒,通过一个后台线程执行一次fsync操作,最多丢失1秒钟的数据。

(2)AOF日志文件没有任何磁盘寻址的开销,写入性能非常高,文件不容易破损。

(3)AOF日志文件即使过大的时候,出现后台重写操作,也不会影响客户端的读写。

(4)AOF日志文件的命令通过可读的方式进行记录,这个特性非常适合做灾难性的误删除的紧急恢复。比如某人不小心用flushall命令清空了所有数据,只要这个时候后台rewrite还没有发生,那么就可以立即拷贝AOF文件,将最后一条flushall命令给删了,然后再将该AOF文件放回去,就可以通过恢复机制,自动恢复所有数据

3.3.5 缺点

(1)对于同一份数据来说,AOF日志文件通常比RDB数据快照文件更大

(2)AOF开启后,支持的写QPS会比RDB支持的写QPS低因为AOF一般会配置成每秒fsync一次日志文件,当然,每秒一次fsync,性能也还是很高的

(3)以前AOF发生过bug,就是通过AOF记录的日志,进行数据恢复的时候,没有恢复一模一样的数据出来。

3.4 RDB和AOF到底该如何选择

选择的话,两者加一起才更好。因为两个持久化机制你明白了,剩下的就是看自己的需求了,需求不同选择的也不一定,但是通常都是结合使用。有一张图可供总结:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oOkAheuS-1588592281738)(https://pics5.baidu.com/feed/8326cffc1e178a82c532308ef2117b8ba977e8ae.jpeg?token=fea28817e45f0e091b5be3854d856fbb&s=BD48B55F1C784C095E61DCEB0300D036)]

四、数据淘汰策略

减少内存紧张的情况,由此获取更为稳健的服务

4.1 lru、lfu(redis 4新增)、random、ttl

LRU:

(1)volatile-lru:从设置了过期时间的数据集中,选择最近最久未使用的数据释放;

(2)allkeys-lru:从数据集中(包括设置过期时间以及未设置过期时间的数据集中),选择最近最久未使用的数据释放;

random:

(3)volatile-random:从设置了过期时间的数据集中,随机选择一个数据进行释放;

(4)allkeys-random:从数据集中随机选择一个数据进行释放

LFU:

(5)volatile-lfu:从设置过期时间的数据集挑选使用频率最低的数据淘汰。

(6)allkeys-lfu:从数据集中挑选使用频率最低的数据淘汰。

(7)volatile-ttl:从设置了过期时间的数据集中,选择马上就要过期的数据进行释放操作;

(8)no-eviction(默认策略):不删除任意数据(但redis还会根据引用计数器进行释放),这时如果内存不够时,会直接返回错误。采用no-enviction策略可以保证数据不被丢失

4.2 淘汰机制的实现

删除失效主键

既然是淘汰,那就需要把这些数据给删除,然后保存新的。Redis 删除失效主键的方法主要有两种:

(1)消极方法(passive way),在主键被访问时如果发现它已经失效,那么就删除它。redis在实现GET、MGET、HGET、LRANGE等所有涉及到读取数据的命令时都会调用 expireIfNeeded,它存在的意义就是在读取数据之前先检查一下它有没有失效,如果失效了就删除它。

(2)积极方法(active way),周期性地探测,发现失效就删除。消极方法的缺点是,如果key 迟迟不被访问,就会占用很多内存空间,所以才有积极方式。

(3)主动删除:当内存超过maxmemory限定时,触发主动清理策略,该策略由启动参数的配置决定

4.3 淘汰数据的量

既然是淘汰数据,那么淘汰多少合适呢?

为了避免频繁的触发淘汰策略,每次会淘汰掉一批数据,淘汰的数据的大小其实是和置换的大小来确定的,如果置换的数据量大,淘汰的肯定也多。

4.4 置换策略是如何工作

理解置换策略的执行方式是非常重要的,比如:

(1)客户端执行一条新命令,导致数据库需要增加数据(比如set key value)

(2)Redis会检查内存使用,如果内存使用超过maxmemory,就会按照置换策略删除一些key

(3)新的命令执行成功

五、Mutil

5.1 redis事务特性

我并没有把mutil称为事务,我更倾向于称作Redis的多任务命令。

事务需要具备ACID四个特性,redis提供的并不是严格的事务

不保证原子性

若在待执行队列中存在语法性错误,exec提交之后,其他正确命令也会被执行,这是单单的错误命令抛出异常。

Redis 开始事务 multi 命令后,Redis 会为这个事务生成一个队列,每次操作的命令都会按照顺序插入到这个队列中。

通过面试题学Redis--基础篇 _ JavaClub全栈架构师技术笔记

这个队列里面的命令不会被马上执行,直到 exec 命令提交事务,所有队列里面的命令会被一次性,并且排他的进行执行。

但原子性又一个特点就是要么全部成功,要么全部失败,也就是我们传统 DB 里面说的回滚。

通过面试题学Redis--基础篇 _ JavaClub全栈架构师技术笔记

可以发现,就算中间出现了失败,set abc x 这个操作也已经被执行了,并没有进行回滚,从严格的意义上来说 Redis 并不具备原子性

没有隔离级别的概念

开启事务之后的操作全部是在待执行队列中缓存,并没有真正执行,也就不存在事务内部的查询要看到事务即将的更新,事务外部也不知道

5.2 watch操作

语法:watch key
watch类似于乐观锁

如果在watch命令观测一个key之后,开启事务后修改该key.这个时候如果有其它连接修改了key,则会导致事务执行失败,在这个事务的其他操作也是失败
exec之后,watch命令监控取消

在使用了watch之后可以保证一定的原子性和数据安全

六、Redis 发布订阅

简介

  • Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。
  • Redis 客户端可以订阅任意数量的频道。

示例

下图展示了频道 channel1 , 以及订阅这个频道的三个客户端 —— client2 、 client5 和 client1 之间的关系:

通过面试题学Redis--基础篇 _ JavaClub全栈架构师技术笔记

当有新消息通过 PUBLISH 命令发送给频道 channel1 时,这个消息就会被发送给订阅它的三个客户端:

通过面试题学Redis--基础篇 _ JavaClub全栈架构师技术笔记

应用场景

  • 这一功能最明显的用法就是构建实时消息系统,比如普通的即时聊天,群聊等功能
  • 在一个博客网站中,有100个粉丝订阅了你,当你发布新文章,就可以推送消息给粉丝们。

七、面试题

7.1 Redis为什么是单线程的?

官方:因为Redis是基于内存的操作,CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存的大小或者网络带宽。既然单线程容易实现,而且CPU不会成为瓶颈,那就顺理成章地采用单线程的方案了(毕竟采用多线程会有很多麻烦!)。

redis 核心就是 如果我的数据全都在内存里,我单线程的去操作 就是效率最高的,为什么呢,因为多线程的本质就是 CPU 模拟出来多个线程的情况,这种模拟出来的情况就有一个代价,就是上下文的切换,对于一个内存的系统来说,它没有上下文的切换就是效率最高的。redis 用 单个CPU 绑定一块内存的数据,然后针对这块内存的数据进行多次读写的时候,都是在一个CPU上完成的,所以它是单线程处理这个事。在内存的情况下,这个方案就是最佳方案

7.2 Redis为什么这么快

  1. 完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1)

  2. 数据结构简单,对数据操作也简单,Redis中的数据结构是专门进行设计的;

  3. 采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;

  4. 使用多路I/O复用模型,非阻塞IO,采用多路 I/O 复用技术可以让单个线程高效的处理多个连接请求;

  5. 使用底层模型不同,Redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求;

7.3 Redis应用场景

因为Redis的性能十分优越,可以支持每秒十几万次的读/写操作,并且它还支持持久化、集群部署、分布式、主从同步等,Redis在高并发的场景下数据的安全和一致性,所以它经常用于这些场景:

  1. 经常要被查询,但是CUD操作频率低的数据;比如数据字典,确定了之后很少被修改,是可以放到缓存中的;还有热点数据,查询极为频繁的数据,放到Redis中可以减少MySQL的压力;
  2. 经常被查询,但是实时性要求不高数据,比如购物网站的热销排行榜,定时统计一次后把统计结果放到Redis中提供查询。
  3. 缓存还可以做数据共享(Session共享),在分布式的架构中,把用户的Session数据放到Redis中。
  4. 高并发场景下的计数器,比如秒杀,把商品库存数量放到Redis中(秒杀的场景会比较复杂,Redis只是其中之一,例如如果请求超过某个数量的时候,多余的请求就会被限流);
  5. 因为Redis对高并发的支持和单线程机制,它也经常用作分布式锁

7.4 Redis和MySql的区别?

  • 类型上

    从类型上来说,mysql是关系型数据库,redis是缓存数据库

  • 作用上

    mysql用于持久化的存储数据到硬盘,功能强大,但是速度较慢

    redis用于存储使用较为频繁的数据到缓存中,读取速度快

  • 需求上

    mysql和redis因为需求的不同,一般都是配合使用。

7.5 redis为什么不能代替mysql?

在使用一项技术的时候,不是看它能不能,而是要看它适合不适合;而在大部分场景下,Redis是无法替代MySQL的。

  • MySQL是关系型数据库,数据储存在磁盘上,数据的格式是我们熟知的二维表格的样式。关系型数据库具有很多强大的功能;大部分都支持SQL语句查询,对事务也有很好的支持。
  • Redis被称作非关系型数据库,属于内存数据库,数据都储存在内存中(Redis有RDB持久化策略),Redis支持的数据类型也比较多,比如字符串,HASH,List等。
  • MySQL和Redis没有竞争的关系,通常当并发访问量比较大的时候,特别是读操作很多,架构中可以引入Redis,帮助提升架构的整体性能,减少Mysql(或其他关系型数据库)的压力;
  • 不是MySQL or Redis;而是MySQL + Redis ;

7.6 Redis和memcached有什么区别?

  1. 性能
    都比较高,性能对我们来说应该都不是瓶颈
    总体来讲,TPS方面redis和memcache差不多

  2. 操作的便利性
    memcache数据结构单一
    redis丰富一些,数据操作方面,redis更好一些

  3. 可靠性(持久化)

    对于数据持久化和数据恢复,

    redis支持(快照、AOF):依赖快照进行持久化,aof增强了可靠性的同时,对性能有所影响

    memcache不支持,通常用在做缓存,提升性能;

  4. 数据一致性(事务支持)

    Memcache 在并发场景下,用cas保证一致性

    redis事务支持比较弱,只能保证事务中的每个操作连续执行

7.7 redis为什么不能存大量的数据呢?

作者:hofe
来源链接:https://www.cnblogs.com/Josepy/p/12846104.html

版权声明:
1、JavaClub(https://www.javaclub.cn)以学习交流为目的,由作者投稿、网友推荐和小编整理收藏优秀的IT技术及相关内容,包括但不限于文字、图片、音频、视频、软件、程序等,其均来自互联网,本站不享有版权,版权归原作者所有。

2、本站提供的内容仅用于个人学习、研究或欣赏,以及其他非商业性或非盈利性用途,但同时应遵守著作权法及其他相关法律的规定,不得侵犯相关权利人及本网站的合法权利。
3、本网站内容原作者如不愿意在本网站刊登内容,请及时通知本站(javaclubcn@163.com),我们将第一时间核实后及时予以删除。


本文链接:https://www.javaclub.cn/server/42773.html

标签: Redis面试题
分享给朋友:

“通过面试题学Redis--基础篇” 的相关文章

35个Redis面试题

35个Redis面试题

      1.什么是redis? Redis 是一个基于内存的高性能key-value数据库。 2.Reids的特点   Redis本质上是一个Key-Value类型的内存数据库,很像memc...

Redis的常见面试题(全)

Redis的常见面试题(全)

目录 前言 1. 了解一下你认识的Redis 2. Redis的适用场景 3. Redis的线程机制 4. Redis单线程,如何提高多核cpu利用率 5. Redis持久化机制...

转:2018最全Redis面试题整理

转:2018最全Redis面试题整理

Java面试----2018最全Redis面试题整理 1、什么是Redis? 答:Redis全称为:Remote Dictionary Server(远程数据服务),是一个基于内存的高性能key-value数据库。 2、Redis的数据类型? 答:Red...

三、后端开发2021面试题总结JAVA_JVM_Redis_ZooKeeper

2021年初面试总结:JAVA_JVM_Redis_ZooKeeper 以下问题是对本人在2021.01.27到2021.02.08这段时间面试过的一些互联网、传统型、初创型公司的技术问题整理。建议小伙伴们在面试前一定不要着急,先静下心来让把下面的面试题或者自...

redis面试题及答案

1、什么是Redis? Redis本质上是一个Key-Value类型的内存数据库,很像memcached,整个数据库统统加载在内存当中进行操作,定期通过异步操作把数据库数据flush到硬盘上进行保存。因为是纯内存操作,Redis的性能非常出色,每秒...

redis 面试题

redis 面试题

redis面试题   redis和memcached比较? redis中数据库默认是多少个db 及作用? python操作redis的模块? 如果redis中的某个列表...

深入理解Redis,以Redis面试题来复习;Java程序员面试必备宝典

深入理解Redis,以Redis面试题来复习;Java程序员面试必备宝典

本文转载自:深入理解Redis,以Redis面试题来复习;Java程序员面试必备宝典 前言 没得前言,因为懒,都是面试题、书籍和学习资料,自己看! 一. Redis面试真题 1. 什么是 Redis?简述它的优缺点?...

Redis常见面试题

Redis常见面试题

介绍:Redis 是一个开源的使用 ANSI C 语言编写、遵守 BSD 协议、支持网络、可基于内存亦可持久化的日志型、Key-Value 数据库,并提供多种语言的 API的非关系型数据库。 传统数据库遵循 ACID 规则。而 Nosql(Not Only SQL 的缩写,是对不...

Redis面试题 总结

Redis面试题 总结

文章目录 概述 什么是Redis Redis有哪些优缺点 为什么要用 Redis /为什么要用缓存...

面试题-Redis篇

面试题-Redis篇

WhyRedis ​ 速度快,完全基于内存,使用C语言实现,网络层使用epoll解决高并发问题,单线程模型避免了不必要的上下文切换及竞争条件;   GuavaCache Tair...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。