redis(二)redis cluster(集群)——高可用

深入理解redis-cluster

集群的引入~~~

从主从-哨兵-集群可以看到redis的不断完善;

主从复制是最简单的节点同步方案无法主从自动故障转移

哨兵可以同时管理多个主从同步方案同时也可以处理主从自动故障转移,通过配置多个哨兵节点可以解决单点网络故障问题,但是单个节点的性能压力问题无法解决

集群解决了前面两个方案的所有问题。

一、关于高可用集群概述

为什么要有集群呢?

1、首先是并发量,一般 QPS 到10万每秒已经非常牛了,随着公司业务的发展,或者当需要离散计算的时候,需要用到中间件缓存的时候,业务需要100万每秒。这个时候,我们就需要使用分布式了。

2、其次是数据量一般一个 Redis 的内存大约是16G~256G,假设我们在一台主从机器上配置了200G内存,但是业务需求是需要500G的时候,我们首先不会通过升级硬件,而是通过分布式。

我们对并发量大和数据量剧增的时候,采取的最常用的手段就是加机器,对数据进行分区。做一个形象的比喻,我们的数据量相当于货物,当货物只有很少一部分的时候,我们可以使用驴来拉货;当货物多起来了的时候,已经超出驴能拉的范围,我们可以使用大象来拉货;当货物更多的时候,已经没有更强壮的动物了,这个时候我们可以考虑使用多只大象来拉货。

分布式就是一种采用某种规则对多台机器管理的方式。采用分布式我们就是为了节省费用。

如何进行数据分布

分为顺序分布和哈希分布

顺序分布:即按照号码一个一个往下排

哈希分布:即按照哈希函数,对求余产生的数字进行,进行分配。

分布 特点1 特点2
顺序分布 数据分散容易倾斜、键值业务相关、可顺序访问、支持批量 Big Table、HBase
哈希分布 数据分散度高、键值分布业务无关、支持批量、无法顺序访问一致性哈希 redis cluster、其他缓存

由上表可知,哈希分布和顺序分布只是场景上的适用。哈希分布不能顺序访问,比如你想访问1~100,哈希分布只能遍历全部数据,同时哈希分布因为做了 hash 后导致与业务数据无关了。而顺序分布会导致数据倾斜的,主要是访问的倾斜。每次点击会重点访问某台机器,这就导致最后数据都到这台机器上了,这就是顺序分布最大的缺点。其他的特点见表可知。

但哈希分布其实是有个问题的,当我们要扩容机器的时候,专业上称之为“节点伸缩”,这个时候,因为是哈希算法,会导致数据迁移。在节点取余的时候,迁移数量和添加的节点数是有关的,这个时候建议使用翻倍扩容。

这里需要说明的是节点取余到底是什么。

比如之前是三个节点,那么现在加一个节点,就是四个节点。这个时候哈希算法就是从3的取余变成了4的取余,这个时候,原来的数字所在的位置肯定是需要发生变化的,整体的数据基本上都是做了漂移。

整体的数据漂移其实是有问题的,对数据库的性能,硬件上都是考验,所以为了减少整体的数据漂移,我们就需要对哈希算法有个一致性哈希算法

关于一致性哈希算法:

假设我们有 n1~n4 这四台机器,我们对每一台机器分配一个唯一 token,每次有数据(图中黄色代表数据),一致性哈希算法规定每次都顺时针漂移数据,也就是图中黄色的数据都指向 n3。这个时候我们需要增加一个节点 n5,在 n2 和 n3 之间,数据还是会发生漂移,但是这个时候你是否注意到,其实只有 n2~n3 这部分的数据被漂移,其他的数据都是不会变的,这样就实现了部分漂移,而没有对所有数据进行漂移的弊端了。

Redis Cluster 的哈希算法 —— 虚拟槽分区

如上图所示,我们知道槽的范围是0~16383,如果此时有五个节点,key 会通过 CRC16 哈希算法,对16383取余,然后保存到 Redis Cluster 里,Redis Cluster 会根据数据判断是否是这个虚拟槽里的数据,如果不是这个槽范围的,由于 Redis Cluster 数据是共享的,于是就会告知数据应该存到那台机器上。

假若现在对虚拟槽的概念还是不太清晰,没关系,下来看看分布式架构的基本结构:

