Redis部署-主从模式

目录

单点问题

主从模式

解析主从模式

配置redis主从模式

info replication命令查看复制相关的状态

断开复制关系

安全性

只读

传输延迟

拓扑结构

数据同步psync

replicationid

offset

psync运行流程

全量复制流程

无硬盘模式

部分复制流程

积压缓冲区

实时复制

主从复制的弊端


单点问题

如果某个服务器程序,只有一个节点(只有一个物理服务器来部署这个服务器程序),那么会引发两个严重的问题:可用性问题:如果这个服务器宕机,意味着整个服务就终端了;性能问题:一台服务器往往支持的并发量和性能是非常有限的.

引入分布式系统,主要就是为了解决上述的单点问题.

在分布式系统中,往往希望有多个服务器来部署redis服务,从而构成一个redis集群,此时就可以让这个集群给整个分布式系统中的其他服务,提供更加稳定和高效的数据存储功能.

在分布式系统中,redis的部署方式主要有:主从模式,主从+哨兵模式和集群模式.


主从模式

解析主从模式

在若干个redis节点中,有的是主节点有的是从节点.

假设现在有三个物理服务器(称为是三个节点)分别部署了一个redis-server进程,此时就可以把其中的一个节点作为主节点,另外的两个节点作为从节点.

从节点就要听取主节点的(从节点上的数据跟随主节点变化,从节点的数据要和主节点上的数据保持一致).

本来,在主节点上保存了一堆数据,引入从节点之后,就要把主节点上的数据,复制出来,放到从节点上,后续主节点上对于数据有任何的修改,都会把这样的修改给同步到从节点上.

所以,从节点就相当于是主节点的副本.

需要注意的是,在redis的主从模式中,从节点上的数据,是不允许修改的,从节点只能进行读取数据的操作!!!

之前只是单个redis服务节点,此时这个机器挂了,整个redis就挂了,但当我们引入主从模式之后,整个系统的可用性就大大提高了.因为上述的主从结构,不太可能出现这些redis机器同时宕机的情况.考虑到更高的可用性,我们也可以把这些机器放到不同的机房中.(异地多活)

由于从节点的数据都是和主节点保持一致的,因此客户从主节点读取数据和从从节点读取数据时没有区别的.所以后续如果有客户来读取数据,就可以从上述节点中随机挑选一个节点给这个客户端来提供读取数据的服务.这样,就能大大提高支持的并发量.

准确来说,主从模式,主要是针对读操作进行并发量和可用性的提高,对于写操作,无论是可用性还是并发性,都是非常依赖主节点的,但是主节点就只有一个.(如果主节点搞多个,那么数据的同步会非常麻烦)

在实际的业务场景中,读操作是比写操作要频繁的多的.


配置redis主从模式

配置redis主从模式,首先要启动多个redis服务器,正常来说,每个redis服务器是应该在一个单独的主机上(这才是真正的分布式),但是由于当前学习资源有限,就在同一个云服务器上来运行多个redis-server进程.

在一台机器上运行多个redis-server进程时,要保证这些进程的端口是不可以冲突的.

指定redis-server的端口有两种方式,一种是在启动程序的时候,通过命令行来指定端口号,--port选项;一种是在配置文件中来设定端口.

我们在这里将创建一个主节点和两个从节点.主节点的配置文件不用改变,只需要修改从节点的配置文件即可.

创建一个新目录,将redis的配置文件复制两份到此目录下.

将这两个配置文件中的port改为6380和6381,并且设定damenize为yes(按照后台进程的方式来执行).

配置完成之后,通过命令行的形式来启动这两个redis.


到这里,当前这几个节点并没有构成主从结构,而是各自为政,要想真正成为主从结构,还需要进一步的进行配置.

要想配置成为主从结构,就需要使用slaveof.

1.在配置⽂件中加⼊ slaveof {masterHost} {masterPort} 随 Redis 启动⽣效。
2. 在 redis-server 启动命令时加⼊ --slaveof {masterHost} {masterPort} ⽣效。
3. 直接使⽤ redis 命令:slaveof {masterHost} {masterPort} ⽣效

这里推荐使用修改配置文件的方式,会一直持久生效.

