Flink实时数仓同步:快照表实战详解

一、背景

在大数据领域,初始阶段业务数据通常被存储于关系型数据库,如MySQL。然而,为满足日常分析和报表等需求,大数据平台采用多种同步方式,以适应这些业务数据的不同存储需求。这些同步存储方式包括离线仓库和实时仓库等,选择取决于业务需求和数据特性。

一项常见需求是,业务使用人员需要大数据分析平台中查看历史某一天的表数据,示例如下:

  1. [Mysql] 业务数据 - 用户表全量数据:
idnamephonegendercreate_timeupdate_time
1jack1112023-06-01 13:00:002023-06-01 13:00:00
2jason2222023-06-01 13:00:002023-06-01 13:00:00
3tom3332023-06-01 13:00:002023-06-01 13:00:00
  1. [Mysql] 2023-06-02 业务数据新增了一名用户,且更改了tom的手机号,此时表数据如下:
idnamephonegendercreate_timeupdate_time
1jack1112023-06-01 13:00:002023-06-01 13:00:00
2jason2222023-06-01 13:00:002023-06-01 13:00:00
3tom4442023-06-01 13:00:002023-06-02 09:00:00
4tony5552023-06-02 10:00:002023-06-02 10:00:00

加粗为更新/新增数据

  1. [大数据平台] 2023-06-03 日业务人员在大数据平台中查看2023-06-02日用户表的历史数据,期望数据如下:
idnamephonegendercreate_timeupdate_time
1jack1112023-06-01 13:00:002023-06-01 13:00:00
2jason2222023-06-01 13:00:002023-06-01 13:00:00
3tom3332023-06-01 13:00:002023-06-01 13:00:00

看到这里,有些同学可能会疑惑为何不采用离线数仓中的快照表,而要选择使用 Flink 实时同步的方式。确实,从需求层面看,离线数仓的快照表似乎是一种合理的选择。然而,我们需要注意离线数仓通常采用凌晨 T+1 执行 SQL 的方式将业务数据筛选后同步至下游,这种操作适用于对业务数据精确度要求不高的场景。

对于对数据精确度要求较高的需求,采用 T+1 的同步方式可能会导致数据不一致的问题。详细的问题分析和解决方案可以参考我另一篇文章:深入数仓离线数据同步:问题分析与优化措施。

那么对于对数据精确度要求较高的场景,我们可以选择实时同步的方式来实现。这是因为实时同步通过读取 binlog 日志,能够获取业务数据的完整变更历史。与离线数仓中的 T+1 执行 SQL 不同,实时同步能够更及时地捕获和应用数据变更,确保数据的高一致性和精确度。

二、技术选型

在实时同步领域,要实现背景中的需求通常有两种常见的解决方式:

  1. 实时同步 + 拉链表:
    • 拉链表完整记录了整个 binlog 的数据流向,并通过 start_dateend_date 字段进行天粒度筛选。
    • 可以采用此方式,实现细节可以参考笔者另一篇文章:Flink实时数仓同步:拉链表实战详解。
  2. 实时同步 + 快照表:
    • 本文主要内容。
    • 快照表适用于对数据的历史状态感兴趣,通过实时同步捕获变更事件,并将精确数据写入快照表。

本文主要介绍第二种实现方式:实时同步 + 快照表。

三、技术架构

鉴于业务数据通常存储在关系型数据库中,这里选择采用Flink-CDC持续读取binlog日志进行实时同步。为了保证实时数据能够高效写入下游并支持用户OLAP查询分析,这里选择了企业中常见的MMP库Doris作为实时数仓的存储层。整体架构如下图所示:

在这里插入图片描述

基于上图的设计,引入了一张额外的流水表到 Doris 中。这个设计的目的是为了实现业务的解耦,建立一张专门存储业务数据表的历史变更记录的流水表。这种结构不仅有助于满足当前需求,而且在后续可能出现的其他需求中也更加灵活可扩展。

在实际实现中,可以通过一个 Flink 程序来构建这两张表:流水表和快照表。这种设计模式使得系统更为模块化,同时也方便了后期其他需求的使用。

