redis 经典面试题

什么是redis

Remote Dictionary Server(Redis)是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。

它通常被称为数据结构服务器,因为值(value)可以是 字符串(String), 哈希(Map), 列表(list), 集合(sets) 和 有序集合(sorted sets)等类型。

redis数据结构

字符串String、字典Hash、列表List、集合Set、有序集合SortedSet。

如果你是Redis中高级用户,还需要加上下面几种数据结构HyperLogLog、Geo、Pub/Sub。

如果你说还玩过Redis Module,像BloomFilter,RedisSearch,Redis-ML,面试官的眼睛就开始发亮了。

使用过Redis分布式锁么,它是什么回事?

先拿setnx来争抢锁,抢到之后,再用expire给锁加一个过期时间防止锁忘记了释放。

如果在setnx之后执行expire之前进程意外crash或者要重启维护了,这个锁就永远得不到释放了。

假如Redis里面有1亿个key,其中有10w个key是以某个固定的已知的前缀开头的,如果将它们全部找出来?

使用keys指令可以扫出指定模式的key列表。redis的单线程的。keys指令会导致线程阻塞一段时间,线上服务会停顿,直到指令执行完毕,服务才能恢复。这个时候可以使用scan指令,scan指令可以无阻塞的提取出指定模式的key列表,但是会有一定的重复概率,在客户端做一次去重就可以了,但是整体所花费的时间会比直接用keys指令长.

Scan 0 match name count 5

Redis做异步队列如何使用

一般使用list结构作为队列,rpush生产消息,lpop消费消息。当lpop没有消息的时候,要适当sleep一会再重试。

list还有个指令叫blpop,在没有消息的时候,它会阻塞住直到消息到来。

使用pub/sub主题订阅者模式,可以实现1:N的消息队列。

在消费者下线的情况下,生产的消息会丢失,得使用专业的消息队列如rabbitmq等。

Redis做延时队列

redis 经典面试题 使用zset,拿时间戳作为score,消息内容作为key调用zadd来生产消息,消费者用zrangebyscore指令获取N秒之前的数据轮询进行处理。

zadd messageque 1594192800 "three message"

redis过期时间设置策略

如果大量的key过期时间设置的过于集中,到过期的那个时间点,redis可能会出现短暂的卡顿现象。一般需要在时间上加一个随机值,使得过期时间分散一些。

Redis如何做持久化

bgsave做镜像全量持久化,aof做增量持久化。因为bgsave会耗费较长时间,不够实时,在停机的时候会导致大量丢失数据,所以需要aof来配合使用。在redis实例重启时,会使用bgsave持久化文件重新构建内存,再使用aof重放近期的操作指令来实现完整恢复重启之前的状态。

bgsave工作流程

redis通过创建(fork)子进程来进行bgsave操作,子进程创建后,父子进程共享数据段,父进程继续提供读写服务,写脏的页面数据会逐渐和子进程分离开来。

aof如何配置

appendonly yes开启持久化,

appendfsync everysec 异步持久化,每秒记录

auto-aof-rewrite-percentage 100 重写aof文件,文件过大

pipeline的作用

可以将多次IO往返的时间缩减为一次,使用Pipeline执行速度比逐条执行要快,特别是客户端与服务端的网络延迟越大,性能体能越明显。

redis主从同步过程

第一次同步时,主节点做一次bgsave,并同时将后续修改操作记录到内存buffer,待完成后将rdb文件全量同步到复制节点,复制节点接受完成后将rdb镜像加载到内存。加载完成后,再通知主节点将期间修改的操作记录同步到复制节点进行重放就完成了同步过程。

从节点执行slaveof[masterIP][masterPort],保存主节点信息

从节点中的定时任务发现主节点信息,建立和主节点的socket连接

从节点发送Ping信号,主节点返回Pong,两边能互相通信

连接建立后,主节点将所有数据发送给从节点(数据同步)

主节点把当前的数据同步给从节点后,便完成了复制的建立过程。接下来,主节点就会持续的把写命令发送给从节点,保证主从数据一致性。

主节点发送数据给从节点过程中,主节点还会进行一些写操作,这时候的数据存储在复制缓冲区中。从节点同步主节点数据完成后,主节点将缓冲区的数据继续发送给从节点,用于部分复制。

主节点响应写命令时,不但会把命名发送给从节点,还会写入复制积压缓冲区,用于复制命令丢失的数据补救。

redis 的线程模型

