Redis基础知识点面试手册

Redis基础知识点面试手册

 

 

 

 

 

 

 

 

 

 

 

基础

概述

Redis 是速度很是快的非关系型(NoSQL)内存键值数据库,能够存储键和五种不一样类型的值之间的映射。

  • 键的类型只能为字符串

  • 值支持的五种类型数据类型为:字符串、列表、集合、有序集合、散列表。

Redis 支持不少特性,例如将内存中的数据持久化到硬盘中,使用复制来扩展读性能,使用分片来扩展写性能。

数据类型

数据类型能够存储的值操做STRING字符串、整数或者浮点数对整个字符串或者字符串的其中一部分执行操做</br> 对整数和浮点数执行自增或者自减操做LIST列表从两端压入或者弹出元素</br> 读取单个或者多个元素</br> 进行修剪,只保留一个范围内的元素SET无序集合添加、获取、移除单个元素</br> 检查一个元素是否存在于集合中</br> 计算交集、并集、差集</br> 从集合里面随机获取元素HASH包含键值对的无序散列表添加、获取、移除单个键值对</br> 获取全部键值对</br> 检查某个键是否存在ZSET有序集合添加、获取、删除元素</br> 根据分值范围或者成员来获取元素</br> 计算一个键的排名

STRING

 

image.png

> set hello world
OK
> get hello
"world"
> del hello
(integer) 1
> get hello
(nil)

LIST

 

image.png

> rpush list-key item
(integer) 1
> rpush list-key item2
(integer) 2
> rpush list-key item
(integer) 3

> lrange list-key 0 -1
1) "item"
2) "item2"
3) "item"

> lindex list-key 1
"item2"

> lpop list-key
"item"

> lrange list-key 0 -1
1) "item2"
2) "item"

SET

 

image.png

> sadd set-key item
(integer) 1
> sadd set-key item2
(integer) 1
> sadd set-key item3
(integer) 1
> sadd set-key item
(integer) 0

> smembers set-key
1) "item"
2) "item2"
3) "item3"

> sismember set-key item4
(integer) 0
> sismember set-key item
(integer) 1

> srem set-key item2
(integer) 1
> srem set-key item2
(integer) 0

> smembers set-key
1) "item"
2) "item3"

HASH

 

image.png

> hset hash-key sub-key1 value1
(integer) 1
> hset hash-key sub-key2 value2
(integer) 1
> hset hash-key sub-key1 value1
(integer) 0

> hgetall hash-key
1) "sub-key1"
2) "value1"
3) "sub-key2"
4) "value2"

> hdel hash-key sub-key2
(integer) 1
> hdel hash-key sub-key2
(integer) 0

> hget hash-key sub-key1
"value1"

> hgetall hash-key
1) "sub-key1"
2) "value1"

ZSET(SORTEDSET)

 

image.png

> zadd zset-key 728 member1
(integer) 1
> zadd zset-key 982 member0
(integer) 1
> zadd zset-key 982 member0
(integer) 0

> zrange zset-key 0 -1 withscores
1) "member1"
2) "728"
3) "member0"
4) "982"

> zrangebyscore zset-key 0 800 withscores
1) "member1"
2) "728"

> zrem zset-key member1
(integer) 1
> zrem zset-key member1
(integer) 0

> zrange zset-key 0 -1 withscores
1) "member0"
2) "982"

zset是set的一个升级版本,他在set的基础上增长了一个顺序属性,这一属性在添加修改元素的时候能够指定,每次指定后,zset会自动从新按新的值调整顺序。 能够对指定键的值进行排序权重的设定,它应用排名模块比较多。

跳跃表(shiplist)是实现sortset(有序集合)的底层数据结构之一

另外还能够用 Sorted Sets 来作带权重的队列,好比普通消息的 score 为1,重要消息的 score 为2,而后工做线程能够选择按 score的倒序来获取工做任务,让重要的任务优先执行。

数据结构

字典

dictht 是一个散列表结构,使用拉链法保存哈希冲突的 dictEntry。

/* This is our hash table structure. Every dictionary has two of this as we
* implement incremental rehashing, for the old to the new table. */
typedef struct dictht {
   dictEntry **table;
   unsigned long size;
   unsigned long sizemask;
   unsigned long used;
} dictht;
typedef struct dictEntry {
   void *key;
   union {
       void *val;
       uint64_t u64;
       int64_t s64;
       double d;
  } v;
   struct dictEntry *next;
} dictEntry;

