Redis连环五十二问!看谁顶得住?

已有133人围观 来源:架构师 发布于:2024-04-08 16:16:28
架构师(JiaGouX)
我们都是架构师!
架构未来,你来不来?



基本

1.说说什么是Redis?

Redis是一种基于键值对(key-value)的NoSQL数据库。

比一般键值对数据库壮大的处所,Redis中的value支撑string(字符串)、hash(哈希)、 list(列表)、set(聚集)、zset(有序聚集)、Bitmaps(位图)、 HyperLogLog、GEO(地理信息定位)等多种数据构造,因此 Redis可以满足很多的应用处景。

而且因为Redis会将所有数据都寄存在内存中,所以它的读写性能非常精彩。

不仅如此,Redis还可以将内存的数据应用快照和日志的情势保留到硬盘上,这样在发生相似断电或者机器故障的时候,内存中的数据不会“丧失”。

除了上述功效以外,Redis还供给了键过期、宣布订阅、事务、流水线、Lua脚本等附加功效。

总之,Redis是一款壮大的性能利器。

2.Redis可以用来干什么?

Redis
  1. 缓存

    这是Redis应用最普遍处所,基本所有的Web应用都会应用Redis作为缓存,来降低数据源压力,进步响应速度。

  2. 计数器 Redis天然支撑计数功效,而且计数性能非常好,可以用来记载阅读量、点赞量等等。

  3. 排行榜 Redis供给了列表和有序聚集数据构造,合理地应用这些数据构造可以很便利地构建各种排行榜体系。

  4. 社交网络 赞/踩、粉丝、共同好友/爱好、推送、下拉刷新。

  5. 资讯队列 Redis供给了宣布订阅功效和阻塞队列的功效,可以满足一般资讯队列功效。

  6. 散布式锁 散布式环境下,应用Redis实现散布式锁,也是Redis常见的应用。

Redis的应用一般会联合项目去问,以一个电商项目的用户服务为例:

  • Token存储:用户登录胜利之后,应用Redis存储Token
  • 登录失败次数计数:应用Redis计数,登录失败超过必定次数,锁定账号
  • 地址缓存:对省市区数据的缓存
  • 散布式锁:散布式环境下登录、注册等操作加散布式锁
  • ……

3.Redis 有哪些数据构造?

Redis有五种基本数据构造。

string

字符串最基本的数据构造。字符串类型的值实际可以是字符串(简略的字符串、庞杂的字符串(例如JSON、XML))、数字 (整数、浮点数),甚至是二进制(图片、音频、视频),但是值最大不能超过512MB。

字符串重要有以下几个典范应用处景:

  • 缓存功效
  • 计数
  • 共享Session
  • 限速

hash

哈希类型是指键值本身又是一个键值对构造。

哈希重要有以下典范应用处景:

  • 缓存用户信息
  • 缓存对象

list

列表(list)类型是用来存储多个有序的字符串。列表是一种比较灵巧的数据构造,它可以充任栈和队列的角色

列表重要有以下几种应用处景:

  • 资讯队列
  • 文章列表

set

聚集(set)类型也是用来保留多个的字符串元素,但和列表类型不一 样的是,聚集中不许可有反复元素,并且聚集中的元素是无序的。

聚集重要有如下应用处景:

  • 标签(tag)
  • 共同关注

sorted set

有序聚集中的元素可以排序。但是它和列表应用索引下标作为排序依据不同的是,它给每个元素设置一个权重(score)作为排序的依据。

有序聚集重要应用处景:

  • 用户点赞统计
  • 用户排序

4.Redis为什么快呢?

Redis的速度⾮常的快,单机的Redis就可以⽀撑每秒十几万的并发,相对于MySQL来说,性能是MySQL的⼏⼗倍。速度快的原因重要有⼏点:

  1. 完全基于内存操作
  2. 使⽤单线程,避免了线程切换和竞态发生的消费
  3. 基于⾮阻塞的IO多路复⽤机制
  4. C语⾔实现,优化过的数据构造,基于⼏种基本的数据构造,redis做了⼤量的优化,性能极⾼

5.能说一下I/O多路复用吗?

引用知乎上一个高赞的答复来解释什么是I/O多路复用。假设你是一个老师,让30个学生解答一道标题,然后检讨学生做的是否准确,你有下面几个选择:

  • 第一种选择:按次序逐个检讨,先检讨A,然后是B,之后是C、D。。。这中间如果有一个学生卡住,全班都会被延误。这种模式就好比,你用循环挨个处置socket,基本不具有并发才能。

  • 第二种选择:你创立30个分身,每个分身检讨一个学生的答案是否准确。这种相似于为每一个用户创立一个进程或者- 线程处置衔接。

  • 第三种选择,你站在讲台上等,谁解答完谁举手。这时C、D举手,表现他们解答问题完毕,你下去依次检讨C、D的答案,然后持续回到讲台上等。此时E、A又举手,然后去处置E和A。

第一种就是阻塞IO模型,第三种就是I/O复用模型。

多路复用模型

Linux体系有三种方法实现IO多路复用:select、poll和epoll。

例如epoll方法是将用户socket对应的fd注册进epoll,然后epoll帮你监听哪些socket上有资讯到达,这样就避免了大批的无用操作。此时的socket应当采取非阻塞模式。

这样,全部进程只在进行select、poll、epoll这些调用的时候才会阻塞,收发客户资讯是不会阻塞的,全部进程或者线程就被充足应用起来,这就是事件驱动,所谓的reactor模式。

6. Redis为什么早期选择单线程?

官方解释:https://redis.io/topics/faq

官方FAQ表现,因为Redis是基于内存的操作,CPU成为Redis的瓶颈的情形很少见,Redis的瓶颈最有可能是内存的大小或者网络限制。

如果想要最大水平应用CPU,可以在一台机器上启动多个Redis实例。

PS:网上有这样的答复,吐槽官方的解释有些搪塞,其实就是历史原因,开发者嫌多线程麻烦,后来这个CPU的应用问题就被抛给了应用者。

同时FAQ里还提到了, Redis 4.0 之后开端变成多线程,除了主线程外,它也有后台线程在处置一些较为迟缓的操作,例如清算脏数据、无用衔接的释放、大 Key 的删除等等。

7.Redis6.0应用多线程是怎么回事?

Redis不是说用单线程的吗?怎么6.0成了多线程的?

Redis6.0的多线程是用多线程来处置数据的读写和协定解析,但是Redis履行命令还是单线程的。

这样做的⽬的是因为Redis的性能瓶颈在于⽹络IO⽽⾮CPU,使⽤多线程能晋升IO读写的效力,从⽽整体提⾼Redis的性能。

持久化

8.Redis持久化⽅式有哪些?有什么差别?

Redis持久化⽅案分为RDB和AOF两种。

RDB

RDB持久化是把当前进程数据生成快照保留到硬盘的进程,触发RDB持久化进程分为手动触发和自动触发。

RDB⽂件是⼀个紧缩的⼆进制⽂件,通过它可以还原某个时刻数据库的状况。由于RDB⽂件是保留在硬盘上的,所以即使Redis瓦解或者退出,只要RDB⽂件存在,就可以⽤它来恢复还原数据库的状况。

手动触发分离对应save和bgsave命令:

  • save命令:阻塞当前Redis服务器,直到RDB进程完成为止,对于内存比较大的实例会造成长时光阻塞,线上环境不建议应用。

  • bgsave命令:Redis进程履行fork操作创立子进程,RDB持久化进程由子进程负责,完成后自动停止。阻塞只发生在fork阶段,一般时光很短。

以下场景会自动触发RDB持久化:

  • 应用save相干配置,如“save m n”。表现m秒内数据集存在n次修正时,自动触发bgsave。
  • 如果从节点履行全量复制操作,主节点自动履行bgsave生成RDB文件并发送给从节点
  • 履行debug reload命令重新加载Redis时,也会自动触发save操作
  • 默认情形下履行shutdown命令时,如果没有开启AOF持久化功效则自动履行bgsave。

AOF

AOF(append only file)持久化:以独立日志的方法记载每次写命令, 重启时再重新履行AOF文件中的命令到达恢复数据的目的。AOF的重要作用是解决了数据持久化的实时性,目前已经是Redis持久化的主流方法。

AOF的工作流程操作:命令写入 (append)、文件同步(sync)、文件重写(rewrite)、重启加载 (load)流程如下:

