目录
面试:如何从海量的key里面查询出具有某一固定前缀的key?
面试:假如说该Redis正在给线上提供服务,使用上面的方法会有什么问题?
AOF(Append Only File)持久化:保存写状态
面试:随着写操作的不断增加,AOF文件的体积会越来越大,该咋解决?
-
缓存中间件——Memcache和Redis的区别
- Memcache没有系统学习过,但我了解二者的一些区别
- Memcache
- 支持简单的数据类型
- 不支持数据持久化存储
- 一旦服务器宕机了数据就丢失了
- 不支持主从同步
- 不支持分片
- Redis
- 数据类型丰富
- 支持数据持久化存储
- 支持分片
- 支持主从
-
面试:为什么Redis这么快?
- Redis支持每秒10w+的查询次数
- 完全基于内存的,绝大部分请求都是纯粹的内存操作,执行效率高
- 数据结构比较简单,都是键值对,对数据的操作也简单,性能相比关系型数据库要高出不止一个量级
- Redis采用单线程,单线程也可以处理多并发请求
- 准确的说是主程序是单线程的
- 对于客户端的所有读写请求都是按照顺序先后处理的,这就不会出现因为并发引起的问题,也不会出现锁竞争,所以效率更高
- 使用多路I/O复用模型,即非阻塞io
- 等待用户输入或者输出操作一般都是阻塞的,所以就有可能导致结果无法及时返回
- 最重要的的函数调用就是Select系统调用
- 可以同时监控多可文件描述符的可读可写情况
- 监听的任务交给Select之后,程序就又有时间做别的事了而不被阻塞了
- 也有别的系统调用函数:epoll/kqueue/evport
- 采用哪个:因地制宜,优先采用系统提供的(时间复杂度为O(1))
- 最后才是Select系统调用保底(时间复杂度为O(n))
-
常用的数据类型
-
String
- 最基本的数据类型
- 二进制安全,可以存储任意类型的数据(图片、视频)
- 最大可存储512MB
- 底层依赖简单动态字符串sds
-
Hash
- 键值对类型的,有属性、有值,特别适合存储对象
- 插入hset
- 获取hget
-
List
- 按照String元素插入顺序排序
- 可以重复
- 插入:lpush key value、rpush key value
- 获取:lrange key start end(end为-1代表获取所有)
- 可以实现最新消息排行榜功能
-
Set
- 存储String类型的,不可重复
- 使用哈希表实现
- Set提供了求交集、并集、差集的操作,可以非常方便的实现共同喜好共同关注等功能
-
Sorted Set
- 每个元素都会关联一个double类型的分数,根据这个分数从小到大排序
- 分数可以重复
- value不可以重复
-
除了这5个常用的还有俩不常用的
- 用于基数统计的HyperLogLog
- 可以用于存储地理信息的Geo
-
-
底层数据类型基础(了解)
-
面试:如何从海量的key里面查询出具有某一固定前缀的key?
- 可以使用keys指令扫描出符合指定模式的key列表
- 这种方法有缺陷,因为它会一次性把所有符合条件的key全部打印出来,比较耗时,会使服务卡顿
-
面试:假如说该Redis正在给线上提供服务,使用上面的方法会有什么问题?
- 可以使用scan cursor [MATCH pattern] [COUNT count],是一种非阻塞的扫描
- 依次返回少量的key
- 基于游标的迭代器,每一次扫描都会基于上一次扫描返回回来的游标参数作为扫描的开始
- 0代表是迭代的开始,返回值为0就代表迭代的结束
- 每次返回的数量不一定是count个,只是一个接近count的大约数
- 返回的数据格式
- 第一行是下一次迭代的游标值
- 下面的都是符合条件的key
- 返回的游标值不一定是递增的,所以有可能会产生获取到重复key的问题
- 解决办法:可以将获取到的key存储到hashSet中,因为set集合是有去重功能的
- Java使用的Jedis工具包操作Redis的
- 可以使用keys指令扫描出符合指定模式的key列表
-
面试:如何实现Redis分布式锁?
- 不同的主机访问同一资源时往往需要互斥的去访问
- 分布式锁需要解决的问题
- 互斥性
- 我访问的时候你不能访问
- 安全性
- 我拿到了锁,只能由我来释放锁,不能由别的进程来释放锁
- 死锁
- 互斥性
- 前期我们使用的是setnx key value指令
- 如果key不存在就创建,返回1,否则创建失败返回0
- 面试:如何解决setnx指令长期有效的问题呢?
- 可以给key设置存活时间
- 使用指令:expire key seconds
- 到时间自动删除key
- 但是使用setnx和expire指令组合的方式就破坏了事务的原子性,所以一般也不会采用
- 从Redis2.6.12开始,使用set指令的同时也可以给key设置存活时间的了
- 具体的指令是:set key valye [ex seconds] [px milliseconds] [nx|xx]
- ex:单位为秒
- px:单位为毫秒
- nx:键不存在是才操作
- xx:键存在时才操作
- 成功返回OK,失败返回nil
- 具体的指令是:set key valye [ex seconds] [px milliseconds] [nx|xx]
-
面试:有大量的key同时过期怎么办?
- 过期的时间过于集中是有可能导致Redis服务器卡顿的
- 解决方法很简单,设置过期时间的时候再加上一个随机时间就可以了,这样就可以把过期时间均匀的分开
-
面试:如何使用Redis做异步消息队列?
- 使用List作为消息队列,rpush生产消息,lpop消费消息
- 但是,lpop是不会等到队列里面有值才会去消费的,如果队列里面没有值了那程序还是会不断的去执行lpop
- 也可以在程序中加入sleep机制,让程序等一会再去消费
- 当然我们很少使用上面的方法,更多的是使用blpop指令(b应该是block的意思)
- 指令是:blpop key timeout
- 当队列中没有消息的时候他会阻塞住,等待一个timeout时间之后就超时
-
面试:能不能生产者生产一个消息可以让多个消费者同时消费呢?
- 可以使用Redis的pub/sub主题订阅者模式
- 发送者(pub)发送消息,订阅者(sub)接收消息
- 具体实现
- 订阅者使用指令 subscribe myTopic 来订阅主题
- 发送者使用publish myTopic "Hello"来向“myTopic”主题中发送消息,所有订阅了此主题的订阅者都可以收到消息了
- 但是这种方式同样是有缺陷的
- 消息的发送时无状态的,无法保证可达
- 如果订阅者在发送者发送消息的时候掉线了,那在上线之后就无法收到发送过的消息了
- 可以使用专业的消息队列卡夫卡来解决
- 使用List作为消息队列,rpush生产消息,lpop消费消息
-
面试:Redis如何做持久化的?
- Redis主要提供了三种持久化的方案,将内存中的数据持久化保存到磁盘中
-
RDB(快照)持久化
- 它保存的是某个时间点的全部数据
- 控制保存的频率是修改的redis.config配置文件里面的save设置
- save 900 1 代表的是:在900秒之内,如果出现了一次写操作,那就会触发bgsave
- 属性stop-writes-on-bgsave-error意思是:如果保存时出错,主进程就会block住客户端的写操作
- 属性rdbcompression意思是:取值为yes时代表存储rdb文件时开启压缩,打开之后会占用cpu资源。关闭的方法是,在属性save下面添加save "" 即可
- rdb文件的创建与载入
- 创建
- 可以使用save和bgsave两个保存指令
- save会阻塞主进程,知道rdb文件创建完毕
- bgsave会fork除一个子进程来创建rdb文件,不会阻塞服务器进程
- 使用lastsave可以查看上一次保存的时间
- 载入
- 启动时如果检测地rdb文件就会自动加载
- 创建
- 自动触发RDB持久化的方式
- 由redis.config里的save m n自动触发
- 执行debug reload是触发
- 执行shutdown且没有开启AOF持久化时触发
- bgsave的原理
- bgsave使用的是写实复制
- linux中fork出来的子进程是和父进程使用同一个资源的,只有当某个进程想要修改临界资源的时候系统才会复制一份临界资源的副本给调用者
- RDB持久化的缺点
- 每次持久化都是把所有的数据全部保存,而不是增量存储,如果数据量太大的话就会严重影响Redis的性能
- RDB保存的是某个时间点的全部数据,在这个时间点之后的数据是没有保存的,所以还是有可能出现数据丢失的情况
-
AOF(Append Only File)持久化:保存写状态
- 是一种增量保存
- 他是记录下客户端对Redis的所有写操作
- 以Append的形式追加到aof文件中
- 默认是关闭的,打开:修改redis.config配置文件里面的appendonly属性为yes
- appendfsync属性
- always:一旦缓存池的内容变化就及时保存
- everysec:每秒保存一次,一般都是用这个
- no:有系统决定,一般都是缓存满的时候保存
-
面试:随着写操作的不断增加,AOF文件的体积会越来越大,该咋解决?
- Redis支持重写aof文件
- 先往aof文件中写入全量数据,再追加增量
- 具体的操作是
- 调用fork创建一个子进程
- 子进程把内存中的全量数据保存到新的aof文件中,这个过程并不会依赖旧的aof文件
- 同时主进程也会持续把变动写到内存和原来的aof文件中,这样就确保了万一重写aof文件失败了也不会丢失数据
- 当子进程把数据全部保存到新的aof文件中后,主进程就会把内存中保存的所有的变动在追加到新的aof文件中
- 用新的aof文件代替旧的aof文件,重写aof文件就完成了
- 这个重写操作可以手动触发,也可以自动触发,就像之前的bgsave一样
-
面试:RDB和AOF两种文件共存的情况下如何进行数据库恢复
- 如果AOF文件存在,就直接加在AOF文件,如果不存在才会去加载RDB文件
-
面试:两种持久化方式的优缺点
- RDB
- 全量保存,数据小,恢复快
- 无法保存快照后面的数据,有可能造成数据丢失
- AOF
- 保存增量数据,数据不易丢失
- 文件体积大,恢复时间长
- RDB
-
RDB-AOF混合持久化方式
- Redis4.0之后推出的,并默认就是用这种持久化方式
- 先以RDB存储全量数据,再用AOF保存增量数据
-
面试:Redis集群的主从同步机制
- 面试:使用pipeline的好处
- Pipeline和Linux的管道类型
- Redis基于请求/响应模型,单个请求处理需要一一作答
- Pipeline批量执行指令,节约IO往返时间
- 允许客户端一次发送多条命令,而不需要等待上一条命令的结果
- 有顺序以来的指令建议分批发送
-
Redis集群的同步机制
- Redis使用的是一个主机多个从机的机制
- 主机负责客户端所有的写操作,从机负责客户端的读操作
- 定期的备份操作也是有一个从机单独完成的
- Redis有两种方式进行主从同步
- 全量同步
- 从机向主机发送sync指令
- 主机开启一个子进程,执行bgsave指令
- 主机在保存数据快照的同时也在记录对数据库的写操作
- 数据保存好之后主机把文件发给从机
- 从机将收到的数据加载到内存
- 主机把这期间的增量数据也同步给从机
- 全量同步完成
- 增量同步
- 主机接收到客户端的操作指令,判断是否需要同步到传播到从机
- 主机把指令写进缓存中
- 然后再把缓存中的指令发送给从机
- 全量同步
- Redis Sentinel
- Redis 哨兵
- 在上面的两种主从同步机制中,如果主机宕机了,那么Redis就无法响应客户端的写操作了,不具有高可用性
- 可以解决主机宕机之后的主从切换问题
- 他可以不断的检查主服务器和从服务器的运行状态
- 当某个服务器出现问题的时候,Sentinel可以通过API向管理员或其他应用进程发送故障通知
- 当主机出现问题的时候,哨兵能够把一台从机升级为主机,并使用流言协议让其他从机能够识别新的主机
- 流言协议(Gossip)
- 集群中的节点都是相互连接的,有的连接一个,有的连接两个。当集群中出现了一个新主机,新主机就会把他的信息发送给他相邻的结点,他相邻的节点又会继续把信息发给相邻的结点,这样很快集群中的结点就会知道新添加的主机
- 流言协议(Gossip)
- 面试:使用pipeline的好处
-
面试:Redis集群原理
- 数据分片:将数据按照某种规则划分,然后分散存储到多个节点上
- 通常使用一次性哈希算法
- 将key的哈希值对2^32次方取模,得到的值就会分布在一个0~2^23-1的哈希环上面
- 下一步就是把各个Redis服务器进行哈希计算,使用上面的方法,让服务器也分布在哈希环上面
- 数据在哈希环上顺时针往下找距离它最近的一个服务器,找到之后就把数据放到这个服务器上
- 这种办法当某个服务器宕机后,需要变更的仅仅是宕机的服务器和它的服务器之间的数据,这样就具有了高可用性
-
面试:哈希环的数据倾斜问题
- 有可能Redis服务器在哈希环上面分布的不均匀,导致数据在服务器上分布也不均匀,有的服务器数据多,有的服务器数据少
- 解决办法:引入虚拟结点
- 比如说现在有个节点A,那么他会在创建虚拟结点A1、A2、A3,这些虚拟节点上面也会分布数据,但是他们都实际指向真是的节点A