Corosync+Pacemaker构建高可用集群

一、概述:
1.1 AIS和OpenAIS简介
AIS应用接口规范,是用来定义应用程序接口(API)的开放性规范的集合,这些应用程序作为中间件为应用服务提供一种开放、高移植性的程序接口。是在实现高可用应用过程中是亟需的。服务可用性论坛(SA Forum)是一个开放性论坛,它开发并发布这些免费规范。使用AIS规范的应用程序接口(API),可以减少应用程序的复杂性和缩短应用程序的开发时间,这些规范的主要目的就是为了提高中间组件可移植性和应用程序的高可用性。
OpenAIS是基于SA Forum 标准的集群框架的应用程序接口规范。OpenAIS提供一种集群模式,这个模式包括集群框架,集群成员管理,通信方式,集群监测等,能够为集群软件或工具提供满足 AIS标准的集群接口,但是它没有集群资源管理功能,不能独立形成一个集群。
1.2 corosync简介
Corosync是OpenAIS发展到Wilson版本后衍生出来的开放性集群引擎工程,corosync最初只是用来演示OpenAIS集群框架接口规范的一个应用,可以说corosync是OpenAIS的一部分,但后面的发展明显超越了官方最初的设想,越来越多的厂商尝试使用corosync作为集群解决方案。如Redhat的RHCS集群套件就是基于corosync实现。
corosync只提供了message layer,而没有直接提供CRM,一般使用Pacemaker进行资源管理。
1.3 pacemaker简介
pacemaker就是Heartbeat 到了V3版本后拆分出来的资源管理器(CRM),用来管理整个HA的控制中心,要想使用pacemaker配置的话需要安装一个pacemaker的接口,它的这个程序的接口叫crmshell,它在新版本的 pacemaker已经被独立出来了,不再是pacemaker的组成部分。
(1)pacemaker 内部结构
《Corosync+Pacemaker构建高可用集群》
(2)群集组件说明:
stonithd:心跳系统。
lrmd:本地资源管理守护进程。它提供了一个通用的接口支持的资源类型。直接调用资源代理(脚本)。
pengine:政策引擎。根据当前状态和配置集群计算的下一个状态。产生一个过渡图,包含行动和依赖关系的列表。
CIB:群集信息库。包含所有群集选项,节点,资源,他们彼此之间的关系和现状的定义。同步更新到所有群集节点。
CRMD:集群资源管理守护进程。主要是消息代理的PEngine和LRM,还选举一个领导者(DC)统筹活动(包括启动/停止资源)的集群。
OpenAIS:OpenAIS的消息和成员层。
Heartbeat:心跳消息层,OpenAIS的一种替代。
CCM:共识群集成员,心跳成员层。
功能概述
CIB使用XML表示集群的集群中的所有资源的配置和当前状态。CIB的内容会被自动在整个集群中同步,使用PEngine计算集群的理想状态,生成指令列表,然后输送到DC(指定协调员)。Pacemaker 集群中所有节点选举的DC节点作为主决策节点。如果当选DC节点宕机,它会在所有的节点上, 迅速建立一个新的DC。DC将PEngine生成的策略,传递给其他节点上的LRMd(本地资源管理守护程序)或CRMD通过集群消息传递基础结构。当集群中有节点宕机,PEngine重新计算的理想策略。在某些情况下,可能有必要关闭节点,以保护共享数据或完整的资源回收。为此,Pacemaker配备了stonithd设备。STONITH可以将其它节点“爆头”,通常是实现与远程电源开关。Pacemaker会将STONITH设备,配置为资源保存在CIB中,使他们可以更容易地监测资源失败或宕机。
(3)CRM中的几个基本概念:
资源粘性:资源粘性表示资源是否倾向于留在当前节点,如果为正整数,表示倾向,负数表示移离,-inf表示正无穷,inf表示正无穷。
资源黏性值范围及其作用:
0:默认选项。资源放置在系统中的最适合位置。这意味着当负载能力“较好”或较差的节点变得可用时才转移资源。
此选项的作用基本等同于自动故障回复,只是资源可能会转移到非之前活动的节点上;
大于0:资源更愿意留在当前位置,但是如果有更合适的节点可用时会移动。值越高表示资源越愿意留在当前位置;
小于0:资源更愿意移离当前位置。绝对值越高表示资源越愿意离开当前位置;
INFINITY:如果不是因节点不适合运行资源(节点关机、节点待机、达到migration-threshold 或配置更改)而强制资源转移,
资源总是留在当前位置。此选项的作用几乎等同于完全禁用自动故障回复;
-INFINITY:资源总是移离当前位置;
资源类型:
primitive(native):基本资源,原始资源
group:资源组
clone:克隆资源(可同时运行在多个节点上),要先定义为primitive后才能进行clone。主要包含STONITH和集群文件系统(cluster filesystem)
master/slave:主从资源,如drdb(下文详细讲解)
RA类型:
Lsb:linux表中库,一般位于/etc/rc.d/init.d/目录下的支持start|stop|status等参数的服务脚本都是lsb
ocf:Open cluster Framework,开放集群架构
heartbeat:heartbaet V1版本
stonith:专为配置stonith设备而用
(4)pacemaker 支持集群
基于OpenAIS的集群
《Corosync+Pacemaker构建高可用集群》
基于心跳信息的传统集群架构
《Corosync+Pacemaker构建高可用集群》
1.4Corosync+Pacemaker 支持集群
可实现多种集群模型,包​括​ Active/Active, Active/Passive, N+1, N+M, N-to-1 and N-to-N
主从架构集群:许多高可用性的情况下,使用Pacemaker和DRBD的双节点主/从集群是一个符合成本效益的解决方案
《Corosync+Pacemaker构建高可用集群》
多节点备份集群:支持多少节点,Pacemaker可以显着降低硬件成本通过允许几个主/从群集要结合和共享一个公用备份节点
《Corosync+Pacemaker构建高可用集群》
共享存储集群(多个节点多个服务):有共享存储时,每个节点可能被用于故障转移,Pacemaker甚至可以运行多个服务
《Corosync+Pacemaker构建高可用集群》
二、在CentOS 6.4(64位)上配置基于corosync的Web高可用
环境部署:
(1)本实验共有两个测试节点:
node1:10.33.100.77
node2:10.33.100.99
(2)集群服务为apache的httpd服务;
(3)提供web服务的地址为10.33.100.88;
《Corosync+Pacemaker构建高可用集群》
2.1 基本前提配置:
两个节点要实现时间同步、ssh互信、hosts名称解析、详细过程参见heartbeat配置博文,这里不再赘述
安装httpd

