Java中高级核心知识全面解析(3),美团Java面试算法题

  1. 主服务器 需要将自己生成的 RDB 文件 发送给从服务器,这个发送操作会 消耗 主服务器 大量的网络资源 (带宽和流量),并对主服务器响应命令请求的时间产生影响;
  2. 接收到 RDB 文件的 从服务器 需要载入主服务器发来的 RBD 文件,并且在载入期间,从服务器会因为阻塞而没办法处理命令请求

特别是当出现 断线重复制 的情况是时,为了让从服务器补足断线时确实的那一小部分数据,却要执行一次如此耗资源的 SYNC 命令,显然是不合理的。

③、PSYNC 命令的引入

所以在 Redis 2.8 中引入了 PSYNC 命令来代替 SYNC ,它具有两种模式:

  1. 全量复制: 用于初次复制或其他无法进行部分复制的情况,将主节点中的所有数据都发送给从节点,是一个非常重型的操作;
  2. 部分复制: 用于网络中断等情况后的复制,只将 中断期间主节点执行的写命令 发送给从节点,与全量复制相比更加高效。需要注意 的是,如果网络中断时间过长,导致主节点没有能够完整地保存中断期间执行的写命令,则无法进行部分复制,仍使用全量复制;

部分复制的原理主要是靠主从节点分别维护一个 复制偏移量,有了这个偏移量之后断线重连之后一比较,之后就可以仅仅把从服务器断线之后确实的这部分数据给补回来了。

3.Redis Sentinel 哨兵

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

  • 哨兵节点: 哨兵系统由一个或多个哨兵节点组成,哨兵节点是特殊的 Redis 节点,不存储数据;
  • 数据节点: 主节点和从节点都是数据节点;

在复制的基础上,哨兵实现了 自动化的故障恢复 功能,下方是官方对于哨兵功能的描述:

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

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

1)快速体验

①、第一步:创建主从节点配置文件并启动

正确安装好 Redis 之后,我们去到 Redis 的安装目录 (mac 默认在 /usr/local/ ),找到 redis.conf文件复制三份分别命名为 redis-master.conf / redis-slave1.conf / redis-slave2.conf ,分别作为1 个主节点和2 个从节点的配置文件 (下图演示了我本机的 redis.conf 文件的位置)

打开可以看到这个 .conf 后缀的文件里面有很多说明的内容,全部删除然后分别改成下面的样子:

#redis-master.conf 
port 6379 
daemonize yes 
logfile "6379.log" 
dbfilename "dump-6379.rdb" 

#redis-slave1.conf 
port 6380 
daemonize yes 
logfile "6380.log" 
dbfilename "dump-6380.rdb" 
slaveof 127.0.0.1 6379 

#redis-slave2.conf 
port 6381 
daemonize yes 
logfile "6381.log" 
dbfilename "dump-6381.rdb" 
slaveof 127.0.0.1 6379

然后我们可以执行 redis-server <config file path> 来根据配置文件启动不同的 Redis 实例,依次启动主从节点:

redis-server /usr/local/redis-5.0.3/redis-master.conf 
redis-server /usr/local/redis-5.0.3/redis-slave1.conf 
redis-server /usr/local/redis-5.0.3/redis-slave2.conf

节点启动后,我们执行 redis-cli 默认连接到我们端口为 6379 的主节点执行 info Replication 检查一下主从状态是否正常:(可以看到下方正确地显示了两个从节点)

②、第二步:创建哨兵节点配置文件并启动

按照上面同样的方法,我们给哨兵节点也创建三个配置文件。(哨兵节点本质上是特殊的 Redis 节点,所以配置几乎没什么差别,只是在端口上做区分就好)

# redis-sentinel-1.conf 
port 26379 
daemonize yes 
logfile "26379.log" 
sentinel monitor mymaster 127.0.0.1 6379 2 

# redis-sentinel-2.conf 
port 26380 
daemonize yes 
logfile "26380.log" 
sentinel monitor mymaster 127.0.0.1 6379 2 

# redis-sentinel-3.conf 
port 26381 
daemonize yes 
logfile "26381.log" 
sentinel monitor mymaster 127.0.0.1 6379 2