我们配置以6379为主节点,6380和6381为从节点.

在从节点的配置文件中加入slaveof配置项.

修改完配置文件之后,需要重新启动才能生效.

我们要使用kill -9的方式来停止这两个redis-server,这个停止方式是和我们直接运行redis的时候的命令是搭配的.

而如果是使用service redis-server start的方式启动,则必须使用service redis-server stop来进行停止.此时如果使用kill -9的方式停止,这个redis-server进程会自动启动.

当重新启动后,从节点和主节点之间就建立了tcp连接.

主节点这边数据产生的修改,从节点就能立即感知到,就是上述这些tcp连接起到的效果.

此时在从节点中写入数据就会报错!!!


info replication命令查看复制相关的状态

主节点的复制状态信息

从节点的复制状态信息(6380)

offset就表示从节点和主节点之间同步数据的进度.(从节点和主节点之间的数据同步不是瞬间完成的,靠网络传输,所以有延迟)

replid可以认为是主节点的身份标识.


断开复制关系

slaveof命令不仅可以建立复制,还可以在从节点上执行slaveof no one命令来断开与主节点之间的复制关系.

断开复制关系流程包括两步:

1.断开与主节点之间的复制关系.

2.从节点晋升为主节点.

从节点断开复制后并不会抛弃原有的数据,只是无法在获取主节点上的数据变化.

通过slaveof命令还可以实现切换主节点的操作,,将当前从节点的数据源切换到另⼀个主节点。执⾏

slaveof {newMasterIp} {newMasterPort} 命令即可。

切主操作的主要流程:

  1. 断开与旧主节点的复制关系.
  2. 与新主节点建立复制关系
  3. 删除从节点上当前的所有数据
  4. 与新主节点进行复制操作

安全性

对于数据⽐较重要的节点,主节点会通过设置 requirepass 参数进⾏密码验证,这时所有的客⼾
端访问必须使⽤ auth 命令实⾏校验。从节点与主节点的复制连接是通过⼀个特殊标识的客⼾端来完成,因此需要配置从节点的masterauth 参数与主节点密码保持⼀致,这样从节点才可以正确地连接到主节点并发起复制流程。

只读

默认情况下,从节点使⽤ slave-read-only=yes 配置为只读模式。由于复制只能从主节点到从节
点,对于从节点的任何修改主节点都⽆法感知,修改从节点会造成主从数据不⼀致。所以建议线上不要修改从节点的只读模式。

传输延迟

主从节点⼀般部署在不同机器上,复制时的⽹络延迟就成为需要考虑的问题,Redis 为我们提供
了 repl-disable-tcp-nodelay 参数⽤于控制是否关闭 TCP_NODELAY,默认为 no,即开启 tcp_nodelay 功能,说明如下:
• 当关闭时,主节点产⽣的命令数据⽆论⼤⼩都会及时地发送给从节点,这样主从之间延迟会变⼩,但增加了⽹络带宽的消耗。适⽤于主从之间的⽹络环境良好的场景,如同机房部署。
• 当开启时,主节点会合并较⼩的 TCP 数据包从⽽节省带宽。默认发送时间间隔取决于 Linux 的内
核,⼀般默认为 40 毫秒。这种配置节省了带宽但增⼤主从之间的延迟。适⽤于主从⽹络环境复杂
的场景,如跨机房部署.


拓扑结构

主从模式的拓扑结构描述了若干个节点之间,是按照什么样的方式来进行组织连接的.

常见的拓扑结构有:一主一从拓扑,一主多从拓扑和树形拓扑.

一主一从

如果写数据的请求太多,此时也会给主节点造成很大压力,可以通过关闭主节点的AOF来缓解主节点的压力,只在从节点这里开启aof.

但是这种设定方式,有一个严重缺陷:主节点一旦宕机,不能让它自动重启,如果自动重启,此时没有aof文件,就会丢失数据,进一步的主从同步会把从节点的数据也给删除.

所以,当主节点挂了之后,就需要让主节点从从节点这里获取到aof的文件,在启动.

一主多从

扁平化结构,此结构一旦从节点个数增加很多,那么主节点同步一条数据就需要传输多次,可能会消耗大量的网络带宽.

