关于后端:Redis面试问题汇总

51次阅读

共计 26683 个字符,预计需要花费 67 分钟才能阅读完成。

一、Redis 根底问题

Redis 是什么

Redis:REmote DIctionary Server(近程字典服务器)。

Redis 是一个全开源收费(BSD 许可)的,内存中的数据结构存储系统,它能够用作 数据库、缓存和消息中间件

和 Memcached 相似,它反对存储的 value 类型绝对更多,包含 string(字符串)、list(链表)、set(汇合)、zset(sorted set – 有序汇合) 和 hash(哈希类型)、bitmap、hyperloglog、GeoHash、streams。这些数据类型都反对 push/pop、add/remove 及取交加并集和差集及更丰盛的操作,而且这些操作都是原子性的。

Redis 内置了复制(Replication),LUA 脚本(Lua scripting),LRU 驱动事件(LRU eviction),事务(Transactions)和不同级别的磁盘长久化(Persistence),并通过 Redis 哨兵(Sentinel)和主动分区(Cluster)提供高可用性(High Availability)。

  • 性能优良,数据在内存中,读写速度十分快,反对并发 10W QPS
  • 单过程单线程,是线程平安的,采纳 IO 多路复用机制
  • Redis 数据库齐全在内存中,应用磁盘仅用于持久性
  • 相比许多键值数据存储,Redis 领有一套较为丰盛的数据类型
  • 操作都是 原子性:所有 Redis 操作是原子的,这保障了如果两个客户端同时拜访的 Redis 服务器将取得更新后的值
  • Redis 能够将数据复制到任意数量的从服务器(主从复制,哨兵,高可用)

Redis 都反对哪些数据类型

Redis 不是简略的键值存储,它实际上是一个数据结构服务器,反对不同类型的值。

  • String(字符串):二进制平安字符串
  • List(列表):依据插入程序排序的字符串元素的汇合。它们基本上是链表
  • Hash(字典):是一个键值对汇合。KV 模式不变,但 V 是一个键值对
  • Set(汇合):惟一,未排序的字符串元素的汇合
  • zset(sorted set:有序汇合):相当于有序的 Set 汇合,每个字符串元素都与一个称为 score 的浮点值相关联。元素总是按它们的分数排序(eg,找出前 10 名或后 10 名)

除了反对最 根底的五种数据类型 外,还反对一些 高级数据类型

  • Bit arrays(位数组,简称位图 bitMap):
  • HyperLogLog():这是一个概率数据结构,用于预计汇合的基数
  • Geo
  • Stream:

那你能说说这些数据类型的应用指令吗?

String: 就是根本的 SET、GET、MSET、MGET、INCR、DECR

List: LPUSH、RPUSH、LRANGE、LINDEX

Hash: HSET、HMSET、HSETNX、HKEYS、HVALS

Set: SADD、SCARD、SDIFF、SREM

SortSet: ZADD、ZCARD、ZCOUNT、ZRANGE

为什么要用缓存?为什么应用 Redis?

提一下当初 Web 利用的现状

在日常的 Web 利用对数据库的拜访中,读操作的次数远超写操作 ,比例大略在 1:93:7,所以须要读的可能性是比写的可能大得多的。当咱们应用 SQL 语句去数据库进行读写操作时,数据库就会 去磁盘把对应的数据索引取回来,这是一个绝对较慢的过程。

应用 Redis or 应用缓存带来的劣势

如果咱们把数据放在 Redis 中,也就是间接放在内存之中,让服务端间接去读取内存中的数据,那么这样 速度 显著就会快上不少 (高性能),并且会 极大减小数据库的压力 (特地是在高并发状况下)

也要提一下应用缓存的思考

然而应用内存进行数据存储开销也是比拟大的,限于老本 的起因,个别咱们只是应用 Redis 存储一些 罕用和次要的数据,比方用户登录的信息等。

一般而言在应用 Redis 进行存储的时候,咱们须要从以下几个方面来思考:

  • 业务数据罕用吗?命中率如何? 如果命中率很低,就没有必要写入缓存;
  • 该业务数据是读操作多,还是写操作多? 如果写操作多,频繁须要写入数据库,也没有必要应用缓存;
  • 业务数据大小如何? 如果要存储几百兆字节的文件,会给缓存带来很大的压力,这样也没有必要;

在思考了这些问题之后,如果感觉有必要应用缓存,那么就应用它!

这些都会,那你能说说 Redis 应用场景不,你们我的项目中是怎么用的

在 Redis 中,罕用的 5 种数据结构和利用场景如下:

  • String:缓存、计数器、分布式锁等。
  • List:链表、队列、微博关注人时间轴列表等。
  • Hash:用户信息、Hash 表等。
  • Set:去重、赞、踩、独特好友等。
  • Zset:访问量排行榜、点击量排行榜等

还有一些,比方:

  • 取最新 N 个数据的操作
  • 排行榜利用, 取 TOP N 操作
  • 须要准确设定过期工夫的利用
  • 定时器、计数器利用
  • Uniq 操作, 获取某段时间所有数据排重值
  • 实时零碎, 反垃圾零碎
  • Pub/Sub 构建实时音讯零碎
  • 构建队列零碎
  • 缓存

用缓存,必定是因为他快,那 Redis 为什么这么快

  • 纯内存操作:读取不须要进行磁盘 I/O,所以比传统数据库要快上不少;(但不要有误区说磁盘就肯定慢,例如 Kafka 就是应用磁盘程序读取但依然较快)
  • 单线程,无锁竞争:这保障了没有线程的上下文切换,不会因为多线程的一些操作而升高性能;
  • 多路 I/O 复用模型,非阻塞 I/O:采纳多路 I/O 复用技术能够让单个线程高效的解决多个网络连接申请(尽量减少网络 IO 的工夫耗费);
  • 高效的数据结构,加上底层做了大量优化:Redis 对于底层的数据结构和内存占用做了大量的优化,例如不同长度的字符串应用不同的构造体示意,HyperLogLog 的密集型存储构造等等..

I/ O 多路复用,I/ O 就是指的咱们网络 I /O, 多路指多个 TCP 连贯(或多个 Channel),复用指复用一个或大量线程。串起来了解就是很多个网络 I / O 复用一个或大量的线程来解决这些连贯。

为什么晚期版本的 Redis 抉择单线程?

咱们首先要明确,上边的种种剖析,都是为了营造一个 Redis 很快的气氛!官网 FAQ 示意,因为 Redis 是基于内存的操作,CPU 不是 Redis 的瓶颈,Redis 的瓶颈最有可能是机器内存的大小或者网络带宽。既然单线程容易实现,而且 CPU 不会成为瓶颈,那就牵强附会地采纳单线程的计划了(毕竟采纳多线程会有很多麻烦!)。

看到这里,你可能会气哭!本认为会有什么重大的技术要点才使得 Redis 应用单线程就能够这么快,没想到就是一句官网看似糊弄咱们的答复!然而,咱们曾经能够很分明的解释了为什么 Redis 这么快,并且正是因为在单线程模式的状况下曾经很快了,就没有必要在应用多线程了!

简略总结一下

  1. 应用单线程模型能带来更好的可维护性,不便开发和调试;
  2. 应用单线程模型也能并发的解决客户端的申请;
  3. Redis 服务中运行的绝大多数操作的性能瓶颈都不是 CPU;

这里咱们始终在强调的单线程,只是在解决咱们的网络申请的时候只有一个线程来解决,一个正式的 Redis Server 运行的时候必定是不止一个线程的,这里须要大家明确的留神一下!例如 Redis 进行长久化的时候会以子过程或者子线程的形式执行;

Redis 抉择应用单线程模型解决客户端的申请次要还是因为 CPU 不是 Redis 服务器的瓶颈,所以应用多线程模型带来的性能晋升并不能对消它带来的开发成本和保护老本,零碎的性能瓶颈也次要在网络 I/O 操作上;而 Redis 引入多线程操作也是出于性能上的思考,对于一些大键值对的删除操作,通过多线程非阻塞地开释内存空间也能缩小对 Redis 主线程阻塞的工夫,进步执行的效率。

举荐浏览:https://draveness.me/whys-the-design-redis-single-thread/

Redis 和 Memcached 的区别

  1. 存储形式上:memcache 会把数据全副存在内存之中,断电后会挂掉,数据不能超过内存大小。redis 有局部数据存在硬盘上,这样能保证数据的持久性。
  2. 数据反对类型上:memcache 对数据类型的反对简略,只反对简略的 key-value,而 redis 反对五种数据类型。
  3. 应用底层模型不同:它们之间底层实现形式以及与客户端之间通信的利用协定不一样。redis 间接本人构建了 VM 机制,因为个别的零碎调用零碎函数的话,会节约肯定的工夫去挪动和申请。
  4. value 的大小:redis 能够达到 1GB,而 memcache 只有 1MB。