1)所有的写入命令会追加到aof_buf(缓冲区)中。

2)AOF缓冲区依据对应的策略向硬盘做同步操作。

3)随着AOF文件越来越大,须要定期对AOF文件进行重写,到达紧缩 的目的。

4)当Redis服务重视启时,可以加载AOF文件进行数据恢复。

9.RDB 和 AOF 各自有什么优缺陷?

RDB | 长处

  1. 只有一个紧凑的二进制文件 dump.rdb,非常适合备份、全量复制的场景。
  2. 容灾性好,可以把RDB文件拷贝道远程机器或者文件体系张,用于容灾恢复。
  3. 恢复速度快,RDB恢复数据的速度远远快于AOF的方法

RDB | 缺陷

  1. 实时性低,RDB 是间隔一段时光进行持久化,没法做到实时持久化/秒级持久化。如果在这一间隔事件发生故障,数据会丧失。
  2. 存在兼容问题,Redis演进进程存在多个格局的RDB版本,存在老版本Redis无法兼容新版本RDB的问题。

AOF | 长处

  1. 实时性好,aof 持久化可以配置 appendfsync 属性,有 always,每进行一次命令操作就记载到 aof 文件中一次。
  2. 通过 append 模式写文件,即使中途服务器宕机,可以通过 redis-check-aof 工具解决数据一致性问题。

AOF | 缺陷

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

10.RDB和AOF如何选择?

  • 一般来说, 如果想到达足以媲美数据库的 数据安全性,应当 同时应用两种持久化功效。在这种情形下,当 Redis 重启的时候会优先载入 AOF 文件来恢复原始的数据,因为在通常情形下 AOF 文件保留的数据集要比 RDB 文件保留的数据集要完全。
  • 如果 可以接收数分钟以内的数据丧失,那么可以 只应用 RDB 持久化
  • 有很多用户都只应用 AOF 持久化,但并不推举这种方法,因为定时生成 RDB 快照(snapshot)非常便于进行数据备份, 并且 RDB 恢复数据集的速度也要比 AOF 恢复的速度要快,除此之外,应用 RDB 还可以避免 AOF 程序的 bug。
  • 如果只须要数据在服务器运行的时候存在,也可以不应用任何持久化方法。

11.Redis的数据恢复?

当Redis发生了故障,可以从RDB或者AOF中恢复数据。

恢复的进程也很简略,把RDB或者AOF文件拷贝到Redis的数据目录下,如果应用AOF恢复,配置文件开启AOF,然后启动redis-server即可。

Redis 启动时加载数据的流程:

  1. AOF持久化开启且存在AOF文件时,优先加载AOF文件。
  2. AOF关闭或者AOF文件不存在时,加载RDB文件。
  3. 加载AOF/RDB文件胜利后,Redis启动胜利。
  4. AOF/RDB文件存在毛病时,Redis启动失败并打印毛病信息。

12.Redis 4.0 的混杂持久化懂得吗?

重启 Redis 时,我们很少应用 RDB 来恢复内存状况,因为会丧失大批数据。我们通常应用 AOF 日志重放,但是重放 AOF 日志性能相对 RDB 来说要慢很多,这样在 Redis 实例很大的情形下,启动须要消费很长的时光。

Redis 4.0 为懂得决这个问题,带来了一个新的持久化选项——混杂持久化。将 rdb 文件的内容和增量的 AOF 日志文件存在一起。这里的 AOF 日志不再是全量的日志,而是 自持久化开端到持久化停止 的这段时光发生的增量 AOF 日志,通常这部分 AOF 日志很小:

于是在 Redis 重启的时候,可以先加载 rdb 的内容,然后再重放增量 AOF 日志就可以完全替代之前的 AOF 全量文件重放,重启效力因此大幅得到晋升。

高可用

Redis保证高可用重要有三种方法:主从、哨兵、集群。

13.主从复制懂得吗?

Redis主从复制简图

主从复制,是指将一台 Redis 服务器的数据,复制到其他的 Redis 服务器。前者称为 **主节点(master)**,后者称为 **从节点(slave)**。且数据的复制是 单向 的,只能由主节点到从节点。Redis 主从复制支撑 主从同步从从同步 两种,后者是 Redis 后续版本新增的功效,以减轻主节点的同步累赘。

主从复制重要的作用?

  • 数据冗余: 主从复制实现了数据的热备份,是持久化之外的一种数据冗余方法。
  • 故障恢复: 当主节点涌现问题时,可以由从节点供给服务,实现迅速的故障恢复 *(实际上是一种服务的冗余)*。
  • 负载均衡: 在主从复制的基本上,配合读写分离,可以由主节点供给写服务,由从节点供给读服务 (即写 Redis 数据时应用衔接主节点,读 Redis 数据时应用衔接从节点),分担服务器负载。尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大进步 Redis 服务器的并发量。
  • 高可用基石: 除了上述作用以外,主从复制还是哨兵和集群能够实行的 基本,因此说主从复制是 Redis 高可用的基本。

14.Redis主从有几种常见的拓扑构造?

Redis的复制拓扑构造可以支撑单层或多层复制关系,依据拓扑庞杂性可以分为以下三种:一主一从、一主多从、树状主从构造。

1.一主一从构造

一主一从构造是最简略的复制拓扑构造,用于主节点涌现宕机时从节点供给故障转移支撑。2.一主多从构造

一主多从构造(又称为星形拓扑构造)使得应用端可以应用多个从节点实现读写分离(见图6-5)。对于读占比较大的场景,可以把读命令发送到从节点来分担主节点压力。3.树状主从构造

树状主从构造(又称为树状拓扑构造)使得从节点不但可以复制主节点数据,同时可以作为其他从节点的主节点持续向下层复制。通过引入复制中间层,可以有效降低主节点负载和须要传送给从节点的数据量。

15.Redis的主从复制原理懂得吗?

Redis主从复制的工作流程大概可以分为如下几步:

  1. 保留主节点(master)信息 这一步只是保留主节点信息,保留主节点的ip和port。
  2. 主从树立衔接 从节点(slave)发现新的主节点后,会尝试和主节点树立网络衔接。
  3. 发送ping命令 衔接树立胜利后从节点发送ping要求进行首次通讯,重要是检测主从之间网络套接字是否可用、主节点当前是否可接收处置命令。
  4. 权限验证 如果主节点要求密码验证,从节点必需准确的密码才能通过验证。
  5. 同步数据集 主从复制衔接正常通讯后,主节点会操纵有的数据全体发送给从节点。
  6. 命令连续复制 接下来主节点会连续地把写命令发送给从节点,保证主从数据一致性。

16.说说主从数据同步的方法?

Redis在2.8及以上版本应用psync命令完成主从数据同步,同步进程分为:全量复制和部分复制。

主从数据同步方法

全量复制一般用于初次复制场景,Redis早期支撑的复制功效只有全量复制,它会把主节点全体数据一次性发送给从节点,当数据量较大时,会对主从节点和网络造成很大的开销。

全量复制的完全运行流程如下:

  1. 发送psync命令进行数据同步,由于是第一次进行复制,从节点没有复制偏移量和主节点的运行ID,所以发送psync-1。
  2. 主节点依据psync-1解析出当前为全量复制,回复+FULLRESYNC响应。
  3. 从节点吸收主节点的响应数据保留运行ID和偏移量offset
  4. 主节点履行bgsave保留RDB文件到本地
  5. 主节点发送RDB文件给从节点,从节点把吸收的RDB文件保留在本地并直接作为从节点的数据文件
  6. 对于从节点开端吸收RDB快照到吸收完成期间,主节点仍然响应读写命令,因此主节点会把这期间写命令数据保留在复制客户端缓冲区内,当从节点加载完RDB文件后,主节点再把缓冲区内的数据发送给从节点,保证主从之间数据一致性。
  7. 从节点吸收完主节点传送来的全体数据后会清空自身旧数据
  8. 从节点清空数据后开端加载RDB文件
  9. 从节点胜利加载完RDB后,如果当前节点开启了AOF持久化功效, 它会立刻做bgrewriteaof操作,为了保证全量复制后AOF持久化文件立刻可用。

