一:项目中缓存如何使用?缓存使用不当会造成什么后果?

1.项目中如何使用:结合自己的项目总体概括一下即可

2.为啥在项目里要用缓存呢:

高性能:

假设这么个场景,你有个操作,一个请求过来,各种乱七八糟操作mysql,半天查出来一个结果,耗时600ms。但是这个结果可能接下来几个小时都不会变了,或者变了也可以不用立即反馈给用户。那么此时咋办?使用缓存,折腾600ms查出来的结果,扔缓存里,一个key对应一个value,下次再有人查,别走mysql折腾600ms了。直接从缓存里,通过一个key查出来一个value,2ms搞定。性能提升300倍。这就是所谓的高性能。就是把你一些复杂操作耗时查出来的结果,如果确定后面不咋变了,然后但是马上还有很多读请求,那么直接结果放缓存,后面直接读缓存就好了。

低延迟发开rtmp配置nginx_低延迟发开rtmp配置nginx

高并发:

mysql这么重的数据库,压根儿设计不是让你玩儿高并发的,虽然也可以玩儿,但是天然支持不好。mysql单机支撑到2000qps也开始容易报警了。所以要是你有个系统,高峰期一秒钟过来的请求有1万,那一个mysql单机绝对会死掉。你这个时候就只能上缓存,把很多数据放缓存,别放mysql。缓存功能简单,说白了就是key-value式操作,单机支撑的并发量轻松一秒几万十几万,支撑高并发so easy。单机承载并发量是mysql单机的几十倍。

低延迟发开rtmp配置nginx_低延迟发开rtmp配置nginx_02

3.用了缓存之后会有啥不良的后果:

  • 缓存与数据库双写不一致
  • 缓存雪崩
  • 缓存穿透
  • 缓存并发竞争

二:redis与memcached有啥区别?redis的线程模型是啥?为什么单线程的redis比多线程的memchached性能要高(为啥redis是单线程的还可以支持高并发)?

1.redis与memcached的区别

  • redis 相比 memcached 来说,拥有更多的数据结构,能支持更丰富的数据操作。如果需要缓存能够支持更复杂的结构和操作, redis 会是不错的选择。
  • 在 redis3.x 版本中,便能支持 cluster 模式,而 memcached 没有原生的集群模式,需要依靠客户端来实现往集群中分片写入数据。
  • redis单线程,memcached为多线程
  • 在写入大数据时,memcached性能要优于redis

2.redis的单线程模型

redis 内部使用文件事件处理器 file event handler,这个文件事件处理器是单线程的,所以 redis 才叫做单线程的模型。它采用 IO 多路复用机制同时监听多个 socket,根据 socket 上的事件来选择对应的事件处理器进行处理。文件事件处理器的结构包含 4 个部分:

  • 多个 socket
  • IO 多路复用程序
  • 文件事件分派器
  • 事件处理器(连接应答处理器、命令请求处理器、命令回复处理器)

多个 socket 可能会并发产生不同的操作,每个操作对应不同的文件事件,但是 IO 多路复用程序会监听多个 socket,会将 socket 产生的事件放入队列中排队,事件分派器每次从队列中取出一个事件,把该事件交给对应的事件处理器进行处理。来看客户端与 redis 的一次通信过程:

低延迟发开rtmp配置nginx_redis_03

  • 客户端 socket01 向 redis 的 server socket 请求建立连接,此时 server socket 会产生一个 AE_READABLE 事件,IO 多路复用程序监听到 server socket 产生的事件后,将该事件压入队列中。文件事件分派器从队列中获取该事件,交给连接应答处理器。连接应答处理器会创建一个能与客户端通信的 socket01,并将该 socket01 的 AE_READABLE 事件与命令请求处理器关联。
  • 此时客户端发送了一个 set key value 请求,此时 redis 中的 socket01 会产生 AE_READABLE 事件,IO 多路复用程序将事件压入队列,此时事件分派器从队列中获取到该事件,由于前面 socket01 的 AE_READABLE 事件已经与命令请求处理器关联,因此事件分派器将事件交给命令请求处理器来处理。命令请求处理器读取 socket01 的 key value 并在自己内存中完成 key value 的设置。操作完成后,它会将 socket01 的 AE_WRITABLE 事件与命令回复处理器关联。
  • 如果此时客户端准备好接收返回结果了,那么 redis 中的 socket01 会产生一个 AE_WRITABLE 事件,同样压入队列中,事件分派器找到相关联的命令回复处理器,由命令回复处理器对 socket01 输入本次操作的一个结果,比如 ok,之后解除 socket01 的 AE_WRITABLE 事件与命令回复处理器的关联。

3.为啥 redis 单线程模型也能效率这么高

  • 核心是基于非阻塞的 IO 多路复用机制:IO多路复用程序只负责监听socket链接,将socket压入队列中,如果是阻塞的,IO多路复用程序就会自己处理这个请求,直到处理完毕后,返回给客户端。
  • 基于存内存快速处理客户请求
  • 单线程反而避免了多线程的频繁上下文切换问题