树形结构

主节点同步数据不会消耗那么多的网络带宽了,但是一旦数据进行同步,同步的延迟是比较长的,因为要要进行多次网络传输.


复制过程

注意的是其中同步数据集这步操作,对于首次建立复制的场景,主节点会把当前所持有的所有数据全部发送给从节点,这步的操作耗时最长,所以又划分为两种情况:全量复制和部分复制.


数据同步psync

redis使用psync命令完成主从数据同步,同步过程分为:全量复制和部分复制.

psync不需要我们手动执行,redis服务器会在建立好主从复制关系之后,自动执行psync.

从节点负责执行psync,从节点从主节点这边拉取数据.

psync的命令格式:PSYNC replicationid offset.

如果replicationid设为?,offset设为-1,此时就是在尝试进行全量复制.

如果replicationid和offset设置了具体的值,则是尝试进行部分复制.

replicationid

replication是复制的意思,这个就表示是一个复制id.

replicationid是由主节点生成的,主节点启动的时候就会生成,从节点晋升为主节点的时候,也会生成.

(即使是同一个主节点,每次重启的时候,生成的replicationid都是不同的).

从节点和主节点建立了复制关系,从节点就会从主节点这里获取到replicationid.

一般replid2用不上.假设此时有一个主节点A和一个从节点B.A负责生成replid,B负责获取A的replid.

如果A和B在通信过程中出现了网络抖动,B可能会认为A挂了,此时B就会自己成为主节点,(给自己也生成一个replid),但是B也会保存之前旧的replid,这个旧的replid就会保存在replid2中,方便后续网络稳定之后,B重新与A建立复制关系.在主从模式下,上述过程需要手动干预,而哨兵模式下可以自动完成.

offset

表示偏移量.主节点和从节点上都会维护偏移量(整数).

主节点的偏移量会根据收到的修改命令占据的字节数来将offset进行累加.

从节点的偏移量就描述了,当前从节点数据同步的进度.

如果主节点的offset和从节点的offset一样,就说明从节点的数据和主节点的数据一致了.

从节点每秒钟会上报自身的复制偏移量给主节点.

综上,replicationid和offset就描述出一个数据集,就可以表示从节点从哪里复制的数据,复制到哪里了!!!!

psync运行流程

  • 从节点发送psync命令给主节点,replid和offset的默认值分别是?和-1.
  • 主节点根据psync的参数和自身的情况决定响应结果.
  • 如果回复+FULLRESYNC replid offset,则从节点需要进行全量复制流程
  • 如果回复+CONTINUE,从节点进行部分复制流程
  • 如果回复-ERR,说明redis主节点版本过低,不支持psync命令.此时从节点可以使用sync命令进行全量复制.

注意:psync一般不需要进行手动执行,redis会在主从模式下自动调用执行.

sync命令(1.0.0版本就有),会阻塞redis-server处理其他的请求,而psync命令则不会.


全量复制流程

何时进行全量复制:首次和主节点进行数据同步或者主节点不方便进行部分复制的时候.

1)从节点发送 psync 命令给主节点进⾏数据同步,由于是第⼀次进⾏复制,从节点没有主节点的运⾏ ID 和复制偏移量,所以发送 psync ? -1。
2)主节点根据命令,解析出要进⾏全量复制,回复 +FULLRESYNC 响应。
3)从节点接收主节点的运⾏信息进⾏保存。
4)主节点执⾏ bgsave 进⾏ RDB ⽂件的持久化。
5)主节点发送 RDB ⽂件给从节点,从节点保存 RDB 数据到本地硬盘。
6)主节点将从⽣成 RDB 到接收完成期间执⾏的写命令,写⼊缓冲区中,等从节点保存完 RDB ⽂件 后,主节点再将缓冲区内的数据补发给从节点,补发的数据仍然按照 rdb 的⼆进制格式追加写⼊到收 到的 rdb ⽂件中. 保持主从⼀致性。
7)从节点清空⾃⾝原有旧数据。
8)从节点加载 RDB ⽂件得到与主节点⼀致的数据。
9)如果从节点加载 RDB 完成之后,并且开启了 AOF 持久化功能,它会进⾏ bgrewrite 操作,得到最近的 AOF ⽂件.
注意:在主节点生成rdb文件和传输rdb文件的过程中,还会继续收到很多新的修改操作.新修改的数据也必须要同步给从节点,所以当从节点接收完从主节点发送来的rdb文件之后,主节点还要把新修改的操作也要发给从节点.

