集群部署方案

本文详细介绍了集群部署方案,包括EZSonar高可用集群的需求背景、ES集群的基本原理、故障切换、节点恢复、架构规划、安装配置,以及Collector集群的原理、架构规划和安装配置。重点讲解了ES集群的主分片、副本、故障切换机制,并提供了硬件规划建议。整个内容旨在实现高可用性、高性能和数据完整性。
摘要由CSDN通过智能技术生成

集群部署方案

目录

 [显示

第一章 EZSonar高可用集群

1.1 集群基本概念

简单的说,集群(cluster)就是一组计算机,它们作为一个整体向用户提供一组网络资源。这些单个的计算机系统就是集群的节点(node)。一个理想的集群是,用户从来不会意识到集群系统底层的节点,在他/她们看来,集群是一个系统,而非多个计算机系统,并且集群系统的管理员可以随意增加和删改集群系统的节点。

1.2 集群的特点

1、高可用性:集群中的一个节点失效,它的任务可传递给其他节点,可以有效防止单点故障。

2、高性能:负载均衡集群允许系统承载更多访问用户。

3、高性价比:可以采用廉价的符合工业标准的硬件构造高性能的系统。

1.3 集群技术的实现

根据计算机集群技术的应用,目前常用的计算机集群系统主要有两种配置方式,即采用N节点配置和N+1节点配置。

N节点配置:计算机集群由N(N最小为2)个计算机节点组成,所有节点在正常情况下都具有自己的用户和工作负载。一个故障节点的资源能够通过故障恢复被转移到另外一个节点,但当剩余服务器承担额外负载的时候,其性能将有所下降。

N+1节点配置:计算机集群由N+1(N最小为2)个计算机节点组成,其中一个节点为热待机节点,它在其它节点正常运行期间一直处于空闲模式。而当运行的节点中某节点发生故障时,则空闲节点负责接管故障节点的工作,从而避免整个系统的性能下降。但是,由于待机节点在正常情况下并不提供服务,因而成本较高。

1.4 EZSonar高可用集群需求和背景

EZSonar产品是过旁路采集网络数据方式,再对流量进行解码,从而对业务系统进行监控。随着监控的业务系统增多,采集流量并发也增大,交易量增多,这样产品原架构就会出现性能瓶颈。同时原产品架构各进程都是单点部署,容易出现单点故障。

为此针对交易监控系统组件存在的问题,提出的集群部署方案,来解决单点问题及提高性能瓶颈,避免单节点故障,增加系统稳定性。  

第二章 ES集群

2.1 ES集群基本原理

2.1.1 基本概念

1、cluster(集群)

集群中由一个或者多个节点组成,其中有一个为主节点,这个主节点是可以通过选举产生的,主从节点是对于集群内部来说的。

集群有以下特点:

1) 集群内节点协同工作,共享数据,并共同分担工作负荷。

2) 由于节点是从属集群的,集群会自我重组来均匀地分发数据。

3) cluster Name是很重要的,因为每个节点只能是群集的一部分,当该节点被设置为相同的名称时,就会自动加入群集。

4) 集群中通过选举产生一个master节点,它将负责管理集群范畴的变更,例如创建或删除索引,添加节点到集群或从集群删除节点。master 节点无需参与文档层面的变更和搜索,这意味着仅有一个 master 节点并不会因流量增长而成为瓶颈。任意一个节点都可以成为 master 节点。我们例举的集群只有一个节点,因此它会扮演 master 节点的角色。

5) 作为用户,我们可以访问包括 master 节点在内的集群中的任一节点。每个节点都知道各个文档的位置,并能够将我们的请求直接转发到拥有我们想要的数据的节点。无论我们访问的是哪个节点,它都会控制从拥有数据的节点收集响应的过程,并返回给客户端最终的结果。

2、node(节点)

一个节点是一个逻辑上独立的服务,可以存储数据,并参与集群的索引和搜索功能, 一个节点也有唯一的名字,群集通过节点名称进行管理和通信。

3、index(索引)

索引与关系型数据库实例(Database)相当。索引只是一个 逻辑命名空间,它指向一个或多个分片(shards),内部用Apache Lucene实现索引中数据的读写。

4、Type(文档类型)