[root@Node1 ~]# yum install httpd –y
[root@Node1 ~]# echo "Node1" >/var/www/html/index.html
[root@Node2 ~]# yum install httpd –y
[root@Node2 ~]# echo "Node2" >/var/www/html/index.html

在各节点手动启动httpd服务,并确认其可以正常提供服务,而后停止服务并关闭开机自启

[root@Node1 ~]# chkconfig httpd off 
[root@Node1 ~]# chkconfig --list httpd
httpd           0:off   1:off   2:off   3:off   4:off   5:off   6:off
[root@Node2 ~]# chkconfig httpd off 
[root@Node2 ~]# chkconfig --list httpd
httpd           0:off   1:off   2:off   3:off   4:off   5:off   6:off

2.2 安装corosync:
注意,以下所有安装都是需要在所有节点上执行的。
2.2.1 安装依赖:


[root@Node1 ~]# yum install libibverbs librdmacm lm_sensors libtool-ltdl openhpi-libs openhpi perl-TimeDate
[root@Node2 ~]# yum install libibverbs librdmacm lm_sensors libtool-ltdl openhpi-libs openhpi perl-TimeDate

2.2.2 安装集群组件:


[root@Node1 ~]# yum install corosync pacemaker
[root@Node2 ~]# yum install corosync pacemaker

