Redis:我这21个灵魂拷问你拿什么挡?

前言

欢迎各位进群973961276一块儿聊聊技术吹吹牛,每周都会有几回抽奖送专业书籍的活动,奖品虽不甚值钱,但也可搏个彩头linux

1.什么是redis?

Redis 是一个基于内存的高性能key-value数据库。c++

2.Reids的特色

Redis本质上是一个Key-Value类型的内存数据库,很像memcached,整个数据库通通加载在内存当中进行操做,按期经过异步操做把数据库数据flush到硬盘上进行保存。由于是纯内存操做,Redis的性能很是出色,每秒能够处理超过 10万次读写操做,是已知性能最快的Key-Value DB。面试

Redis的出色之处不只仅是性能,Redis最大的魅力是支持保存多种数据结构,此外单个value的最大限制是1GB,不像 memcached只能保存1MB的数据,所以Redis能够用来实现不少有用的功能,比方说用他的List来作FIFO双向链表,实现一个轻量级的高性 能消息队列服务,用他的Set能够作高性能的tag系统等等。另外Redis也能够对存入的Key-Value设置expire时间,所以也能够被看成一 个功能增强版的memcached来用。redis

Redis的主要缺点是数据库容量受到物理内存的限制,不能用做海量数据的高性能读写,所以Redis适合的场景主要局限在较小数据量的高性能操做和运算上。数据库


零基础和大三大四的朋友看这里>>c/c++ 企业级项目实战后端

已经工做了想继续自我提高跳槽涨薪的工程师看这里>>c/c++ linux服务器高级架构师学习数组

3.使用redis有哪些好处?

1.速度快,由于数据存在内存中,相似于HashMap,HashMap的优点就是查找和操做的时间复杂度都是O(1)缓存

2.支持丰富数据类型,支持string,list,set,sorted set,hash服务器

1)String网络

经常使用命令:set/get/decr/incr/mget等;

应用场景:String是最经常使用的一种数据类型,普通的key/value存储均可以归为此类;

实现方式:String在redis内部存储默认就是一个字符串,被redisObject所引用,当遇到incr、decr等操做时会转成数值型进行计算,此时redisObject的encoding字段为int。

2)Hash

经常使用命令:hget/hset/hgetall等

应用场景:咱们要存储一个用户信息对象数据,其中包括用户ID、用户姓名、年龄和生日,经过用户ID咱们但愿获取该用户的姓名或者年龄或者生日;

实现方式:Redis的Hash实际是内部存储的Value为一个HashMap,并提供了直接存取这个Map成员的接口。Key是用户ID, value是一个Map。这个Map的key是成员的属性名,value是属性值。这样对数据的修改和存取均可以直接经过其内部Map的Key(Redis里称内部Map的key为field), 也就是经过 key(用户ID) + field(属性标签) 就能够操做对应属性数据。

当前HashMap的实现有两种方式:当HashMap的成员比较少时Redis为了节省内存会采用相似一维数组的方式来紧凑存储,而不会采用真正的HashMap结构,这时对应的value的redisObject的encoding为zipmap,当成员数量增大时会自动转成真正的HashMap,此时encoding为ht。

3)List

经常使用命令:lpush/rpush/lpop/rpop/lrange等;

应用场景:Redis list的应用场景很是多,也是Redis最重要的数据结构之一,好比twitter的关注列表,粉丝列表等均可以用Redis的list结构来实现;

实现方式:Redis list的实现为一个双向链表,便可以支持反向查找和遍历,更方便操做,不过带来了部分额外的内存开销,Redis内部的不少实现,包括发送缓冲队列等也都是用的这个数据结构。

4)Set

经常使用命令:sadd/spop/smembers/sunion等;

应用场景:Redis set对外提供的功能与list相似是一个列表的功能,特殊之处在于set是能够自动排重的,当你须要存储一个列表数据,又不但愿出现重复数据时,set是一个很好的选择,而且set提供了判断某个成员是否在一个set集合内的重要接口,这个也是list所不能提供的;

