Zookeeper学习笔记

                            ZooKeeper学习笔记

1.   zookeeper基本概念

zookeeper是一个分布式的,开放源码的分布式应用程序协调服务,是hadoop和Habase的重要组件,是为分布式应用提供一致性服务的软件。

2.   zookeeper的特征

2.1. 简易

    ZooKeeper的最重要核心就是一个精简文件系统,提供一些简单的操作以及附加的抽象(例如排序和通知)。

2.2. 易表达

    ZooKeeper的原型是一个丰富的集合,它们是一些已建好的块,可以用来构建大型的协作数据结构和协议,例如:分布式队列、分布式锁以及一组对等体的选举。

2.3. 高可用性

    ZooKeeper运行在一些集群上,被设计成可用性较高的,因此应用程序可以依赖它。ZooKeeper可以帮助你的系统避免单点故障,从而建立一个可靠的应用程序。

2.4. 松散耦合

    ZooKeeper的交互支持参与者之间并不了解对方。例如:ZooKeeper可以被当做一种公共的机制,使得进程彼此不知道对方的存在也可以相互发现并且交互,对等方可能甚至不是同步的。  

2.5. Zookeeper一致性的保证

顺序一致性:按照客户端发送请求的顺序更新数据。

原子性:更新要么成功,要么失败,不会出现部分更新。

单一性:无论客户端连接哪个server,都会看到同一个视图。

可靠性:一旦数据更新成功,将一直保持,直到新的更新。

实时性:Zookeeper保证客户端将在一个时间间隔范围内获得服务器的更新信息或者服务器失效的信息。

3.   zookeeper工作原理

    Zookeeper的核心是原子广播,这个机制保证了各个Server之间的同步。实现这个机制的协议叫做Zab协议。Zab协议有两种模式,它们分别是恢复模式(选主)和广播模式(同步)。当服务启动或者在领导者崩溃后,Zab就进入了恢复模式,当领导者被选举出来,且大多数Server完成了和leader的状态同步以后,恢复模式就结束了。状态同步保证了leader和Server具有相同的系统状态。 

为了保证事务的顺序一致性,zookeeper采用了递增的事务id号(zxid)来标识事务。所有的提议(proposal)都在被提出的时候加上了zxid。实现中zxid是一个64位的数字,它高32位是epoch用来标识leader关系是否改变,每次一个leader被选出来,它都会有一个新的epoch,标识当前属于那个leader的统治时期。低32位用于递增计数。

·        在zookeeper集群中有三种角色和四种状态

 

角色:leader,follower,observer

状态:leading,following,observing,looking

 

每个Server在工作过程中有4种状态:

LOOKING:当前Server不知道leader是谁,正在搜寻。

LEADING:当前Server即为选举出来的leader。

FOLLOWING:leader已经选举出来,当前Server与之同步。

OBSERVING:observer的行为在大多数情况下与follower完全一致,但是他们不参加选举和投票,而仅仅接受(observing)选举和投票的结果。

3.1 Leader选举

    当leader崩溃或leader失去大多数的follower时,zookeeeper将进入恢复模式,恢复模式选举出新的leader让所有的server进入正确状态,选举算法包括两种:基于basic  paxos和基于fast paxos,系统默认是fast  paxos。

3.2 basic paxos

1.选举线程由当前Server发起选举的线程担任,其主要功能是对投票结果进行统计,并选出推荐的Server;

2 .选举线程首先向所有Server发起一次询问(包括自己);

3 .选举线程收到回复后,验证是否是自己发起的询问(验证zxid是否一致),然后获取对方的id(myid),并存储到当前询问对象列表中,最后获取对方提议的leader相关信息(id,zxid),并将这些信息存储到当次选举的投票记录表中;

4.  收到所有Server回复以后,就计算出zxid最大的那个Server,并将这个Server相关信息设置成下一次要投票的Server;

5.  线程将当前zxid最大的Server设置为当前Server要推荐的Leader,如果此时获胜的Server获得n/2 + 1的Server票数, 设置当前推荐的leader为获胜的Server,将根据获胜的Server相关信息设置自己的状态,否则,继续这个过程,直到leader被选举出来。

3.3 fast paxox