Redis 的字典 dict 中包含两个哈希表 dictht,这是为了方便进行 rehash 操做。

在扩容时,将其中一个 dictht 上的键值对 rehash 到另外一个 dictht 上面,完成以后释放空间并交换两个 dictht 的角色。

typedef struct dict {
   dictType *type;
   void *privdata;
   dictht ht[2];
   long rehashidx; /* rehashing not in progress if rehashidx == -1 */
   unsigned long iterators; /* number of iterators currently running */
} dict;

rehash 操做不是一次性完成,而是采用渐进方式,这是为了不一次性执行过多的 rehash 操做给服务器带来过大的负担。

渐进式 rehash 经过记录 dict 的 rehashidx 完成,它从 0 开始,而后每执行一次 rehash 都会递增。例如在一次 rehash 中,要把 dict[0] rehash 到 dict[1],这一次会把 dict[0] 上 table[rehashidx] 的键值对 rehash 到 dict[1] 上,dict[0] 的 table[rehashidx] 指向 null,并令 rehashidx++。

在 rehash 期间,每次对字典执行添加、删除、查找或者更新操做时,都会执行一次渐进式 rehash。

采用渐进式 rehash 会致使字典中的数据分散在两个 dictht 上,所以对字典的操做也须要到对应的 dictht 去执行。

/* Performs N steps of incremental rehashing. Returns 1 if there are still
* keys to move from the old to the new hash table, otherwise 0 is returned.
*
* Note that a rehashing step consists in moving a bucket (that may have more
* than one key as we use chaining) from the old to the new hash table, however
* since part of the hash table may be composed of empty spaces, it is not
* guaranteed that this function will rehash even a single bucket, since it
* will visit at max N*10 empty buckets in total, otherwise the amount of
* work it does would be unbound and the function may block for a long time. */
int dictRehash(dict *d, int n) {
   int empty_visits = n * 10; /* Max number of empty buckets to visit. */
   if (!dictIsRehashing(d)) return 0;

   while (n-- && d->ht[0].used != 0) {
       dictEntry *de, *nextde;

       /* Note that rehashidx can't overflow as we are sure there are more
        * elements because ht[0].used != 0 */
       assert(d->ht[0].size > (unsigned long) d->rehashidx);
       while (d->ht[0].table[d->rehashidx] == NULL) {
           d->rehashidx++;
           if (--empty_visits == 0) return 1;
      }
       de = d->ht[0].table[d->rehashidx];
       /* Move all the keys in this bucket from the old to the new hash HT */
       while (de) {
           uint64_t h;

           nextde = de->next;
           /* Get the index in the new hash table */
           h = dictHashKey(d, de->key) & d->ht[1].sizemask;
           de->next = d->ht[1].table[h];
           d->ht[1].table[h] = de;
           d->ht[0].used--;
           d->ht[1].used++;
           de = nextde;
      }
       d->ht[0].table[d->rehashidx] = NULL;
       d->rehashidx++;
  }

   /* Check if we already rehashed the whole table... */
   if (d->ht[0].used == 0) {
       zfree(d->ht[0].table);
       d->ht[0] = d->ht[1];
       _dictReset(&d->ht[1]);
       d->rehashidx = -1;
       return 0;
  }

   /* More to rehash... */
   return 1;
}

跳跃表

什么是跳跃表?(程序员小灰)http://blog.jobbole.com/111731/

https://blog.csdn.net/qq910894904/article/details/37883953

来看看跳跃表的复杂度分析:

  • 空间复杂度: O(n) (指望)

  • 跳跃表高度: O(logn) (指望)

相关操做的时间复杂度:

  • 查找: O(logn) (指望)

  • 插入: O(logn) (指望)

  • 删除: O(logn) (指望)

其效率可比拟于二叉查找树(对于大于数操做须要O(log n)平均时间),而且不须要像二叉树同样过段时间从新平衡。

它是按层建造的。底层是一个普通的有序链表。每一个更高层都充当下面列表的“快速跑道”,这里在层i中的元素按几率l/p出如今层i+1中。