部分复制部分复制重要是Redis针对全量复制的过高开销做出的一种优化办法, 应用psync{runId}{offset}命令实现。当从节点(slave)正在复制主节点 (master)时,如果涌现网络闪断或者命令丧失等异常情形时,从节点会向 主节点要求补发丧失的命令数据,如果主节点的复制积存缓冲区内存在这部分数据则直接发送给从节点,这样就可以坚持主从节点复制的一致性。

  1. 当主从节点之间网络涌现中止时,如果超过repl-timeout时光,主节点会以为从节点故障并中止复制衔接
  2. 主从衔接中止期间主节点依然响应命令,但因复制衔接中止命令无法发送给从节点,不过主节点内部存在的复制积存缓冲区,依然可以保留最近一段时光的写命令数据,默认最大缓存1MB。
  3. 当主从节点网络恢复后,从节点会再次连上主节点
  4. 当主从衔接恢复后,由于从节点之前保留了自身已复制的偏移量和主节点的运行ID。因此会把它们当作psync参数发送给主节点,要求进行部分复制操作。
  5. 主节点接到psync命令后首先核对参数runId是否与自身一致,如果一 致,解释之前复制的是当前主节点;之后依据参数offset在自身复制积存缓冲区查找,如果偏移量之后的数据存在缓冲区中,则对从节点发送+CONTINUE响应,表现可以进行部分复制。
  6. 主节点依据偏移量把复制积存缓冲区里的数据发送给从节点,保证主从复制进入正常状况。

17.主从复制存在哪些问题呢?

主从复制虽好,但也存在一些问题:

  • 一旦主节点涌现故障,须要手动将一个从节点晋升为主节点,同时须要修正应用方的主节点地址,还须要命令其他从节点去复制新的主节点,全部进程都须要人工干涉。
  • 主节点的写才能受到单机的限制。
  • 主节点的存储才能受到单机的限制。

第一个问题是Redis的高可用问题,第二、三个问题属于Redis的散布式问题。

18.Redis Sentinel(哨兵)懂得吗?

主从复制存在一个问题,没法完成自动故障转移。所以我们须要一个计划来完成自动故障转移,它就是Redis Sentinel(哨兵)。

Redis Sentinel

Redis Sentinel ,它由两部分组成,哨兵节点和数据节点:

  • 哨兵节点: 哨兵体系由一个或多个哨兵节点组成,哨兵节点是特殊的 Redis 节点,不存储数据,对数据节点进行监控。
  • 数据节点: 主节点和从节点都是数据节点;

在复制的基本上,哨兵实现了 自动化的故障恢复 功效,下面是官方对于哨兵功效的描写:

  • 监控(Monitoring): 哨兵会不断地检讨主节点和从节点是否运作正常。
  • 自动故障转移(Automatic failover):主节点 不能正常工作时,哨兵会开端 自动故障转移操作,它会将失效主节点的其中一个 从节点升级为新的主节点,并让其他从节点改为复制新的主节点。
  • 配置供给者(Configuration provider): 客户端在初始化时,通过衔接哨兵来获得当前 Redis 服务的主节点地址。
  • 通知(Notification): 哨兵可以将故障转移的成果发送给客户端。

其中,监控和自动故障转移功效,使得哨兵可以及时发现主节点故障并完成转移。而配置供给者和通知功效,则须要在与客户端的交互中才能体现。

19.Redis Sentinel(哨兵)实现原理知道吗?