基本结构:

分布式架构是一种彼此通讯的架构,通过每个节点之间负责对应的槽,每个节点都负责读写。如下图:

那么 Redis Cluster 是怎样的架构呢?实际上我们可以通过安装来了解 Redis Cluster 的架构。了解Redis Cluster 架构,我们从了解节点、meet操作、指派槽、复制四个知识点开始。

节点

Redis Cluster 是有很多节点的,每个节点负责读和写。

meet 操作

我们知道 Redis Cluster 是通过节点完成数据交换的,meet 操作则是这个过程的基础。

上图所示,A 和 C 之间有 meet 操作通讯数据,A 和 B 之间也有 meet 操作通讯,那么 B 和 C 之间也就可以相互通信。只要某个节点和另外的节点能够正常读写,那么任何节点之间其实也是可以相互进行数据交换的。

可以说,所有节点都可以共享消息。

指派槽

我们只有对节点进行指派某个槽,每个 key 算出来的哈希值是否在某个槽内,它才能正常的读写。


如上图所示,当我们的 Redis Cluster 有三台机器的时候,把0~16383的槽平均分配给每台机器(Redis Cluster 制定给每个槽分配16384个槽,也就是0~16383)。每当 key 访问过来,Redis Cluster 会计算哈希值是否在这个区间里。它们彼此都知道对应的槽在哪台机器上。

客户端只需要计算一个 key 的哈希值,然后传给 Redis Cluster 就可以了。

复制

Redis Cluster 是一个主从复制的过程。所以在这里就不解释了。

  前面的文章中介绍过了redis的主从和哨兵两种集群方案,redis从3.0版本开始引入了redis-cluster(集群)
从主从-哨兵-集群可以看到redis的不断完善;主从复制是最简单的节点同步方案,但是无法进行故障的自动转移。
哨兵可以同时管理多个主从同步方案同时也可以处理主从自动故障转移,通过配置多个哨兵节点可以解决单点网络故障问题。

但是单个节点的性能压力问题无法解决集群解决了前面两个方案的所有问题。

RedisCluster是redis的分布式解决方案,在3.0版本后推出的方案,有效地解决了Redis分布式的需求,当一个服务挂了可以快速的切换到另外一个服务,当遇到单机内存、并发等瓶颈时,可使用此方案来解决这些问题

概述: rediscluster它的实现负载均衡的原理本质上也是通过对槽位的分配。
设计结构: Redis-Cluster采用的是一种无中心结构,每个节点保存对应槽位的数据和整个集群的状态,并且每个节点和其他所有节点连接。使用时并不需要代理层,直接连接任意一个集群节点就可以和整个集群通信了。

  • 高可用: Redis-Cluster为了保证高可用性要求每一个服务器都至少有一个或多个从服务器,主服务器提供服务,从服务器只保证数据的备份,当主服务器宕机后会自动切换到从服务器,但是如果主从都宕机了那么整个集群将无法正确的提供服务。
  • 搭建方式: 十分简单。

关于redis-cluster特点:

(1)Redis-Cluster采用无中心结构
每个节点都和其它节点通过互ping保持连接,每个节点保存整个集群的状态信息,可以通过连接任意节点读取或者写入数据
(甚至是没有数据的空节点)。

(2)只有当集群中的大多数节点同时fail整个集群才fail
一般情况下是集群当中超过一半以上的节点fail的时候,集群才会fail

(3)整个集群有16384个slot(槽)
当需要在 Redis 集群中放置一个key-value 时,根据 CRC16(key) mod 16384的值,决定将一个key放到哪个桶中。
读取一个key时也是相同的算法。

(4)当主节点fail时从节点会升级为主节点
fail的主节点online之后自动变成了从节

理解redis-cluster

二、redis cluster的配置

进行官方安装

先关闭之前的redis

[root@server1 ~]# /etc/init.d/redis_6379 stop

1、在server1(master)上面进行设置

(1)、overcommit_memory是什么?
overcommit_memory是一个内核对内存分配的一种策略。 具体可见/proc/sys/vm/overcommit_memory下的值