其中, sentinel monitor mymaster 127.0.0.1 6379 2 配置的含义是:该哨兵节点监控 127.0.0.1:6379 这个主节点,该主节点的名称是 mymaster ,最后的 2 的含义与主节点的故障判定有关:至少需要 2 个哨兵节点同意,才能判定主节点故障并进行故障转移。

执行下方命令将哨兵节点启动起来:

redis-server /usr/local/redis-5.0.3/redis-sentinel-1.conf --sentinel 
redis-server /usr/local/redis-5.0.3/redis-sentinel-2.conf --sentinel 
redis-server /usr/local/redis-5.0.3/redis-sentinel-3.conf --sentinel

使用 redis-cil 工具连接哨兵节点,并执行 info Sentinel 命令来查看是否已经在监视主节点了:

# 连接端口为 26379 的 Redis 节点 
➜ ~ redis-cli -p 26379 
127.0.0.1:26379> info Sentinel 
# Sentinel 
sentinel_masters:1 
sentinel_tilt:0 
sentinel_running_scripts:0 
sentinel_scripts_queue_length:0 
sentinel_simulate_failure_flags:0 master0:name=mymaster,status=ok,address=127.0.0.1:6379,slaves=2,sentinels=3

此时你打开刚才写好的哨兵配置文件,你还会发现出现了一些变化:

③、第三步:演示故障转移

首先,我们使用 kill -9 命令来杀掉主节点,同时 在哨兵节点中执行 info Sentinel 命令来观察故障节点的过程:

➜ ~ ps aux | grep 6379 
longtao 	74529 	0.3 	0.0 	4346936 	2132 	?? 	Ss 	10:30上午 	0:03.09 
redis-server *:26379 [sentinel] 
longtao 	73541 	0.2 	0.0 	4348072 	2292 	?? 	Ss 	10:18上午 	0:04.79 
redis-server *:6379 
longtao 	75521 	0.0 	0.0 	4286728 	728 s008 	S+ 	10:39上午 	0:00.00 
grep --color=auto --exclude-dir=.bzr --exclude-dir=CVS --exclude-dir=.git -- exclude-dir=.hg --exclude-dir=.svn 6379 
longtao 	74836 	0.0 	0.0 	4289844 	944 s006 	S+ 	10:32上午 	0:00.01 
redis-cli -p 26379 
➜ ~ kill -9 73541

如果 刚杀掉瞬间 在哨兵节点中执行 info 命令来查看,会发现主节点还没有切换过来,因为哨兵发现主节点故障并转移需要一段时间:

# 第一时间查看哨兵节点发现并未转移,还在 6379 端口 
127.0.0.1:26379> info Sentinel 
# Sentinel 
sentinel_masters:1 
sentinel_tilt:0 sentinel_running_scripts:0 
sentinel_scripts_queue_length:0 
sentinel_simulate_failure_flags:0 
master0:name=mymaster,status=ok,address=127.0.0.1:6379,slaves=2,sentinels=3

一段时间之后你再执行 info 命令,查看,你就会发现主节点已经切换成了 6381 端口的从节点:

# 过一段时间之后在执行,发现已经切换了 6381 端口 
127.0.0.1:26379> info Sentinel 
# Sentinel 
sentinel_masters:1 
sentinel_tilt:0 
sentinel_running_scripts:0 
sentinel_scripts_queue_length:0 
sentinel_simulate_failure_flags:0 
master0:name=mymaster,status=ok,address=127.0.0.1:6381,slaves=2,sentinels=3

但同时还可以发现,哨兵节点认为新的主节点仍然有两个从节点 (上方 slaves=2),这是因为哨兵在将 6381 切换成主节点的同时,将 6379 节点置为其从节点。虽然 6379 从节点已经挂掉,但是由于 哨兵并不会对从节点进行客观下线,因此认为该从节点一直存在。当 6379 节点重新启动后,会自动变成6381 节点的从节点。

另外,在故障转移的阶段,哨兵和主从节点的配置文件都会被改写:

  • 对于主从节点: 主要是 slaveof 配置的变化,新的主节点没有了 slaveof 配置,其从节点则slaveof 新的主节点。
  • 对于哨兵节点: 除了主从节点信息的变化,纪元(epoch) (记录当前集群状态的参数) 也会变化,纪元相关的参数都 +1 了。

2)客户端访问哨兵系统代码演示

