面试整理——Redis

Redis

一. 综合问题

位于速度相差较大的两种硬件之间,用于协调两者数据传输速度差异的结构,可称之为缓存。比如做一次内存寻址大概需要 100ns,而做一次磁盘的查找则需要 10ms,差了万倍。缓冲区则是一块临时存储数据的区域,用以弥补高速和低速设备通信时的速度差。

  • 缓存比较适合于读多写少的业务场景,并且数据最好带有一定的热点属性。
  • 缓存会给整体系统带来复杂度,并且会有数据不一致的风险。
  • 缓存通常使用内存作为存储介质,但是内存并不是无限的。
  • 缓存会给运维也带来一定的成本。

缓存分类:

  1. 静态缓存
  2. 分布式缓存
  3. 热点本地缓存:热点本地缓存主要部署在应用服务器的代码中,用于阻挡热点查询对于分布式缓存节点或者数据库的压力。

我们在选择策略时也需要考虑诸多的因素,比如说,缓存中是否有可能被写入脏数据,策略的读写性能如何,是否存在缓存命中率下降的情况等等

Cache Aside(旁路缓存)策略:更新数据时,先更新数据库数据,缓存不做更新,而是删除后重新读取库中最新数据。

  • 读策略:
    • 从缓存中读取数据;
    • 如果缓存命中,则直接返回数据;
    • 如果缓存不命中,则从数据库中查询数据;
    • 查询到数据后,将数据写入到缓存中,并且返回给用户。
  • 写策略:
    • 更新数据库中的记录;
    • 删除缓存记录。

问:你为什么要在项目中使用Redis,如何使用的呢?

问:简单描述下 Redis?支持哪些数据结构?有什么优点?

C语言开发的一个基于高性能键值对开源内存数据库,是一个非关系型数据库(NoSQL)。因为是纯内存操作,所以性能非常出色,每秒可以处理10W次读写操作。

支持八种数据结构:字符串String,哈希Hash,数组List,集合Set,有序集合ZSet,位图BitMaps,基数统计HyperLogLig,地理信息定位GEO。

Redis的优势有:

  • 支持多种数据结构和算法,所以应用面广
  • 将数据存放在内存,所以有高效的读写性能
  • 内存中的数据通过快照和日志的方式保存在硬盘,所以不易丢失

问:redis 最适合的场景?

  1. 缓存模块:常用于大型网站来加速数据访问和缓解后端数据库压力。Redis提供了键值过期时间设置,也提供了灵活控制最大内存和内存溢出后的淘汰策略。
    • 会话缓存(session cache),Redis提供持久化。
    • 全页缓存(FPC),即使重启了 Redis 实例,因为有磁盘的持久化,用户也不会看到页面加载速度的下降,这是一个极大改进,类似 PHP 本地 FPC
  2. 排行榜:几乎所有网站都需要各种规则的排行榜,或是依据热度排行,或是发布时间,或是结合各种复杂维度计算。Redis提供了列表和有序集合数据结构,合理的使用这些结构可以很方便的构建各种排行榜系统。如 ZRANGE user_scores 0 10 WITHSCORES
  3. 计数器:如一些视频网站的播放数,电商网站的浏览数,对于数据的实时性有较高的要求,如果并发量很大时,传统关系型数据库很难做好这一工作。Redis天然支持计数功能,且性能优越。
  4. 社交网络:比如赞/踩、粉丝、共同好友/喜好、推送、下拉刷新等常见功能,由于社交网站访问量通常较大,且传统关系型数据库不太适合存储这种类型的数据,而Redis提供的数据结构比较容易实现这些功能。
  5. 消息队列:大型网站的必备基础组件,因为其具有业务解耦、非实时业务削峰等特性。Redis提供了发布订阅功能和阻塞队列功能,虽然相比专业的消息队列还有所欠缺,但可以满足大部分基础需求。

问:redis单线程为什么执行速度这么快?

  1. 纯内存操作,避免大量访问数据库,减少直接读取磁盘数据,redis将数据储存在内存里面,读写数据的时候都不会受到硬盘 I/O 速度的限制,所以速度快。
  2. 单线程操作,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗。
  3. 采用了非阻塞I/O多路复用机制,从而支撑起大量的网络请求,减少了线程切换时上下文切换和竞争。

问:Redis 是单线程的,如何提高多核 CPU 的利用率?

