高可用主要来自两方面:

  1. 数据主从节点之间会有同步,如果主节点没了,从节点还有数据
  2. 有哨兵节点进行监控,选主。主节点崩了,重新选一个,继续向外提供服务

为什么要有哨兵?

在Redis的主从架构中,读写是分离的,如果主没了,那就没人来处理写命令了,也没法给同节点同步数据了

这时如果要恢复服务的话,需要人工介入,选择一个「从节点」切换为「主节点」,然后让其他从节点指向新

的主节点,同时还需要通知上游那些连接 Redis 主节点的客户端,将其配置中的主节点 IP 地址更新为「新主

节点」的 IP 地址。

redis 2.8后就推出了一种新模式,即Sentinel,干的事情就是监控主节点是否宕机

以及主节点宕机选主,然后把数据同步给从节点和客户端

有了哨兵后,客户端就可以 和sentinel通信,然后获取主节点信息

哨兵如何工作?

哨兵其实是运行在特殊环境下的一个Redis进程,所以它是一个节点,但实际落地哨兵也是有多个的。

主要负责三件事:监控,选主,通知

如何判断主节点是否真故障?

哨兵每隔一秒给主从节点发心跳包,主从节点在规定时间内回了那就是正常的。

如果没有哨兵就会将它们标记为「主观下线」。这个「规定的时间」是配置项 down-after-millisecon

参数设定的,单位是毫秒。

下线分主观和客观,主观就是唯心的即实际不一定的,从节点只会被标记为客观

主节点可能会因为请求压力大,导致网络阻塞从而回消息慢了点,不是真下线

然后为了避免这种情况,即增强纠错机制,哨兵也是议会制的,即多个哨兵都认为它宕机了它才宕机

增加容错度

具体是怎么判定主节点为「客观下线」的呢?

当有一个哨兵认为主节点下线后,就会通知其他哨兵,根据自身和主节点的网络情况,做出攒成和反对的投

票。

当这个哨兵的赞同票数达到哨兵配置文件中的 quorum 配置项设定的值后,这时主节点就会被该哨兵标记为

「客观下线」。

PS:quorum 的值一般设置为哨兵个数的二分之一加 1,例如 3 个哨兵就设置 2。

判断完后就会从从节点里选一个出来作为主节点

哪个哨兵来进行选主呢?

哨兵投票投出来的。一般会有候选者。

哪个哨兵节点判断主节点为「客观下线」,这个哨兵节点就是候选者,所谓的候选者就是想

当 Leader 的哨兵。

每个哨兵只有一次投票机会,如果用完后就不能参与投票了,可以投给自己或投给别人,但是只有候选者才能

把票投给自己。

那么在投票过程中,任何一个「候选者」,要满足两个条件:

第一,拿到半数以上的赞成票;

第二,拿到的票数同时还需要大于等于哨兵配置文件中的 quorum 值。

举个例子,假设哨兵节点有 3 个,quorum 设置为 2,那么任何一个想成为 Leader 的哨兵只要拿到 2 张赞成票,就可以选举成功了。如果没有满足条件,就需要重新进行选举。

如果某个时间点,刚好有两个哨兵节点判断到主节点为客观下线,那这时不就有两个候选者了?这时该如何决定谁是 Leader 呢?

简单来说,谁先发现有个节点故障了,谁就是候选者,如果只有一个候选者,一般就是它成为Leader,如果

同时有多个候选者,看其它投票者先给谁投票,谁就是Leader

主从故障转移的过程是怎样的?

  1. 从已下线的主节点下的从节点里挑一个出来
  2. 让从节点修改copy目标
  3. 将新主节点的ip地址和信息,通过pub/sub通知到客户端里去
  4. 继续监视已下线的主节点,如果它复活了,那就将它设置为新主节点的从节点

步骤一:挑选过程

先过滤掉网络不健康的从节点,然后三轮考察,一是优先级,二是复制进度,三是ID号

前两个都是谁大谁win,id号是谁小谁win

winer出来后,然后向这个「从节点」发送 SLAVEOF no one 命令,将这个「从节点」转换为「主节点」

步骤二:让从节点更改

哨兵向原从节点发送 slaveof 命令

步骤三:通知客户的主节点已更换

经过前面一系列的操作后,哨兵集群终于完成主从切换的工作,那么新主节点的信息要如何通知给客户端

呢?

这主要通过 Redis 的发布者/订阅者机制来实现的。每个哨兵节点提供发布者/订阅者机制,客户端可以从哨兵订阅消息。

哨兵提供的消息订阅频道有很多,不同频道包含了主从节点切换过程中的不同关键事件,几个常见的事件如下:

客户端和哨兵建立连接后,客户端会订阅哨兵提供的频道。主从切换完成后,哨兵就会向 +switch-master 频道发布新主节点的 IP 地址和端口的消息,这个时候客户端就可以收到这条信息,然后用这里面的新主节点的 IP 地址和端口进行通信了。

通过发布者/订阅者机制机制,有了这些事件通知,客户端不仅可以在主从切换后得到新主节点的连接信息,还可以监控到主从节点切换过程中发生的各个重要事件。这样,客户端就可以知道主从切换进行到哪一步了,有助于了解切换进度

步骤四:将旧主节点变为从节点

