Flink广播流
Flink实时topN
在实习中一般都怎么用Flink
Savepoint知道是什么吗
为什么用Flink不用别的微批考虑过吗
解释一下啥叫背压
Flink分布式快照
Flink SQL解析过程
Flink on YARN模式
Flink如何保证数据不丢失
Flink广播流
Apache Flink 中的广播流(Broadcast State)是一种特殊类型的状态管理机制,它允许将一个流中的数据广播到所有并行实例上的所有或者部分 operator 实例中,使得每个实例都能接收到完整的广播数据。这对于需要在多个流之间共享固定数据或动态配置信息的场景非常有用,例如规则引擎、动态参数配置更新等。
1、使用场景
- 规则或配置更新:当有新的业务规则或配置需要实时推送到所有计算节点时,可以使用广播流来高效地分发这些变化。
- 维表关联:在流处理中,有时需要将流数据与维度表(通常是相对静态的大表)进行关联。广播流可以用来广播维度表数据到所有任务,实现类似于数据库join的操作,而无需每个事件都查询外部系统。
- 事件驱动的应用:在事件驱动架构中,全局事件(如系统状态变更通知)可以通过广播流发送,确保所有相关组件都能及时响应。
2、实现机制
- BroadcastState API:Flink 提供了BroadcastState接口,它可以在BroadcastProcessFunction或KeyedBroadcastProcessFunction中使用。这些函数允许用户处理常规的数据流和广播流的组合。
- Connect & Process:要使用广播流,通常先将主数据流(data stream)和广播流(通常来源于较小、变化不频繁的数据源)通过connect()方法连接起来,然后使用上述函数处理这两个流的交互。
- 状态管理:在接收端的 operator 中,可以访问广播状态来存储和查询广播的数据。每个并行子任务都会维护一份完整的广播数据副本。
注意事项
- 数据复制:广播会导致数据复制到所有相关任务,因此对于大型数据集,应谨慎使用以避免内存压力。
- 一致性:广播的状态更新是全有全无的,即所有任务要么同时收到新广播的数据,要么都不收到。因此,它不适合需要精确控制数据版本或顺序的场景。
- 资源消耗:广播流可能会增加网络传输量和状态存储需求,因此在设计时需考虑资源优化。
示例代码片段
// 创建广播流
DataStream<String> broadcastStream = ...;
// 主数据流
DataStream<Event> mainStream = ...;
// 连接主数据流和广播流
BroadcastStream<String> broadcastedStream = broadcastStream.broadcast(StateDescriptor);
// 使用 KeyedBroadcastProcessFunction 处理连接后的流
DataStream<OutputType> result = mainStream
.connect(broadcastedStream)
.process(new MyKeyedBroadcastProcessFunction());
在实际应用中,根据具体需求选择合适的函数和状态描述符来实现广播流的处理逻辑。
Flink实时topN
Flink实时TopN是指在Apache Flink流处理框架中,根据实时数据流计算并输出某个维度下的前N个最大或最小值。这种查询在实时数据分析、监控和推荐系统中非常常见。以下将详细阐述Flink实时TopN的实现方法、关键点及优化策略。
实现方法
1) 数据源定义:
- 首先,需要定义数据源,这可以是Kafka、文件、数据库等任何支持的数据源。数据源应包含需要进行TopN计算的字段,如商品ID、销量、时间戳等。
2) 数据转换:
- 对数据流进行必要的转换,如映射、过滤、时间戳提取等。确保数据流中的每条记录都包含正确的时间戳和用于排序的字段。
3) 窗口定义:
- 使用Flink的窗口机制(如滚动窗口、滑动窗口)来定义时间范围。窗口的大小和滑动间隔取决于业务需求,例如每分钟计算一次TopN。
4) 分组与排序:
- 使用keyBy函数根据特定字段(如商品类别)对数据进行分组。然后,在窗口内使用ROW_NUMBER()、RANK()或DENSE_RANK()等窗口函数对数据进行排序,并分配排名。
5) 过滤与输出:
- 通过WHERE子句过滤出排名在前N的记录,并将结果输出到指定的目的地,如Kafka、数据库或控制台。
关键点
1) 时间管理:
- Flink中的时间管理非常重要,包括事件时间(Event Time)、处理时间(Processing Time)和摄入时间(Ingestion Time)。在处理实时TopN时,通常使用事件时间,并设置合理的水印(Watermark)来处理乱序事件和数据延迟。
2) 状态管理:
- Flink使用状态来存储窗口内的数据。对于实时TopN,可能需要使用较大的状态来存储每个窗口内的TopN记录。这要求合理配置状态后端(如RocksDBStateBackend)以支持大规模状态存储。
3) 性能优化:
- 为了提高性能,可以考虑使用增量聚合函数来减少窗口内的计算量。此外,还可以优化数据源的读取和结果的写入过程,以减少I/O开销。
4) 容错处理:
- Flink通过检查点(Checkpoint)机制来确保在发生故障时能够恢复状态。对于实时TopN,需要确保检查点机制能够正常工作,并在故障发生时快速恢复状态和数据。
优化策略
1) 减少状态大小:
- 可以通过只存储必要的TopN记录来减少状态大小。例如,如果只需要前100名,则无需存储整个窗口内的所有记录。
2) 使用增量聚合:
- 在窗口内使用增量聚合函数来减少计算量。例如,在每次窗口触发时只计算新增数据的TopN,并与前一个窗口的结果合并。
3) 并行处理:
- 利用Flink的并行处理能力来加速数据处理。通过增加并行度,可以将数据分布在多个任务槽(Task Slot)中并行处理。
4) 定期清理旧状态:
- 对于基于时间窗口的TopN计算,可以定期清理旧窗口的状态数据,以释放内存和磁盘空间。
综上所述,Flink实时TopN的实现涉及数据源定义、数据转换、窗口定义、分组与排序、过滤与输出等多个环节。在实现过程中,需要关注时间管理、状态管理、性能优化和容错处理等关键点,并采取相应的优化策略来提高处理效率和可靠性。
在项目中一般都怎么用Flink
1、需求分析与设计:
- 明确项目需求,比如是否需要实时处理、数据源是什么、处理逻辑复杂度、输出目标等。
- 设计数据流拓扑,包括数据源(Source)、处理逻辑(Transformations)、以及数据接收方(Sink)。
2、环境搭建:
- 准备基础设施,可以是本地开发环境、云平台(如阿里云Flink服务)或自建集群。
- 确保安装了Java(通常需要Java 8及以上版本)和Maven,用于构建和运行Flink应用。
- 下载并配置Flink,包括配置JobManager、TaskManager等。
3、编写代码:
- 使用Flink的API(如DataStream API或Table API)编写处理逻辑。
- 实现Source、Transformation和Sink。例如,使用FlinkKafkaConsumer消费Kafka数据,使用各种Transformations进行数据处理,然后通过FlinkKafkaProducer或其他Sink输出数据。
- 对于复杂逻辑,可能需要实现自定义的函数(如RichFunction系列)来处理状态管理和定时器等高级功能。
4、测试:
- 单元测试和集成测试,利用Flink的测试工具,如TestEnvironment,来模拟流处理环境进行测试。
- 可以在本地或小型集群上进行端到端测试,验证应用逻辑正确性。
5、部署与监控:
- 将应用打包成JAR文件,使用Flink的命令行工具或REST API提交作业到集群。
- 监控作业执行情况,可以使用Flink Web UI查看作业状态、性能指标等。
- 配置告警和日志收集,以便于问题排查和性能优化。
6、运维与优化:
- 根据作业运行情况调整资源配置,如并行度、内存和CPU等。
- 利用Flink的Checkpoint机制保证作业的容错性和状态一致性。
- 对于长期运行的任务,考虑使用Savepoint进行升级或迁移。
- 持续监控并根据需要进行性能调优和故障排除。
7、扩展与集成:
- 根据项目需求集成外部系统,如数据库、消息队列、文件系统等。
- 利用Flink的连接器(Connectors)和格式(Formats)简化与外部系统的交互。
- 考虑使用Flink SQL或Table API来简化数据处理逻辑,特别是当涉及复杂查询或与关系型数据库交互时。
8、持续迭代与优化:
- 根据业务需求变化和性能反馈不断优化和调整应用逻辑。
- 保持Flink及其依赖库的更新,以获取最新的功能和性能提升。
在实际项目中,上述步骤可能根据团队习惯、项目规模和技术栈有所不同,但整体流程大致相似。
Savepoint知道是什么吗
一、定义
Savepoint是Flink中一种特殊的检查点(Checkpoint),但它与自动触发的Checkpoint在触发方式、用途和管理方式上有所不同。Savepoint允许用户通过手动方式触发Checkpoint,并将结果持久化存储到指定路径中,主要用于避免Flink集群在重启或升级时导致状态丢失。
二、特点
- 手动触发:与Checkpoint的自动触发不同,Savepoint需要用户显式触发,这提供了更高的灵活性和可控性。
- 全量备份:Savepoint是全量的,不支持增量的。这意味着它包含了作业状态的完整快照,而不是像某些Checkpoint那样只包含增量变化。
- 可移植性和版本兼容性:Savepoint更注重可移植性和版本兼容性,确保在不同版本或不同集群环境中都能成功恢复作业状态。
- 用户掌控:Savepoint的触发、存储和清理都由用户掌控,这使得用户可以根据实际需求灵活管理作业状态。
三、使用场景
- 集群重启或升级:在Flink集群需要重启或升级时,使用Savepoint可以避免作业状态的丢失,确保作业的连续性和稳定性。
- 作业状态备份:用户可以在作业运行的任意时刻创建Savepoint,以备份当前作业状态。这在需要回滚到某个特定状态或进行故障排查时非常有用。
- 作业迁移:在需要将作业从一个Flink集群迁移到另一个集群时,Savepoint提供了一种便捷的方式来迁移作业状态。
四、操作方法
1) 创建Savepoint:
- 可以通过Flink的命令行工具手动触发Savepoint的创建。例如,使用flink savepoint :jobId [:targetDirectory]命令来创建Savepoint。
- 也可以在作业停止时自动保存Savepoint,这需要在Flink的配置文件中设置相关参数。
2) 恢复作业:
- 当需要从Savepoint恢复作业时,可以使用flink run -s :savepointPath [:runArgs]命令来启动作业,并指定Savepoint的路径作为启动参数。
- Flink会自动从指定的Savepoint加载作业状态,并继续执行作业。
3) 删除Savepoint:
- 如果不再需要某个Savepoint,可以使用flink savepoint -d :savepointPath命令来删除它,以释放存储空间。
五、总结
Savepoint是Flink中一种重要的状态管理机制,它允许用户手动创建和恢复作业状态的快照。通过Savepoint,用户可以更好地控制作业状态的管理,提高作业的可靠性和稳定性。在实际应用中,用户应根据具体需求选择合适的时机创建Savepoint,并在需要时从Savepoint恢复作业状态。
为什么用Flink不用别的微批考虑过吗
1、真正的流处理能力:
- Flink是原生为流处理设计的框架,它可以逐个事件地处理数据,提供低延迟的实时处理能力。相比之下,基于微批的系统(如早期的Spark Streaming)通过将数据分成小批次来模拟流处理,这种方式在处理时间敏感型应用时可能导致较高的延迟。
2、低延迟与高吞吐量:
- Flink设计上优化了流处理性能,能够实现实时的、低延迟的数据处理,同时保持高吞吐量。这对于要求实时响应的应用场景(如实时分析、实时欺诈检测)至关重要。
3、强大的时间处理能力:
- Flink支持事件时间(Event Time)处理,这意味着它能够准确地处理乱序事件,并通过Watermark机制处理迟到数据,这对于很多需要精确时间语义的业务逻辑非常重要。相比之下,虽然Spark Structured Streaming也引入了类似的功能,但在Flink中这一特性更为成熟和广泛使用。
4、灵活的窗口机制:
- Flink支持丰富的窗口操作,不仅限于时间窗口,还包括滑动窗口、滚动窗口、会话窗口等,且窗口可以基于事件时间、处理时间和数据本身定义,提供了高度灵活的数据处理能力。
5、状态管理与容错:
- Flink拥有强大的状态管理机制,允许状态在算子间共享,这对于复杂的流处理应用至关重要。其检查点(Checkpointing)机制能够在故障发生时快速恢复状态,保证了应用的高可用性。
6、批处理与流处理的统一:
- Flink支持同时处理批数据和流数据,采用同一套API,使得开发者可以更容易地在两种处理模式间切换,减少代码重写和维护成本。这与Spark的“Lambda架构”不同,后者需要分别处理批处理和流处理逻辑。
7、生态系统与社区支持:
- Flink拥有活跃的社区和不断增长的生态系统,提供了丰富的连接器、转换函数和库,方便与各种数据源和系统集成,增强了其在实际应用中的灵活性和适用范围。
综上所述,Flink之所以在某些场景下成为比微批处理框架更优的选择,是因为它在实时性、时间处理、状态管理等方面具有明显优势,特别适合那些对低延迟和事件处理精度有严格要求的应用场景。当然,具体选择哪种技术还需要根据项目的具体需求、团队熟悉度、生态支持等因素综合考量。
解释一下啥叫背压
背压(Backpressure)是在数据处理系统中,尤其是在流处理和消息传递系统中,一个重要的概念。它指的是数据生产速度超过数据消费速度时,系统为了保持稳定性,会向数据生产端施加的一种反向压力,从而减慢生产速度或者暂时缓冲数据,避免因消费端处理能力不足而导致的数据丢失、系统崩溃或性能恶化。
具体到不同的上下文,背压机制的实现方式和表现形式可能有所不同,但核心目的都是为了平衡生产者和消费者之间的速率差异,确保系统的整体稳定性和可靠性。以下是几个与背压相关的要点:
1、消息队列:在消息队列系统中,如果消费者处理消息的速度跟不上生产者的发布速度,队列的长度会不断增加。此时,一些队列系统会实施背压策略,如拒绝新消息、减速生产者发送速率或等待消费者确认后再发送更多消息,以此防止内存或磁盘空间耗尽。
2、流处理:在实时流处理系统中,背压机制尤为重要。例如,在Flink或Kafka Streams应用中,如果下游操作(如计算、写入数据库)无法跟上上游数据流入的速度,系统会自动调整,比如减缓数据读取速度或在某些环节增加缓冲,以维持处理管道的稳定流动,避免数据积压过多导致的内存溢出等问题。
3、网络通信:在TCP/IP协议中,接收端通过流量控制机制(如窗口大小调整)也可以实现背压,通知发送端减慢发送速率,直到接收端有能力处理更多数据。
4、反应式编程:在反应式系统设计中,背压是响应式流规范(Reactive Streams)的核心原则之一,它通过标准化的API(如Java的Flow API或Akka Streams)让生产者和消费者能够协商数据流动速率,自动管理数据流的速率匹配,避免过载。
总之,背压机制是现代分布式系统中用于保护系统资源、维持数据处理管道健康运行的关键策略,通过动态调整数据生成和消费的速度,确保系统的稳定性和可伸缩性。
Flink分布式快照
Flink分布式快照(Distributed Snapshots)是Apache Flink中实现状态一致性和容错性的关键机制。以下是对Flink分布式快照的详细解释,包括其生成过程、存储方式、恢复机制以及特点等方面:
一、生成过程
1) 状态树遍历:
- Flink中的状态被组织成一个有向无环图(DAG)结构,称为状态树。快照生成过程首先对状态树进行遍历,从根节点开始逐层遍历直到叶子节点,以收集状态的当前值和元数据信息。
2) 序列化:
- 在状态树遍历过程中,系统会将每个状态的当前值和元数据信息进行序列化,以便将其写入快照文件中。序列化过程通常使用Flink提供的序列化器,将状态数据转换为字节流并写入输出流。
3) 写入快照文件:
- 序列化后的状态数据被写入快照文件中,这些文件通常存储在持久化存储系统(如分布式文件系统、对象存储系统等)中,以确保数据的持久性和可靠性。
4) 记录元数据信息:
- 在生成快照的过程中,系统还会记录快照的元数据信息,包括快照的版本号、生成时间、状态树的结构信息等。这些元数据信息通常存储在外部存储系统(如ZooKeeper、HDFS等)中,以便在恢复过程中快速定位和加载快照文件。
二、存储方式
持久化存储系统:
- 快照文件通常以分布式文件的形式存储在持久化存储系统中,如分布式文件系统(HDFS、S3等)、对象存储系统(MinIO、Aliyun OSS等)以及分布式数据库(RocksDB、Cassandra等)。
- 系统通常会根据配置和需求选择合适的存储系统,并将快照文件写入其中。
三、恢复机制
1) 加载元数据信息:
- 在恢复过程开始时,系统首先加载快照的元数据信息,包括快照的版本号、生成时间、状态树的结构信息等。
2) 定位并加载快照文件:
- 根据元数据信息,系统定位快照文件并将其加载到内存中。这通常涉及从持久化存储系统中读取快照文件。
3) 解析快照文件:
- 系统解析快照文件,将其中的状态数据和元数据信息恢复到内存中。这包括读取快照文件、反序列化状态数据、重建状态树等步骤。
4) 应用状态数据:
- 在解析快照文件完成后,系统会将快照中的状态数据应用到相应的算子和任务中,以恢复处理的上下文和状态信息。
四、特点
1、一致性保证:
- Flink分布式快照机制保证了在发生故障或重启时,能够将状态恢复到之前的某个一致性点,从而保证数据处理的正确性和完整性。
2、容错性:
- 通过快照机制,Flink能够在发生故障时快速恢复状态,减少数据丢失和处理中断的风险。
3、灵活性:
- Flink支持多种存储系统用于存储快照文件,用户可以根据实际需求选择合适的存储方案。
4、可扩展性:
- Flink分布式快照机制能够处理大规模数据流,支持在成千上万的节点上运行,并具有良好的可扩展性。
综上所述,Flink分布式快照是实现状态一致性和容错性的重要机制,它通过状态树的遍历、序列化、存储和恢复等步骤,确保在发生故障时能够快速恢复状态,保证数据处理的连续性和准确性。
Flink SQL解析过程
Flink SQL的解析过程主要涉及以下几个阶段,这些步骤确保了从用户编写的SQL查询到执行计划的生成:
1、解析(Parsing):
- SQL到AST转换:首先,Flink利用Apache Calcite这一开源框架对输入的SQL查询语句进行解析。Calcite的SQL解析器会将SQL文本转换成抽象语法树(Abstract Syntax Tree, AST),即SqlNode Tree。这个树状结构清晰地展现了SQL语句的各个组成部分及其之间的关系。
2、验证(Validation):
- SqlNode验证:接下来,Calcite的验证器会对生成的SqlNode进行校验。这一步骤确保SQL语句的语法正确无误,同时检查表达式的合法性和表信息的有效性。如果存在任何语法错误或是表、字段不存在的情况,验证器会抛出相应的异常。
3、语义分析(Semantic Analysis):
- 转换为RelNode:经过验证的SqlNode会被进一步转换成关系表达式节点(RelNode),这是查询计划的逻辑表示,也称为Logical Plan。这个过程涉及到对SQL语句进行更深层次的语义理解,比如确定表的引用、字段的映射等,并将之转化为关系代数的形式。
4、优化(Optimization):
- 在生成Logical Plan之后,Flink会运用一系列优化规则对逻辑计划进行优化。这包括但不限于重写查询、消除冗余操作、选择最优的执行路径等,目的是为了提高执行效率和减少资源消耗。
5、物理规划(Physical Planning):
- Materialization:优化后的逻辑计划会被转换为物理执行计划。在这个阶段,系统会决定如何具体执行查询,比如选择特定的运算符实现、数据分区策略等,这一步是为了适应Flink的执行环境并选择最佳的物理实现方式。
6、执行(Execution):
- 最后,物理执行计划会被提交到Flink的运行时环境中执行。根据物理计划,Flink会调度任务,创建必要的数据流,并开始处理数据,最终产生查询结果。
整个解析过程中,Flink依赖于Calcite进行SQL解析和验证,同时也结合自身的优化器来进一步提升SQL查询的执行效率。通过这些步骤,Flink确保了SQL查询能够高效、准确地在分布式环境中执行。
Flink on YARN模式
Flink on YARN 模式是指 Apache Flink 应用程序在 YARN(Yet Another Resource Negotiator)集群上运行的一种部署方式。YARN 是 Hadoop 生态系统中的一个资源管理和作业调度框架,它允许多个应用程序共享同一个 Hadoop 集群的资源。Flink on YARN 模式使得 Flink 作业能够动态地申请和释放 YARN 集群中的资源,从而实现高效的资源利用和灵活的作业调度。
Flink on YARN 的主要特点:
1) 资源动态分配:
- Flink on YARN 模式允许 Flink 作业根据需求动态地向 YARN 集群申请资源(如 CPU、内存等),并在作业完成后释放这些资源。这种动态的资源分配机制使得 Flink 能够更加高效地利用集群资源。
2) 容错性:
- YARN 提供了容错机制,当 Flink 作业中的某个 TaskManager 或 JobManager 失败时,YARN 能够自动重启这些组件,确保作业的连续性和稳定性。
3) 多租户支持:
- YARN 支持多租户环境,允许多个 Flink 作业同时运行在同一个 YARN 集群上,每个作业都可以独立地管理自己的资源和执行状态。
4) 易用性:
- Flink 提供了与 YARN 集成的客户端和命令行工具,使得用户能够轻松地在 YARN 集群上提交、管理和监控 Flink 作业。
Flink on YARN 的部署流程:
1) 环境准备:
- 确保 Hadoop 和 YARN 集群已经正确安装并配置。
- 安装 Flink 并配置 Flink 以支持 YARN 模式。
2) 提交作业:
- 使用 Flink 提供的命令行工具(如 flink run)提交作业到 YARN 集群。
- 在提交作业时,可以指定作业所需的资源(如 CPU、内存等)和其他配置参数。
3) 资源分配:
- YARN 集群根据作业的资源请求和集群的当前状态,为 Flink 作业分配相应的资源。
- Flink 启动 JobManager 和 TaskManager 组件,并加载作业的执行图。
4) 作业执行:
- Flink 作业在分配的资源上执行,处理输入数据流并产生输出。
- YARN 监控作业的执行状态,并在需要时提供容错支持。
5) 资源释放:
- 当 Flink 作业完成时,YARN 集群释放分配给该作业的资源。
注意事项:
- 确保 Flink 版本与 YARN 集群的版本兼容。
- 根据作业的需求合理配置资源,避免资源浪费或不足。
- 监控 YARN 集群和 Flink 作业的性能指标,以便及时发现和解决问题。
Flink on YARN 模式为 Flink 应用程序提供了一种灵活、高效和可靠的部署方式,使得 Flink 能够更好地适应大规模数据处理和实时分析的需求。
Flink如何保证数据不丢失
Apache Flink 通过以下几个关键机制来确保数据不丢失,这些机制共同工作以实现高可靠性和数据一致性:
1、Checkpointing(检查点): Flink 的检查点机制是其数据不丢失的核心保障。定期创建检查点可以保存流应用的快照,包括所有操作的状态和源的读取位置。当发生故障时,Flink 会从最近完成的检查点恢复,从而恢复所有状态并重新定位到正确的读取位置,继续处理数据,避免数据丢失。
2、Exactly-Once Semantics(精确一次语义): 为了实现数据不丢失且不重复,Flink 支持端到端的精确一次处理语义。这要求Source、Transformation 和Sink都支持事务性或幂等操作。在Sink端,Flink 实现了两阶段提交协议来确保数据被精确地写入一次,即使在写入过程中发生故障也是如此。
3、Savepoints(保存点): 保存点类似于检查点,但它们是手动触发的,并且可以在升级或迁移作业时使用,以保持状态的连续性。在作业重启或迁移时,可以从保存点恢复,确保数据处理的连贯性,避免数据丢失。
4、Watermarks(水印机制): Flink 使用水印机制来处理乱序事件和实现事件时间的一致性。水印允许系统知道某个时间点之前的所有事件都已经到达,这样就可以在处理延迟数据时作出适当处理,而不是简单地丢弃,从而保证数据完整性。
5、状态管理: Flink 的状态后端(如RocksDB State Backend)可以将状态持久化到外部存储,确保状态在故障恢复时可用。这增强了状态的持久性,减少了数据丢失的风险。
综上所述,Flink通过频繁的检查点创建、精确一次的处理语义、灵活的保存点机制、水印机制以及强大的状态管理能力,共同构建了一个高度可靠的流处理系统,有效保证了数据在处理过程中的不丢失。用户需要合理配置Checkpoint间隔,确保在性能和数据安全性之间达到平衡,并且根据应用场景选择合适的sink类型和配置,以实现期望的数据处理语义。
引用:https://www.nowcoder.com/discuss/353159520220291072
通义千问、文心一言
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明原文出处。如若内容造成侵权/违法违规/事实不符,请联系SD编程学习网:675289112@qq.com进行投诉反馈,一经查实,立即删除!