Redis是单线程指处理网络请求时是一个线程来处理,但Redis运行时绝非只使用一个线程。可以在同一个服务器部署多个 Redis 的实例,并把他们当作不同的服务器来使用,在某些时候,无论如何一个服务器是不够的,所以,如果想使用多个 CPU,可以考虑一下分片(shard)。

问:Redis高并发快的原因?

  1. 内存数据库,读写速度快。
  2. 采用了非阻塞I/O多路复用机制。
  3. 单线程模型,保证了操作原子性,也避免了不必要的上下文切换和竞争条件。
  4. 数据结构有做底层优化,如Hash、压缩表、压缩存储、跳跃表等。
  5. Redis实现了高效率的事件分离器,内部采用非阻塞的执行方式,吞吐能力很高。

问:Redis有哪些Java客户端?其中Jedis 与 Redisson 对比优缺点?

客户端包括:Redisson、Jedis、lettuce 等,官方推荐使用 Redisson。

  • Jedis 是 Redis 的 Java 实现的客户端,其 API 提供了比较全面的 Redis 命令的支持;
  • Redisson实现了分布式和可扩展的 Java 数据结构,和 Jedis 相比,功能较为简单,不支持字符串操作,不支持排序、事务、管道、分区等 Redis 特性。Redisson 的宗旨是促进使用者对 Redis 的关注分离,从而让使用者能够将精力更集中地放在处理业务逻辑上。

问:Redis是单线程?redis的单线程特性有什么优缺点?

redis的单进程单线程模型;延伸到IO多路复用;一路延伸到epoll、poll、select(主要讲了三者使用的数据结构区别以及各自的特点);

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

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

问:讲一下平时使用Redis遇到过哪些问题?如何解决的?


二. 底层数据结构

问:redis支持哪些数据结构?

string(字符串)、hash(哈希)、list(列表)、set(集合)、zset(有序集合)、Bitmaps(位图)、HyperLogLog(做基数统计)、GEO(地理信息定位)等多种数据结构和算法

问:Redis各种数据结构的使用场景、内部编码及底层实现?

String

  • 【应用场景】:

    • 缓存
    • 计数器
    • 共享Session
    • 限速
    • 分布式锁
    • 分布式ID
  • 【内部编码】:最大512M

    • int:8个字节的长整型。
    • embstr:小于等于39个字节的字符串。
    • raw:大于39个字节的字符串。
  • 【底层实现】:Simple dynamic string(SDS)的数据结构:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    struct sdshdr{
    //记录buf数组中已使用字节的数量
    //等于SDS保存字符串的长度
    int len;
    //记录buf数组中未使用字节的数量
    int free
    //字节数组,用于保存字符串
    char buf[];
    }

    优点(都是C字符串的缺点弥补):

    • 不会出现字符串变更造成的内存溢出问题。
    • 获取字符串长度时间复杂度为1,C字符串没有长度信息(以 \0 来明确表示结尾),必须遍历字符串,时间复杂度为O(n)。
    • 空间预分配, 惰性空间释放free字段,会默认留够一定的空间防止多次重分配内存。

Hash

  • 【应用场景】:
    • 保存结构体信息,可部分获取不用序列化所有字段。如保存用户类,原生字符串每个属性都要对应一个键,序列化字符串需要序列与反序列的开销,而哈希类型只须一个键即可存放一个用户信息。
    • 保存一些键值对。
  • 【内部编码】:
    • ziplist(压缩列表):当哈希类型元素个数小于 hash-max-ziplist-entries 配置(默认为512个),同时所有值都小于 hash-max-ziplist-value 配置(默认为6字节)时,Redis使用ziplist作为哈希的内部实现,ziplist使用更加紧凑的结构实现多个元素的连续存储,所以相比hashtable会更节省空间。
    • hashtable(哈希表):当哈希类型无法满足ziplist的条件时,Redis会使用hashtable作为哈希的内部实现,这种情况ziplist的读写效率会下降,而hashtable读写时间复杂度为 O(1)
  • 【底层实现】:在数组+链表的基础上,进行了一些rehash优化。
    • Reids的Hash采用链地址法来处理冲突,然后它没有使用红黑树优化
    • 哈希表节点采用单链表结构
    • rehash优化 (采用分而治之的思想,将庞大的迁移工作量划分到每一次CURD中,避免了服务繁忙)