三:Redis的数据类型?分别在哪些场景下使用合适?

1.redis数据类型:

  • string:这是最简单的类型,就是普通的 set 和 get,做简单的 KV 缓存。
  • hash:这个是类似 map 的一种结构,这个一般就是可以将结构化的数据,比如一个对象(前提是这个对象没嵌套其他的对象)给缓存在 redis 里,然后每次读写缓存的时候,可以就操作 hash 里的某个字段。
  • list:有序列表,其中的数据可重复
  • set:无序列表,其中数据不可重复
  • sorted set:有序,其中数据不可重复,写进去的时候给一个分数,自动根据分数排序。

2.分别适合的场景:

  • string:分布式下可以保存用户登陆信息,并通过expireat key 时间(秒),设置其登陆有效期
  • hash:可以保存用户购物车信息,hset cart_用户id 购物车数据
  • list:通过 list 存储一些列表型的数据结构,类似粉丝列表、文章的评论列表等。通过 lrange 命令,读取某个闭区间内的元素,可以基于 list 实现分页查询,这个是很棒的一个功能,基于 redis 实现简单的高性能分页,可以做类似微博那种下拉不断分页的东西,性能高。lrange key 起始位置 结束位置(-1表示最后)
  • set:可以基于 set 玩儿交集、并集、差集的操作,比如交集吧,可以把两个人的粉丝列表整一个交集,看看俩人的共同好友是谁,交集:sinter key1 key2,并集:sunion key1 key2
  • sorted set:可以做排序,获取前三名用户:zrevrange key 0 3

四:Redis的过期策略?手写一个LRU算法?

1.reids过期策略:过期删除+惰性删除+内存淘汰机制

过期删除:指的是 redis 默认是每隔 100ms 就随机抽取一些设置了过期时间的 key,检查其是否过期,如果过期就删除,假设 redis 里放了 10w 个 key,都设置了过期时间,你每隔几百毫秒,就检查 10w 个 key,那 redis 基本上就死了,cpu 负载会很高的,消耗在你的检查过期 key 上了。注意,这里可不是每隔 100ms 就遍历所有的设置过期时间的 key,那样就是一场性能上的灾难。实际上 redis 是每隔 100ms 随机抽取一些 key 来检查和删除的。但是问题是,定期删除可能会导致很多过期 key 到了时间并没有被删除掉,那咋整呢?所以就是惰性删除了。

惰性删除:这就是说,在你获取某个 key 的时候,redis 会检查一下 ,这个 key 如果设置了过期时间那么是否过期了?如果过期了此时就会删除,不会给你返回任何东西。但是实际上这还是有问题的,如果过期删除漏掉了很多过期 key,然后你也没及时去查,也就没走惰性删除,此时会怎么样?如果大量过期 key 堆积在内存里,导致 redis 内存块耗尽了,咋整?使用内存淘汰机制

内存淘汰机制:

  • noeviction: 当内存不足以容纳新写入数据时,新写入操作会报错,这个一般没人用吧。
  • allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的 key(这个是最常用的)。
  • allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个 key,这个一般没人用吧。
  • volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的 key(这个一般不太合适)。
  • volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个 key。
  • volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键中,有更早过期时间的 key 优先移除。

2.手写一个LRU算法

class LRUCache<K, V> extends LinkedHashMap<K, V> {
    
	//缓存大小
	private final int CACHE_SIZE;
 
    /**
     * 传递进来最多能缓存多少数据
     */
    public LRUCache(int cacheSize) {
        // true 表示让 linkedHashMap 按照访问顺序来进行排序,最近访问的放在头部,最老访问的放在尾部。
        super((int) Math.ceil(cacheSize / 0.75) + 1, 0.75f, true);
        CACHE_SIZE = cacheSize;
    }
 
    @Override
    protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
        // 当 map中的数据量大于指定的缓存个数的时候,linkedHashMap就自动删除最老的数据。
        return size() > CACHE_SIZE;
    }
}

五:保证Redis的高并发及高可用?

1.保证高并发:redis的高并发可使用其主从复制实现读写分离来解决,主从架构,一主多从,一般来说,很多项目其实就足够了,单主用来写入数据,单机几万QPS,多从用来查询数据,多个从实例可以提供每秒10万的QPS。redis高并发的同时,还需要容纳大量的数据:一主多从,每个实例都容纳了完整的数据,比如redis主就10G的内存量,其实你就最对只能容纳10g的数据量。如果你的缓存要容纳的数据量很大,达到了几十g,甚至几百g,或者是几t,那你就需要redis集群,而且用redis集群之后,可以提供可能每秒几十万的读写并发。

