zookeeper及hadoop高可用

Zookeeper

Zookeeper是什么

Zookeeper是什么

  • Zookeeper是一个开源的分布式应用程序协调服务

Zookeeper能做什么

  • Zookeeper是用来保证数据在集群间的事务一致性

Zookeeper应用场景

  • 集群分布式锁
  • 集群统一命名服务
  • 分布式协调服务

角色与特性

Zookeeper角色与特性

  • Leader:接受所有Follower的提案请求并统一协调发起提案的投票,负责与所有的Follower进行内部数据交换
  • Follower:直接为客户端服务并参与提案的投票,同时与Leader进行数据交换
  • Observer:直接为客户端服务但并不参与提案的投票,同时也与Leader进行数据交换
角色与选举

Zookeeper角色与选举

  • 服务在启动的时候是没有角色的(LOOKING)
  • 角色是通过选举产生的
  • 选举产生一个Leader,剩下的是Follower

选举Leader原则

  • 集群中超过半数机器投票选择Leader
  • 假如集群中拥有n台服务器,那么Leader必须得到n/2+1台服务器的投票
  • 如果Leader死亡,重新选举Leader
  • 如果死亡的机器数量达到一半,则集群挂掉
  • 如果无法得到足够的投票数量,就重新发起投票,如果参与投票的机器不足n/2+1,则集群停止工作
  • 如果Follower死亡过多,剩余机器不足n/2+1,则集群也会停止工作
  • Observer不计算在投票总设备数量里面

Zookeeper原理与设计

在这里插入图片描述

zookeeper可伸缩扩展性原理与设计

  • Leader所有写相关操作
  • Follower读操作与响应Leader提议
  • 在Observer出现以前,Zookeeper的伸缩性由Follower来实现,我们可以通过添加Follower节点的数量来保证Zookeeper服务的读性能,但是随着Follower节点数量的增加,Zookeeper服务的写性能受到了影响
  • 客户端提交一个请求,若是读请求,则由每台Server的本地副本数据库直接响应。若是写请求,需要通过一致性协议(Zab)来处理
  • Zab协议规定:来自Client的所有写请求都要转发给ZK服务中唯一的Leader,由Leader根据该请求发起一个Proposal。然后其他的Server对该Proposal进行Vote。之后Leader对Vote进行收集,当Vote数量过半时Leader会向所有的Server发送一个通知消息。最后当Client所连接的Server收到该消息时,会把该操作更新到内存中并对Client的写请求做出回应
  • ZooKeeper在上述协议中实际扮演了两个职能。一方面从客户端接受连接与操作请求,另一方面对操作结果进行投票。这两个职能在Zookeeper集群扩展的时候彼此制约
  • 从Zab协议对写请求的处理过程中可以发现,增加Follower的数量,则增加了协议投票过程的压力。因为Leader节点必须等待集群中过半Server响应投票,是节点的增加使得部分计算机运行较慢,从而拖慢整个投票过程的可能性也随之提高,随着集群变大,写操作也会随之下降
  • 所以,我们不得不在增加Client数量的期望和我们希望保持较好吞吐性能的期望间进行权衡。要打破这一耦合关系,我们引入了不参与投票的服务器Observer。Observer可以接受客户端的连接,并将写请求转发给Leader节点。但Leader节点不会要求Observer参加投票,仅仅在上述第3歩那样,和其他服务节点一起得到投票结果
  • Observer的扩展,给Zookeeper的可伸缩性带来了全新的景象。加入很多Observer节点,无须担心严重影响写吞吐量。但并非是无懈可击,因为协议中的通知阶段,仍然与服务器的数量呈线性关系。但是这里的串行开销非常低。因此,我们可以认为在通知服务器
    阶段的开销不会成为瓶颈
  • Observer提升读性能的可伸缩性
  • Observer提供了广域网能力
Zookeeper集群

Zookeeper集群的安装配置

  • 配置文件改名zoo.cfg
    # mv zoo_sample.cfg zoo.cfg
  • zoo.cfg文件最后添加如下内容
server.1=node1:2888:3888
server.2=node2:2888:3888
server.3=node3:2888:3888
server.4=nn01:2888:3888:observer