List

  • 【应用场景】:

    • 消息队列lpush + brpop 命令组合即可实现阻塞队列

    • 文章列表:缓存比如twitter的关注列表,粉丝列表等

    • 经典口诀:

      1
      2
      3
      4
      lpush + lpop = Stack(栈)
      lpush + rpop = Queue(队列)
      lpush + ltrim = Capped Collection(有限集合)
      lpush + brpop = Message Queue(消息队列)
  • 【内部编码】:

    • ziplist:当列表的元素个数小于 list-max-ziplist-entries 配置(默认512个),同时列表中每个元素的值都小于 list-max-ziplist-value 配置(默认64字节),Redis会选用ziplist来作为列表的内部实现来减少内存的使用。
    • linkedlist(链表):当列表类型无法满足ziplist的条件时,Redis会使用链表实现。
  • 【底层实现】:list的实现为一个双向链表,即可以支持反向查找和遍历。

Set

  • 【应用场景】:
    • 去重的场景
    • 求交集(sinter)、并集(sunion)、差集(sdiff)
    • 标签:实现如共同关注、共同喜好、二度好友等功能。
  • 【内部编码】:
    • intset(整数集合):当集合中的元素都是整数且元素个数小于 set-max-intset-entries 配置(默认512个)时,Redis会选用intset来作为集合内部实现,从而减少内存的使用。
    • hashtable(哈希表):集合无法满足intset的条件时,选用hashtable作为集合内部实现。
  • 【底层实现】:是一个value为null的HashMap,实际就是通过计算hash的方式来快速排重的,这也是set能提供判断一个成员是否在集合内的原因。

ZSet

  • 【应用场景】:
    • 排行榜
    • 实现延时队列
  • 【内部编码】:
    • ziplist(压缩列表):当有序集合的元素个数小于 zset-max-ziplist-entries 配置(默认128个),同时列表中每个元素的值都小于 zset-max-ziplist-value 配置(默认64字节),Redis会选用ziplist来作为列表的内部实现来减少内存的使用。
    • skiplist(跳跃表):当无法满足ziplist的条件时,Redis选用skiplist作为内部实现,因为此时ziplist的读写效率会下降。
  • 【底层实现】:内部使用HashMap跳跃表(SkipList)来保证数据的存储和有序,HashMap里放的是成员到score的映射,而跳跃表里存放的是所有的成员,排序依据是HashMap里存的score,使用跳跃表的结构可以获得比较高的查找效率,并且在实现上比较简单。
    • 跳表:每个节点中维持多个指向其他节点的指针,从而达到快速访问节点的目的

问:redis中的字符串类型是怎么实现的?

通过一个抽象数据结构SDS来实现,包含三个重要属性:已使用字节数量,未使用字节数量,字节数组。存储时保留了C语言以 \0 为结尾的习惯以便能兼容C语言的函数。分配空间时,当长度小于1MB,会根据已使用空间分配相等大小的未使用空间以备扩展,大于1MB时只会分配1MB。

问:redis 如何存储一个 String 的?

假设要存储一个字符串 hello ,SDS会记录已使用字节长度为5,并分配相同大小的未使用字节空间(长度小于1MB时)所以也为5,并保留 \0 作为字符串结尾但不计入空间占用,所以此时SDS分配空间为11。

问:SDS相比原生的char[]有什么优点?知道动态字符串sds的优缺点么?

首先原生C字符串获取字符串长度需要遍历整个字符串,复杂度为O(n),而SDS则为O(1)。

因为有预分配的空间以及惰性释放空间,所以可以避免重复内存分配。

因为有了长度控制,所以避免了C字符串常见的内存溢出问题。

C字符串对于字符编码有要求,对于一些如图片、音频等格式的二进制编码未必能支持,而SDS虽然保留了空字符结尾但并不以它来判断字符串结尾,所以可以安全的存储一些特殊格式要求的二进制数据。

问:hash底层如何实现?rehash做了哪些优化?

Redis 的 hash 数据有两种底层编码实现:ziplist和hashtable。

字典由 dict.h 文件定义,其中ht是哈希表结构,0是正常使用的表,1则是渐进rehash时用来转移0的节点。

rehashidx用来在rehash过程记录正在转移的键,平时为-1。