Redis 线程模型

Redis 基于 Reactor 模式开发了网络事件处理器,这个处理器被称为文件事件处理器(file event handler)。它的组成构造为 4 局部:多个套接字、IO 多路复用程序、文件事件分派器、事件处理器。因为文件事件分派器队列的生产是单线程的,所以 Redis 才叫单线程模型。

文件事件处理器应用 I/O 多路复用(multiplexing)程序来同时监听多个套接字,并依据套接字目前执行的工作来为套接字关联不同的事件处理器。

当被监听的套接字筹备好执行连贯应答(accept)、读取(read)、写入(write)、敞开(close)等操作时,与操作绝对应的文件事件就会产生,这时文件事件处理器就会调用套接字之前关联好的事件处理器来解决这些事件。

尽管文件事件处理器以单线程形式运行,但通过应用 I/O 多路复用程序来监听多个套接字,文件事件处理器既实现了高性能的网络通信模型,又能够很好地与 redis 服务器中其余同样以单线程形式运行的模块进行对接,这放弃了 Redis 外部单线程设计的简略性。

参考:https://www.cnblogs.com/barrywxx/p/8570821.html

Redis 内存模型

Redis 内存次要能够分为:数据局部、Redis 过程自身、缓冲区内存、内存碎片这四个局部。Redis 默认通过 jemalloc 来分配内存。

  • 数据内存:数据内存用来存储 Redis 的键值对、慢查问日志等,是次要占用内存的局部,这部分内存会统计在 used_memory 中
  • Redis 过程内存:Redis 过程自身也会占用一部分内存,这部分内存不是 jemalloc 调配,不会统计在 used_memory 中。执行 RDB 和 AOF 时创立的子过程也会占用内存,但也不会统计在 used_memory 中。
  • 缓冲内存

    缓冲内存包含:

    • 客户端缓冲区:存储客户端连贯的输出和输入缓冲
    • 复制积压缓冲区:用于 PSYNC 的局部复制性能
    • AOF 缓冲区:AOF 操作时,保留最近写入的命令。

    这部分内存由 jemalloc 调配,会被统计在 used_memory 中

  • 内存碎片:Redis 在调配和回收物理内存的过程中会产生内存碎片,这部分不会统计在 used_memory 中。内存碎片太多的话能够通过平安重启形式缩小内存碎片,重启之后 Redis 会应用 RDB 或者 AOF 复原数据,内存会被重排。

最初总结下 Redis 优缺点

长处

  • 读写性能优异,Redis 能读的速度是 110000 次 /s,写的速度是 81000 次 /s。
  • 反对数据长久化,反对 AOF 和 RDB 两种长久化形式。
  • 反对事务,Redis 的所有操作都是原子性的,同时 Redis 还反对对几个操作合并后的原子性执行。
  • 数据结构丰盛,除了反对 string 类型的 value 外还反对 hash、set、zset、list 等数据结构。
  • 反对主从复制,主机会主动将数据同步到从机,能够进行读写拆散。

毛病

  • 数据库 容量受到物理内存的限度,不能用作海量数据的高性能读写,因而 Redis 适宜的场景次要局限在较小数据量的高性能操作和运算上。
  • Redis 不具备主动容错和复原性能,主机从机的宕机都会导致前端局部读写申请失败,须要期待机器重启或者手动切换前端的 IP 能力复原。
  • 主机宕机,宕机前有局部数据未能及时同步到从机,切换 IP 后还会引入数据不统一的问题,升高了 零碎的可用性
  • Redis 较难反对在线扩容,在集群容量达到下限时在线扩容会变得很简单。为防止这一问题,运维人员在零碎上线时必须确保有足够的空间,这对资源造成了很大的节约。

二、Redis 数据结构问题

首先在 Redis 外部会应用一个 RedisObject 对象来示意所有的 keyvalue

Redis 提供了五种根本数据类型,String、Hash、List、Set、Zset(sorted set:有序汇合)

因为 Redis 是基于规范 C 写的,只有最根底的数据类型,因而 Redis 为了满足对外应用的 5 种数据类型,开发了属于本人 独有的一套根底数据结构,应用这些数据结构来实现 5 种数据类型。

Redis 底层的数据结构包含:简略动静数组 SDS、链表、字典、跳跃链表、整数汇合、压缩列表、对象。

Redis 为了均衡空间和工夫效率,针对 value 的具体类型在底层会采纳不同的数据结构来实现,其中哈希表和压缩列表是复用比拟多的数据结构,如下图展现了对外数据类型和底层数据结构之间的映射关系:

String 是如何实现的

Redis 是用 C 语言开发实现的,但在 Redis 字符串中,并没有应用 C 语言中的字符串,而是用一种称为 SDS(Simple Dynamic String)的构造体来保留字符串。

String 是 Redis 最根本的类型,你能够了解成与 Memcached 截然不同的类型,一个 key 对应一个 value。

String 类型是二进制平安的。意思是 Redis 的 String 能够蕴含任何数据。比方 jpg 图片或者序列化的对象。

Redis 的字符串是动静字符串,是能够批改的字符串,内部结构实现上相似于 Java 的 ArrayList,采纳预调配冗余空间的形式来缩小内存的频繁调配,外部为以后字符串理论调配的空间 capacity 个别要高于理论字符串长度 len。当字符串长度小于 1M 时,扩容都是加倍现有的空间,如果超过 1M,扩容时一次只会多扩 1M 的空间。须要留神的是 字符串最大长度为 512M

Redis 的 SDS 和 C 中字符串相比有什么劣势?

C 语言应用了一个长度为 N+1 的字符数组来示意长度为 N 的字符串,并且字符数组最初一个元素总是 \0,这种简略的字符串示意形式 不合乎 Redis 对字符串在安全性、效率以及性能方面的要求

再来说 C 语言字符串的问题

这样简略的数据结构可能会造成以下一些问题:

  • 获取字符串长度为 O(N) 级别的操作 → 因为 C 不保留数组的长度,每次都须要遍历一遍整个数组;
  • 不能很好的杜绝 缓冲区溢出 / 内存透露 的问题 → 跟上述问题起因一样,如果执行拼接 or 缩短字符串的操作,如果操作不当就很容易造成上述问题;
  • C 字符串 只能保留文本数据 → 因为 C 语言中的字符串必须合乎某种编码(比方 ASCII),例如两头呈现的 '\0' 可能会被断定为提前结束的字符串而辨认不了;

Redis 如何解决的 | SDS 的劣势

如果去看 Redis 的源码 sds.h/sdshdr 文件,你会看到 SDS 残缺的实现细节,这里简略来说一下 Redis 如何解决的:

  1. 多减少 len 示意以后字符串的长度:这样就能够间接获取长度了,复杂度 O(1);
  2. 主动扩大空间:当 SDS 须要对字符串进行批改时,首先借助于 lenalloc 查看空间是否满足批改所需的要求,如果空间不够的话,SDS 会主动扩大空间,防止了像 C 字符串操作中的笼罩状况;
  3. 无效升高内存调配次数 :C 字符串在波及减少或者革除操作时会扭转底层数组的大小造成重新分配,SDS 应用了 空间预调配 惰性空间开释 机制,简略了解就是每次在扩大时是成倍的多调配的,在缩容是也是先留着并不正式归还给 OS;
  4. 二进制平安:C 语言字符串只能保留 ascii 码,对于图片、音频等信息无奈保留,SDS 是二进制平安的,写入什么读取就是什么,不做任何过滤和限度;

说说 List

Redis 列表是简略的字符串列表,依照插入程序排序。你能够增加一个元素到列表的头部(右边)或者尾部(左边)。

Redis 的列表相当于 Java 语言外面的 LinkedList,留神它是链表而不是数组。这意味着 list 的插入和删除操作十分快,工夫复杂度为 O(1),然而索引定位很慢,工夫复杂度为 O(n)。

Redis 的列表构造罕用来做异步队列应用。将须要延后解决的工作构造体序列化成字符串塞进 Redis 的列表,另一个线程从这个列表中轮询数据进行解决。

字典 Hash 是如何实现的?Rehash 理解吗?

Redis 中的字典相当于 Java 中的 HashMap,外部实现也差不多相似,都是通过 “数组 + 链表”链地址法 来解决局部哈希抵触,同时这样的构造也排汇了两种不同数据结构的长处。