fast paxos流程是在选举过程中,某Server首先向所有Server提议自己要成为leader,当其它Server收到提议以后,解决epoch和zxid的冲突,并接受对方的提议,然后向对方发送接受提议完成的消息,重复这个流程,最后一定能选举出Leader。

3.4 同步流程

选完leader以后,zk就进入状态同步过程。

1. leader等待server连接;

2 .Follower连接leader,将最大的zxid发送给leader;

3 .Leader根据follower的zxid确定同步点;

4 .完成同步后通知follower 已经成为uptodate状态;

5 .Follower收到uptodate消息后,又可以重新接受client的请求进行服务了。

3.5 Leader工作流程

1 .恢复数据;

2 .维持与Learner的心跳,接收Learner请求并判断Learner的请求消息类型;

3 .Learner的消息类型主要有PING消息、REQUEST消息、ACK消息、REVALIDATE消息,根据不同的消息类型,进行不同的处理。

PING消息是指Learner的心跳信息;REQUEST消息是Follower发送的提议信息,包括写请求及同步请求;ACK消息是Follower的对提议的回复,超过半数的Follower通过,则commit该提议;REVALIDATE消息是用来延长SESSION有效时间。

3.6 Follower工作流程

1. 向Leader发送请求(PING消息、REQUEST消息、ACK消息、REVALIDATE消息);

2 .接收Leader消息并进行处理;

3 .接收Client的请求,如果为写请求,发送给Leader进行投票;

4 .返回Client结果。

Follower的消息循环处理如下几种来自Leader的消息:

1 .PING消息:心跳消息;

2 .PROPOSAL消息:Leader发起的提案,要求Follower投票;

3 .COMMIT消息:服务器端最新一次提案的信息;

4 .UPTODATE消息:表明同步完成;

5 .REVALIDATE消息:根据Leader的REVALIDATE结果,关闭待revalidate的session还是允许其接受消息;

6 .SYNC消息:返回SYNC结果到客户端,这个消息最初由客户端发起,用来强制得到最新的更新。

 

3.7 ZooKeeper数据模型

Zookeeper会维护一个具有层次关系的数据结构,它非常类似于一个标准的文件系统,如图所示:

Zookeeper这种数据结构有如下这些特点:

1)每个子目录项如NameService都被称作为znode,这个znode是被它所在的路径唯一标识,如Server1这个znode的标识为/NameService/Server1。

2)znode可以有子节点目录,并且每个znode可以存储数据,注意EPHEMERAL(临时的)类型的目录节点不能有子节点目录。

3)znode是有版本的(version),每个znode中存储的数据可以有多个版本,也就是一个访问路径中可以存储多份数据,version号自动增加。

4)znode可以是临时节点(EPHEMERAL),可以是持久节点(PERSISTENT)。如果创建的是临时节点,一旦创建这个EPHEMERALznode的客户端与服务器失去联系,这个znode也将自动删除,Zookeeper的客户端和服务器通信采用长连接方式,每个客户端和服务器通过心跳来保持连接,这个连接状态称为session,如果znode是临时节点,这个session失效,znode也就删除了。

5)znode的目录名可以自动编号,如App1已经存在,再创建的话,将会自动命名为App2。

6)znode可以被监控,包括这个目录节点中存储的数据的修改,子节点目录的变化等,一旦变化可以通知设置监控的客户端,这个是Zookeeper的核心特性,Zookeeper的很多功能都是基于这个特性实现的。

7)ZXID:每次对Zookeeper的状态的改变都会产生一个zxid(ZooKeeperTransaction Id),zxid是全局有序的,如果zxid1小于zxid2,则zxid1在zxid2之前发生。

3.8 Watcher机制

1.概述

ZK中引入Watcher机制来实现分布式的通知功能,ZK允许客户端向服务端注册一个Watcher监听,当服务点的的指定事件触发监听时,那么服务端就会向客户端发送事件通知,以便客户端完成逻辑操作(即客户端向服务端注册监听,并将watcher对象存在客户端的Watchermanager中,服务端触发事件后,向客户端发送通知,客户端收到通知后从wacherManager中取出对象来执行回调逻辑)

2.特性

a.一次性:一旦一个watcher被触发,ZK都会将其从相应的的存储中移除,所以watcher是需要每注册一次,才可触发一次。

b.ZK客户端串行执行:客户端watcher回调过程是一个串行同步的过程