zoo.cfg集群的安装配置

  • 创建datadir指定的目录
    # mkdir /tmp/zookeeper
  • 在目录下创建id对应主机名的myid文件

关于myid文件

  • myid文件中只有一个数字
  • 注意:请确保每个server的myid文件中id数字不同
  • server.id中的id与myid中的id必须一致
  • id的范围是1~255

(1)安装zookeeper并修改配置文件

[root@had-node1 ~]# cd /usr/local/zookeeper/conf/
[root@had-node1 conf]# mv zoo_sample.cfg zoo.cfg
[root@had-node1 conf]# vim zoo.cfg
...	...
server.1=had-node2:2888:3888
server.2=had-node3:2888:3888
server.3=had-node4:2888:3888
server.4=had-node1:2888:3888:observer		//申明observer

(2)同步到所有节点

[root@had-node1 ~]# for i in {71..73}
> do
> rsync -av --delete /usr/local/zookeeper root@192.168.1.$i:/usr/local/
> done

(3)每个节点(had-node1~had-node4)创建id目录并创建id

[root@had-node1 conf]# mkdir /tmp/zookeepermkdir /tmp/zookeeper
[root@had-node1 ~]# echo 4 > /tmp/zookeeper/myid	
[root@had-node2 ~]# echo 1 > /tmp/zookeeper/myid
[root@had-node3 ~]# echo 2 > /tmp/zookeeper/myid
[root@had-node4 ~]# echo 3 > /tmp/zookeeper/myid
  • 启动集群,查看验证(在所有集群节点执行)
    # /usr/local/zookeeper/bin/zkServer.sh start
  • 查看角色
    # /usr/local/zookeeper/bin/zkServer.sh status
  • Zookeeper管理文档
    http://zookeeper.apache.org/doc/r3.4.10/zookeeperAdmin.html

Kafka集群

什么是Kafka

Kafka是什么

  • Kafka是由LinkedIn开发的一个分布式的消息系统
  • Kafka是使用Scala编写
  • Kafka是一种消息中间件

为什么要使用Kafka

  • 解耦、冗余、提高扩展性、缓冲
  • 保证顺序,灵活,削峰填谷
  • 异步通信
Kafka角色

Kafka角色与集群结构

在这里插入图片描述

  • producer:生产者,负责发布消息
  • consumer:消费者,负责读取处理消息
  • topic:消息的类别
  • Parition:每个Topic包含一个或多个Partition
  • Broker:Kafka集群包含一个或多个服务器

Kafka通过Zookeeper管理集群配置,选举Leader

Kafka集群安装与配置

Kafka角色与集群结构

Kafka集群的安装配置

  • Kafka集群的安装配置依赖Zookeeper,搭建Kafka集群之前,请先创建好一个可用的Zookeeper集群
  • 安装OpenJDK运行环境
  • 同步Kafka拷贝到所有集群主机
  • 修改配置文件
  • 启动与验证

Kafka集群的安装配置

  • erver.properties
  • broker.id
  • 每台服务器的broker.id都不能相同

zookeeper.connect

  • zookeeper集群地址,不用都列出,写一部分即可

Kafka集群的安装配置

  • 在所有主机启动服务
    # ./bin/kafka-server-start.sh -daemon config/server.properties

验证

  • jps命令应该能看到Kafka模块
  • netstat应该能看到9092在监听

集群验证与消息发布

  • 创建一个 topic
    # ./bin/kafka-topics.sh --create --partitions 2 --replication-factor 2 \ --zookeeper localhost:2181 --topic mymsg
  • 生产者
    # ./bin/kafka-console-producer.sh \ --broker-list localhost:9092 --topic mymsg
  • 消费者
    # ./bin/kafka-console-consumer.sh \ --bootstrap-server localhost:9092 --topic mymsg

(1)安装kafka安装包到/usr/local/kafka

[root@had-node1 ~]# tar -xf hadoop/kafka_2.12-2.1.0.tgz 
[root@had-node1 ~]# mv kafka_2.12-2.1.0/ /usr/local/kafka

(2)修改配置文件

[root@had-node1 ~]# cd /usr/local/kafka/config/
[root@had-node1 config]# vim server.properties
...	...
broker.id=0
...	...
zookeeper.connect=had-node2:2181,had-node2:2181,had-node3:2181
...	...

(3)同步配置文件

