Redis三种集群模式介绍及搭建

1.   redis集群简介

1.1   集群的概念

所谓的集群,就是通过添加服务器的数量,提供相同的服务,从而让服务器达到一个稳定、高效的状态。

redis集群就是多个redis节点一起工作的模式。它没有代理节点和中心节点,各个节点平等。

1.2  使用redis集群的必要性

(1)单个redis存在不稳定性。当redis服务宕机了,就没有可用的服务了。

(2)单个redis的读写能力是有限的。

总结:redis集群是为了强化redis的读写能力。

1.3  redis集群提供了以下两个好处

1、将数据自动切分(split)到多个节点

2、当集群中的某一个节点故障时,redis还可以继续处理客户端的请求。

1.4  集群中的主从复制

集群中的每个节点都有1个至N个复制品,其中一个为主节点,其余的为从节点,如果主节点下线了,集群就会把这个主节点的一个从节点设置为新的主节点,继续工作。这样集群就不会因为一个主节点的下线而无法正常工作。

注意:

1、如果某一个主节点和他所有的从节点都下线的话,redis集群就会停止工作了。redis集群不保证数据的强一致性,在特定的情况下,redis集群会丢失已经被执行过的写命令

2、使用异步复制(asynchronous replication)是redis 集群可能会丢失写命令的其中一个原因,有时候由于网络原因,如果网络断开时间太长,redis集群就会启用新的主节点,之前发给主节点的数据就会丢失。

以下情况可能导致写操作丢失:

  • 过期 key 被清理
  • 最大内存不足,导致 Redis 自动清理部分 key 以节省空间
  • 主库故障后自动重启,从库自动同步
  • 单独的主备方案,网络不稳定触发哨兵的自动切换主从节点,切换期间会有数据丢失

1.5  redis集群中不可缺少的一点-持久化:

持久化的话是Redis高可用中比较重要的一个环节,因为Redis数据在内存的特性,持久化必须得有,持久化是有两种方式的。

RDB:RDB 持久化机制,是对Redis中的数据执行周期性的持久化。

AOF:AOF机制对每条写入命令作为日志,以append-only的模式写入一个日志文件中,因为这个模式是只追加的方式,所以没有任何磁盘寻址的开销,所以很快,有点像Mysql中的二进制日志 binlog

两种方式都可以把Redis内存中的数据持久化到磁盘上,然后再将这些数据备份到别的地方去,RDB更适合做冷备,AOF更适合做热备,比如杭州的某电商公司有这两个数据,我备份一份到我杭州的节点,再备份一个到上海的,就算发生无法避免的自然灾害,也不会两个地方都一起挂吧,这灾备也就是异地容灾。

两种机制全部开启的时候,Redis在重启的时候会默认使用AOF去重新构建数据,因为AOF的数据是比RDB更完整。

优缺点:

RDB

优点:

他会生成多个数据文件,每个数据文件分别都代表了某一时刻Redis里面的数据,这种方式,有没有觉得很适合做冷备,完整的数据运维设置定时任务,定时同步到远端的服务器,比如阿里的云服务,这样一旦线上挂了,你想恢复多少分钟之前的数据,就去远端拷贝一份之前的数据就好了。RDB对Redis的性能影响非常小,是因为在同步数据的时候他只是fork了一个子进程去做持久化的,而且他在数据恢复的时候速度比AOF来的快。

缺点:

RDB都是快照文件,都是默认五分钟甚至更久的时间才会生成一次,这意味着你这次同步到下次同步这中间五分钟的数据都很可能全部丢失掉。AOF则最多丢一秒的数据,数据完整性上高下立判。还有就是RDB在生成数据快照的时候,如果文件很大,客户端可能会暂停几毫秒甚至几秒,你公司在做秒杀的时候他刚好在这个时候fork了一个子进程去生成一个大快照,那就会出大问题。

AOF

优点:

上面提到了,RDB五分钟一次生成快照,但是AOF是一秒一次去通过一个后台的线程fsync操作,那最多丢这一秒的数据。AOF在对日志文件进行操作的时候是以append-only的方式去写的,他只是追加的方式写数据,自然就少了很多磁盘寻址的开销了,写入性能惊人,文件也不容易破损。AOF的日志是通过一个叫非常可读的方式记录的,这样的特性就适合做灾难性数据误删除的紧急恢复了,比如公司的实习生通过flushall清空了所有的数据,只要这个时候后台重写还没发生,你马上拷贝一份AOF日志文件,把最后一条flushall命令删了就完事了。

缺点:

一样的数据,AOF文件比RDB还要大。AOF开启后,Redis支持写的QPS会比RDB支持写的要低,他不是每秒都要去异步刷新一次日志嘛fsync,当然即使这样性能还是很高。

那两者怎么选择?

全都要,单独用RDB你会丢失很多数据,单独用AOF,数据恢复没RDB来的快,真出什么时候第一时间用RDB恢复,然后AOF做数据补全,冷备热备一起上,才是一个高健壮性系统所必须的。

 

2.  示例搭建:

一:Redis Cluster 介绍

Redis Cluster是Redis的分布式解决方案,在Redis 3.0版本正式推出的,集群通过分片(sharding)来进行数据共享,并提供复制和故障转移功能,能有效解决了Redis分布式方面的需求。当遇到单机内存、并发、流量等瓶颈时,都可以采用Cluster架构达到负载均衡的目的。

Cluster采用无中心结构,它的特点如下:

所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽节点的fail,是通过集群中超过半数的节点检测失效时才生效客户端与redis节点直连,不需要中间代理层.客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可。

官方说明是必须要三个 Master 节点。

  • 根据节点有效性投票理论,一半的节点认为某节点失效,才算失效。
  • 一个节点,不能给自己投票。
  • 两个节点 A 说 B 下线,B 认为 A 下线,两个人互相说我连接不上你,没有定论。
  • 至少三个节点,A、B 发现 C 不通,互相通知,得到一致性状态:C 的确下线。

集群组成

一个Redis集群通常由多个节点(node)组成,刚开始的时候,每隔节点都是相互独立的,他们都处于一个只包含自己的集群当中,要组件一个真正可工作的集群,我们必须将各个独立的节点连接起来,构成一个包含多个节点的集群。

命令如下:CLUSTER MEET <IP> <PORT>

