hadoop离线分析(简单版)-Kafka

23 篇文章 0 订阅
22 篇文章 0 订阅

目录

 

kafka概述

kafka特性和应用场景

kafka基本架构及原理

Zookeeper在kafka的作用

Kafka核心组件

Kafka备份机制

kafka的安装配置(所有节点)


kafka概述

Kafka是最初由Linkedin公司开发,是一个分布式、支持分区的(partition)、多副本的(replica),基于zookeeper协调的分布式消息系统,它的最大的特性就是可以实时的处理大量数据以满足各种需求场景:比如基于hadoop的批处理系统、低延迟的实时系统、storm/Spark流式处理引擎,web/nginx日志、访问日志,消息服务等等。用scala语言编写,Linkedin于2010年贡献给了Apache基金会并成为顶级开源项目。kafka提供了类似于JMS的特性,但是在设计实现上完全不同,此外它并不是JMS规范的实现。kafka在消息保存时根据Topic进行归类,消息发送者成为Producer,消息接受者成为Consumer,此外kafka集群由多个Kafka实例组成,每个实例(Server)成为Broker(每台机器)。无论是Kafka集群,还是Producer和Consummer都依赖于zookeeper,来保证系统可用性,而集群保存一些meta信息。kafka集群消费者和生产者如右图:

消息队列的性能好坏,其文件存储机制设计是衡量一个消息队列服务技术水平和最关键指标之一。

其他主流分布式消息系统的对比

kafka特性和应用场景

特性

①高吞吐量、低延迟:kafka每秒可以处理几十万条消息,它的延迟最低只有几毫秒
②可扩展性:kafka集群支持热扩展
③持久性、可靠性:消息被持久化到本地磁盘,并且支持数据备份防止数据丢失
④容错性:允许集群中节点失败(若副本数量为n,则允许n-1个节点失败)
⑤高并发:支持数千个客户端同时读写
⑥支持实时在线处理和离线处理:可以使用Storm这种实时流处理系统对消息进行实时进行处理,同时还可以使用Hadoop这种批处理系统进行离线处理;

应用场景

①日志收集:一个公司可以用Kafka可以收集各种服务的log,通过kafka以统一接口服务的方式开放给各种consumer,例如hadoop、Hbase、Solr等。
②解耦和生产者和消费者、缓存消息等。
③用户活动跟踪:Kafka经常被用来记录web用户或者app用户的各种活动,如浏览网页、搜索、点击等活动,这些活动信息被各个服务器发布到kafka的topic中,然后订阅者通过订阅这些topic来做实时的监控分析,或者装载到hadoop、数据仓库中做离线分析和挖掘。
④运营指标:Kafka也经常用来记录运营监控数据。包括收集各种分布式应用的数据,生产各种操作的集中反馈,比如报警和报告。
⑤流式处理:比如spark streaming和storm
⑥事件源

kafka基本架构及原理

基本架构

它的架构包括以下组件:        
    1.话题/主题(Topic):是特定类型的消息流。消息是字节的有效负载(Payload),话题是消息的分类名或种子(Feed)名;也可以理解成消息存放的目录即主题    
    2.生产者(Producer):是能够发布消息到topic的任何对象,即生产消息到topic的一方。    
    3.服务代理(Broker):已发布的消息保存在一组服务器中,它们被称为代理(Broker)或Kafka集群;Kafka的服务实例就是一个broker。
    4.消费者(Consumer):可以订阅一个或多个topic,并从Broker拉数据,从而消费这些已发布的消息;    
    即:订阅topic消费消息的一方

上图中可以看出,生产者将数据发送到Broker代理,Broker代理有多个话题topic,消费者从Broker获取数据。

原理

我们将消息的发布(publish)称作producer,将消息的订阅(subscribe)表述为consumer,将中间的存储阵列称作broker(代理),这样就可以大致描绘出这样一个场面:

生产者将数据生产出来,交给 broker 进行存储,消费者需要消费数据了,就从broker中去拿出数据来,然后完成一系列对数据的处理操作。乍一看返也太简单了,不是说了它是分布式吗,难道把producer、broker和consumer放在三台不同的机器上就算是分布式了吗。看 kafka 官方给出的图:

多个broker协同合作,producer和consumer部署在各个业务逻辑中被频繁的调用,三者通过 zookeeper管理协调请求和转发。
这样一个高性能的分布式消息发布订阅系统就完成了。图上有个细节需要注意,producer到broker的过程是push,也就是有数据就推送到broker,而consumer到broker的过程是pull,是通过consumer主动去拉数据的,而不是broker把数据主动发送到consumer端的。Kafka在这块的架构设计,非常优秀,采用了Partition的概念实现数据分片,支持分布式的数据存储,而且还支持动态扩容。

Zookeeper在kafka的作用

1)无论是kafka集群,还是producer和consumer都依赖于zookeeper来保证系统可用性集群保存一些meta信息。
2)Kafka使用zookeeper作为其分布式协调框架,很好的将消息生产、消息存储、消息消费的过程结合在一起。
3)同时借助zookeeper,kafka能够生产者、消费者和broker在内的所有组件在无状态的情况下,建立起生产者和消费者的订阅关系,并实现生产者与消费者的负载均衡。

我们看上面的图,我们把 broker 的数量减少,叧有一台。现在假设我们按照上图进行部署:        
(1)Server-1 broker 其实就是 kafka 的 server,因为 producer 和 consumer 都要去还它。 Broker 主要还是做存储用。        
(2)Server-2 是 zookeeper 的 server 端,它维持了一张表,记录了各个节点的 IP、端口等信息。        
(3)Server-3、 4、 5 他们的共同之处就是都配置了 zkClient,更明确的说,就是运行前必须配置 zookeeper的地址,道理也很简单,这之间的连接都是需要 zookeeper 来进行分发的。        
(4)Server-1 和 Server-2 的关系,他们可以放在一台机器上,也可以分开放,zookeeper 也可以配集群。目的是防止某一台挂了。        
简单说下整个系统运行的顺序:        
(1)启动zookeeper 的 server        
(2)启动kafka 的 server        
(3)Producer 如果生产了数据,会先通过 zookeeper 找到 broker,然后将数据存放到 broker        
(4)Consumer 如果要消费数据,会先通过 zookeeper 找对应的 broker,然后消费。

Kafka核心组件

1)Replications、Partitions 和Leaders        
kafka中的数据是持久化的并且能够容错的。Kafka允许用户为每个topic设置副本数量,副本数量决定了有几个broker来存放写入的数据。如果你的副本数量设置为3,那么一份数据就会被存放在3台不同的机器上,那么就允许有2个机器失败。一般推荐副本数量至少为2,这样就可以保证增减、重启机器时不会影响到数据消费。如果对数据持久化有更高的要求,可以把副本数量设置为3或者更多。Kafka中的topic是以partition的形式存放的,每一个topic都可以设置它的partition数量,Partition的数量决定了组成topic的log的数量。Producer在生产数据时,会按照一定规则(这个规则是可以自定义的)把消息发布到topic的各个partition中。上面将的副本都是以partition为单位的,不过只有一个partition的副本会被选举成leader作为读写用。关于如何设置partition值需要考虑的因素。一个partition只能被一个消费者消费(一个消费者可以同时消费多个partition)因此,如果设置的partition的数量小于consumer的数量,就会有消费者消费不到数据。所以,推荐partition的数量一定要大于同时运行的consumer的数量。另外一方面,建议partition的数量大于集群broker的数量,这样leader partition就可以均匀的分布在各个broker中,最终使得集群负载均衡。在Cloudera,每个topic都有上百个partition。需要注意的是,kafka需要为每个partition分配一些内存来缓存消息数据,如果partition数量越大,就要为kafka分配更大的heap space。        
2)Producers        
Producers直接发送消息到broker上的leader partition,不需要经过任何中介一系列的路由转发。为了实现这个特性,kafka集群中的每个broker都可以响应producer的请求,并返回topic的一些元信息,这些元信息包括哪些机器是存活的,topic的leader partition都在哪,现阶段哪些leader partition是可以直接被访问的。Producer客户端自己控制着消息被推送到哪些partition。实现的方式可以是随机分配、实现一类随机负载均衡算法,或者指定一些分区算法。Kafka提供了接口供用户实现自定义的分区,用户可以为每个消息指定一个partitionKey,通过这个key来实现一些hash分区算法。比如,把userid作为partitionkey的话,相同userid的消息将会被推送到同一个分区。以Batch的方式推送数据可以极大的提高处理效率,kafka Producer可以将消息在内存中累计到一定数量后作为一个batch发送请求。Batch的数量大小可以通过Producer的参数控制,参数值可以设置为累计的消息的数量(如500条)、累计的时间间隔(如100ms)或者累计的数据大小(64KB)。通过增加batch的大小,可以减少网络请求和磁盘IO的次数,当然具体参数设置需要在效率和时效性方面做一个权衡。Producers可以异步的并行的向kafka发送消息,但是通常producer在发送完消息之后会得到一个future响应,返回的是offset值或者发送过程中遇到的错误。这其中有个非常重要的参数“acks”,这个参数决定了producer要求leader partition收到确认的副本个数,如果acks设置数量为0,表示producer不会等待broker的响应,所以,producer无法知道消息是否发送成功,这样有可能会导致数据丢失,但同时,acks值为0会得到最大的系统吞吐量。         
若acks设置为1,表示producer会在leader partition收到消息时得到broker的一个确认,这样会有更好的可靠性,因为客户端会等待直到broker确认收到消息。若设置为-1,producer会在所有备份的partition收到消息时得到broker的确认,这个设置可以得到最高的可靠性保证。        
Kafka消息有一个定长的header和变长的字节数组组成。因为kafka消息支持字节数组,也就使得kafka可以支持任何用户自定义的序列号格式或者其它已有的格式如Apache Avro、protobuf等。Kafka没有限定单个消息的大小,但我们推荐消息大小不要超过1MB,通常一般消息大小都在1~10kB之前。