平均起来,每一个元素都在p/(p-1)个列表中出现,而最高层的元素(一般是在跳跃列表前段的一个特殊的头元素)在O(logp n)个列表中出现。

调节p的大小能够在内存消耗和时间消耗上进行折中。

 

image.png

在查找时,从上层指针开始查找,找到对应的区间以后再到下一层去查找。下图演示了查找 22 的过程。

 

image.png

与红黑树等平衡树相比,跳跃表具备如下优势:

  • 插入速度很是快速,由于不须要进行旋转等操做来维护平衡性;

  • 更容易实现

  • 支持无锁操做

使用场景

会话缓存

在分布式场景下具备多个应用服务器,可使用 Redis 来统一存储这些应用服务器的会话信息。

当应用服务器再也不存储用户的会话信息,也就再也不具备状态,一个用户能够请求任意一个应用服务器。

缓存

将热点数据放到内存中,设置内存的最大使用量以及过时淘汰策略来保证缓存的命中率。

计数器

能够对 String 进行自增自减运算,从而实现计数器功能。

Redis 这种内存型数据库的读写性能很是高,很适合存储频繁读写的计数量。

查找表

例如 DNS 记录就很适合使用 Redis 进行存储。

查找表和缓存相似,也是利用了 Redis 快速的查找特性。可是查找表的内容不能失效,而缓存的内容能够失效,由于缓存不做为可靠的数据来源。

消息队列

List 是一个双向链表,能够经过 lpop 和 lpush 写入和读取消息。

不过最好使用 Kafka、RabbitMQ 等消息中间件。

分布式 Session

多个应用服务器的 Session 都存储到 Redis 中来保证 Session 的一致性。

分布式锁

分布式锁实现 在分布式场景下,没法使用单机环境下的锁来对多个节点上的进程进行同步。

可使用 Reids 自带的 SETNX 命令实现分布式锁,除此以外,还可使用官方提供的 RedLock 分布式锁实现。

其它

Set 能够实现交集、并集等操做,从而实现共同好友等功能。

ZSet 能够实现有序性操做,从而实现排行榜等功能。

Redis 与 Memcached 对比

 

image.png

数据类型

Memcached 仅支持字符串类型,而 Redis 支持五种不一样种类的数据类型,使得它能够更灵活地解决问题。

数据持久化

Redis 支持两种持久化策略:RDB 快照和 AOF 日志,而 Memcached 不支持持久化。

单线程

https://www.cnblogs.com/syyong/p/6231326.html

Redis快的主要缘由是:

  • 彻底基于内存

  • 数据结构简单,对数据操做也简单

  • 使用多路 I/O 复用模型

  • 单进程单线程好处

    • 代码更清晰,处理逻辑更简单

    • 不用去考虑各类锁的问题,不存在加锁释放锁操做,没有由于可能出现死锁而致使的性能消耗

    • 不存在多进程或者多线程致使的切换而消耗CPU

 

  • 单进程单线程弊端

    • 没法发挥多核CPU性能,不过能够经过在单机开多个Redis实例来完善;

 

  • 其余一些优秀的开源软件采用的模型

    • 多进程单线程模型:Nginx

    • 单进程多线程模型:Memcached

 

 

 

分布式

Memcached 不支持分布式,只能经过在客户端使用像一致性哈希这样的分布式算法来实现分布式存储,这种方式在存储和查询时都须要先在客户端计算一次数据所在的节点

Redis Cluster 实现了分布式的支持。采用虚拟槽。(为什么不须要计算了?不懂)

内存管理机制

在 Redis 中,并非全部数据都一直存储在内存中,能够将一些好久没用的 value 交换到磁盘。而Memcached 的数据则会一直在内存中。

Memcached 将内存分割成特定长度的块来存储数据,以彻底解决内存碎片的问题,可是这种方式会使得内存的利用率不高,例如块的大小为 128 bytes,只存储 100 bytes 的数据,那么剩下的 28 bytes 就浪费掉了。

键的过时时间

Redis 能够为每一个键设置过时时间,当键过时时,会自动删除该键。

对于散列表这种容器,只能为整个键设置过时时间(整个散列表),而不能为键里面的单个元素设置过时时间。

数据淘汰策略