c.轻量:watcher数据结构中只包含:通知状态、事件类型和节点路径

3.watcher类型

Wacher类型

所监听类型

data watches

getData()exists()以及create()

child watches

getChildren()

4.Watch事件类型

ZOO_CREATED_EVENT:节点创建事件,需要watch一个不存在的节点,当节点被创建时触发,此watch通过zoo_exists()设置
ZOO_DELETED_EVENT:节点删除事件,此watch通过zoo_exists()或zoo_get()设置
ZOO_CHANGED_EVENT:节点数据改变事件,此watch通过zoo_exists()或zoo_get()设置
ZOO_CHILD_EVENT:子节点列表改变事件,此watch通过zoo_get_children()或zoo_get_children2()设置
ZOO_SESSION_EVENT:会话失效事件,客户端与服务端断开或重连时触发
ZOO_NOTWATCHING_EVENT:watch移除事件,服务端出于某些原因不再为客户端watch节点时

3.9 Zookeeper会话状态转换

3.10 Zookeeper故障切换

ZooKeeper客户端可以自动地进行故障切换,切换至另一台ZooKeeper服务器。并且关键的一点是,在另一台服务器接替故障服务器之后,所有的会话和相关的短暂Znode仍然是有效的。在故障切换过程中,应用程序将收到断开连接和连接至服务的通知。当客户端断开连接时,观察通知将无法发送;但是当客户端成功恢复连接后,这些延迟的通知会被发送。当然,在客户端重新连接至另一台服务器的过程中,如果应用程序试图执行一个操作,这个操作将会失败。这充分体现了在真实的ZooKeeper应用中处理连接丢失异常的重要性。

3.11 Zookeeper节点

PERSISTENT持久节点,就一直存在,直到有删除操作来主动清除这个节点

PERSISTENT_SEQUENTIAL持久顺序节点,需删除操作来清除,除此之外,每个父节点会为他的第一级子节点维护一份时序,会记录每个子节点创建的先后顺序

EPHEMERAL临时节点与持久节点不同的是,临时节点会随着会话的结束而自动清除

EPHEMERAL_SEQUENTIAL临时顺序节点与临时节点一样,会随着会话结束,而自动清除,除此之外还具有时序



4. Zookeeper环境部署

Zookeeper使用Java语言编写的,所以运行环境需要Java环境的支持(即需要JDK1.6或1.6以上版本的支持)。

ZK环境模式分为三种模式:单机模式、伪集群模式、集群模式。

4.1 单机模式配置

 配置文件目录conf下的配置文件zoo.cfg配置如下

tickTime=2000   

dataDir=/opt/zookeeper/data   

dataLogDir=/opt/zookeeper/logs   

clientPort=21280

  配置参数说明:

tickTime: zookeeper中使用的基本时间单位, 毫秒值.

dataDir: 数据目录. 可以是任意目录.

dataLogDir: log目录, 同样可以是任意目录. 如果没有设置该参数, 将使用和dataDir相同的设置.

clientPort: 监听client连接的端口号.

4.2 伪集群模式配置

伪集群是指在单台机器中启动多个zookeeper进程, 并组成一个集群. 以启动3个zookeeper进程为例.

Zookeeper1的配置

1.配置文件目录conf下的配置文件zoo.cfg配置如下:

tickTime=2000   

initLimit=5   

syncLimit=2   

dataDir=/opt/zookeeper0/data   

dataLogDir=/opt/zookeeper0/logs   

clientPort=2181

server.0=127.0.0.1:8880:7770   

server.1=127.0.0.1:8881:7771   

server.2=127.0.0.1:8882:7772 

2.在之前设置的dataDir中新建myid文件, 写入一个数字, 该数字表示这是第几号server. 该数字必须和zoo.cfg文件中的server.X中的X一一对应.

 

注意:因是单机伪集群配置,故每个端口号只能被一个应用程序使用,所以剩余两个配置的客户端监听端口、leader消息交换端口和选举端口参数必须不一样

 

配置参数说明:

inittime:用于配置leader服务器等待follower启动,并完成数据同步的时间,默认值10

synclimit:用于配置leader服务器和follower之间进行心条检测的最大延时时间,默认值5