实现方式:set 的内部实现是一个 value永远为null的HashMap,实际就是经过计算hash的方式来快速排重的,这也是set能提供判断一个成员是否在集合内的缘由。

5)Sorted Set

经常使用命令:zadd/zrange/zrem/zcard等;

应用场景:Redis sorted set的使用场景与set相似,区别是set不是自动有序的,而sorted set能够经过用户额外提供一个优先级(score)的参数来为成员排序,而且是插入有序的,即自动排序。当你须要一个有序的而且不重复的集合列表,那么能够选择sorted set数据结构,好比twitter 的public timeline能够以发表时间做为score来存储,这样获取时就是自动按时间排好序的。

实现方式:Redis sorted set的内部使用HashMap和跳跃表(SkipList)来保证数据的存储和有序,HashMap里放的是成员到score的映射,而跳跃表里存放的是全部的成员,排序依据是HashMap里存的score,使用跳跃表的结构能够得到比较高的查找效率,而且在实现上比较简单。

3.支持事务,操做都是原子性,所谓的原子性就是对数据的更改要么所有执行,要么所有不执行

4.丰富的特性:可用于缓存,消息,按key设置过时时间,过时后将会自动删除

4.redis相比memcached有哪些优点?

  • memcached全部的值均是简单的字符串,redis做为其替代者,支持更为丰富的数据类型
  • redis的速度比memcached快不少 (3) redis能够持久化其数据

5.Memcache与Redis的区别都有哪些?

  • 存储方式 Memecache把数据所有存在内存之中,断电后会挂掉,数据不能超过内存大小。Redis有部份存在硬盘上,这样能保证数据的持久性。
  • 数据支持类型 Memcache对数据类型支持相对简单。Redis有复杂的数据类型。
  • 使用底层模型不一样 它们之间底层实现方式 以及与客户端之间通讯的应用协议不同。Redis直接本身构建了VM 机制 ,由于通常的系统调用系统函数的话,会浪费必定的时间去移动和请求。

6.redis适用于的场景?

Redis最适合全部数据in-momory的场景,如:

1.会话缓存(Session Cache)

最经常使用的一种使用Redis的情景是会话缓存(session cache)。用Redis缓存会话比其余存储(如Memcached)的优点在于:Redis提供持久化。

2.全页缓存(FPC)

除基本的会话token以外,Redis还提供很简便的FPC平台。回到一致性问题,即便重启了Redis实例,由于有磁盘的持久化,用户也不会看到页面加载速度的降低,这是一个极大改进,相似PHP本地FPC。

3.队列

Reids在内存存储引擎领域的一大优势是提供 list 和 set 操做,这使得Redis能做为一个很好的消息队列平台来使用。Redis做为队列使用的操做,就相似于本地程序语言(如Python)对 list 的 push/pop 操做。

若是你快速的在Google中搜索“Redis queues”,你立刻就能找到大量的开源项目,这些项目的目的就是利用Redis建立很是好的后端工具,以知足各类队列需求。例如,Celery有一个后台就是使用Redis做为broker,你能够从这里去查看。

4.排行榜/计数器

Redis在内存中对数字进行递增或递减的操做实现的很是好。集合(Set)和有序集合(Sorted Set)也使得咱们在执行这些操做的时候变的很是简单,Redis只是正好提供了这两种数据结构。因此,咱们要从排序集合中获取到排名最靠前的10个用户–咱们称之为“user_scores”,咱们只须要像下面同样执行便可:

固然,这是假定你是根据你用户的分数作递增的排序。若是你想返回用户及用户的分数,你须要这样执行:

ZRANGE user_scores 0 10 WITHSCORES

Agora Games就是一个很好的例子,用Ruby实现的,它的排行榜就是使用Redis来存储数据的,你能够在这里看到。

5.发布/订阅

最后(但确定不是最不重要的)是Redis的发布/订阅功能。发布/订阅的使用场景确实很是多。推荐阅读:Redis 的 8 大应用场景

七、redis的缓存失效策略和主键失效机制

做为缓存系统都要按期清理无效数据,就须要一个主键失效和淘汰策略.