相当于数据库中的table概念。每个文档在ElasticSearch中都必须设定它的类型。文档类型使得同一个索引中在存储结构不同文档时,只需要依据文档类型就可以找到对应的参数映射(Mapping)信息,方便文档的存取。

5、Document(文档)

相当于数据库中的row, 是可以被索引的基本单位。在一个索引中,您可以存储多个的文档(文档格式是json)。虽然在一个索引中有多份文档,但这些文档的结构是一致的,并在第一次存储的时候指定, 文档属于一种类型(type),各种各样的类型存在于一个索引中。

6、shard(分片)和replica(副本)

代表索引分片,es可以把一个完整的索引分成多个分片,这样的好处是可以把一个大的索引拆分成多个,分布到不同的节点上,构成分布式搜索。主分片的数量只能在索引创建前指定,并且索引创建后不能更改,实际上,这个数字定义了能存储到索引中的数据最大量(具体的数量取决于你的数据,硬件的使用情况)。副本是主分片的一个副本,它用于用于冗余数据及提高搜索性能,从分片的数量可以在运行的集群中动态的调整,这样我们就可以根据实际需求扩展或者缩小规模。

7、recovery(数据恢复)

代表数据恢复或叫数据重新分布,es在有节点加入或退出时会根据机器的负载对索引分片进行重新分配,挂掉的节点重新启动时也会进行数据恢复。

8、discovery.zen

代表es的自动发现节点机制,es是一个基于p2p的系统,它先通过广播寻找存在的节点,再通过多播协议来进行节点之间的通信,同时也支持点对点的交互。

9、Transport

代表es内部节点或集群与客户端的交互方式,默认内部是使用tcp协议进行交互,同时它支持http协议(json格式)、thrift、servlet、memcached、zeroMQ等的传输协议(通过插件方式集成)。

2.1.2 ES集群启动原理

1、 单一节点集群

2.1.2.1-1.png
2.1.2.2.png

启动流程:

(1) 先读取hostname信息、es版本信息,加载数据目录等;

(2) 启动节点;

(3) 查看是否有集群和对应的集群master;

(4) 如果没有master,就会选举master节点;

(5) 恢复数据。 如下图启动日志:

2.1.2.3.png

总结:一节点存在很大风险,如果这个节点故障了,那所有的数据都会丢失,因此单节点ES集群会有数据丢失的风险。

2、 两个节点集群

2.1.2.4.png
2.1.2.5.png

启动流程:

(1) ES2节点启动,启动时与ES1启动方式一样;

(2) 集群自我感知,把ES2节点加到集群中,原集群成双节点集群;(配置ES2与ES1的 cluster.name 相同)

(3) ES1把副本分配到ES2节点(复制分配过程中,先是存储在主分片中,然后平行复制到关联的复制节点上);

ES1新增的日志:

2.1.2.6.png

ES2的日志:

2.1.2.7.png

总结:双节点集群确保我们的数据在节点和复制节点上都可以被检索,同时也意味着在丢失一个节点的情况下,依旧能保证数据的完整性。双节点是能保证单节点故障,还没达到高可用性,仅仅是保证数据的完整性。

3、 三个节点集群

2.1.2.8.png
2.1.2.9.png

启动流程:

(1) ES3节点启动,启动时与ES1启动方式一样;

(2) 集群自我感知,把ES3节点加到集群中,原集群变为三节点集群;(配置ES3与ES1、ES2的 cluster.name 相同)

(3) ES3节点的数据是由ES1和ES2转移过去,最后三个节点都含2个分片,代替原来每个节点3个分片。

ES1新增的日志:

2.1.2.10.png

ES2新增的日志:

2.1.2.11.png

ES3的日志:

2.1.2.12.png

总结:三节点,6个分片,每个节点含2个分片,这意味着每个节点的硬件资源(CPU、RAM、I/O)被较少的分片共享,这样每个分片就会有更好的表现。从而性能比原来双节点集群提高,达到高可用。

2.1.3 ES集群故障切换原理

当集群中,一个节点挂掉,ES集群是怎样切换的呢?

1、 master节点故障切换原理 master节点故障,首先是要重新选举master,master选举机制是根据加入集群的先后顺序

(1) master节点故障(例如ES1),ES1原来的主分片1和主分片2丢失,缺少主分片的时候索引是不能工作;