dictht即哈希表结构,内部包含一个 dictEntry **table table即dictEntry二维数组,为了哈希冲突时能够串联所有冲突的节点。size即哈希表最大可寻址大小,即一维数组最大长度。dictEntry则是一个键值对结构。

在数组+链表的基础上,进行了一些rehash优化。TODO

  • Reids的Hash采用链地址法来处理冲突,然后它没有使用红黑树优化
  • 哈希表节点采用单链表结构
  • rehash优化 (采用分而治之的思想,将庞大的迁移工作量划分到每一次CURD中,避免了服务繁忙)

问:zset底层怎么实现的?zset为什么使用跳跃链表而不用红黑树实现?

  1. skiplist的复杂度和红黑树一样,而且实现起来更简单。
  2. 在并发环境下红黑树在插入和删除时需要rebalance,性能不如跳表。

问:redis如何实现延时队列?

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

问:一个 Redis 实例最多能存放多少的keys?List、Set、Sorted Set 他们最多能存放多少元素?

理论上 Redis 可以处理多达 232 的 keys,并且在实际中进行了测试,每个实例至少存放了 2亿5 千万的 keys。测试一些较大的值。

任何 list、set、和 sorted set 都可以放 232 个元素。

换句话说,Redis的存储极限是系统中的可用内存值。

X. Redis内存

问:Redis 的内存占用情况怎么样?

举个例子: 100万个键值对(键是 0到 999999值是字符串“hello world”)在32 位的 Mac 笔记本上 用了 100MB。同样的数据放到一个 key 里只需要 16MB, 这是因为键值有一个很大的开销。 在 Memcached 上执行也是类似的结果,但是相对 Redis的开销要小一点点,因为 Redis 会记录类型信息引用计数等等。当然,大键值对时两者的比例要好很多。64 位的系统比 32 位的需要更多的内存开销,尤其是键值对都较小时,这是因为 64 位的系统里指针占用了 8 个字节。 但是,当然,64 位系统支持更大的内存,所以为了运行大型的 Redis 服务器或多或少的需要使用 64 位的系统。

问:Redis如何压缩内存(内存优化)?

尽可能使用散列表(hashes),散列表(是说散列表里面存储的数少)使用的内存非常小,所以应该尽可能的将数据模型抽象到一个散列表里面。比如 web 系统中有一个用户对象,不要为这个用户的名称,姓氏,邮箱,密码设置单独的 key,而是应该把这个用户的所有信息存储到一张散列表里面。

如果使用的是 32 位的 Redis 实例,可以好好利用 Hash,list,sorted set,set 等集合类型数据,因为通常情况下很多小的 Key-Value 可以用更紧凑的方式存放到一起。

问:Redis 的内存用完了会发生什么?

如果达到设置的上限,Redis 的写命令会返回错误信息(但是读命令还可以正常返回。)或者可以将 Redis 当缓存来使用配置淘汰机制,当 Redis 达到内存上限时会冲刷掉旧的内容。

问:如何利用Redis处理热点数据?

Redis 内存数据集大小上升到一定大小的时候,就会施行数据淘汰策略。

问:Redis有哪些缓存淘汰策略?

  1. 先进先出算法(FIFO)

  2. 最近使用最少Least Frequently Used(LFU)

  3. 最长时间未被使用的Least Recently Used(LRU)当存在热点数据时,LRU的效率很好,但偶发性的、周期性的批量操作会导致LRU命中率急剧下降,缓存污染情况比较严重。

  • noeviction:返回错误当内存限制达到并且客户端尝试执行会让更多内存被使用的命令(大部分的写入指令,但 DEL 和几个例外)
  • allkeys-lru:尝试回收最少使用的键(LRU),使得新添加的数据有空间存放。
  • volatile-lru:尝试回收最少使用的键(LRU),但仅限于在过期集合的键,使得新添加的数据有空间存放。
  • allkeys-random:回收随机的键使得新添加的数据有空间存放。
  • volatile-random:回收随机的键使得新添加的数据有空间存放,但仅限于在过期集合的键。
  • volatile-ttl:回收在过期集合的键,并且优先回收存活时间(TTL)较短的键,使得新添加的数据有空间存放。

问:redis过期key删除策略?

  1. 惰性删除,cpu友好,但是浪费cpu资源

  2. 定时删除(不常用)

  3. 定期删除,cpu友好,节省空间

设置过期时间和永久有效:EXPIRE 和 PERSIST 命令。