上面我们在 快速体验 中主要感受到了服务端自己对于当前主从节点的自动化治理,下面我们以 Java 代码为例,来演示一下客户端如何访问我们的哨兵系统:

public static void testSentinel() throws Exception { 
	String masterName = "mymaster"; 
	Set<String> sentinels = new HashSet<>(); 
	sentinels.add("127.0.0.1:26379"); 
	sentinels.add("127.0.0.1:26380"); 
	sentinels.add("127.0.0.1:26381"); 

	// 初始化过程做了很多工作 
	JedisSentinelPool pool = new JedisSentinelPool(masterName, sentinels); 
	Jedis jedis = pool.getResource(); 
	jedis.set("key1", "value1"); 
	pool.close(); 
}

①、客户端原理

Jedis 客户端对哨兵提供了很好的支持。如上述代码所示,我们只需要向 Jedis 提供哨兵节点集合和 masterName ,构造 JedisSentinelPool 对象,然后便可以像使用普通 Redis 连接池一样来使用了:通过 pool.getResource() 获取连接,执行具体的命令。

在整个过程中,我们的代码不需要显式的指定主节点的地址,就可以连接到主节点;代码中对故障转移没有任何体现,就可以在哨兵完成故障转移后自动的切换主节点。之所以可以做到这一点,是因为在 JedisSentinelPool 的构造器中,进行了相关的工作;主要包括以下两点:

  1. 遍历哨兵节点,获取主节点信息: 遍历哨兵节点,通过其中一个哨兵节点 + masterName 获得主节点的信息;该功能是通过调用哨兵节点的 sentinel get-master-addr-by-name 命令实现;
  2. 增加对哨兵的监听: 这样当发生故障转移时,客户端便可以收到哨兵的通知,从而完成主节点的切换。具体做法是:利用 Redis 提供的 发布订阅 功能,为每一个哨兵节点开启一个单独的线程,订阅哨兵节点的 + switch-master 频道,当收到消息时,重新初始化连接池。

3)新的主服务器是怎样被挑选出来的?

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

简单来说 Sentinel 使用以下规则来选择新的主服务器:

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

4.Redis 集群

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

1)基本原理

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

再结合集群的配置信息就能够知道这个 key 值应该存储在哪一个具体的 Redis 节点中,如果不属于自己管,那么就会使用一个特殊的 MOVED 命令来进行一个跳转,告诉客户端去连接这个节点以获取数据:

GET x 
-MOVED 3999 127.0.0.1:6381

MOVED 指令第一个参数 3999 是 key 对应的槽位编号,后面是目标节点地址, MOVED 命令前面有一个减号,表示这是一个错误的消息。客户端在收到 MOVED 指令后,就立即纠正本地的 槽位映射表,那么下一次再访问 key 时就能够到正确的地方去获取了。

2)集群的主要作用

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

3)快速体验

①、第一步:创建集群节点配置文件

首先我们找一个地方创建一个名为 redis-cluster 的目录:

mkdir -p ~/Desktop/redis-cluster

然后按照上面的方法,创建六个配置文件,分别命名为:redis_7000.conf / redis_7001.conf … redis_7005.conf,然后根据不同的端口号修改对应的端口值就好了:

# 后台执行 
daemonize yes 
# 端口号 
port 7000 
# 为每一个集群节点指定一个 pid_file 
pidfile ~/Desktop/redis-cluster/redis_7000.pid 
# 启动集群模式 
cluster-enabled yes 
# 每一个集群节点都有一个配置文件,这个文件是不能手动编辑的。确保每一个集群节点的配置文件不通 
cluster-config-file nodes-7000.conf 
# 集群节点的超时时间,单位:ms,超时后集群会认为该节点失败 
cluster-node-timeout 5000 
# 最后将 appendonly 改成 yes(AOF 持久化) 
appendonly yes

记得把对应上述配置文件中根端口对应的配置都修改掉 (port/ pidfile/ cluster-config-file)。

②、第二步:分别启动 6 个 Redis 实例
redis-server ~/Desktop/redis-cluster/redis_7000.conf 
redis-server ~/Desktop/redis-cluster/redis_7001.conf 
redis-server ~/Desktop/redis-cluster/redis_7002.conf 
redis-server ~/Desktop/redis-cluster/redis_7003.conf 
redis-server ~/Desktop/redis-cluster/redis_7004.conf 
redis-server ~/Desktop/redis-cluster/redis_7005.conf