server.X=A:B:C:其中X是一个数字, 表示这是第几号server. A是该server所在的IP地址. B配置该server和集群中的leader交换消息所使用的端口. C配置选举leader时所使用的端口. 由于配置的是伪集群模式, 所以各个server的A,B,C参数必须不同.

 

4.3 集群模式配置

1. 配置文件目录conf下的配置文件zoo.cfg配置如下

tickTime=2000   

initLimit=5   

syncLimit=2   

dataDir=/opt/zookeeper/data   

dataLogDir=/opt/zookeeper/logs   

clientPort=4180 

server.43=10.1.39.43:2888:3888 

server.47=10.1.39.47:2888:3888   

server.48=10.1.39.48:2888:3888

2.在之前设置的dataDir中新建myid文件, 写入一个数字, 该数字表示这是第几号server. 该数字必须和zoo.cfg文件中的server.X中的X一一对应.

 

注意:此处各个端口号尽可能保持一致

配置参数说明:

同上

4.4 ZooKeeper详细配置

配置类型

配置项

描述

基本配置

clientport

客户端连接端口号

dataDir

快照存储、myid配置项存储和zk服务进程pid存储目录

dataLogDir

Zk日志存储目录,如果不配置此项,日志将存储到dataDir目录下

tickTime

ZK中的一个时间单元。ZK中所有时间都是以这个时间单元为基础,进行整数倍配置的,默认值为3000毫秒。

存储配置

preAllocSize

预先开辟磁盘空间,用于后续写入事务日志。默认是64M,每个事务日志大小就是64M。如果ZK的快照频率较大的话,建议适当减小这个参数。(Java system property:zookeeper.preAllocSize)

snapCount

每进行snapCount次事务日志输出后,触发一次快照(snapshot), 此时,ZK会生成一个snapshot.*文件,同时创建一个新的事务日志文件log.*。默认是100000.(真正的代码实现中,会进行一定的随机数处理,以避免所有服务器在同一时间进行快照而影响性能)(Java system property:zookeeper.snapCount)

autopurge.snapRetainCoun

这个参数和上面的参数搭配使用,这个参数指定了需要保留的文件数目。默认是保留3个。

autopurge.purgeInterval

ZK提供了自动清理事务日志和快照文件的功能,这个参数指定了清理频率,单位是小时,需要配置一个1或更大的整数,默认是0,表示不开启自动清理功能。

fsync.warningthresholdms


事务日志输出时,如果调用fsync方法超过指定的超时时间,那么会在日志中输出警告信息。默认是1000ms。( fsync.warningthresholdms)New in 3.3.4

 

Weight.x=n  group.x=nnnnn[:nnnnn]

权重和分组设置

traceFile

用于记录所有请求的log,一般调试过程中可以使用,但是生产环境不建议使用,会严重影响性能。(Java system property:? requestTraceFile)

网络配置

globalOutstandingLimit

最大请求堆积数。默认是1000。ZK运行的时候, 尽管server已经没有空闲来处理更多的客户端请求了,但是还是允许客户端将请求提交到服务器上来,以提高吞吐性能。当然,为了防止Server内存溢出,这个请求堆积数还是需要限制下的。 
(zookeeper.globalOutstandingLimit.)

maxClientCnxns

单个客户端与单台服务器之间的连接数的限制,是ip级别的,默认是60,如果设置为0,那么表明不作任何限制。请注意这个限制的使用范围,仅仅是单台客户端机器与单台ZK服务器之间的连接数限制,不是针对指定客户端IP,也不是ZK集群的连接数限制,也不是单台ZK对所有客户端的连接数限制。指定客户端IP的限制策略,这里有一个patch,可以尝试一下

clientPortAddress

对于多网卡的机器,可以为每个IP指定不同的监听端口。默认情况是所有IP都监听 clientPort指定的端口。 New in 3.3.0

minSessionTimeout

Session最小超时限制,默认值2 * tickTime

maxSessionTimeout

Session最大超时限制,默认值20 * tickTime

集群配置

initLimit

Follower在启动过程中,会从Leader同步所有最新数据,然后确定自己能够对外服务的起始状态。Leader允许F在initLimit时间内完成这个工作,默认值3*ticktime

syncLimit