无硬盘模式

Redis从2.8.18版本开始支持无硬盘模式的全量复制.
主节点生成的rdb二进制数据不在直接保存在文件中了,而是直接进行网络传输给从节点,这样就省下了一系列的读硬盘和写硬盘的操作.
从节点之前也是先把收到的rdb数据写入到硬盘中,然后再加载到内存,在此模式下,也可以直接把收到的数据进行加载.
需要注意的是虽然引入了无硬盘模式,但是整个全量复制的过程仍然是比较重量比较耗时的.网络传输是大头!!!

部分复制流程

全量复制虽然稳妥,但是比较低效,开销比较大.

有些时候,从节点本身已经持有了主节点的绝大部分数据,就不需要再进行全量复制了.

比如出现网络抖动,主节点最近修改的数据无法同步给从节点,进一步可能从节点已经感知不到主节点了.但是网络抖动一般是暂时的,网络恢复之后,此时就可以让从节点和主节点重新建立连接.重建连接之后,再进行数据的同步,具体是全量复制还是部分部分复制还是要看具体的交互流程.

psync带有具体的参数值,主节点就要根据参数和当前自身情况进行判定,当前这次是按照全量复制合适还是部分复制合适.

1)当主从节点之间出现⽹络中断时,如果超过 repl-timeout 时间,主节点会认为从节点故障并中断复制连接。
2)主从连接中断期间主节点依然响应命令,但这些复制命令都因⽹络中断⽆法及时发送给从节点,所以暂时将这些命令滞留在复制积压缓冲区中。
3)当主从节点⽹络恢复后,从节点再次连上主节点。
4)从节点将之前保存的 replicationId 和 复制偏移量作为 psync 的参数发送给主节点,请求进⾏部分复制。
5)主节点接到 psync 请求后,进⾏必要的验证。随后根据 offset 去复制积压缓冲区查找合适的数据,并响应 +CONTINUE 给从节点。如果数据在积压缓冲区内能找到,说明从节点拉下的进度不多,就进行部分复制;如果确实当前从节点拉下的进度已经超出积压缓冲区的范围了,就只能进行全量复制了.
6)主节点将需要从节点同步的数据发送给从节点,最终完成⼀致性.
所以部分复制可以理解为全量复制的特殊情况,是一种全量复制的优化手段!!!!

积压缓冲区

积压缓冲区是保存在主节点上的⼀个固定⻓度的队列,默认⼤⼩为 1MB,当主节点有连接的从节 点(slave)时被创建,这时主节点(master)响应写命令时,不但会把命令发送给从节点,还会写⼊复制积压缓冲区.由于缓冲区本质上是先进先出的定⻓队列,所以能实现保存最近已复制数据的功能,⽤于部分复制和复制命令丢失的数据补救.

积压缓冲区的信息可以在主节点的info replication中查看.

图中四项分别表示:开启复制缓冲区,缓冲区最大长度,起始偏移量(可用于计算当前缓冲区可用范围),已保存数据的有效长度.


实时复制

当从节点和主节点执行完全量复制或者部分复制之后,这一时刻从节点和主节点的数据就一致了.但是之后,主节点还会收到源源不断的新的修改数据的请求,主节点上的数据也会随之改变,此时就需要能将这些数据也同步给主节点.

所以从节点和主节点之间会建立tcp长连接,然后主节点会把自己收到的修改数据的请求,通过tcp连接发给从节点,从节点会根据收到这些修改请求来修改内存中的数据,来保持和主节点的数据一致.

在进行实时复制的时候,需要保证连接处于可用状态,在此处使用心跳包机制来实现.

主节点:默认每隔10s给从节点发送一个ping命令,从节点收到返回pong.

从节点:默认每隔1s就给主节点发起一个特定的请求,就会上报当前从节点复制的进度(offset).

这里的数值都可以可以配置.


主从复制的弊端