因此建议读者先阅读笔者另一篇文章:Flink实时数仓同步:流水表实战详解;再回到本文。这样能够更好地理解整个系统设计的背景和实际应用。

四、数据流转过程

Flink实时同步程序负责处理捕获到的MySQL数据变更事件。在处理流程中,首先将全量数据存储到快照表,然后针对新增(INSERT)、修改(UPDATE)、删除(DELETE)等操作,将其同步至流水表。当符合以下任意一个条件便会触发合并任务:

  1. 当binlog数据中的日期为第二天。
  2. 凌晨过了5分钟 [自定义阈值]。

一旦触发合并任务,程序将执行JOIN操作,将流水表前一天数据与快照表中前两天的数据进行整合,最终得到前一天的全量数据,并将其写入至快照表的前一天分区中。这种设计模式既保证了数据的完整性和准确性,又有效地将全量数据存储于快照表中,数据流转过程如下图所示:
在这里插入图片描述

五、实时同步+快照表实现

5.1、快照表设计

  1. 快照表用于存储某个特定时间点的所有数据,通常以天为粒度,相当于对每天的业务数据进行一次全量快照,将当天的全部数据记录下来。举例来说,12号分区中的数据包含了从历史开始一直到11号的全部数据,而13号分区中的数据则包含了从历史一直到12号的全部数据,其余分区以此类推。
  2. 此处只介绍快照表的设计,关于流水表的建表语句请参考笔者另一篇文章:Flink实时数仓同步:流水表实战详解,此快照表采用了Unique数据模型,建表语句如下:
CREATE TABLE `example_user_snapshot`
(`id` largeint(40) NOT NULL COMMENT '用户id',`dt` date NULL COMMENT '流水日期',`name` varchar(50) NOT NULL COMMENT '用户昵称',`phone` largeint(40) NULL COMMENT '手机号',`gender` varchar(5) NULL COMMENT '用户性别',`create_time` datetime NULL COMMENT '用户注册时间',`update_time` datetime NULL COMMENT '用户更新时间'
) ENGINE=OLAP
UNIQUE KEY(`id`, `dt`)
COMMENT '用户流水表'
PARTITION BY RANGE(dt)()
DISTRIBUTED BY HASH(id) BUCKETS 8
PROPERTIES
("dynamic_partition.enable" = "true","dynamic_partition.time_unit" = "DAY","dynamic_partition.start" = "-90","dynamic_partition.end" = "3","dynamic_partition.prefix" = "p","dynamic_partition.buckets" = "8"
);

该表利用了Doris的动态分区功能,将分区粒度设置为天级,并采取了预先建立3天分区的策略,同时设定了90天的过期时间;更多信息可参考Doris动态分区介绍

5.2、实时同步逻辑

5.2.1、前提介绍

  1. 首先,由于实时流水表同步使用Flink-cdc读取关系型数据库,flink-cdc提供了四种模式: “initial”,“earliest-offset”,“latest-offset”,“specific-offset” 和 “timestamp”。本文使用的Flink-connector-mysq是2.3版本,这里简单介绍一下这四种模式:

    • initial (默认):在第一次启动时对受监视的数据库表执行初始快照,并继续读取最新的 binlog。

    • earliest-offset:跳过快照阶段,从可读取的最早 binlog 位点开始读取

    • latest-offset:首次启动时,从不对受监视的数据库表执行快照, 连接器仅从 binlog 的结尾处开始读取,这意味着连接器只能读取在连接器启动之后的数据更改。

    • specific-offset:跳过快照阶段,从指定的 binlog 位点开始读取。位点可通过 binlog 文件名和位置指定,或者在 GTID 在集群上启用时通过 GTID 集合指定。

    • timestamp:跳过快照阶段,从指定的时间戳开始读取 binlog 事件。

  2. 这里采用initial模式作为实时同步方式,先全量后增量,此外由于实时流水表同步需要对 binlog 数据进行解析及判断更新操作类型,因此,Flink CDC SQL 方式的表建立不再满足我们的要求。为了更好地实现这一功能,我们需要采用 API 方式来构建解决方案,代码如下:

import org.apache.flink.api.common.eventtime.WatermarkStrategy;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import com.ververica.cdc.debezium.JsonDebeziumDeserializationSchema;
import com.ververica.cdc.connectors.mysql.source.MySqlSource;public class MySqlSourceExample {public static void main(String[] args) throws Exception {MySqlSource<String> mySqlSource = MySqlSource.<String>builder().hostname("yourHostname").port(yourPort).databaseList("yourDatabaseName") // 设置捕获的数据库, 如果需要同步整个数据库,请将 tableList 设置为 ".*"..tableList("yourDatabaseName.yourTableName") // 设置捕获的表.username("yourUsername").password("yourPassword").startupOptions(StartupOptions.timestamp(1685548800000L)) // 从2023-06-01零点处读取binlog.deserializer(new JsonDebeziumDeserializationSchema()) // 将 SourceRecord 转换为 JSON 字符串.build();StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();// 设置 3s 的 checkpoint 间隔env.enableCheckpointing(3000);env.fromSource(mySqlSource, WatermarkStrategy.noWatermarks(), "MySQL Source")// 设置 source 节点的并行度为 4.setParallelism(4).print().setParallelism(1); // 设置 sink 节点并行度为 1 env.execute("Print MySQL Snapshot + Binlog");}
}

代码摘自mysql-cdc-connector官网示例

5.2.2、全量同步阶段

  1. 接下来我们将从全量同步开始逐步演示同步过程,这里我们以2023-06-0日的[Mysql]业务数据为例,此时表数据如下:
idnamephonegendercreate_timeupdate_time
1jack1112023-06-01 13:00:002023-06-01 13:00:00
2jason2222023-06-01 13:00:002023-06-01 13:00:00
3tom3332023-06-01 13:00:002023-06-01 13:00:00
  1. 此时Flink应用启动获取到的数据如下:仅展示一条
{"before": null,"after": {		 # 实际数据"id": 1,"name": "jack","phone": "111","gender": "男","create_time": "2023-06-01T05:00:00Z",  # 该日期是UTC时间,只需增加8小时即可转化为北京时间"update_time": "2023-06-01T05:00:00Z"	# 该日期是UTC时间,只需增加8小时即可转化为北京时间},"source": {		 # 元数据"version": "1.6.4.Final","connector": "mysql","name": "mysql_binlog_source","ts_ms": 0,"snapshot": "false","db": "yushu_dds","sequence": null,"table": "user","server_id": 0,"gtid": null,"file": "","pos": 0,"row": 0,"thread": null,"query": null},"op": "r",  	 # 记录每条数据的操作类型[重要]"ts_ms": 1705471382867,"transaction": null
}
  1. 在我们使用 Flink CDC MySQL 同步数据时,默认采用 initial 模式,这意味着首先进行全量同步,然后再进行增量同步。因此,在区分全量和增量同步时,关键在于观察获取到的数据中的 op 字段。op 字段是用来记录每条数据的操作类型的标志。具体的操作类型如下:

    • op=d 代表删除操作

    • op=u 代表更新操作

    • op=c 代表新增操作

    • op=r 代表全量读取,而不是来自 binlog 的增量读取

  2. 在 Flink 程序中,只需要通过 op=r 即可筛选出全量数据。在全量数据同步阶段只需将op=r的业务数据直接同步至快照表,流水表在全量阶段无需同步,导入语句如下:

INSERT INTO example_user_snapshot (id, dt, name, phone, gender, create_time, update_time)
VALUES(1, '2023-06-01', 'jack', 111, '男', '2023-06-01 13:00:00', '2023-06-01 13:00:00'),(2, '2023-06-01', 'jason', 222, '男', '2023-06-01 13:00:00', '2023-06-01 13:00:00'),(3, '2023-06-01', 'tom', 333, '男', '2023-06-01 13:00:00', '2023-06-01 13:00:00');
  1. 此时doris快照表数据如下所示:
iddtnamephonegendercreate_timeupdate_time
12023-06-01jack1112023-06-01 13:00:002023-06-01 13:00:00
22023-06-01jason2222023-06-01 13:00:002023-06-01 13:00:00
32023-06-01tom3332023-06-01 13:00:002023-06-01 13:00:00
  1. 此时doris流水表数据如下所示:全量阶段流水表无需同步
idupdate_timedtcreate_timenamephonegenderopbeforebinlog
NULLNULLNULLNULLNULLNULLNULLNULLNULLNULL

5.2.3、增量同步阶段

  1. 这里我们以2023-06-02日的[Mysql]业务数据为例,新增了一名tony用户,且更改了tom的手机号,此时表数据如下:
idnamephonegendercreate_timeupdate_time
1jack1112023-06-01 13:00:002023-06-01 13:00:00
2jason2222023-06-01 13:00:002023-06-01 13:00:00
3tom4442023-06-01 13:00:002023-06-02 09:00:00
4tony5552023-06-02 10:00:002023-06-02 10:00:00
  1. 此时Flink应用获取到的数据如下:
# 新增tony变更数据如下
{"before": null,"after": {"id": 4,"name": "tony","phone": "666","gender": "男","create_time": "2023-06-02T02:00:00Z","update_time": "2023-06-02T02:00:00Z"},"source": {# 元数据信息忽略},"op": "c", # 操作类型"ts_ms": 1706768344113,"transaction": null
}
# tom手机号333->444变更数据如下
{"before": {"id": 3,"name": "tom","phone": "333","gender": "男","create_time": "2023-06-01T05:00:00Z","update_time": "2023-06-01T05:00:00Z"},"after": {"id": 3,"name": "tom","phone": "444","gender": "男","create_time": "2023-06-01T05:00:00Z","update_time": "2023-06-01T23:00:00Z"},"source": {# 元数据信息忽略},"op": "u", # 操作类型"ts_ms": 1706768454904,"transaction": null
}
  1. 当 Flink 同步程序接收到 op=c/u/d 表示增量更新数据时,提取其中的 opbeforeafter 数据。接着将这些信息拼装成 Doris 的 INSERT 语句后插入到流水表中,此时流水表数据如下所示:
idupdate_timedtcreate_timenamephonegenderopbeforebinlog
42023-06-02 10:00:002023-06-022023-06-02 10:00:00tony555cNULL{“before”:null,“after”:{“id”:4,“name”:“tony”,“phone”:“666”,“gender”:“男”,“create_time”:“2023-06-02T02:00:00Z”,“update_time”:“2023-06-02T02:00:00Z”},“source”:{“version”:“1.6.4.Final”,“connector”:“mysql”,“name”:“mysql_binlog_source”,“ts_ms”:1706768344000,“snapshot”:“false”,“db”:“yushu_dds”,“sequence”:null,“table”:“user”,“server_id”:2307031958,“gtid”:“71221bfd-56e8-11ee-8275-fa163e4ecceb:33719321”,“file”:“3509-binlog.000191”,“pos”:643757739,“row”:0,“thread”:null,“query”:null},“op”:“c”,“ts_ms”:1706768344113,“transaction”:null}
32023-06-02 08:00:002023-06-022023-06-02 13:00:00tom444u{“id”:3,“name”:“tom”,“phone”:“333”,“gender”:“男”,“create_time”:“2023-06-01T05:00:00Z”,“update_time”:“2023-06-01T05:00:00Z”}{“before”:{“id”:3,“name”:“tom”,“phone”:“333”,“gender”:“男”,“create_time”:“2023-06-01T05:00:00Z”,“update_time”:“2023-06-01T05:00:00Z”},“after”:{“id”:3,“name”:“tom”,“phone”:“444”,“gender”:“男”,“create_time”:“2023-06-01T05:00:00Z”,“update_time”:“2023-06-01T23:00:00Z”},“source”:{“version”:“1.6.4.Final”,“connector”:“mysql”,“name”:“mysql_binlog_source”,“ts_ms”:1706768454000,“snapshot”:“false”,“db”:“yushu_dds”,“sequence”:null,“table”:“user”,“server_id”:2307031958,“gtid”:“71221bfd-56e8-11ee-8275-fa163e4ecceb:33719761”,“file”:“3509-binlog.000191”,“pos”:692873739,“row”:0,“thread”:null,“query”:null},“op”:“u”,“ts_ms”:1706768454904,“transaction”:null}
  1. 因增量数据无需同步至快照表,故此时快照表与之前06-01号一样保持不变,快照表数据如下:
iddtnamephonegendercreate_timeupdate_time
12023-06-01jack1112023-06-01 13:00:002023-06-01 13:00:00
22023-06-01jason2222023-06-01 13:00:002023-06-01 13:00:00
32023-06-01tom3332023-06-01 13:00:002023-06-01 13:00:00

5.2.4、合并阶段

在合并阶段,我们将流水表前一天的数据与快照表中前两天的数据进行整合,最终得到前一天的全量数据,并将其写入至快照表的前一天分区。

合并任务会在满足以下任意一个条件时触发:

  1. 当binlog数据中的日期为第二天。
  2. 当凌晨过了5分钟(这是一个自定义的时间阈值)。

第二个条件的存在是因为业务数据很可能在凌晨00:00 ~ 00:05 分之间没有增量数据。因此,即使在没有业务数据同步的情况下,我们仍然可以通过第二个条件触发合并阶段,确保数据的完整性和准确性。


  1. 这里我们假设2023-06-03 00:05:00 触发合并阶段为例,此时业务数据如下所示:
idnamephonegendercreate_timeupdate_time
1jack1112023-06-01 13:00:002023-06-01 13:00:00
2jason2222023-06-01 13:00:002023-06-01 13:00:00
3tom4442023-06-01 13:00:002023-06-02 09:00:00
4tony5552023-06-02 10:00:002023-06-02 10:00:00
  1. flink程序中无新增数据,但由于满足第二个触发条件,在flink程序中将会触发合并任务[可用单独线程实现],此时执行的doris合并语句如下:
INSERT INTO example_user_snapshot (id, dt, name, phone, gender, create_time, update_time)
SELECTid,'2023-06-02' as dt, -- 通过固定dt字段值从而写入快照表p20230602分区中name,phone,gender,create_time,update_time
FROM (SELECTsnap.id,snap.name,snap.phone,snap.gender,snap.create_time,snap.update_timeFROM example_user_snapshot PARTITION p20230601 snapLEFT JOIN example_user_stream PARTITION p20230602 stream ON snap.id = stream.idWHERE stream.id IS NULLUNIONSELECTid,name,phone,gender,create_time,update_timeFROM (SELECTid,name,phone,gender,create_time,update_time,-- 使用窗口函数的目的是处理流水表中可能存在多条相同id的记录,例如tom在06-02日更改多次手机号则会有多条相同id的数据,故此窗口函数用于确保选择每个id对应的update_time最大的记录;如果流水表设计的unique key = (id) 则不会出现重复情况无需此处的窗口函数。ROW_NUMBER() OVER (PARTITION BY id ORDER BY update_time DESC) AS row_num FROM example_user_stream PARTITION p20230602) rankedWHERE row_num = 1) AS temp;

该 SQL 查询是先获取两表联接中未更新的数据,与已更新的数据合并,最后写入到快照表中,确保了 2023-06-02 分区的数据是完整的全量数据。

若想详细剖析此sql的运算逻辑可参考笔者另一篇文章:数仓日常维护:剖析每日增量同步的内部机制

  1. 此时快照表的数据如下:
iddtnamephonegendercreate_timeupdate_time
12024-02-02jack1112023-06-01 13:00:002023-06-01 13:00:00
22024-02-02jason2222023-06-01 13:00:002023-06-01 13:00:00
32024-02-02tom3332023-06-01 13:00:002023-06-01 13:00:00
12024-02-03jack1112023-06-01 13:00:002023-06-01 13:00:00
22024-02-03jason2222023-06-01 13:00:002023-06-01 13:00:00
32024-02-03tom5552023-06-02 13:00:002023-06-02 09:00:00
42024-02-03tony5552023-06-02 10:00:002023-06-02 10:00:00
  1. 用户可以通过如下语句查询2023-06-02全量数据:
SELECT * FROM example_user_snapshot PARTITION p20230602;
12024-02-03jack1112023-06-01 13:00:002023-06-01 13:00:00
22024-02-03jason2222023-06-01 13:00:002023-06-01 13:00:00
32024-02-03tom5552023-06-02 13:00:002023-06-02 09:00:00
42024-02-03tony5552023-06-02 10:00:002023-06-02 10:00:00

合并阶段的主要压力是Doris,Flink程序只是传递sql执行后获取结果即可;至此实时快照表同步逻辑结束。

5.3、数据一致性设计

在上述快照表同步过程中,如果Flink程序挂掉或者重启,是否会影响数据一致性?由于Flink程序是通过定时执行checkpoint且binlog可重读溯源,因此在数据获取阶段不会出现数据一致性问题。

需要考虑的地方在于合并阶段,如果触发了合并任务,而此时Flink程序还在不断消费业务变更数据,这里是异步还是阻塞?笔者建议使用异步:即Flink程序仍实时同步业务变更数据至流水表,而快照表的合并阶段主要是下沉到Doris库中执行。

需要注意的是如果在合并阶段时Flink程序挂掉,重启后该如何处理?笔者建议在Flink程序中采用有状态的计算,即Rich functions 富函数中的ValueState,用于记录当前合并阶段是否成功,如下:

javaCopy codeimport org.apache.flink.api.common.functions.RichMapFunction;
import org.apache.flink.api.common.state.StateTtlConfig;
import org.apache.flink.api.common.state.ValueState;
import org.apache.flink.api.common.state.ValueStateDescriptor;
import org.apache.flink.api.common.time.Time;
import org.apache.flink.configuration.Configuration;public class TestMapFunction extends RichMapFunction<Tuple2<String, Integer>, Tuple2<String, Integer>>  {// state 用于存放合并后的分区,例如: state=p20230601private transient ValueState<String> state;@Overridepublic Tuple2<String, Integer> map(Tuple2<String, Integer> in) throws Exception {// 业务逻辑}public void open(Configuration parameters) throws Exception {// 初始化 state}
}

通过这种方式,即便Flink在同步过程中宕掉,只要根据checkpoint重启后便可检测到上一个分区任务失败,即state != 20230602,从而再次触发合并阶段!

关于flink有状态的计算可参考Flink官网介绍

五、总结

此设计方式主要面向对数据准确性要求较高的场景。如果对数据准确性要求不高,完全可以考虑采用离线数仓 T+1 的方式构建快照表。

另外,此背景需求也可以通过拉链表实现。值得注意的是,拉链表能够支持更多的需求,例如实时数据查看。相比之下,本文介绍的快照表主要用于查看历史数据,不支持实时数据查看。

六、相关资料

  • Doris 数据模型
  • Flink状态计算
  • MySQL CDC Connector
  • 数仓日常维护:剖析每日增量同步的内部机制
  • Flink实时数仓同步:流水表实战详解
  • Flink实时数仓同步:拉链表实战详解
  • 深入数仓离线数据同步:问题分析与优化措施

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/449863.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

python-游戏篇-初级-飞机大战

文章目录 开发环境要求运行方法代码效果 开发环境要求 本系统的软件开发及运行环境具体如下。 操作系统&#xff1a;Windows 7、Windows 10。Python版本&#xff1a;Python 3.7.1。开发工具&#xff1a;PyCharm 2018或VScode。Python内置模块&#xff1a;sys、random、codecs…

C++集群聊天服务器 网络模块+业务模块+CMake构建项目 笔记 (上)

跟着施磊老师做C项目&#xff0c;施磊老师_腾讯课堂 (qq.com) 一、网络模块ChatServer chatserver.hpp #ifndef CHATSERVER_H #define CHATSERVER_H#include <muduo/net/TcpServer.h> #include <muduo/net/EventLoop.h> using namespace muduo; using namespace …

kubeadm安装K8S_v1.28.x容器使用docker

一&#xff0e;环境部署 1.1 基础环境配置&#xff08;只有1台服务器&#xff0c;作为masrer&#xff0c;也作为node使用&#xff09; [rootecs-cf5e ~]# cat /etc/redhat-release CentOS Linux release 7.9.2009 (Core) [rootecs-cf5e ~]# uname -a Linux ecs-cf5e 3.10.0-11…

算法设计与分析实验:动态规划与回溯

目录​​​​​​​ 一、编辑距离 1.1 具体思路 1.2 思路展示 1.3 代码实现 1.4 复杂度分析 1.5 运行结果 二、买卖股票的最佳时机 2.1 具体思路 2.2 思路展示 2.3 代码实现 2.4 复杂度分析 2.5 运行结果 三、单词拆分 3.1 具体思路 3.2 思路展示 3.3 代码实现…

Leetcode206:反转链表

一、题目 给你单链表的头节点 head &#xff0c;请你反转链表&#xff0c;并返回反转后的链表 示例&#xff1a; 输入&#xff1a;head [1,2,3,4,5] 输出&#xff1a;[5,4,3,2,1]输入&#xff1a;head [1,2] 输出&#xff1a;[2,1]输入&#xff1a;head [] 输出&#xff1…

Linux mount

挂载移动硬盘 1、通过 命令 fdisk -l 查看移动硬盘 2、创建 挂载点及文件 mkdir zen 3、mount -t ntfs /dev/sdb1 zen 报错&#xff1a;mount: unknown filesystem type ‘ntfs’ 需要安装ntfs-3g 如下才用编译安装方法&#xff1a; wget https://tuxera.com/opensource/ntf…

【Spark实践6】特征转换FeatureTransformers实践Scala版--补充算子

本节介绍了用于处理特征的算法&#xff0c;大致可以分为以下几组&#xff1a; 提取&#xff08;Extraction&#xff09;&#xff1a;从“原始”数据中提取特征。转换&#xff08;Transformation&#xff09;&#xff1a;缩放、转换或修改特征。选择&#xff08;Selection&…

2024年第三届能源与环境工程国际会议(CFEEE 2024) | Ei&Scopus双检索

会议简介 Brief Introduction 2024年第三届能源与环境工程国际会议(CFEEE 2024) 会议时间&#xff1a;2024年12月12日-14日 召开地点&#xff1a;澳大利亚凯恩斯 大会官网&#xff1a;CFEEE 2024-2024 International Conference on Frontiers of Energy and Environment Engine…

【乳腺肿瘤诊断分类及预测】基于Elman神经网络

课题名称&#xff1a;基于Elman神经网络的乳腺肿瘤诊断分类及预测 版本日期&#xff1a;2023-05-15 运行方式: 直接运行Elman0501.m 文件即可 代码获取方式&#xff1a;私信博主或QQ&#xff1a;491052175 模型描述&#xff1a; 威斯康辛大学医学院经过多年的收集和整理&a…

20240202在WIN10下使用whisper.cpp

20240202在WIN10下使用whisper.cpp 2024/2/2 14:15 【结论&#xff1a;在Windows10下&#xff0c;确认large模式识别7分钟中文视频&#xff0c;需要83.7284 seconds&#xff0c;需要大概1.5分钟&#xff01;效率太差&#xff01;】 83.7284/4200.1993533333333333333333333333…

云计算基础(云计算概述)

目录 一、云计算概述 1.1 云计算的概念 1.1.1 云计算解决的问题 1.1.2 云计算的概念 1.1.3 云计算的组成 1.2 云计算主要特征 1.2.1 按需自助服务 1.2.2 泛在接入 1.2.3 资源池化 1.2.4 快速伸缩性 1.2.5 服务可度量 1.3 云计算服务模式 1.3.1 软件即服务(Softwar…

海外IP代理:解锁网络边界的实战利器

文章目录 引言&#xff1a;正文&#xff1a;一、Roxlabs全球IP代理服务概览特点&#xff1a;覆盖范围&#xff1a;住宅IP真实性&#xff1a;性价比&#xff1a;在网络数据采集中的重要性&#xff1a; 二、实战应用案例一&#xff1a;跨境电商竞品分析步骤介绍&#xff1a;代码示…