向一个节点node发送CLUSTER MEET命令,让node节点与指定的<ip>:<port>节点进行握手(handshake),当握手成功时,node节点就会将指定的<ip>:<port>节点添加到node节点当前的集群,过程如下:

注:

  • clusterNode:clusterNode结构保存了一个节点的当前状态(注:包含节点的创建时间、节点名字、IP、port等信息),每个节点都使用一个clusterNode来记录自己的状态,并为集群中的所有节点(包括主节点和从节点)都创建一个相应的clusterNode结构,来记录其他节点信息
  • clusterState:每个节点node都保存着一个clusterState结构,这个结构记录了在当前节点的视角下,集群目前所处的状态(clusteState中存储了所有slusterNode信息)

思考:集群有了,节点也有了,那数据怎么分布呢?

槽指派(数据分布)

Redis集群通过分片的方式来保存数据库中的键值对,集群的整个数据库被分为16384个槽(slot),数据库中的每个键都属于这16384个槽的其中一个,集群中的每个节点可以处理0个或最多16384个槽。每一个节点负责维护一部分槽以及槽所映射的键值数据。

Cluster模式的具体工作机制:

1. 在Redis的每个节点上,都有一个插槽(slot),取值范围为0-16383

2. 当我们存取key的时候,Redis会根据CRC16的算法得出一个结果,然后把结果对16384求余数,这样每个key都会对应一个编号在0-16383之间的哈希槽,通过这个值,去找到对应的插槽所对应的节点,然后直接自动跳转到这个对应的节点上进行存取操作

HASH_SLOT = CRC16(key) mod 16384

3. 为了保证高可用,Cluster模式也引入主从复制模式,一个主节点对应一个或者多个从节点,当主节点宕机的时候,就会启用从节点。

4. 当其它主节点ping一个主节点A时,如果半数以上的主节点与A通信超时,那么认为主节点A宕机了。如果主节点A和它的从节点都宕机了,那么该集群就无法再提供服务了

下图展示某个包含5个节点的示意图:

注:槽指派可以不均匀分布,计算公式CRC16(KEY)&16383 计算key所属的槽位

集群中每个节点都会传播槽归属信息(传播当前节点处理哪些槽),让集群中每个节点都持有槽指派的元信息,知道槽位分派在哪个节点

 

集群中命令执行流程

(1)计算键属于哪个slot

(2)如果在当前节点,便执行该命令,如果不是,则返回MOVED错误

(3)前提是返回MOVED错误,客户端收到MOVED错误并根据提供的信息转向正确的节点进行访问(该节点会再次执行以上流程)

如下图:

这也是为什么有时候客户端需要访问两次redis服务器

复制和故障转移

Redis集群中的节点分为主节点(master)和从节点(slave),其中主节点用于处理槽,从节点则用于复制某个主节点,并在主节点下线时,代替下线主节点继续处理命令。

如下图,若主节点挂掉,会在从节点中选出一个节点作为新的主节点并客户端发来的命令(可以有多个从机)

 

搭建

一、下载

首先准备一个redis ,我用的是 redis-4.0.11.tar ,下载地址为:

http://download.redis.io/releases/redis-4.0.11.tar.gz

二、创建目录 + 配置文件

进入下载文件所在的地址路径:cd /Users/apple/Downloads/redis-4.0.11/redisCluter

#进入下载的目录,因为刚才redis就是下载到该目录了。
cd Downloads/
#创建一个目录
mkdir redisCluter
#进入这个目录
cd redisCluter/
#创建6个节点目录
mkdir 7000 7001 7002 7003 7004 7005

在刚才解压的包下找到redis-4.0.11,将redis-4.0.11依次复制到7000-7005文件下

更改配置

/Users/apple/Downloads/redis-4.0.11/redisCluter/7000/redis-4.0.11/redis.conf

# 端口号,每个目录都不同
port 7000

# 开启集群模式
cluster-enabled yes

#节点超时实际,单位毫秒
cluster-node-timeout 5000

#集群内部配置文件(默认为 nodes-6379.conf)
cluster-config-file nodes.conf

# 启动 AOF
appendonly yes

#daemonize是用来指定redis是否要用守护线程的方式启动。
#默认是no,改成 yes,意思是是否要后台启动。
daemonize yes

#当我们采用yes时,redis会在后台运行,此时redis将一直运行,除非手动kill该进程。同时将进程pid号写入至redis.conf选项pidfile设置的文件中,
#默认会生成在/var/run/redis.pid,也可以通过pidfile来指定pid文件生成的位置
#而采用no时,当前界面将进入redis的命令行界面,exit强制退出或者关闭连接工具(putty,xshell等)都会导致redis进程退出


#################此次示例演示只需修改以上配置也可以,以下配置可根据自己项目需要变更#################


# 当Redis以守护进程方式运行时,Redis默认会把pid写入/var/run/redis.pid文件,可以通过pidfile指定
pidfile /var/run/redis.pid

# 指定日志记录级别,Redis总共支持四个级别:debug、verbose、notice、warning,默认为verbose
# debug (很多信息, 对开发/测试比较有用)
# verbose (许多很少有用的信息,但不像调试级别那样混乱)
# notice (适度冗长,你想在生产中)
# warning (只记录非常重要/关键的消息)
loglevel verbose


################################ SNAPSHOTTING  #################################
# 指定在多长时间内,有多少次更新操作,就将数据同步到数据文件,可以多个条件配合
# Save the DB on disk:
#
#   save <seconds> <changes>
#
#   Will save the DB if both the given number of seconds and the given
#   number of write operations against the DB occurred.
#
#   满足以下条件将会同步数据:
#   900秒(15分钟)内有1个更改
#   300秒(5分钟)内有10个更改
#   60秒内有10000个更改
#   Note: 可以把所有“save”行注释掉,这样就取消同步操作了

save 900 1
save 300 10
save 60 10000

# 指定存储至本地数据库时是否压缩数据,默认为yes,Redis采用LZF压缩,如果为了节省CPU时间,可以关闭该选项,但会导致数据库文件变的巨大
rdbcompression yes


# 指定本地数据库文件名,默认值为dump.rdb
dbfilename dump.rdb


