Redis常见面试题(一)

1、为什么使用redis?

主要有两个考虑角度:高性能、高并发。

(PS:Redis大多数情况下用在缓存上,或者共享Session上面。如果只是为了分布式锁这些其他功能,还有其他中间件 Zookpeer 等代替,并非一定要使用 Redis。)

我们在碰到需要执行耗时特别久,且结果不频繁变动的 SQL,就特别适合将运行结果放入缓存。这样,后面的请求就去缓存中读取,使得请求能够迅速响应。

例如:现在有一个商品秒杀系统,同一时间,有几万甚至几十万的人在刷新页面数据。执行的是同一操作——向数据库查数据,查询的是同一个商品的信息,一次查询的时间是600ms,这样对于我们的数据库而言对于几万几十万的并发是无法支撑的,因为我们数据库一般单机情况2000qps就差不多报警了,即使能支撑,也需要太多从机数据库,而且执行的都是重复的命令,这对于数据库未免太浪费性能了。这时我们把缓存(redis或memcached)加进来,先去缓存中查询,如果缓存中没有,再去数据库查,把数据库中查出来的结果放到redis中,下次再有人查,别走 mysql 折腾 600ms 了,直接从缓存里查询,2ms 搞定。性能提升 300 倍,而且可以省去一大堆对数据库的请求,减少了数据库的压力。

所以,通过上面的例子我们可以看出来,使用缓存可以提高性能(600ms变成了2ms),可以支撑更高的并发(减少了数据库的压力)。

但是:既然memcached也能实现,为什么我们非要用redis呢?继续往下看。

 

2、redis和memcached有什么区别?

  • redis 支持复杂的数据结构

redis 相比 memcached 来说,拥有更多的数据结构,能支持更丰富的数据操作。如果需要缓存能够支持更复杂的结构和操作, redis 会是不错的选择(这是redis最大的优点之一,也是大多企业选择redis的原因之一)。

  • redis 原生支持集群模式

在 redis3.x 版本中,便能支持 cluster 模式,而 memcached 没有原生的集群模式,需要依靠客户端来实现往集群中分片写入数据。

  • 性能对比

由于 redis 只使用单核,而 memcached 可以使用多核,所以平均每一个核上 redis 在存储小数据时比 memcached 性能更高。而在 100k 以上的数据中,memcached 性能要高于 redis。虽然 redis 最近也在存储大数据的性能上进行优化,但是比起 memcached,还是稍有逊色。

  • redis 的线程模型

redis 内部使用文件事件处理器 file event handler(具体:请看本博客最下方的补充),这个文件事件处理器是单线程的,所以 redis 才叫做单线程的模型。它采用 IO 多路复用机制同时监听多个 socket,将 socket 产生的事件压入内存队列中,事件分派器根据 socket 上的事件来选择对应的事件处理器进行处理。

 

总结:大多数企业之所以选择Redis而不选择memcached都是因为前两个原因,即便有第三个缺点的存在,但是对于大多数企业的需求,影响并不大,因此近些年很多企业都开始由memcached转到redis上面来。

 

3、为什么redis单线程模型效率这么高?

  • 纯内存操作
  • 核心是基于非阻塞的 IO 多路复用机制
  • 单线程反而避免了多线程的频繁上下文切换问题

 

4、redis都有哪些数据类型?

  • string(字符串)、hash(哈希)、list(列表)、set(集合)、zset(有序集合)
#string(字符串,使用方式为get和set,用做最基础key/value缓存。)
set key1 value1
get key1


#hash(可以理解成map)
hset myhash name mercury    #往myhash中set一个键值对
hset myhash age 26          #往person中set一个键值对 
hset myhash id 1            #往myhash中set一个键值对
hget myhash name            #读取myhash中的某个字段的值
"mercury"
#myhash的结构如下
#myhash = {
#    "name": "mercury",
#    "age": 26,
#    "id": 1
#}


#list(有序列表)
lpush mylist 1        #往mylist中push一个值
lpush mylist 2        #往mylist中push一个值
lpush mylist 3 4 5    #往mylist中push多个值
rpop mylist           #以先进后出的方式,从mylist中取值。
#可以使用lrange实现分页功能,性能高。
#0开始位置,-1结束位置,结束位置为-1时,表示列表的最后一个位置,即查看所有。
lrange mylist 0 -1


#set(无序集合,自动去重。)
sadd mySet 1            #添加元素
smembers mySet          #查看全部元素
sismember mySet 3       #判断是否包含某个值
srem mySet 1            #删除某个/些元素
srem mySet 2 4          #删除某个/些元素
scard mySet             #查看元素个数
spop mySet              #随机删除一个元素
smove testSet mySet 2   #将一个set的元素移动到另外一个set
sinter testSet mySet    #求两set的交集
sunion testSet mySet    #求两set的并集
sdiff yourSet mySet     #求在testSet中而不在mySet中的元素


#zset(zset是排序的set)
#注意:不同的是每个元素都会关联一个double类型的分数。
#redis正是通过分数来为集合中的成员进行从小到大的排序。
zset的成员是唯一的,但分数(score)却可以重复。
zadd sun 80 venus       #往sun中添加元素
zadd sun 75 earth       #往sun中添加元素
zadd sun 90 mercury     #往sun中添加元素
zadd sun 62 mars        #往sun中添加元素

zrevrange sun 0 3      # 获取排名前三的用户(默认是升序,所以需要 rev 改为降序)
zrank sun earth        #获取某用户的排名

 