[root@had-node1 ~]# for i in {71..73}
> do 
> rsync -av /usr/local/kafka  192.168.1.$i:/usr/local/
> done

(4)修改每个节点的broker.id,不要重复

(5)在所有主机启动服务

/usr/local/kafka/bin/kafka-server-start.sh -daemon /usr/local/kafka/config/server.properties

(6)验证,jps,netstat查看9092端口

(7)集群消息验证

had-node2创建一个topic

[root@had-node2 ~]# cd /usr/local/kafka/
[root@had-node2 kafka]# ./bin/kafka-topics.sh --create --partitions 2 --replication-factor 2 --zookeeper localhost:2181 --topic mymsg

had-node3 生产者,生产者发送消息,消费者可以收到

[root@had-node3 ~]# cd /usr/local/kafka/
[root@had-node3 kafka]# ./bin/kafka-console-producer.sh --broker-list localhost:9092 --topic mymsg

had-node4 消费者

[root@had-node4 ~]# cd /usr/local/kafka/
[root@had-node4 kafka]# ./bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic mymsg

Hadoop高可用

为什么需要NameNode

原因

  • NameNode是HDFS的核心配置,HDFS又是Hadoop核心组件,NameNode在Hadoop集群中至关重要
  • NameNode宕机,将导致集群不可用,如果NameNode数据丢失将导致整个集群的数据丢失,而NameNode的数据的更新又比较频繁,实现NameNode高可用势在必行

解决方案
官方提供了两种解决方案

  • HDFS with NFS
  • HDFS with QJM

两种方案异同

NFSQJM
NNNN
ZKZK
ZKFailoverControllerZKFailoverController
NFSJournalNode

HA方案对比

  • 都能实现热备
  • 都是一个Active NN和一个Standby NN
  • 都使用Zookeeper和ZKFC来实现自动失效恢复
  • 失效切换都使用Fencin配置的方法来Active NN
  • NFS数据共享变更方案把数据存储在共享存储里,我们还需要考虑NFS的高可用设计
  • QJM不需要共享存储,但需要让每一个DN都知道两个NN的位置,并把块信息和心跳包发送给Active和Standby这两个NN
使用方案

使用原因(QJM)

  • 解决NameNode单点故障问题
  • Hadoop给出了HDFS的高可用HA方案:HDFS通常由两个NameNode组成,一个处于Active状态,另一个处于Standby状态。Active NameNode对外提供服务,比如处理来自客户端的RPC请求,而Standby NameNode则不对外提供服务,仅同步Active NameNode的状态,以便能够在它失败时进行切换

典型的HA集群

  • NameNode会被配置在两台独立的机器上,在任何时候 , 一 个 NameNode 处 于 活 动 状 态 , 而 另 一 个NameNode则处于备份状态
  • 活动状态的NameNode会响应集群中所有的客户端,备份状态的NameNode只是作为一个副本,保证在必要的时候提供一个快速的转移
NameNode高可用

NameNode高可用架构

  • 为了让Standby Node与Active Node保持同步,这两个Node 都 与 一 组 称 为 JNS 的 互 相 独 立 的 进 程 保 持 通 信(Journal Nodes)。当Active Node更新了namespace,它将记录修改日志发送给JNS的多数派。Standby Node将会从JNS中读取这些edits,并持续关注它们对日志的变更
  • Standby Node将日志变更应用在自己的namespace中,当Failover发生时,Standby将会在提升自己为Active之前,确保能够从JNS中读取所有的edits,即在Failover发生之前Standy持有的namespace与Active保持完全同步
  • NameNode更新很频繁,为了保持主备数据的一致性,为了支持快速Failover,Standby Node持有集群中blocks的最新位置是非常必要的。为了达到这一目的,DataNodes上需要同时配置这两个Namenode的地址,同时和它们都建立心跳连接,并把block位置发送给它们
  • 任何时刻,只能有一个Active NameNode,否则会导致集群操作混乱,两个NameNode将会有两种不同的数据状态,可能会导致数据丢失或状态异常,这种情况通常称为"split-brain"(脑裂,三节点通讯阻断,即集群中不同的DataNode看到了不同的Active NameNodes)
  • 对于JNS而言,任何时候只允许一个NameNode作为writer;在Failover期间,原来的Standby Node将会接管Active的所有职能,并负责向JNS写入日志记录,这种机制阻止了其他NameNode处于Active状态的问题