(2)、overcommit_memory有什么作用?
overcommit_memory取值又三种分别为0, 1, 2
overcommit_memory=0, 表示内核将检查是否有足够的可用内存供应用进程使用;
如果有足够的可用内存,内存申请允许;否则,内存申请失败,并把错误返回给应用进程。
overcommit_memory=1, 表示内核允许分配所有的物理内存,而不管当前的内存状态如何。
overcommit_memory=2, 表示内核允许分配超过所有物理内存和交换空间总和的内存
 

在这里将overcommit_memory设置为1

[root@server1 vm]# cat overcommit_memory 
0
[root@server1 vm]# echo 1 > overcommit_memory 
[root@server1 vm]# cat overcommit_memory 
1
[root@server1 vm]# pwd
/proc/sys/vm

2、新建redis目录

[root@server1 ~]# mkdir /usr/local/rediscluster

[root@server1 rediscluster]# mkdir 700{1..6}

在master先配置文件,若干个单点

#编辑配置文件(http://www.redis.cn/topics/cluster-tutorial.html 配置复制官网)

root@server1 7001]# cat redis.conf
port  7001                                       //端口       
cluster-enabled  yes                           //开启集群
cluster-config-file  nodes.conf       //集群的配置,配置文件首次启动自动生成 7001,7002,7003,7004,7005,7006
cluster-node-timeout  15000                //请求超时  默认15秒,可自行设置
appendonly  yes                           //aof日志开启  有需要就开启,它会每次写操作都记录一条日志 
pidfile "/usr/local/rediscluster/7001/redis.pid"     #进程文件
logfile "/usr/local/rediscluster/7001/redis.log"     #日志文件
daemonize yes                                        #后台运行
dir "/usr/local/rediscluster/7001"

#启动
[root@server1 7001]# redis-server redis.conf

#查看
ps ax    

 2329 ?        Ssl    0:00 redis-server *:7001 [cluster]
 2333 pts/1    R+     0:00 ps ax

#测试

[root@server1 7001]# redis-cli -p 7001
127.0.0.1:7001> info
# Server
redis_version:5.0.3
redis_git_sha1:00000000
redis_git_dirty:0
redis_build_id:2174e884849545db
redis_mode:cluster
os:Linux 3.10.0-514.el7.x86_64 x86_64
arch_bits:64
multiplexing_api:epoll

同理配置7002~7006,并启动

[root@server1 7001]# cp redis.conf ../7002
[root@server1 7001]# cp redis.conf ../7003
[root@server1 7001]# cp redis.conf ../7004
[root@server1 7001]# cp redis.conf ../7005
[root@server1 7001]# cp redis.conf ../7006
 

 2329 ?        Ssl    0:00 redis-server *:7001 [cluster]
 2361 ?        Ssl    0:00 redis-server *:7002 [cluster]
 2379 ?        Ssl    0:00 redis-server *:7003 [cluster]
 2385 ?        Ssl    0:00 redis-server *:7004 [cluster]
 2391 ?        Ssl    0:00 redis-server *:7005 [cluster]
 2397 ?        Ssl    0:00 redis-server *:7006 [cluster]
 2401 pts/1    R+     0:00 ps ax

#拷贝ruby安装脚本:

ruby是什么?Ruby,一种简单快捷的面向对象面向对象程序设计脚本语言

[root@server1 src]# pwd
/root/redis-5.0.3/src

[root@server1 src]# cp redis-trib.rb /usr/local/bin/

#执行命令

[root@server1 src]# redis-trib.rb
/usr/bin/env: ruby: No such file or directory    ##报错了,因为没有ruby

[root@server1 src]# yum install -y ruby

[root@server1 src]# redis-trib.rb
WARNING: redis-trib.rb is not longer available!
You should use redis-cli instead.   

查看报错
 ##redis-trib.rb不可用了,用redis-cli


