Kafka连接器建立数据管道

1.概述

最近,有同学留言咨询Kafka连接器的相关内容,今天笔者给大家分享一下Kafka连接器建立数据管道的相关内容。

2.内容

Kafka连接器是一种用于Kafka系统和其他系统之间进行功能扩展、数据传输的工具。通过Kafka连接器能够简单、快速的将大量数据集移入到Kafka系统,或者从Kafka系统中移出,例如Kafka连接器可以低延时的将数据库或者应用服务器中的指标数据收集到Kafka系统主题中。
另外,Kafka连接器可以通过作业导出的方式,将Kafka系统主题传输到二次存储和查询系统中,或者传输到批处理系统中进行离线分析。

2.1 使用场景

Kafka连接器通常用来构建数据管道,一般来说有两种使用场景。
1. 开始和结束的端点
第一种,将Kafka系统作为数据管道的开始和结束的端点。例如,将Kafka系统主题中的数据移出到HBase数据库,或者把Oracle数据库中的数据移入到Kafka系统。

2. 数据传输的中间介质
第二种,把Kafka系统作为一个中间传输介质。例如,为了把海量日志数据存储到ElasticSearch中,可以先把这些日志数据传输到Kafka系统中,然后再从Kafka系统中将这些数据移出到ElasticSearch进行存储。

ElasticSearch是一个基于Lucene(Lucene是一款高性能、可扩展的信息检索工具库)实现的存储介质。它提供了一个分布式多用户能力的全文搜索引擎,基RESTful(一种软件架构风格、设计风格,但是并非标准,只是提供了一组设计原则和约束条件)接口实现。

Kafka连接器的存在,给数据管道带来很重要的价值。例如,Kafka连接器可以作为数据管道各个数据阶段的缓冲区,有效的将消费者实例和生产者实例进行解耦。
Kafka系统解除耦合的能力、系统的安全性、数据处理的效率等方面均表现不俗,因而使用Kafka连接器来构建数据管道是一个最佳的选举。

2.2 特性和优势

Kafka连接器包含一些重要的特性,并且给数据管道提供了一个成熟稳定的框架。同时,Kafka连接器还提供了一些简单易用的工具库,大大降低的开发人员的研发成本。
1. 特性
Kafka连接器具体包含的特性如下。

  • 通用框架:Kafka连接器制定了一种标准,用来约束Kafka系统与其他系统集成,简化了Kafka连接器的开发、部署和管理;
  • 单机模式和分布式模式:Kafka连接器支持两种模式,既能扩展到支持大型集群的服务管理,也可以缩小到开发、测试等小规模的集群;
  • REST接口:使用REST API来提交请求并管理Kafka集群;
  • 自动管理偏移量:通过连接器的少量信息,Kafka连接器可以自动管理偏移量;
  • 分布式和可扩展:Kafka连接器是建立在现有的组管理协议上,通过添加更多的连接器实例来水平扩展,实现分布式服务;
  • 数据流和批量集成:利用Kafka系统已有的能力,Kafka连接器是桥接数据流和批处理系统的一种理想的解决方案。

2. 优势
在Kafka连接器中有两个核心的概念,它们分别是Source和Sink。其中Source负责将数据导入到Kafka系统,而Sink则负责将数据从Kafka系统中进行导出。
Source和Sink在实现数据导入和导出的过程被称之连接器,即Source连接器和Sink连接器。这两种连接器提供了对业务层面数据读取和写入的抽象接口,简化了生命周期的管理工作。
在处理数据时,Source连接器和Sink连接器会初始化各自的任务,并将数据结构进行标准化的封装。在实际应用场景中,不同的业务中的数据格式是不一样的,因此,Kafka连接器通过注册数据结构,来解决数据格式验证和兼容性问题。
当数据源发生变化时,Kafka连接器会生成新的数据结构,通过不同的处理策略来完成对数据格式的兼容。

2.3 核心概念

