RocketMQ一:快速入门和集群架构

RocketMQ快速实战和集群架构

RocketMQ快速实战

下载安装
RocketMQ的官网地址: http://rocketmq.apache.org ,github地址是 https://github.com/apache/rocketmq 。
最新版本的RocketMQ可以到官网上进行下载。历史版本需要到Github仓库中下载。下载地址:https://github.com/apache/rocketmq/releases。本次案例采用4.9.1版本
下载上传到Linux服务器,解压。rocketmq需要安装jdk。
配置rocketmq的环境变量 vim /etc/profile

export ROCKETMQ_HOME=/usr/local/rocketmq/rocketmq-all-4.9.1-bin-release
export PATH=$JAVA_HOME/bin:$ROCKETMQ_HOME/bin:$PATH

刷新配置

source /etc/profile

这样RocketMQ就安装完成了

快速运行RocketMQ

RocketMQ由以下这几个组件组成
NameServer : 提供轻量级的Broker路由服务。
Broker:实际处理消息存储、转发等服务的核心组件。
Producer:消息生产者集群。通常是业务系统中的一个功能模块。
Consumer:消息消费者集群。通常也是业务系统中的一个功能模块。

所以我们要启动RocketMQ服务,需要先启动NameServer。
启动NameServer服务
启动NameServer非常简单, 在$ROCKETMQ_HOME/bin目录下有个mqadminsrv。直接执行这个脚本就可以启动RocketMQ的NameServer服务。
​ 但是要注意,RocketMQ默认预设的JVM内存是4G,这是RocketMQ给我们的最佳配置。但是通常我们用虚拟机的话都是不够4G内存的,所以需要调整下JVM内存大小。修改的方式是直接修改runserver.sh。 用 vim /usr/local/rocketmq/rocketmq-all-4.9.1-bin-release/bin/runserver.sh 编辑这个脚本,在脚本中找到这一行

JAVA_OPT="${JAVA_OPT} -server -Xms4g -Xmx4g -Xmn2g -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=320m"

调整内存大小为512M

JAVA_OPT="${JAVA_OPT} -server -Xms512m -Xmx512m -Xmn256m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=320m"

启动NameServer服务:nohup bin/mqnamesrv &
启动完成后,在nohup.out里看到这一条关键日志就是启动成功了。并且使用jps指令可以看到有一个NamesrvStartup进程。

在这里插入图片描述
在这里插入图片描述

启动Broker服务

启动Broker的脚本是runbroker.sh。Broker的默认预设内存是8G,启动前,如果内存不够,同样需要调整下JVM内存。vim /usr/local/rocketmq/rocketmq-all-4.9.1-bin-release/bin/runbroker.sh,找到这一行

JAVA_OPT="${JAVA_OPT} -server -Xms8g -Xmx8g"

进行内存调整

JAVA_OPT="${JAVA_OPT} -server -Xms512m -Xmx512m"

然后我们需要找到$ROCKETMQ_HOME/conf/broker.conf, vi指令进行编辑,在最下面加入一个配置autoCreateTopicEnable=true
启动broker服务:nohup bin/mqbroker -n localhost:9876 >> broker_info.log & 同样查看是否成功
在这里插入图片描述
查看broker是否注册到NameServer服务:bin/mqadmin clusterList -n localhost:9876
在这里插入图片描述
表示成功注册

测试消息发送接收

在RocketMQ的安装包中,提供了一个tools.sh工具可以用来在命令行快速验证RocketMQ服务。首先需要配置一个环境变量NAMESRV_ADDR指向我们启动的NameServer服务。export NAMESRV_ADDR='localhost:9876'
然后启动消息生产者发送消息:默认会发1000条消息:bin/tools.sh org.apache.rocketmq.example.quickstart.Producer
在这里插入图片描述
这日志中,上面部分就是我们发送的消息的内容。后面两句标识消息生产者正常关闭
启动消费者消费消息:bin/tools.sh org.apache.rocketmq.example.quickstart.Consumer
在这里插入图片描述
日志中MessageExt后的整个内容就是一条完整的RocketMQ消息。我们要对这个消息的结构有个大概的了解,其中比较关键的属性有:brokerName,queueId,msgId,topic,cluster,tags,body,transactionId。