字典构造外部蕴含 两个 hashtable,通常状况下只有一个 hashtable 有值,然而在字典扩容缩容时,须要调配新的 hashtable,而后进行 渐进式搬迁 (rehash),这时候两个 hashtable 别离存储旧的和新的 hashtable,待搬迁完结后,旧的将被删除,新的 hashtable 取而代之。

扩缩容的条件

失常状况下,当 hash 表中 元素的个数等于第一维数组的长度时 ,就会开始扩容,扩容的新数组是 原数组大小的 2 倍 。不过如果 Redis 正在做 bgsave(长久化命令),为了缩小内存也得过多拆散,Redis 尽量不去扩容,然而如果 hash 表十分满了, 达到了第一维数组长度的 5 倍了 ,这个时候就会 强制扩容

当 hash 表因为元素逐步被删除变得越来越稠密时,Redis 会对 hash 表进行缩容来缩小 hash 表的第一维数组空间占用。所用的条件是 元素个数低于数组长度的 10%,缩容不会思考 Redis 是否在做 bgsave

说说 Zset 吧

它相似于 Java 的 SortedSet 和 HashMap 的结合体,一方面它是一个 set,保障了外部 value 的唯一性,另一方面它能够给每个 value 赋予一个 score,代表这个 value 的排序权重。它的外部实现用的是一种叫做「跳跃列表」的数据结构。

Redis 正是通过 score 来为汇合中的成员进行从小到大的排序。Zset 的成员是惟一的,但 score 却能够反复。

跳跃表是如何实现的?原理?

从图中能够看到,跳跃表次要由以下局部形成:

  • 表头(head):负责保护跳跃表的节点指针。
  • 跳跃表节点:保留着元素值,以及多个层。
  • 层:保留着指向其余元素的指针。高层的指针越过的元素数量大于等于低层的指针,为了进步查找的效率,程序总是从高层先开始拜访,而后随着元素值范畴的放大,缓缓升高档次。
  • 表尾:全副由 NULL 组成,示意跳跃表的开端。

压缩列表理解吗?

这是 Redis 为了节约内存 而应用的一种数据结构,zsethash 容器对象会在元素个数较少的时候,采纳压缩列表(ziplist)进行存储。压缩列表是 一块间断的内存空间,元素之间紧挨着存储,没有任何冗余空隙。

疾速列表 quicklist 理解吗?

Redis 晚期版本存储 list 列表数据结构应用的是压缩列表 ziplist 和一般的双向链表 linkedlist,也就是说当元素少时应用 ziplist,当元素多时用 linkedlist。但思考到链表的附加空间绝对较高,prevnext 指针就要占去 16 个字节(64 位操作系统占用 8 个字节),另外每个节点的内存都是独自调配,会家具内存的碎片化,影响内存管理效率。

起初 Redis 新版本(3.2)对列表数据结构进行了革新,应用 quicklist 代替了 ziplistlinkedlist

同上.. 倡议浏览一下以下的文章:

  • Redis 列表 list 底层原理 – https://zhuanlan.zhihu.com/p/102422311

除了 5 种根本数据类型,还晓得其余数据结构不

Bitmaps(位图)

位图不是理论的数据类型,而是在 String 类型上定义的一组面向位的操作。能够看作是 byte 数组。咱们能够应用一般的 get/set 间接获取和设置整个位图的内容,也能够应用位图操作 getbit/setbit 等将 byte 数组看成「位数组」来解决。

个别用于:各种实时剖析;存储与对象 ID 相干的布尔信息

HyperLogLog

HyperLogLog 是一种概率数据结构,用于对惟一事物进行计数(从技术上讲,这是指预计汇合的基数)

https://www.wmyskxz.com/2020/03/02/reids-4-shen-qi-de-hyperlo…


三、Redis 长久化问题

你对 redis 的长久化机制理解吗?能讲一下吗?

Redis 的数据全副在内存里,如果忽然宕机,数据就会全副失落,因而必须有一种机制来保障 Redis 的数据不会因为故障而失落,这种机制就是 Redis 的长久化机制,它会将内存中的数据库状态 保留到磁盘 中。

解释一下长久化产生了什么

咱们来略微考虑一下 Redis 作为一个 “内存数据库” 要做的对于长久化的事件。通常来说,从客户端发动申请开始,到服务器实在地写入磁盘,须要产生如下几件事件:

具体版 的文字描述大略就是上面这样:

  1. 客户端向数据库 发送写命令 (数据在客户端的内存中)
  2. 数据库 接管 到客户端的 写申请 (数据在服务器的内存中)
  3. 数据库 调用零碎 API 将数据写入磁盘 (数据在内核缓冲区中)
  4. 操作系统将 写缓冲区 传输到 磁盘控控制器 (数据在磁盘缓存中)
  5. 操作系统的磁盘控制器将数据 写入理论的物理媒介(数据在磁盘中)

Redis 长久化的形式有哪写

Redis 有两种长久化的形式:快照(RDB 文件)和追加式文件(AOF 文件)

RDB(Redis DataBase)

在指定的工夫距离内将内存中的数据集快照写入磁盘,也就是行话讲的 Snapshot 快照,它复原时是将快照文件间接读到内存里。

Redis 会独自创立(fork)一个子过程来进行长久化,会先将数据写入到一个临时文件中,待长久化过程都完结了,再用这个临时文件替换上次长久化好的文件。整个过程中,主过程是不进行任何 IO 操作的,这就确保了极高的性能,如果须要进行大规模数据的复原,且对于数据恢复的完整性不是十分敏感,那 RDB 形式要比 AOF 形式更加的高效。RDB 的毛病是最初一次长久化后的数据可能失落。

What ? Redis 不是单过程的吗?

Redis 应用操作系统的多过程 COW(Copy On Write) 机制来实现快照长久化,fork 是类 Unix 操作系统上 创立过程 的次要办法。COW(Copy On Write)是计算机编程中应用的一种优化策略。

fork 的作用是复制一个与以后过程一样的过程。新过程的所有数据(变量、环境变量、程序计数器等)数值都和原过程统一,然而是一个全新的过程,并作为原过程的子过程。子过程读取数据,而后序列化写到磁盘中。

rdb 默认保留的是 dump.rdb 文件

你能够对 Redis 进行设置,让它在“N 秒内数据集至多有 M 个改变”这一条件被满足时,主动保留一次数据集。

你也能够通过调用 SAVE 或者 BGSAVE,手动让 Redis 进行数据集保留操作。

比如说,以下设置会让 Redis 在满足“60 秒内有至多有 1000 个键被改变”这一条件时,主动保留一次数据集:

save 60 1000

AOF(Append Only File)

以日志的模式来记录每个写操作,将 Redis 执行过的所有写指令记录下来(读操作不记录),只许追加文件但不能够改写文件,redis 启动之初会读取该文件从新构建数据,也就是「重放」。换言之,redis 重启的话就依据日志文件的内容将写指令从前到后执行一次以实现数据的复原工作。

AOF 默认保留的是 appendonly.aof 文件

RDB 和 AOF 各自有什么优缺点?

RDB | 长处

  1. 只有一个文件 dump.rdb不便长久化
  2. 容灾性好,一个文件能够保留到平安的磁盘。
  3. 性能最大化fork 子过程来实现写操作,让主过程持续解决命令,所以使 IO 最大化。应用独自子过程来进行长久化,主过程不会进行任何 IO 操作,保障了 Redis 的高性能
  4. 绝对于数据集大时,比 AOF 的 启动效率 更高。

RDB | 毛病

  1. 数据安全性低。RDB 是距离一段时间进行长久化,如果长久化之间 Redis 产生故障,会产生数据失落。所以这种形式更适宜数据要求不谨严的时候;

AOF | 长处

  1. 数据安全,aof 长久化能够配置 appendfsync 属性,有 always,每进行一次命令操作就记录到 aof 文件中一次。
  2. 通过 append 模式写文件,即便中途服务器宕机,能够通过 redis-check-aof 工具解决数据一致性问题。
  3. AOF 机制的 rewrite 模式。AOF 文件没被 rewrite 之前(文件过大时会对命令 进行合并重写),能够删除其中的某些命令(比方误操作的 flushall)

AOF | 毛病

  1. AOF 文件比 RDB 文件大 ,且 复原速度慢
  2. 数据集大 的时候,比 rdb 启动效率低

aof 如果文件越来愈大 怎么办?