# 指定更新日志条件,共有3个可选值:
# no:表示等操作系统进行数据缓存同步到磁盘(快)
# always:表示每次更新操作后手动调用fsync()将数据写到磁盘(慢,安全)
# everysec:表示每秒同步一次(折衷,默认值)
appendfsync everysec


################################## INCLUDES ###################################

# 指定包含其他的配置文件,可以在同一主机上多个Redis实例之间使用同一份配置文件,而同时各实例又拥有自己的特定配置文件
# include /path/to/local.conf
# include /path/to/other.conf

配置的最后一步是把端口号:port 7000 改成相应目录的端口号。也是刚才的步骤,进入每一个redis.conf,修改端口。

三、启动6个节点的redis

分别进入每一个端口下的redis 执行启动命令,例如

cd 7000/redis-4.0.11/ 
redis-server redis.conf

6个节点都启动成功后, ps -ef|grep redis 看一下

sh-3.2# ps -ef|grep redis
    0 10056     1   0  3:11下午 ??         0:00.30 redis-server 127.0.0.1:7000 [cluster] 
    0 10069     1   0  3:11下午 ??         0:00.22 redis-server 127.0.0.1:7001 [cluster] 
    0 10076     1   0  3:11下午 ??         0:00.17 redis-server 127.0.0.1:7002 [cluster] 
    0 10080     1   0  3:12下午 ??         0:00.13 redis-server 127.0.0.1:7003 [cluster] 
    0 10084     1   0  3:12下午 ??         0:00.10 redis-server 127.0.0.1:7004 [cluster] 
    0 10088     1   0  3:12下午 ??         0:00.07 redis-server 127.0.0.1:7005 [cluster] 
    0 10094 10022   0  3:12下午 ttys001    0:00.01 grep redis
sh-3.2# 

四、关联所有节点

进入7000节点的redis目录中执行命令,进入控制台

redis-cli -p 7000

依次执行下面的命令(上面介绍的集群的组成)

127.0.0.1:7000> cluster meet 127.0.0.1 7001
OK
127.0.0.1:7000> cluster meet 127.0.0.1 7002
OK
127.0.0.1:7000> cluster meet 127.0.0.1 7003
OK
127.0.0.1:7000> cluster meet 127.0.0.1 7004
OK
127.0.0.1:7000> cluster meet 127.0.0.1 7005
OK

此时,所有的节点都关联起来了。

五、 分配槽 slot

数据块(slots)

一个Redis集群包含16384个插槽,数据库中每个键都属于这16384槽中的一个,集群使用公司CRC16%16384来计算键属于哪一个槽。

每个节点负责处理一部分插槽,如一个集群有3个节点,A节点负责处理0-5500号插槽,节点B负责处理5501-11000号插槽,节点C负责处理11001-16383号插槽。

redis-cli查询键值时,如客户端对应的服务器中不存在该键,则Redis会报错。而是用redis-cli -c -p port中的-c参数就可以实现自动重定向。

不在一个slot中的数据,不能使用mget和mset等多键操作。

进入7002的redis目录中执行命令

cd 7002/redis-4.0.11

redis-cli -p 7000 cluster addslots {0..5461}

redis-cli -p 7001 cluster addslots {5462..10922}

redis-cli -p 7002 cluster addslots {10923..16383}

此时节点已经分配好了。通过以下命令验证:

redis-cli -p 7000 cluster nodes
sh-3.2# redis-cli -p 7000 cluster nodes
248ec24662f43e13e0f6a33990b9bf479b2e4495 127.0.0.1:7000@17000 myself,master - 0 1610608495000 1 connected 0-5461
dda3e65ab367c0d1588dce5bea8e60a104d9c452 127.0.0.1:7005@17005 master - 0 1610608495830 0 connected
7272c16417975b443a5b84afa5d8e90ccd332ca8 127.0.0.1:7002@17002 master - 0 1610608495000 2 connected 10923-16383
66bac654c0ac5b84beb79d809c79cf2cf7599dbb 127.0.0.1:7003@17003 master - 0 1610608494819 3 connected
493f4ea7348fbc047f9d5aef3361458a348c2f06 127.0.0.1:7004@17004 master - 0 1610608496033 4 connected
8e4b9fa9e15824b3a4ee34497a4e1842080500f2 127.0.0.1:7001@17001 master - 0 1610608494000 5 connected 5462-10922

分配的原则是:保证每个主库运行在不同的ip地址上;每个从库和主库不在一个ip地址上。

现在6个节点都是主节点,并且给7000 、7001 、7002分配了槽。

六、变成主从复制

去7000的redis目录下执行

redis-cli -p 7000 cluster nodes
sh-3.2# redis-cli -p 7000 cluster nodes
248ec24662f43e13e0f6a33990b9bf479b2e4495 127.0.0.1:7000@17000 myself,master - 0 1610608495000 1 connected 0-5461
dda3e65ab367c0d1588dce5bea8e60a104d9c452 127.0.0.1:7005@17005 master - 0 1610608495830 0 connected
7272c16417975b443a5b84afa5d8e90ccd332ca8 127.0.0.1:7002@17002 master - 0 1610608495000 2 connected 10923-16383
66bac654c0ac5b84beb79d809c79cf2cf7599dbb 127.0.0.1:7003@17003 master - 0 1610608494819 3 connected
493f4ea7348fbc047f9d5aef3361458a348c2f06 127.0.0.1:7004@17004 master - 0 1610608496033 4 connected
8e4b9fa9e15824b3a4ee34497a4e1842080500f2 127.0.0.1:7001@17001 master - 0 1610608494000 5 connected 5462-10922

那一串16 进制字符串,其实就是后面对应进程节点的NodeId,这个是用处的,先放着。

然后分别设置7003、 7004 、7005节点的主库,执行下面命令,要对应自己的NodeId。

sh-3.2# redis-cli -p 7003 cluster replicate 248ec24662f43e13e0f6a33990b9bf479b2e4495  (7000的NodeID)
OK
sh-3.2# redis-cli -p 7004 cluster replicate 8e4b9fa9e15824b3a4ee34497a4e1842080500f2  (7001的NodeID)
OK
sh-3.2# redis-cli -p 7005 cluster replicate 7272c16417975b443a5b84afa5d8e90ccd332ca8  (7002的NodeID)
OK
sh-3.2#

这时候再执行