关闭rocketmq服务

要关闭RocketMQ服务可以通过mqshutdown脚本直接关闭

# 1.关闭NameServer
sh bin/mqshutdown namesrv
# 2.关闭Broker
sh bin/mqshutdown broker

RocketMQ集群架构

概念解析
一个完整的RocketMQ集群中,有如下几个角色

Producer:消息的发送者;举例:发信者
Consumer:消息接收者;举例:收信者
Broker:暂存和传输消息;举例:邮局
NameServer:管理Broker;举例:各个邮局的管理机构
Topic:区分消息的种类;一个发送者可以发送消息给一个或者多个Topic;一个消息的接收者可以订阅一个或者多个Topic消息
Message Queue:相当于是Topic的分区;用于并行发送和接收消息(在我们之前的测试案例中,一个queueId就代表了一个MessageQueue。有哪些queueId? 0,1,2,3四个)

在RocketMQ的conf目录下,有各种配置目录是rocketmq建议的各种配置方式
在这里插入图片描述

2m-2s-async: 2主2从异步刷盘(吞吐量较大,但是消息可能丢失),
2m-2s-sync:2主2从同步刷盘(吞吐量会下降,但是消息更安全),
2m-noslave:2主无从(单点故障),然后还可以直接配置broker.conf,进行单点环境配置。
而dleger就是用来实现主从切换的。集群中的节点会基于Raft协议随机选举出一个leader,其他的就都是follower。通常正式环境都会采用这种方式来搭建集群。

配置RocketMQ主从集群

本次案例搭建一个2主2从异步刷盘的集群

服务ip	nemaeServer节点部署	broker节点部署
192.168.213.138	nameserver	broker-a, broker-b-s
192.168.213.139	nameserver	broker-b,broker-a-s

broker-b-s表示broker-b的从节点,broker-a-s表示broker-a的从节点

搭建步骤

一:在138服务器配置broker-a 主节点
修改conf/2m-2s-async下的broker-a.properties
#所属集群名字,名字一样的节点就在同一个集群内

brokerClusterName=rocketmq-cluster
#broker名字,名字一样的节点就是一组主从节点。
brokerName=broker-a
#brokerid,0就表示是Master,>0的都是表示 Slave
brokerId=0
#nameServer地址,分号分割
namesrvAddr=192.168.10.138:9876;192.168.10.139:9876
#在发送消息时,自动创建服务器不存在的topic,默认创建的队列数
defaultTopicQueueNums=4
#是否允许 Broker 自动创建Topic,建议线下开启,线上关闭
autoCreateTopicEnable=true
#是否允许 Broker 自动创建订阅组,建议线下开启,线上关闭
autoCreateSubscriptionGroup=true
#Broker 对外服务的监听端口
listenPort=10911
#删除文件时间点,默认凌晨 4点
deleteWhen=04
#文件保留时间,默认 48 小时
fileReservedTime=120
#commitLog每个文件的大小默认1G
mapedFileSizeCommitLog=1073741824
#ConsumeQueue每个文件默认存30W条,根据业务情况调整
mapedFileSizeConsumeQueue=300000
#destroyMapedFileIntervalForcibly=120000
#redeleteHangedFileInterval=120000
#检测物理文件磁盘空间
diskMaxUsedSpaceRatio=88
#存储路径
storePathRootDir=/app/rocketmq/m_store
#commitLog 存储路径
storePathCommitLog=/app/rocketmq/m_store/commitlog
#消费队列存储路径存储路径
storePathConsumeQueue=/app/rocketmq/m_store/consumequeue
#消息索引存储路径
storePathIndex=/app/rocketmq/m_store/index
#checkpoint 文件存储路径
storeCheckpoint=/app/rocketmq/m_store/checkpoint
#abort 文件存储路径
abortFile=/app/rocketmq/m_store/abort
#限制的消息大小
maxMessageSize=65536
#flushCommitLogLeastPages=4
#flushConsumeQueueLeastPages=2
#flushCommitLogThoroughInterval=10000
#flushConsumeQueueThoroughInterval=60000
#Broker 的角色
#- ASYNC_MASTER 异步复制Master
#- SYNC_MASTER 同步双写Master
#- SLAVE
brokerRole=ASYNC_MASTER
#刷盘方式
#- ASYNC_FLUSH 异步刷盘
#- SYNC_FLUSH 同步刷盘
flushDiskType=ASYNC_FLUSH
#checkTransactionMessageEnable=false
#发消息线程池数量
#sendMessageThreadPoolNums=128
#拉消息线程池数量
#pullMessageThreadPoolNums=128