能够设置内存最大使用量,当内存使用量超过期施行淘汰策略,具体有 6 种淘汰策略。

策略描述volatile-lru从已设置过时时间的数据集中挑选最近最少使用的数据淘汰volatile-ttl从已设置过时时间的数据集中挑选将要过时的数据淘汰volatile-random从已设置过时时间的数据集中任意选择数据淘汰allkeys-lru从全部数据集中挑选最近最少使用的数据淘汰allkeys-random从全部数据集中任意选择数据进行淘汰noeviction禁止驱逐数据

做为内存数据库,出于对性能和内存消耗的考虑,Redis 的淘汰算法实际实现上并不是针对全部 key,而是抽样一小部分而且从中选出被淘汰的 key。

使用 Redis 缓存数据时,为了提升缓存命中率,须要保证缓存数据都是热点数据。能够将内存最大使用量设置为热点数据占用的内存量,而后启用 allkeys-lru 淘汰策略,将最近最少使用的数据淘汰。

Redis 4.0 引入了 volatile-lfu 和 allkeys-lfu 淘汰策略,LFU 策略经过统计访问频率,将访问频率最少的键值对淘汰。

持久化

Redis 是内存型数据库,为了保证数据在断电后不会丢失,须要将内存中的数据持久化到硬盘上。

https://my.oschina.net/davehe/blog/174662

RDB 快照持久化

将某个时间点的全部数据都存放到硬盘上。

能够将快照复制到其它服务器从而建立具备相同数据的服务器副本。

若是系统发生故障,将会丢失最后一次建立快照以后的数据。

若是数据量很大,保存快照的时间会很长。

AOF 持久化

将写命令添加到 AOF 文件(Append Only File)的末尾。

对硬盘的文件进行写入时,写入的内容首先会被存储到缓冲区,而后由操做系统决定何时将该内容同步到硬盘,用户能够调用 file.flush() 方法请求操做系统尽快将缓冲区存储的数据同步到硬盘。能够看出写入文件的数据不会当即同步到硬盘上,在将写命令添加到 AOF 文件时,要根据需求来保证什么时候同步到硬盘上。

有如下同步选项:

选项同步频率always每一个写命令都同步everysec每秒同步一次no让操做系统来决定什么时候同步

  • always 选项会严重减低服务器的性能;

  • everysec 选项比较合适,能够保证系统奔溃时只会丢失一秒左右的数据,而且 Redis 每秒执行一次同步对服务器性能几乎没有任何影响;

  • no 选项并不能给服务器性能带来多大的提高,并且也会增长系统奔溃时数据丢失的数量。

随着服务器写请求的增多,AOF 文件会愈来愈大。Redis 提供了一种将 AOF 重写的特性,可以去除 AOF 文件中的冗余写命令,使得 AOF 文件的体积不会超出保存数据集状态所需的实际大小。

若是 AOF 文件出错了,怎么办?

服务器可能在程序正在对 AOF 文件进行写入时停机, 若是停机形成了 AOF 文件出错(corrupt), 那么 Redis 在重启时会拒绝载入这个 AOF 文件, 从而确保数据的一致性不会被破坏。

发布与订阅

订阅者订阅了频道以后,发布者向频道发送字符串消息会被全部订阅者接收到。

某个客户端使用 SUBSCRIBE 订阅一个频道,其它客户端可使用 PUBLISH 向这个频道发送消息。

发布与订阅模式和观察者模式有如下不一样:

  • 观察者模式中,观察者和主题都知道对方的存在;而在发布与订阅模式中,发布者与订阅者不知道对方的存在,它们之间经过频道进行通讯。

  • 观察者模式是同步的,当事件触发时,主题会去调用观察者的方法;而发布与订阅模式是异步的;

事务

http://www.runoob.com/redis/redis-transactions.html

事务中的多个命令被一次性发送给服务器,而不是一条一条发送,这种方式被称为流水线,它能够减小客户端与服务器之间的网络通讯次数从而提高性能。

Redis 最简单的事务实现方式是使用 MULTI 和 EXEC 命令将事务操做包围起来。

它先以 MULTI 开始一个事务, 而后将多个命令入队到事务中, 最后由 EXEC 命令触发事务, 一并执行事务中的全部命令