3)Consumers    
Kafka提供了两套consumer api,分为high-level api和sample-api:    
    Sample-api是一个底层的API,它维持了一个和单一broker的连接,并且这个API是完全无状态的,每次请求都需要指
    定offset值,因此,这套API也是最灵活的。
    在kafka中,当前读到消息的offset值是由consumer来维护的,因此,consumer可以自己决定如何读取kafka中的数据。
    比如,consumer可以通过重设offset值来重新消费已消费过的数据。不管有没有被消费,kafka会保存数据一段时间,
    这个时间周期是可配置的,只有到了过期时间,kafka才会删除这些数据。
    High-level API封装了对集群中一系列broker的访问,可以透明的消费一个topic。它自己维持了已消费消息的状态,
    即每次消费的都是下一个消息。High-level API还支持以组的形式消费topic,如果consumers有同一个组名,那么
    kafka就相当于一个队列消息服务,而各个consumer均衡的消费相应partition中的数据。若consumers有不同的组名,
    那么此时kafka就相当与一个广播服务,会把topic中的所有消息广播到每个consumer。

Kafka备份机制

备份机制是Kafka0.8版本的新特性,备份机制的出现大大提高了Kafka集群的可靠性、稳定性。有了备份机制后,Kafka允许集群中的节点挂掉后而不影响整个集群工作。一个备份数量为n的集群允许n-1个节点失败。在所有备份节点中,有一个节点作为lead节点,这个节点保存了其它备份节点列表,并维持各个备份间的状态同步。

kafka的安装配置(所有节点)

下载最新版本:kafka_2.12-1.1.0.tgz(此版本release日期是2018.03.28)此版本需要Scala2.12支持,本次集群环境搭建是Scala2.12.5版本
cd /usr/software
tar -zxvf kafka_2.12-1.1.0.tgz
ln -s kafka_2.12-1.1.0 kafka
在配置以下配置文件之前要把zookeeper集群配置好,kafka集群依赖于zookeeper

注:log.dirs必须保证目录存在,不会根据配置文件自动生成

1)修改server.properties(Broker)
cd /usr/software/kafka/config
vim server.properties