在Redis当中,有生存期的key被称为volatile。在建立缓存时,要为给定的key设置生存期,当key过时的时候(生存期为0),它可能会被删除。

一、影响生存时间的一些操做

生存时间能够经过使用 DEL 命令来删除整个 key 来移除,或者被 SET 和 GETSET 命令覆盖原来的数据,也就是说,修改key对应的value和使用另外相同的key和value来覆盖之后,当前数据的生存时间不一样。

好比说,对一个 key 执行INCR命令,对一个列表进行LPUSH命令,或者对一个哈希表执行HSET命令,这类操做都不会修改 key 自己的生存时间。另外一方面,若是使用RENAME对一个 key 进行更名,那么更名后的 key的生存时间和更名前同样。

RENAME命令的另外一种多是,尝试将一个带生存时间的 key 更名成另外一个带生存时间的 another_key ,这时旧的 another_key (以及它的生存时间)会被删除,而后旧的 key 会更名为 another_key ,所以,新的 another_key 的生存时间也和本来的 key 同样。使用PERSIST命令能够在不删除 key 的状况下,移除 key 的生存时间,让 key 从新成为一个persistent key 。

二、如何更新生存时间

能够对一个已经带有生存时间的 key 执行EXPIRE命令,新指定的生存时间会取代旧的生存时间。过时时间的精度已经被控制在1ms以内,主键失效的时间复杂度是O(1),EXPIRE和TTL命令搭配使用,TTL能够查看key的当前生存时间。设置成功返回 1;当 key 不存在或者不能为 key 设置生存时间时,返回 0 。

最大缓存配置,在 redis 中,容许用户设置最大使用内存大小

server.maxmemory默认为0,没有指定最大缓存,若是有新的数据添加,超过最大内存,则会使redis崩溃,因此必定要设置。redis 内存数据集大小上升到必定大小的时候,就会实行数据淘汰策略。

redis 提供 6种数据淘汰策略:

  • 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(驱逐):禁止驱逐数据

注意这里的6种机制,volatile和allkeys规定了是对已设置过时时间的数据集淘汰数据仍是从所有数据集淘汰数据,后面的lru、ttl以及random是三种不一样的淘汰策略,再加上一种no-enviction永不回收的策略。

使用策略规则:

  • 若是数据呈现幂律分布,也就是一部分数据访问频率高,一部分数据访问频率低,则使用allkeys-lru
  • 若是数据呈现平等分布,也就是全部的数据访问频率都相同,则使用allkeys-random

三种数据淘汰策略:

ttl和random比较容易理解,实现也会比较简单。主要是Lru最近最少使用淘汰策略,设计上会对key 按失效时间排序,而后取最早失效的key进行淘汰

8.为何redis须要把全部数据放到内存中?

Redis为了达到最快的读写速度将数据都读到内存中,并经过异步的方式将数据写入磁盘。因此redis具备快速和数据持久化的特征。若是不将数据放在内存中,磁盘I/O速度为严重影响redis的性能。在内存愈来愈便宜的今天,redis将会愈来愈受欢迎。

若是设置了最大使用的内存,则数据已有记录数达到内存限值后不能继续插入新值。

9.Redis是单进程单线程的

redis利用队列技术将并发访问变为串行访问,消除了传统数据库串行控制的开销

10.redis的并发竞争问题如何解决?

Redis为单进程单线程模式,采用队列模式将并发访问变为串行访问。Redis自己没有锁的概念,Redis对于多个客户端链接并不存在竞争,可是在Jedis客户端对Redis进行并发访问时会发生链接超时、数据转换错误、阻塞、客户端关闭链接等问题,这些问题均是因为客户端链接混乱形成。对此有2种解决方法:

  1. 客户端角度,为保证每一个客户端间正常有序与Redis进行通讯,对链接进行池化,同时对客户端读写Redis操做采用内部锁synchronized。
  2. 服务器角度,利用setnx实现锁。
注:对于第一种,须要应用程序本身处理资源的同步,可使用的方法比较通俗,可使用synchronized也可使用lock;第二种须要用到Redis的setnx命令,可是须要注意一些问题。