[root@server1 bin]# redis-cli --cluster help
Cluster Manager Commands:
  create         host1:port1 ... hostN:portN
                 --cluster-replicas <arg>
  check          host:port
                 --cluster-search-multiple-owners
  info           host:port
  fix            host:port
                 --cluster-search-multiple-owners
  reshard        host:port
                 --cluster-from <arg>
                 --cluster-to <arg>
                 --cluster-slots <arg>
                 --cluster-yes
                 --cluster-timeout <arg>
                 --cluster-pipeline <arg>
                 --cluster-replace
  rebalance      host:port
                 --cluster-weight <node1=w1...nodeN=wN>
                 --cluster-use-empty-masters
                 --cluster-timeout <arg>
                 --cluster-simulate
                 --cluster-pipeline <arg>
                 --cluster-threshold <arg>
                 --cluster-replace
  add-node       new_host:new_port existing_host:existing_port
                 --cluster-slave
                 --cluster-master-id <arg>
  del-node       host:port node_id
  call           host:port command arg arg .. arg
  set-timeout    host:port milliseconds
  import         host:port
                 --cluster-from <arg>
                 --cluster-copy
                 --cluster-replace
  help           

在server1上创建集群:(注意:设置好一主一从)

[root@server1 ~]# redis-cli --cluster create --cluster-replicas 1 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 127.0.0.1:7006

#--cluster-replicas 1表示为每一个master创建1个slave

然后输入yes
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.

[OK] All 16384 slots covered.    #看到这个表示16384个槽都分配好了

>>> Nodes configuration updated
>>> Assign a different config epoch to each node
>>> Sending CLUSTER MEET messages to join the cluster
Waiting for the cluster to join
...
>>> Performing Cluster Check (using node 127.0.0.1:7001)
M: f74a3cda8a6df52b6dc864a0f190c56de832c348 127.0.0.1:7001
   slots:[0-5460] (5461 slots) master
   1 additional replica(s)
S: fb247197c2cebf6cd2764e4be97bd00020028086 127.0.0.1:7005
   slots: (0 slots) slave
   replicates f74a3cda8a6df52b6dc864a0f190c56de832c348
M: 8b55ca1e435b61104d564e57f926664d41fe03d7 127.0.0.1:7003
   slots:[10923-16383] (5461 slots) master
   1 additional replica(s)
S: 3395594b80bbde1d9a60c6d437d6f141e7fb80c8 127.0.0.1:7006
   slots: (0 slots) slave
   replicates ed3bd017514e75e8f77f8b470e642e6006d21ac9
M: ed3bd017514e75e8f77f8b470e642e6006d21ac9 127.0.0.1:7002
   slots:[5461-10922] (5462 slots) master
   1 additional replica(s)
S: 06adbd0f2eadca6763d8cdd352a24fed675ecb35 127.0.0.1:7004
   slots: (0 slots) slave
   replicates 8b55ca1e435b61104d564e57f926664d41fe03d7
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.

要指定一主一从–cluster-replicas 1表示为每一个master创建1个slave
可以看到节点1、2、3为master
节点4、5、6为slave
 

ps ax查看,j集群全部创建好i

 2329 ?        Ssl    0:01 redis-server *:7001 [cluster]
 2361 ?        Ssl    0:01 redis-server *:7002 [cluster]
 2379 ?        Ssl    0:01 redis-server *:7003 [cluster]
 2385 ?        Ssl    0:01 redis-server *:7004 [cluster]
 2391 ?        Ssl    0:01 redis-server *:7005 [cluster]
 2397 ?        Ssl    0:01 redis-server *:7006 [cluster]
 2426 ?        S<     0:00 [kworker/0:0H]
 2438 ?        S<     0:00 [kworker/0:1H]
 2439 pts/1    R+     0:00 ps ax

#查看集群信息
 

[root@server1 ~]# redis-cli --cluster info 127.0.0.1:7001    ##输入其他端口也可以,但是必须输一个端口
127.0.0.1:7001 (f74a3cda...) -> 0 keys | 5461 slots | 1 slaves.
127.0.0.1:7003 (8b55ca1e...) -> 0 keys | 5461 slots | 1 slaves.
127.0.0.1:7002 (ed3bd017...) -> 0 keys | 5462 slots | 1 slaves.
[OK] 0 keys in 3 masters.
0.00 keys per slot on average.
[root@server1 src]# redis-cli --cluster info 127.0.0.1:7002
127.0.0.1:7002 (ed3bd017...) -> 0 keys | 5462 slots | 1 slaves.
127.0.0.1:7001 (f74a3cda...) -> 0 keys | 5461 slots | 1 slaves.
127.0.0.1:7003 (8b55ca1e...) -> 0 keys | 5461 slots | 1 slaves.
[OK] 0 keys in 3 masters.
0.00 keys per slot on average.