redis-cli -p 7000 cluster nodes
sh-3.2# redis-cli -p 7000 cluster nodes
248ec24662f43e13e0f6a33990b9bf479b2e4495 127.0.0.1:7000@17000 myself,master - 0 1610611512000 1 connected 0-5461
dda3e65ab367c0d1588dce5bea8e60a104d9c452 127.0.0.1:7005@17005 slave 7272c16417975b443a5b84afa5d8e90ccd332ca8 0 1610611514506 2 connected
7272c16417975b443a5b84afa5d8e90ccd332ca8 127.0.0.1:7002@17002 master - 0 1610611514000 2 connected 10923-16383
66bac654c0ac5b84beb79d809c79cf2cf7599dbb 127.0.0.1:7003@17003 slave 248ec24662f43e13e0f6a33990b9bf479b2e4495 0 1610611514000 3 connected
493f4ea7348fbc047f9d5aef3361458a348c2f06 127.0.0.1:7004@17004 slave 8e4b9fa9e15824b3a4ee34497a4e1842080500f2 0 1610611513000 5 connected
8e4b9fa9e15824b3a4ee34497a4e1842080500f2 127.0.0.1:7001@17001 master - 0 1610611514608 5 connected 5462-10922

到这里三主三从已经设置好了。

70007003
70017004
70027005

查看集群:

 $  redis-cli -h 127.0.0.1 -p 7000 cluster info
 $  redis-cli -h 127.0.0.1 -p 7000 cluster nodes 

操作集群

redis-cli -p 7000

127.0.0.1:7000> get foo
(error) MOVED 12182 127.0.0.1:7002
127.0.0.1:7000> set fooo barr
OK
127.0.0.1:7000> get foo
(error) MOVED 12182 127.0.0.1:7002
127.0.0.1:7000> get fooo
"barr"
127.0.0.1:7000> quit

redis-cli -p 7003
127.0.0.1:7003> get fooo
(error) MOVED 3916 127.0.0.1:7000
127.0.0.1:7003> quit

redis-cli -c -p 7003

注意添加 -c 参数表示以集群模式,否则报 (error) MOVED 12182 127.0.0.1:7002 错误

sh-3.2# redis-cli -c -p 7000
127.0.0.1:7000> set fo bar
-> Redirected to slot [15557] located at 127.0.0.1:7005
OK
127.0.0.1:7005>

从上面命令看到key为fo算出的slot为15557,落在7005节点上,所以有Redirected to slot [15557] located at 127.0.0.1:7005,集群会自动进行跳转。因此客户端可以连接任何一个节点来进行数据的存取。

 

6.redis集群高可用性测试

通过cluster nodes可查看集群的节点信息,有前面可知,目前的集群关系是:

70007003
70017004
70027005

 

6.1 redis节点模拟宕机

我们可以假设,如果7002挂掉了,那么7005会不会代替7002进行工作呢?我们可以模拟一下这个情况,首先先做个准备工作:在redis客户端输入cluster nodes查看集群的状态,可以看到从节点(slave)为7003、7004、7005,主节点(master)为7000、7001、7002 而7003后的myself就表示当前节点(也就是在哪一个节点上执行cluster nodes命令的):

127.0.0.1:7003> cluster nodes
248ec24662f43e13e0f6a33990b9bf479b2e4495 127.0.0.1:7000@17000 master - 0 1610779580548 1 connected 0-5461
8e4b9fa9e15824b3a4ee34497a4e1842080500f2 127.0.0.1:7001@17001 master - 0 1610779579000 5 connected 5462-10922
dda3e65ab367c0d1588dce5bea8e60a104d9c452 127.0.0.1:7005@17005 slave 7272c16417975b443a5b84afa5d8e90ccd332ca8 0 1610779579000 2 connected
7272c16417975b443a5b84afa5d8e90ccd332ca8 127.0.0.1:7002@17002 master - 0 1610779580000 2 connected 10923-16383
493f4ea7348fbc047f9d5aef3361458a348c2f06 127.0.0.1:7004@17004 slave 8e4b9fa9e15824b3a4ee34497a4e1842080500f2 0 1610779579536 5 connected
66bac654c0ac5b84beb79d809c79cf2cf7599dbb 127.0.0.1:7003@17003 myself,slave 248ec24662f43e13e0f6a33990b9bf479b2e4495 0 1610779577000 3 connected

而每一个节点前面的一长串数字字母组合就是节点的id,,主从节点标识后面的就是父节点的id,例如下图中从节点7005的父节点id是7272c16417975b443a5b84afa5d8e90ccd332ca8刚好是7002的节点id,这因为7005是7002的从节点,,而7002没有父节点id,因为7002是主节点。

在服务器上上ps -ef|grep redis查看7002的进程,软杀掉7002(模拟挂机):

sh-3.2# ps -ef|grep redis
    0   972     1   0 五10上午 ??         6:12.44 redis-server 127.0.0.1:7000 [cluster] 
    0   980     1   0 五10上午 ??         6:11.71 redis-server 127.0.0.1:7001 [cluster] 
    0   985     1   0 五10上午 ??         6:12.11 redis-server 127.0.0.1:7002 [cluster] 
    0   994     1   0 五10上午 ??         6:09.06 redis-server 127.0.0.1:7003 [cluster] 
    0  1000     1   0 五10上午 ??         6:09.85 redis-server 127.0.0.1:7004 [cluster] 
    0  1005     1   0 五10上午 ??         6:08.49 redis-server 127.0.0.1:7005 [cluster] 
    0 17705   853   0  2:46下午 ttys000    0:00.01 redis-cli -c -p 7003
    0 17726  1026   0  2:47下午 ttys001    0:00.01 grep redis
sh-3.2# kill -9 985
sh-3.2# 

执行了kill命令之后马上转到另一客户端上一直键入cluster nodes命令查看redis各节点的状态变化,可以发现中间有个过程是在判断7002状态是否失败(fail?),再到判断为失败fail,最后7002对应的7005从节点上升为主节点