如果redis要支撑超过10万+的并发,那应该怎么做?单机的redis几乎不太可能说QPS超过10万+,除非一些特殊情况,比如你的机器性能特别好,配置特别高,物理机,维护做的特别好,而且你的整体的操作不是太复杂单机在几万,读写分离,一般来说,对缓存,一般都是用来支撑读高并发的,写的请求是比较少的,可能写请求也就一秒钟几千,一两千,大量的请求都是读,一秒钟二十万次读,读写分离,主从架构 -> 读写分离 -> 支撑10万+读QPS的架构

redis replication以及master持久化对主从架构的安全意义:

主从架构读写分离图:

低延迟发开rtmp配置nginx_redis_04

redis replication 的核心机制:

  • redis 采用异步方式复制数据到 slave 节点,不过 redis2.8 开始,slave node 会周期性地确认自己每次复制的数据量;
  • 一个 master node 是可以配置多个 slave node 的;
  • slave node 也可以连接其他的 slave node;
  • slave node 做复制的时候,不会 block master node 的正常工作;
  • slave node 在做复制的时候,也不会 block 对自己的查询操作,它会用旧的数据集来提供服务;但是复制完成的时候,需要删除旧数据集,加载新数据集,这个时候就会暂停对外服务了;
  • slave node 主要用来进行横向扩容,做读写分离,扩容的 slave node 可以提高读的吞吐量。

注意,如果采用了主从架构,那么建议必须开启 master node 的持久化,不建议用 slave node 作为 master node 的数据热备,因为那样的话,如果关掉 master 的持久化,可能在 master 宕机重启的时候数据是空的,然后可能一经过复制, slave node 的数据也丢了。master节点,必须要使用持久化机制。另外,master 的各种备份方案,也需要做。万一本地的所有文件丢失了,从备份中挑选一份 rdb 去恢复 master,这样才能确保启动的时候,是有数据的。即使采用了后续讲解的高可用(哨兵模式)机制,slave node 可以自动接管 master node,但也可能 sentinel 还没检测到 master failure,master node 就自动重启了,还是可能导致上面所有的 slave node 数据被清空。

redis主从复制原理:

低延迟发开rtmp配置nginx_数据_05

  • 当启动一个 slave node 的时候,它会发送一个 PSYNC 命令给 master node。
  • slave node 初次连接到 master node,那么会触发一次 full resynchronization 全量复制。
  • 开始full resynchronization的时候,master 会启动一个后台线程,开始生成一份 RDB 快照文件,同时还会将从客户端新收到的所有写命令缓存在内存中。RDB 文件生成完毕后, master 会将这个 RDB 发送给 slave,slave 会先写入本地磁盘,然后再从本地磁盘加载到内存中,接着 master 会将内存中缓存的写命令发送到 slave,slave 也会同步这些数据。
  • 如果这是slave node重新连接master node,那么master node仅仅会复制给slave部分缺少的数据
  • slave node 如果跟 master node 有网络故障,断开了连接,会自动重连,连接之后 master node 仅会复制给 slave 部分缺少的数据。master如果发现有多个slave node都来重新连接,仅仅会启动一个rdb save操作,用一份数据服务所有slave node。

主从复制的断点续传:

  • 从 redis2.8 开始,就支持主从复制的断点续传,如果主从复制过程中,网络连接断掉了,那么可以接着上次复制的地方,继续复制下去,而不是从头开始复制一份。
  • master node 会在内存中维护一个 backlog,master 和 slave 都会保存一个 replica offset 还有一个 master run id,offset 就是保存在 backlog 中的。如果 master 和 slave 网络连接断掉了,slave 会让 master 从上次 replica offset 开始继续复制,如果没有找到对应的 offset,那么就会执行一次 resynchronization全量复制。
  • 注意如果根据 host+ip 定位 master node,是不靠谱的,如果 master node 重启或者数据出现了变化,那么 slave node 应该根据不同的 run id 区分。

无磁盘化复制:

  • master 在内存中直接创建 RDB,然后发送给 slave,不会在自己本地落地磁盘了。只需要在配置文件中开启 repl-diskless-sync yes 即可。配置repl-diskless-sync-delay 5表示等待 5s 后再开始复制,因为要等更多 slave 重新连接过来

过期key处理:

  • slave 不会过期 key,只会等待 master 过期 key。如果 master 过期了一个 key,或者通过 LRU 淘汰了一个 key,那么会模拟一条 del 命令发送给 slave。

完整复制流程再解析:

低延迟发开rtmp配置nginx_面试详解_06

  • slave node 启动时,会在自己本地保存 master node 的信息,包括 master node 的host和ip,但是复制流程没开始。redis.conf里面的slaveof配置有master的host和ip。
  • slave node 内部有个定时任务,每秒检查是否有新的 master node 要连接和复制,如果发现,就跟 master node 建立 socket 网络连接。
  • slave node 发送 ping 命令给 master node。
  • 口令认证,如果 master 设置了 requirepass,那么 slave node 必须发送 masterauth 的口令过去进行认证。
  • master node 第一次执行全量复制,将所有数据发给slave node。
  • 之后,master node 持续将写命令,异步复制给 slave node。