单个 Redis 命令的执行是原子性的,但 Redis 没有在事务上增长任何维持原子性的机制,因此 Redis 事务的执行并非原子性的。

事务能够理解为一个打包的批量执行脚本,但批量指令并不是原子化的操做,中间某条指令的失败不会致使前面已作指令的回滚,也不会形成后续的指令不作。

事件

Redis 服务器是一个事件驱动程序。

文件事件

服务器经过套接字与客户端或者其它服务器进行通讯,文件事件就是对套接字操做的抽象。

 

 

Redis 基于 Reactor 模式开发了本身的网络时间处理器,使用 I/O 多路复用程序来同时监听多个套接字,并将到达的时间传送给文件事件分派器,分派器会根据套接字产生的事件类型调用响应的时间处理器。

时间事件

服务器有一些操做须要在给定的时间点执行,时间事件是对这类定时操做的抽象。

时间事件又分为:

  • 定时事件:是让一段程序在指定的时间以内执行一次;

  • 周期性事件:是让一段程序每隔指定时间就执行一次。

Redis 将全部时间事件都放在一个无序链表中,经过遍历整个链表查找出已到达的时间事件,并调用响应的事件处理器。

事件的调度与执行

服务器须要不断监听文件事件的套接字才能获得待处理的文件事件,可是不能一直监听,不然时间事件没法在规定的时间内执行,所以监听时间应该根据距离如今最近的时间事件来决定。

事件调度与执行由 aeProcessEvents 函数负责,伪代码以下:

def aeProcessEvents():
   # 获取到达时间离当前时间最接近的时间事件
   time_event = aeSearchNearestTimer()
   # 计算最接近的时间事件距离到达还有多少毫秒
   remaind_ms = time_event.when - unix_ts_now()
   # 若是事件已到达,那么 remaind_ms 的值可能为负数,将它设为 0
   if remaind_ms < 0:
       remaind_ms = 0
   # 根据 remaind_ms 的值,建立 timeval
   timeval = create_timeval_with_ms(remaind_ms)
   # 阻塞并等待文件事件产生,最大阻塞时间由传入的 timeval 决定
   aeApiPoll(timeval)
   # 处理全部已产生的文件事件
   procesFileEvents()
   # 处理全部已到达的时间事件
   processTimeEvents()

将 aeProcessEvents 函数置于一个循环里面,加上初始化和清理函数,就构成了 Redis 服务器的主函数,伪代码以下:

def main():
   # 初始化服务器
   init_server()
   # 一直处理事件,直到服务器关闭为止
   while server_is_not_shutdown():
       aeProcessEvents()
   # 服务器关闭,执行清理操做
   clean_server()

从事件处理的角度来看,服务器运行流程以下:

 

image.png

复制(加强读性能)

经过使用 slaveof host port 命令来让一个服务器成为另外一个服务器的从服务器。

一个从服务器只能有一个主服务器,而且不支持主主复制。

链接过程

  1. 主服务器建立快照文件,发送给从服务器,并在发送期间使用缓冲区记录执行的写命令。快照文件发送完毕以后,开始向从服务器发送存储在缓冲区中的写命令;

  2. 从服务器丢弃全部旧数据,载入主服务器发来的快照文件,以后从服务器开始接受主服务器发来的写命令;

  3. 主服务器每执行一次写命令,就向从服务器发送相同的写命令。

主从链

随着负载不断上升,主服务器可能没法很快地更新全部从服务器,或者从新链接和从新同步从服务器将致使系统超载。为了解决这个问题,能够建立一个中间层来分担主服务器的复制工做。中间层的服务器是最上层服务器的从服务器,又是最下层服务器的主服务器。

 

image.png

Sentinel(哨兵)

Sentinel(哨兵)能够监听主服务器,并在主服务器进入下线状态时,自动从从服务器中选举出新的主服务器。

分片(加强写性能)

分片是将数据划分为多个部分的方法,能够将数据存储到多台机器里面.

主要有三种分片方式:

  • 客户端分片:客户端使用一致性哈希等算法决定键应当分布到哪一个节点。

  • 代理分片:将客户端请求发送到代理上,由代理转发请求到正确的节点上。

  • 服务器分片:Redis Cluster。

Redis-cluster (Redis分布式)

https://blog.csdn.net/chunlongyu/article/details/53339288