在Kafka连接器中存在几个核心的概念,它们分别是连接器实例(Connectors)、任务数(Tasks)、事件线程数(Workers)、转换器(Converters)。
1. 连机器实例
在Kafka连接器中,连接器实例决定了消息数据的流向,即消息数据从何处复制,以及将复制的消息数据写入到何处。
一个连接器实例负责Kafka系统与其他系统之间的逻辑处理,连接器实例通常以JAR包的形式存在,通过实现Kafka系统应用接口来完成。
2. 任务数
在分布式模式下,每一个连接器实例可以将一个作业切分成多个任务(Task),然后再将任务分发到各个事件线程(Worker)中去执行。任务不会保存当前的状态信息,通常由特定的Kafka主题来保存,例如指定具体属性offset.storage.topic和status.storage.topic的值来保存。
在分布式模式中,会存在任务均衡的概念。当一个连接器实例首次提交到Kafka集群,所有的事件线程都会做一个任务均衡的操作,来保证每一个事件线程都运行差不多数量的任务,避免所有任务集中到某一个事件线程。
3. 事件线程
在Kafka系统中,连接器实例和任务数都是逻辑层面的,需要有具体的线程来执行。在Kafka连接器中,事件线程就是用来执行具体的任务,事件线程包含两种,分别是单机模式和分布式模式。
4. 转换器
转换器会将字节数据转换成Kafka连接器内部的格式,同时,也能将Kafka连接器内部存储的数据格式转换成字节数据。

3.操作连接器

连接器作为Kafka的一部分,随着Kafka系统一起发布,所以无需独立安装。在大数据应用场景下,建议在每台物理机上安装一个Kafka。根据实际需求,可以在一部分物理机上启动Kafka实例(即代理节点Broker),在另一部分物理机上启动连接器。

在Kafka系统中,Kafka连接器最终是以一个常驻进程的形式运行在后台服务中,它提供了一个用来管理连接器实例的REST API。默认情况下,服务端口地址是8083。

提示:
Representational State Transfer,简称REST,即表现层状态转移。REST是所有Web应用程序都应用遵守的一种规范。符合REST设计规范的应用接口,即REST API。

在Kafka连接器中,REST API支持获取、写入、创建等接口,具体内容如下图所示:

 在Kafka系统中,Kafka连接器目前支持两种运行模式,它们分别是单机模式和分布式模式。

3.1 单击模式导入

在单机模式下,所有的事件线程都在一个单进程中运行。单机模式使用起来更加简单,特别是在开发和定位分析问题的时候,使用单机模式会比较适合。
(1)编辑单机模式配置文件。
在单机模式下,主题的偏移量是存储在/tmp/connect.offsets目录下,在$KAFKA_HOME/config目录下有一个connect-standalone.properties文件,通过设置offset.storage.file.filename属性值来改变存储路径。
每次Kafka连接器启动时,通过加载$KAFKA_HOME/config/connect-file-source.properties配置文件中的name属性来获取主题的偏移量,然后执行后续的读写操作。

复制代码

# 设置连接器名称
name=local-file-source
# 指定连接器类
connector.class=FileStreamSource
# 设置最大任务数
tasks.max=1
# 指定读取的文件
file=/tmp/test.txt
# 指定主题名
topic=connect_test

复制代码

(2)在即将读取的文件中,添加数据,具体操作命令如下。
# 新建一个test.txt文件并添加数据
[hadoop@dn1 ~]$ vi /tmp/test.txt

复制代码

# 添加内容如下
kafka
hadoop
kafka-connect

# 保存并退出

复制代码

在使用Kafka文件连接器时,连接器实例会监听配置的数据文件,如果文件中有数据更新,例如:追加新的消息数据。连接器实例会及时处理新增的消息数据。
(3)启动Kafka连接器单机模式的命令与启动Kafka代理节点类似,具体操作命令如下。

# 启动一个单机模式的连接器
[hadoop@dn1 bin]$ ./connect-standalone.sh ../config/connect-standalone.properties\
../config/connect-file-source.properties

(4)使用Kafka系统命令查看导入到主题(connect_test)中的数据,具体操作命令如下。

# 使用Kafka命令查看
[hadoop@dn1 bin]$ ./kafka-console-consumer.sh --zookeeper dn1:2181 --topic connect_test\
--from-beginning

3.2 分布式模式导入