2.2.3 安装crmsh实现资源管理:
从pacemaker 1.1.8开始,crm发展成为一个独立项目crmsh。也就是说安装了pacemaker后,并没有crm这个命令,要实现对集群资源管理,还需要独立安装crmsh,crmsh依赖于pssh。
特别注意:
使用了crmsh,就不在需要安装heartbeat,之前的版本中都需要安装heartbeat以利用其crm进行资源管理。
网上教程大多数都是下载crmsh的rpm包安装,但是实验过程中依赖关系始终无法解决,本文最终使用源码编译安装,安装前确保准备好开发环境
《Corosync+Pacemaker构建高可用集群》
下载以下2个安装包
http://download.savannah.gnu.org/releases/crmsh/
《Corosync+Pacemaker构建高可用集群》
首先编译安装pssh
《Corosync+Pacemaker构建高可用集群》
接着解压crmsh包
《Corosync+Pacemaker构建高可用集群》
根据README文档可知安装步骤
《Corosync+Pacemaker构建高可用集群》
《Corosync+Pacemaker构建高可用集群》
《Corosync+Pacemaker构建高可用集群》
./configure报错:没有发现头文件,去crmsh官网查阅资料
《Corosync+Pacemaker构建高可用集群》
可知下载cluster-glue-libs-devel和cluster-glue-libs-devel即可(本文实验系统为centos6.4_64位,两个devel包挂载本地光盘yum安装)
《Corosync+Pacemaker构建高可用集群》
./configure成功
《Corosync+Pacemaker构建高可用集群》
接下来
《Corosync+Pacemaker构建高可用集群》
《Corosync+Pacemaker构建高可用集群》
测试成功,crmsh安装完毕
《Corosync+Pacemaker构建高可用集群》
Node1上执行相同操作

[root@Node1 ~]# tar xf pssh-2.3.1.tar.gz -C /usr/src
[root@Node1 ~]# cd /usr/src/pssh-2.3.1/
[root@Node1 pssh-2.3.1]# vim INSTALL 
[root@Node1 pssh-2.3.1]# python setup.py install
[root@Node1 ~]# tar xf crmsh-1.2.6.tar.bz2 -C /usr/src/
[root@Node1 ~]# cd /usr/src/crmsh-crmsh-1.2.6/
[root@Node1 crmsh-crmsh-1.2.6]# ./autogen.sh 
[root@Node1 crmsh-crmsh-1.2.6]# yum install cluster-glue-libs-devel pacemaker-libs-devel
[root@Node1 crmsh-crmsh-1.2.6]# ./configure 
[root@Node1 crmsh-crmsh-1.2.6]# make
[root@Node1 crmsh-crmsh-1.2.6]# make install
[root@Node1 crmsh-crmsh-1.2.6]# mkdir /var/lib/pacemaker/cores/root

2.3 配置corosync:
2.3.1 主配置文件:
《Corosync+Pacemaker构建高可用集群》

# Please read the corosync.conf.5 manual page 
compatibility: whitetank ##这个表示是否兼容0.8之前的版本
totem {              ##图腾用来定义集群中各节点中是怎么通信的以及参数 
        version: 2      ##版本号,只能是2,不能修改 
        secauth: on     ##安全认证,当使用aisexec时,会非常消耗CPU 
        threads: 2      ##实现认证时的并行线程数,根据CPU个数和核心数确定 
        interface {     ##指定发送心跳信息的接口,是个子模块
                ringnumber: 0   ##冗余环号,集群中有多个节点,每个节点上有多个网卡,信息传送时,其它节点可以接收,
##同时本机的另一块网卡也可以接收,为了避免心跳信息的环状发送,因此要为这个网卡定义一个唯一的环号码,可定义对应网卡在一个环内
                bindnetaddr: 10.33.0.0   ##绑定心跳网段 ,这里设定两个节点所在的网络地址
                mcastaddr: 226.99.12.17  ##心跳组播地址,一对多通信
                mcastport: 5405          ##心跳组播使用端口 
                ttl: 1                   ##表示只向外播一次
        } 
}
logging { 
        fileline: off       ##指定要打印的行 
        to_stderr: no       ##是否发送到标准错误输出 
        to_logfile: yes     ##记录到文件 
        to_syslog: no       ##记录到syslog 
        logfile: /var/log/cluster/corosync.log ##日志文件路径
        debug: off        ##是否启动调试 
        timestamp: on     ##是否打印时间戳,利于定位错误,但会消耗CPU 
        logger_subsys {   ##日志的子系统
                subsys: AMF 
                debug: off 
        } 
}
service {              ##定义启动pacemaker的服务
  ver:  0              ##定义版本
  name: pacemaker      ##定义启动时corosync同时启动pacemaker 
}
amf {                  ##跟编程接口相关的
        mode: disabled 
}
aisexec {  ##表示启动ais的功能时以哪个用户的身份去运行的
    user: root
    group: root 
     ##其实这个块定义不定义都可以,corosync默认就是以root身份去运行的
}