数据同步相关的核心机制:数据同步即第一次全量复制中的一些细节机制

  • master和slave都会维护一个offset,master会在自身不断累加offset,slave也会在自身不断累加offset,slave每秒都会上报自己的offset给master,同时master也会保存每个slave的offset这个倒不是说特定就用在全量复制的,主要是master和slave都要知道各自的数据的offset,才能知道互相之间的数据不一致的情况
  • backlog,master node有一个backlog,默认是1MB大小,master node给slave node复制数据时,也会将数据在backlog中同步写一份,backlog主要是用来做全量复制中断后的增量复制的。
  • master run id,如果根据host+ip定位master node,是不靠谱的,如果master node重启或者数据出现了变化,那么slave node应该根据不同的run id区分,run id不同就做全量复制,如果需要不更改run id重启redis,可以使用redis-cli debug reload命令
  • psync,从节点使用psync从master node进行复制,psync runid offset。master node会根据自身的情况返回响应信息,可能是FULLRESYNC runid offset触发全量复制,可能是CONTINUE触发增量复制

全量复制:

  • master 执行 bgsave ,在本地生成一份 rdb 快照文件。
  • master node 将 rdb 快照文件发送给 slave node,如果 rdb 复制时间超过 60秒(repl-timeout),那么 slave node 就会认为复制失败,可以适当调大这个参数(对于千兆网卡的机器,一般每秒传输 100MB,6G 文件,很可能超过 60s)
  • master node 在生成 rdb 时,会将所有新的写命令缓存在内存中,在 slave node 保存了 rdb 之后,再将新的写命令复制给 slave node。
  • 如果在复制期间,内存缓冲区持续消耗超过 64MB,或者一次性超过 256MB,那么停止复制,复制失败。
  • slave node 接收到 rdb 之后,清空自己的旧数据,然后重新加载 rdb 到自己的内存中,同时基于旧的数据版本对外提供服务。
  • 如果 slave node 开启了 AOF,那么会立即执行 BGREWRITEAOF,重写 AOF。rdb生成、rdb通过网络拷贝、slave旧数据的清理、slave aof rewrite,很耗费时间

增量复制:

  • 如果全量复制过程中,master-slave 网络连接断掉,那么 slave 重新连接 master 时,会触发增量复制。
  • master 直接从自己的 backlog 中获取部分丢失的数据,发送给 slave node,默认 backlog 就是1MB。
  • msater就是根据 slave 发送的 psync 中的 offset 来从 backlog 中获取数据的。

heartbeat:

  • 主从节点互相都会发送 heartbeat 信息。
  • master 默认每隔 10秒 发送一次 heartbeat,slave node 每隔 1秒 发送一个 heartbeat。

异步复制:

  • master 每次接收到写命令之后,先在内部写入数据,然后异步发送给 slave node。

2.保证高可用:

一个 slave 挂掉了,是不会影响可用性的,还有其它的 slave 在提供相同数据下的相同的对外的查询服务。但是,如果 master node 死掉了,会怎么样?没法写数据了,写缓存的时候,全部失效了。slave node 还有什么用呢,没有 master 给它们复制数据了,系统相当于不可用了。redis 的高可用架构,叫做 failover 故障转移,也可以叫做主备切换。master node 在故障时,自动检测,并且将某个 slave node 自动切换位 master node的过程,叫做主备切换。这个过程,实现了 redis 的主从架构下的高可用。

哨兵模式:sentinel,中文名是哨兵。哨兵是 redis 集群机构中非常重要的一个组件,主要有以下功能:

  • 集群监控:负责监控 redis master 和 slave 进程是否正常工作。
  • 消息通知:如果某个 redis 实例有故障,那么哨兵负责发送消息作为报警通知给管理员。
  • 故障转移:如果 master node 挂掉了,会自动转移到 slave node 上。
  • 配置中心:如果故障转移发生了,通知 client 客户端新的 master 地址。

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

  • 故障转移时,判断一个 master node 是否宕机了,需要大部分的哨兵都同意才行,涉及到了分布式选举的问题。
  • 即使部分哨兵节点挂掉了,哨兵集群还是能正常工作的,因为如果一个作为高可用机制重要组成部分的故障转移系统本身是单点的,那就很坑爹了。

目前采用的是sentinal 2版本,sentinal 2相对于sentinal 1来说,重写了很多代码,主要是让故障转移的机制和算法变得更加健壮和简单

哨兵的核心知识:

  • 哨兵至少需要 3 个实例,来保证自己的健壮性。
  • 哨兵 + redis 主从的部署架构,是不保证数据零丢失的,只能保证 redis 集群的高可用性。
  • 对于哨兵 + redis 主从这种复杂的部署架构,尽量在测试环境和生产环境,都进行充足的测试和演练。