问:Redis 回收进程如何工作的?

Redis 检查内存使用情况,如果大于 maxmemory 的限制, 则根据设定好的策略进行回收。一个新的命令被执行,等等。

所以不断地穿越内存限制的边界,通过不断达到边界然后不断地回收回到边界以下。

如果一个命令的结果导致大量内存被使用(例如很大的集合的交集保存到一个新的键),不用多久内存限制就会被这个内存使用量超越。

回收使用LRU 算法。

问:Redis如何大批量插入数据?

Redis2.6 开始 Redis-cli 支持一种新的被称之为 pipe mode 的新模式用于执行大量数据插入工作。

问:Redis 常见性能问题和解决方案?

(1) Master 最好不要做任何持久化工作,如 RDB 内存快照和 AOF 日志文件

(2) 如果数据比较重要,某个 Slave 开启 AOF 备份数据,策略设置为每秒同步一次

(3) 为了主从复制的速度和连接的稳定性,Master 和 Slave 最好在同一个局域网内

(4) 尽量避免在压力很大的主库上增加从库

(5) 主从复制不要用图状结构,用单向链表结构更为稳定,即:Master <- Slave1 <- Slave2 <- Slave3…

这样的结构方便解决单点故障问题,实现 Slave 对 Master 的替换。如果 Master 挂了,可以立刻启用 Slave1 做 Master,其他不变。


三. 事务

问:redis事务?

事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。

事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行。

事务相关命令:

  1. Multi开启事务
  2. Exec执行事务块内命令
  3. Discard 取消事务
  4. Watch 监视一个或多个key,如果事务执行前key被改动,事务将打断

问:redis事务的实现特征?

  1. 所有命令都将会被串行化的顺序执行,事务执行期间,Redis不会再为其它客户端的请求提供任何服务,从而保证了事物中的所有命令被原子的执行
  2. Redis事务中如果有某一条命令执行失败,其后的命令仍然会被继续执行
  3. 在事务开启之前,如果客户端与服务器之间出现通讯故障并导致网络断开,其后所有待执行的语句都将不会被服务器执行。然而如果网络中断事件是发生在客户端执行EXEC命令之后,那么该事务中的所有命令都会被服务器执行
  4. 当使用Append-Only模式时,Redis会通过调用系统函数write将该事务内的所有写操作在本次调用中全部写入磁盘。

然而如果在写入的过程中出现系统崩溃,如电源故障导致的宕机,那么此时也许只有部分数据被写入到磁盘,而另外一部分数据却已经丢失。

Redis服务器会在重新启动时执行一系列必要的一致性检测,一旦发现类似问题,就会立即退出并给出相应的错误提示。此时,我们就要充分利用Redis工具包中提供的redis-check-aof工具,该工具可以帮助我们定位到数据不一致的错误,并将已经写入的部分数据进行回滚。修复之后我们就可以再次重新启动Redis服务器了


四. 同步阻塞

问:Redis的同步机制?

  • 全量拷贝:
    1. slave第一次启动时,连接Master,发送PSYNC命令,
    2. master会执行bgsave命令来生成rdb文件,期间的所有写命令将被写入缓冲区。
    3. master bgsave执行完毕,向slave发送rdb文件
    4. slave收到rdb文件,丢弃所有旧数据,开始载入rdb文件
    5. rdb文件同步结束之后,slave执行从master缓冲区发送过来的所以写命令。此后 master 每执行一个写命令,就向slave发送相同的写命令。
  • 增量拷贝:
    • 如果出现网络闪断或者命令丢失等异常情况,从节点之前保存了自身已复制的偏移量和主节点的运行ID
      • 主节点根据偏移量把复制积压缓冲区里的数据发送给从节点,保证主从复制进入正常状态。

问:redis阻塞原因?

  1. 数据结构使用不合理bigkey
  2. CPU饱和
  3. 持久化阻塞,rdb fork子线程,aof每秒刷盘等

问:讲一下分布式锁?

问:说说怎么用redis实现分布式锁?

问:Redis分布式锁?

2.6版本以后lua脚本保证setnx跟setex进行原子性(setnx之后,未setex,服务挂了,锁不释放) a获取锁,超过过期时间,自动释放锁,b获取到锁执行,a代码执行完remove锁,a和b是一样的key,导致a释放了b的锁。 解决办法:remove之前判断value(高并发下value可能被修改,应该用lua来保证原子性)

