Redis面试题锦
概述
常见问题
一、概述
最近领导让我来面试一些候选人,我挑了一些高频知识点总结,以免到时候忽悠不了候选人(哈哈),此为Redis篇。
二、常见问题
2.1 Redis概念(参考百度百科)
1. Redis全称:REmote DIctionary Server(Redis) ,即远程字典服务,是一个开源的使用ANSI C语言编写、遵守BSD协议、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。
2. 支持的数据类型:String: 字符串、Hash: 散列、List: 列表、Set: 集合、Sorted Set: 有序集合
3. 操作类型:push/pop、add/remove及交集并集等等,且全是原子操作。
4. Redis为了效率,也同memcached一样,将数据缓存在内存中,但是会定期将数据写入磁盘或者将修改追加到日志文件,请再次基础上实现master-slave同步。
5. Redis支持主从同步。数据可以从主服务器向任意数量的从服务器上同步,从服务器可以是关联其他从服务器的主服务器。这使得Redis可执行单层树复制。存盘可以有意无意的对数据进行写操作。由于完全实现了发布/订阅机制,使得从数据库在任何地方同步树时,可订阅一个频道并接收主服务器完整的消息发布记录。同步对读取操作的可扩展性和数据冗余很有帮助。
2.2 比较Redis与Memcached
1. redis除了支持k-v,还支持其他的数据结构;Memcached只支持k-v和String
2. redis支持主从复制功能(数据备份)
3. Redis支持持久化操作,可以在断电重启后,再次加载数据进行使用;Memcached将数据全部存储在内存之中,断电后消失。
4. Memcached是多线程、非阻塞IO复用模型。;Redis是单线程的IO复用模型。
- 若想更进一步了解,《脚踏两只船的困惑 - Memcached与Redis》 请点击
- 复用:多个任务,一个线程
2.3 如何选择Memcached和redis
使用Redis的String类型做的事,都可以用Memcached替换,以此换取更好的性能提升; 除此以外,优先考虑Redis
2.4 使用Redis的好处
1. 速度:数据存储于内存之中,类似于hashMap,查找和操作的时间复杂度为O(1);
2. 支持多种数据类型
3. 支持主从复制,持久化,可用作消息队列,缓存(设置key过期时间)
4. 支持事务:如果在入队时出错,则都不会执行;在非入队时出错,那么成功执行
2.5 Redis常见数据结构使用场景
命令用法:
COMMAND KEY_NAME
2.5.1 String
127.0.0.1:6379> set my_redis redis //设置k-v
OK
127.0.0.1:6379> get my_redis //获取value
"redis"
127.0.0.1:6379> GETRANGE my_redis 0 3 //获取子集
"redi"
127.0.0.1:6379> GETSET my_redis mongodb //返回value并重置
"redis"
127.0.0.1:6379> get my_redis
"mongodb"
127.0.0.1:6379> SETEX my_redis 10 redis //设置过期时间
OK
127.0.0.1:6379> get my_redis
"redis"
127.0.0.1:6379> get my_redis //已过期
(nil)
127.0.0.1:6379> SETEX my_redis 5 redis1 //设置过期时间
OK
127.0.0.1:6379> get my_redis
"redis1"
127.0.0.1:6379> SETNX my_redis redis //SET if Not eXists
(integer) 1
127.0.0.1:6379> get my_redis
"redis"
127.0.0.1:6379> STRLEN my_redis //返回value长度
(integer) 5
127.0.0.1:6379> DEL my_redis
(integer) 1
127.0.0.1:6379> get my_redis
(nil)
- 其他对多个key及value中数字的加减操作未展示
- 常用命令: set,get,decr,incr,mget 等。
2.5.2 Hash
127.0.0.1:6379> HMSET date y "2020" m "09" d "30" //设置hash
OK
127.0.0.1:6379> hgetall date //查看hash
1) "y"
2) "2020"
3) "m"
4) "09"
5) "d"
6) "30"
127.0.0.1:6379> HDEL date y //删除hash中的field
(integer) 1
127.0.0.1:6379> hgetall date
1) "m"
2) "09"
3) "d"
4) "30"
127.0.0.1:6379> HEXISTS date d //检查hash中包含某个field
(integer) 1
127.0.0.1:6379> HSETNX date t 16
(integer) 1
127.0.0.1:6379> hgetall date
1) "m"
2) "09"
3) "d"
4) "30"
5) "t"
6) "16"
- 常用命令: hget,hset,hgetall 等。
2.5.3 List操作
127.0.0.1:6379> LPUSH my_arr redis
(integer) 1
127.0.0.1:6379> LPOP my_arr
"redis"
- 常用命令: lpush,rpush,lpop,rpop,lrange等
2.5.4 Set
127.0.0.1:6379> SADD myset redis
(integer) 1
127.0.0.1:6379> SADD myset mysql
(integer) 1
127.0.0.1:6379> SADD myset mysql
(integer) 0
127.0.0.1:6379> SADD myset mongodb
(integer) 1
127.0.0.1:6379> SMEMBERS myset
1) "mongodb"
2) "redis"
3) "mysql"
4) "mqsql"
- 常用命令: sadd,spop,smembers,sunion 等
2.5.5 sorted set
127.0.0.1:6379> ZADD myst 1 redis
(integer) 1
127.0.0.1:6379> ZADD myst 2 mysql
(integer) 1
127.0.0.1:6379> ZADD myst 3 mongodb
(integer) 1
127.0.0.1:6379> ZRANGE myst 0 10 WITHSCORES
1) "redis"
2) "1"
3) "mysql"
4) "2"
5) "mongodb"
6) "3"
- 常用命令: zadd,zrange,zrem,zcard等
2.6 如何保证redis中都是热点数据
相关:redis内存数据集达到一定大小后会施行淘汰策略
-
- volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
-
- volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
-
- volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
-
- allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰
-
- allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
-
- no-enviction(驱逐):禁止驱逐数据
2.7 Redis的并发竞争问题如何解决?
背景:redis使用单线程模式,采用队列模式将并发访问变为串行访问。Redis本身没有锁的概念,多个客户端连接时并不存在竞争,但在客户端对redis访问时会发生连接超时、数据转换错误、阻塞、客户端关闭连接等问题,这些问题均由客户端连接混乱导致。
解决方法如下
-
- 客户端角度:为保证每个客户端间正常有序与Redis进行通信,对连接进行池化,同时对客户端读写Redis操作采用内部锁同步。
-
- 服务器角度,利用setnx实现锁。
对于第一种,需要应用程序自己处理资源的同步,可以使用的方法比较通俗,可以使用锁机制;第二种需要用到Redis的setnx命令,但是需要注意一些问题。
2.8 Redis回收机制
-
- 引用计数法:
-
- LRU算法:最近最久未使用算法
2.9 Redis 大量数据插入
背景:使用正常模式的Redis,会不停地进行IO操作,思路是将多条数据,使用管道一次插入。
-
- 以前使用netcat方式,创建redis命令集文件data.txt, 执行命令:(cat data.txt; sleep 10) | nc localhost 6379 > /dev/null,但无检查错误能力。
-
- pipe mode:cat data.txt | redis-cli --pipe,
使用redis-cli将有效的确保错误输出到Redis实例的标准输出里面
- pipe mode:cat data.txt | redis-cli --pipe,
2.10 Redis 分区的优势、不足以及分区类型
分区:分区是分割数据到多个Redis实例的处理过程,因此每个实例只保存key的一个子集。
优势与劣势
- 优势:
集合了计算资源,使用了更多地cpu、网络、内存来提高redis的性能
- 劣势:
多个分区的数据不能像同一分区那样使用并集、交集、事务等操作
分区类型
- 范围分区:维护区间范围到实例的映射表(额外开销,不推荐)
- 哈希分区:使用hash算法,来选择实例
2.11 Redis持久化方式及选择
持久化方式
- RDB:间隔时间内对数据进行快照存储
- AOF:记录每次对服务器的写操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据,Redis协议追加保存每次写的操作到文件末尾.Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大。
如何选择
- RDB:默认开启,会按照配置的指定时间将内存中的数据快照到磁盘中,创建一个dump.rdb文件,Redis启动时再恢复到内存中。Redis会单独创建fork()一个子进程,将当前父进程的数据库数据复制到子进程的内存中,然后由子进程写入到临时文件中,持久化的过程结束了,再用这个临时文件替换上次的快照文件,然后子进程退出,内存释放。需要注意的是,每次快照持久化都会将主进程的数据库数据复制一遍,导致内存开销加倍,若此时内存不足,则会阻塞服务器运行,直到复制结束释放内存;都会将内存数据完整写入磁盘一次,所以如果数据量大的话,而且写操作频繁,必然会引起大量的磁盘I/O操作,严重影响性能,并且最后一次持久化后的数据可能会丢失;
- AOF:以日志的形式记录每个写操作(读操作不记录),只需追加文件但不可以改写文件,Redis启动时会根据日志从头到尾全部执行一遍以完成数据的恢复工作。包括flushDB也会执行。主要有两种方式触发:有写操作就写、每秒定时写(也会丢数据)。因为AOF采用追加的方式,所以文件会越来越大,针对这个问题,新增了重写机制,就是当日志文件大到一定程度的时候,会fork出一条新进程来遍历进程内存中的数据,每条记录对应一条set语句,写到临时文件中,然后再替换到旧的日志文件(类似rdb的操作方式)。默认触发是当aof文件大小是上次重写后大小的一倍且文件大于64M时触发。
当两种方式同时开启时,数据恢复Redis会优先选择AOF恢复。一般情况下,只要使用默认开启的RDB即可,因为相对于AOF,RDB便于进行数据库备份,并且恢复数据集的速度也要快很多。开启持久化缓存机制,对性能会有一定的影响,特别是当设置的内存满了的时候,更是下降到几百reqs/s。所以如果只是用来做缓存的话,可以关掉持久化。
2.12 Redis常见性能问题和解决方案?
-
- Master最好不要做任何持久化工作,如RDB内存快照和AOF日志文件
-
- 如果数据比较重要,某个Slave开启AOF备份数据,策略设置为每秒同步一次
-
- 为了主从复制的速度和连接的稳定性,Master和Slave最好在同一个局域网内
-
- 尽量避免在压力很大的主库上增加从库
-
- 主从复制不要用图状结构,用单向链表结构更为稳定,即:Master <- Slave1 <- Slave2 <- Slave3…
这样的结构方便解决单点故障问题,实现Slave对Master的替换。如果Master挂了,可以立刻启用Slave1做Master,其他不变。
- 主从复制不要用图状结构,用单向链表结构更为稳定,即:Master <- Slave1 <- Slave2 <- Slave3…