(2) 重新选举新的master(例如ES3);

(3) 由于丢失主分片的副本是分布在其他节点上,所以新的master(ES3)此时需提升ES2和ES3节点中的相应副本,让它们成为主分片,此时集群的健康状态为yellow;

(4) 由于集群有三个主分片,由于相同数据不能同时存储同一个节点,因此到这里,会导致把剩下的分片分下次,从而达到集群要求。此时集群的健康状态因此就变为green;

(5) 当新的master(ES3)也随着故障,集群会重新再选举master(ES2),由于ES2节点保存了每个分配的副本,所以应用是不会对数据丢失。

如下图所示:

2.1.3.1.png
2.1.3.2.png
2.1.3.3.png


2、 普通节点故障切换原理

非master节点故障,切换过程比master节点故障少一个选举过程,由master节点对剩下的分配根据相同数据不能同时存储同一个节点原则进行分配,从而达到数据正常。

2.1.4 ES集群节点恢复原理

当故障的节点恢复,ES集群自我感知到,发现新节点,因此把该节点加入到集群中,同时master节点会把集群原来两个节点的分片移到到新节点,从而达到数据的平衡和高可用性能。

2.2 ES集群架构规划

2.2.1 shard和复制分片的规划

ES集群通过分片( shard )和副本( replica )实现了高性能、高伸缩和高可用。分片技术为大规模并行索引和搜索提供了支持,极大地提高了索引和搜索的性能,极大地提高了水平扩展能力;副本技术为数据提供冗余,部分机器故障不影响系统的正常使用,保证了系统的持续高可用。

主分片的数量在创建索引时已经给定。实际上,这个数字定义了能存储到索引里数据的最大数量(实际的数量取决于你的数据、硬件和使用情况)。当然,读请求搜索和文档检索能够通过主分片或者副本处理,所以数据的冗余越多。副本的数量可以在运行中的集群中动态地变更,这允许我们可以根据需求扩大或者缩小规模。

主分片的数量取决于数据的量级来定,副本的数量可以让集群横向拓展点,但是不是越多副本越好,当副本太多,因为大部分请求都聚集到了分片少的节点,导致一个节点吞吐量太大,反而降低性能。

建议集群至少含3个主分片和1个副本。

例如:

index.number_of_replicas: 1

index.number_of_shards: 3

2.2.2 node角色规划

每个节点是否允许被选举为主节点,是否允许存储数据,都是可以配置的。不同情况,有不同效果。

1、如果你想该节点可以为主节点和数据存储(默认情况);

node.master: true

node.data: true

2、如果你想该节点成为集群的“负载器”,那应该把这个节点设置为数据存储节点,非主节点;

node.master: false

node.data: true

3、如果你想该节点成为集群的“分配器”,那应该把这个节点只设置为主节点;

node.master: true

node.data: false

4、如果你想该节点成为集群的“搜索负载均衡器”,那应该把该节点设置非master和data;

node.master: false

node.data: false

2.2.3 硬件规划

整个集群的节点分为以下四种类型:

1、 既是master又是data节点:负责维护集群状态,又保存数据,硬件要求:配置要求越高越好,给es进程分配24g内存,硬盘最好是SSD硬盘;

2、 Master 节点:负责维护集群状态,不保存index数据,硬件要求: 一般性能的机器就可以,给es进程分配16g内存;

3、 只是data节点,只保存index的数据,不被选举为Master nodes 硬件要求: 配置要求越高越好,使用大硬盘,有条件可以上SSD硬盘;

4、 Client Nodes:主要用于负载均衡,不被选举为Master node, 也不保存index数据,硬件要求: 4核CPU, 64G内存或更高;

一个合理的集群应该包含三个master nodes, 1到多个data nodes, 最少一个client node,同时每个节点最好单独部署在一台硬件服务器。

2.3 ES安装

2.3.1 JDK安装

ES依赖java,因此安装ES前先确保安装好jdk,在这里选择jdk-8u65-linux-x64.rpm包。如果安装好的系统已经安装过jdk,并且jdk版本低于8,要先卸载低版本,再安装指定版本。

步骤如下:

 检查系统是否存在jdk

[root@ezsonar]# rpm -qa|grep jdk

如存在,卸载,如下