故障转移操作最后要做的是,继续监视旧主节点,当旧主节点重新上线时,哨兵集群就会向它发送 SLAVEOF 命令,让它成为新主节点的从节点

哨兵集群如何组成?

在配置哨兵信息的时候,只需要设置这几个参数,主节点名字,主节点的IP地址和端口号以及 qunorum值

不需要填其他哨兵的信息,哨兵节点之间是通过订阅发现机制来互相发现的

在主从集群中,主节点上有一个名为 sentinel:hello的频道,不同哨兵就是通过它来互相发现的,

实现互相通信的

哨兵主节点只要发布信息到到这个频道上,哨兵b,c订阅了这个频道,就可以互相通信了,集群就这样形成了

哨兵如何知道从节点信息?

主节点知道所有从节点的信息,所以从节点每秒10s就向主节点发INFO请求获取所有从节点的信息

1.对于一些更改操作多的数据,可以考虑使用hash存储

2.使用时避免存储过大的数据块

3.线上的一些命令使用

不要用keys,会扫描所有key,导致主线程阻塞

避免使用FLUSHALL,FLUSHDB删除数据库

5.合理设置key的过期时间

4.高并发场景,对于一些热点key要做好监控告警措施

5.根据redis的使用场景,选择其持久化策略

Fail-safe

Fail-safe迭代器在迭代过程中如果检测到集合的结构被修改,将抛出ConcurrentModificationException。这

种行为主要是通过迭代器中的一个modcount的计数器实现的,元素增or删都会发生变动。迭代器遍历时都会

检查这个计数器是否与size符合,不符合就抛错

主要集合

HashMap,Set,ArrayList等

优点:快速响应错误,帮助开发者早点知道错误

缺点:多线程环境下,如果不进行外部同步,容易抛错

Fail-fast

Fail-fast迭代器允许在迭代过程中对集合进行修改,不会抛concurrentModification异常但不会在原集合上修

改,而是在集合的副本上修改,即修改不会影响到迭代过程。这样的话这种机制在多线程环境下更加安全

copyonwrite….

concurrenthashmap….

ArrayList底层是个数组

LinkedList双向链表,添加和删除元素比ArrayList性能高一点,get()和set()就要比其低点,只不过这里的比较基于大数据量来说,少数据量其实没啥可以忽略不计

system.gc()?

finalize()?

是一个protected方法,我们可以去重写这个逻辑。回收对象时会去调用这个方法,但最多只能调用一次

STW

任何垃圾回收器都是会STW的

开发时不要使用system.gc(),会STW!!!

内存泄漏是什么

简单地说,就是本该回收即不会再被使用的内存没被回收,俗话讲就是占着茅坑不拉屎

内存泄漏多了就会发生内存溢出即OOM

什么情况下会发生?

安全点和安全区域

哪些情况新生代会进入老年代?

  • 新生代gc后survivor区不够存放存活下来的对象,会将对象通过内存担保机制晋升到老年代
  • 大对象直接进入老年代,-XX:PretenureSizeThreshold=1048576配置,因为大对象在新生代发生来回复制的话,影响gc性能
  • 长期存活的对象,比如经过了15次gc后还存活的对象,由-XX:MaxTenuringThreshold=10配置
    • 但是这个年龄是会动态调整的,每次新生代GC后,JVM都会动态调整这个阈值大小,调整的方式是,从年龄为1的所有对象向上累加,直到内存大小大于-XX:TargetSurvivorRatio(默认50%)
    • 例如总共有100MB新生代大小,阈值就是50MB,累加年龄为1的对象,此时10MB,累加年龄为2的对象,此时25MB,累加年龄为3的对象,此时45MB,累加年龄为4的对象,此时55MB>50MB,那么阈值就被设置成4,下次GC时年龄大于等于4的对象会晋升到老年代
    • 算法源码地址:jdk/hotspot/src/share/vm/gc_implementation/shared/ageTable.cpp at jdk8-b120 · openjdk/jdk (github.com)
    • 为什么默认是15?因为对象头里有个age字段,占4个bit位,所以最大就是15,初始值设为最大,然后依靠后面动态调整。那这样的话,小于等于15都可以吧,反正有动态调整,为什么选15呢?因为-XX:MaxTenuringThreshold其实是限定了一个动态调整年龄范围的上限,设为15能让动态调整更为灵活

什么时候full gc?

  • 程序代码中调用System.gc()
  • 当新生代对象需要晋升老年代或大对象直接在老年代开辟内存时,老年代空间不足
  • 空间分配担保失败
  • 元空间或永久代(JDK7以前)空间不足,full gc回收未使用的类(需要这个类没有对象,所以要gc回收)和元数据
  • 执行CMS GC的过程中同时有对象要放入老年代,而此时老年代空间不足(可能是 GC 过程中浮动垃圾过多导致暂时性的空间不足),便会报 Concurrent Mode Failure 错误,并触发 Full GC。
  • G1内存回收速度赶不上内存分配的速度,也会导致Full GC

什么是Concurrent Mode Failure

CMS收集器在工作时,因为用于线程和垃圾回收在并发标记和并发清除阶段是并行的,此时老年代空间不足(例如浮动垃圾过多,用户创建对象频繁,新生代晋升老年代),就会出现Concurrent Mode Failure

出现Concurrent Mode Failure会怎样

触发Full GC