broker.id=0        #broker的全局唯一编号,不能重复,修改其他节点中的server.properties中的broker.id,分别设置为 0,1,2,3,4            
port=9092        #用来监听链接的端口,producer或consumer将在此端口建立连接            
或者listeners=PLAINTEXT://hadoop01:9092                    #对应本机的ip
num.network.threads=3            #处理网络请求的线程数量        
num.io.threads=8            #用来处理磁盘IO的线程数量        
socket.send.buffer.bytes=102400                #发送套接字的缓冲区大小    
socket.receive.buffer.bytes=102400                #接受套接字的缓冲区大小    
socket.request.max.bytes=104857600                #请求套接字的缓冲区大小    
log.dirs=/usr/software/kafka/logs                #kafka消息存放的路径,数据存放地址所以,在kafka文件夹下mkdir /usr/software/kafka/logs    
num.partitions=2                #topic在当前broker上的分片个数,五台机器就10个partitions    
num.recovery.threads.per.data.dir=1                #用来恢复和清理data下数据的线程数量    
offsets.topic.replication.factor=1                    
transaction.state.log.replication.factor=1                    
transaction.state.log.min.isr=1                    
log.retention.hours=168                #segment文件保留的最长时间,超时将被删除    
log.roll.hours=168                #滚动生成新的segment文件的最大时间,168h=7day    
log.segment.bytes=1073741824                #日志文件中每个segment的大小,默认为1G    
log.retention.check.interval.ms=300000                #周期性检查文件大小的时间300000=5分钟    
log.cleaner.enable=true                #日志清理是否打开    
zookeeper.connect=hadoop01:2181,hadoop02:2181,hadoop03:2181,hadoop04:2181,hadoop05:2181                    
zookeeper.connection.timeout.ms=9000                #zookeeper链接超时时间(9秒)    
log.flush.interval.messages=10000                #partion buffer中,消息的条数达到阈值,将触发flush到磁盘    
log.flush.interval.ms=3000                #消息buffer的时间,达到阈值,将触发flush到磁盘    
delete.topic.enable=true                #删除topic需要server.properties中设置delete.topic.enable=true否则只是标记删除    
host.name=hadoop01            #此处的host.name为本机IP(重要),如果不改,则客户端会抛出:Producerconnection to localhost:9092 unsuccessful 错误!        
group.initial.rebalance.delay.ms=10000                    
对于用户来说,这个改进最直接的效果就是新增了一个broker配置:group.initial.rebalance.delay.ms,默认是3秒                    
钟。用户需要在server.properties文件中自行修改为想要配置的值。这个参数的主要效果就是让coordinator推迟空消                    
费组接收到成员加入请求后本应立即开启的rebalance。在实际使用时,假设你预估你的所有consumer组成员加入需要在                    
10s内完成,那么你就可以设置该参数=10000。目前来看,这个参数的使用还是很方便的~                   

2)修改producer.properties(producer)                        
bootstrap.servers=hadoop01:9092,hadoop02:9092,hadoop03:9092,hadoop04:9092,hadoop05:9092                        
用于建立与kafka集群连接的host/port组。数据将会在所有servers上均衡加载,不管哪些server是指定用于                        
bootstrapping。这个列表仅仅影响初始化的hosts(用于发现全部的servers)。这个列表格式:host1:port1,host2:port2,                        
…因为这些server仅仅是用于初始化的连接,以发现集群所有成员关系(可能会动态的变化),这个列表不需要包含所有的servers(你可能想要不止一个server,尽管这样,可能某个server宕机了)。如果没有server在这个列表出现,则发送数据会一直失败,直到列表可用。        
或者                        
metadata.broker.list=hadoop03:9092,hadoop04:9092,hadoop05:9092                    #指定kafka节点列表,用于获取metadata,不必全部指定    
#partitioner.class=kafka.producer.DefaultPartitioner                        # 指定分区处理类。默认kafka.producer.DefaultPartitioner,表通过key哈希到对应分区
compression.type/compression.codec=none            # 是否压缩,默认0表示不压缩,1表示用gzip压缩,2表示用snappy压缩。压缩后消息中会有头来指明消息压缩类型,故在消费者端消息解压是透明的无需指定。            
serializer.class=kafka.serializer.DefaultEncoder                        # 指定序列化处理类
#compressed.topics=            # 如果要压缩消息,这里指定哪些topic要压缩消息,默认empty,表示不压缩。            
request.required.acks=1            #设置发送数据是否需要服务端的反馈,有三个值0,1,-1,0: producer不会等待broker发送ack,1: 当leader接收到消息之后发送ack,-1: 当所有的follower都同步消息成功后发送ack.            
            本次用1,保证一定可靠性            