[root@ezsonar]# rpm -e jdk1.7.0_65-1.7.0_65-fcs.x86_64

 安装jdk

[root@ezsonar]# rpm -ivh jdk-8u65-linux-x64.rpm

 添加环境变量,编辑/etc/profile文件,添加以下内容

export JAVA_HOME=/usr/java/default

export JRE_HOME=/usr/java/default/jre export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar

export PATH=$PATH:/sbin:/bin:/usr/sbin:/usr/bin:/usr/X11R6/bin:$JAVA_HOME/bin

2.3.2 ntp安装和设置

EZSonar的解码引擎和分析引擎必须进行时间同步,否则数据会出现严重的准确性问题。

如果客户环境部署了NTP服务器,则配置解码引擎和分析引擎同客户环境的NTP服务器进行同步。如果客户环境无NTP服务器,则配置分析引擎为NTP服务器,解码引擎为NTP客户端,彼此进行数据同步。

设置ntp步骤如下:

 设置ntpd开机自启动

[root@ezsonar]# chkconfig ntpd on

 配置ntp服务连接

[root@ezsonar]# vi /etc/ntp.conf

 修改server的地址,示例使用了外网的NTP服务器地址

server 202.112.10.36

server 202.118.1.81

server 202.118.1.130

 启动ntpd服务

[root@ezsonar]# service ntpd start

 查看ntp的时间同步状态

[root@ezsonar]# ntpq -np

2.3.3 系统参数配置

 在/etc/profile文件最后面添加

[root@ezsonar]# vi /etc/profile

ulimit -s unlimited

unset i

 在limits.conf文件最后面添加

[root@ezsonar]# vi /etc/security/limits.conf

  • soft nofile 64000
  • hard nofile 64000
  • soft nproc unlimited
  • hard nproc unlimited
  • soft memlock unlimited
  • hard memlock unlimited

 将90-nproc.conf文件root那行1024修改成unlimited

[root@ezsonar]# vi /etc/security/limits.d/90-nproc.conf

  • soft nproc unlimited

root soft nproc unlimited

2.3.4 安装ES

elasticsearch安装如下:

 安装路径elasticsearch.tar.gz

[root@ezsonar]#tar -xf elasticsearch.tar.gz -C /usr/local/ezsonar/

2.4 ES集群配置

2.4.1 内存设置

默认24G,可根据虚拟机内存大小做更改,ES_MIN_MEM和ES_MAX_MEM设置的值要相同。

[root@ezsonar]# vi /usr/local/ezsonar/elasticsearch/bin/elasticsearch.in.sh/
1 if [ "x$ES_MIN_MEM" = "x" ]; then 2     ES_MIN_MEM=1g 3 fi 4 if [ "x$ES_MAX_MEM" = "x" ]; then 5 ES_MAX_MEM=1g 
2.4.2 ES节点配置

1、 配置文件详解

(1)集群名称

cluster.name: fushionskye

配置es的集群名称,默认是elasticsearch,es会自动发现在同一网段下的es,如果在同一网段下有多个集群,就可以用这个属性来区分不同的集群。

(2)节点名

node.name: "ES1"

(3)节点是否有资格被选举为master,默认为true。

node.master: true

(4)指定该节点是否存储索引数据,默认为true。

node.data: true

(5)设置默认索引分片个数,默认为5片。

index.number_of_shards: 5

(6)设置默认索引副本个数,默认为1个副本。

index.number_of_replicas: 1

(7)设置索引数据的存储路径,默认是es根目录下的data文件夹,可以设置多个存储路径,用逗号隔开.

path.data: /ezdata/es/

(8)设置日志文件的存储路径

path.logs: /var/log/ezsonar/es

(9)设置绑定的ip地址,可以是ipv4或ipv6的,默认为0.0.0.0。

network.bind_host: 192.168.0.1

(10)设置其它节点和该节点交互的ip地址。

network.publish_host: 192.168.0.1

(11)这个参数是用来同时设置bind_host和publish_host上面两个参数。

network.host: 192.168.0.1

(12)设置节点间交互的tcp端口,默认是9300。

transport.tcp.port: 9300

(13)设置是否压缩tcp传输时的数据,默认为false,不压缩。

transport.tcp.compress: true

(14)设置对外服务的http端口,默认为9200。

http.port: 9200 (15)设置内容的最大容量,默认100mb