127.0.0.1:7003> cluster nodes
248ec24662f43e13e0f6a33990b9bf479b2e4495 127.0.0.1:7000@17000 master - 0 1610780080131 1 connected 0-5461
8e4b9fa9e15824b3a4ee34497a4e1842080500f2 127.0.0.1:7001@17001 master - 0 1610780081144 5 connected 5462-10922
dda3e65ab367c0d1588dce5bea8e60a104d9c452 127.0.0.1:7005@17005 slave 7272c16417975b443a5b84afa5d8e90ccd332ca8 0 1610780080000 2 connected
7272c16417975b443a5b84afa5d8e90ccd332ca8 127.0.0.1:7002@17002 master - 1610780078105 1610780075574 2 disconnected 10923-16383
493f4ea7348fbc047f9d5aef3361458a348c2f06 127.0.0.1:7004@17004 slave 8e4b9fa9e15824b3a4ee34497a4e1842080500f2 0 1610780081044 5 connected
66bac654c0ac5b84beb79d809c79cf2cf7599dbb 127.0.0.1:7003@17003 myself,slave 248ec24662f43e13e0f6a33990b9bf479b2e4495 0 1610780080000 3 connected
127.0.0.1:7003> cluster nodes
248ec24662f43e13e0f6a33990b9bf479b2e4495 127.0.0.1:7000@17000 master - 0 1610780082665 1 connected 0-5461
8e4b9fa9e15824b3a4ee34497a4e1842080500f2 127.0.0.1:7001@17001 master - 0 1610780083000 5 connected 5462-10922
dda3e65ab367c0d1588dce5bea8e60a104d9c452 127.0.0.1:7005@17005 slave 7272c16417975b443a5b84afa5d8e90ccd332ca8 0 1610780082158 2 connected
7272c16417975b443a5b84afa5d8e90ccd332ca8 127.0.0.1:7002@17002 ==master,fail?== - 1610780078105 1610780075574 2 disconnected 10923-16383
493f4ea7348fbc047f9d5aef3361458a348c2f06 127.0.0.1:7004@17004 slave 8e4b9fa9e15824b3a4ee34497a4e1842080500f2 0 1610780083185 5 connected
66bac654c0ac5b84beb79d809c79cf2cf7599dbb 127.0.0.1:7003@17003 myself,slave 248ec24662f43e13e0f6a33990b9bf479b2e4495 0 1610780081000 3 connected
127.0.0.1:7003> cluster nodes
248ec24662f43e13e0f6a33990b9bf479b2e4495 127.0.0.1:7000@17000 master - 0 1610780087240 1 connected 0-5461
8e4b9fa9e15824b3a4ee34497a4e1842080500f2 127.0.0.1:7001@17001 master - 0 1610780087748 5 connected 5462-10922
dda3e65ab367c0d1588dce5bea8e60a104d9c452 127.0.0.1:7005@17005 master - 0 1610780088255 6 connected 10923-16383
7272c16417975b443a5b84afa5d8e90ccd332ca8 127.0.0.1:7002@17002 ==master,fail== - 1610780078105 1610780075574 2 disconnected
493f4ea7348fbc047f9d5aef3361458a348c2f06 127.0.0.1:7004@17004 slave 8e4b9fa9e15824b3a4ee34497a4e1842080500f2 0 1610780088000 5 connected
66bac654c0ac5b84beb79d809c79cf2cf7599dbb 127.0.0.1:7003@17003 myself,slave 248ec24662f43e13e0f6a33990b9bf479b2e4495 0 1610780087000 3 connected
127.0.0.1:7003> cluster nodes

我们再把7002节点启动,可以看到7002已经变成7005的从节点了

sh-3.2# redis-cli -h 127.0.0.1 -p 7000 cluster nodes 
dda3e65ab367c0d1588dce5bea8e60a104d9c452 127.0.0.1:7005@17005 master - 0 1612497350466 6 connected 10923-16383
248ec24662f43e13e0f6a33990b9bf479b2e4495 127.0.0.1:7000@17000 myself,master - 0 1612497350000 1 connected 0-5461
7272c16417975b443a5b84afa5d8e90ccd332ca8 127.0.0.1:7002@17002 slave dda3e65ab367c0d1588dce5bea8e60a104d9c452 0 1612497350000 6 connected
8e4b9fa9e15824b3a4ee34497a4e1842080500f2 127.0.0.1:7001@17001 master - 0 1612497351476 5 connected 5462-10922
66bac654c0ac5b84beb79d809c79cf2cf7599dbb 127.0.0.1:7003@17003 slave 248ec24662f43e13e0f6a33990b9bf479b2e4495 0 1612497351575 3 connected
493f4ea7348fbc047f9d5aef3361458a348c2f06 127.0.0.1:7004@17004 slave 8e4b9fa9e15824b3a4ee34497a4e1842080500f2 0 1612497351000 5 connected

向现有集群中添加新节点时要注意重新分配哈希槽。

从集群中移除节点,第一步先删除从节点(因为没有哈希槽),删除主节点之前先把节点上的哈希曹全部移到其他节点(只能移到一个节点)上。

 

Cluster模式的优缺点

优点:

1.  无中心架构,数据按照slot分布在多个节点。
2.  集群中的每个节点都是平等的关系,每个节点都保存各自的数据和整个集群的状态。每个节点都和其他所有节点连接,而且这些连接保持活跃,这样就保证了我们只需要连接集群中的任意一个节点,就可以获取到其他节点的数据。
3.  可线性扩展到1000多个节点,节点可动态添加或删除
4.  能够实现自动故障转移,节点之间通过gossip协议交换状态信息,用投票机制完成slave到master的角色转换

缺点:

1. 客户端实现复杂,驱动要求实现Smart Client,缓存slots mapping信息并及时更新,提高了开发难度。目前仅JedisCluster相对成熟,异常处理还不完善,比如常见的“max redirect exception”
 2. 节点会因为某些原因发生阻塞(阻塞时间大于 cluster-node-timeout)被判断下线,这种failover是没有必要的
 3. 数据通过异步复制,不保证数据的强一致性
 4. slave充当“冷备”,不能缓解读压力
 5. 批量操作限制,目前只支持具有相同slot值的key执行批量操作,对mset、mget、sunion等操作支持不友好
 6. key事务操作支持有线,只支持多key在同一节点的事务操作,多key分布不同节点时无法使用事务功能
 7. 不支持多数据库空间,单机redis可以支持16个db,集群模式下只能使用一个,即db 0

 

 

二:主从复制模式

1. 基本原理

主从复制模式中包含一个主数据库实例(master)与一个或多个从数据库实例(slave),如下图