在分布式模式中,Kafka连接器会自动均衡每个事件线程所处理的任务数。允许用户动态的增加或者减少,在执行任务、修改配置、以及提交偏移量时能够得到容错保障。
在分布式模式中,Kafka连接器会在主题中存储偏移量、配置、以及任务状态。建议手动创建存储偏移量的主题,可以按需设置主题分区数和副本数。
需要注意的是,除了配置一些通用的属性之外,还需要配置以下几个重要的属性。

  • group.id(默认值connect-cluster):连接器组唯一名称,切记不能和消费者组名称冲突;
  • config.storage.topic(默认值connect-configs):用来存储连接器实例和任务配置,需要注意的是,该主题应该以单分区多副本的形式存在,建议手动创建,如果自动创建可能会存在多个分区;
  • offset.storage.topic(默认值connect-offsets):用来存储偏移量,该主题应该以多分区多副本的形式存在;
  • status.storage.topic(默认值connect-status):用来存储任务状态,该主题建议以多分区多副本的形式存在。

在分布式模式中,Kafka连接器配置文件不能使用命令行,需要使用REST API来执行创建、修改和销毁Kafka连接器操作。
(1)编辑分布式模式配置文件(connect-distributed.properties)

复制代码

# 设置Kafka集群地址
bootstrap.servers=dn1:9092,dn2:9092,dn3:9092
# 设置连接器唯一组名称
group.id=connect-cluster
# 指定键值对JSON转换器类
key.converter=org.apache.kafka.connect.json.JsonConverter
value.converter=org.apache.kafka.connect.json.JsonConverter
# 启用键值对转换器
key.converter.schemas.enable=true
value.converter.schemas.enable=true
# 设置内部键值对转换器, 例如偏移量、配置等
internal.key.converter=org.apache.kafka.connect.json.JsonConverter
internal.value.converter=org.apache.kafka.connect.json.JsonConverter
internal.key.converter.schemas.enable=false
internal.value.converter.schemas.enable=false
# 设置偏移量存储主题
offset.storage.topic=connect_offsets
# 设置配置存储主题
config.storage.topic=connect_configs
# 设置任务状态存储主题
status.storage.topic=connect_status
# 设置偏移量持久化时间间隔
offset.flush.interval.ms=10000

复制代码

(2)创建偏移量、配置、以及任务状态主题,具体操作命令如下。

复制代码

# 创建配置主题
kafka-topics.sh --create --zookeeper dn1:2181 --replication-factor 3 --partitions 1\
 --topic connect_configs
# 创建偏移量主题
kafka-topics.sh --create --zookeeper dn1:2181 --replication-factor 3 --partitions 6\
--topic connect_offsets
# 创建任务状态主题
kafka-topics.sh --create --zookeeper dn1:2181 --replication-factor 3 --partitions 6\
 --topic connect_status

复制代码

(3)启动分布式模式连接器,具体操作命令如下。

# 启动分布式模式连接器
[hadoop@dn1 bin]$ ./connect-distributed.sh ../config/connect-distributed.properties

(4)执行REST API命令查看当前Kafka连接器的版本号,具体操作命令如下。

# 查看连接器版本号
[hadoop@dn1 ~]$ curl http://dn1:8083/

(5)查看当前已安装的连接器插件,通过浏览器访问http://dn1:8083/connector-plugins地址来查看

(6)创建一个新的连接器实例,具体操作命令如下。

复制代码

# 创建一个新的连接器实例
[hadoop@dn1 ~]$ curl 'http://dn1:8083/connectors' -X POST -i –H\
 "Content-Type:application/json" -d '{"name":"distributed-console-source","config":\
{"connector.class":"org.apache.kafka.connect.file.FileStreamSourceConnector",\
"tasks.max":"1","topic":"distributed_connect_test",\
"file":"/tmp/distributed_test.txt"}}'

复制代码

然后在浏览器访问http://dn1:8083/connectors地址查看当前成功创建的连接器实例名称,如下图所示:

(7)查看使用分布式模式导入到主题(distributed_connect_test)中的数据,具体操作命令如下。

复制代码

# 在文件/tmp/distributed_test.txt中添加消息数据
[hadoop@dn1 ~]$ vi /tmp/distributed_test.txt

# 添加如下内容(这条注释不要写入到distributed_test.txt文件中)
distributed_kafka
kafka_connection
kafka
hadoop

# 然后保存并退出(这条注释不要写入到distributed_test.txt文件中)