http.max_content_length: 100mb

(16)设置集群中N个节点启动时进行数据恢复,默认为1。

gateway.recover_after_nodes: 1

(17)设置初始化数据恢复进程的超时时间,默认是5分钟。

gateway.recover_after_time: 5m

(18)设置这个集群中节点的数量,默认为2。

gateway.expected_nodes: 2

(19)设置这个参数来保证集群中的节点可以知道其它N个有master资格的节点。默认为1,对于大的集群来说,设置规则为(N/2+1)。

discovery.zen.minimum_master_nodes: 1

(20)设置集群中自动发现其它节点时ping连接超时时间,默认为3秒,对于比较差的网络环境可以高点的值来防止自动发现时出错。

discovery.zen.ping.timeout: 3s

(21)设置是否打开多播发现节点,默认是true。

discovery.zen.ping.multicast.enabled: false

(22)设置集群中master节点的初始列表,可以通过这些节点来自动发现新加入集群的节点。

discovery.zen.ping.unicast.hosts: ["host1", "host2:port", "host3[portX-portY]"]

(23)慢日志参数设置

index.search.slowlog.threshold.query.warn: 8s

index.search.slowlog.threshold.query.info: 3s index.search.slowlog.threshold.fetch.warn: 1s

index.search.slowlog.threshold.fetch.info: 800ms

index.indexing.slowlog.threshold.index.warn: 10s

index.indexing.slowlog.threshold.index.info: 5s

monitor.jvm.gc.old.warn: 10s

monitor.jvm.gc.old.info: 5s monitor.jvm.gc.old.debug: 2s

monitor.jvm.gc.ConcurrentMarkSweep.warn: 10s

monitor.jvm.gc.ConcurrentMarkSweep.info: 5s

monitor.jvm.gc.ConcurrentMarkSweep.debug: 2s

2、 涉及参数配置

(1)、 修改集群名称

luster.name: fusionskye

(2)、 修改节点名称

node.name: ES1

(3)、 修改master和data参数

node.master: true node.data: true

(4)、 配置集群参数

discovery.zen.ping.multicast.enabled: false

discovery.zen.ping.unicast.hosts:

["192.168.137.8","192.168.137.9:9300","192.168.137.10:9300"]

discovery.zen.minimum_master_nodes: 1

(5)、 配置主分片和副本

index.number_of_replicas: 1

index.number_of_shards: 3

2.4.3 tomcat配置

在路径/usr/local/ezsonar/tomcat7/conf/ezsonar下修改indexer.properties配置文件

修改indexer.host项,保证每个节点都连接tomcat。

indexer.host=192.168.137.8:9300,192.168.137.9:9300,192.168.137.10:9300