从节点和主节点之间断开连接,有两种情况:

1.从节点主动和主节点之间断开连接.从节点执行slaveof no one.此时从节点能够自动晋升为主节点.

2.主节点崩溃.这个时候,从节点不会自动晋升为主节点,必须通过人工干预的方式,恢复主节点.

当主节点挂了之后,从节点无法感知到主节点,就迷茫了.虽然能够提供读操作,但是此时从节点不能自动升级成为主节点,此时就只能程序员/运维来手工恢复主节点.这一过程是非常繁琐的!!!

Redis哨兵就实现了对挂了的主节点自动进行替换的功能,省去了人工手动恢复的过程.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/208033.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

CCC数字车钥匙(八)——BLE配对相关字段

2.1 配对连接协议 2.1.3 所有者配对广播 对于所有者配对,仅支持Legacy LE 1M PHY。ADV_IND需要按照Section 2.3.1.1 Volume 6 Part B。 事件类型:无指向可连接和可扫描。 ADV_IND中包含广播地址和广播数据,如下所示,其中广播地址…

iris+vue上传到本地存储【go/iris】

iris部分 //main.go package mainimport ("fmt""io""net/http""os" )//上传视频文件部分 func uploadHandler_video(w http.ResponseWriter, r *http.Request) {// 解析上传的文件err : r.ParseMultipartForm(10 << 20) // 设置…

多线程(初阶五:wait和notify)

目录 一、概念 二、用法 &#xff08;1&#xff09;举个栗子&#xff1a; &#xff08;2&#xff09;wait和notify的使用 1、没有上锁的wait 2、当一个线程被wait&#xff0c;但没有其他线程notify来释放这个wait 3、两个线程&#xff0c;有一个线程wait&#xff0c;有一…

目标检测——Mask R-CNN算法解读

论文&#xff1a;Mask R-CNN 作者&#xff1a;Kaiming He Georgia Gkioxari Piotr Dollar Ross Girshick 链接&#xff1a;https://arxiv.org/abs/1703.06870 代码&#xff1a;https://github.com/facebookresearch/Detectron R-CNN系列其他文章&#xff1a; R-CNN算法解读SPP…

Leecode 【一】

环形链表: 给你一个链表的头节点 head &#xff0c;判断链表中是否有环。 如果链表中有某个节点&#xff0c;可以通过连续跟踪 next 指针再次到达&#xff0c;则链表中存在环。 为了表示给定链表中的环&#xff0c;评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置&…

教你用AI做治愈系风景动态视频

这几天刚发布AI小红薯商单变现案例库&#xff0c;同学们私信表示案例库启发很大&#xff0c;很有价值&#xff0c;只是能不能再多来点手把手式的实操教程&#xff01; 这是个好需求&#xff0c;没问题~&#xff0c;今天就手把手地给大家分享一个近半年来&#xff0c;在各大平台…

优思学院:六西格玛项目中什么是顾客之声?

让客户的声音成就您的成功&#xff01; 顾客之声(Voice of customer-VOC)是六西格玛项目中的一个重要概念&#xff0c;指的是从顾客的角度和需求出发&#xff0c;通过收集和分析顾客的反馈和意见&#xff0c;以了解他们对产品或服务的期望、满意度和不满意之处。顾客之声的目的…

分享几个可以免费使用GPT工具

1. 国产可以使用GPT3.5和4.0的网站&#xff0c;每日有免费的使用额度&#xff0c;响应速度&#xff0c;注册时不用使用手机号&#xff0c;等个人信息&#xff0c;注重用户隐私&#xff0c;好评&#xff01; 一个好用的ChatGPT系统 &#xff0c;可以免费使用3.5 和 4.0https://…

springboot+java校园自助洗衣机预约系统的分析与设计ssm+jsp

洗衣服是每个人都必须做的事情&#xff0c;而洗衣机更成为了人们常见的电器&#xff0c;但是单个洗衣机价格不菲&#xff0c;如果每人都买&#xff0c;就会造成资源的冗余。所有就出现了公用设备&#xff0c;随着时代的发展&#xff0c;很多公用都开始向着无人看守的自助模式经…

ChatGLM2详细安装部署(chatglm2大模型安装步骤三)

