一、redis和memcache的比较
1. memcache
代码层面类似hash
支持简单数据类型
不支持数据持久化存储
不支持主从
不支持分片
2 redis
数据类型丰富
支持数据磁盘持久化存储
支持主从
在3.0之后支持数据分片
二、redis相关
1. 为什么redis能这么快?qps:10W+(qps:每秒内查询的次数)
- 完全基于内存,绝大部分请求是纯粹的内存操作,执行效率高
- 数据结构简单,对数据操作也简单
- 主线程采用单线程,单线程也能处理高并发请求,想多核也可以启动多实例
- 使用多路I/O复用模型,非阻塞IO
2. redis采用的IO多路复用函数:epoll/kqueue/evport/select?
- 因地制宜
- 优先选择时间复杂度为O(1)的IO多路复用函数作为底层实现
- 以时间复杂度为O(n)的select作为保底
- 基于react设计模式监听IO事件
- redis服务采用react设计模式来实现文件事件处理器的,文件事件处理器使用IO多路复用模块同时监听多个fd,当accept、read、write和close文件事件产生时,文件事件处理器就会回调fd绑定的文件事件处理器,虽然整个文件事件处理器是在单线程上运行的,但是通过IO多路复用模块的引用实现了同时对多个fd读写的监控,提高了网络通信模型的性能,同时也可以保证整个redis服务实现的简单。
3. 基本使用类型
-
String:最基本的数据类型,二进制安全
底层保存字符串对象的结构:|
1 2 3 4 5 6 7 8| ``` struct sdshdr{ //buf中已占用空间的长度 int len; //buf中剩余可用空间的长度 int free; //数据空间,数据存储在这个buf[]中 char buf[]; }| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------ | -
Hash:String元素组成的字典,适合用于存储对象
-
List:列表,按照String元素插入顺序排序
-
Set:String元素组成的无序集合,通过哈希表来实现,不允许重复
-
Sorted Set:通过分数来为集合中的成员进行从小到大排序
-
用于计数的HyperLogLog
-
用于支持存储地理位置信息的Geo
4. 底层数据类型基础
- 简单动态字符串
- 链表
- 字典
- 跳跃表
- 整数集合
- 压缩列表
- 对象
三、相关面试题
1. 从海量key中查询出某一固定前缀的key
- keys指令对线上业务的影响:
KEYS pattern:查找出所有符合给定模式pattern的keyKEYS指令一次性返回所有匹配的key
键的数量过大会使得服务卡顿 - scan指令:
无阻塞地提取。会返回两个值:游标和结果集SCAN cursor [MATCH pattern][COUNT count]``scan指令时一个基于游标的迭代器,需要基于上一次的游标延续之前的迭代过程
以0作为游标开始一次新的迭代,直到命令返回游标0完成一次遍历。从0到0的过程为一次完整的遍历
不保证每次执行都返回某个给定数量的元素,支持模糊查询
一次返回的数量不可控,只是大概率符合count参数
2. 如何实现分布式锁?
分布式锁是控制分布式系统或不用系统之间共同访问共享资源的一种锁的实现,不同系统之间需要互斥来保证共享数据的线程安全。
2.1 分布式锁需要解决的问题:
- 互斥性:任一时刻,只能允许有一个客户端获取锁
- 安全性:锁只能由持有锁的客户端删除,不能由其他没锁的客户端删除
- 死锁:获得锁的客户端由于某些原因宕机了而不能释放锁,其他客户端不能获取锁而产生死锁,此时需要有机制来避免该问题的发生
- 容错:当redis某些节点宕机的时候,客户端仍然能够获取锁或释放锁
2.2 实现
SETNX key value:如果key不存在,则创建并赋值
时间复杂度:O(1)
返回值:设置成功-1;设置失败-0
2.3 如何解决SETNX长期有效的问题?
-
EXPIRE key seconds,为key设定给定的过期时间
这种方式的缺点:不能保证原子性 -
在2.6.12之后:
SET key value [EX seconds][PX miliseconds][NX|XX]EX seconds:设置键的过期时间为second秒
PX miliseconds:设置键的过期时间为miliseconds毫秒
NX:只有在键不存在时,才对键进行设置操作
XX:只有在键已经存在时,才对键进行设置操作
SET操作成功时,返回Ok,否则返回nil
2.4 大量的key同时过期的注意事项
集中过期,由于清除大量的key会很耗时,会出现短暂的卡顿现象
解决方案:在设置key的过期时间的时候,给每个时间加上随机值,使得过期时间分散一些
3. 如何使用redis做异步队列?
- 使用list作为队列,Rpush生产消息,Lpop消费消息
缺点:没有等待队列里有值就直接消费
弥补:可以通过在应用层引入sleep机制去调用lpop重试
BLPOP key timeout:阻塞直到队列有消息或者超时
缺点:只能供一个消费者消费
如何让生产者消费,多个消费者消费?
- pub/sub:主题订阅者模式
发送者(pub)发送消息,订阅者(sub)接收消息
订阅者可以订阅任意数量的频道(topic) - 多个客户端运行:
subscribe myTopic命令 - 在一个客户端中:
public myTopic "Hello",那么在其他订阅该topic的消费者能够接收到这个消息
缺点:消息的发布是无状态的,无法保证可达。某个客户端在获取的时候宕机,重启之后是接收不到这个消息的,如果要解决这个问题,需要借助消息队列,即kafka等。
4. redis如何做持久化?
4.1 RDB(快照)持久化:保存某个时间点的全量数据快照
在redis.conf配置文件中,默认:
| 1 2 3 4 5 6 7 8 9 10 11 12 | ```
900秒之内如果有一条写入指令则执行一次备份 save 900 1 # 300秒之内如果有十条写入指令则执行一次备份 save 300 10 # 60秒之内如果有十万条写入指令则执行一次备份 save 60 10000 # 设置成yes的时候表示当备份进程出错的时候主进程就停止接收写入操作了,为了保护数据持久化的一致性问题 stop-writes-on-basave-error yes # rdb持久化的时候,对持久化备份文件进行压缩,建议设置为no,因为redis是数据cpu密集型服务,开启压缩会带来更多的cpu消耗 rdbcompression yes # 禁用rdb配置 save ""
| ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
在src目录下,会生成rdb后缀的文件(dump.rdb),即备份文件,是二进制文件
**rdb文件可以通过两个命令来生成:** `SAVE`:阻塞redis的服务器进程,直到RDB文件被创建完毕(很少被使用)`BGSAVE`:fork出一个子进程来创建RDB文件,不会阻塞服务器进程。主进程会继续接收客户端的操作命令
**自动化触发RDB持久化方式**\
根据redis.conf配置中的save m n定时触发(用的是BGSAVE)\
主从复制时,主节点自动触发\
执行Debug Reload\
执行Shutdown且没有开启AOF持久化
**BGSAVE原理**\
系统调用fork():创建进程,实现了copy-on-write
**copy-on-write**\
如果有多个调用者同时要求相同资源(如内存或磁盘上的数据存储),他们会共同获取相同的指针指向相同的资源,直到某个调用者试图修改资源的内容时,系统才会真正复制一份专属副本给该调用者,而其他调用者所见到的最初的资源仍然保持不变。
**RDB缺点**
* 内存数据的全量同步,数据量大会由于IO而严重影响性能
* 可能会因为redis挂掉而丢失当前至最近一次快照期间的数据
#### [](#4-2-AOF-append-only-file-%E6%8C%81%E4%B9%85%E5%8C%96%EF%BC%9A%E4%BF%9D%E5%AD%98%E5%86%99%E7%8A%B6%E6%80%81 "4.2 AOF(append-only-file)持久化:保存写状态")4.2 AOF(append-only-file)持久化:保存写状态
记录下除了查询以外的所有变更数据库状态的指令,以append的形式追加保存到AOF文件中(增量),默认是关闭的
| ```
1 2 3 4 5 6
``` | ```
appendonly no appendfilename "appendonly.conf" # always:一旦缓冲区的内容发生变化,则总是及时地将缓冲区的内容写入到aof当中 # everysec:将缓冲区的内容每隔一秒写入到aof中(推荐且默认,速度比较快,安全性也不错) # no:写入aof的操作交给操作系统去决定,一般而言操作系统会等待缓冲区被填满才同步 appendfsync everysec
``` |
| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
导致的问题:随着时间的推移,数据量越来越大,AOF文件也很大,怎么办?
**日志重写解决AOF文件大小不断增大的问题**
原理如下:
* 调用fork(),创建一个子进程
* 子进程把新的AOF写到一个临时文件中,新的AOF的重写直接将当前内存的数据生成对应的命令,不依赖原来的AOF文件
* 主进程持续将新的变动同时写到内存和原来的AOF中
* 主进程获取子进程重写AOF的完成信号之后,往新的AOF同步增量变动
* 使用新的AOF文件替换掉旧的AOF文件
**BGOVERWRITEAOF指令即可触发AOF的重写,也可以让redis自动触发**
#### [](#4-3-RDB%E5%92%8CAOF%E7%9A%84%E4%BC%98%E7%BC%BA%E7%82%B9 "4.3 RDB和AOF的优缺点")4.3 RDB和AOF的优缺点
RDB优点:全量数据快照,文件小,恢复快\
RDB缺点:无法保存最近一次快照之后的数据\
AOF优点:可读性高,适合保存增量数据,数据不易丢失\
AOF缺点:文件体积大,恢复时间长
#### [](#4-4-Redis4-0%E4%B9%8B%E5%90%8E%E6%8F%90%E5%87%BA%E4%BA%86%E7%BB%93%E5%90%88%E4%B8%A4%E7%A7%8D%E7%9A%84RDB-AOF%E6%B7%B7%E5%90%88%E6%8C%81%E4%B9%85%E5%8C%96%E6%96%B9%E5%BC%8F%EF%BC%8C%E5%B9%B6%E4%B8%94%E4%BD%9C%E4%B8%BA%E9%BB%98%E8%AE%A4%E7%9A%84%E9%85%8D%E7%BD%AE%E6%9D%A5%E4%BD%BF%E7%94%A8 "4.4 Redis4.0之后提出了结合两种的RDB-AOF混合持久化方式,并且作为默认的配置来使用")4.4 Redis4.0之后提出了结合两种的RDB-AOF混合持久化方式,并且作为默认的配置来使用
* 子进程在做AOF重写时,会通过管道从父进程读取增量数据并缓存下来,在以RDB格式保存全量数据时,也会从管道读取数据,同时不会造成管道的阻塞,也就是说AOF文件的前半段是RDB格式的全量数据,而后半段是redis命令格式的增量数据
* BGSAVE做镜像全量持久化,AOF做增量持久化
### [](#5-pipeline "5. pipeline")5. pipeline
pipeline和linux的管道类似\
redis基于请求/响应模型,单个请求处理需要一一应答\
pipeline批量执行指令,节省多次IO往返的时间\
有顺序依赖的指令建议分批发送
## [](#%E5%9B%9B%E3%80%81%E9%9B%86%E7%BE%A4%E7%9B%B8%E5%85%B3 "四、集群相关")四、集群相关
### [](#1-redis%E7%9A%84%E5%90%8C%E6%AD%A5%E6%9C%BA%E5%88%B6%EF%BC%88%E4%B8%BB%E4%BB%8E%E3%80%81%E5%93%A8%E5%85%B5%EF%BC%89 "1. redis的同步机制(主从、哨兵)")1. redis的同步机制(主从、哨兵)
主从同步原理
* 一个master进行写操作,多个salve进行读操作
* 定期的数据备份操作是单独选择一个salve去完成的,这样可以最大程度发挥出redis的性能,为的是让redis支持弱一致性,即最终一致性。我们不需要实时保证master和salve的数据是同步的,但是在过了一段时间之后他们的数据是趋于同步的。
* redis可以使用主从同步、从从同步。
* 第一次同步时主节点进行一次BGSAVE并同时将后续的写操作记录到内存中的buffer中去,待完成后将rdb文件全量同步到从节点中,从节点接收到rdb文件之后将rdb文件加载到内存中,加载完成后再通知主节点将期间修改的操作记录(即增量数据)同步到从节点进行重放,这样子就完成了整个同步的过程。
**全同步过程**\
Slave发送sync命令到master\
master启动一个后台进程,将redis中的数据快照保存到文件中(BGSAVE)\
master将保存数据快照期间接收到的写命令缓存起来\
master完成写文件操作后,将该文件发送给Slave\
使用新的RDB文件替换掉旧的RDB文件\
master将这期间收集的增量写命令发送给slave端。
**增量同步过程**
Master接收到用户的操作指令,判断是否需要传播到Slave(增删改)\
将操作记录追加到AOF文件中\
将操作传播到其他slave中:\
\- 1.对齐主从库。确保从数据库是该操作所对应的数据库\
\- 2.往响应缓存写入指令。将命令和参数按照redis的协议格式写入到相应的缓存中\
将缓存中的数据发送给Slave
**主从模式的弊端**
不具备高可用性,当master宕机之后redis将不能对外提供写入操作
### [](#2-%E5%93%A8%E5%85%B5%E6%A8%A1%E5%BC%8F%EF%BC%9A%E8%A7%A3%E5%86%B3%E4%B8%BB%E4%BB%8E%E5%90%8C%E6%AD%A5Master%E5%AE%95%E6%9C%BA%E5%90%8E%E7%9A%84%E4%B8%BB%E4%BB%8E%E5%88%87%E6%8D%A2%E9%97%AE%E9%A2%98 "2. 哨兵模式:解决主从同步Master宕机后的主从切换问题")2. 哨兵模式:解决主从同步Master宕机后的主从切换问题
监控:sentinel检查主从服务器是否运行正常\
提醒:通过API向管理员或者其他应用程序发送故障通知\
自动故障迁移:主从切换
**流言协议Gossip**
在杂乱无章中寻求一致\
每个节点都随机地与对方通信,最终所有节点的状态达成一致\
种子节点定期随机向其他节点发送节点列表以及需要传播的消息\
不保证信息一定会传递给所有节点,但是最终会趋于一致
### [](#3-%E5%A6%82%E4%BD%95%E4%BB%8E%E6%B5%B7%E9%87%8F%E6%95%B0%E6%8D%AE%E4%B8%AD%E5%BF%AB%E9%80%9F%E6%89%BE%E5%88%B0%E6%89%80%E9%9C%80%E8%A6%81%E7%9A%84%E6%95%B0%E6%8D%AE%EF%BC%9F%EF%BC%88%E9%9B%86%E7%BE%A4%EF%BC%89 "3.如何从海量数据中快速找到所需要的数据?(集群)")3.如何从海量数据中快速找到所需要的数据?(集群)
分片:按照某种规则去划分数据,分散存储在多个节点上\
通过数据分片,来降低单节点服务器的压力\
redisCluster采用无中心结构,每个节点保存数据和整个redis集群的状态,每个节点和其他所有节点连接,每个节点通过Gossip协议传播信息以及发现新的节点。\
常规的按照hash无法实现节点的动态增减,采用`一致性hash`算法来实现
### [](#4-%E4%B8%80%E8%87%B4%E6%80%A7hash%E7%AE%97%E6%B3%95 "4.一致性hash算法")4.一致性hash算法
* 一致性hash:对2^32取模,将hash值空间组织成虚拟的圆环
* 物理机的ip经过hash计算得出hash值,所有节点得到的hash值组织成一个虚拟的圆环
* 当数据需要被存储的时候,按照key的hash值,按照圆环的顺时针方向保存到离他最近的hash对应的节点上,经过这样的方式就可以实现数据的分片
**好处:**\
假设`一个节点C宕机`,此时ABD并不会收到影响,原先需要分配到C节点的数据会重新定位到D中去,在一致性hash算法中,如果一个机器不可用,则受影响的仅仅是此服务器到其环空间前一台之间的服务器,即沿着逆时针方向行走遇到第一台服务器中间的数据,做到最小化有损的服务。如果在系统中`新增了服务器`,则影响的数据是新服务器到其环空间中的前一台服务器之间的数据,其他的数据不会受到影响
**Hash环的数据倾斜问题**
如果节点数量很少的时候,容易在圆环上造成hash分布不均匀的情况,导致一个节点上的数据比其他节点上的数据多出很多的情况。造成数据倾斜,数据倾斜就是大部分的数据分布在同一个节点上。
**引入虚拟节点来解决数据倾斜问题**
* 对每个服务器节点计算多个hash,每个计算结果位置都放置一个节点为该节点的虚拟节点(具体做法可以在主机名或者ip后增加编号来实现),这样来实现圆环的大致均匀。数据算法不变,只是多了一步虚拟节点到真实节点的映射。
* 通常将虚拟节点设置为32甚至更大。
## [](#%E4%BA%94%E3%80%81Redis%E7%9A%84%E4%BA%94%E7%A7%8D%E6%B7%98%E6%B1%B0%E7%AD%96%E7%95%A5 "五、Redis的五种淘汰策略")五、Redis的五种淘汰策略
* `volatile-lru`:从设置了过期时间的数据集中,选择最近最久未使用的数据释放;
* `allkeys-lru`:从数据集中(包括设置过期时间以及未设置过期时间的数据集中),选择最近最久未使用的数据释放;
* `volatile-random`:从设置了过期时间的数据集中,随机选择一个数据进行释放;
* `allkeys-random`:从数据集中(包括了设置过期时间以及未设置过期时间)随机选择一个数据进行入释放;
* `volatile-ttl`:从设置了过期时间的数据集中,选择马上就要过期的数据进行释放操作;
* `noeviction`:不删除任意数据(但redis还会根据引用计数器进行释放),这时如果内存不够时,会直接返回错误。