Redis interview
说说Redis为什么那么快
Redis是C语言开发的基于(BSD开源协议)高性能key-value型的内存数据库, 可以用作数据库、缓存、消息中间件等。 是一种NoSQL(not-only sql),泛指非关系型数据库。
- 性能优秀, 数据存储在内存中, 读写速度很快, 支持10w QPS。
- 单进程单线程, 采用IO多路复用机制。
- 具有丰富的数据类型。
- 支持数据持久化。
- 主从复制, 哨兵 高可用。
- 可作为分布式锁。
- 可作为消息中间件使用,支持发布订阅
说说Redis有那几种数据类型
- string
常用命令: set, get, mget, incr, decr, setex, setnx, append, getrange等
应用场景:常规的key-value缓存应用, 常用的计数:微博数, 粉丝数
- hash
常用命令: hset,hget,hmget, hgetall,hexists,hdel, hvalues, hscan等
应用场景:比如存储用户信息,商品信息等
- list
常用命令: lpush, lpop, rpush, rpop, lrange,lrem, linsert,blpop等
应用场景:关注列表, 粉丝列表, 分页, 消息队列等
- set
常用命令: sadd, scard,sdiff, sinter, smembers,spop, sunion等
应用场景: 比如微博共同的好友, 统计访问网站所有的ip
- sorted set
常用命令: zadd, zrange, zrem, zcard,zscan, zcount等
应用场景: 如排行榜
Redis sorted set内部使用HasMap和跳跃表(skipList)保证数据存储和有序, HashMap放的成员的score映射, 使用跳跃表是可以获得比较高的查询效率, 实现简单
- 位图bitmap
常用命令:setbit, getbit, bitcount
应用场景: 统计用户的登陆天数
- HyperLogLog
常用命令: pfadd,pfcount, pfmerge
- 发布订阅pub/sub
说说Redis雪崩、击穿、穿透
Redis雪崩: 数据未缓存、或者缓存同一时间大面积失效,导致所有请求查询数据库,导致数据库cpu或者cpu负载过高,甚至宕机。
解决方法: 1、给缓存数据过期时间价加随机值或者设置数据永不过期 2、缓存的高可用性, Redis Sentinel 和 Redis Cluster 都实现了高可用Redis击穿: 和Redis雪崩有点像, 缓存击穿是一个key非常热点, 不停的扛着大量请求, 当这个key失效的瞬间, 大并发直接落到数据库上
Redis穿透:缓存和数据库都没有的数据, 用户不断发起请求
解决方法: 1、在接口层增加校验 2、使用布隆过滤器
Redis为什么是单线程的
Redis是进程单线程的模型, 因为Redis完全基于内存操作, cpu不是Redis瓶颈, Redis最有可能的是机器的内存或网络带宽。
Redis单线程为什么这么快
1、Redis完全基于内存, 大部分请求是纯内存操作, 非常迅速, 类型HashMap,查找和操作的时间复杂度是O(1),
2、数据结构简单,对数据操作简单
3、采用单线程,避免不必要的上下文切换和竞争条件, 不用考虑各种锁问题
4、使用多路复用的IO模型,非阻塞IO
Redis的淘汰策略
策略 | 描述 |
---|---|
volatil-lru | 从设置已过期的kv中优先对最少使用(less recently used)的数据淘汰 |
volatil-ttl | 从设置已过期的kv中优先对剩余时间短的数据淘汰 |
volatil-random | 从设置已过期的kv中随机选择数据淘汰 |
allkey-lru | 从所有kv中优先最少使用的淘汰 |
allkey-random | 从所有kv中随机选择数据淘汰 |
noeviction | 不淘汰策略 |
4.0版本后新增以下两种策略:
volatile-lfu:从已设置过期的kv中挑选最不经常使用的数据淘汰
allkeys-lfu: 当雷池不足写入时, 移除最不经常使用key
Redis持久化机制
Redis为了保证效率, 数据缓存在内存中, 但是会周期性的写入磁盘或者写入追加的文件中,保证数据的持久化。
Redis持久化有两种策略:
1、RDB(镜像持久化):快照形式把内存数据保存到dump.rdb文件中, 默认是RDB持久化方式
2、AOF(增量持久化):把所有的对Redis服务器的修改命令放到一个文件里,命令的集合。Redis重启时优先使用AOF文件还原数据, 因为AOF比RDB数据集完整。
Redis的RDB是怎么工作的
当Redis需要持久化是,Redis是fork一个子进程, 子进程将数据写到磁盘上一个临时RDB文件中, 当子进程写完临时文件后, 将原来的RDB替换掉,可以实现copy-on-write的好处
RDB的优点是:非常适用备份
RDB的缺点是:如果需要尽量避免故障时丢失数据,RDB不适合
Redis的AOF是怎么工作的
使用AOF持久化, 每写一个命令都通过write函数追加到appendonly.aof中
appendfsync yes
appendfsync always # 每次有数据发生修改时写入AOF文件中
appendfsync everysec # 每秒中同步一次
AOF的缺点:对于相同的数据集,AOF的文件体积大于RDB的文件体积, 根据使用的ysync策略, AOF的速度慢RDB
AOF的优点:Redis变得持久, 可以设置不同的策略
** Redis4.0持久化机制**
开启RDB和AOF混合持久化(aof-use-rdb-preamble)。
混合持久化打开,AOF重写时,直接把RDB内容复制到AOF文件开头, 可快速加载同时避免丢失过多数据, 缺点是压缩格式不是AOF格式, 可读性差
** AOF重写**
在执行bgrewriteaof命令时, Redis服务器会维护一个AOF重写的缓冲区, 缓冲区在子进程创建新的aof文件期间, 记录服务器执行的所有写命令。子进程创建新的AOF文件后, 服务器会将梭有的内容
追加好新的AOF文件的末尾, 使新旧两个AOF文件保存的数据状态一致。最后服务器用新的AOF文件替换旧的AOF文件, 完成AOF重写。
主从复制
- 主从复制的过程
1、从节点执行salveof[masterip][masterport],保存主节点信息
2、从节点的定时任务发现主节点信息, 建立和主节点的socket连接
3、从节点发送Ping信号,主节点返回Pong信号, 两边互相通信
4、建立连接后, 主节点将所有数据发送给从节点
5、主节点吧当前的数据同步给从节点后,完成复制的建立过程。主节点会持续把写命令发送给从节点, 保证主从数据一致性 - 能详细说下数据同步的过程?
Redis2.8之前使用sync[runid][offset]同步命令, 在Redis2.8后使用psync[runid][offset]命令, 两者区别在于, sync仅支持全量复制过程, psync支持全量和部分复制。 - runid: 每个redis启动都会生成一个唯一的uuid, 每次redis重启后,runid都会发生变化
- offset:主节点和从节点各自维护主从复制的偏移量offset, 主节点写入命令, offset=offset+命令的字节长度。 从节点收到主节点发送的命令后, 会增加自己的offset,并把自己的offset发送给主节点。 这样主节点同时保存自己的offset和从节点的offset, 通过对比判断主从节点数据是否一致。
- repl_backlog_size: 保存主节点上一个固定长度的先进先出队列, 默认大小1M。
1、从节点发送psync[runid][offset]命令,主节点有三种响应: 1)、FULLRESYNC: 第一次连接, 进行全量复制 2)、CONTINUE: 进行部分复制 3)、ERR: 不支持psync命令,进行全量复制
说说save和bgsave的区别?
save 和 bgsave两个命令都会调用 rdbSave 函数,但它们调用的方式各有不同:
- save 直接调用 rdbSave ,阻塞 Redis 主进程,直到保存完成为止。在主进程阻塞期间,服务器不能处理客户端的任何请求。
- bgsave 则 fork 出一个子进程,子进程负责调用 rdbSave ,并在保存完成之后向主进程发送信号,通知保存已完成。 Redis 服务器在BGSAVE 执行期间仍然可以继续处理客户端的请求。
说下全量复制和部分复制的过程?
- 从节点发送psync ? -1 命令(因为第一次发,不知道主节点的runid,所以为?, 因为第一次复制, 所以offset=-1)
- 主节点发现从节点是第一次复制, 返回FULLRESYNC[runid][offset]命令, runid是主节点的runid, offset是主节点目前的offset
- 从节点接收到主节点的信息后, 保存到info中
- 主节点在发送FULLRESYNc后,启动bgsave命令,生成RDB文件(数据持久化)
- 主节点发送RDB文件给从节点,从节点加载数据完期间,主节点写命令放入缓冲区
- 从节点清理自己的数据库
- 从节点加载RDB文件, 将数据保存到自己的数据库中。 如果从节点开启AOF, 从节点会异步重写AOF文件j
部分复制的说明
- 部分复制是针对Redis全量复制过高开销的一种优化措施, 使用psync[runid][offset] 命令实现。 当从节点正在复制主节点时, 如果出现网络闪断或者命令丢失等异常情况,主节点会向从节点要求补发丢失的命令, 主节点的复制积压缓冲区直接将部分数据发送给从节点, 可以保证主从复制的一致性。
- 主从连接中断期间,主节点依然响应命令, 但是无法发送给从节点, 不过主从节点的复制积压缓冲区依然可以保存最近一段时间的写命令数据
- 主从连接恢复后, 由于从节点保存了自身已复制的偏移量和主节点运行id, 因此会把他们当作psync参数发送给主节点, 要求进行部分复制
- 主节点接受到psync命令首先核对参数的runid和自身是否一致, 如果一致, 说明之前复制的是当前主节点, 之后根据参数offset在缓冲区查找哦, 如果是offset后的数据, 则对从节点发送+CONTINUE命令, 表示进行部分复制, 若发送缓冲区溢出, 则进行全量复制
- 主节点根据偏移量把复制积压在缓冲区的数据节点发送给从节点, 保证主从复制进入正常状态
哨兵
- 哨兵的功能: 主节点存活检测, 主从运行情况检查,自动故障转移, 主动切换。
说说哨兵的工作原理?
- 每个sentinel节点需要定期执行以下任务:每个sentinel以每秒一次的频率, 向已知的主服务器,从服务器和其他的setinel发送一个ping命令。
- 如果一个示例距离最后一次有效回复ping命令超过down-after-milliseconds所设定的值, 这个实例被sentinel标记为主观下线。
- 如果一个主服务器被标记为主观下线, 那么正在监视这个服务器的所有sentinel节点, 要以每秒一次的频率确认主服务器的确进入主管下线状态
- 如果一个主服务器被标记主观下线,并且有足够的sentinel在指定时间范围内同意这个判断,则主服务器被标记客观下线。
- 一般情况下, 每个sentinel会以每10秒一次的频率向所有主服务器和从服务器发送info命令,当一个主服务器被标记为客观下线,sentinel向下线主服务器的所有从服务器发送info命令的频率, 从10秒一次变为1秒一次
- sentinel和其他sentinel协商客观下线的主节点状态, 如果处于sdown状态, 则投标自动选出新的主节点, 将剩余从节点指向新的主节点进行数据复制。
- 当没有足够数量的sentinel同意主服务器下线时,主服务器的客观下线状态会被移除, 当主服务器重新向sentinel发送的ping命令返回有效回复时, 主服务器的主管下线状态会被移除。