二:在139配置broker-a-s从节点
修改conf/2m-2s-async下的broker-a-s.properties

#所属集群名字,名字一样的节点就在同一个集群内
brokerClusterName=rocketmq-cluster
#broker名字,名字一样的节点就是一组主从节点。
brokerName=broker-a
#brokerid,0就表示是Master,>0的都是表示 Slave
brokerId=1
#nameServer地址,分号分割
namesrvAddr=192.168.10.138:9876;192.168.10.139:9876
#在发送消息时,自动创建服务器不存在的topic,默认创建的队列数
defaultTopicQueueNums=4
#是否允许 Broker 自动创建Topic,建议线下开启,线上关闭
autoCreateTopicEnable=true
#是否允许 Broker 自动创建订阅组,建议线下开启,线上关闭
autoCreateSubscriptionGroup=true
#Broker 对外服务的监听端口
listenPort=10911
#删除文件时间点,默认凌晨 4点
deleteWhen=04
#文件保留时间,默认 48 小时
fileReservedTime=120
#commitLog每个文件的大小默认1G
mapedFileSizeCommitLog=1073741824
#ConsumeQueue每个文件默认存30W条,根据业务情况调整
mapedFileSizeConsumeQueue=300000
#destroyMapedFileIntervalForcibly=120000
#redeleteHangedFileInterval=120000
#检测物理文件磁盘空间
diskMaxUsedSpaceRatio=88
#存储路径
storePathRootDir=/app/rocketmq/s_store
#commitLog 存储路径
storePathCommitLog=/app/rocketmq/s_store/commitlog
#消费队列存储路径存储路径
storePathConsumeQueue=/app/rocketmq/s_store/consumequeue
#消息索引存储路径
storePathIndex=/app/rocketmq/s_store/index
#checkpoint 文件存储路径
storeCheckpoint=/app/rocketmq/s_store/checkpoint
#abort 文件存储路径
abortFile=/app/rocketmq/s_store/abort
#限制的消息大小
maxMessageSize=65536
#flushCommitLogLeastPages=4
#flushConsumeQueueLeastPages=2
#flushCommitLogThoroughInterval=10000
#flushConsumeQueueThoroughInterval=60000
#Broker 的角色
#- ASYNC_MASTER 异步复制Master
#- SYNC_MASTER 同步双写Master
#- SLAVE
brokerRole=SLAVE
#刷盘方式
#- ASYNC_FLUSH 异步刷盘
#- SYNC_FLUSH 同步刷盘
flushDiskType=ASYNC_FLUSH
#checkTransactionMessageEnable=false
#发消息线程池数量
#sendMessageThreadPoolNums=128
#拉消息线程池数量
#pullMessageThreadPoolNums=128

三:在139上配置broker-b主节点
修改conf/2m-2s-async下的broker-b.properties