但从Redis 3.0开始,引入了Redis Cluster,今后Redis进入了真正的“分布式集群“时代。

 

image.png

P2P架构

 

image.png

为何是16384?

很显然,咱们须要维护节点和槽之间的映射关系,每一个节点须要知道本身有哪些槽,而且须要在结点之间传递这个消息。

为了节省存储空间,每一个节点用一个Bitmap来存放其对应的槽: 2k = 2*1024 *8 = 16384,也就是说,每一个结点用2k的内存空间,总共16384个比特位,就能够存储该结点对应了哪些槽。而后这2k的信息,经过Gossip协议,在结点之间传递。

客户端存储路由信息

对于客户端来讲,维护了一个路由表:每一个槽在哪台机器上。这样存储(key, value)时,根据key计算出槽,再根据槽找到机器。

无损扩容

虽然Hash环(Memcached)能够减小扩容时失效的key的数量,但毕竟有丢失。而在redis-cluster中,当新增机器以后,槽会在机器之间从新分配,同时被影响的数据会自动迁移,从而作到无损扩容。

这里能够结合补充知识点-缓存-一致性哈希来一块儿理解。虚拟槽改变的是槽的分配,一致性哈希则会使旁边的节点key失效。

主从复制

redis-cluster也引入了master-slave机制,从而提供了fail-over机制,这很大程度上解决了“缓存雪崩“的问题。关于这个,后面有机会再详细阐述。

https://blog.csdn.net/men_wen/article/details/72853078

 

image.png

Redis集群相对单机在功能上有必定限制。

key批量操做支持有限。如:MSET``MGET,目前只支持具备相同slot值的key执行批量操做

key事务操做支持有限。支持多key在同一节点上的事务操做,不支持分布在多个节点的事务功能。

key做为数据分区的最小粒度,所以不能将一个大的键值对象映射到不一样的节点。如:hash、list。

不支持多数据库空间。单机下Redis支持16个数据库,集群模式下只能使用一个数据库空间,即db 0。

复制结构只支持一层,不支持嵌套树状复制结构。

十4、一个简单的论坛系统分析

该论坛系统功能以下:

  • 能够发布文章;

  • 能够对文章进行点赞;

  • 在首页能够按文章的发布时间或者文章的点赞数进行排序显示。

文章信息

文章包括标题、做者、赞数等信息,在关系型数据库中很容易构建一张表来存储这些信息,在 Redis 中可使用 HASH 来存储每种信息以及其对应的值的映射。

Redis 没有关系型数据库中的表这一律念来将同种类型的数据存放在一块儿,而是使用命名空间的方式来实现这一功能。键名的前面部分存储命名空间,后面部分的内容存储 ID,一般使用 : 来进行分隔。例以下面的 HASH 的键名为 article:92617,其中 article 为命名空间,ID 为 92617。

 

image.png

点赞功能

当有用户为一篇文章点赞时,除了要对该文章的 votes 字段进行加 1 操做,还必须记录该用户已经对该文章进行了点赞,防止用户点赞次数超过 1。能够创建文章的已投票用户集合来进行记录。

为了节约内存,规定一篇文章发布满一周以后,就不能再对它进行投票,而文章的已投票集合也会被删除,能够为文章的已投票集合设置一个一周的过时时间就能实现这个规定。

 

image.png

对文章进行排序

为了按发布时间和点赞数进行排序,能够创建一个文章发布时间的有序集合和一个文章点赞数的有序集合。(下图中的 score 就是这里所说的点赞数;下面所示的有序集合分值并不直接是时间和点赞数,而是根据时间和点赞数间接计算出来的)

 

image.png

Redis经典面试题

Redis有哪些数据结构?

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

若是对方追问pub/sub有什么缺点?

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

若是对方追问redis如何实现延时队列?

使用sortedset,拿时间戳做为score,消息内容做为key调用zadd来生产消息,消费者用zrangebyscore指令获取N秒以前的数据轮询进行处理。

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

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

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,子进程建立后,父子进程共享数据段,父进程继续提供读写服务,写脏的页面数据会逐渐和子进程分离开来。

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

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

Redis的同步机制了解么?

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

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

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

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

参考与拓展阅读

相关文章
相关标签/搜索