redis 内部使用文件事件处理器 file event handler,这个文件事件处理器是单线程的,所以 redis 才叫做单线程的模型。它采用 IO 多路复用机制同时监听多个 socket,根据 socket 上的事件来选择对应的事件处理器进行处理。

redis6 哪些新特性

新增ACL权限控制

(1)接入权限:用户名和密码(2)可以执行的命令(3)可以操作的 KEY

增加新的 Redis 通信协议

Resp3 redis serialization protoal

客户端缓存

IO多线程

IO多线程其实指客户端交互部分的网络IO交互处理模块多线程,而非执行命令多线程

Disque

Distribute job queue 分布式任务队列,一个插件。

redis 单线程模型效率高的原因

纯内存操作

核心是基于非阻塞的 IO 多路复用机制

单线程反而避免了多线程的频繁上下文切换问题

redis的过期策略

定期删除和惰性删除结合。

内存淘汰机制

noeviction: 当内存不足以容纳新写入数据时,新写入操作会报错,这个一般没人用吧,实在是太恶心了。

allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的 key(这个是最常用的)。

allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个 key,这个一般没人用吧,为啥要随机,肯定是把最近最少使用的 key 给干掉啊。

volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的 key(这个一般不太合适)。

volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个 key。

redis的哨兵是什么

sentinel用来监控redis实例运行状况,负责多个实例之间交换监控信息。

主从Redis的主服务器不能正常工作时,Sentinel会开始进行故障迁移操作。将一个从服务器升级新的主服务器。 让其他从服务器挂到新的主服务器。同时向客户端提供新的主服务器地址。

redis集群如何配置

使用redis cluster来搭建集群,然后采用master-salve配置高可用。支持更多的数据,只需要扩充cluster节点redis实例数即可。

Redis cluster可以自动将数据进行分片,每个master节点保存部分数据,一个节点坏了,其它的节点依然可以正常工作。

redis 集群节点如何通信

集中式:底层采用zookeeper进行调度,将所有节点信息保存在一个节点上。时效性非常好,数据存储有压力。

Gossip: gossip 协议是可扩展的,一般需要 O(logN) 轮就可以将信息传播到所有的节点,其中 N 代表节点的个数。每个节点仅发送固定数量的消息,并且与网络中节点数目无关。在数据传送的时候,节点并不会等待消息的 ack,所以消息传送失败也没有关系,因为可以通过其他节点将消息传递给之前传送失败的节点。系统可以轻松扩展到数百万个进程。节点数据存储无压力,但是元数据更新有延迟。

gossip消息类型有哪些

Ping pong meet fail

一致性hash算法

将hash值组成一个虚拟的圆环,整个空间按照顺时针方向排列,当一个节点挂掉,只会影响当前节点和下一个节点之间的环形区域。 一开始节点过少的时候,可以增加虚拟节点,这样可以实现数据的分布均匀。

hash slot 算法

Redis cluster 一共有16384个hash slot,对每个 key 计算 CRC16 值,然后对 16384 取模,可以获取 key 对应的 hash slot。

redis cluster 中每个 master 都会持有部分 slot,比如有 3 个 master,那么可能每个 master 持有 5000 多个 hash slot。hash slot 让 node 的增加和移除很简单,增加一个 master,就将其他 master 的 hash slot 移动部分过去,减少一个 master,就将它的 hash slot 移动到其他 master 上去。

移动 hash slot 的成本是非常低的。客户端的 api,可以对指定的数据,让他们走同一个 hash slot,通过 hash tag 来实现。

任何一台机器宕机,另外两个节点,不影响的。因为 key 找的是 hash slot,不是机器。

主从复制中,主节点选举机制

每个从节点,都根据自己对 master 复制数据的 offset,来设置一个选举时间,offset 越大(复制数据越多)的总节点,选举时间越靠前,优先进行选举。

所有的 master node 开始 slave 选举投票,给要进行选举的 slave 进行投票,如果大部分 master node(N/2 + 1)都投票给了某个从节点,那么选举通过,那个从节点可以切换成 master。

从节点执行主备切换,从节点切换为主节点。

redis的雪崩和缓存穿透

缓存雪崩:因为某种原因,redis机器挂掉,所有的请求打到mysql数据库上,造成数据库挂掉。

解决方案:主从配置,配置redis-cluster,避免redis 全部宕机,增加限流算法,避免请求数据全部打到数据库上,redis一定要开启rdb和aof备份,方便快速恢复。