客户端可对主数据库进行读写操作,对从数据库进行读操作,主数据库写入的数据会实时自动同步给从数据库。

具体工作机制为:

  1.  slave启动后,向master发送SYNC命令,master接收到SYNC命令后通过bgsave保存快照(即上文所介绍的RDB持久化),并使用缓冲区记录保存快照这段时间内执行的写命令
  2.  master将保存的快照文件发送给slave,并继续记录执行的写命令
  3.  slave接收到快照文件后,加载快照文件,载入数据
  4.  master快照发送完后开始向slave发送缓冲区的写命令,slave接收命令并执行,完成复制初始化
  5.  此后master每次执行一个写命令都会同步发送给slave,保持master与slave之间数据的一致性

2. 部署示例

redis.conf的主要配置

###网络相关### 
 
# bind 127.0.0.1 # 绑定监听的网卡IP,注释掉或配置成0.0.0.0可使任意IP均可访问 
 
port 6379  # 设置监听端口,建议生产环境均使用自定义端口 
 
timeout 30 # 客户端连接空闲多久后断开连接,单位秒,0表示禁用 
 
###通用配置### 
 
daemonize yes # 在后台运行 
 
pidfile /var/run/redis_6379.pid  # pid进程文件名 
 
###RDB持久化配置### 
 
save 900 1 # 900s内至少一次写操作则执行bgsave进行RDB持久化 
  
save 60 1000
每隔60s,如果有超过1000个key发生了变更,那么就生成一个新的dump.rdb文件,就是当前redis内存中完整的数据快照。
 
# 如果禁用RDB持久化,可在这里添加 save "" 即可
 
rdbcompression yes #是否对RDB文件进行压缩,建议设置为no,以(磁盘)空间换(CPU)时间 
 
dbfilename dump.rdb # RDB文件名称 
 
dir /usr/local/redis/datas # RDB文件保存路径,AOF文件也保存在这里 
 
###AOF配置### 
 
appendonly yes # 默认值是no,表示不使用AOF增量持久化的方式,使用RDB全量持久化的方式 
 
appendfsync everysec # 可选值 always, everysec,no,建议设置为everysec 

部署主从复制模式只需稍微调整slave的配置,在redis.conf中添加

replicaof 127.0.0.1 6379 # master的ip,port 

replica-serve-stale-data no # 如果slave无法与master同步,设置成slave不可读,方便监控脚本发现问题 

此选项为以前的 slave-serve-stale-data,replicas 节点和 master 节点断开连接后,replicas 节点根据 replica-serve-stale-data 选项的配置可能有以下行为:

选项设置为 yes(默认值),replicas 节点会继续向客户端提供服务,此时返回给客户端的数据很可能是过期的
选项设置为 no,除了 INFO, replicaOF, AUTH, PING, SHUTDOWN, REPLCONF, ROLE, CONFIG, SUBSCRIBE, UNSUBSCRIBE, PSUBSCRIBE, PUNSUBSCRIBE, PUBLISH, PUBSUB, COMMAND, POST, HOST, 和 LATENCY 命令外的其他命令,replicas 节点都会返回 "SYNC with master in progress" 给客户端

本示例在单台服务器上配置master端口6379,两个slave端口分别为7001,7002,启动master,再启动两个slave

[root@dev-server-1 master-slave]# redis-server master.conf  
[root@dev-server-1 master-slave]# redis-server slave1.conf  
[root@dev-server-1 master-slave]# redis-server slave2.conf 

进入master数据库,写入一个数据,再进入一个slave数据库,立即便可访问刚才写入master数据库的数据。如下所示

[root@dev-server-1 master-slave]# redis-cli    
127.0.0.1:6379> set site csdn.cn  
OK  
127.0.0.1:6379> get site  
"csdn.cn"  
127.0.0.1:6379> info replication  
# Replication  
role:master  
connected_slaves:2  
slave0:ip=127.0.0.1,port=7001,state=online,offset=13364738,lag=1  
slave1:ip=127.0.0.1,port=7002,state=online,offset=13364738,lag=0 
...  
127.0.0.1:6379> exit  
[root@dev-server-1 master-slave]# redis-cli -p 7001  
127.0.0.1:7001> get site  
"csdn.cn" 

执行info replication命令可以查看连接该数据库的其它库的信息,如上可看到有两个slave连接到master

3. 主从复制的优缺点

优点:

  1.  master能自动将数据同步到slave,可以进行读写分离,分担master的读压力
  2.  master、slave之间的同步是以非阻塞的方式进行的,同步期间,客户端仍然可以提交查询或更新请求

缺点:

  1.  不具备自动容错与恢复功能,master或slave的宕机都可能导致客户端请求失败,需要等待机器重启或手动切换客户端IP才能恢复
  2.  master宕机,如果宕机前数据没有同步完,则切换IP后会存在数据不一致的问题
  3.  难以支持在线扩容,Redis的容量受限于单机配置

 

三,Sentinel(哨兵)模式

1. 基本原理

哨兵模式基于主从复制模式,只是引入了哨兵来监控与自动处理故障。如图:

哨兵顾名思义,就是来为Redis集群站哨的,一旦发现问题能做出相应的应对处理。其功能包括:

  1.  监控master、slave是否正常运行
  2.  当master出现故障时,能自动将一个slave转换为master
  3.  多个哨兵可以监控同一个Redis,哨兵之间也会自动监控

哨兵模式的具体工作机制:

在配置文件中通过 sentinel monitor来定位master的IP、端口,一个哨兵可以监控多个master数据库,只需要提供多个该配置项即可。哨兵启动后,会与要监控的master建立两条连接:

  1.  一条连接用来订阅master的_sentinel_:hello频道与获取其他监控该master的哨兵节点信息
  2.  另一条连接定期向master发送INFO等命令获取master本身的信息

与master建立连接后,哨兵会执行三个操作:

  1.  定期(一般10s一次,当master被标记为主观下线时,改为1s一次)向master和slave发送INFO命令
  2.  定期向master和slave的_sentinel_:hello频道发送自己的信息
  3.  定期(1s一次)向master、slave和其他哨兵发送PING命令

发送INFO命令可以获取当前数据库的相关信息从而实现新节点的自动发现。所以说哨兵只需要配置master数据库信息就可以自动发现其slave信息。获取到slave信息后,哨兵也会与slave建立两条连接执行监控。通过INFO命令,哨兵可以获取主从数据库的最新信息,并进行相应的操作,比如角色变更等。