rewrite(AOF 重写)

  • 是什么:AOF 采纳文件追加形式,文件会越来越大为避免出现此种状况,新增了重写机制,当 AOF 文件的大小超过所设定的阈值时,Redis 就会启动 AOF 文件的内容压缩,只保留能够复原数据的最小指令集,能够应用命令bgrewriteaof,这个操作相当于对 AOF 文件“瘦身”。
  • 重写原理:AOF 文件持续增长而过大时,会 fork 出一条新过程来将文件重写(也是先写临时文件最初再 rename),遍历新过程的内存中数据,每条记录有一条的 Set 语句。重写 aof 文件的操作,并没有读取旧的 aof 文件,而是将整个内存中的数据库内容用命令的形式重写了一个新的 aof 文件,这点和快照有点相似
  • 触发机制:Redis 会记录上次重写时的 AOF 大小,默认配置是当 AOF 文件大小是上次 rewrite 后大小的一倍且文件大于 64M 时触发

两种长久化形式如何抉择?

  • RDB 长久化形式可能在指定的工夫距离能对你的数据进行快照存储
  • AOF 长久化形式记录每次对服务器写的操作, 当服务器重启的时候会从新执行这些命令来复原原始的数据,AOF 命令以 redis 协定追加保留每次写的操作到文件开端。Redis 还能对 AOF 文件进行后盾重写(bgrewriteaof), 使得 AOF 文件的体积不至于过大
  • 只做缓存:如果你只心愿你的数据在服务器运行的时候存在, 你也能够不应用任何长久化形式。
  • 同时开启两种长久化形式

    • 在这种状况下, 当 redis 重启的时候会优先载入 AOF 文件来复原原始的数据, 因为在通常状况下 AOF 文件保留的数据集要比 RDB 文件保留的数据集要残缺。
    • RDB 的数据不实时,同时应用两者时服务器重启也只会找 AOF 文件。那要不要只应用 AOF 呢?倡议不要,因为 RDB 更适宜用于备份数据库(AOF 在一直变动不好备份),疾速重启,而且不会有 AOF 可能潜在的 bug,留着作为一个万一的伎俩。

四、Redis 事务问题

Redis 事务的概念?

Redis 事务的实质是通过 MULTI、EXEC、WATCH 等一组命令的汇合。事务反对一次执行多个命令,一个事务中所有命令都会被序列化。在事务执行过程,会依照程序串行化执行队列中的命令,其余客户端提交的命令申请不会插入到事务执行命令序列中。

总结说:redis 事务就是一次性、程序性、排他性的执行一个队列中的一系列命令。

MULTI 命令用于开启一个事务,它总是返回 OK。

MULTI 执行之后,客户端能够持续向服务器发送任意多条命令,这些命令不会立刻被执行,而是被放到一个队列中,当 EXEC 命令被调用时,所有队列中的命令才会被执行。

另一方面,通过调用 DISCARD,客户端能够清空事务队列,并放弃执行事务。

WATCH 使得 EXEC 命令须要有条件地执行:事务只能在所有被监督键都没有被批改的前提下执行,如果这个前提不能满足的话,事务就不会被执行。

Redis 事务的三个阶段、三个性

三阶段

  1. 开启:以 MULTI 开始一个事务
  2. 入队:将多个命令入队到事务中,接到这些命令并不会立刻执行,而是放到期待执行的事务队列外面
  3. 执行:由 EXEC 命令触发事务

三个性

  1. 独自的隔离操作:事务中的所有命令都会序列化、按程序地执行。事务在执行的过程中,不会被其余客户端发送来的命令申请所打断。
  2. 没有隔离级别的概念:队列中的命令没有提交之前都不会理论的被执行,因为事务提交前任何指令都不会被理论执行,也就不存在”事务内的查问要看到事务里的更新,在事务外查问不能看到”这个让人万分头痛的问题
  3. 不保障原子性:redis 同一个事务中如果有一条命令执行失败,其后的命令依然会被执行,没有回滚

Redis 事务反对隔离性吗?

Redis 是单过程程序,并且它保障在执行事务时,不会对事务进行中断,事务能够运行直到执行完所有事务队列中的命令为止。因而,Redis 的事务是总是带有隔离性的

Redis 事务保障原子性吗,反对回滚吗?

Redis 中,单条命令是原子性执行的,但 事务不保障原子性,且没有回滚。事务中任意命令执行失败,其余的命令仍会被执行。

  1. 如果在一个事务中的命令呈现谬误,那么所有的命令都不会执行
  2. 如果在一个事务中呈现运行谬误,那么正确的命令会被执行

五、Redis 集群问题

redis 单节点存在单点故障问题,为了解决单点问题,个别都须要对 redis 配置从节点,而后应用哨兵来监听主节点的存活状态,如果主节点挂掉,从节点能持续提供缓存性能

主从同步理解吗?

主从复制 ,是指将一台 Redis 服务器的数据,复制到其余的 Redis 服务器。前者称为 主节点 (master),后者称为 从节点 (slave)。且数据的复制是 单向 的,只能由主节点到从节点。Redis 主从复制反对 主从同步 从从同步 两种,后者是 Redis 后续版本新增的性能,以加重主节点的同步累赘。

主从复制次要的作用

  • 数据冗余: 主从复制实现了数据的热备份,是长久化之外的一种数据冗余形式。
  • 故障复原: 当主节点呈现问题时,能够由从节点提供服务,实现疾速的故障复原 (实际上是一种服务的冗余)
  • 负载平衡: 在主从复制的根底上,配合读写拆散,能够由主节点提供写服务,由从节点提供读服务 (即写 Redis 数据时利用连贯主节点,读 Redis 数据时利用连贯从节点),分担服务器负载。尤其是在写少读多的场景下,通过多个从节点分担读负载,能够大大提高 Redis 服务器的并发量。
  • 高可用基石: 除了上述作用以外,主从复制还是哨兵和集群可能施行的 根底,因而说主从复制是 Redis 高可用的根底。

实现原理

为了节俭篇幅,我把次要的步骤都 稀释 在了上图中,其实也能够 简化成三个阶段:筹备阶段 - 数据同步阶段 - 命令流传阶段

redis2.8 之前应用 sync[runId][offset] 同步命令,redis2.8 之后应用 psync[runId][offset] 命令。两者不同在于,sync 命令仅反对全量复制过程,psync 反对全量和局部复制

那主从复制会存在哪些问题呢?

  1. 一旦主节点宕机,从节点降职为主节点,同时须要批改利用方的主节点地址,还须要命令所有从节点去复制新的主节点,整个过程须要人工干预
  2. 主节点的写能力受到单机的限度
  3. 主节点的存储能力受到单机的限度
  4. 原生复制的弊病在晚期的版本中也会比较突出,比方:redis 复制中断后,从节点会发动 psync。此时如果同步不胜利,则会进行全量同步,主库执行全量备份的同时,可能会造成毫秒或秒级的卡顿

那比拟支流的解决方案是什么呢?哨兵

什么是哨兵

上图 展现了一个典型的哨兵架构图,它由两局部组成,哨兵节点和数据节点:

  • 哨兵节点: 哨兵零碎由一个或多个哨兵节点组成,哨兵节点是非凡的 Redis 节点,不存储数据
  • 数据节点: 主节点和从节点都是数据节点;

哨兵的介绍

sentinel,中文名是哨兵。哨兵是 redis 集群机构中十分重要的一个组件,次要有以下性能:

  1. 集群监控:负责监控 redis master 和 slave 过程是否失常工作。
  2. 音讯告诉:如果某个 redis 实例有故障,那么哨兵负责发送音讯作为报警告诉给管理员。
  3. 故障转移:如果 master node 挂掉了,会主动转移到 slave node 上。
  4. 配置核心:如果故障转移产生了,告诉 client 客户端新的 master 地址。

哨兵用于实现 redis 集群的高可用,自身也是分布式的,作为一个哨兵集群去运行,相互协同工作。

哨兵的外围常识

  1. 哨兵至多须要 3 个实例,来保障本人的健壮性。
  2. 哨兵 + redis 主从的部署架构,是不保证数据零失落的,只能保障 redis 集群的高可用性。
  3. 对于哨兵 + redis 主从这种简单的部署架构,尽量在测试环境和生产环境,都进行短缺的测试和演练。