5、redis有哪些过期策略?

两种:定期删除和惰性删除。

  • 定期删除:

Redis 默认每个 100ms 检查,有过期 Key 则删除。需要说明的是,Redis 不是每个 100ms 将所有的 Key 检查一次,而是随机抽取进行检查,因为如果redis中有数量很大的key(例如几十万key),那么每隔100ms检查一次,redis很可能就死掉了,因为数据太多,检查太频繁,cpu负载太高了。如果只采用定期删除策略,会导致很多 Key 到时间没有删除。于是,惰性删除派上用场。

  • 过期删除:

当你在获取某个 key 的时候,redis 会检查这个 key是否设置了过期时间,如果设置了过期时间,检查key是否过期,如果过期了此时就会删除,返回空值。

 

注意:由于定期删除是随机抽取 key进行检查的,所以一定会漏掉一部分key,而这时候如果该key也没走惰性删除,那相当于沉积在内存中了,时间一久,必定大量堆积在内存中,导致内存耗尽。这时候要依靠redis的内存淘汰机制。继续往下看。

 

6、redis有哪些内存淘汰机制?

  • noeviction: 当内存不足以容纳新写入数据时,新写入操作会报错(使用较少)。
  • allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的 key(这个是最常用)。
  • allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个 key(使用较少)。
  • volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的 key(使用较少)。
  • volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个 key(使用较少)。
  • volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的 key 优先移除(使用较少)。

配置内存淘汰机制方式:

在 redis.conf 中有一行配置:

# maxmemory-policy volatile-lru

 

7、redis主从架构?

  • 主从架构:

一般一台redis平均能支撑的QPS为几万。对于缓存而言,大多数情况下是用来支撑读高并发的。所以会把架构做成主从(master-slave)架构,一主多从,主(master)负责写请求,并将数据复制到其它的从(slave)节点,从节点负责读请求。这样可以很轻松实现水平扩容,以支撑读高并发。

 

  • 主从机制: 
  1. redis采用异步方式把数据复制到从(slave)节点,自redis2.8 开始,slave会周期性地确认自己每次复制的数据量;

  2. 一个主节点(master node)是可以配置多个从节点(slave node)的;

  3. 从节点可以连接其他的从节点;

  4. 从节点做复制的时候,不会阻塞主节点的正常工作;

  5. 从节点在做复制的时候,也不会阻塞对自己的查询操作,它会用旧的数据集来为外部请求提供服务;但是复制完成的时候,会先删除旧数据集,加载新数据集,这个时候会暂停对外服务;

  6. 从节点主要用来进行横向扩容,做读写分离,扩容的从节点可以提高读的吞吐量。

注意:在采用主从架构时,建议开启主节点的持久化(主节点的各种备份方案也建议开启),不建议用子节点作为主节点的数据热备份,因为,如果把主节点的持久化功能关掉,可能在(主节点)master宕机重启的时候数据是空的,这时从节点可能会来主节点复制数据,这样从节点(slave node)的数据也丢了。

 

 


补充:文件事件处理器(File Event Handler

IO多路复用:I/O是指网络I/O,多路指多个TCP连接(即socket或者channel),复用指复用一个或几个线程。意思说一个或一组线程处理多个TCP连接。

例子:模拟一个tcp服务器处理30个客户socket。

假设你是一个老师,让30个学生解答一道题目,然后检查学生做的是否正确,你有下面几个选择:   

1. 第一种选择:按顺序逐个检查,先检查A,然后是B,之后是C、D......这中间如果有一个学生卡住,全班都会被耽误。这种模式就好比,你用循环挨个处理socket,根本不具有并发能力。 (单线程模型)  

2. 第二种选择:你创建30个分身,每个分身检查一个学生的答案是否正确。 这种类似于为每一个用户创建一个进程或者线程处理连接。 (多线程模型) 

3. 第三种选择,你站在讲台上等,谁解答完谁举手。这时C、D举手,表示他们解答问题完毕,你下去依次检查C、D的答案,然后继续回到讲台上等。此时E、A又举手,然后去处理E和A.... 。(IO多路复用模型)

文件事件处理器:

  如果是客户端要连接redis,那么会为socket关联连接应答处理器
  如果是客户端要写数据到redis,那么会为socket关联命令请求处理器
  如果是客户端要从redis读数据,那么会为socket关联命令回复处理器

客户端与redis通信的一次流程:

  在redis启动初始化的时候,redis会将连接应答处理器跟AE_READABLE事件关联起来,接着如果一个客户端跟redis发起连接,此时会产生一个AE_READABLE事件,然后由连接应答处理器来处理跟客户端建立连接,创建客户端对应的socket,同时将这个socket的AE_READABLE事件跟命令请求处理器关联起来。

  当客户端向redis发起请求的时候(不管是读请求还是写请求,都一样),首先就会在socket产生一个AE_READABLE事件,然后由对应的命令请求处理器来处理。这个命令请求处理器就会从socket中读取请求相关数据,然后进行执行和处理。接着redis这边准备好了给客户端的响应数据之后,就会将socket的AE_WRITABLE事件跟命令回复处理器关联起来,当客户端这边准备好读取响应数据时,就会在socket上产生一个AE_WRITABLE事件,会由对应的命令回复处理器来处理,就是将准备好的响应数据写入socket,供客户端来读取。

  命令回复处理器写完之后,就会删除这个socket的AE_WRITABLE事件和命令回复处理器的关联关系。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章