ChatGLM2安装部署 1.服务器配置 服务器系统:Centos7.9 x64 显卡:RTX3090 (24G) 虚拟环境:Miniconda3 2.安装部署 2.1 ChatGLM2下载 输入命令:git clone https://github.moeyy.xyz/https://github.com/THUDM/ChatGLM2-6B.git 输入命令:cd ChatGLM2-6B 注:https://g…

【note: This is an issue with the package mentioned above, not pip.】

安装gym时出现问题&#xff0c;note: This is an issue with the package mentioned above, not pip. 报错原因&#xff1a; 缺失了某些依赖模块&#xff0c;所以安装报错。 Collecting package metadata (current_repodata.json): done Solving environment: failed with in…

2021年11月10日 Go生态洞察:Twelve Years of Go

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

关于电脑提示vcruntime140_1.dll无法继续执行代码的解决办法

vcruntime140_1.dll是Visual C运行时库的一个组成部分&#xff0c;它包含了大量用于支持C应用程序运行时的功能。这个文件通常在开发和使用C程序时被调用&#xff0c;特别是在使用Microsoft Visual Studio进行开发时。vcruntime140_1.dll文件丢失或损坏会导致C程序无法正常运行…

python 使用reportlab打造29页图文并茂pdf(全网reportlab最强pdf自动化生成代码)

python 使用reportlab打造29页图文并茂pdf(全网reportlab最强pdf自动化生成代码&#xff09; 这次项目所使用的代码如果同志们可以灵活使用&#xff0c;基本上可以解决百分之九十以上的pdf模板自动化生成。 最近博主&#xff0c;做了一个项目&#xff0c;使用reportlab制作pd…

JDK10新特性

目录 传送门一、局部变量类型推断1、概念1.1、简单局部变量推断1.2、自定义对象的推断 2、可以使用的场景3、不能使用的场景4、注意事项 二、垃圾回收器的优化1、前置知识1.1、10种垃圾回收器1.2、分代与分区1.3、10种垃圾回收器小总结 2、优化点 三、新增API1、集合新方法copy…

多线程(初阶六:单例模式)

一、单例模式的简单介绍 二、饿汉模式 三、懒汉模式 四、饿汉模式和懒汉模式的线程安全问题分析 一、单例模式的简单介绍 单例模式是一种设计模式&#xff0c;其中设计模式是软性的规定&#xff0c;与它关联的框架是硬性的规定&#xff0c;这些都是大佬已经设计好了的&…

【工作记录】spider-flow使用插件连接并操作mongodb数据库

前言 前面说过&#xff0c;spider-flow有着非常优秀的插件机制&#xff0c;可以通过插件实现功能的扩展。前面有小伙伴问到mongodb的集成使用&#xff0c;本文就来梳理下spider-flow中使用mongodb插件的过程&#xff0c;其实非常简单。 PS: spider-flow的作者已经实现了一些常…

线程池技术总结

1. 线程池解决了什么问题 线程池是集中管理线程的&#xff0c;以实现线程的重用&#xff0c;降低资源消耗&#xff0c;提高响应速度&#xff0c;提高线程的可管理性等。线程用于执行异步任务&#xff0c;单个的线程既是工作单元也是执行机制&#xff0c;从JDK1.5开始&#xff…

文心一言 VS 讯飞星火 VS chatgpt (147)-- 算法导论12.2 2题

二、用go语言&#xff0c;写出 TREE-MINIMUM 和 TREE-MAXIMUM 的递归版本。 灵捷3.5 。 文心一言&#xff1a; 以下是用 Go 语言编写的树的最大值和最小值的递归版本代码&#xff1a; package main import ( "fmt" ) type TreeNode struct { Val int Le…

哈希表 LeetCode 2661. 找出叠涂元素

2661. 找出叠涂元素 给你一个下标从 0 开始的整数数组 arr 和一个 m x n 的整数 矩阵 mat 。arr 和 mat 都包含范围 [1&#xff0c;m * n] 内的 所有 整数。 从下标 0 开始遍历 arr 中的每个下标 i &#xff0c;并将包含整数 arr[i] 的 mat 单元格涂色。 请你找出 arr 中在 …
最新文章