为什么redis哨兵集群只有2个节点无法正常工作?

低延迟发开rtmp配置nginx_缓存_07

配置quorum = 1,表示两个哨兵其中一个认为master宕机即可进行主备切换,master宕机,s1和s2中只要有1个哨兵认为master宕机就可以还行切换,同时s1和s2中会选举出一个哨兵来执行故障转移,同时这个时候,需要majority(大多数哨兵还在运行),也就是大多数哨兵都是运行的,2个哨兵的majority就是2(2的majority=2,3的majority=2,5的majority=3,4的majority=2),2个哨兵都运行着,就可以允许执行故障转移,但是如果整个M1和S1运行的机器宕机了,那么哨兵只有1个了,此时就没有majority来允许执行故障转移,虽然另外一台机器还有一个R1,但是故障转移不会执行

经典的3节点哨兵集群:

低延迟发开rtmp配置nginx_低延迟发开rtmp配置nginx_08

quorum = 2,majority=2,如果M1所在机器宕机了,那么三个哨兵还剩下2个,S2和S3可以一致认为master宕机,然后选举出一个来执行故障转移,同时3个哨兵的majority是2,所以还剩下的2个哨兵运行着,就可以允许执行故障转移

redis哨兵主备切换的数据丢失问题:

异步复制导致的数据丢失:

低延迟发开rtmp配置nginx_低延迟发开rtmp配置nginx_09

因为 master到slave 的复制是异步的,所以可能有部分数据还没复制到 slave,master 就宕机了,此时这部分数据就丢失了。

脑裂导致的数据丢失:

低延迟发开rtmp配置nginx_redis_10

脑裂,也就是说,某个 master 所在机器突然脱离了正常的网络,跟其他 slave 机器不能连接,但是实际上 master 还运行着。此时哨兵可能就会认为 master 宕机了,然后开启选举,将其他 slave 切换成了 master。这个时候,集群里就会有两个 master ,也就是所谓的脑裂。此时虽然某个 slave 被切换成了master,但是可能 client 还没来得及切换到新的 master,还继续向旧 master 写数据。因此旧 master 再次恢复的时候,会被作为一个 slave 挂到新的 master 上去,自己的数据会清空,重新从新的 master 复制数据。而新的 master 并没有后来 client 写入的数据,因此,这部分数据也就丢失了。

数据丢失问题的解决方案:

min-slaves-to-write 1
min-slaves-max-lag 10

要求至少有 1 个 slave,数据复制和同步的延迟不能超过 10 秒。如果说一旦所有的 slave,数据复制和同步的延迟都超过了 10 秒钟,那么这个时候,master 就不会再接收任何请求了。

低延迟发开rtmp配置nginx_低延迟发开rtmp配置nginx_11

异步复制导致数据延迟,随着时间的增加,slave缺少的数据可能会更多,如果master宕机了,丢失的数据就会很多。

  • 减少异步复制数据的丢失:有了 min-slaves-max-lag 这个配置,就可以确保说,一旦 slave 复制数据和 ack 延时太长,就认为可能 master 宕机后损失的数据太多了,那么就拒绝写请求,这样可以把 master 宕机时由于部分数据未同步到 slave 导致的数据丢失降低的可控范围内。
  • 减少脑裂的数据丢失:如果一个 master 出现了脑裂,跟其他 slave 丢了连接,那么上面两个配置可以确保说,如果不能继续给指定数量的 slave 发送数据,而且 slave 超过 10 秒没有给自己 ack 消息,那么就直接拒绝客户端的写请求。

哨兵如何发现master宕机:sdown和odown转换机制

sdown 是主观宕机,就一个哨兵如果自己觉得一个 master 宕机了,那么就是主观宕机。odown 是客观宕机,如果 quorum 数量的哨兵都觉得一个 master 宕机了,那么就是客观宕机。sdown 达成的条件很简单,如果一个哨兵 ping 一个 master,超过了 is-master-down-after-milliseconds 指定的毫秒数之后,就主观认为 master 宕机了。sdown到odown转换的条件很简单,如果一个哨兵在指定时间内,收到了 quorum 数量的 其它哨兵也认为那个 master 是 sdown 的,那么就认为是 odown 了,客观认为master宕机。

哨兵集群的自动发现机制:

哨兵互相之间的发现,是通过 redis 的 pub/sub 系统实现的,每个哨兵都会往__sentinel__:hello这个 channel 里发送一个消息,这时候所有其他哨兵都可以消费到这个消息,并感知到其他的哨兵的存在。每隔两秒钟,每个哨兵都会往自己监控的某个 master+slaves 对应的__sentinel__:hello channel 里发送一个消息,内容是自己的 host、ip 和 runid 还有对这个 master 的监控配置。每个哨兵也会去监听自己监控的每个 master+slaves 对应的__sentinel__:hello channel,然后去感知到同样在监听这个 master+slaves 的其他哨兵的存在。每个哨兵还会跟其他哨兵交换对 master 的监控配置,互相进行监控配置的同步。
哨兵选举slave为master:slave->master 选举算法