NameNode架构图

  • NameNode高可用架构图

在这里插入图片描述

创建一台机器,192.168.1.77(had-node01-02),作为namenode的备用节点

(1)安装java-openjdk

[root@had-node01-02 ~]# yum -y install java-1.8.0-openjdk-devel

(2)配置本机可以ssh无密码登录所有主机,包括自己,配置had-node01可以ssh无密码登录本机

[root@had-node1 ~]# ssh-copy-id 192.168.1.77
[root@had-node01-02 ~]# ssh-keygen 
[root@had-node01-02 ~]# for i in {70..77}
> do
> ssh-copy-id 192.168.1.$i
> done

(3)配置本机的hosts解析

[root@had-node1 ~]# vim /etc/hosts
...	...
192.168.1.70 had-node1
192.168.1.71 had-node2
192.168.1.72 had-node3
192.168.1.73 had-node4
192.168.1.74 had-node5
192.168.1.75 had-client
192.168.1.76 had-nfsgw
192.168.1.77 had-node01-02

[root@had-node1 ~]# for i in {70..77}
> do
> scp /etc/hosts root@192.168.1.$i:/etc/hosts
> done

(4)除了zookeeper服务,其他服务都停掉

[root@had-node1 ~]# rm -rf /var/hadoop/*			//所有节点都执行
[root@had-node01-02 ~]# mkdir /var/hadoop		//node01-02节点创建hadoop目录

[root@had-node1 ~]# cd /usr/local/hadoop/   	//所有节点清除掉hadoop日志
[root@had-node1 hadoop]# rm -rf logs/*

core-site配置

  • core-site.xml文件
<property>
<name>fs.defaultFS</name>
<value>hdfs://mycluster</value>
</property>
<property>
<name>hadoop.tmp.dir</name>
<value>/var/hadoop</value>
</property>
<property>
<name>ha.zookeeper.quorum</name>
<value>node1:2181,node2:2181,node3:2181</value>

hdfs-site配置

  • hdfs-site.xml文件
<property>
<name>dfs.replication</name>
<value>2</value>
</property>
  • SecondaryNameNode在高可用里没有用,这里把它关闭
  • NameNode在后面定义hdfs-site配置

<!-- 指定hdfs的nameservices名称为mycluster -->

<property>
<name>dfs.nameservices</name>
<value>mycluster</value>
</property>
  • 指定集群的两个NaneNode的名称分别为nn1,nn2
<property>
<name>dfs.ha.namenodes.mycluster</name>
<value>nn1,nn2</value>
</property>hdfs-site配置(续2)
  • 配置nn1,nn2的rpc通信端口
<property>
<name>dfs.namenode.rpc-
address.mycluster.nn1</name>
<value>nn01:8020</value>
</property>
<property>
<name>dfs.namenode.rpc-
address.mycluster.nn2</name>
<value>nn02:8020</value>
  • 配置nn1,nn2的http通信端口
<property>
<name>dfs.namenode.http-
address.mycluster.nn1</name>
<value>nn01:50070</value>
</property>
<property>
<name>dfs.namenode.http-
address.mycluster.nn2</name>
<value>nn02:50070</value>
  • 指定NameNode元数据存储在journalnode中的路径
<property>
<name>dfs.namenode.shared.edits.dir</name>
<value>qjournal://node1:8485;node2:8485;node3:8485/my
cluster</value>
</property>
  • 指定journalnode日志文件存储的路径
<property>
<name>dfs.journalnode.edits.dir</name>
<value>/var/hadoop/journal</value>
  • 指定HDFS客户端连接Active NameNode的java类
<property>
<name>dfs.client.failover.proxy.provider.mycluster</name>
<value>org.apache.hadoop.hdfs.server.namenode.ha.Confi
guredFailoverProxyProvider</value>
  • 配置隔离机制为SSH
<property>
<name>dfs.ha.fencing.methods</name>
<value>sshfence</value>
</property>
  • 指定密钥的位置
<property>
<name>dfs.ha.fencing.ssh.private-key-files</name>
<value>/root/.ssh/id_rsa</value>
  • 开启自动故障转移