补充知识:
多播地址(multicast address)即组播地址,是一组主机的标示符,它已经加入到一个多播组中。在以太网中,多播地址是一个48位的标示符,命名了一组应该在这个网络中应用接收到一个分组的站点。在IPv4中,它历史上被叫做D类地址,一种类型的IP地址,它的范围从224.0.0.0到239.255.255.255,或,等同的,在224.0.0.0/4。在IPv6,多播地址都有前缀ff00::/8。

多播是第一个字节的最低位为1的所有地址,例如01-12-0f-00-00-02。广播地址是全1的48位地址,也属于多播地址。但是广播又是多播中的特例,就像是正方形属于长方形,但是正方形有长方形没有的特点。

2.3.2 生成认证key:
使用corosync-keygen生成key时,由于要使用/dev/random生成随机数,因此如果新装的系统操作不多,如果没有足够的熵
(关于random使用键盘敲击产生随机数的原理可自行google),可能会出现如下提示:
《Corosync+Pacemaker构建高可用集群》
此时一直狂敲键盘,直到出现下图中红色标记内容
《Corosync+Pacemaker构建高可用集群》
生成密钥文件
《Corosync+Pacemaker构建高可用集群》
2.3.3 拷贝配置文件与密钥问价至节点2:
《Corosync+Pacemaker构建高可用集群》
2.3.4 启动corosync
《Corosync+Pacemaker构建高可用集群》
2.3.5 检查启动情况:
(1)查看corosync引擎是否正常启动:
《Corosync+Pacemaker构建高可用集群》
(2)查看初始化成员节点通知是否正常发出:
《Corosync+Pacemaker构建高可用集群》
(3)检查启动过程中是否有错误产生:
《Corosync+Pacemaker构建高可用集群》
(4)查看pacemaker是否正常启动:
《Corosync+Pacemaker构建高可用集群》
(5)查看集群节点状态
《Corosync+Pacemaker构建高可用集群》
可能存在的问题:iptables没有配置相关策略,导致两个节点无法通信。可关闭iptables或配置节点间的通信策略。
三、集群资源管理
3.1 crmsh基本介绍

[root@node1 ~]# crm <--进入crmsh

crm(live)# help ##查看帮助

This is crm shell, a Pacemaker command line interface.

Available commands:
    cib              manage shadow CIBs       ##CIB管理模块
    resource         resources management     ##资源管理模块
    configure        CRM cluster configuration  ##CRM配置,包含资源粘性、资源类型、资源约束等
    node             nodes management  ##节点管理
    options          user preferences  ##用户偏好
    history          CRM cluster history  ##CRM历史
    site             Geo-cluster support  ##地理集群支持
    ra               resource agents information center ##资源代理配置 
    status           show cluster status ##查看集群状态
    help,?           show help (help topics for list of topics) ##查看帮助 
    end,cd,up        go back one level ##返回上一级
    quit,bye,exit    exit the program  ##退出
crm(live)# configure <--进入配置模式
crm(live)configure# property ##切换到property目录下,可以用两次tab键进行补全和查看
usage: property [$id=<set_id>] <option>=<value>    ##property的用法和格式
crm(live)configure# verify ##检查设置的属性是否正确
crm(live)configure# commit ##检查没问题就可以提交了
crm(live)configure# show ##查看当前集群的所有配置信息

(1)查看一下默认配置
《Corosync+Pacemaker构建高可用集群》
(2)检查当前配置语法
《Corosync+Pacemaker构建高可用集群》
禁用stonith后再次检查配置,无报错
《Corosync+Pacemaker构建高可用集群》

crm(live)# ra <--进入RA(资源代理配置)模式
crm(live)ra# help
This level contains commands which show various information about 
the installed resource agents. It is available both at the top 
level and at the `configure` level.
Available commands:
    classes          list classes and providers    ##查看RA类型
    list             list RA for a class (and provider)  ##查看指定类型(或提供商)的RA
    meta,info        show meta data for a RA   ##查看RA详细信息
    providers        show providers for a RA and a class  ##查看指定资源的提供商和类型

(3)查看当前集群系统所支持的类型
《Corosync+Pacemaker构建高可用集群》
(4)查看某种类别下的所用资源代理的列表
《Corosync+Pacemaker构建高可用集群》
(5)查看某个资源代理的配置方法