如果一个 master 被认为 odown 了,而且 majority 数量的哨兵都允许主备切换,那么某个哨兵就会执行主备切换操作,此时首先要选举一个 slave 来,会考虑 slave 的一些信息:

  • 跟 master 断开连接的时长
  • slave 优先级
  • 复制 offset
  • run id

如果一个 slave 跟 master 断开连接的时间已经超过了down-after-milliseconds的 10 倍,外加 master 宕机的时长,那么 slave 就被认为不适合选举为 master。(down-after-milliseconds * 10) + milliseconds_since_master_is_in_SDOWN_state接下来会对 slave 进行排序:

  • 按照 slave 优先级进行排序,slave priority 越低,优先级就越高。
  • 如果 slave priority 相同,那么看 replica offset,哪个 slave 复制了越多的数据,offset 越靠后,优先级就越高。
  • 如果上面两个条件都相同,那么选择一个 run id 比较小的那个 slave。

slave节点配置的自动纠正:

哨兵会负责自动纠正 slave 的一些配置,比如 slave 如果要成为潜在的 master 候选人,哨兵会确保 slave 复制现有 master 的数据; 如果 slave 连接到了一个错误的 master 上,比如故障转移之后,那么哨兵会确保它们连接到正确的 master 上。

quorum 和 majority:

每次一个哨兵要做主备切换,首先需要 quorum 数量的哨兵认为 odown,然后选举出一个哨兵来做切换,这个哨兵还得得到 majority 哨兵的授权,才能正式执行切换。如果 quorum < majority,比如 5 个哨兵,majority 就是 3,quorum 设置为2,那么就 3 个哨兵授权就可以执行切换。但是如果 quorum >= majority,那么必须 quorum 数量的哨兵都授权,比如 5 个哨兵,quorum 是 5,那么必须 5 个哨兵都同意授权,才能执行切换。

configuration epoch:

哨兵会对一套 redis master+slaves 进行监控,有相应的监控的配置。执行切换的那个哨兵,会从要切换到的新 master(salve->master)那里得到一个 configuration epoch,这就是一个 version 号,每次切换的 version 号都必须是唯一的。如果第一个选举出的哨兵切换失败了,那么其他哨兵会等待 failover-timeout 时间,然后接替继续执行切换,此时会重新获取一个新的 configuration epoch,作为新的 version 号。

configuraiton 传播:

哨兵完成切换之后,会在自己本地更新生成最新的 master 配置,然后同步给其他的哨兵,就是通过之前说的 pub/sub 消息机制。这里之前的 version 号就很重要了,因为各种消息都是通过一个 channel 去发布和监听的,所以一个哨兵完成一次新的切换之后,新的 master 配置是跟着新的 version 号的。其他的哨兵都是根据版本号的大小来更新自己的 master 配置的。

总结:redis如何事先高并发高可用

redis 实现高并发主要依靠主从架构,一主多从,一般来说,很多项目其实就足够了,单主用来写入数据,单机几万 QPS,多从用来查询数据,多个从实例可以提供每秒 10w 的 QPS。如果想要在实现高并发的同时,容纳大量的数据,那么就需要 redis 集群,使用 redis 集群之后,可以提供每秒几十万的读写并发。redis 高可用,如果是做主从架构部署,那么加上哨兵就可以了,就可以实现,任何一个实例宕机,可以进行主备切换。

六:如何保证Redis宕机后,重启数据可以恢复(持久化方面的问题)?

1.redis的持久化意义:解决redis宕机后数据恢复的问题,比如你部署了一个redis,作为cache缓存,当然也可以保存一些较为重要的数据。如果没有持久化的话,redis遇到灾难性故障的时候,就会丢失所有的数据。如果通过持久化将数据搞一份儿在磁盘上去,然后定期比如说同步和备份到一些云存储服务上去,那么就可以保证数据不丢失全部,还是可以恢复一部分数据回来的。

低延迟发开rtmp配置nginx_面试详解_12

2.关于AOF及RDB的介绍:

七:Reids cluster集群模式的原理?

1.redis cluster:

redis cluster是Redis的分布式解决方案,在3.0版本推出后有效地解决了redis分布式方面的需求,自动将数据进行分片,每个master上放一部分数据,提供内置的高可用支持,部分master不可用时,还是可以继续工作的,支撑N个redis master node,每个master node都可以挂载多个slave node,高可用,因为每个master都有salve节点,那么如果mater挂掉,redis cluster这套机制,就会自动将某个slave切换成master

低延迟发开rtmp配置nginx_面试详解_13