request.timeout.ms=10000            #在向producer发送ack之前,broker允许等待的最大时间 ,如果超时,broker将会向producer发送一个error ACK.意味着上一次消息因为某种原因未能成功(比如follower未能同步成功)             
producer.type=sync            #同步还是异步发送消息,默认“sync”表同步,"async"表异步。异步可以提高发送吞吐量,也意味着消息将会在本地buffer中,并适时批量发送,但是也可能导致丢失未发送过去的消息            
queue.buffering.max.ms=5000            # 在async模式下,当message被缓存的时间超过此值后,将会批量发送给broker,默认为5000ms,此值和batch.num.messages协同工作.            
queue.buffering.max.messages=20000                在async模式下,producer端允许buffer的最大消息量,无论如何,producer都无法尽快的将消息发送给broker,从而导致消息在producer端大量沉积,此时,如果消息的条数达到阀值,将会导致producer端阻塞或者消息被抛弃,默认为10000        
batch.num.messages=500            # 如果是异步,指定每次批量发送数据量,默认为200            
queue.enqueue.timeout.ms=-1            当消息在producer端沉积的条数达到"queue.buffering.max.meesages"后,阻塞一定时间后,队列仍然没有enqueue(producer仍然没有发送出任何消息) ,此时producer可以继续阻塞或者将消息抛弃,此timeout值用于控制"阻塞"的时间,-1: 无阻塞超时限制,消息不会被抛弃,0:立即清空队列,消息被抛弃            
message.send.max.retries=3            当producer接收到error ACK,或者没有接收到ACK时,允许消息重发的次数。因为broker并没有完整的机制来避免消息重复,所以当网络异常时(比如ACK丢失) ,有可能导致broker接收到重复的消息,默认值为3.            
                        
# producer刷新topicmetada的时间间隔,producer需要知道partitionleader的位置,以及当前topic的情况                        
# 因此producer需要一个机制来获取最新的metadata,当producer遇到特定错误时,将会立即刷新                        
#(比如topic失效,partition丢失,leader失效等),此外也可以通过此参数来配置额外的刷新机制,默认值600000                        
topic.metadata.refresh.interval.ms=60000                    60秒

3)修改consumer.properties(consumer)                        
bootstrap.servers=hadoop01:9092,hadoop02:9092,hadoop03:9092,hadoop04:9092,hadoop05:9092                        
zookeeper.connect=hadoop01:2181,hadoop02:2181,hadoop03:2181,hadoop04:2181,hadoop05:2181                        #zookeeper连接服务器地址
zookeeper.session.timeout.ms=8000                #zookeeper的session过期时间,默认5000ms,用于检测消费者是否挂掉        
zookeeper.connection.timeout.ms=16000                    #当消费者挂掉,其他消费者要等该指定时间才能检查到并且触发重新负载均衡    
zookeeper.sync.time.ms=2000                #指定多久消费者更新offset到zookeeper中。注意offset更新时基于time而不是每次获得的消息。一旦在更新zookeeper发生异常并重启,将可能拿到已拿到过的消息        
group.id=hjh_consumer_group        #指定消费组,当前消费者的group名称                
auto.commit.enable=true            #当consumer消费一定量的消息之后,将会自动向zookeeper提交offset信息,注意offset信息并不是每消费一次消息就向zk提交一次,而是现在本地保存(内存),并定期提交,默认为true            
auto.commit.interval.ms=2000                # 自动更新时间。默认60 * 1000        
#conusmer.id=xxx        #当前consumer的标识,可以设定,也可以有系统生成,主要用来跟踪消息消费情况,便于观察                
#client.id=xxxx        #消费者客户端编号,用于区分不同客户端,默认客户端程序自动产生                
queued.max.message.chunks=50            #最大取多少块缓存到消费者(默认10)            
rebalance.max.retries=5            #当有新的consumer加入到group时,将会reblance,此后将会有partitions的消费端迁移到新  的consumer上,如果一个consumer获得了某个partition的消费权限,那么它将会向zk注册"Partition Owner registry"节点信息,但是有可能此时旧的consumer尚没有释放此节点, 此值用于控制,注册节点的重试次数.             
fetch.min.bytes=6553600            #获取消息的最大尺寸,broker不会像consumer输出大于此值的消息chunk 每次feth将得到多条消息,此值为总大小,提升此值,将会消耗更多的consumer端内存            
fetch.wait.max.ms=5000            # 当消息的尺寸不足时,server阻塞的时间,如果超时,消息将立即发送给consumer            
socket.receive.buffer.bytes=655360                        
auto.offset.reset=smallest            # 如果zookeeper没有offset值或offset值超出范围。那么就给个初始的offset。有smallest、largest、anything可选,分别表示给当前最小的offset、当前最大的offset、抛异常。默认largest            
derializer.class=kafka.serializer.DefaultDecoder                        # 指定序列化处理类
cd /usr/software                        
scp -r kafka_2.12-1.1.0 hadoop02@hadoop02:/home/hadoop02                        
scp -r kafka_2.12-1.1.0 hadoop03@hadoop03:/home/hadoop03                        
scp -r kafka_2.12-1.1.0 hadoop04@hadoop04:/home/hadoop04                        
scp -r kafka_2.12-1.1.0 hadoop05@hadoop05:/home/hadoop05