测试:集群中三个master,三个slave,数据完全同步
三个master分别为:server1、server2、server3
登陆7001查看info可知:

server1的slave是7005

[root@server1 src]# redis-cli -c -p 7001
127.0.0.1:7001> info

# Replication
role:master
connected_slaves:1
slave0:ip=127.0.0.1,port=7005,state=online,offset=756,lag=0

测试:

此时登陆7005:

先在7001上存储数据~~
[root@server1 src]# redis-cli -c -p 7001
127.0.0.1:7001> set name wsp
-> Redirected to slot [5798] located at 127.0.0.1:7002
OK
 
在slave上查看键值~~
[root@server1 src]# redis-cli -c -p 7005
127.0.0.1:7005> get name
-> Redirected to slot [5798] located at 127.0.0.1:7002
"wsp"

测试存储信息

我们可以看到数据保存在5798个哈希槽中,在7002上
在任意节点都可以获取到信息,但是都会跳转到7002

原因??

[root@server1 ~]# redis-cli -c -p 7001
127.0.0.1:7001> set name wsp
-> Redirected to slot [5798] located at 127.0.0.1:7002
OK
127.0.0.1:7002> get name
"wsp"

[root@server1 ~]# redis-cli -c -p 7003
127.0.0.1:7003> set name wsp
-> Redirected to slot [5798] located at 127.0.0.1:7002
OK
127.0.0.1:7002> get name
"wsp"

查看节点的信息:

[root@server1 src]# redis-cli --cluster check 127.0.0.1:7001
127.0.0.1:7001 (f74a3cda...) -> 0 keys | 5461 slots | 1 slaves.
127.0.0.1:7003 (8b55ca1e...) -> 0 keys | 5461 slots | 1 slaves.
127.0.0.1:7002 (ed3bd017...) -> 1 keys | 5462 slots | 1 slaves.
[OK] 1 keys in 3 masters.
0.00 keys per slot on average.
>>> Performing Cluster Check (using node 127.0.0.1:7001)
M: f74a3cda8a6df52b6dc864a0f190c56de832c348 127.0.0.1:7001
   slots:[0-5460] (5461 slots) master
   1 additional replica(s)
S: fb247197c2cebf6cd2764e4be97bd00020028086 127.0.0.1:7005
   slots: (0 slots) slave
   replicates f74a3cda8a6df52b6dc864a0f190c56de832c348
M: 8b55ca1e435b61104d564e57f926664d41fe03d7 127.0.0.1:7003
   slots:[10923-16383] (5461 slots) master
   1 additional replica(s)
S: 3395594b80bbde1d9a60c6d437d6f141e7fb80c8 127.0.0.1:7006
   slots: (0 slots) slave
   replicates ed3bd017514e75e8f77f8b470e642e6006d21ac9
M: ed3bd017514e75e8f77f8b470e642e6006d21ac9 127.0.0.1:7002
   slots:[5461-10922] (5462 slots) master
   1 additional replica(s)
S: 06adbd0f2eadca6763d8cdd352a24fed675ecb35 127.0.0.1:7004
   slots: (0 slots) slave
   replicates 8b55ca1e435b61104d564e57f926664d41fe03d7
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.

3、测试集群的高可用
 

[root@server1 ~]# redis-cli -c -p 7005
127.0.0.1:7005> get name
-> Redirected to slot [5798] located at 127.0.0.1:7002
"wsp"
127.0.0.1:7002> SHUTDOWN
not connected>exit


[root@server1 ~]# redis-cli -c -p 7005
127.0.0.1:7005> get name
-> Redirected to slot [5798] located at 127.0.0.1:7006
"wsp"
127.0.0.1:7006> SHUTDOWN       #在挂掉7006
not connected>exit

此时key丢失,找不到

[root@server1 src]# redis-cli -c -p 7005
127.0.0.1:7005> get name
(error) CLUSTERDOWN The cluster is down
127.0.0.1:7005> exit

再次查看发现只有两个master了