然后执行 ps -ef | grep redis 查看是否启动成功:

可以看到 6 个 Redis 节点都以集群的方式成功启动了,但是现在每个节点还处于独立的状态,也就是说它们每一个都各自成了一个集群,还没有互相联系起来,我们需要手动地把他们之间建立起联系。

③、第三步:建立集群

执行下列命令:

redis-cli --cluster create --cluster-replicas 1 127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005

  • 这里稍微解释一下这个 --replicas 1 的意思是:我们希望为集群中的每个主节点创建一个从节点。

观察控制台输出:

看到 [OK] 的信息之后,就表示集群已经搭建成功了,可以看到,这里我们正确地创建了三主三从的集群。

④、第四步:验证集群

我们先使用 redic-cli 任意连接一个节点:

redis-cli -c -h 127.0.0.1 -p 7000 
127.0.0.1:7000>

  • -c 表示集群模式;-h 指定 ip 地址; -p 指定端口。

然后随便 set 一些值观察控制台输入:

127.0.0.1:7000> SET name wmyskxz 
-> Redirected to slot [5798] located at 127.0.0.1:7001 
OK
127.0.0.1:7001>

可以看到这里 Redis 自动帮我们进行了Redirected操作跳转到了7001这个实例上。

我们再使用 cluster info (查看集群信息) 和 cluster nodes (查看节点列表) 来分别看看:(任意节点输入均可)

127.0.0.1:7001> CLUSTER INFO 
cluster_state:ok 
cluster_slots_assigned:16384 
cluster_slots_ok:16384 
cluster_slots_pfail:0 
cluster_slots_fail:0 
cluster_known_nodes:6 
cluster_size:3 
cluster_current_epoch:6 
cluster_my_epoch:2 
cluster_stats_messages_ping_sent:1365 
cluster_stats_messages_pong_sent:1358 
cluster_stats_messages_meet_sent:4 
cluster_stats_messages_sent:2727 
cluster_stats_messages_ping_received:1357 
cluster_stats_messages_pong_received:1369 
cluster_stats_messages_meet_received:1 
cluster_stats_messages_received:2727 

127.0.0.1:7001> CLUSTER NODES 
56a04742f36c6e84968cae871cd438935081e86f 127.0.0.1:7003@17003 slave 
4ec8c022e9d546c9b51deb9d85f6cf867bf73db6 0 1584428884000 4 connected 
4ec8c022e9d546c9b51deb9d85f6cf867bf73db6 127.0.0.1:7000@17000 master - 0 
1584428884000 1 connected 0-5460 
e2539c4398b8258d3f9ffa714bd778da107cb2cd 127.0.0.1:7005@17005 slave 
a3406db9ae7144d17eb7df5bffe8b70bb5dd06b8 0 1584428885222 6 connected 
d31cd1f423ab1e1849cac01ae927e4b6950f55d9 127.0.0.1:7004@17004 slave 
236cefaa9cdc295bc60a5bd1aed6a7152d4f384d 0 1584428884209 5 connected 
236cefaa9cdc295bc60a5bd1aed6a7152d4f384d 127.0.0.1:7001@17001 myself,master - 0 
1584428882000 2 connected 5461-10922 
a3406db9ae7144d17eb7df5bffe8b70bb5dd06b8 127.0.0.1:7002@17002 master - 0 1584428884000 3 connected 10923-16383 
127.0.0.1:7001>

5)数据分区方案简析

最后

给大家送一个小福利

资料附送高清脑图,高清知识点讲解教程,以及一些面试真题及答案解析。送给需要的提升技术、准备面试跳槽、自身职业规划迷茫的朋友们。

CodeChina开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频】

7.0.0.1:7002@17002 master - 0 1584428884000 3 connected 10923-16383
127.0.0.1:7001>


### 5)数据分区方案简析



### 最后

给大家送一个小福利

[外链图片转存中...(img-chX9CJZ9-1630376720787)]

资料附送高清脑图,高清知识点讲解教程,以及一些面试真题及答案解析。送给需要的提升技术、准备面试跳槽、自身职业规划迷茫的朋友们。

**[CodeChina开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频】](https://codechina.csdn.net/m0_60958482/java-p7)**

[外链图片转存中...(img-PiVa0CBj-1630376720788)]

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值