2.4.4 collector配置
修改collector的es-source.properties配置文件,修改时注意channel数和sink数要一致,同时至少每个ES节点有两个详细索引的sink,一个统计索引的sink。配置文件如下所示:
  1 agentes.sources = source1 memSrc aggegation   2 agentes.sinks = sink1 sink2 sink3 sink4 sink5sink6 sink7 sink8 sink9 sink10 sink11 sink12   3 agentes.channels = channel1 channel2 channel3 channel4 channel5 channel6 channel7 channel8 channel9 channel10 channel11 channel12   4    5 # Describe/configure source1   6 agentes.sources.source1.type = avro   7 agentes.sources.source1.bind = 0.0.0.0   8 agentes.sources.source1.port = 44444   9 agentes.sources.source1.channels = channel1 channel2 channel3 channel4 channel5 channel6 channel7 channel8 channel9 channel10 channel11 channel12  10 agentes.sources.source1.selector.type = com.fusionskye.ezsonar.collector.channel.BalanceChannelSelector  11 agentes.sources.source1.selector.summaryIndex = channel10 channel11 channel12  12 agentes.sources.source1.interceptors = interceptor1  13 agentes.sources.source1.interceptors.interceptor1.type = com.fusionskye.ezsonar.collector.interceptor.EZSonarSourceInterceptor$Builder  14 agentes.sources.source1.interceptors.interceptor1.mongoHost = 127.0.0.1  15 agentes.sources.source1.interceptors.interceptor1.mongoPort = 27017  16 agentes.sources.source1.interceptors.interceptor1.mongoDatabase = ezsonar  17 agentes.sources.source1.interceptors.interceptor1.mongoUser = ezsonaruser  18 agentes.sources.source1.interceptors.interceptor1.mongoPassword = 123  19 agentes.sources.source1.interceptors.interceptor1.host = ezsonar_host  20 agentes.sources.source1.interceptors.interceptor1.streamFrequency = 60   21 agentes.sources.source1.interceptors.interceptor1.hostLocationFrequency = 60  22 agentes.sources.source1.interceptors.interceptor1.metricFrequency = 20  23 agentes.sources.source1.interceptors.interceptor1.cacheMaximumSize = 1000  24 agentes.sources.source1.interceptors.interceptor1.cacheExpire = 10  25 agentes.sources.source1.interceptors.interceptor1.filters = geo_ip, add_field, busi_filter  26 agentes.sources.source1.interceptors.interceptor1.databaseFile = /usr/local/ezsonar/collector/GeoLite2-City.mmdb  27 #agentes.sources.source1.interceptors.interceptor1.statsFile = /var/log/ezsonar/collector/collector_source_benchmark.log  28 #agentes.sources.source1.interceptors.interceptor1.statsMetricFilter = .*[t|T]ime.*  29 #agentes.sources.source1.interceptors.interceptor1.statsMetricFilter = aaaaa  30 agentes.sources.source1.interceptors.interceptor1.amountIsYuan = false  31 agentes.sources.source1.interceptors.interceptor1.debugStatus = false  32 agentes.sources.source1.interceptors.interceptor1.debugMessage = false  33 agentes.sources.source1.interceptors.interceptor1.timerStatus = true   34 agentes.sources.source1.interceptors.interceptor1.ttmGShortMessage = %{INT\:facility}[\\|]%{DATA\:_probe_name}[\\|]%{IP\:_src_ip}[\\|]%{INT\:_sport}[\\|]%{IP\:_dst_ip}[\\|]%{INT\:_dport}[\\|]%{DATA\:_trans_id}[\\|]%{DATA\:_trans_ref}[\\|]%{DATA\:_ret_code}[\\|]%{DATA\:_ret_code_x}[\\|]%{INT\:_in_pkts}[\\|]%{INT\:_in_bytes}[\\|]%{INT\:_out_pkts}[\\|]%{INT\:_out_bytes}[\\|]%{INT\:_in_retran}[\\|]%{INT\:_out_retran}[\\|]%{INT\:_in_ooo}[\\|]%{INT\:_out_ooo}[\\|]%{INT\:_latency_msec}[\\|]%{INT\:_tot_syn}[\\|]%{INT\:_tot_synack}[\\|]%{INT\:_tot_fin}[\\|]%{INT\:_tot_fin_s}[\\|]%{INT\:_tot_rst}[\\|]%{INT\:_tot_rst_s}[\\|]%{INT\:_tot_zero_server}[\\|]%{INT\:_tot_zero_client}[\\|]%{INT\:_rtt}[\\|]%{INT\:_start_at}[\\|]%{INT\:_start_at_ms}[\\|]%{DATA\:_start_at_s}[\\|]%{INT\:_cip}[\\|]%{INT\:_sip}[\\|]%{INT\:_protocol}[\\|]%{INT\:_trans_transfer_ms}  35 #agentes.sources.source1.interceptors.interceptor1.ntmGShortMessage = %{INT\:facility}[\\|]%{DATA\:_probe_name}[\\|]%{IP\:_src_ip}[\\|]%{INT\:_sport}[\\|]%{IP\:_dst_ip}[\\|]%{INT\:_dport}[\\|]%{INT\:_protocol}[\\|]%{INT\:_start_at}[\\|]%{INT\:_start_at_ms}[\\|]%{INT\:_in_bytes}[\\|]%{INT\:_out_bytes}[\\|]%{INT\:_in_pkts}[\\|]%{INT\:_out_pkts}[\\|]%{INT\:_in_retran}[\\|]%{INT\:_out_retran}[\\|]%{INT\:_nw_delay_c2p_s}[\\|]%{INT\:_nw_delay_c2p_us}[\\|]%{INT\:_nw_delay_p2s_s}[\\|]%{INT\:_nw_delay_p2s_us}[\\|]%{DATA\:_flow_state} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值