缓存穿透:所有的请求全部没有命中缓存,导致请求全部打到数据库上。

解决方案:

数据库中没有的数据设置一个空值,这样请求就会走缓存。

数据库的双写一致性

读请求,写请求串行化。

Cash aside pattern 先读缓存,没有命中读数据库,写入缓存。更新的时候,更新数据库,删除缓存。

更新数据的时候,根据数据的唯一标识,将操作路由之后,发送到一个 jvm 内部队列中。读取数据的时候,如果发现数据不在缓存中,那么将重新读取数据+更新缓存的操作,根据唯一标识路由之后,也发送同一个 jvm 内部队列中。

一个队列对应一个工作线程,每个工作线程串行拿到对应的操作,然后一条一条的执行。这样的话,一个数据变更的操作,先删除缓存,然后再去更新数据库,但是还没完成更新。此时如果一个读请求过来,读到了空的缓存,那么可以先将缓存更新的请求发送到队列中,此时会在队列中积压,然后同步等待缓存更新完成。

这里有一个优化点,一个队列中,其实多个更新缓存请求串在一起是没意义的,因此可以做过滤,如果发现队列中已经有一个更新缓存的请求了,那么就不用再放个更新请求操作进去了,直接等待前面的更新操作请求完成即可。

待那个队列对应的工作线程完成了上一个操作的数据库的修改之后,才会去执行下一个操作,也就是缓存更新的操作,此时会从数据库中读取最新的值,然后写入缓存中。

Redis cas 机制

将key保存到数据库的时候加上时间戳,如果更新的时候时间戳比缓存新的话,可以直接更新,否则不能更新。

使用wath监控多个键,然后使用multi开启事务,如果执行的过程中还想数据被修改了,则执行失败,回滚整个操作。

redis事物执行过程

multi开始事物

执行操作入队

exec提交事物

Redis中的常用命令哪些

ncr 让当前键值以1的数量递增,并返回递增后的值

incrby 可以指定参数一次增加的数值,并返回递增后的值

incrby 可以指定参数一次增加的数值,并返回递增后的值

decrby 可以指定参数一次递减的数值,并返回递减后的值

incrbyfloat 可以递增一个双精度浮点数

append 作用是向键值的末尾追加value。如果键不存在则将该键的值设置为value。返回值是追加后字符串的总长度。

mget/mset 作用与get/set相似,不过mget/mset可以同时获得/设置多个键的键值

del 根据key来删除value

flushdb 清除当前库的所有数据

hset 存储一个哈希键值对的集合

hget获取一个哈希键的值

hmset 存储一个或多个哈希是键值对的集合

hmget 获取多个指定的键的值

hexists 判断哈希表中的字段名是否存在 如果存在返回1 否则返回0

hdel 删除一个或多个字段

hgetall 获取一个哈希是键值对的集合

hvals 只返回字段值

hkeys 只返回字段名

hlen 返回key的hash的元素个数

lpush key value向链表左侧添加

rpush key value向链表右侧添加

lpop key 从左边移出一个元素

rpop key 从右边移出一个元素

llen key 返回链表中元素的个数 相当于关系型数据库中 select count(*)

lrange key start end lrange命令将返回索引从start到stop之间的所有元素。Redis的列表起始索引为0。

lrange也支持负索引 lrange nn -2 -1 如 -1表示最右边第一个元素 -2表示最右边第二个元素,依次类推。

lindex key indexnumber 如果要将列表类型当做数组来用,lindex命令是必不可少的。lindex命令用来返回指定索引的元素,索引从0开始

如果是负数表示从右边开始计算的索引,最右边元素的索引是-1。

Lset key indexnumber value 是另一个通过索引操作列表的命令,它会将索引为index的元素赋值为value。

sadd key value 添加一个string元素到,key对应的set集合中,成功返回1,如果元素已经在集合中返回0

scard key 返回set的元素个数,如果set是空或者key不存在返回0

smembers key 返回key对应set的所有元素,结果是无序的

sismember key value 判断value 是否在set中,存在返回1,0表示不存在或者key不存在

srem key value 从key对应set中移除给定元素,成功返回1,如果value 在集合中不存在或者key不存在返回0

zadd key score value 将一个或多个value及其socre加入到set中

zrange key start end 0和-1表示从索引为0的元素到最后一个元素(同LRANGE命令相似)