那你能说下哨兵的工作原理吗?

  1. 每个 Sentinel 节点都须要定期执行以下工作:每个 Sentinel 以每秒一次的频率,向它所知的主服务器、从服务器以及其余的 Sentinel 实例发送一个 PING 命令。
  2. 如果一个实例间隔最初一次无效回复 PING 命令的工夫超过 down-after-milliseconds 所指定的值,那么这个实例会被 Sentinel 标记为主观下线
  3. 如果一个主服务器被标记为主观下线,那么正在监督这个服务器的所有 Sentinel 节点,要以每秒一次的频率确认主服务器确实进入了主观下线状态
  4. 如果一个主服务器被标记为主观下线,并且有足够数量的 Sentinel(至多要达到配置文件指定的数量)在指定的工夫范畴内批准这一判断,那么这个主服务器被标记为主观下线
  5. 个别状况下,每个 Sentinel 会以每 10 秒一次的频率向它已知的所有主服务器和从服务器发送 INFO 命令,当一个主服务器被标记为主观下线时,Sentinel 向下线主服务器的所有从服务器发送 INFO 命令的频率,会从 10 秒一次改为每秒一次
  6. Sentinel 和其余 Sentinel 协商主观下线的主节点的状态,如果处于 SDOWN 状态,则投票主动选出新的主节点,将残余从节点指向新的主节点进行数据复制
  7. 当没有足够数量的 Sentinel 批准主服务器下线时,主服务器的主观下线状态就会被移除。当主服务器从新向 Sentinel 的 PING 命令返回无效回复时,主服务器的主观下线状态就会被移除

新的主服务器是怎么被筛选进去的?

故障转移操作的第一步 要做的就是在已下线主服务器属下的所有从服务器中,挑选出一个状态良好、数据残缺的从服务器,而后向这个从服务器发送 slaveof no one 命令,将这个从服务器转换为主服务器。然而这个从服务器是怎么样被筛选进去的呢?

简略来说 Sentinel 应用以下规定来抉择新的主服务器:

  1. 在生效主服务器属下的从服务器当中,那些被标记为主观下线、已断线、或者最初一次回复 PING 命令的工夫大于五秒钟的从服务器都会被 淘汰
  2. 在生效主服务器属下的从服务器当中,那些与生效主服务器连贯断开的时长超过 down-after 选项指定的时长十倍的从服务器都会被 淘汰
  3. 经验了以上两轮淘汰之后 剩下来的从服务器中,咱们选出 复制偏移量(replication offset)最大 的那个 从服务器 作为新的主服务器;如果复制偏移量不可用,或者从服务器的复制偏移量雷同,那么 带有最小运行 ID 的那个从服务器成为新的主服务器。

Redis 集群应用过吗?原理?

上图 展现了 Redis Cluster 典型的架构图,集群中的每一个 Redis 节点都 相互两两相连 ,客户端任意 直连 到集群中的 任意一台 ,就能够对其余 Redis 节点进行 读写 的操作。

基本原理

Redis 集群中内置了 16384 个哈希槽。当客户端连贯到 Redis 集群之后,会同时失去一份对于这个 集群的配置信息 ,当客户端具体对某一个 key 值进行操作时,会计算出它的一个 Hash 值,而后把后果对 16384 求余数 ,这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽,Redis 会依据节点数量 大抵均等 的将哈希槽映射到不同的节点。

再联合集群的配置信息就可能晓得这个 key 值应该存储在哪一个具体的 Redis 节点中,如果不属于本人管,那么就会应用一个非凡的 MOVED 命令来进行一个跳转,通知客户端去连贯这个节点以获取数据:

GET x
-MOVED 3999 127.0.0.1:6381

MOVED 指令第一个参数 3999key 对应的槽位编号,前面是指标节点地址,MOVED 命令后面有一个减号,示意这是一个谬误的音讯。客户端在收到 MOVED 指令后,就立刻纠正本地的 槽位映射表,那么下一次再拜访 key 时就可能到正确的中央去获取了。

集群的次要作用

  1. 数据分区: 数据分区 (或称数据分片) 是集群最外围的性能。集群将数据扩散到多个节点,一方面 冲破了 Redis 单机内存大小的限度, 存储容量大大增加 另一方面 每个主节点都能够对外提供读服务和写服务, 大大提高了集群的响应能力。Redis 单机内存大小受限问题,在介绍长久化和主从复制时都有提及,例如,如果单机内存太大,bgsavebgrewriteaoffork 操作可能导致主过程阻塞,主从环境下主机切换时可能导致从节点长时间无奈提供服务,全量复制阶段主节点的复制缓冲区可能溢出……
  2. 高可用: 集群反对主从复制和主节点的 主动故障转移 (与哨兵相似),当任一节点产生故障时,集群依然能够对外提供服务。

集群中数据如何分区?

Redis 采纳计划三。

计划一:哈希值 % 节点数

哈希取余分区思路非常简单:计算 key 的 hash 值,而后对节点数量进行取余,从而决定数据映射到哪个节点上。

不过该计划最大的问题是,当新增或删减节点时 ,节点数量发生变化,零碎中所有的数据都须要 从新计算映射关系,引发大规模数据迁徙。

计划二:一致性哈希分区

一致性哈希算法将 整个哈希值空间 组织成一个虚构的圆环,范畴是 [0 – 232 – 1],对于每一个数据,依据 key 计算 hash 值,确数据在环上的地位,而后从此地位沿顺时针行走,找到的第一台服务器就是其应该映射到的服务器:

与哈希取余分区相比,一致性哈希分区将 增减节点的影响限度在相邻节点。以上图为例,如果在 node1node2 之间减少 node5,则只有 node2 中的一部分数据会迁徙到 node5;如果去掉 node2,则原 node2 中的数据只会迁徙到 node4 中,只有 node4 会受影响。

一致性哈希分区的次要问题在于,当 节点数量较少 时,减少或删减节点, 对单个节点的影响可能很大,造成数据的重大不均衡。还是以上图为例,如果去掉 node2node4 中的数据由总数据的 1/4 左右变为 1/2 左右,与其余节点相比负载过高。

计划三:带有虚构节点的一致性哈希分区

该计划在 一致性哈希分区的根底上 ,引入了 虚构节点 的概念。Redis 集群应用的便是该计划,其中的虚构节点称为 槽(slot)。槽是介于数据和理论节点之间的虚构概念,每个理论节点蕴含肯定数量的槽,每个槽蕴含哈希值在肯定范畴内的数据。

在应用了槽的一致性哈希分区中,槽是数据管理和迁徙的根本单位 。槽 解耦 数据和理论节点 之间的关系,减少或删除节点对系统的影响很小。仍以上图为例,零碎中有 4 个理论节点,假如为其调配 16 个槽(0-15);

  • 槽 0-3 位于 node1;4-7 位于 node2;以此类推….

如果此时删除 node2,只须要将槽 4-7 重新分配即可,例如槽 4-5 调配给 node1,槽 6 调配给 node3,槽 7 调配给 node4;能够看出删除 node2 后,数据在其余节点的散布依然较为平衡。

节点之间的通信机制理解吗?

集群的建设离不开节点之间的通信,如果咱们启动六个集群节点之后通过 redis-cli 命令帮忙咱们搭建起来了集群,实际上背地每个集群之间的两两连贯是通过了 CLUSTER MEET 命令发送 MEET 音讯实现的,上面咱们开展具体说说。

两个端口

哨兵零碎 中,节点分为 数据节点 哨兵节点 :前者存储数据,后者实现额定的管制性能。在 集群 中,没有数据节点与非数据节点之分: 所有的节点都存储数据,也都参加集群状态的保护。为此,集群中的每个节点,都提供了两个 TCP 端口:

  • 一般端口: 即咱们在后面指定的端口 (7000 等)。一般端口次要用于为客户端提供服务 (与单机节点相似);但在节点间数据迁徙时也会应用。
  • 集群端口: 端口号是一般端口 + 10000 (10000 是固定值,无奈扭转),如 7000 节点的集群端口为 17000集群端口只用于节点之间的通信,如搭建集群、增减节点、故障转移等操作时节点间的通信;不要应用客户端连贯集群接口。为了保障集群能够失常工作,在配置防火墙时,要同时开启一般端口和集群端口。

Gossip 协定

节点间通信,依照通信协议能够分为几种类型:单对单、播送、Gossip 协定等。重点是播送和 Gossip 的比照。

  • 播送是指向集群内所有节点发送音讯。长处 是集群的收敛速度快(集群收敛是指集群内所有节点取得的集群信息是统一的), 毛病 是每条音讯都要发送给所有节点,CPU、带宽等耗费较大。
  • Gossip 协定的特点是:在节点数量无限的网络中,每个节点都“随机”的与局部节点通信 (并不是真正的随机,而是依据特定的规定抉择通信的节点),通过一番横七竖八的通信,每个节点的状态很快会达到统一。Gossip 协定的 长处 有负载 (比播送) 低、去中心化、容错性高 (因为通信有冗余) 等;毛病 次要是集群的收敛速度慢。