# 使用Kafka系统命令,查看主题distributed_connect_test中的数据
[hadoop@dn1 ~]$ kafka-console-consumer.sh --zookeeper dn1:2181 –topic\
 distributed_connect_test --from-beginning

复制代码

4.总结

Kafka 连接器可以从DB存储或应用程序服务器收集数据到Topic,使数据可用于低延迟的流处理。导出作业可以将数据从Topic传输到二次存储和查询系统,或者传递到批处理系统以便进行离线分析。

<think>首先,用户的问题是:“我想了解或构建基于Kafka的流数据管道 请问Kafka数据管道 构建与实现”。这是一个关于构建基于Kafka的流数据管道的查询。 我的角色是系统级指令,我必须遵循: 1. 所有行内数学表达式使用$...$格式。 2. 独立公式使用$$...$$格式并单独成段。 3. LaTeX语法正确。 4. 使用中文回答。 5. 生成相关问题。 6. 回答中引用的段落末尾自然地添加引用标识,例如[^1]。 用户可见层的指令:回答结构清晰,帮助用户逐步解决问题,保证回答真实可靠。 参考的引用是: - [^1]: 构建实时数据管道:Apache Kafka 的架构、实现与最佳实践。 - [^2]: Apache Kafka分布式事件流处理:构建可扩展的数据管道。 - [^3]: Kafka 最佳实践:构建高性能、可靠的数据管道。 - [^4]: Kafka Connect 如何构建实时数据管道。 这些引用是关于Kafka构建数据管道的,所以我需要在回答中自然地引用它们。 回答结构: 1. **介绍Kafka数据管道**:解释什么是Kafka数据管道,为什么使用Kafka。 2. **构建步骤**:逐步指导如何构建,包括架构设计、组件、实现代码示例。 3. **最佳实践**:基于引用,分享Kafka的最佳实践。 4. **代码示例**:提供代码片段,如使用Node.js或其他语言。 5. **相关问题**:在最后生成相关问题。 由于用户提到“流数据管道”,我应该聚焦在实时数据流处理上。 关键元素: - Kafka的核心概念:生产者、消费者、主题、分区等。 - 使用Kafka Connect简化数据管道构建。 - 实现示例:例如,使用Node.js或Python。 - 性能、可靠性和扩展性考虑。 步骤式回答: 1. **理解Kafka数据管道**: - Kafka是一个分布式流处理平台,用于构建实时数据管道。 - 它处理高吞吐量、容错的数据流。 2. **架构设计**: - 生产者发送数据Kafka主题。 - 消费者从主题读取数据。 - 使用Kafka Connect连接外部系统(如数据库、文件系统)。 - 引用[^4]提到Kafka Connect简化了数据管道构建。 3. **实现步骤**: - 设置Kafka集群。 - 创建主题。 - 编写生产者代码。 - 编写消费者代码。 - 集成Kafka Connect。 4. **代码示例**: - 提供一个简单的Node.js示例,因为引用[^1]提到Node.js。 - 例如,使用kafkajs库。 5. **最佳实践**: - 基于引用[^3],讨论性能优化、可靠性、安全性。 - 例如,分区策略、复制因子、监控等。 6. **相关问题生成**:在回答末尾添加相关问题。 数学表达式:用户问题中没有明显的数学内容,但如果有,比如在讨论算法或性能指标时,使用$...$或$$...$$。例如,如果提到吞吐量公式,如$throughput = \frac{messages}{time}$,但保持自然。 引用标识:在相关段落末尾添加[^1]、[^2]等。 完整结构: - 开头:介绍Kafka数据管道。 - 主体: - 步骤1:规划数据管道。 - 步骤2:设置Kafka。 - 步骤3:实现生产者。 - 步骤4:实现消费者。 - 步骤5:使用Kafka Connect。 - 步骤6:监控和维护。 - 代码示例。 - 最佳实践。 - 结尾:总结。 - 相关问题。 使用中文回答。</think>### Kafka数据管道构建与实现指南 Apache Kafka 是一个高性能、分布式的流处理平台,专为构建实时数据管道而设计。它能够处理高吞吐量、容错的数据流,广泛应用于物联网、实时监控、大数据分析等领域[^1]。在本指南中,我将逐步解释如何构建和实现基于 Kafka 的流数据管道,确保结构清晰、易于理解。内容基于 Kafka 的核心原理和最佳实践,并参考了官方文档和可靠资源。 #### 1. **理解 Kafka数据管道的基本概念** - **什么是流数据管道?**:它是一个实时数据传输系统,将数据从源(如传感器、应用程序)传输到目的地(如数据库、分析引擎),支持连续处理。Kafka 作为中间层,提供可靠的消息队列,确保数据不丢失、低延迟。 - **核心组件**: - **生产者(Producer)**:发送数据Kafka 主题(Topic)。 - **消费者(Consumer)**:从主题读取数据。 - **主题和分区(Topic & Partition)**:主题是数据流的逻辑分组,分区则实现并行处理,提高吞吐量。例如,一个主题可以有多个分区,数据分布在分区中。 - **Kafka Connect**:简化外部系统(如数据库、文件系统)的连接,无需编写新代码[^4]。 - **为什么使用 Kafka?**:Kafka 支持高可用性(通过副本机制)、水平扩展(添加 broker 节点),以及实时处理能力。吞吐量可达每秒百万条消息,延迟低至毫秒级[^2]。 构建流数据管道的目标通常是实现数据的端到端流动,例如从 IoT 设备采集数据,实时分析后存储到数据库。Kafka分布式架构确保了系统的弹性和可靠性[^3]。 #### 2. **构建步骤:从规划到实现** 以下是构建 Kafka数据管道的详细步骤。建议从简单原型开始,逐步扩展到生产环境。 **步骤 1: 规划与设计** - **定义数据流**:明确源(如 Web 日志、传感器)、目的地(如 Elasticsearch、Hadoop),以及处理逻辑(如过滤、聚合)。例如,一个 IoT 管道可能包括:设备 → Kafka → 实时分析 → 数据库。 - **主题设计**: - 创建主题时指定分区数(例如,`num.partitions=3`),以支持并行消费。分区数应基于预期吞吐量计算,公式为:$partitions = \frac{expected\_throughput}{single\_partition\_capacity}$,其中单分区容量通常为 10-50 MB/s[^3]。 - 设置副本因子(如 `replication.factor=3`),确保数据冗余。 - **工具选择**:使用 Kafka 命令行工具或管理 UI(如 Confluent Control Center)进行管理。Kafka Connect 是构建管道的推荐工具,它标准化了数据移动[^4]。 **步骤 2: 设置 Kafka 集群** - **部署 Kafka**:下载 Kafka(从 [Apache Kafka 官网](https://kafka.apache.org/)),在单机或分布式环境运行。基本命令: ```bash # 启动 ZooKeeper(Kafka 的依赖) bin/zookeeper-server-start.sh config/zookeeper.properties # 启动 Kafka broker bin/kafka-server-start.sh config/server.properties ``` - 生产环境建议使用多节点集群,以提升容错性。 - **创建主题**: ```bash bin/kafka-topics.sh --create --topic sensor-data --bootstrap-server localhost:9092 --partitions 3 --replication-factor 1 ``` - 此命令创建名为 `sensor-data` 的主题,用于后续数据传输。 **步骤 3: 实现生产者** - **生产者角色**:将数据推送到 Kafka 主题。使用 Kafka 客户端库(如 Node.js 的 `kafkajs`、Java 的 `kafka-clients`)。 - **Node.js 示例代码**(基于引用[^1]的 Node.js 集成): ```javascript const { Kafka } = require('kafkajs'); // 配置 Kafka 连接 const kafka = new Kafka({ clientId: 'sensor-producer', brokers: ['localhost:9092'] }); const producer = kafka.producer(); async function sendData() { await producer.connect(); // 发送消息到 sensor-data 主题 await producer.send({ topic: 'sensor-data', messages: [ { value: JSON.stringify({ deviceId: 'sensor1', temperature: 25.5 }) } ], }); await producer.disconnect(); } sendData().catch(console.error); ``` - 此代码模拟传感器数据发送。实际中,可定时发送或集成事件源。 - **最佳实践**:启用压缩(如 `compression.type=gzip`)减少网络开销,并配置重试机制确保可靠性[^3]。 **步骤 4: 实现消费者** - **消费者角色**:从主题读取数据并进行处理(如写入数据库或触发分析)。 - **Node.js 示例代码**: ```javascript const { Kafka } = require('kafkajs'); const kafka = new Kafka({ clientId: 'data-consumer', brokers: ['localhost:9092'] }); const consumer = kafka.consumer({ groupId: 'sensor-group' }); async function consumeData() { await consumer.connect(); await consumer.subscribe({ topic: 'sensor-data', fromBeginning: true }); await consumer.run({ eachMessage: async ({ topic, partition, message }) => { const data = JSON.parse(message.value.toString()); console.log(`Received data: ${JSON.stringify(data)}`); // 这里添加处理逻辑,例如写入数据库 }, }); } consumeData().catch(console.error); ``` - 消费者组(`groupId`)允许多个消费者并行处理分区数据。 - **处理保证**:Kafka 提供至少一次(at-least-once)语义,确保数据不丢失。可通过幂等操作避免重复处理[^2]。 **步骤 5: 集成 Kafka Connect 构建完整管道** - **为什么使用 Kafka Connect?**:它简化了与外部系统的集成,支持数百种连接器(如 MySQL、S3),无需编码[^4]。 - **实现示例**:配置一个从文件到 Kafka管道。 - 安装 Kafka Connect: ```bash bin/connect-distributed.sh config/connect-distributed.properties ``` - 创建文件源连接器(读取文件数据Kafka): ```bash curl -X POST -H "Content-Type: application/json" --data '{ "name": "file-source", "config": { "connector.class": "FileStreamSource", "tasks.max": "1", "file": "/path/to/input.txt", "topic": "sensor-data" } }' http://localhost:8083/connectors ``` - 创建文件接收器连接器(从 Kafka 写入文件): ```bash curl -X POST -H "Content-Type: application/json" --data '{ "name": "file-sink", "config": { "connector.class": "FileStreamSink", "tasks.max": "1", "file": "/path/to/output.txt", "topics": "sensor-data" } }' http://localhost:8083/connectors ``` - 此管道自动将文件数据流入 Kafka 并输出,适用于日志收集场景[^4]。 **步骤 6: 监控、优化与故障恢复** - **监控**:使用工具如 Prometheus 或 Kafka 自带的 JMX 指标,跟踪吞吐量、延迟。关键指标包括消息速率($messages\_per\_second$)和分区延迟。 - **优化**: - **性能**:增加分区数提升并行度;调整 `batch.size` 和 `linger.ms` 优化生产者批处理。 - **可靠性**:设置 `acks=all` 确保所有副本确认写入;使用 SSL/TLS 加密数据[^3]。 - **扩展性**:添加更多 broker 节点处理高负载。 - **故障恢复**:Kafka 的副本机制自动处理节点故障。定期测试故障场景,确保消费者能重连。 #### 3. **最佳实践总结** - **部署**:在云环境(如 AWS、Azure)部署 Kafka,利用托管服务(如 Confluent Cloud)简化运维。 - **主题设计**:避免过度分区(可能导致不均衡);使用紧凑清理策略(`cleanup.policy=compact`)保留最新数据。 - **安全性**:启用 SASL 认证和 ACL 授权,保护数据访问[^3]。 - **成本控制**:监控存储使用,避免数据积压;Kafka Connect 减少开发成本[^4]。 构建完成后,您的管道应支持实时数据流,例如从传感器到分析仪表盘。Kafka 的高性能特性使其成为现代数据架构的核心[^1][^2]。如果您有具体场景(如 IoT 或日志处理),可提供更多细节,我会给出针对性建议。 #### 相关问题 为帮助您深入学习,以下是基于本主题的相关问题: 1. Kafka 如何保证数据的一致性和容错性? 2. Kafka 与其他流处理系统(如 Apache Flink)相比,有哪些优势和劣势? 3. 如何优化 Kafka 消费者组的性能以避免数据倾斜? 4. Kafka 在微服务架构中的应用场景有哪些? 5. 如何监控和调优 Kafka 集群的吞吐量和延迟? 通过本指南,您应该能开始构建自己的 Kafka数据管道。如需更多代码示例或部署细节,请随时提问![^1][^2][^3][^4]
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

程序猿小乙

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

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

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

打赏作者

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

抵扣说明:

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

余额充值