问:redis锁续租问题?

  1. 基于redis的redission分布式可重入锁RLock,以及配合java集合中lock;

  2. Redission 内部提供了一个监控锁的看门狗,不断延长锁的有效期,默认检查锁的超时时间是30秒

  3. 此方案的问题:如果你对某个redis master实例,写入了myLock这种锁key的value,此时会异步复制给对应的master ,slave实例。但是这个过程中一旦发生redis master宕机,主备切换,redis slave变为了redis master。

接着就会导致,客户端2来尝试加锁的时候,在新的redis master上完成了加锁,而客户端1也以为自己成功加了锁。 此时就会导致多个客户端对一个分布式锁完成了加锁 解决办法:只需要将新的redis实例,在一个TTL时间内,对客户端不可用即可,在这个时间内,所有客户端锁将被失效或者自动释放.

问:如何使用Redis做异步队列?

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

问:可不可以不用sleep呢?

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

问:如何解决 Redis 的并发竞争 Key 问题?


x. 发布订阅

问:能不能生产一次消费多次呢?

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

问:pub/sub有什么缺点?

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

问:讲一下项目中Redis消息队列如何做的?

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


x. 持久化

问:Redis 持久化策略?如何选择合适的策略?Redis如何做持久化?

Redis持久化分两种:RDB和AOF,RDB是全量持久化,aof则是增量持久化。因为全量持久化是重操作,无法进行实时的持久化,所以通过AOF解决。

RDB是将进程数据的快照保存到硬盘,通过fork子进程来异步生成RDB文件。

AOF则将命令写入缓冲区,通过三种策略同步到硬盘(每次都同步,每秒同步,不同步),随着文件增大还会进行文件重写,服务器重启时通过AOF文件恢复。

在redis实例重启时,会使用bgsave持久化文件重新构建内存,再使用aof重放近期的操作指令来实现完整恢复重启之前的状态。

RDB 持久化方式能够在指定的时间间隔能对数据进行快照存储。

AOF 持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据,AOF 命令以 Redis 协议追加保存每次写的操作到文件末尾.Redis 还能对AOF文件进行后台重写,使得 AOF 文件的体积不至于过大.如果只希望数据在服务器运行的时候存在,也可以不使用任何持久化方式.也可以同时开启两种持久化方式, 在这种情况下, 当 Redis 重启的时候会优先载入 AOF文件来恢复原始的数据,因为在通常情况下 AOF 文件保存的数据集要比 RDB 文件保存的数据集要完整.最重要的事情是了解 RDB 和 AOF 持久化方式的不同,以 RDB 持久化方式开始。

一般来说, 如果想达到足以媲美 PostgreSQL 的数据安全性, 应该同时使用两种持久化功能。如果非常关心数据,但仍然可以承受数分钟以内的数据丢失,那么可以只使用 RDB 持久化。有很多用户都只使用 AOF 持久化,但并不推荐这种方式:因为定时生成 RDB 快照(snapshot)非常便于进行数据库备份, 并且 RDB 恢复数据集的速度也要比 AOF 恢复的速度要快,除此之外,使用 RDB 还可以避免之前提到的 AOF 程序的 bug。

问:如果突然机器断电会怎样?

一般会选择everysec模式,即每秒执行一次fsync同步文件,所以只会丢失1s的数据(同步时主线程会判断最近2s是否有进行同步,有则直接返回,否则阻塞等同步线程执行完毕,所以最多可能丢失2s的数据)。

问:bgsave的原理是什么?

bgsave相比于已废弃的save,后者阻塞服务器直到RDB过程结束,前者则通过fork创建子进程,持久化过程由子进程来完成,减少了主进程的阻塞时间。通过copy-on-write机制,父子进程共享内存,父进程继续提供读写服务,子进程根据父进程内存生成快照文件,并替换到RDB文件。

问:RDB与AOF区别?

  1. RDB压缩文件格式紧凑,适合备份和全量复制,数据恢复快。但没法实时持久化,且有不同版本多个格式问题。

  2. AOF则不断的追加命令到文件(文本协议RESP),因此文件会不断变大,需要重写机制来压缩体积。重写时通过重写缓冲区保存此期间主进程响应的命令。

问:Redis 持久化数据和缓存怎么做扩容?