哨兵模式是通过哨兵节点完成对数据节点的监控、下线、故障转移。

  • 定时监控Redis Sentinel通过三个定时监控义务完成对各个节点发现和监控:
  1. 每隔10秒,每个Sentinel节点会向主节点和从节点发送info命令获取最新的拓扑构造
  2. 每隔2秒,每个Sentinel节点会向Redis数据节点的__sentinel__:hello 频道上发送该Sentinel节点对于主节点的断定以及当前Sentinel节点的信息
  3. 每隔1秒,每个Sentinel节点会向主节点、从节点、其余Sentinel节点发送一条ping命令做一次心跳检测,来确认这些节点当前是否可达
  • 主观下线和客观下线主观下线就是哨兵节点以为某个节点有问题,客观下线就是超过必定数目的哨兵节点以为主节点有问题。
    1. 主观下线 每个Sentinel节点会每隔1秒对主节点、从节点、其他Sentinel节点发送ping命令做心跳检测,当这些节点超过 down-after-milliseconds没有进行有效回复,Sentinel节点就会对该节点做失败判定,这个行动叫做主观下线。

    2. 客观下线 当Sentinel主观下线的节点是主节点时,该Sentinel节点会通过sentinel is- master-down-by-addr命令向其他Sentinel节点讯问对主节点的断定,当超过 <quorum>个数,Sentinel节点以为主节点确切有问题,这时该Sentinel节点会做出客观下线的决议

    • 引导者Sentinel节点选举Sentinel节点之间会做一个引导者选举的工作,选出一个Sentinel节点作为引导者进行故障转移的工作。Redis应用了Raft算法实现引导者选举。

    • 故障转移

      引导者选举出的Sentinel节点负责故障转移,进程如下:

    1. 在从节点列表中选出一个节点作为新的主节点,这一步是相对庞杂一些的一步
    2. Sentinel引导者节点会对第一步选出来的从节点履行slaveof no one命令让其成为主节点
    3. Sentinel引导者节点会向剩余的从节点发送命令,让它们成为新主节点的从节点
    4. Sentinel节点聚集会将原来的主节点更新为从节点,并坚持着对其关注,当其恢复后命令它去复制新的主节点

    20.引导者Sentinel节点选举懂得吗?

    Redis应用了Raft算法实 现引导者选举,大致流程如下:

    1. 每个在线的Sentinel节点都有资历成为引导者,当它确认主节点主观 下线时候,会向其他Sentinel节点发送sentinel is-master-down-by-addr命令, 要求将自己设置为引导者。
    2. 收到命令的Sentinel节点,如果没有赞成过其他Sentinel节点的sentinel is-master-down-by-addr命令,将赞成该要求,否则谢绝。
    3. 如果该Sentinel节点发现自己的票数已经大于等于max(quorum, num(sentinels)/2+1),那么它将成为引导者。
    4. 如果此进程没有选举出引导者,将进入下一次选举。

    21.新的主节点是怎样被挑选出来的?

    选出新的主节点,大概分为这么几步:

    1. 过滤:“不健康”(主观下线、断线)、5秒内没有回复过Sentinel节 点ping响应、与主节点失联超过down-after-milliseconds*10秒。
    2. 选择slave-priority(从节点优先级)最高的从节点列表,如果存在则返回,不存在则持续。
    3. 选择复制偏移量最大的从节点(复制的最完全),如果存在则返 回,不存在则持续。
    4. 选择runid最小的从节点。

    22.Redis 集群懂得吗?

    前面说到了主从存在高可用和散布式的问题,哨兵解决了高可用的问题,而集群就是终极计划,一举解决高可用和散布式问题。

    1. 数据分区: 数据分区 (或称数据分片) 是集群最核心的功效。集群将数据疏散到多个节点,一方面 突破了 Redis 单机内存大小的限制,存储容量大大增长另一方面 每个主节点都可以对外供给读服务和写服务,大大进步了集群的响应才能

    2. 高可用: 集群支撑主从复制和主节点的 自动故障转移 (与哨兵相似),当任一节点发生故障时,集群仍然可以对外供给服务。

    23.集群中数据如何分区?

    散布式的存储中,要把数据集依照分区规矩映射到多个节点,常见的数据分区规矩三种:

    计划一:节点取余分区

    节点取余分区,非常好懂得,应用特定的数据,比如Redis的键,或者用户ID之类,对响应的hash值取余:hash(key)%N,来肯定数据映射到哪一个节点上。

    不过该计划最大的问题是,当节点数目变更时,如扩容或压缩节点,数据节点映射关 系须要重新盘算,会导致数据的重新迁移。

    节点取余分区

    计划二:一致性哈希分区

    将全部 Hash 值空间组织成一个虚拟的圆环,然后将缓存节点的 IP 地址或者主机名做 Hash 取值后,放置在这个圆环上。当我们须要肯定某一个 Key 需 要存取到哪个节点上的时候,先对这个 Key 做同样的 Hash 取值,肯定在环上的地位,然后依照顺时针方向在环上“行走”,遇到的第一个缓存节点就是要拜访的节点。

    比如说下面 这张图里面,Key 1 和 Key 2 会落入到 Node 1 中,Key 3、Key 4 会落入到 Node 2 中,Key 5 落入到 Node 3 中,Key 6 落入到 Node 4 中。

    这种方法相比节点取余最大的利益在于参加和删除节点只影响哈希环中 相邻的节点,对其他节点无影响。

    但它还是存在问题:

    • 缓存节点在圆环上散布不平均,会造成部分缓存节点的压力较大
    • 当某个节点故障时,这个节点所要承担的所有拜访都会被顺移到另一个节点上,会对后面这个节点造成力。

    计划三:虚拟槽分区

    这个计划 一致性哈希分区的基本上,引入了 虚拟节点 的概念。Redis 集群应用的便是该计划,其中的虚拟节点称为 槽(slot)。槽是介于数据和实际节点之间的虚拟概念,每个实际节点包括必定数目的槽,每个槽包括哈希值在必定规模内的数据。

    在应用了槽的一致性哈希分区中,槽是数据管理和迁移的基本单位。槽解耦了数据和实际节点 之间的关系,增长或删除节点对体系的影响很小。仍以上图为例,体系中有 4 个实际节点,假设为其分配 16 个槽(0-15);

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

    如果此时删除 node2,只须要将槽 4-7 重新分配即可,例如槽 4-5 分配给 node1,槽 6 分配给 node3,槽 7 分配给 node4,数据在其他节点的散布仍然较为均衡。

    24.能说说Redis集群的原理吗?

    Redis集群通过数据分区来实现数据的散布式存储,通过自动故障转移实现高可用。

    集群创立

    数据分区是在集群创立的时候完成的。

    设置节点Redis集群一般由多个节点组成,节点数目至少为6个才能保证组成完全高可用的集群。每个节点须要开启配置cluster-enabled yes,让Redis运行在集群模式下。节点握手节点握手是指一批运行在集群模式下的节点通过Gossip协定彼此通讯, 到达感知对方的进程。节点握手是集群彼此通讯的第一步,由客户端发起命 令:cluster meet{ip}{port}。完成节点握手之后,一个个的Redis节点就组成了一个多节点的集群。

    分配槽(slot)Redis集群把所有的数据映射到16384个槽中。每个节点对应若干个槽,只有当节点分配了槽,才能响应和这些槽关联的键命令。通过 cluster addslots命令为节点分配槽。

    分配槽

    故障转移

    Redis集群的故障转移和哨兵的故障转移相似,但是Redis集群中所有的节点都要承担状况保护的义务。

    故障发现Redis集群内节点通过ping/pong资讯实现节点通讯,集群中每个节点都会定期向其他节点发送ping资讯,吸收节点回复pong 资讯作为响应。如果在cluster-node-timeout时光内通讯一直失败,则发送节 点会以为吸收节点存在故障,把吸收节点标志为主观下线(pfail)状况。当某个节点断定另一个节点主观下线后,相应的节点状况会追随资讯在集群内流传。通过Gossip资讯流传,集群内节点不断收集到故障节点的下线报告。当 半数以上持有槽的主节点都标志某个节点是主观下线时。触发客观下线流程。

    故障恢复

    故障节点变为客观下线后,如果下线节点是持有槽的主节点则须要在它 的从节点中选出一个调换它,从而保证集群的高可用。

    故障恢复流程
    1. 资历检讨 每个从节点都要检讨最后与主节点断线时光,断定是否有资历调换故障 的主节点。
    2. 预备选举时光 当从节点符合故障转移资历后,更新触发故障选举的时光,只有到达该 时光后才能履行后续流程。
    3. 发起选举 当从节点定时义务检测到达故障选举时光(failover_auth_time)到达后,发起选举流程。
    4. 选举投票 持有槽的主节点处置故障选举资讯。投票进程其实是一个引导者选举的进程,如集群内有N个持有槽的主节 点代表有N张选票。由于在每个配置纪元内持有槽的主节点只能投票给一个 从节点,因此只能有一个从节点获得N/2+1的选票,保证能够找出唯一的从节点。
    5. 调换主节点 当从节点收集到足够的选票之后,触发调换主节点操作。

    安排Redis集群至少须要几个物理节点?

    在投票选举的环节,故障主节点也算在投票数内,假设集群内节点规模是3主3从,其中有2 个主节点安排在一台机器上,当这台机器宕机时,由于从节点无法收集到 3/2+1个主节点选票将导致故障转移失败。这个问题也实用于故障发现环节。因此安排集群时所有主节点最少须要安排在3台物理机上才能避免单点问题。

    25.说说集群的伸缩?

    Redis集群供给了灵巧的节点扩容和压缩计划,可以在不影响集群对外服务的情形下,为集群添加节点进行扩容也可以下线部分节点进行缩容。其实,集群扩容和缩容的症结点,就在于槽和节点的对应关系,扩容和缩容就是将一部分数据迁移给新节点。

    例如下面一个集群,每个节点对应若干个槽,每个槽对应必定的数据,如果愿望参加1个节点愿望实现集群扩容时,须要通过相干命令把一部分槽和内容迁移给新节点。缩容也是相似,先把槽和数据迁移到其它节点,再把对应的节点下线。

    缓存设计

    26.什么是缓存击穿、缓存穿透、缓存雪崩?

    PS:这是多年黄历的老八股了,必定要懂得清晰。

    缓存击穿

    一个并发拜访量比较大的key在某个时光过期,导致所有的要求直接打在DB上。

    解决⽅案:

    1. 加锁更新,⽐如要求查询A,发现缓存中没有,对A这个key加锁,同时去数据库查询数据,写⼊缓存,再返回给⽤户,这样后⾯的要求就可以从缓存中拿到数据了。

    2. 将过期时光组合写在value中,通过异步的⽅式不断的刷新过期时光,防⽌此类现象。

    缓存穿透

    缓存穿透指的查询缓存和数据库中都不存在的数据,这样每次要求直接打到数据库,就好像缓存不存在一样。

    缓存穿透将导致不存在的数据每次要求都要到存储层去查询,失去了缓存掩护后端存储的意义。

    缓存穿透可能会使后端存储负载加大,如果发现大批存储层空命中,可能就是涌现了缓存穿透问题。

    缓存穿透可能有两种原因:

    1. 自身业务代码问题
    2. 恶意攻击,爬虫造成空命中

    它重要有两种解决办法:

    • 缓存空值/默认值

    一种方法是在数据库不命中之后,把一个空对象或者默认值保留到缓存,之后再拜访这个数据,就会从缓存中获取,这样就掩护了数据库。

    缓存空值/默认值

    缓存空值有两大问题:

    1. 空值做了缓存,意味着缓存层中存了更多的键,须要更多的内存空间(如果是攻击,问题更严重),比较有效的办法是针对这类数据设置一个较短的过期时光,让其自动剔除。

    2. 缓存层和存储层的数据会有一段时光窗口的不一致,可能会对业务有必定影响。例如过期时光设置为5分钟,如果此时存储层添加了这个数据,那此段时光就会涌现缓存层和存储层数据的不一致。这时候可以应用资讯队列或者其它异步方法清算缓存中的空对象。

    • 布隆过滤器除了缓存空对象,我们还可以在存储和缓存之前,加一个布隆过滤器,做一层过滤。

    布隆过滤器里会保留数据是否存在,如武断定数据不不能再,就不会拜访存储。两种解决计划的比较:

    缓存雪崩

    某⼀时刻发⽣⼤规模的缓存失效的情形,例如缓存服务宕机、大批key在同一时光过期,这样的效果就是⼤量的要求进来直接打到DB上,可能导致全部体系的瓦解,称为雪崩。

    缓存雪崩是三大缓存问题里最严重的一种,我们来看看怎么预防和处置。

    • 进步缓存可用性
    1. 集群安排:通过集群来晋升缓存的可用性,可以应用Redis本身的Redis Cluster或者第三方集群计划如Codis等。
    2. 多级缓存:设置多级缓存,第一级缓存失效的基本上,拜访二级缓存,每一级缓存的失效时光都不同。
    • 过期时光
    1. 均匀过期:为了避免大批的缓存在同一时光过期,可以把不同的 key 过期时光随机生成,避免过期时光太过集中。
    2. 热点数据永不过期。
    • 熔断降级
    1. 服务熔断:当缓存服务器宕机或超时响应时,为了防止全部体系涌现雪崩,暂时停止业务服务拜访缓存体系。
    2. 服务降级:当涌现大批缓存失效,而且处在高并发高负荷的情形下,在业务体系内部暂时舍弃对一些非核心的接口和数据的要求,而直接返回一个提前预备好的 fallback(退路)毛病处置信息。

    27.能说说布隆过滤器吗?

    布隆过滤器,它是一个连续的数据构造,每个存储位存储都是一个bit,即0或者1, 来标识数据是否存在。

    存储数据的时时候,应用K个不同的哈希函数将这个变量映射为bit列表的的K个点,把它们置为1。

    我们断定缓存key是否存在,同样,K个哈希函数,映射到bit列表上的K个点,断定是不是1:

    • 如果全不是1,那么key不存在;
    • 如果都是1,也只是表现key可能存在。

    布隆过滤器也有一些缺陷:

    1. 它在断定元素是否在聚集中时是有必定毛病几率,因为哈希算法有必定的碰撞的概率。
    2. 不支撑删除元素。

    28.如何保证缓存和数据库数据的⼀致性?

    依据CAP理论,在保证可用性和分区容错性的前提下,无法保证一致性,所以缓存和数据库的绝对一致是不可能实现的,只能尽可能保留缓存和数据库的最终一致性。

    选择适合的缓存更新策略

    1. 删除缓存而不是更新缓存

    当一个线程对缓存的key进行写操作的时候,如果其它线程进来读数据库的时候,读到的就是脏数据,发生了数据不一致问题。

    相比较而言,删除缓存的速度比更新缓存的速度快很多,所用时光相对也少很多,读脏数据的概率也小很多。

    1. 先更数据,后删缓存先更数据库还是先删缓存?这是一个问题。

    更新数据,耗时可能在删除缓存的百倍以上。在缓存中不存在对应的key,数据库又没有完成更新的时候,如果有线程进来读取数据,并写入到缓存,那么在更新胜利之后,这个key就是一个脏数据。

    毫无疑问,先删缓存,再更数据库,缓存中key不存在的时光的时光更长,有更大的概率会发生脏数据。

    目前最风行的缓存读写策略cache-aside-pattern就是采取先更数据库,再删缓存的方法。

    缓存不一致处置

    如果不是并发特殊高,对缓存依附性很强,其实必定程序的不一致是可以接收的。

    但是如果对一致性要求比较高,那就得想办法保证缓存和数据库中数据一致。

    缓存和数据库数据不一致常见的两种原因:

    • 缓存key删除失败
    • 并发导致写入了脏数据
    缓存一致性

    资讯队列保证key被删除可以引入资讯队列,把要删除的key或者删除失败的key丢尽资讯队列,应用资讯队列的重试机制,重试删除对应的key。

    这种计划看起来不错,缺陷是对业务代码有必定的侵入性。

    数据库订阅+资讯队列保证key被删除可以用一个服务(比如阿里的 canal)去监听数据库的binlog,获取须要操作的数据。

    然后用一个公共的服务获取订阅程序传来的信息,进行缓存删除操作。这种方法降低了对业务的侵入,但其实全部体系的庞杂度是晋升的,适合基建完美的大厂。

    延时双删防止脏数据还有一种情形,是在缓存不存在的时候,写入了脏数据,这种情形在先删缓存,再更数据库的缓存更新策略下发生的比较多,解决计划是延时双删。

    简略说,就是在第一次删除缓存之后,过了一段时光之后,再次删除缓存。

    延时双删

    这种方法的延时时光设置须要细心考量和测试。

    设置缓存过期时光兜底

    这是一个朴实但是有用的办法,给缓存设置一个合理的过期时光,即使发生了缓存数据不一致的问题,它也不会永远不一致下去,缓存过期的时候,自然又会恢复一致。

    29.如何保证本地缓存和散布式缓存的一致?

    PS:这道题面试很少问,但实际工作中很常见。

    在日常的开发中,我们常常采取两级缓存:本地缓存+散布式缓存。

    所谓本地缓存,就是对应服务器的内存缓存,比如Caffeine,散布式缓存基本就是采取Redis。

    那么问题来了,本地缓存和散布式缓存怎么坚持数据一致?Redis缓存,数据库发生更新,直接删除缓存的key即可,因为对于应用体系而言,它是一种中心化的缓存。

    但是本地缓存,它是非中心化的,散落在散布式服务的各个节点上,没法通过客户端的要求删除本地缓存的key,所以得想办法通知集群所有节点,删除对应的本地缓存key。

    可以采撤资讯队列的方法:

    1. 采取Redis本身的Pub/Sub机制,散布式集群的所有节点订阅删除本地缓存频道,删除Redis缓存的节点,同事宣布删除本地缓存资讯,订阅者们订阅到资讯后,删除对应的本地key。但是Redis的宣布订阅不是可靠的,不能保证必定删除胜利。
    2. 引入专业的资讯队列,比如RocketMQ,保证资讯的可靠性,但是增长了体系的庞杂度。
    3. 设置恰当的过期时光兜底,本地缓存可以设置相对短一些的过期时光。

    30.怎么处置热key?

    什么是热Key?所谓的热key,就是拜访频率比较的key。

    比如,热点新闻事件或商品,这类key通常有大流量的拜访,对存储这类信息的 Redis来说,是不小的压力。

    假如Redis集群安排,热key可能会造成整体流量的不均衡,个别节点涌现OPS过大的情形,极端情形下热点key甚至会超过 Redis本身能够蒙受的OPS。

    怎么处置热key?

    对热key的处置,最症结的是对热点key的监控,可以从这些端来监控热点key:

    1. 客户端 客户端其实是距离key“最近”的处所,因为Redis命令就是从客户端发出的,例如在客户端设置全局字典(key和调用次数),每次调用Redis命令时,应用这个字典进行记载。

    2. 代理端 像Twemproxy、Codis这些基于代理的Redis散布式架构,所有客户端的要求都是通过代理端完成的,可以在代理端进行收集统计。

    3. Redis服务端 应用monitor命令统计热点key是很多开发和运维人员首先想到,monitor命令可以监控到Redis履行的所有命令。

    只要监控到了热key,对热key的处置就简略了:

    1. 把热key打散到不同的服务器,降低压⼒

    2. 加⼊⼆级缓存,提前加载热key数据到内存中,如果redis宕机,⾛内存查询

    31.缓存预热怎么做呢?

    所谓缓存预热,就是提前把数据库里的数据刷到缓存里,通常有这些办法:

    1、直接写个缓存刷新页面或者接口,上线时手动操作

    2、数据量不大,可以在项目启动的时候自动进行加载

    3、定时义务刷新缓存.

    32.热点key重建?问题?解决?

    开发的时候一般应用“缓存+过期时光”的策略,既可以加速数据读写,又保证数据的定期更新,这种模式基本能够满足绝大部分需求。

    但是有两个问题如果同时涌现,可能就会涌现比较大的问题:

    • 当前key是一个热点key(例如一个热点的娱乐新闻),并发量非常大。

    • 重建缓存不能在短时光完成,可能是一个庞杂盘算,例如庞杂的 SQL、多次IO、多个依附等。在缓存失效的瞬间,有大批线程来重建缓存,造成后端负载加大,甚至可能会让应用瓦解。

    怎么处置呢?

    要解决这个问题也不是很庞杂,解决问题的要点在于:

    • 减少重建缓存的次数。
    • 数据尽可能一致。
    • 较少的潜在危险。

    所以一般采取如下方法:

    1. 互斥锁(mutex key) 这种办法只许可一个线程重建缓存,其他线程期待重建缓存的线程履行完,重新从缓存获取数据即可。
    2. 永远不过期 “永远不过期”包括两层意思:
    • 从缓存层面来看,确切没有设置过期时光,所以不会涌现热点key过期后发生的问题,也就是“物理”不过期。
    • 从功效层面来看,为每个value设置一个逻辑过期时光,当发现超过逻辑过期时光后,会应用单独的线程去构建缓存。

    33.无底洞问题吗?如何解决?

    什么是无底洞问题?

    2010年,Facebook的Memcache节点已经到达了3000个,承载着TB级别的缓存数据。但开发和运维人员发现了一个问题,为了满足业务要求添加了大批新Memcache节点,但是发现性能不但没有好转反而降低了,当时将这 种现象称为缓存的“无底洞”现象。

    那么为什么会发生这种现象呢?

    通常来说添加节点使得Memcache集群 性能应当更强了,但事实并非如此。键值数据库由于通常采取哈希函数将 key映射到各个节点上,造成key的散布与业务无关,但是由于数据量和拜访量的连续增长,造成须要添加大批节点做水平扩容,导致键值散布到更多的 节点上,所以无论是Memcache还是Redis的散布式,批量操作通常须要从不同节点上获取,相比于单机批量操作只涉及一次网络操作,散布式批量操作会涉及多次网络时光。

    无底洞问题如何优化呢?

    先剖析一下无底洞问题:

    • 客户端一次批量操作会涉及多次网络操作,也就意味着批量操作会随着节点的增多,耗时会不断增大。

    • 网络衔接数变多,对节点的性能也有必定影响。

    常见的优化思路如下:

    • 命令本身的优化,例如优化操作语句等。

    • 减少网络通讯次数。

    • 降低接入成本,例如客户端应用长连/衔接池、NIO等。

    Redis运维

    34.Redis报内存不足怎么处置?

    Redis 内存不足有这么几种处置方法:

    • 修正配置文件 redis.conf 的 maxmemory 参数,增长 Redis 可用内存
    • 也可以通过命令set maxmemory动态设置内存上限
    • 修正内存淘汰策略,及时释放内存空间
    • 应用 Redis 集群模式,进行横向扩容。

    35.Redis的过期数据回收策略有哪些?

    Redis重要有2种过期数据回收策略:

    惰性删除

    惰性删除指的是当我们查询key的时候才对key进⾏检测,如果已经到达过期时光,则删除。显然,他有⼀个缺陷就是如果这些过期的key没有被拜访,那么他就⼀直⽆法被删除,⽽且⼀直占⽤内存。

    定期删除

    定期删除指的是Redis每隔⼀段时光对数据库做⼀次检讨,删除⾥⾯的过期key。由于不可能对所有key去做轮询来删除,所以Redis会每次随机取⼀些key去做检讨和删除。

    36.Redis有哪些内存溢出掌握/内存淘汰策略?

    Redis所用内存到达maxmemory上限时会触发相应的溢出掌握策略,Redis支撑六种策略:

    1. noeviction:默认策略,不会删除任何数据,谢绝所有写入操作并返 回客户端毛病信息,此 时Redis只响应读操作。
    2. volatile-lru:依据LRU算法删除设置了超时属性(expire)的键,直 到腾出足够空间为止。如果没有可删除的键对象,回退到noeviction策略。
    3. allkeys-lru:依据LRU算法删除键,不管数据有没有设置超时属性, 直到腾出足够空间为止。
    4. allkeys-random:随机删除所有键,直到腾出足够空间为止。
    5. volatile-random:随机删除过期键,直到腾出足够空间为止。
    6. volatile-ttl:依据键值对象的ttl属性,删除最近将要过期数据。如果 没有,回退到noeviction策略。

    37.Redis阻塞?怎么解决?

    Redis发生阻塞,可以从以下几个方面排查:

    • API或数据构造应用不合理

      通常Redis履行命令速度非常快,但是不合理地应用命令,可能会导致履行速度很慢,导致阻塞,对于高并发的场景,应当尽量避免在大对象上履行算法庞杂 度超过O(n)的命令。

      对慢查询的处置分为两步:

    1. 发现慢查询:slowlog get{n}命令可以获取最近 的n条慢查询命令;
    2. 发现慢查询后,可以从两个方向去优化慢查询:1)修正为低算法庞杂度的命令,如hgetall改为hmget等,禁用keys、sort等命 令 2)调剂大对象:缩减大对象数据或把大对象拆分为多个小对象,防止一次命令操作过多的数据。
  • CPU饱和的问题

    单线程的Redis处置命令时只能应用一个CPU。而CPU饱和是指Redis单核CPU应用率跑到接近100%。

    针对这种情形,处置步骤一般如下:

    1. 断定当前Redis并发量是否已经到达极限,可以应用统计命令redis-cli-h{ip}-p{port}--stat获取当前 Redis应用情形
    2. 如果Redis的要求几万+,那么大概就是Redis的OPS已经到了极限,应当做集群化水品扩大来分摊OPS压力
    3. 如果只有几百几千,那么就得排查命令和内存的应用
  • 持久化相干的阻塞

    对于开启了持久化功效的Redis节点,须要排查是否是持久化导致的阻塞。

    1. fork阻塞 fork操作发生在RDB和AOF重写时,Redis主线程调用fork操作发生共享 内存的子进程,由子进程完成持久化文件重写工作。如果fork操作本身耗时过长,必定会导致主线程的阻塞。
    2. AOF刷盘阻塞 当我们开启AOF持久化功效时,文件刷盘的方法一般采取每秒一次,后台线程每秒对AOF文件做fsync操作。当硬盘压力过大时,fsync操作须要等 待,直到写入完成。如果主线程发现距离上一次的fsync胜利超过2秒,为了 数据安全性它会阻塞直到后台线程履行fsync操作完成。
    3. HugePage写操作阻塞 对于开启Transparent HugePages的 操作体系,每次写命令引起的复制内存页单位由4K变为2MB,放大了512 倍,会拖慢写操作的履行时光,导致大批写操作慢查询。

    38.大key问题懂得吗?

    Redis应用进程中,有时候会涌现大key的情形, 比如:

    • 单个简略的key存储的value很大,size超过10KB
    • hash, set,zset,list 中存储过多的元素(以万为单位)

    大key会造成什么问题呢?

    • 客户端耗时增长,甚至超时
    • 对大key进行IO操作时,会严重占用带宽和CPU
    • 造成Redis集群中数据倾斜
    • 自动删除、被动删等,可能会导致阻塞

    如何找到大key?

    • bigkeys命令:应用bigkeys命令以遍历的方法剖析Redis实例中的所有Key,并返回整体统计信息与每个数据类型中Top1的大Key
    • redis-rdb-tools:redis-rdb-tools是由Python写的用来剖析Redis的rdb快照文件用的工具,它可以把rdb快照文件生成json文件或者生成报表用来剖析Redis的应用详情。

    如何处置大key?

    大key处置
    • 删除大key

      • 当Redis版本大于4.0时,可应用UNLINK命令安全地删除大Key,该命令能够以非阻塞的方法,逐步地清算传入的Key。
      • 当Redis版本小于4.0时,避免应用阻塞式命令KEYS,而是建议通过SCAN命令履行增量迭代扫描key,然后断定进行删除。
    • 紧缩和拆分key

      • 当vaule是string时,比较难拆分,则应用序列化、紧缩算法将key的大小掌握在合理规模内,但是序列化和反序列化都会带来更多时光上的消费。
      • 当value是string,紧缩之后仍然是大key,则须要进行拆分,一个大key分为不同的部分,记载每个部分的key,应用multiget等操作实现事务读取。
      • 当value是list/set等聚集类型时,依据预估的数据规模来进行分片,不同的元素盘算后分到不同的片。

    39.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,其他不变。

    Redis应用

    40.应用Redis 如何实现异步队列?

    我们知道redis支撑很多种构造的数据,那么如何应用redis作为异步队列应用呢?一般有以下几种方法:

    • 应用list作为队列,lpush生产资讯,rpop消费资讯

    这种方法,消费者逝世循环rpop从队列中消费资讯。但是这样,即使队列里没有资讯,也会进行rpop,会导致Redis CPU的消费。可以通过让消费者休眠的方法的方法来处置,但是这样又会又资讯的延迟问题。

    -应用list作为队列,lpush生产资讯,brpop消费资讯

    brpop是rpop的阻塞版本,list为空的时候,它会一直阻塞,直到list中有值或者超时。

    这种方法只能实现一对一的资讯队列。

    • 应用Redis的pub/sub来进行资讯的宣布/订阅

    宣布/订阅模式可以1:N的资讯宣布/订阅。宣布者将资讯宣布到指定的频道频道(channel),订阅相应频道的客户端都能收到资讯。

    但是这种方法不是可靠的,它不保证订阅者必定能收到资讯,也不进行资讯的存储。

    所以,一般的异步队列的实现还是交给专业的资讯队列。

    41.Redis 如何实现延时队列?

    • 应用zset,应用排序实现

    可以应用 zset这个构造,用设置好的时光戳作为score进行排序,应用 zadd score1 value1 ....命令就可以一直往内存中生产资讯。再应用 zrangebysocre 查询符合条件的所有待处置的义务,通过循环履行队列义务即可。

    42.Redis 支撑事务吗?

    Redis供给了简略的事务,但它对事务ACID的支撑并不完备。

    multi命令代表事务开端,exec命令代表事务停止,它们之间的命令是原子次序履行的:

    127.0.0.1:6379> multi 
    OK
    127.0.0.1:6379> sadd user:a:follow user:b 
    QUEUED 
    127.0.0.1:6379> sadd user:b:fans user:a 
    QUEUED
    127.0.0.1:6379> sismember user:a:follow user:b 
    (integer) 0
    127.0.0.1:6379> exec 1) (integer) 1
    2) (integer) 1

    Redis事务的原理,是所有的指令在 exec 之前不履行,而是缓存在 服务器的一个事务队列中,服务器一旦收到 exec 指令,才开履行全部事务队列,履行完毕后一次性返回所有指令的运行成果。

    因为Redis履行命令是单线程的,所以这组命令次序履行,而且不会被其它线程打断。

    Redis事务的注意点有哪些?

    须要注意的点有:

    • Redis 事务是不支撑回滚的,不像 MySQL 的事务一样,要么都履行要么都不履行;

    • Redis 服务端在履行事务的进程中,不会被其他客户端发送来的命令要求打断。直到事务命令全体履行完毕才会履行其他客户端的命令。

    Redis 事务为什么不支撑回滚?

    Redis 的事务不支撑回滚。

    如果履行的命令有语法毛病,Redis 会履行失败,这些问题可以从程序层面捕获并解决。但是如果涌现其他问题,则依然会持续履行余下的命令。

    这样做的原因是因为回滚须要增长很多工作,而不支撑回滚则可以坚持简略、迅速的特征

    43.Redis和Lua脚本的应用懂得吗?

    Redis的事务功效比较简略,平时的开发中,可以应用Lua脚原来加强Redis的命令。

    Lua脚本能给开发人员带来这些利益:

    • Lua脚本在Redis中是原子履行的,履行进程中间不会插入其他命令。
    • Lua脚本可以赞助开发和运维人员发明出自己定制的命令,并可以将这 些命令常驻在Redis内存中,实现复用的效果。
    • Lua脚本可以将多条命令一次性打包,有效地减少网络开销。

    比如这一段很(烂)经(大)典(街)的秒杀体系应用lua扣减Redis库存的脚本:

       -- 库存未预热
       if (redis.call('exists', KEYS[2]) == 1) then
            return -9;
        end;
        -- 秒杀商品库存存在
        if (redis.call('exists', KEYS[1]) == 1) then
            local stock = tonumber(redis.call('get', KEYS[1]));
            local num = tonumber(ARGV[1]);
            -- 剩余库存少于要求数目
            if (stock < num) then
                return -3
            end;
            -- 扣减库存
            if (stock >= num) then
                redis.call('incrby', KEYS[1], 0 - num);
                -- 扣减胜利
                return 1
            end;
            return -2;
        end;
        -- 秒杀商品库存不存在
        return -1;

    44.Redis的管道懂得吗?

    Redis 供给三种将客户端多条命令打包发送给服务端履行的方法:

    Pipelining(管道) 、 Transactions(事务) 和 Lua Scripts(Lua 脚本) 。

    Pipelining(管道)

    Redis 管道是三者之中最简略的,当客户端须要履行多条 redis 命令时,可以通过管道一次性将要履行的多条命令发送给服务端,其作用是为了降低 RTT(Round Trip Time) 对性能的影响,比如我们应用 nc 命令将两条指令发送给 redis 服务端。

    Redis 服务端吸收到管道发送过来的多条命令后,会一直执命令,并将命令的履行成果进行缓存,直到最后一条命令履行完成,再所有命令的履行成果一次性返回给客户端 。

    Pipelining的优势

    在性能方面, Pipelining 有下面两个优势:

    • 节俭了RTT:将多条命令打包一次性发送给服务端,减少了客户端与服务端之间的网络调用次数
    • 减少了高低文切换:当客户端/服务端须要从网络中读写数据时,都会发生一次体系调用,体系调用是非常耗时的操作,其中设计到程序由用户态切换到内核态,再从内核态切换回用户态的进程。当我们履行 10 条 redis 命令的时候,就会发生 10 次用户态到内核态的高低文切换,但如果我们应用 Pipeining 将多条命令打包成一条一次性发送给服务端,就只会发生一次高低文切换。

    45.Redis实现散布式锁懂得吗?

    Redis是散布式锁实质上要实现的目的就是在 Redis 里面占一个“茅坑”,当别的进程也要来占时,发现已经有人蹲在那里了,就只好废弃或者稍后再试。

    • V1:setnx命令

    占坑一般是应用 setnx(set if not exists) 指令,只许可被一个客户端占坑。先来先占, 用完了,再调用 del 指令释放茅坑。

    > setnx lock:fighter true
    OK
    ... do something critical ...
    > del lock:fighter
    (integer) 1

    但是有个问题,如果逻辑履行到中间涌现异常了,可能会导致 del 指令没有被调用,这样就会陷入逝世锁,锁永远得不到释放。

    • V2:锁超时释放

    所以在拿到锁之后,再给锁加上一个过期时光,比如 5s,这样即使中间涌现异常也可以保证 5 秒之后锁会自动释放。

    > setnx lock:fighter true
    OK
    > expire lock:fighter 5
    ... do something critical ...
    > del lock:fighter
    (integer) 1

    但是以上逻辑还有问题。如果在 setnx 和 expire 之间服务器进程突然挂掉了,可能是因为机器掉电或者是被人为杀掉的,就会导致 expire 得不到履行,也会造成逝世锁。

    这种问题的根源就在于 setnx 和 expire 是两条指令而不是原子指令。如果这两条指令可以一起履行就不会涌现问题。

    • V3:set指令

    这个问题在Redis 2.8 版本中得到懂得决,这个版本参加了 set 指令的扩大参数,使得 setnx 和expire 指令可以一起履行。

    set lock:fighter3 true ex 5 nx OK ... do something critical ... > del lock:codehole

    上面这个指令就是 setnx 和 expire 组合在一起的原子指令,这个就算是比较完美的散布式锁了。

    当然实际的开发,没人会去自己写散布式锁的命令,因为有专业的轮子——Redisson

    底层构造

    这一部分就比较深了,如果不是简历上写了精通Redis,应当不会怎么问。

    46.说说Redis底层数据构造?

    Redis有动态字符串(sds)链表(list)字典(ht)跳跃表(skiplist)整数聚集(intset)紧缩列表(ziplist) 等底层数据构造。

    Redis并没有应用这些数据构造来直接实现键值对数据库,而是基于这些数据构造创立了一个对象体系,来表现所有的key-value。

    我们常用的数据类型和编码对应的映射关系:

    简略看一下底层数据构造,如果对数据构造掌握不错的话,懂得这些构造应当不是特殊难:

    1. 字符串:redis没有直接使⽤C语⾔传统的字符串表现,⽽是⾃⼰实现的叫做简略动态字符串SDS的抽象类型。

      C语⾔的字符串不记载⾃身的⻓度信息,⽽SDS则保留了⻓度信息,这样将获取字符串⻓度的时光由O(N)降低到了O(1),同时可以避免缓冲区溢出和减少修正字符串⻓度时所需的内存重分配次数。

    SDS
    1. 链表linkedlist:redis链表是⼀个双向⽆环链表构造,很多宣布订阅、慢查询、监督器功效都是使⽤到了链表来实现,每个链表的节点由⼀个listNode构造来表现,每个节点都有指向前置节点和后置节点的指针,同时表头节点的前置和后置节点都指向NULL。
    链表linkedlist
    1. 字典dict:⽤于保留键值对的抽象数据构造。Redis使⽤hash表作为底层实现,一个哈希表里可以有多个哈希表节点,而每个哈希表节点就保留了字典里中的一个键值对。每个字典带有两个hash表,供平时使⽤和rehash时使⽤,hash表使⽤链地址法来解决键冲突,被分配到同⼀个索引地位的多个键值对会形成⼀个单向链表,在对hash表进⾏扩容或者缩容的时候,为了服务的可⽤性,rehash的进程不是⼀次性完成的,⽽是渐进式的。

    2. 跳跃表skiplist:跳跃表是有序聚集的底层实现之⼀,Redis中在实现有序聚集键和集群节点的内部构造中都是⽤到了跳跃表。Redis跳跃表由zskiplist和zskiplistNode组成,zskiplist⽤于保留跳跃表信息(表头、表尾节点、⻓度等),zskiplistNode⽤于表现表跳跃节点,每个跳跃表节点的层⾼都是1-32的随机数,在同⼀个跳跃表中,多个节点可以包括雷同的分值,但是每个节点的成员对象必需是唯⼀的,节点依照分值⼤⼩排序,如果分值雷同,则依照成员对象的⼤⼩排序。

    3. 整数聚集intset:⽤于保留整数值的聚集抽象数据构造,不会涌现反复元素,底层实现为数组。

    4. 紧缩列表ziplist:紧缩列表是为勤俭内存⽽开发的次序性数据构造,它可以包括任意多个节点,每个节点可以保留⼀个字节数组或者整数值。

    紧缩列表组成

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

    C 语言应用了一个长度为 N+1 的字符数组来表现长度为 N 的字符串,并且字符数组最后一个元素总是 ,这种简略的字符串表现方法 不符合 Redis 对字符串在安全性、效力以及功效方面的要求。

    C语言的字符串

    C语言的字符串可能有什么问题?

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

    • 获取字符串长度庞杂度高 :因为 C 不保留数组的长度,每次都须要遍历一遍全部数组,时光庞杂度为O(n);
    • 不能杜绝 缓冲区溢出/内存泄露 的问题 : C字符串不记载自身长度带来的另外一个问题是容易造成缓存区溢出(buffer overflow),例如在字符串拼接的时候,新的
    • C 字符串 只能保留文本数据 → 因为 C 语言中的字符串必需符合某种编码(比如 ASCII),例如中间涌现的 '' 可能会被判定为提前停止的字符串而辨认不了;

    Redis如何解决?优势?

    Redis sds

    简略来说一下 Redis 如何解决的:

    1. 多增长 len 表现当前字符串的长度:这样就可以直接获取长度了,庞杂度 O(1);
    2. 自动扩大空间:当 SDS 须要对字符串进行修正时,首先借助于 lenalloc 检讨空间是否满足修正所需的要求,如果空间不够的话,SDS 会自动扩大空间,避免了像 C 字符串操作中的溢出情形;
    3. 有效降低内存分配次数:C 字符串在涉及增长或者消除操作时会转变底层数组的大小造成重新分配,SDS 应用了 空间预分配惰性空间释放 机制,简略懂得就是每次在扩大时是成倍的多分配的,在缩容是也是先留着并不正式归还给 OS;
    4. 二进制安全:C 语言字符串只能保留 ascii 码,对于图片、音频等信息无法保留,SDS 是二进制安全的,写入什么读取就是什么,不做任何过滤和限制;

    48.字典是如何实现的?Rehash 懂得吗?

    字典是 Redis 服务器中涌现最为频繁的复合型数据构造。除了 hash 构造的数据会用到字典外,全部 Redis 数据库的所有 keyvalue 也组成了一个 全局字典,还有带过期时光的 key 也是一个字典。*(存储在 RedisDb 数据构造中)*

    字典构造是什么样的呢?

    Redis 中的字典相当于 Java 中的 HashMap,内部实现也差不多相似,采取哈希与运算盘算下标地位;通过 "数组 + 链表" 链地址法 来解决哈希冲突,同时这样的构造也吸收了两种不同数据构造的长处。

    字典是怎么扩容的?

    字典构造内部包括 两个 hashtable,通常情形下只有一个哈希表 ht[0] 有值,在扩容的时候,把ht[0]里的值rehash到ht[1],然落后行 渐进式rehash ——所谓渐进式rehash,指的是这个rehash的动作并不是一次性、集中式地完成的,而是分多次、渐进式地完成的。

    待搬迁停止后,h[1]就取代h[0]存储字典的元素。

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

    PS:跳跃表是比较常问的一种构造。

    跳跃表(skiplist)是一种有序数据构造,它通过在每个节点中保持多个指向其它节点的指针,从而到达迅速拜访节点的目的。

    为什么应用跳跃表?

    首先,因为 zset 要支撑随机的插入和删除,所以它 不宜应用数组来实现,关于排序问题,我们也很容易就想到 红黑树/ 平衡树 这样的树形构造,为什么 Redis 不应用这样一些构造呢?

    1. 性能斟酌: 在高并发的情形下,树形构造须要履行一些相似于 rebalance 这样的可能涉及整棵树的操作,相对来说跳跃表的变更只涉及局部;
    2. 实现斟酌: 在庞杂度与红黑树雷同的情形下,跳跃表实现起来更简略,看起来也更加直观;

    基于以上的一些斟酌,Redis 基于 William Pugh 的论文做出一些改良后采取了 跳跃表 这样的构造。

    实质是解决查找问题。

    跳跃表是怎么实现的?

    跳跃表的节点里有这些元素:

    • 跳跃表节点的level数组可以包括多个元素,每个元素都包括一个指向其它节点的指针,程序可以通过这些层来加快拜访其它节点的速度,一般来说,层的数目月多,拜访其它节点的速度就越快。

      每次创立一个新的跳跃表节点的时候,程序都依据幂次定律,随机生成一个介于1和32之间的值作为level数组的大小,这个大小就是层的“高度”

    • 前进指针每个层都有一个指向表尾的前进指针(level[i].forward属性),用于从表头向表尾方向拜访节点。

      我们看一下跳跃表从表头到表尾,遍历所有节点的路径:

    • 跨度层的跨度用于记载两个节点之间的距离。跨度是用来盘算排位(rank)的:在查找某个节点的进程中,将沿途拜访过的所有层的跨度累计起来,得到的成果就是目的节点在跳跃表中的排位。

      例如查找,分值为3.0、成员对象为o3的节点时,沿途阅历的层:查找的进程只经过了一个层,并且层的跨度为3,所以目的节点在跳跃表中的排位为3。

    • 分值和成员节点的分值(score属性)是一个double类型的浮点数,跳跃表中所有的节点都按分值从小到大来排序。

      节点的成员对象(obj属性)是一个指针,它指向一个字符串对象,而字符串对象则保留这一个SDS值。

    50.紧缩列表懂得吗?

    紧缩列表是 Redis 为了勤俭内存 而应用的一种数据构造,是由一系列特殊编码的连续内存快组成的次序型数据构造。

    一个紧缩列表可以包括任意多个节点(entry),每个节点可以保留一个字节数组或者一个整数值。

    紧缩列表由这么几部分组成:

    • zlbyttes:记载全部紧缩列表占用的内存字节数
    • zltail:记载紧缩列表表尾节点距离紧缩列表的起始地址有多少字节
    • zllen:记载紧缩列表包括的节点数目
    • entryX:列表节点
    • zlend:用于标志紧缩列表的末端
    紧缩列表现例

    51.迅速列表 quicklist 懂得吗?

    Redis 早期版本存储 list 列表数据构造应用的是紧缩列表 ziplist 和普通的双向链表 linkedlist,也就是说当元素少时应用 ziplist,当元素多时用 linkedlist。

    但斟酌到链表的附加空间相对较高,prevnext 指针就要占去 16 个字节(64 位操作体系占用 8 个字节),另外每个节点的内存都是单独分配,会家具内存的碎片化,影响内存管理效力。

    后来 Redis 新版本(3.2)对列表数据构造进行了改革,应用 quicklist 取代了 ziplistlinkedlist,quicklist是综合斟酌了时光效力与空间效力引入的新型数据构造。

    quicklist由list和ziplist联合而成,它是一个由ziplist充任节点的双向链表。

    其他问题

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

    应用 keys 指令可以扫出指定模式的 key 列表。但是要注意 keys 指令会导致线程阻塞一段时光,线上服务会停顿,直到指令履行完毕,服务才能恢复。这个时候可以应用 scan 指令,scan 指令可以无阻塞的提取出指定模式的 key 列表,但是会有必定的反复概率,在客户端做一次去重就可以了,但是整体所消费的时光会比直接用 keys 指令长。


    如爱好本文,请点击右上角,把文章分享到朋友圈
    如有想懂得学习的技巧点,请留言给若飞支配分享

    因大众,号更改推送规矩,请点“在看”并加“星标”第一时光获取精彩技巧分享

    ·END·

    相干阅读:


    作者:三分恶

    起源:三分恶

    版权声名:内容起源网络,仅供学习研讨,版权归原创者所有。如有侵权烦请告诉,我们会立即删除并表现歉意。谢谢!

    架构师

    我们都是架构师!



    关注架构师(JiaGouX),添加“星标”

    获取每天技巧干货,一起成为牛逼架构师

    技巧群请加若飞:1321113940 进架构师群

    投稿、合作、版权等邮箱:admin@137x.com