#所属集群名字,名字一样的节点就在同一个集群内
brokerClusterName=rocketmq-cluster
#broker名字,名字一样的节点就是一组主从节点。
brokerName=broker-b
#brokerid,0就表示是Master,>0的都是表示 Slave
brokerId=0
#nameServer地址,分号分割
namesrvAddr=192.168.10.138:9876;192.168.10.139:9876
#在发送消息时,自动创建服务器不存在的topic,默认创建的队列数
defaultTopicQueueNums=4
#是否允许 Broker 自动创建Topic,建议线下开启,线上关闭
autoCreateTopicEnable=true
#是否允许 Broker 自动创建订阅组,建议线下开启,线上关闭
autoCreateSubscriptionGroup=true
#Broker 对外服务的监听端口
listenPort=11011
#删除文件时间点,默认凌晨 4点
deleteWhen=04
#文件保留时间,默认 48 小时
fileReservedTime=120
#commitLog每个文件的大小默认1G
mapedFileSizeCommitLog=1073741824
#ConsumeQueue每个文件默认存30W条,根据业务情况调整
mapedFileSizeConsumeQueue=300000
#destroyMapedFileIntervalForcibly=120000
#redeleteHangedFileInterval=120000
#检测物理文件磁盘空间
diskMaxUsedSpaceRatio=88
#存储路径
storePathRootDir=/app/rocketmq/m_store
#commitLog 存储路径
storePathCommitLog=/app/rocketmq/m_store/commitlog
#消费队列存储路径存储路径
storePathConsumeQueue=/app/rocketmq/m_store/consumequeue
#消息索引存储路径
storePathIndex=/app/rocketmq/m_store/index
#checkpoint 文件存储路径
storeCheckpoint=/app/rocketmq/m_store/checkpoint
#abort 文件存储路径
abortFile=/app/rocketmq/m_store/abort
#限制的消息大小
maxMessageSize=65536
#flushCommitLogLeastPages=4
#flushConsumeQueueLeastPages=2
#flushCommitLogThoroughInterval=10000
#flushConsumeQueueThoroughInterval=60000
#Broker 的角色
#- ASYNC_MASTER 异步复制Master
#- SYNC_MASTER 同步双写Master
#- SLAVE
brokerRole=ASYNC_MASTER
#刷盘方式
#- ASYNC_FLUSH 异步刷盘
#- SYNC_FLUSH 同步刷盘
flushDiskType=ASYNC_FLUSH
#checkTransactionMessageEnable=false
#发消息线程池数量
#sendMessageThreadPoolNums=128
#拉消息线程池数量
#pullMessageThreadPoolNums=128

四:在138上配置broker-b-s从节点
修改conf/2m-2s-async下的broker-b-s.properties

#所属集群名字,名字一样的节点就在同一个集群内
brokerClusterName=rocketmq-cluster
#broker名字,名字一样的节点就是一组主从节点。
brokerName=broker-b
#brokerid,0就表示是Master,>0的都是表示 Slave
brokerId=1
#nameServer地址,分号分割
namesrvAddr=192.168.10.138:9876;192.168.10.139:9876
#在发送消息时,自动创建服务器不存在的topic,默认创建的队列数
defaultTopicQueueNums=4
#是否允许 Broker 自动创建Topic,建议线下开启,线上关闭
autoCreateTopicEnable=true
#是否允许 Broker 自动创建订阅组,建议线下开启,线上关闭
autoCreateSubscriptionGroup=true
#Broker 对外服务的监听端口
listenPort=11011
#删除文件时间点,默认凌晨 4点
deleteWhen=04
#文件保留时间,默认 48 小时
fileReservedTime=120
#commitLog每个文件的大小默认1G
mapedFileSizeCommitLog=1073741824
#ConsumeQueue每个文件默认存30W条,根据业务情况调整
mapedFileSizeConsumeQueue=300000
#destroyMapedFileIntervalForcibly=120000
#redeleteHangedFileInterval=120000
#检测物理文件磁盘空间
diskMaxUsedSpaceRatio=88
#存储路径
storePathRootDir=/app/rocketmq/s_store
#commitLog 存储路径
storePathCommitLog=/app/rocketmq/s_store/commitlog
#消费队列存储路径存储路径
storePathConsumeQueue=/app/rocketmq/s_store/consumequeue
#消息索引存储路径
storePathIndex=/app/rocketmq/s_store/index
#checkpoint 文件存储路径
storeCheckpoint=/app/rocketmq/s_store/checkpoint
#abort 文件存储路径
abortFile=/app/rocketmq/s_store/abort
#限制的消息大小
maxMessageSize=65536
#flushCommitLogLeastPages=4
#flushConsumeQueueLeastPages=2
#flushCommitLogThoroughInterval=10000
#flushConsumeQueueThoroughInterval=60000
#Broker 的角色
#- ASYNC_MASTER 异步复制Master
#- SYNC_MASTER 同步双写Master
#- SLAVE
brokerRole=SLAVE
#刷盘方式
#- ASYNC_FLUSH 异步刷盘
#- SYNC_FLUSH 同步刷盘
flushDiskType=ASYNC_FLUSH
#checkTransactionMessageEnable=false
#发消息线程池数量
#sendMessageThreadPoolNums=128
#拉消息线程池数量
#pullMessageThreadPoolNums=128