音讯类型

集群中的节点采纳 固定频率(每秒 10 次)定时工作 进行通信相干的工作:判断是否须要发送音讯及音讯类型、确定接管节点、发送音讯等。如果集群状态产生了变动,如增减节点、槽状态变更,通过节点间的通信,所有节点会很快得悉整个集群的状态,使集群收敛。

节点间发送的音讯次要分为 5 种:meet 音讯ping 音讯pong 音讯fail 音讯publish 音讯。不同的音讯类型,通信协议、发送的频率和机会、接管节点的抉择等是不同的:

  • MEET 音讯: 在节点握手阶段,当节点收到客户端的 CLUSTER MEET 命令时,会向新退出的节点发送 MEET 音讯,申请新节点退出到以后集群;新节点收到 MEET 音讯后会回复一个 PONG 音讯。
  • PING 音讯: 集群里每个节点每秒钟会抉择局部节点发送 PING 音讯,接收者收到音讯后会回复一个 PONG 音讯。PING 音讯的内容是本身节点和局部其余节点的状态信息 ,作用是彼此替换信息,以及检测节点是否在线。PING 音讯应用 Gossip 协定发送,接管节点的抉择兼顾了收敛速度和带宽老本, 具体规定如下 :(1) 随机找 5 个节点,在其中抉择最久没有通信的 1 个节点;(2)扫描节点列表,抉择最近一次收到 PONG 音讯工夫大于 cluster_node_timeout / 2 的所有节点,避免这些节点长时间未更新。
  • PONG 音讯: PONG 音讯封装了本身状态数据。能够分为两种:第一种 是在接到 MEET/PING 音讯后回复的 PONG 音讯; 第二种 是指节点向集群播送 PONG 音讯,这样其余节点能够获知该节点的最新信息,例如故障复原后新的主节点会播送 PONG 音讯。
  • FAIL 音讯: 当一个主节点判断另一个主节点进入 FAIL 状态时,会向集群播送这一 FAIL 音讯;接管节点会将这一 FAIL 音讯保存起来,便于后续的判断。
  • PUBLISH 音讯: 节点收到 PUBLISH 命令后,会先执行该命令,而后向集群播送这一音讯,接管节点也会执行该 PUBLISH 命令。

集群数据如何存储的有理解吗?

节点须要专门的数据结构来存储集群的状态。所谓集群的状态,是一个比拟大的概念,包含:集群是否处于上线状态、集群中有哪些节点、节点是否可达、节点的主从状态、槽的散布……

节点为了存储集群状态而提供的数据结构中,最要害的是 clusterNodeclusterState 构造:前者记录了一个节点的状态,后者记录了集群作为一个整体的状态。

clusterNode 构造

clusterNode 构造保留了 一个节点的以后状态,包含创立工夫、节点 id、ip 和端口号等。每个节点都会用一个 clusterNode 构造记录本人的状态,并为集群内所有其余节点都创立一个 clusterNode 构造来记录节点状态。

上面列举了 clusterNode 的局部字段,并阐明了字段的含意和作用:

typedef struct clusterNode {
    // 节点创立工夫
    mstime_t ctime;
    // 节点 id
    char name[REDIS_CLUSTER_NAMELEN];
    // 节点的 ip 和端口号
    char ip[REDIS_IP_STR_LEN];
    int port;
    // 节点标识:整型,每个 bit 都代表了不同状态,如节点的主从状态、是否在线、是否在握手等
    int flags;
    // 配置纪元:故障转移时起作用,相似于哨兵的配置纪元
    uint64_t configEpoch;
    // 槽在该节点中的散布:占用 16384/ 8 个字节,16384 个比特;每个比特对应一个槽:比特值为 1,则该比特对应的槽在节点中;比特值为 0,则该比特对应的槽不在节点中
    unsigned char slots[16384/8];
    // 节点中槽的数量
    int numslots;
    …………
} clusterNode;

除了上述字段,clusterNode 还蕴含节点连贯、主从复制、故障发现和转移须要的信息等。

clusterState 构造

clusterState 构造保留了在以后节点视角下,集群所处的状态。次要字段包含:

typedef struct clusterState {
    // 本身节点
    clusterNode *myself;
    // 配置纪元
    uint64_t currentEpoch;
    // 集群状态:在线还是下线
    int state;
    // 集群中至多蕴含一个槽的节点数量
    int size;
    // 哈希表,节点名称 ->clusterNode 节点指针
    dict *nodes;
    // 槽散布信息:数组的每个元素都是一个指向 clusterNode 构造的指针;如果槽还没有调配给任何节点,则为 NULL
    clusterNode *slots[16384];
    …………
} clusterState;

除此之外,clusterState 还包含故障转移、槽迁徙等须要的信息。

Redis 集群最大节点个数是多少?

16384

Redis 集群会有写操作失落吗?为什么?

Redis 并不能保证数据的强一致性,这象征这在理论中集群在特定的条件下可能会失落写操作。

Redis 集群之间是如何复制的?

异步复制

Redis 是单线程的,如何进步多核 CPU 的利用率?

能够在同一个服务器部署多个 Redis 的实例,并把他们当作不同的服务器来应用,在某些时候,无论如何一个服务器是不够的,所以,如果你想应用多个 CPU,你能够考虑一下分片(shard)。

为什么要做 Redis 分区?

分区能够让 Redis 治理更大的内存,Redis 将能够应用所有机器的内存。如果没有分区,你最多只能应用一台机器的内存。分区使 Redis 的计算能力通过简略地减少计算机失去成倍晋升,Redis 的网络带宽也会随着计算机和网卡的减少而成倍增长。

有哪些 Redis 分区实现计划?

  1. 客户端分区就是在客户端就曾经决定数据会被存储到哪个 redis 节点或者从哪个 redis 节点读取。大多数客户端曾经实现了客户端分区。
  2. 代理分区 意味着客户端将申请发送给代理,而后代理决定去哪个节点写数据或者读数据。代理依据分区规定决定申请哪些 Redis 实例,而后依据 Redis 的响应后果返回给客户端。redis 和 memcached 的一种代理实现就是 Twemproxy
  3. 查问路由(Query routing) 的意思是客户端随机地申请任意一个 redis 实例,而后由 Redis 将申请转发给正确的 Redis 节点。Redis Cluster 实现了一种混合模式的查问路由,但并不是间接将申请从一个 redis 节点转发到另一个 redis 节点,而是在客户端的帮忙下间接 redirected 到正确的 redis 节点。

Redis 分区有什么毛病?

  1. 波及多个 key 的操作通常不会被反对。例如你不能对两个汇合求交加,因为他们可能被存储到不同的 Redis 实例(实际上这种状况也有方法,然而不能间接应用交加指令)。
  2. 同时操作多个 key, 则不能应用 Redis 事务.
  3. 分区应用的粒度是 key,不能应用一个十分长的排序 key 存储一个数据集
  4. 当应用分区的时候,数据处理会非常复杂,例如为了备份你必须从不同的 Redis 实例和主机同时收集 RDB / AOF 文件。
  5. 分区时动静扩容或缩容可能非常复杂。Redis 集群在运行时减少或者删除 Redis 节点,能做到最大水平对用户通明地数据再均衡,但其余一些客户端分区或者代理分区办法则不反对这种个性。然而,有一种预分片的技术也能够较好的解决这个问题。

六、Redis 内存相干问题

Redis 过期键的删除策略?

先抛开 Redis 想一下几种可能的删除策略:

  1. 定时删除: 在设置键的过期工夫的同时,创立一个定时器 timer. 让定时器在键的过期工夫来长期,立刻执行对键的删除操作。
  2. 惰性删除: 放任键过期不论,然而每次从键空间中获取键时,都查看获得的键是否过期,如果过期的话,就删除该键; 如果没有过期,就返回该键。
  3. 定期删除: 每隔一段时间程序就对数据库进行一次查看,删除外面的过期键。至于要删除多少过期键,以及要查看多少个数据库,则由算法决定。

在上述的三种策略中定时删除和定期删除属于不同工夫粒度的 被动删除 ,惰性删除属于 被动删除

三种策略都有各自的优缺点

  1. 定时删除对内存使用率有劣势,然而对 CPU 不敌对;
  2. 惰性删除对内存不敌对,如果某些键值对始终不被应用,那么会造成一定量的内存节约;
  3. 定期删除是定时删除和惰性删除的折中。

Redis 中的实现