root@server1 src]# redis-cli --cluster info 127.0.0.1:7001
Could not connect to Redis at 127.0.0.1:7002: Connection refused
127.0.0.1:7001 (70f07453...) -> 0 keys | 5461 slots | 1 slaves.
127.0.0.1:7003 (ae486b18...) -> 0 keys | 5461 slots | 1 slaves.
[OK] 0 keys in 2 masters.
0.00 keys per slot on average.

再次启动2和6的服务,注意2和6一定是匹配的

ps ax查看状态,此时都正常了。

[root@server1 ~]# redis-cli --cluster info 127.0.0.1:7001
Could not connect to Redis at 127.0.0.1:7002: Connection refused
127.0.0.1:7001 (4f5065af...) -> 0 keys | 5461 slots | 1 slaves.
127.0.0.1:7004 (fc590a43...) -> 1 keys | 5462 slots | 0 slaves.
127.0.0.1:7003 (0b2f0c60...) -> 0 keys | 5461 slots | 1 slaves.
[OK] 1 keys in 3 masters.
0.00 keys per slot on average.

此时进行查看,数据恢复!!!!

在这里插入图片描述

查看自己的master和slave相对应信息,可以查看到刚才添加的信息

节点2和节点6是相对应的master和cluster在这里插入图片描述

在这里插入图片描述

注意:在本次实验中,我们只是宕掉了一个master,接着再宕掉了master对应的slave,但是当集群当中的一半以上的master都down掉以后,集群也会down掉了

如果节点不够用,我们可以添加新的节点

redis添加新节点

[root@server1 rediscluster]# cp 7001/redis.conf 7007/
[root@server1 rediscluster]# cp 7001/redis.conf 7008/

[root@server1 7007]# redis-server redis.conf
[root@server1 7008]# redis-server redis.conf

[root@server1 7008]# redis-cli --cluster add-node 127.0.0.1:7007 127.0.0.1:7001    ##添加节点

[root@server1 7008]# redis-cli -c -p 7007
127.0.0.1:7007> cluster nodes    ##查看节点信息

b9ee016d45dde6a0067dd3e19139f1044a621d77 127.0.0.1:7007@17007 myself,master - 0 1554949235000 0 connected
#发现7007没有哈希槽
#尽管新节点没有包含任何哈希槽, 但它仍然是一个主节点, 所以在集群需要将某个从节点升级为新的主节点时, 这个新节点不会被选中,所以要添加一个slave

[root@server2 ~]# redis-cli --cluster help    ##查看帮助

  add-node       new_host:new_port existing_host:existing_port
                 --cluster-slave
                 --cluster-master-id <arg>


[root@server1 ~]# redis-cli --cluster add-node 127.0.0.1:7008 127.0.0.1:7007 --cluster-slave --cluster-master-id b9ee016d45dde6a0067dd3e19139f1044a621d77

[root@server1 ~]# redis-cli -c -p 7001
127.0.0.1:7001> cluster nodes    ##查看

[root@server1 ~]# redis-cli --cluster info 127.0.0.1:7001    ##查看集群信息

##为新节点分配哈希槽
[root@server1 ~]# redis-cli --cluster reshard 127.0.0.1:7007

How many slots do you want to move (from 1 to 16384)? 300    ##随便分配一点

What is the receiving node ID? b9ee016d45dde6a0067dd3e19139f1044a621d77    ##接收哈希槽的节点ID

Please enter all the source node IDs.
  Type 'all' to use all the nodes as source nodes for the hash slots.
  Type 'done' once you entered all the source nodes IDs.
Source node #1: all
#从所有节点获取哈希槽

[root@server1 ~]# redis-cli --cluster check 127.0.0.1:7001    ##检查可以看到已经分配哈希槽

####但是上面分配不均等,可能导致数据不同步
[root@server1 ~]# redis-cli --cluster rebalance --cluster-threshold 1 --cluster-use-empty-masters 127.0.0.1:7001    ##均分哈希槽

[root@server1 ~]# redis-cli --cluster check 127.0.0.1:7001

##查看数据
[root@server1 ~]# redis-cli -c -p 7008
127.0.0.1:7008> get name
-> Redirected to slot [5798] located at 127.0.0.1:7007
"wsp"

集群完成

发布了124 篇原创文章 · 获赞 18 · 访问量 3112

猜你喜欢

转载自blog.csdn.net/weixin_42221657/article/details/102762335