这样2主2从的集群配置基本就完成了。搭建过程中需要注意的配置项:
1、同一机器上两个实例的store目录不能相同,否则会报错 Lock failed,MQ already started
2、同一机器上两个实例的listenPort也不能相同。否则会报端口占用的错
nameserver不需要进行配置,直接启动就行。这也看出nameserver是无状态的。
3、如果是多网卡的机器,比如云服务器,那么需要在broker.conf中增加brokerIP1属性,指定所在机器的外网网卡地址。
五:启动RocketMQ

服务启动前记得关闭防火墙或者开启对应端口

先启动nameServer

在138和139服务器上启动:nohup bin/mqnamesrv &
通过jps查看是否启动成功

在启动broker服务

在138服务上分别启动:

nohup bin/mqbroker -c conf/2m-2s-async/broker-a.properties >> broker_info.log &
nohup bin/mqbroker -c conf/2m-2s-async/broker-b-s.properties >> broker_info.log &

查看是否启动成功 cat broker_info.log
在这里插入图片描述
在139服务上分别启动:

nohup bin/mqbroker -c conf/2m-2s-async/broker-a-s.properties >> broker_info.log &
nohup bin/mqbroker -c conf/2m-2s-async/broker-b.properties >> broker_info.log &

同样方式查看是否启动成功
六:查看集群启动状态
RocketMQ源码中并没有提供管理控制台,只提供了一个mqadmin指令来管理RocketMQ。指令的位置在bin目录下。直接使用该指令就会列出所有支持的命令。
在这里插入图片描述
查看集群状态:bin/mqadmin clusterList -n 192.168.213.138:9876
在这里插入图片描述
七:集群测试
RocketMQ提供了一个tools.sh工具可以用来在命令行快速验证RocketMQ服务。
注意,这是官方提供的Demo,但是官方的源码中,这两个类都是没有指定nameServer的,所以运行会有点问题。要指定NameServer地址,可以配置一个环境变量NAMESRV_ADDR,这样默认会读取这个NameServer地址。
配置NameServer:vim /etc/profile

export NAMESRV_ADDR='192.168.10.138:9876;192.168.10.139:9876'

发送消息:bin/tools.sh org.apache.rocketmq.example.quickstart.Producer
在这里插入图片描述
接收消息:bin/tools.sh org.apache.rocketmq.example.quickstart.Consumer
在这里插入图片描述
到此2主2从集群搭建完毕 案例搭建的集群是主从备份的集群,并非高可用的集群,两个从节点主要为了负载均衡,而主节点挂掉,从节点并不能升级为主节点。但是可以通过其他方式,比如keeplive 实现挂了重启,也相当于高可用的功能。 从节点主要是为了数据备份。

如果要搭建高可用集群,根据conf/dleger目录下的案例搭建。后续有时间在演示高可用的案例

搭建管理控制台

RocketMQ源代码中并没有提供控制台,但是有一个Rocket的社区扩展项目中提供了一个控制台,地址:https://github.com/apache/rocketmq-dashboard
编译完成后,获取target下的jar包,就可以直接执行。但是这个时候要注意,在这个项目的application.yml中需要指定nameserver的地址。默认这个属性是指向本地。如果配置为空,会读取环境变量NAMESRV_ADDR。视情况要不要修改。
运行:nohup java -jar rocketmq-dashboard-1.0.1-SNAPSHOT.jar &
访问:http://192.168.213.138:8080
在这里插入图片描述

下面通过控制台对一些重要的概念进行分析。

RocketMQ消息模型概念

消息发送模型

RocketMQ主要由 Producer、Broker、Consumer 三部分组成,其中Producer 负责生产消息,Consumer 负责消费消息,Broker 负责存储消息。Broker 在实际部署过程中对应一台服务器,每个 Broker 可以存储多个Topic的消息,每个Topic的消息也可以分片存储于不同的 Broker。Message Queue 用于存储消息的物理地址,每个Topic中的消息地址存储于多个 Message Queue 中。ConsumerGroup 由多个Consumer 实例构成。
在这里插入图片描述

主题(Topic)

表示一类消息的集合,每个主题包含若干条消息,每条消息只能属于一个主题,是RocketMQ进行消息订阅的基本单位。