Reids 采纳的是 惰性删除和定时删除 的联合,一般来说能够借助最小堆来实现定时器,不过 Redis 的设计思考到工夫事件的无限品种和数量,应用了无序链表存储工夫事件,这样如果在此基础上实现定时删除,就意味着 O(N) 遍历获取最近须要删除的数据。

实现过期键惰性删除策略的外围是 db.c/expireIfNeeded 函数 —— 所有命令在读取或写入数据库之前,程序都会调用 expireIfNeeded 对输出键进行查看,并将过期键删除:

![digraph expire_check { node [style = filled, shape = plaintext]; edge [style = bold]; // node write_commands [label = “SET、\n LPUSH、\n SADD、\n 等等 ”, fillcolor = “#FADCAD”]; read_commands [label = “GET、\n LRANGE、\n SMEMBERS、\n 等等 ”, fillcolor = “#FADCAD”]; expire_if_needed [label = “ 调用 expire_if_needed() \n 删除过期键 ”, shape = box, fillcolor = “#A8E270”]; process [label = “ 执行理论的命令流程 ”]; // edge write_commands -> expire_if_needed [label = “ 写申请 ”]; read_commands -> expire_if_needed [label = “ 读申请 ”]; expire_if_needed -> process; }](https://redisbook.readthedocs.io/en/latest/_images/graphviz-e…)

比如说,GET 命令的执行流程能够用下图来示意:

![digraph get_with_expire { node [style = filled, shape = plaintext]; edge [style = bold]; // node get [label = “GET key”, fillcolor = “#FADCAD”]; expire_if_needed [label = “ 调用 \n expire_if_needed() \n 如果键曾经过期 \n 那么将它删除 ”, shape = diamond, fillcolor = “#A8E270”]; expired_and_deleted [label = “key 不存在 \n 向客户端返回 NIL”]; not_expired [label = “ 向客户端返回 key 的值 ”]; get -> expire_if_needed; expire_if_needed -> expired_and_deleted [label = “ 已过期 ”]; expire_if_needed -> not_expired [label = “ 未过期 ”]; }](https://redisbook.readthedocs.io/en/latest/_images/graphviz-a…)

expireIfNeeded 的作用是,如果输出键曾经过期的话,那么将键、键的值、键保留在 expires 字典中的过期工夫都删除掉。

对过期键的定期删除由 redis.c/activeExpireCycle 函执行:每当 Redis 的例行处理程序 serverCron 执行时,activeExpireCycle 都会被调用 —— 这个函数在规定的工夫限度内,尽可能地遍历各个数据库的 expires 字典,随机地查看一部分键的过期工夫,并删除其中的过期键。

Redis 的淘汰策略有哪些?

Redis 有六种淘汰策略

为了保障 Redis 的平安稳固运行,设置了一个 max-memory 的阈值,那么当内存用量达到阈值,新写入的键值对无奈写入,此时就须要内存淘汰机制,在 Redis 的配置中有几种淘汰策略能够抉择,具体如下:

策略 形容
volatile-lru 从已设置过期工夫的 KV 集中优先对最近起码应用 (less recently used) 的数据淘汰
volitile-ttl 从已设置过期工夫的 KV 集中优先对剩余时间短 (time to live) 的数据淘汰
volitile-random 从已设置过期工夫的 KV 集中随机抉择数据淘汰
allkeys-lru 从所有 KV 集中优先对最近起码应用 (less recently used) 的数据淘汰
allKeys-random 从所有 KV 集中随机抉择数据淘汰
noeviction 不淘汰策略,若超过最大内存,返回错误信息

4.0 版本后减少以下两种

  • volatile-lfu:从已设置过期工夫的数据集 (server.db[i].expires) 中筛选最不常常应用的数据淘汰
  • allkeys-lfu:当内存不足以包容新写入数据时,在键空间中,移除最不常常应用的 key

七、Redis 缓存异样问题

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

  1. Master 最好不要做任何长久化工作,包含内存快照和 AOF 日志文件,特地是不要启用内存快照做长久化。
  2. 如果数据比拟要害,某个 Slave 开启 AOF 备份数据,策略为每秒同步一次。
  3. 为了主从复制的速度和连贯的稳定性,Slave 和 Master 最好在同一个局域网内。
  4. 尽量避免在压力较大的主库上减少从库。
  5. Master 调用 BGREWRITEAOF 重写 AOF 文件,AOF 在重写的时候会占大量的 CPU 和内存资源,导致服务 load 过高,呈现短暂服务暂停景象。
  6. 为了 Master 的稳定性,主从复制不要用图状构造,用单向链表构造更稳固,即主从关系为:Master<–Slave1<–Slave2<–Slave3…,这样的构造也不便解决单点故障问题,实现 Slave 对 Master 的替换,也即,如果 Master 挂了,能够立马启用 Slave1 做 Master,其余不变。

如何保障缓存与数据库双写时的数据一致性?

你只有用缓存,就可能会波及到缓存与数据库双存储双写,你只有是双写,就肯定会有数据一致性的问题,那么你如何解决一致性问题?

一般来说,就是如果你的零碎不是严格要求缓存 + 数据库必须一致性的话,缓存能够略微的跟数据库偶然有不统一的状况,最好不要做这个计划,读申请和写申请串行化,串到一个内存队列里去,这样就能够保障肯定不会呈现不统一的状况。

串行化之后,就会导致系统的吞吐量会大幅度的升高,用比失常状况下多几倍的机器去撑持线上的一个申请。

操作缓存的时候咱们都是采取 删除缓存 策略的,起因如下:

  1. 高并发环境下,无论是先操作数据库还是后操作数据库而言,如果加上更新缓存,那就 更加容易 导致数据库与缓存数据不统一问题。(删除缓存 间接和简略 很多)
  2. 如果每次更新了数据库,都要更新缓存【这里指的是频繁更新的场景,这会消耗肯定的性能】,倒不如间接删除掉。等再次读取时,缓存里没有,那我到数据库找,在数据库找到再写到缓存里边 (体现 懒加载)

这里就又有个问题:是先更新数据库,再删除缓存,还是先删除缓存,再更新数据库呢

先更新数据库,再删除缓存

失常的状况是这样的:

  • 先操作数据库,胜利;
  • 再删除缓存,也胜利;

如果原子性被毁坏了:

  • 第一步胜利 (操作数据库),第二步失败(删除缓存),会导致 数据库里是新数据,而缓存里是旧数据
  • 如果第一步 (操作数据库) 就失败了,咱们能够间接返回谬误(Exception),不会呈现数据不统一。

如果在高并发的场景下,呈现数据库与缓存数据不统一的 概率特地低,也不是没有:

  • 缓存 刚好 生效
  • 线程 A 查询数据库,得一个旧值
  • 线程 B 将新值写入数据库
  • 线程 B 删除缓存
  • 线程 A 将查到的旧值写入缓存

先删除缓存,再更新数据库

失常状况是这样的:

  • 先删除缓存,胜利;
  • 再更新数据库,也胜利;

如果原子性被毁坏了:

  • 第一步胜利(删除缓存),第二步失败(更新数据库),数据库和缓存的数据还是统一的。
  • 如果第一步 (删除缓存) 就失败了,咱们能够间接返回谬误(Exception),数据库和缓存的数据还是统一的。

看起来是很美妙,然而咱们在并发场景下剖析一下,就晓得还是有问题的了:

  • 线程 A 删除了缓存
  • 线程 B 查问,发现缓存已不存在
  • 线程 B 去数据库查问失去旧值
  • 线程 B 将旧值写入缓存
  • 线程 A 将新值写入数据库

所以也会导致数据库和缓存不统一的问题。然而咱们个别抉择这种

举荐浏览:

https://mp.weixin.qq.com/s/3Fmv7h5p2QDtLxc9n1dp5A

https://zhuanlan.zhihu.com/p/48334686

应用缓存会呈现什么问题?

Redis 雪崩

<mark> 缓存雪崩是指缓存同一时间大面积的生效 </mark>,所以,前面的申请都会落到数据库上,造成数据库短时间内接受大量申请而崩掉。

解决方案

  1. 缓存数据的过期工夫设置随机,避免同一时间大量数据过期景象产生。
  2. 个别并发量不是特地多的时候,应用最多的解决方案是加锁排队(key 上锁,其余线程不能拜访,假如在高并发下,缓存重建期间 key 是锁着的,这是过去 1000 个申请 999 个都在阻塞的。同样会导致用户期待超时,这是个治标不治本的办法!)。
  3. 给每一个缓存数据减少相应的缓存标记,记录缓存的是否生效,如果缓存标记生效,则更新数据缓存。

缓存穿透

<mark> 缓存穿透是指缓存和数据库中都没有的数据 </mark>,导致所有的申请都落到数据库上,造成数据库短时间内接受大量申请而崩掉。

解决方案

  1. 接口层减少校验,如用户鉴权校验,id 做根底校验,id<= 0 的间接拦挡;
  2. 从缓存取不到的数据,在数据库中也没有取到,这时也能够将 key-value 对写为 key-null,缓存无效工夫能够设置短点,如 30 秒(设置太长会导致失常状况也没法应用)。这样能够避免攻打用户重复用同一个 id 暴力攻打
  3. 采纳布隆过滤器,将所有可能存在的数据哈希到一个足够大的 bitmap 中,一个肯定不存在的数据会被这个 bitmap 拦挡掉,从而防止了对底层存储系统的查问压力。

缓存击穿

<mark> 缓存击穿是指缓存中没有但数据库中有的数据 </mark>(个别是缓存工夫到期),这时因为并发用户特地多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力霎时增大,造成过大压力。

缓存击穿是指一个 Key 十分热点,在不停地扛着大量的申请,大并发集中对这一个点进行拜访,当这个 Key 在生效的霎时,继续的大并发间接落到了数据库上,就在这个 Key 的点上击穿了缓存

和缓存雪崩不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。

解决方案

  1. 热点数据永远不过期
  2. 加互斥锁

缓存预热

缓存预热就是零碎上线后,将相干的缓存数据间接加载到缓存零碎。这样就能够防止在用户申请的时候,先查询数据库,而后再将数据缓存的问题!用户间接查问当时被预热的缓存数据!

解决方案

  1. 间接写个缓存刷新页面,上线时手工操作一下;
  2. 数据量不大,能够在我的项目启动的时候主动进行加载;
  3. 定时刷新缓存;

缓存降级

当访问量剧增、服务呈现问题(如响应工夫慢或不响应)或非核心服务影响到外围流程的性能时,依然须要保障服务还是可用的,即便是有损服务。零碎能够依据一些要害数据进行主动降级,也能够配置开关实现人工降级。

缓存降级的最终目标是保障外围服务可用,即便是有损的。而且有些服务是无奈降级的(如退出购物车、结算)。

在进行降级之前要对系统进行梳理,看看零碎是不是能够丢卒保帅;从而梳理出哪些必须誓死爱护,哪些可降级;比方能够参考日志级别设置预案:

  1. 个别:比方有些服务偶然因为网络抖动或者服务正在上线而超时,能够主动降级;
  2. 正告:有些服务在一段时间内成功率有稳定(如在 95~100% 之间),能够主动降级或人工降级,并发送告警;
  3. 谬误:比方可用率低于 90%,或者数据库连接池被打爆了,或者访问量忽然猛增到零碎能接受的最大阀值,此时能够依据状况主动降级或者人工降级;
  4. 严重错误:比方因为非凡起因数据谬误了,此时须要紧急人工降级。

服务降级的目标,是为了避免 Redis 服务故障,导致数据库跟着一起产生雪崩问题。因而,对于不重要的缓存数据,能够采取服务降级策略,例如一个比拟常见的做法就是,Redis 呈现问题,不去数据库查问,而是间接返回默认值给用户。

缓存热点 key

缓存中的一个 Key(比方一个促销商品),在某个工夫点过期的时候,恰好在这个工夫点对这个 Key 有大量的并发申请过去,这些申请发现缓存过期个别都会从后端 DB 加载数据并回设到缓存,这个时候大并发的申请可能会霎时把后端 DB 压垮。

解决方案

  1. 对缓存查问加锁,如果 KEY 不存在,就加锁,而后查 DB 入缓存,而后解锁;
  2. 其余过程如果发现有锁就期待,而后等解锁后返回数据或者进入 DB 查问

八、分布式相干问题

Redis 实现分布式锁

Redis 为单过程单线程模式,采纳队列模式将并发拜访变成串行拜访,且多客户端对 Redis 的连贯并不存在竞争关系 Redis 中能够应用 SETNX 命令实现分布式锁。

当且仅当 key 不存在,将 key 的值设为 value。若给定的 key 曾经存在,则 SETNX 不做任何动作

SETNX 是『SET if Not eXists』(如果不存在,则 SET)的简写。

返回值:设置胜利,返回 1。设置失败,返回 0。

应用 SETNX 实现同步锁的流程及事项如下:

应用 SETNX 命令获取锁,若返回 0(key 已存在,锁已存在)则获取失败,反之获取胜利

为了避免获取锁后程序出现异常,导致其余线程 / 过程调用 SETNX 命令总是返回 0 而进入死锁状态,须要为该 key 设置一个“正当”的过期工夫

开释锁,应用 DEL 命令将锁数据删除

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

所谓 Redis 的并发竞争 Key 的问题也就是多个零碎同时对一个 key 进行操作,然而最初执行的程序和咱们冀望的程序不同,这样也就导致了后果的不同!

举荐一种计划:分布式锁(zookeeper 和 redis 都能够实现分布式锁)。(如果不存在 Redis 的并发竞争 Key 问题,不要应用分布式锁,这样会影响性能)

基于 zookeeper 长期有序节点能够实现的分布式锁。大抵思维为:每个客户端对某个办法加锁时,在 zookeeper 上的与该办法对应的指定节点的目录下,生成一个惟一的刹时有序节点。判断是否获取锁的形式很简略,只须要判断有序节点中序号最小的一个。当开释锁的时候,只需将这个刹时节点删除即可。同时,其能够防止服务宕机导致的锁无奈开释,而产生的死锁问题。实现业务流程后,删除对应的子节点开释锁。

在实践中,当然是从以可靠性为主。所以首推 Zookeeper。

参考:https://www.jianshu.com/p/8bddd381de06

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

既然 Redis 是如此的轻量(单实例只应用 1M 内存),为避免当前的扩容,最好的方法就是一开始就启动较多实例。即使你只有一台服务器,你也能够一开始就让 Redis 以分布式的形式运行,应用分区,在同一台服务器上启动多个实例。

一开始就多设置几个 Redis 实例,例如 32 或者 64 个实例,对大多数用户来说这操作起来可能比拟麻烦,然而从短暂来看做这点就义是值得的。

这样的话,当你的数据一直增长,须要更多的 Redis 服务器时,你须要做的就是仅仅将 Redis 实例从一台服务迁徙到另外一台服务器而已(而不必思考从新分区的问题)。一旦你增加了另一台服务器,你须要将你一半的 Redis 实例从第一台机器迁徙到第二台机器。

什么是 RedLock

Redis 官网站提出了一种权威的基于 Redis 实现分布式锁的形式名叫 Redlock,此种形式比原先的单节点的办法更平安。它能够保障以下个性:

平安个性:互斥拜访,即永远只有一个 client 能拿到锁
防止死锁:最终 client 都可能拿到锁,不会呈现死锁的状况,即便本来锁住某资源的 client crash 了或者呈现了网络分区
容错性:只有大部分 Redis 节点存活就能够失常提供服务


十、其余问题

应用 Redis 做过异步队列吗,是如何实现的

应用 list 类型保留数据信息,rpush 生产音讯,lpop 生产音讯,当 lpop 没有音讯时,能够 sleep 一段时间,而后再查看有没有信息,如果不想 sleep 的话,能够应用 blpop, 在没有信息的时候,会始终阻塞,直到信息的到来。redis 能够通过 pub/sub 主题订阅模式实现一个生产者,多个消费者,当然也存在肯定的毛病,当消费者下线时,生产的音讯会失落。

Redis 如何实现延时队列

应用 sortedset,应用工夫戳做 score, 音讯内容作为 key,调用 zadd 来生产音讯,消费者应用 zrangbyscore 获取 n 秒之前的数据做轮询解决。

Redis 如何做内存优化?

尽可能应用散列表(hashes),散列表(是说散列表外面存储的数少)应用的内存十分小,所以你应该尽可能的将你的数据模型形象到一个散列表外面。比方你的 web 零碎中有一个用户对象,不要为这个用户的名称,姓氏,邮箱,明码设置独自的 key,而是应该把这个用户的所有信息存储到一张散列表外面。

起源

https://juejin.im/post/6844904017387077640

https://www.wmyskxz.com/2020/03/25/dong-yi-dian-python-xi-lie…

https://mp.weixin.qq.com/s/f9N13fnyTtnu2D5sKZiu9w

https://blog.csdn.net/ThinkWon/article/details/103522351/

本文由 mdnice 多平台公布

正文完
 0