在运行过程中,Leader负责与ZK集群中所有机器进行通信,例如通过一些心跳检测机制,来检测机器的存活状态。如果L发出心跳包在syncLimit之后,还没有从F那里收到响应,那么就认为这个F已经不在线了。注意:不要把这个参数设置得过大,否则可能会掩盖一些问题。

leaderServes

默认情况下,Leader是会接受客户端连接,并提供正常的读写服务。但是,如果你想让Leader专注于集群中机器的协调,那么可以将这个参数设置为no,这样一来,会大大提高写操作的性能。(Java system property: zookeeper.leaderServes)。

server.x=[hostname]

:prot1:port2[:observer]

这里的x是一个数字,与myid文件中的id是一致的。右边可以配置两个端口,第一个端口用于F和L之间的数据同步和其它通信,第二个端口用于Leader选举过程中投票通信,还有可选配置observer’。

cnxTimeout

Leader选举过程中,打开一次连接的超时时间,默认是5s。(zookeeper. cnxTimeout)

electionAlg

在之前的版本中, 这个参数配置是允许我们选择leader选举算法,但是由于在以后的版本中,只会留下一种“TCP-based version of fast leader election”算法,所以这个参数目前看来没有用了。

授权配置

zookeeper.

DigestAuthenticationProvider
.superDigest

超级用户密码认证选项,默认是关闭的

不安全选项

forceSync

这个参数确定了是否需要在事务日志提交的时候调用FileChannel.force来保证数据完全同步到磁盘。(Java system property: zookeeper.forceSync)

jute.maxbuffer

每个节点最大数据量,是默认是1M。这个限制必须在server和client端都进行设置才会生效。(Java system property: jute.maxbuffer)

 

skipACL

对所有客户端请求都不作ACL检查。如果之前节点上设置有权限限制,一旦服务器上打开这个开头,那么也将失效。(Java system property: zookeeper.skipACL)

Readonlymode.enabled

 


4.4 运行服务

完成配置后可启动ZK服务,用 ZK自带的服务启动脚本来启动服务。

ZK自带的脚本有:

脚本

说明

zkCleanup

清理ZK历史数据包括庶务日志文件和数据快照

zkCli

ZK的一个简易客户端

zkServer

ZK的服务启动、停止、重启和状态查看start,stop、restart、status

zkEnv

设置ZK的环境变量

 

ZK常用四字命令

命令:echo 四字命令 | nc  IP PORT   (注意:此处IP和port之间是空格不是冒号)

zookeeper四字命令

功能描述

conf

输出相关服务配置的详细信息

cons

列出所有连接到服务器的客户端的完全的连接 /会话的详细信息。包括“接受 / 发送”的包数量、会话 id 、操作延迟、最后的操作执行等等信息。

dump

列出未经处理的会话和临时节点。

envi

输出关于服务环境的详细信息(区别于 conf命令)。

reqs

列出未经处理的请求

ruok

测试服务是否处于正确状态。如果确实如此,那么服务返回“imok ”,否则不做任何相应。

stat

输出关于性能和连接的客conf户端的列表。

wchs

列出服务器watch的详细信息。

wchc

通过 session列出服务器 watch的详细信息,它的输出是一个与watch相关的会话的列表。

wchp

通过路径列出服务器 watch的详细信息。它输出一个与 session相关的路径

 

zk客户端常用命令

命令

功能描述

ls

查看子节点

ls2

查看当前节点数据并能看到更新次数等数据

create [-s] [-e] path data

创建节点

delete

删除节点

get

获取节点数据

set

更新节点数据

 

4.5 ZooKeeper Client API

ZooKeeperClient Library提供了丰富直观的API供用户程序使用,下面是一些常用的API:

create(path, data, flags): 创建一个ZNode, path是其路径,data是要存储在该ZNode上的数据,flags常用的有:PERSISTEN, PERSISTENT_SEQUENTAIL, EPHEMERAL, EPHEMERAL_SEQUENTAIL

delete(path, version): 删除一个ZNode,可以通过version删除指定的版本, 如果version是-1的话,表示删除所有的版本

exists(path, watch): 判断指定ZNode是否存在,并设置是否Watch这个ZNode。这里如果要设置Watcher的话,Watcher是在创建ZooKeeper实例时指定的,如果要设置特定的Watcher的话,可以调用另一个重载版本的exists(path, watcher)。以下几个带watch参数的API也都类似