接下来哨兵向主从数据库的sentinel:hello频道发送信息与同样监控这些数据库的哨兵共享自己的信息,发送内容为哨兵的ip端口、运行id、配置版本、master名字、master的ip端口还有master的配置版本。这些信息有以下用处:

  1.  其他哨兵可以通过该信息判断发送者是否是新发现的哨兵,如果是的话会创建一个到该哨兵的连接用于发送PING命令。
  2.  其他哨兵通过该信息可以判断master的版本,如果该版本高于直接记录的版本,将会更新
  3.  当实现了自动发现slave和其他哨兵节点后,哨兵就可以通过定期发送PING命令定时监控这些数据库和节点有没有停止服务。

如果被PING的数据库或者节点超时未回复,哨兵认为其主观下线。如果下线的是master,哨兵会向其它哨兵发送命令询问它们是否也认为该master主观下线,如果达到一定数目投票,哨兵会认为该master已经客观下线,并选举领头的哨兵节点对主从系统发起故障恢复。若没有足够的sentinel进程同意master下线,master的客观下线状态会被移除,若master重新向sentinel进程发送的PING命令返回有效回复,master的主观下线状态就会被移除

哨兵认为master客观下线后,故障恢复的操作需要由选举的领头哨兵来执行,选举采用Raft算法:

  1.  发现master下线的哨兵节点(我们称他为A)向每个哨兵发送命令,要求对方选自己为领头哨兵
  2.  如果目标哨兵节点没有选过其他人,则会同意选举A为领头哨兵
  3.  如果有超过一半的哨兵同意选举A为领头,则A当选
  4.  如果有多个哨兵节点同时参选领头,此时有可能存在一轮投票无竞选者胜出,此时每个参选的节点等待一个随机时间后再次发起参选请求,进行下一轮投票竞选,直至选举出领头哨兵

选出领头哨兵后,领头者开始对系统进行故障恢复,从出现故障的master的从数据库中挑选一个来当选新的master,选择规则如下:

  1.  所有在线的slave中选择优先级最高的,优先级可以通过slave-priority配置
  2.  如果有多个最高优先级的slave,则选取复制偏移量最大(即复制越完整)的当选
  3.  如果以上条件都一样,选取id最小的slave

挑选出需要继任的slave后,领头哨兵向该数据库发送命令使其升格为master,然后再向其他slave发送命令接受新的master,最后更新数据。将已经停止的旧的master更新为新的master的从数据库,使其恢复服务后以slave的身份继续运行。

2. 部署演示

本示例基于Redis 5.0.3版

哨兵模式基于前文的主从复制模式。哨兵的配置文件为sentinel.conf,在文件中添加

sentinel monitor mymaster 127.0.0.1 6379 1 # mymaster定义一个master数据库的名称,后面是master的ip, port,1表示至少需要一个Sentinel进程同意才能将master判断为失效,如果不满足这个条件,则自动故障转移(failover)不会执行  
sentinel down-after-milliseconds mymaster 5000 # 5s未回复PING,则认为master主观下线,默认为30s 
sentinel parallel-syncs mymaster 2  # 指定在执行故障转移时,最多可以有多少个slave实例在同步新的master实例,在slave实例较多的情况下这个数字越小,同步的时间越长,完成故障转移所需的时间就越长 
sentinel failover-timeout mymaster 300000 # 如果在该时间(ms)内未能完成故障转移操作,则认为故障转移失败,生产环境需要根据数据量设置该值 

一个哨兵可以监控多个master数据库,只需按上述配置添加多套

分别以26379,36379,46379端口启动三个sentinel

[root@dev-server-1 sentinel]# redis-server sentinel1.conf --sentinel  
[root@dev-server-1 sentinel]# redis-server sentinel2.conf --sentinel 
[root@dev-server-1 sentinel]# redis-server sentinel3.conf --sentinel 

也可以使用redis-sentinel sentinel1.conf 命令启动。此时集群包含一个master、两个slave、三个sentinel,如图:

我们来模拟master挂掉的场景,执行 kill -9 3017 将master进程干掉,进入slave中执行 info replication查看,

[root@dev-server-1 sentinel]# redis-cli -p 7001  
127.0.0.1:7001> info replication  
# Replication  
role:slave  
master_host:127.0.0.1  
master_port:7002  
master_link_status:up  
master_last_io_seconds_ago:1  
master_sync_in_progress:0  
# 省略 
127.0.0.1:7001> exit  
[root@dev-server-1 sentinel]# redis-cli -p 7002  
127.0.0.1:7002> info replication  
# Replication  
role:master  
connected_slaves:1  
slave0:ip=127.0.0.1,port=7001,state=online,offset=13642721,lag=1  
# 省略 

可以看到slave 7002已经成功上位晋升为master(role:master),接收一个slave 7001的连接。此时查看slave2.conf配置文件,发现replicaof的配置已经被移除了,slave1.conf的配置文件里replicaof 127.0.0.1 6379 被改为 replicaof 127.0.0.1 7002。重新启动master,也可以看到master.conf配置文件中添加了replicaof 127.0.0.1 7002的配置项。

3. 哨兵模式的优缺点

优点:

  1.  哨兵模式基于主从复制模式,所以主从复制模式有的优点,哨兵模式也有
  2.  哨兵模式下,master挂掉可以自动进行切换,系统可用性更高

缺点:

  1.  同样也继承了主从模式难以在线扩容的缺点,Redis的容量受限于单机配置
  2.  需要额外的资源来启动sentinel进程,实现相对复杂一点,同时slave节点作为备份节点不提供服务

总结

Redis集群方案的三种模式,其中主从复制模式能实现读写分离,但是不能自动故障转移;哨兵模式基于主从复制模式,能实现自动故障转移,达到高可用,但与主从复制模式一样,不能在线扩容,容量受限于单机的配置;Cluster模式通过无中心化架构,实现分布式存储,可进行线性扩展,也能高可用,但对于像批量操作、事务操作等的支持性不够好。三种模式各有优缺点,可根据实际场景进行选择。

 

四、集群可视化工具和工具类