十一、redis常见性能问题和解决方案:

1.Master写内存快照,save命令调度rdbSave函数,会阻塞主线程的工做,当快照比较大时对性能影响是很是大的,会间断性暂停服务,因此Master最好不要写内存快照。

2.Master AOF持久化,若是不重写AOF文件,这个持久化方式对性能的影响是最小的,可是AOF文件会不断增大,AOF文件过大会影响Master重启的恢复速度。Master最好不要作任何持久化工做,包括内存快照和AOF日志文件,特别是不要启用内存快照作持久化,若是数据比较关键,某个Slave开启AOF备份数据,策略为每秒同步一次。

3.Master调用BGREWRITEAOF重写AOF文件,AOF在重写的时候会占大量的CPU和内存资源,致使服务load太高,出现短暂服务暂停现象。

4.Redis主从复制的性能问题,为了主从复制的速度和链接的稳定性,Slave和Master最好在同一个局域网内。

12.redis事物的了解CAS(check-and-set 操做实现乐观锁 )?

和众多其它数据库同样,Redis做为NoSQL数据库也一样提供了事务机制。在Redis中,MULTI/EXEC/DISCARD/WATCH这四个命令是咱们实现事务的基石。相信对有关系型数据库开发经验的开发者而言这一律念并不陌生,即使如此,咱们仍是会简要的列出Redis中事务的实现特征:

1). 在事务中的全部命令都将会被串行化的顺序执行,事务执行期间,Redis不会再为其它客户端的请求提供任何服务,从而保证了事物中的全部命令被原子的执行。

2). 和关系型数据库中的事务相比,在Redis事务中若是有某一条命令执行失败,其后的命令仍然会被继续执行。

3). 咱们能够经过MULTI命令开启一个事务,有关系型数据库开发经验的人能够将其理解为"BEGIN TRANSACTION"语句。在该语句以后执行的命令都将被视为事务以内的操做,最后咱们能够经过执行EXEC/DISCARD命令来提交/回滚该事务内的全部操做。这两个Redis命令可被视为等同于关系型数据库中的COMMIT/ROLLBACK语句。

4). 在事务开启以前,若是客户端与服务器之间出现通信故障并致使网络断开,其后全部待执行的语句都将不会被服务器执行。然而若是网络中断事件是发生在客户端执行EXEC命令以后,那么该事务中的全部命令都会被服务器执行。

5). 当使用Append-Only模式时,Redis会经过调用系统函数write将该事务内的全部写操做在本次调用中所有写入磁盘。然而若是在写入的过程当中出现系统崩溃,如电源故障致使的宕机,那么此时也许只有部分数据被写入到磁盘,而另一部分数据却已经丢失。Redis服务器会在从新启动时执行一系列必要的一致性检测,一旦发现相似问题,就会当即退出并给出相应的错误提示。此时,咱们就要充分利用Redis工具包中提供的redis-check-aof工具,该工具能够帮助咱们定位到数据不一致的错误,并将已经写入的部分数据进行回滚。修复以后咱们就能够再次从新启动Redis服务器了。

13.WATCH命令和基于CAS的乐观锁?

在Redis的事务中,WATCH命令可用于提供CAS(check-and-set)功能。假设咱们经过WATCH命令在事务执行以前监控了多个Keys,假若在WATCH以后有任何Key的值发生了变化,EXEC命令执行的事务都将被放弃,同时返回Null multi-bulk应答以通知调用者事务执行失败。例如,咱们再次假设Redis中并未提供incr命令来完成键值的原子性递增,若是要实现该功能,咱们只能自行编写相应的代码。

其伪码以下:

val = GET mykey  
val = val + 1  
SET mykey $val

以上代码只有在单链接的状况下才能够保证执行结果是正确的,由于若是在同一时刻有多个客户端在同时执行该段代码,那么就会出现多线程程序中常常出现的一种错误场景--竞态争用(race condition)。

好比,客户端A和B都在同一时刻读取了mykey的原有值,假设该值为10,此后两个客户端又均将该值加一后set回Redis服务器,这样就会致使mykey的结果为11,而不是咱们认为的12。为了解决相似的问题,咱们须要借助WATCH命令的帮助,见以下代码:

WATCH mykey  
val = GET mykey  
val = val + 1  
MULTI  
SET mykey $val  
EXEC

和此前代码不一样的是,新代码在获取mykey的值以前先经过WATCH命令监控了该键,此后又将set命令包围在事务中,这样就能够有效的保证每一个链接在执行EXEC以前,若是当前链接获取的mykey的值被其它链接的客户端修改,那么当前链接的EXEC命令将执行失败。这样调用者在判断返回值后就能够获悉val是否被从新设置成功。

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

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

这时候对方会告诉你说你回答得不错,而后接着问若是在setnx以后执行expire以前进程意外crash或者要重启维护了,那会怎么样?

这时候你要给予惊讶的反馈:唉,是喔,这个锁就永远得不到释放了。紧接着你须要抓一抓本身得脑壳,故做思考片刻,好像接下来的结果是你主动思考出来的,而后回答:我记得set指令有很是复杂的参数,这个应该是能够同时把setnx和expire合成一条指令来用的!对方这时会显露笑容,内心开始默念:摁,这小子还不错。

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

使用keys指令能够扫出指定模式的key列表。

对方接着追问:若是这个redis正在给线上的业务提供服务,那使用keys指令会有什么问题?

这个时候你要回答redis关键的一个特性:redis的单线程的。keys指令会致使线程阻塞一段时间,线上服务会停顿,直到指令执行完毕,服务才能恢复。这个时候可使用scan指令,scan指令能够无阻塞的提取出指定模式的key列表,可是会有必定的重复几率,在客户端作一次去重就能够了,可是总体所花费的时间会比直接用keys指令长。

16.使用过Redis作异步队列么,你是怎么用的?

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

若是对方追问可不能够不用sleep呢?list还有个指令叫blpop,在没有消息的时候,它会阻塞住直到消息到来。

若是对方追问能不能生产一次消费屡次呢?使用pub/sub主题订阅者模式,能够实现1:N的消息队列。

若是对方追问pub/sub有什么缺点?在消费者下线的状况下,生产的消息会丢失,得使用专业的消息队列如rabbitmq等。

若是对方追问redis如何实现延时队列?我估计如今你很想把面试官一棒打死若是你手上有一根棒球棍的话,怎么问的这么详细。可是你很克制,而后神态自若的回答道:使用sortedset,拿时间戳做为score,消息内容做为key调用zadd来生产消息,消费者用zrangebyscore指令获取N秒以前的数据轮询进行处理。

到这里,面试官暗地里已经对你竖起了大拇指。可是他不知道的是此刻你却竖起了中指,在椅子背后。

17.若是有大量的key须要设置同一时间过时,通常须要注意什么?

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

18.Redis如何作持久化的?

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

对方追问那若是忽然机器掉电会怎样?取决于aof日志sync属性的配置,若是不要求性能,在每条写指令时都sync一下磁盘,就不会丢失数据。可是在高性能的要求下每次都sync是不现实的,通常都使用定时sync,好比1s1次,这个时候最多就会丢失1s的数据。

对方追问bgsave的原理是什么?你给出两个词汇就能够了,fork和cow。fork是指redis经过建立子进程来进行bgsave操做,cow指的是copy on write,子进程建立后,父子进程共享数据段,父进程继续提供读写服务,写脏的页面数据会逐渐和子进程分离开来。

19.Pipeline有什么好处,为何要用pipeline?

能够将屡次IO往返的时间缩减为一次,前提是pipeline执行的指令之间没有因果相关性。使用redis-benchmark进行压测的时候能够发现影响redis的QPS峰值的一个重要因素是pipeline批次指令的数目。

20.Redis的同步机制了解么?

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

21.是否使用过Redis集群,集群的原理是什么?

Redis Sentinal着眼于高可用,在master宕机时会自动将slave提高为master,继续提供服务。

Redis Cluster着眼于扩展性,在单个redis内存不足时,使用Cluster进行分片存储。