crm(live)ra# info ocf:heartbeat:IPaddr 
Manages virtual IPv4 addresses (portable version) (ocf:heartbeat:IPaddr) 
This script manages IP alias IP addresses  
It can add an IP alias, or remove one. 
Parameters (* denotes required, [] the default): 
ip* (string): IPv4 address  
    The IPv4 address to be configured in dotted quad notation, for example  
    "192.168.1.1". 

(6)查看集群状态
《Corosync+Pacemaker构建高可用集群》
3.2 法定票数问题
在双节点集群中,由于票数是偶数,当心跳出现问题【脑裂】时,两个节点都将达不到法定票数,默认quorum策略会关闭集群服务,为了避免这种情况,可以增加票数为奇数(增加ping节点),或者调整默认quorum策略为【ignore】

property
Set the cluster (crm_config) options.
Usage:
        property [$id=<set_id>] <option>=<value> [<option>=<value> ...]

《Corosync+Pacemaker构建高可用集群》
3.3 防​止​资​源​在​节​点​恢​复​后​移​动​
故障发生时,资源会迁移到正常节点上,但当故障节点恢复后,资源可能再次回到原来节点,这在有些情况下并非是最好的策略,因为资源的迁移是有停机时间的,资源在节点间每一次的来回流动都会造成那段时间内节点提供的服务无法被正常访问,特别是一些复杂的应用,如MySQL数据库,其停机时间会更长。为了避免这种情况,可以根据需要,使用本文1.3(3)介绍的资源粘性策略

rsc_defaults
Set defaults for the resource meta attributes.
Usage:
        rsc_defaults [$id=<set_id>] <option>=<value> [<option>=<value> ...]
Example:
        rsc_defaults failure-timeout=3m

《Corosync+Pacemaker构建高可用集群》
3.4 配置一个web集群
《Corosync+Pacemaker构建高可用集群》
3.4.1 定义IP地址资源:IP是主资源
《Corosync+Pacemaker构建高可用集群》
在命令行配置资源时,只要不用commit提交配置好资源,就不会生效,一但用commit命令提交,就会写入到cib.xml的配置文件中
《Corosync+Pacemaker构建高可用集群》
注意上述最后一行,定义的资源已经的Node1上启动。使用ifconfig命令也可以看到该IP已生效
《Corosync+Pacemaker构建高可用集群》
此时,在Node2上停止Node1的corosync服务,再查看状态可以看到Node1已经离线,此时的集群状态为”WITHOUT quorum”,即集群服务本身已经不满足正常运行的条件,这对于只有两节点的集群来讲是不合理的,但此前已经通过命令:property no-quorum-policy=ignore设定忽略quorum不能满足集群状态的检查,因此webIP自动转移到Node2,测试完重新启动Node1
《Corosync+Pacemaker构建高可用集群》
3.4.2 配置httpd资源:
接下来将此httpd服务添加为集群资源
将httpd添加为集群资源有两处资源代理可用:lsb和ocf:heartbeat,为了简单起见,这里使用lsb类型:
首先可以使用如下命令查看lsb类型的httpd资源的语法格式:
《Corosync+Pacemaker构建高可用集群》
接下来新建资源httpd:
《Corosync+Pacemaker构建高可用集群》
从上面的信息中可以看出VIP和httpd有可能会分别运行于两个节点上,这对于通过此IP提供Web服务的应用来说是不成立的,即此两个资源必须同时运行在某节点上。
有两种方法可以解决:
(1)定义组资源,将VIP和httpd同时加入一个组中,实现将资源运行在同节点上
(2)定义资源约束以实现将资源运行在同一节点上
3.4.3 组资源
下面先定义一个组资源
《Corosync+Pacemaker构建高可用集群》
可以看到所有资源全部运行在Node1上,下面测试一下
《Corosync+Pacemaker构建高可用集群》
下面模拟故障,测试一下
《Corosync+Pacemaker构建高可用集群》
可以看到当Node1节点设置为standby时,所有资源全部切换到Node2上,下面再来访问一下Web页面
《Corosync+Pacemaker构建高可用集群》
接着先让Node1上线,再删除组资源
《Corosync+Pacemaker构建高可用集群》
可以看到资源又重新运行在两个节点上了
《Corosync+Pacemaker构建高可用集群》
3.4.4 资源约束
资源约束则用以指定在哪些群集节点上运行资源,以何种顺序装载资源,以及特定资源依赖于哪些其它资源。
pacemaker共给我们提供了三种资源约束方法:
(1)Resource Location(资源位置):定义资源可以、不可以或尽可能在哪些节点上运行;
(2)Resource Collocation(资源排列):排列约束用以定义集群资源可以或不可以在某个节点上同时运行;
(3)Resource Order(资源顺序):顺序约束定义集群资源在节点上启动的顺序;
定义约束时,还需要指定分数。各种分数是集群工作方式的重要组成部分。其实,从迁移资源到决定在已降级集群中停止哪些资源的整个过程是通过以某种方式修改分数来实现的。分数按每个资源来计算,资源分数为负的任何节点都无法运行该资源。在计算出资源分数后,集群选择分数最高的节点。INFINITY(无穷大)目前定义为 1,000,000。加减无穷大遵循以下3个基本规则:
任何值 + 无穷大 = 无穷大
任何值 – 无穷大 = -无穷大
无穷大 – 无穷大 = -无穷大
定义资源约束时,也可以指定每个约束的分数。分数表示指派给此资源约束的值。分数较高的约束先应用,分数较低的约束后应用。通过使用不同的分数为既定资源创建更多位置约束,可以指定资源要故障转移至的目标节点的顺序。
因此,对于前述的WebIP和WebServer可能会运行于不同节点的问题,可以通过以下命令来解决:
《Corosync+Pacemaker构建高可用集群》