<property>
<name>dfs.ha.automatic-failover.enabled</name>
<value>true</value>
yarn高可用

ResourceManager高可用

  • RM的高可用原理与NN一样,需要依赖ZK来实现,这里
    配置文件的关键部分,感兴趣的同学可以自己学习和测试
  • yarn.resourcemanager.hostname
  • 同理因为使用集群模式,该选项应该关闭yarn-site配置

yarn-site.xml配置

<property>
<name>yarn.resourcemanager.ha.enabled</name>
<value>true</value>
</property>
<property>
<name>yarn.resourcemanager.ha.rm-ids</name>
<value>rm1,rm2</value>
<name>yarn.resourcemanager.recovery.enabled</name>
<value>true</value>
</property>
<property>
<name>yarn.resourcemanager.store.class</name>
<value>org.apache.hadoop.yarn.server.resourcemanager.re
covery.ZKRMStateStore</value>
<property>
<name>yarn.resourcemanager.zk-address</name>
<value>node1:2181,node2:2181,node3:2181</value>
</property>
<property>
<name>yarn.resourcemanager.cluster-id</name>
<value>yarn-ha</value>
<name>yarn.resourcemanager.hostname.rm1</name>
<value>nn01</value>
</property>
<property>
<name>yarn.resourcemanager.hostname.rm2</name>
<value>nn02</value>
</property>

(5)修改node1上的配置文件

[root@had-node1 ~]# cd /usr/local/hadoop/
[root@had-node1 hadoop]# vim etc/hadoop/core-site.xml 
<configuration>
  <property>
    <name>fs.defaultFS</name>
    <value>hdfs://mynodes</value>		//namenodes,namenode组的名称
  </property>
  <property>
   <name>hadoop.tmp.dir</name>
   <value>/var/hadoop</value>
  </property>
  <property>
    <name>hadoop.proxyuser.nfsuser.groups</name>
    <value>*</value>
  </property>
  <property>
    <name>hadoop.proxyuser.nfsuser.hosts</name>
    <value>*</value>
  </property>
  <property>
    <name>ha.zookeeper.quorum</name>
    <value>had-node1:2181,had-node2:2181,had-node3:2181</value>		//zookeeper地址
  </property>
</configuration>
[root@had-node1 hadoop]# vim etc/hadoop/hdfs-site.xml 
... ...
<configuration>
  <property>
    <name>dfs.namenode.http-address</name>
    <value>had-node1:50070</value>
  </property>
  <property>
    <name>dfs.namenode.secondary.http-address</name>
    <value>had-node1:50090</value>
  </property>
  <property>
    <name>dfs.replication</name>
    <value>2</value>
  </property>
  <property>
    <name>dfs.hosts.exclude</name>
    <value>/usr/local/hadoop/etc/hadoop/exclude</value>
  </property>
    <property>
    <name>dfs.nameservices</name>
    <value>mynodes</value>
  </property>
  <property>
    <name>dfs.ha.namenodes.mynodes</name>
    <value>nn1,nn2</value>
  </property>
  <property>
    <name>dfs.namenode.rpc-address.mynodes.nn1</name>
    <value>had-node1:8020</value>
  </property>
  <property>
    <name>dfs.namenode.rpc-address.mynodes.nn2</name>
    <value>had-node01-02:8020</value>
  </property>
  <property>
    <name>dfs.namenode.http-address.mynodes.nn1</name>
    <value>had-node1:50070</value>
  </property>
  <property>
    <name>dfs.namenode.http-address.mynodes.nn2</name>
    <value>had-node01-02:50070</value>
  </property>
  <property>
    <name>dfs.namenode.shared.edits.dir</name>
    <value>qjournal://had-node1:8485;had-node2:8485;had-node3:8485/mynodes</value>
  </property>
  <property>
    <name>dfs.journalnode.edits.dir</name>
    <value>/var/hadoop/journal</value>
  </property>
  <property>
    <name>dfs.client.failover.proxy.provider.mynodes</name>
    <value>org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider</value>
  </property>
  <property>
    <name>dfs.ha.fencing.methods</name>
    <value>sshfence</value>
  </property>
  <property>
    <name>dfs.ha.fencing.ssh.private-key-files</name>
    <value>/root/.ssh/id_rsa</value>
  </property>
  <property>
    <name>dfs.ha.automatic-failover.enabled</name>
    <value>true</value>
  </property>