如果 Redis 被当做缓存使用,使用一致性哈希实现动态扩容缩容。

如果 Redis 被当做一个持久化存储使用,必须使用固定的 keys-to-nodes 映射关系,节点的数量一旦确定不能变化。否则的话(即 Redis 节点需要动态变化的情况),必须使用可以在运行时进行数据再平衡的一套系统,而当前只有 Redis 集群可以做到这样。


x. 哨兵、复制、集群

问:分布式 Redis 是前期做还是后期规模上来了再做好?为什么?

既然 Redis 是如此的轻量(单实例只使用 1M 内存),为防止以后的扩容,最好的办法就是一开始就启动较多实例。即便只有一台服务器,也可以一开始就让 Redis 以分布式的方式运行,使用分区,在同一台服务器上启动多个实例。一开始就多设置几个 Redis 实例,例如 32或者 64个实例,对大多数用户来说这操作起来可能比较麻烦,但是从长久来看做这点牺牲是值得的。这样的话,当数据不断增长,需要更多的 Redis 服务器时,需要做的就是仅仅将 Redis实例从一台服务迁移到另外一台服务器而已(而不用考虑重新分区的问题)。一旦添加了另一台服务器,需要将一半的 Redis 实例从第一台机器迁移到第二台机器。

问:redis主从机制了解么?怎么实现的?

为了使在部分节点失败或者大部分节点无法通信的情况下集群仍然可用,所以集群使用了主从复制模型,每个节点都会有 N-1 个复制品。

问:redis 的部署方式,主从,集群?

问:谈谈Redis哨兵、复制、集群?

异步复制,集群最大节点数为 16384 个。

问:redis 的哨兵模式,一个 key 值如何在 redis 集群中找到存储在哪?

问:redis集群模式性能优化?

  1. Master最好不要做任何持久化工作,如RDB内存快照和AOF日志文件
  2. 如果数据比较重要,某个Slave开启AOF备份数据,策略设置为每秒同步一次
  3. 为了主从复制的速度和连接的稳定性,Master和Slave最好在同一个局域网内
  4. 尽量避免在压力很大的主库上增加从库
  5. 主从复制不要用图状结构,用单向链表结构更为稳定,即:Master - Slave1 - Slave2 - Slave3… 这样的结构方便解决单点故障问题,实现Slave对Master的替换。如果Master挂了,可以立刻启用Slave1做Master,其他不变。

问:Redis集群方案?

Redis提供了三种集群策略:

  1. 主从模式:主库可以读写,会和从库进行数据同步。该模式下,客户端直连主库或从库,当有节点宕机后,客户端需要手动修改IP,该模式较难扩容,集群所有存储数据受限于单个节点的内存容量,无法支持较大数据量。
  2. 哨兵模式:该模式在主从模式上新增了哨兵节点,当主库宕机后,哨兵发现该情况并在从库中选择一个库作为新的主库。也可以对哨兵做集群,避免哨兵的单点问题。该模式能较好的保证Redis的高可用,但无法解决容量上限的问题。
  3. cluster模式:支持多主多从,按照key进行槽位的分配,可以使不同的key分散到不同的主节点上。该模式可以使集群能支持更大的数据容量,同时每个主节点有多个从节点保证高可用。

当数据量不大时选择哨兵模式,数据量大时选择cluster模式。

  1. twemproxy,它类似于一个代理方式,使用方法和普通Redis 无任何区别,设置好它下属的多个 Redis 实例后,使用时在本需要连接 Redis 的地方改为连接twemproxy,它会以一个代理的身份接收请求并使用一致性 hash 算法,将请求转接到具体 Redis,将结果再返回twemproxy。使用方式简便(相对 Redis 只需修改连接端口),对旧项目扩展的首选。 问题:twemproxy 自身单端口实例的压力,使用一致性 hash后,对Redis 节点数量改变时候的计算值的改变,数据无法自动移动到新的节点。
  2. codis,目前用的最多的集群方案,基本和 twemproxy 一致的效果,但它支持在节点数量改变情况下,旧节点数据可恢复到新 hash 节点。
  3. Redis cluster3.0 自带的集群,特点在于他的分布式算法不是一致性 hash,而是 hash槽的概念,以及自身支持节点设置从节点。具体看官方文档介绍。
  4. 在业务代码层实现,起几个毫无关联的Redis 实例,在代码层,对 key进行 hash 计算,然后去对应的 Redis 实例操作数据。 这种方式对 hash 层代码要求比较高,考虑部分包括,节点失效后的替代算法方案,数据震荡后的自动脚本恢复,实例的监控,等等。