zrange key 0 -1 withscores 也可以连同score一块输出,使用WITHSCORES参数

zremrangebyscore key start end 可用于范围删除操作

ping 测试redis是否链接 如果已链接返回 PONG

echo value测试redis是否链接 如果已链接返回 echo命令后给定的值

keys 返回所有的key 可以加通配

exists key判断string类型一个key是否存在 如果存在返回1 否则返回0

expire key time(s) 设置一个key的过期时间 单位秒。时间到达后会删除key及value

ttl key 查询已设置过期时间的key的剩余时间 如果返回-2表示该键值对已经被删除

persist 移除给定key的过期时间

select dbindex 选择数据库(0-15)

move key dbIndex 将当前数据库中的key转移到其他数据库中

dbsize 返回当前数据库中的key的数目

info 获取服务器的信息和统计

flushdb 删除当前选择的数据库中的key

flushall 删除所有数据库中的所有key

quit 退出连接

Reids 比memcache有什么优势

有丰富的数据类型

redis数据更快一些

redis支持持久化

一个字符串最多可以存储多大

512M

redis主要使用场景

会话缓存,保存用户session等信息

全页缓存,缓存整个网页页面

用做消息队列

做排行榜

Redis hash如何使用

hash主要是用来存储对象的,可以非常快速的存储对象的各个属性。

Redis回收进程如何工作

客户端执行了一条命令,如果查过了redis的默认内存大小,那么redis就会根据设置的内存回收策略进行回收。

Redis 分布式锁怎么使用

先用setnx增强锁,发送lua脚本给redis.然后给锁加一个expire过期时间。

watch dog自动延期机制

后台线程每10s进行查询,没过期,延长锁的过期时间

Redis如何设置过期时间

Expire persist设置时间

Expire key second

Pexpire key ms

Exipreat key timestamp

Pexpireat key timestamp

Redis集群最大节点个数

16384

Redis默认端口号

6379

查看内存使用信息查看

Info memory

used_memory_human: 分配的总内存

Used_memory_rss_human 不包括虚拟内存

Used_memory_peak_human 内存使用最大值,就是used_memory峰值

Total_system_memory 系统总内存

mem_fragmentation_ratio = used_memory_rss / used_memory

Redis调整内存上限

Config set maxmemroy 1G

Redis为什么是二进制安全的

因为它内部使用sds来存储字符,它有length字段来表示何时截断字符串,而不是使用\0来截断字符串。

查看一个key的过期时间

Ttl key

Redis 中的ziplist是一种什么数据结构

zlbytes 记录着整一个压缩表的长度

zltail 记录着最后一个元素的偏移量,这是为了倒序遍历整个ziplist

zlen 用来记录压缩表中节点的数量

entryX 列表中的节点,节点用来存储具体的数据。

zlend 一个特殊值0XFF,用来标记压缩列表已经结束了。

Redis内存优化方法

关闭vm,提升内存使用效率

设置maxmemory防止redis使用swap内存

zset底层数据结构

Ziplist skiplist

Ziplist 条件,元素数量小于128,每个元素小于64个字节

skiplist

有序列表的基本操作

zadd(key, score, member):向名称为key的zset中添加元素member,score用于排序。如果该元素已经存在,则根据score更新该元素的顺序。

zrem(key, member) :删除名称为key的zset中的元素member

zincrby(key, increment, member) :如果在名称为key的zset中已经存在元素member,则该元素的score增加increment;否则向集合中添加该元素,其score的值为increment

zrank(key, member) :返回名称为key的zset(元素已按score从小到大排序)中member元素的rank(即index,从0开始),若没有member元素,返回“nil”

zrevrank(key, member) :返回名称为key的zset(元素已按score从大到小排序)中member元素的rank(即index,从0开始),若没有member元素,返回“nil”

zrange(key, start, end):返回名称为key的zset(元素已按score从小到大排序)中的index从start到end的所有元素

zrevrange(key, start, end):返回名称为key的zset(元素已按score从大到小排序)中的index从start到end的所有元素

zrangebyscore(key, min, max):返回名称为key的zset中score >= min且score <= max的所有元素 zcard(key):返回名称为key的zset的基数

zscore(key, element):返回名称为key的zset中元素element的score zremrangebyrank(key, min, max):删除名称为key的zset中rank >= min且rank <= max的所有元素 zremrangebyscore(key, min, max) :删除名称为key的zset中score >= min且score <= max的所有元素