</configuration>
[root@had-node1 hadoop]# vim etc/hadoop/yarn-site.xml
...	...
<configuration>
  <property>
    <name>yarn.nodemanager.aux-services</name>
    <value>mapreduce_shuffle</value>
  </property>
  <property>
    <name>yarn.resourcemanager.ha.enabled</name>
    <value>true</value>
  </property>
  <property>
    <name>yarn.resourcemanager.ha.rm-ids</name>
    <value>rm1,rm2</value>
  </property>
  <property>
    <name>yarn.resourcemanager.recovery.enabled</name>
    <value>true</value>
  </property>
  <property>
    <name>yarn.resourcemanager.store.class</name>
    <value>org.apache.hadoop.yarn.server.resourcemanager.recovery.ZKRMStateStore</value>
  </property>
  <property>
     <name>yarn.resourcemanager.zk-address</name>
     <value>had-node1:2181,had-node2:2181,had-node3:2181</value>
  </property>
  <property>
    <name>yarn.resourcemanager.cluster-id</name>
    <value>yarn-ha</value>
  </property>
  <property>
    <name>yarn.resourcemanager.hostname.rm1</name>
    <value>had-node1</value>
  </property>
  <property>
    <name>yarn.resourcemanager.hostname.rm2</name>
    <value>had-node01-02</value>
  </property>
</configuration>

(6)集群启动及验证

<1>将had-node1上的配置同步到had-node2~had-node3和had-node01-02节点上

[root@had-node1 ~]# for i in {71..73}
> do
> rsync -av --delete /usr/local/hadoop root@192.168.1.$i:/usr/local/
> done
[root@had-node1 ~]# scp -r /usr/local/hadoop root@192.168.1.77:/usr/local/
[root@had-node1 ~]# /usr/local/hadoop/bin/hdfs zkfc -formatZK

<2>had-node2~had-node3都执行

[root@had-node2 ~]# /usr/local/hadoop/sbin/hadoop-daemon.sh start journalnode  

<3>节点格式化

[root@had-node1 ~]# /usr/local/hadoop/sbin/start-all.sh
[root@had-node1 ~]# /usr/local/hadoop/bin/hdfs namenode -format

<4>将had-node01上的/var/hadoop/dfs同步到had-node01-02

[root@had-node1 ~]# scp -r /var/hadoop/dfs root@192.168.1.77:/var/hadoop/

<5>初始化jns

[root@had-node1 ~]# /usr/local/hadoop/bin/hdfs namenode -initializeSharedEdits  //输入Y

<6>had-node2had-node3上停止journalnode服务.had-node2had-node3都执行

[root@had-node2 ~]# /usr/local/hadoop/sbin/hadoop-daemon.sh stop journalnode

<7>had-node1启动hdfs,yarn

[root@had-node1 ~]# /usr/local/hadoop/sbin/start-dfs.sh
[root@had-node1 ~]# /usr/local/hadoop/sbin/start-yarn.sh

<8>had-node01-02启动热备ResourceManager

[root@had-node01-02 ~]# /usr/local/hadoop/sbin/yarn-daemon.sh start resourcemanager

<9>查看集群状态

[root@had-node1 ~]# /usr/local/hadoop/bin/hdfs haadmin -getServiceState nn1
standby
[root@had-node1 ~]# /usr/local/hadoop/bin/hdfs haadmin -getServiceState nn2
active

<10>获取ResourceManager状态

[root@had-node01-02 ~]# /usr/local/hadoop/sbin/start-all.sh
[root@had-node1 ~]# /usr/local/hadoop/bin/yarn rmadmin -getServiceState rm1
active
[root@had-node1 ~]# /usr/local/hadoop/bin/yarn rmadmin -getServiceState rm2
standby

查看集群状态

  • 获取节点信息
    # ./bin/hdfs dfsadmin -report
    # ./bin/yarn node -list

  • 访问集群文件

# ./bin/hadoop fs -mkdir /input
# ./bin/hadoop fs -ls hdfs://mycluster/
  • 主从切换Activate
# ./sbin/hadoop-daemon.sh stop namenode
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值