这个redis可以横向扩容,如果要支撑大量数据的缓存,那就横向扩容更多的master节点。

redis cluster vs replication + sentinal:

如果你的数据量很少,主要是承载高并发高性能的场景,比如你的缓存一般就几个G,单机足够了replication,一个mater,多个slave,要几个slave跟你的要求的读吞吐量有关系,然后自己搭建一个sentinal集群,去保证redis主从架构的高可用性,就可以了,redis cluster,主要是针对海量数据+高并发+高可用的场景,海量数据,如果你的数据量很大,那么建议就用redis cluster

2.数据分布算法:

hash算法:

比如你有 N 个 redis实例,那么如何将一个key映射到redis上呢,你很可能会采用类似下面的通用方法计算 key的 hash 值,然后均匀的映射到到 N 个 redis上:

  • hash(key)%N
  • 如果增加一个redis,映射公式变成了 hash(key)%(N+1)
  • 如果一个redis宕机了,映射公式变成了 hash(key)%(N-1)
  • 在这两种情况下,几乎所有的缓存都失效了(增加或宕机master,就会导致获取数据的取模运算与之前插入数据的取模运算得到的结果不一致,从而导致大部分请求无法获得缓存)。会导致数据库访问的压力陡增,严重情况,还可能导致数据库宕机。

一致性hash算法:

低延迟发开rtmp配置nginx_redis_14

  • 一个key过来以后,同样也是计算hash值,然后会用hash值在圆环对应的各个点上(每个圆环都有hash值)去对比,看hash值应该落在这个圆环的那个部位
  • key落在圆环上以后,会顺时针旋转,寻找距离自己最近的一个点,插入数据
  • 当其中某一个master节点宕机后,查询数据依旧是采用顺时针查找,只会丢失宕机节点的数据
  • 存在缓存热点的问题:可能会有大量数据存储在同一个节点上,性能出现瓶颈

用虚拟节点改进,解决一致性hash缓存热点问题:

低延迟发开rtmp配置nginx_面试详解_15

给每个master做了均匀分布的虚拟节点,在每个区间内大量的数据都虚拟的分布到不同节点上,而不是按顺时针走,全部进入到一个master。

redis cluster的hash slot算法:

redis cluster有固定的16384个hash slot,对每个key计算CRC16值,然后对16384取模,可以获取key对应的hash slot,redis cluster中每个master都会持有部分slot,比如有3个master,那么可能每个master持有5000多个hash slot,hash slot让node的增加和移除很简单,增加一个master,就将其他master的hash slot移动部分过去,减少一个master,就将它的hash slot移动到其他master上去,移动hash slot的成本是非常低的,客户端的api,可以对指定的数据,让他们走同一个hash slot,通过hash tag来实现。其中一个节点宕机后,不会影响其他maste

3.节点间的内部通信机制

集中式的集群元数据存储:

集群中有很多元数据,包括hashslot与node之间的关系,master与slave之间的对应关系,故障信息等

低延迟发开rtmp配置nginx_缓存_16

优缺点:元数据的变更时效性非常好,一旦元数据变更会立即更行到存储中,其他节点即可知道,所有元数据的更新压力集中到一点,可能会导致元素据的存储有压力

redis cluster节点间采取gossip协议进行通信:

小道流言,所有节点持有一份元数据,节点出现元数据变更,就发送给其他节点,其他节点在变更元数据。

优缺点:元数据的根性比较分散,不是集中在一个地方,更新请求会陆陆续续在节点中更新,有一定延迟,降低压力,元素据更行存在延迟,可能导致集群的操作会有滞后。

10000端口:

每个节点都有一个专门用于节点间通信的端口,就是自己提供服务的端口号+10000,比如7001,那么用于节点间通信的就是17001端口,每隔节点每隔一段时间都会往另外几个节点发送ping消息,同时其他几点接收到ping之后返回pong

交换的信息:故障信息,节点的增加和移除,hash slot信息,等等

4.gossip协议

gossip协议包含多种消息,包括ping,pong,meet,fail,等等,meet: 某个节点发送meet给新加入的节点,让新节点加入集群中,然后新节点就会开始与其他节点进行通信,redis-trib.rb add-node,其实内部就是发送了一个gossip meet消息,给新加入的节点,通知那个节点去加入我们的集群,ping: 每个节点都会频繁给其他节点发送ping,其中包含自己的状态还有自己维护的集群元数据,互相通过ping交换元数据,每个节点每秒都会频繁发送ping给其他的集群,ping,频繁的互相之间交换数据,互相进行元数据的更新,pong: 返回ping和meet,包含自己的状态和其他信息,也可以用于信息广播和更新,fail: 某个节点判断另一个节点fail之后,就发送fail给其他节点,通知其他节点,指定的节点宕机了

5.ping消息深入