4)测试命令                
Step 1: Start the server                
后台方式启动,推荐第一次配置的新手不要加入-daemon参数,看看控制台输出的是否有success.                
bin/kafka-server-start.sh -daemon config/server.propertiesvim                
Step 2: Create a topic(replication-factor一定要大于1,否则kafka只有一份数据,leader一                
旦崩溃程序就没有输入源了,分区数目视输入源而定)                
bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 3 --partitions 3 --topic topicTest                
Step 3: Describe a topic                
bin/kafka-topics.sh --describe --zookeeper localhost:2181 --topic topicTest                
step 4: list the topic                
bin/kafka-topics.sh --list --zookeeper localhost:2181                
step 5: send some message                
bin/kafka-console-producer.sh --broker-list localhost:2181 --topic topicTest                
step 6: start a consumer                
bin/kafka-console-consumer.sh --zookeeper localhost:2181 --topic topicTest --from-beginning                
step 7: delete a topic                
要事先在 serve.properties 配置 delete.topic.enable=true                
bin/kafka-topics.sh --delete --zookeeper localhost:2181 --topic topicTest                
#如果仍然只是仅仅被标记了删除(zk中并没有被删除),那么启动zkCli.sh,输入如下指令                
rmr /brokers/topics/topicTest

5)kafka集群测试        nohup bin/kafka-server-stop.sh config/server.properties &        
a.依次在各自节点上启动kafka                
nohup bin/kafka-server-start.sh config/server.properties &                
启动后:没有出错,验证端口出现:netstat -tunlp|egrep "(2181|9092)"

bin/kafka-server-start.sh config/server.properties &
启动正常,没出错,显示已经启动完毕

bin/kafka-server-stop.sh config/server.properties &

备注:为了方便启动kafka。可以自己写个脚本同时启动多台kafka。以下是我自己写的,同时启动多台kafka的脚本。
cd /usr/software/kafka,进入目录后,创建文件touch kafka-start-all.sh

b.kafka创建topic和查看topic    
创建topic    
bin/kafka-topics.sh --create --zookeeper hadoop01:2181,hadoop02:2181,hadoop03:2181 hadoop04:2181 hadoop05:2181 --replication-factor 3 --partitions 3 --topic topicTest    
查看topic    
bin/kafka-topics.sh --list --zookeeper hadoop01:2181,hadoop02:2181,hadoop03:2181 hadoop04:2181 hadoop05:2181

在一台机器上创建topic,所有机器都可以看到该主题

c.启动消息生产者和消息消费者

master服务器(hadoop01)    
bin/kafka-console-producer.sh --broker-list hadoop01:9092,hadoop02:9092,hadoop03:9092,hadoop04:9092,hadoop05:9092 --topic topicTest    
其它slaver服务器任意一个(hadoop01~04)    
bin/kafka-console-consumer.sh --zookeeper hadoop01:2181,hadoop02:2181,hadoop03:2181,hadoop04:2181,hadoop05:2181 --topic topicTest --from-beginning

master服务器输入信息,slave服务器会显示master输的的信息

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

戰士

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值