Redis Desktop Manager,   CodisManager,   RedisPlus,

 Redis 可视化工具最全的横向评测:https://zhuanlan.zhihu.com/p/210483494?utm_source=com.yinxiang

下面为Redis Desktop Manager界面:

 

工具类Jedis与Redisson选型对比

1.概况对比

Jedis是Redis官方推荐的Java连接开发工具,Redis的Java实现的客户端,其API提供了比较全面的Redis命令的支持;Redisson实现了分布式和可扩展的Java数据结构,和Jedis相比,功能较为简单,不支持字符串操作,不支持排序、事务、管道、分区等Redis特性。Redisson的宗旨是促进使用者对Redis的关注分离,从而让使用者能够将精力更集中地放在处理业务逻辑上。

Jedis中的方法调用是比较底层的暴露的Redis的API,也即Jedis中的Java方法基本和Redis的API保持着一致,了解Redis的API,也就能熟练的使用Jedis。而Redisson中的方法则是进行比较高的抽象,每个方法调用可能进行了一个或多个Redis方法调用

import redis.clients.jedis.Jedis;
 
public class RedisStringJava {
    public static void main(String[] args) {
        //连接本地的 Redis 服务
        Jedis jedis = new Jedis("localhost");
        System.out.println("连接成功");
        //设置 redis 字符串数据
        jedis.set("runoobkey", "www.runoob.com");
        // 获取存储的数据并输出
        System.out.println("redis 存储的字符串为: "+ jedis.get("runoobkey"));
    }
}
Redisson操作map:

Redisson redisson = …

RMap map = redisson.getMap("my-map"); // implement java.util.Map

map.put("key", "value");

map.containsKey("key");

map.get("key");

2.可伸缩性

Jedis使用阻塞的I/O,且其方法调用都是同步的,程序流需要等到sockets处理完I/O才能执行,不支持异步。Jedis客户端实例不是线程安全的,所以需要通过连接池来使用Jedis。

Redisson使用非阻塞的I/O和基于Netty框架的事件驱动的通信层,其方法调用是异步的。Redisson的API是线程安全的,所以可以操作单个Redisson连接来完成各种操作。

3.数据结构

Jedis仅支持基本的数据类型如:String、Hash、List、Set、Sorted Set。

Redisson不仅提供了一系列的分布式Java常用对象,基本可以与Java的基本数据结构通用,还提供了许多分布式服务,其中包括(BitSet, Set, Multimap, SortedSet, Map, List, Queue, BlockingQueue, Deque, BlockingDeque, Semaphore, Lock, AtomicLong, CountDownLatch, Publish / Subscribe, Bloom filter, Remote service, Spring cache, Executor service, Live Object service, Scheduler service)。

在分布式开发中,Redisson可提供更便捷的方法。

4.应用场景分析

4.1、热点数据缓存

对于不经常变化的热点数据,两种方式都没有问题,但也要从几个方面考虑:

  1. 数据量大小。其实20万的数据已经挺大了,但是也就20M左右,所以除非要缓存的数据超过100M,优先选择Jedis
  2. 聚合计算比较多。把数据放到列表或者集合中,然后做数据精确查找,或者简单的模糊查询,比如根据IP找城市或者根据姓名模糊查询通信录等。Redisson可能更方便点,因为这些查找或计算一般都在应用程序里面执行,所以相对而言 Redisson使用更加方便。
  3. 数据增长比较快。需要增量添加到redis中,同时还有很多查询,优先使用Redisson。
  4. 需要使用本地缓存。Jedis 不支持本地缓存,优先使用Redisson。

4.2、账户资金缓存

对资金数据的操作,必须使用同步的,一般使用incr做加减,这时必须使用Jedis。必须要时对单个账号使用分布式锁。Redisson是异步的不适合对资金的操作。

4.3、网络统计数据

网络统计数据主要是用来分析,要执行简单的聚合计算或者检索,且数据量一般都比较大,我个人认为使用Redisson是合理的。

4.4、分布式锁

Redisson有成熟的分布式锁实现方式,并且提供了多种分布式锁实现方式。基于Jedis也可以也实现简单的互斥锁,但是要使用SETNX命令,并注意锁的粒度。Redisson实现了基于Redis集群的RedLock 分布式锁算法。不过这里有一篇反驳这个算法的文章:http://martin.kleppmann.com/2016/02/08/how-to-do-distributed-locking.html,在使用的时候可以参考下。

4.5、统计分析

对于一些专业的统计数据分析,需要使用复杂的计算公式,比如天气预报算法等,需要存储中间结果或需要提前加载大量基础数据,这时适合使用Redisson。但是对于hyperloglog基数计算,还是要使用Jedis。

4.6、搜索功能

Redis 中集合可以用来实现简单搜索的功能,可以使用集合以及有序集合的交集、并集和差集操作查找符合指定要求的元素。这种应用场景还是使用Jedis。

4.7、分布式Session

Spring session  就是用Jedis 吧,完全不用考虑Redisson

 

 

 

参考文章:

https://blog.csdn.net/wangxuelei036/article/details/106328081

https://database.51cto.com/art/202011/632701.htm 

https://blog.csdn.net/weixin_41715077/article/details/102403763

 

 

  • 0
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Redis集群中,三主三从架构是一种常见的搭建方式。每个节点都可以相互连通,客户端可以连接任何一个节点来访问整个集群中的数据和执行操作。三个主节点提供服务,而三个从节点则提供备份功能,存储集群中所有主节点和从节点的信息。 在搭建这种集群时,不需要修改端口配置,因为每个节点都在不同的机器上,这样才能实现真正的高可用性。如果在同一台机器上搭建集群,那就是伪集群了。 使用哨兵模式搭建三主三从的Redis集群有一些优点和缺点。优点包括:主从节点的自动切换使得系统更加健壮,可用性更高。缺点则在于Redis较难支持在线扩容,特别是在集群容量达到上限时,进行在线扩容会变得非常复杂。这时候,集群模式可能是一个更好的选择。 综上所述,使用三主三从架构搭建Redis集群可以提供高可用性和数据备份功能,但在线扩容可能存在一定的挑战。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [Redis三主三从集群搭建(三台机器)](https://blog.csdn.net/weixin_44519124/article/details/113274037)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [Redis搭建集群,三主三从集群模式](https://blog.csdn.net/xiefazhi123/article/details/119147657)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值