colocation (collocation)
This constraint expresses the placement relation between two or more resources.
If there are more than two resources, then the constraint is called a resource set. 
Collocation resource sets have an extra attribute to allow for sets of resources which don’t depend on each other in terms of state. 
The shell syntax for such sets is to put resources in parentheses.
Usage:
        colocation <id> <score>: <rsc>[:<role>] <rsc>[:<role>] ...
Example:
        colocation dummy_and_apache -inf: apache dummy
        colocation c1 inf: A ( B C )

提交后查看状态:可以看到所有资源全部运行在Node1上
《Corosync+Pacemaker构建高可用集群》
接着,还得确保WebSite在某节点启动之前得先启动WebIP,这可以使用如下命令实现:
《Corosync+Pacemaker构建高可用集群》

order
This constraint expresses the order of actions on two resources or more resources. 
If there are more than two resources, then the constraint is called a resource set. 
Ordered resource sets have an extra attribute to allow for sets of resources whose actions may run in parallel.
The shell syntax for such sets is to put resources in parentheses.
Usage:
        order <id> score-type: <rsc>[:<action>] <rsc>[:<action>] ...
          [symmetrical=<bool>]
        score-type :: advisory | mandatory | <score>
Example:
        order c_apache_1 mandatory: apache:start ip_1
        order o1 inf: A ( B C )

此外,由于HA集群本身并不强制每个节点的性能相同或相近,所以,某些时候可能希望正常服务总能在某个性能较强的节点上运行,这可以通过位置约束来实现:
《Corosync+Pacemaker构建高可用集群》
3.4.5 测试:在节点切换期间,web服务可一直正常访问
《Corosync+Pacemaker构建高可用集群》
3.4.6配置NFS资源
配置NFS服务器
《Corosync+Pacemaker构建高可用集群》
节点测试挂载:手动测试Node1和Node2上是否可以正常挂载共享目录
《Corosync+Pacemaker构建高可用集群》
配置资源nfs
《Corosync+Pacemaker构建高可用集群》
查看crm状态,可以看到三个资源不在同一个节点上
《Corosync+Pacemaker构建高可用集群》
下面定义排列约束,使三个资源在同一节点上
《Corosync+Pacemaker构建高可用集群》
提交后查看资源状态,所有资源全部在node1上
《Corosync+Pacemaker构建高可用集群》
访问测试
《Corosync+Pacemaker构建高可用集群》
最后我们模拟一下资源故障
《Corosync+Pacemaker构建高可用集群》
当Node1故障时,所有资源全部移动到Node2上,web页面可正常访问,至此,一个基本的HA集群就搭建完成

    原文作者:Celeste7777
    原文地址: https://blog.csdn.net/Celeste7777/article/details/48438835
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