getData(path, watch): 读取指定ZNode上的数据,并设置是否watch这个ZNode

setData(path, watch): 更新指定ZNode的数据,并设置是否Watch这个ZNode

getChildren(path, watch): 获取指定ZNode的所有子ZNode的名字,并设置是否Watch这个ZNode

sync(path): 把所有在sync之前的更新操作都进行同步,达到每个请求都在半数以上的ZooKeeper Server上生效。path参数目前没有用

setAcl(path, acl): 设置指定ZNode的Acl信息

getAcl(path): 获取指定ZNode的Acl信息

 

5  ZooKeeper的典型应用场景

典型应用场景

1)数据发布/订阅

目的:动态获取数据,来实现配置信息的集中式管理和数据的动态更新Zookeeper采用设计模式:推拉相结合(客户端向服务端注册自己需要关注的节点,一旦该节点数据发生变更,那么服务端就会向相应的客户端发送Watcher事件通知,客户端接到消息后,主动到服务端获取最新的数据)

  例:

(1).将应用中的配置信息放到ZK上集中处理,通常应用初始化时主动获取所需配置信息,并在相对应的节点注册Watcher,以后配置信息每发生变更一次,就通知相应的订阅的客户端,客户端完成从节点获取最新的配置信息。

(2).分布式搜索服务中,索引元信息和服务器集群机器的节点状态存储在指定的ZK节点中,供客户端的订阅使用

(3).分布式日志收集系统,将应用日志以应用为任务单元收集日志,在ZK上以应用名为节点,把该应用的服务器IP做为子节点,当应用服务器出现宕机或服务器发生变化时,通知日志收集器,日志收集器获得最新的服务器信息,来实现收集日志的任务。

2)     负载均衡

目的:通常同一个应用或同一个服务的提供方都会部署多份,达到对等服务。而消费者就须要在这些对等的服务器中选择一个来执行相关的业务逻辑,其中比较典型的是消息中间件中的生产者,消费者负载均衡。

例:

生产者负载均衡:metaq发送消息的时候,生产者在发送消息的时候必须选择一台broker上的一个分区来发送消息,因此metaq在运行过程中,会把所有broker和对应的分区信息全部注册到ZK指定节点上,默认的策略是一个依次轮询的过程,生产者在通过ZK获取分区列表之后,会按照brokerId和partition的顺序排列组织成一个有序的分区列表,发送的时候按照从头到尾循环往复的方式选择一个分区来发送消息。

3)命名服务

目的:通过使用命名服务,客户端应用能够根据指定名字来获取资源的实体、服务地址和提供者的信息。

例:

(1).分布式任务调度系统中,通过调用Zookeeper节点创建API中的顺序节点创建,返回全局唯一的命名,且可得到节点创建的顺序。

4)分布式协调/通知

   目的:将不同的分布式组件有机结合起来,协调分布式系统的全局运行流程

   例:将需要互相协调的分布式系统组件注册在ZK同一节点上,并对该节点注册Wathcher,当其中一个组件更新节点信息时,其他节点将收到信息,并作出相应的处理。

 

5)     集群管理

目的:为了灵活的管理大规模的集群中机器的运行状态,统计宕机率等

 例:在线云主机管理,首先将应用部署到这些机器上,在ZK上的机器列表节点下面创建临时子节点,机器列表节点发出“子节点变更的”的消息,

6)     Master选举

   目的:避免重复劳动,提高集群的性能,让集群中的单机或部分集群去完成耗时操作。

   例:海量数据处理模型,用ZK强一致性选举出master,并让他处理耗时的海量数据,其余客户端在该节点注册Watcher,监控master的存活。

7)分布式锁

目的:分布式锁的实现包括独占和控制时序

 

8)分布式队列

队列包括先进先出(FIFO)队列和队列成员聚齐

例如:

分布式环境中,一个大任务TaskA,需要在很多子任务完成(或条件就绪)情况下才能进行。这个时候,凡是其中一个子任务完成(就绪),那么就去 /taskList 下建立自己的临时时序节点(CreateMode.EPHEMERAL_SEQUENTIAL),当 /taskList 发现自己下面的子节点满足指定个数,就可以进行下一步按序进行处理了。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值