ping很频繁,而且要携带一些元数据,所以可能会加重网络负担,每个节点每秒会执行10次ping,每次会选择5个最久没有通信的其他节点,当然如果发现某个节点通信延时达到了cluster_node_timeout / 2,那么立即发送ping,避免数据交换延时过长,落后的时间太长了,比如说,两个节点之间都10分钟没有交换数据了,那么整个集群处于严重的元数据不一致的情况,就会有问题,所以cluster_node_timeout可以调节,如果调节比较大,那么会降低发送的频率,每次ping,一个是带上自己节点的信息,还有就是带上1/10其他节点的信息,发送出去,进行数据交换,至少包含3个其他节点的信息,最多包含总节点-2个其他节点的信息

6.面向集群的jedis的客户端原理

请求重定向:

客户端可能会挑选任意一个redis实例去发送命令,每个redis实例接收到命令,都会计算key对应的hash slot,如果在本地就在本地处理,否则返回moved给客户端,让客户端进行重定向,cluster keyslot mykey,可以查看一个key对应的hash slot是什么,用redis-cli的时候,可以加入-c参数,支持自动的请求重定向,redis-cli接收到moved之后,会自动重定向到对应的节点执行命令

计算hash slot:

计算hash slot的算法,就是根据key计算CRC16值,然后对16384取模,拿到对应的hash slot,用hash tag可以手动指定key对应的slot,同一个hash tag下的key,都会在一个hash slot中,比如set mykey1:{100}和set mykey2:{100}

hash slot查找:节点间通过gossip协议进行数据交换,就知道每个hash slot在哪个节点上

7.smart jedis

什么是smart jedis:

基于重定向的客户端,很消耗网络IO,因为大部分情况下,可能都会出现一次请求重定向,才能找到正确的节点,所以大部分的客户端,比如java redis客户端,就是jedis,都是smart的,本地维护一份hashslot -> node的映射表,缓存,大部分情况下,直接走本地缓存就可以找到hashslot -> node,不需要通过节点进行moved重定向

JedisCluster的工作原理:

在JedisCluster初始化的时候,就会随机选择一个node,初始化hashslot -> node映射表,同时为每个节点创建一个JedisPool连接池,每次基于JedisCluster执行操作,首先JedisCluster都会在本地计算key的hashslot,然后在本地映射表找到对应的节点,如果那个node正好还是持有那个hashslot,那么就ok; 如果说进行了reshard这样的操作,可能hashslot已经不在那个node上了,就会返回moved,如果JedisCluter API发现对应的节点返回moved,那么利用该节点的元数据,更新本地的hashslot -> node映射表缓存,重复上面几个步骤,直到找到对应的节点,如果重试超过5次,那么就报错,JedisClusterMaxRedirectionException,jedis老版本,可能会出现在集群某个节点故障还没完成自动切换恢复时,频繁更新hash slot,频繁ping节点检查活跃,导致大量网络IO开销,jedis最新版本,对于这些过度的hash slot更新和ping,都进行了优化,避免了类似问题

hashslot迁移和ask重定向:

如果hash slot正在迁移,那么会返回ask重定向给jedis,jedis接收到ask重定向之后,会重新定位到目标节点去执行,但是因为ask发生在hash slot迁移过程中,所以JedisCluster API收到ask是不会更新hashslot本地缓存,已经可以确定说,hashslot已经迁移完了,moved是会更新本地hashslot->node映射表缓存的

8.高可用性与主备切换原理:redis cluster的高可用的原理,几乎跟哨兵是类似的

判断节点宕机:

如果一个节点认为另外一个节点宕机,那么就是pfail,主观宕机,如果多个节点都认为另外一个节点宕机了,那么就是fail,客观宕机,跟哨兵的原理几乎一样,sdown,odown,在cluster-node-timeout内,某个节点一直没有返回pong,那么就被认为pfail,如果一个节点认为某个节点pfail了,那么会在gossip ping消息中,ping给其他节点,如果超过半数的节点都认为pfail了,那么就会变成fail

从节点过滤:

对宕机的master node,从其所有的slave node中,选择一个切换成master node,检查每个slave node与master node断开连接的时间,如果超过了cluster-node-timeout * cluster-slave-validity-factor,那么就没有资格切换成master,这个也是跟哨兵是一样的,从节点超时过滤的步骤

从节点选举:

哨兵:对所有从节点进行排序,slave priority,offset,run id,每个从节点,都根据自己对master复制数据的offset,来设置一个选举时间,offset越大(复制数据越多)的从节点,选举时间越靠前,优先进行选举,所有的master node开始slave选举投票,给要进行选举的slave进行投票,如果大部分master node(N/2 + 1)都投票给了某个从节点,那么选举通过,那个从节点可以切换成master,从节点执行主备切换,从节点切换为主节点

与哨兵比较:

整个流程跟哨兵相比,非常类似,所以说,redis cluster功能强大,直接集成了replication和sentinal的功能