问:集群不可用场景?

  1. master挂掉,且当前master没有slave
  2. 集群超过半数以上master挂掉,无论是否有slave集群进入fail状态。
  3. 有 A,B,C 三个节点的集群,在没有复制模型的情况下,如果节点 B 失败了,那么整个集群就会以为缺少 5501-11000 这个范围的槽而不可用。

问:Redis集群会发生写操作丢失吗?为什么?

Redis 并不能保证数据的强一致性,这意味这在实际中集群在特定的条件下可能会丢失写操作。

问:hot key出现造成集群访问量倾斜解决办法?

  1. 使用本地缓存
  2. 利用分片算法的特性,对key进行打散处理(给hot key加上前缀或者后缀,把一个hotkey 的数量变成 redis 实例个数N的倍数M,从而由访问一个 redis key 变成访问 N * M 个redis key)

问:缓存雪崩以及处理办法?

同一时刻大量缓存失效;

处理方法:

  1. 缓存数据增加过期标记
  2. 设置不同的缓存失效时间
  3. 双层缓存策略C1为短期,C2为长期
  4. 定时更新策略

问:缓存击穿原因以及处理办法?

频繁请求查询系统中不存在的数据导致;

处理方法:

  1. cache null策略,查询反馈结果为null仍然缓存这个null结果,设置不超过5分钟过期时间
  2. 布隆过滤器,所有可能存在的数据映射到足够大的bitmap中 google布隆过滤器:基于内存,重启失效不支持大数据量,无法在分布式场景 redis布隆过滤器:可扩展性,不存在重启失效问题,需要网络io,性能低于google

问:你们怎么解决缓存击穿问题的?

注:估计答了Hystrix

问:缓存穿透、缓存击穿、缓存雪崩问题及解决方法?

问:Hystrix的隔离机制有哪些?Hystrix常见配置是哪些?

注:估计答了Hystrix

问:缓存和数据库一致性问题?如何保证缓存与数据库双写时的数据一致性?

问:Redis分区场景?实现方案?分区的缺点?

分区可以让 Redis 管理更大的内存,Redis 将可以使用所有机器的内存。如果没有分区,最多只能使用一台机器的内存。分区使 Redis 的计算能力通过简单地增加计算机得到成倍提升,Redis 的网络带宽也会随着计算机和网卡的增加而成倍增长。

客户端分区就是在客户端就已经决定数据会被存储到哪个 Redis 节点或者从哪个 Redis 节点读取。大多数客户端已经实现了客户端分区。代理分区意味着客户端将请求发送给代理,然后代理决定去哪个节点写数据或者读数据。代理根据分区规则决定请求哪些Redis 实例,然后根据 Redis 的响应结果返回给客户端。Redis 和 memcached 的一种代理实现就是 Twemproxy查询路由(Query routing) 的意思是客户端随机地请求任意一个 Redis实例,然后由 Redis将请求转发给正确的 Redis 节点。RedisCluster 实现了一种混合形式的查询路由,但并不是直接将请求从一个 Redis 节点转发到另一个 Redis 节点,而是在客户端的帮助下直接redirected 到正确的Redis 节点。

  • 涉及多个 key 的操作通常不会被支持。例如不能对两个集合求交集,因为他们可能被存储到不同的 Redis 实例(实际上这种情况也有办法,但是不能直接使用交集指令)。
  • 同时操作多个 key,则不能使用 Redis 事务。
  • 分区使用的粒度是 key,不能使用一个非常长的排序 key 存储一个数据集(The partitioning granularity is the key, so it is not possible to shard a dataset with a single huge keylike a very big sorted set)。
  • 当使用分区的时候,数据处理会非常复杂,例如为了备份必须从不同的 Redis 实例和主机同时收集RDB / AOF 文件。
  • 分区时动态扩容或缩容可能非常复杂。Redis集群在运行时增加或者删除 Redis 节点,能做到最大程度对用户透明地数据再平衡,但其他一些客户端分区或者代理分区方法则不支持这种特性。然而,有一种预分片的技术也可以较好的解决这个问题。