​ Topic只是一个逻辑概念,并不实际保存消息。同一个Topic下的消息,会分片保存到不同的Broker上,而每一个分片单位,就叫做MessageQueue。MessageQueue是一个具有FIFO特性的队列结构,生产者发送消息与消费者消费消息的最小单位。
在这里插入图片描述

MessageQueue

用来存储消息。
点击TopicTest旁边的状态按钮出现
在这里插入图片描述
queueId表示MessageQueue的id 。刚才搭建的集群是2主2从,其中从节点功能是备份,主节点主要是负载均衡。每一个主从就是一个broker,每个broker默认为主题创建4个MessageQueue。所以一共8个MessageQueue,id从0-4。其中最小位点和最大位点表示该MessageQueue存储的消息索引(也可以理解为偏移量)。索引从0开始,所以每个MessageQueue都有250条消息。
如果消息有删除,比如MessageQueue删除了前100条消息,那么最小位点就是100,最大位点就是250,那么该MessageQueue就有150条消息。
至于消息什么时候删除,根据之前的配置文件配置,也可以手动删除,但并不是消费过的消息就删除。

消费者 Consumer

负责消费消息,一般是后台系统负责异步消费。一个消息消费者会从Broker服务器拉取消息、并将其提供给应用程序。
​ 消费者同样会把同一类Consumer组成一个集合,叫做消费者组,这类Consumer通常消费同一类消息且消费逻辑一致。消费者组使得在消息消费方面,实现负载均衡和容错的目标变得非常容易。要注意的是,消费者组的消费者实例必须订阅完全相同的Topic。RocketMQ 支持两种消息模式:集群消费(Clustering)和广播消费(Broadcasting)。

  1. 集群消费模式下, 相同Consumer Group的每个Consumer实例平均分摊消息。
  2. 广播消费模式下,相同Consumer Group的每个Consumer实例都接收全量的消息。

在这里插入图片描述
在这里插入图片描述
订阅组就是消费者组,代理者位点表示 该MessageQueue消息最大位点,消费者位点就是消费者消费到了该MessageQueue那个位点,差值就是多少消息未消费,也就是消息堆积。

消息生产者(Producer)

负责生产消息,一般由业务系统负责生产消息。一个消息生产者会把业务应用系统里产生的消息发送到broker服务器。RocketMQ提供多种发送方式,同步发送、异步发送、顺序发送、单向发送。同步和异步方式均需要Broker返回确认信息,单向发送不需要。
​ 生产者中,会把同一类Producer组成一个集合,叫做生产者组。同一组的Producer被认为是发送同一类消息且发送逻辑一致。

代理服务器(Broker Server)

消息中转角色,负责存储消息、转发消息。代理服务器在RocketMQ系统中负责接收从生产者发送来的消息并存储、同时为消费者的拉取请求作准备。代理服务器也存储消息相关的元数据,包括消费者组、消费进度偏移和主题和队列消息等。
Broker 包含了MessageQueue 不包括Topic ;Topic是个逻辑概念最终落实到Broker 中的MessageQueue。
在这里插入图片描述

Name Server

名称服务充当路由消息的提供者。Broker Server会在启动时向所有的Name Server注册自己的服务信息,并且后续通过心跳请求的方式保证这个服务信息的实时性。生产者或消费者能够通过名字服务查找各主题相应的Broker IP列表。多个Namesrv实例组成集群,但相互独立,没有信息交换。

​ 这种特性也就意味着NameServer中任意的节点挂了,只要有一台服务节点正常,整个路由服务就不会有影响。当然,这里不考虑节点的负载情况。
在这里插入图片描述

消息(Message)

消息系统所传输信息的物理载体,生产和消费数据的最小单位,每条消息必须属于一个主题Topic。RocketMQ中每个消息拥有唯一的Message ID,且可以携带具有业务标识的Key。系统提供了通过Message ID和Key查询消息的功能。

​ 并且Message上有一个为消息设置的标志,Tag标签。用于同一主题下区分不同类型的消息。来自同一业务单元的消息,可以根据不同业务目的在同一主题下设置不同标签。标签能够有效地保持代码的清晰度和连贯性,并优化RocketMQ提供的查询系统。消费者可以根据Tag实现对不同子主题的不同消费逻辑,实现更好的扩展性。
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值