Flink学习笔记(四):Flink 四大基石之 Window 和 Time

文章目录

  • 1、 概述
  • 2、 Flink 的 Window 和 Time
    • 2.1、Window API
      • 2.1.1、WindowAssigner
      • 2.1.2、Trigger
      • 2.1.3、Evictor
    • 2.2、窗口类型
      • 2.2.1、Tumbling Windows
      • 2.2.2、Sliding Windows
      • 2.2.3、Session Windows
      • 2.2.4、Global Windows
    • 2.3、Time 时间语义
    • 2.4、乱序和延迟数据处理
    • 2.5、综合案例

1、 概述

Flink 基石

  • 窗口 Window

    • 流数据计算中一般对数据尽心操作之前都会先进行开窗,即基于一个什么样的窗口上做这个计算
    • Flink 提供了开箱即用的各种窗口,比如滑动窗口、滚动窗口、会话窗口以及非常灵活的自定义窗口
  • 时间 Time

    • Flink 中窗口计算,基本都是基于时间窗口设置
    • Flink 实现了 Watermark 的机制,能够支持基于事件时间的处理,能够容忍迟到、乱序的数据
  • 状态 State

    • Flink计算引擎,自身就是基于状态计算框架,默认情况下程序自己管理状态
    • 提供一致性的语义,使得用户在编程时能够更轻松、更容易地去管理状态
    • 提供一套非常简单明了的 State API,包括ValueState、ListState、MapState,BroadcastState
  • 检查点 Checkpoint

    • Flink Checkpoint 检查点:保存状态数据
    • 基于 Chandy-Lamport 算法实现了一个分布式的一致性的快照,从而提供了一致性的语义
    • 进行 Checkpoint 后,可以设置自动进行故障恢复
    • 保存点 Savepoint,人工进行 Checkpoint 操作,进行程序恢复执行

2、 Flink 的 Window 和 Time

2.1、Window API

在 Flink 流计算中,提供 Window 窗口 API 分为 2 种:

  • 针对 KeyedStream 窗口 API
    Window 先对数据流 DataStream 进行分组 keyBy ,再设置窗口 Window,最后进行聚合 apply 操作。
    • 第一步、数据流 DataStream 调用 keyBy 函数分组,获取 KeyedStream
    • 第二步、KeyedStream.window 设置窗口
    • 第三步、聚合操作,对窗口中数据进行聚合统计,函数:reduce、aggregate、apply() 等。
stream.keyBy(...)          <-  keyed versus non-keyed windows.window(...)         <-  required: "assigner"[.trigger(...)]       <-  optional: "trigger" (else default trigger)[.evictor(...)]       <-  optional: "evictor" (else no evictor)[.allowedLateness()]  <-  optional, else zero.reduce/fold/apply() <-  required: "function"
  • 针对 KeyedStream 窗口 API
    • 直接调用窗口函数:windowAll,然后再对窗口所有数据进行处理,未进行分组;
    • 聚合操作,对窗口中数据进行聚合统计,函数:reduce、aggregate、apply() 等。
stream.windowAll(...)      <-  required: "assigner"[.trigger(...)]       <-  optional: "trigger" (else default trigger)[.evictor(...)]       <-  optional: "evictor" (else no evictor)[.allowedLateness()]  <-  optional, else zero.reduce/fold/apply() <-  required: "function"

方括号 [ ] 内的命令是可选的,这表明 Flink 允许根据需求自定义 window 逻辑。使用 keyBy 的流,应该使用 window 方法,未使用 keyBy 的流,应该调用 windowAll 方法

2.1.1、WindowAssigner

window/windowAll 方法接收的输入是一个 WindowAssigner, WindowAssigner 负责将每条输入的数据分发到正确的 window 中。如果需要自己定制数据分发策略,则可以实现一个 class,继承自 WindowAssigner。

2.1.2、Trigger

trigger 用来判断一个窗口是否需要被触发,每个 WindowAssigner 都自带一个默认的 trigger,如果默认的 trigger 不能满足你的需求,则可以自定义一个类,继承自Trigger 即可。

  • onElement()
  • onEventTime()
  • onProcessingTime()

此抽象类的这三个方法会返回一个 TriggerResult, TriggerResult 有如下几种可能的选择:

  • CONTINUE 不做任何事情
  • FIRE 触发 window
  • PURGE 清空整个 window 的元素并销毁窗口
  • FIRE_AND_PURGE 触发窗口,然后销毁窗口

2.1.3、Evictor

evictor 主要用于做一些数据的自定义操作,可以在执行用户代码之前,也可以在执行用户代码之后。本接口提供了两个重要的方法,即 evicBeforeevicAfter两个方法。

Flink 提供了如下三种通用的 evictor:

  • CountEvictor 保留指定数量的元素
  • TimeEvictor 设定一个阈值 interval,删除所有不再 max_ts - interval 范围内的元素,其中 max_ts 是窗口内时间戳的最大值
  • DeltaEvictor 通过执行用户给定的 DeltaFunction 以及预设的 theshold,判断是否删
    除一个元素。

2.2、窗口类型

Flink Window 窗口的结构中,有两个必须的两个操作:

  • 第一、窗口分配器(WindowAssigner):将数据流中的元素分配到对应的窗口。
  • 第二、窗口函数(Window Function):当满足窗口触发条件后,对窗口内的数据使用窗口处理函数(Window Function)进行处理,常用的有 reduce、aggregate、process。

在 Flink 窗口计算中,无论时间窗口还是计数窗口,都可以分为 2 种类型:滚动 Tumbling滑动 Sliding 窗口

  • 滚动窗口(Tumbling Window)
    条件:窗口大小 size = 滑动间隔 slide

  • 滚动窗口(Tumbling Window)
    条件:窗口大小 != 滑动间隔,通常条件【窗口大小 size > 滑动间隔 slide

Window 的生命周期是什么?
简单的说,当有第一个属于该 window 元素到达时就创建了一个 window,当时间或事件触发该 windowremoved 的时候则结束。每个 window 都有一个 Trigger 和 一个 Function,function用于计算,tigger 用于触发 window 条件。同时也可以使用 Evictor 在 Trigger 触发前后对 window 的元素进行处理。

2.2.1、Tumbling Windows

滚动窗口分配器(Tumbling windows assigner)将每个元素分配给指定窗口大小的窗口。滚动窗口具有固定大小,不会重叠。例如,如果指定大小为 5 分钟的滚动窗口,则将评估当前窗口,并且每 5 分钟启动一个新窗口,如下图所示:
滑动窗口
示例代码:

// 3-1. 对数据进行转换处理: 过滤脏数据,解析封装到二元组中SingleOutputStreamOperator<Tuple2<String, Integer>> mapStream = inputStream.filter(line -> line.trim().split(",").length == 2).map(new MapFunction<String, Tuple2<String, Integer>>() {@Overridepublic Tuple2<String, Integer> map(String line) throws Exception {System.out.println("item: " + line);String[] array = line.trim().split(",");Tuple2<String, Integer> tuple = Tuple2.of(array[0], Integer.parseInt(array[1]));// 返回return tuple;}});// todo: 3-2. 窗口计算,每隔5秒计算最近5秒各个卡口流量SingleOutputStreamOperator<String> windowStream = mapStream// a. 设置分组key,按照卡口分组.keyBy(tuple -> tuple.f0)// b. 设置窗口,并且为滚动窗口:size=slide.window(TumblingProcessingTimeWindows.of(Time.seconds(5)))// c. 窗口计算,窗口函数.apply(new WindowFunction<Tuple2<String, Integer>, String, String, TimeWindow>() {// 定义变量,对日前时间数据进行转换private FastDateFormat format = FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss") ;@Overridepublic void apply(String key, TimeWindow window,Iterable<Tuple2<String, Integer>> input,Collector<String> out) throws Exception {// 获取窗口时间信息:开始时间和结束时间String winStart = this.format.format(window.getStart());String winEnd = this.format.format(window.getEnd()) ;// 对窗口中数据进行统计:求和int sum = 0 ;for (Tuple2<String, Integer> tuple : input) {sum += tuple.f1 ;}// 输出结果数据String output = "window: [" + winStart + " ~ " + winEnd + "], " + key + " = " + sum ;out.collect(output);}});

2.2.2、Sliding Windows

滑动窗口分配器(sliding windows assigner)将元素分配给固定长度的窗口。与滚动窗口分配器类似,窗口的大小由窗口大小参数配置。窗口滑动参数控制滑动窗口的启动频率。因此,如果 sliding小于size,则滑动窗口可能会重叠。在这种情况下,元素被分配给多个窗口。例如,可以有大小为 10 分钟的窗口,该窗口滑动 5 分钟。这样,您每 5 分钟就会得到一个窗口,其中包含过去 10 分钟内到达的事件,如下图所示:
滑动窗口
示例代码:

// 3-1. 对数据进行转换处理: 过滤脏数据,解析封装到二元组中SingleOutputStreamOperator<Tuple2<String, Integer>> mapStream = inputStream.filter(line -> line.trim().split(",").length == 2).map(new MapFunction<String, Tuple2<String, Integer>>() {@Overridepublic Tuple2<String, Integer> map(String line) throws Exception {System.out.println("item: " + line);String[] array = line.trim().split(",");return Tuple2.of(array[0], Integer.parseInt(array[1]));}});// todo: 3-2. 窗口计算,每隔5秒计算最近5秒各个卡口流量SingleOutputStreamOperator<String> windowStream = mapStream// a. 设置分组key,按照卡口分组.keyBy(tuple -> tuple.f0)// b. 设置窗口,并且为滚动窗口:size != slide.window(SlidingProcessingTimeWindows.of(Time.seconds(10), Time.seconds(5)))// c. 窗口计算,窗口函数.apply(new WindowFunction<Tuple2<String, Integer>, String, String, TimeWindow>() {// 定义变量,对日前时间数据进行转换private FastDateFormat format = FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss") ;@Overridepublic void apply(String key, TimeWindow window,Iterable<Tuple2<String, Integer>> input,Collector<String> out) throws Exception {// 获取窗口时间信息:开始时间和结束时间String winStart = this.format.format(window.getStart());String winEnd = this.format.format(window.getEnd()) ;// 对窗口中数据进行统计:求和int sum = 0 ;for (Tuple2<String, Integer> tuple : input) {sum += tuple.f1 ;}// 输出结果数据String output = "window: [" + winStart + " ~ " + winEnd + "], " + key + " = " + sum ;out.collect(output);}});

2.2.3、Session Windows

会话窗口分配器(session windows assigner)按活动会话对元素进行分组。与滚动窗口和滑动窗口相比,会话窗口不重叠,也没有固定的开始和结束时间。相反,当会话窗口在一段时间内未收到元素时(即,当出现不活动间隙时),会话窗口将关闭。会话窗口分配器可以配置静态会话间隙或会话间隙提取器功能,该函数定义不活动时间的时间。当此时间段到期时,当前会话将关闭,后续元素将分配给新的会话窗口。
Session Windows
示例代码:

// 3-1. 过滤和转换数据类型SingleOutputStreamOperator<Integer> mapStream = inputStream.filter(line -> line.trim().length() > 0).map(new MapFunction<String, Integer>() {@Overridepublic Integer map(String value) throws Exception {System.out.println("item: " + value);return Integer.parseInt(value);}});// 3-2. 直接对DataStream流进行窗口操作SingleOutputStreamOperator<String> windowStream = mapStream// a. 设置窗口:会话窗口,超时时间为5秒.windowAll(ProcessingTimeSessionWindows.withGap(Time.seconds(5)))// b. 设置窗口函数,对窗口中数据进行计算.apply(new AllWindowFunction<Integer, String, TimeWindow>() {// 定义变量,对日前时间数据进行转换private FastDateFormat format = FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss") ;@Overridepublic void apply(TimeWindow window, Iterable<Integer> values, Collector<String> out) throws Exception {// 获取窗口时间信息:开始时间和结束时间String winStart = this.format.format(window.getStart());String winEnd = this.format.format(window.getEnd()) ;// 对窗口中数据进行求和int sum = 0 ;for (Integer value : values) {sum += value ;}// 输出结果数据String output = "window: " + winStart + " ~ " + winEnd + " -> " + sum ;out.collect(output);}});

2.2.4、Global Windows

全局窗口分配器(global windows assigner)将具有相同键的所有元素分配给同一个全局窗口。只有自己自定义触发器的时候该窗口才能使用。否则,将不会执行任何计算,因为全局窗口没有一个自然的终点,我们可以在该端点处理聚合元素。
Global Windows
示例代码:

// 3-1. 过滤和转换数据类型SingleOutputStreamOperator<Integer> mapStream = inputStream.filter(line -> line.trim().length() > 0).map(new MapFunction<String, Integer>() {@Overridepublic Integer map(String value) throws Exception {System.out.println("item: " + value);return Integer.parseInt(value);}});// TODO: 3-2. 直接对DataStream流进行窗口操作SingleOutputStreamOperator<String> windowStream = mapStream// a. 设置窗口,滚动计数窗口.countWindowAll(5)// b. 设置窗口函数,计算窗口中数据.apply(new AllWindowFunction<Integer, String, GlobalWindow>() {@Overridepublic void apply(GlobalWindow window, Iterable<Integer> values, Collector<String> out) throws Exception {// 对窗口中数据进行求和int sum = 0 ;for (Integer value : values) {sum += value ;}// 输出累加求和值String output = "sum = " + sum ;out.collect(output);}});

2.3、Time 时间语义

  • 事件时间 EventTime:事件真真正正发生产生的时间,比如订单数据中订单时间表示订单产生的时间;
  • 摄入时间 IngestionTime:数据被流式程序获取的时间;
  • 处理时间 ProcessingTime:事件真正被处理/计算的时间。

基于事件时间 EventTime 窗口分析,指定事件时间字段,使用 assignTimestampsAndWatermarks 方法,类型必须为 Long 类型。

// 3-1. 过滤脏数据和指定事件时间字段字段SingleOutputStreamOperator<String> timeStream = inputStream.filter(line -> line.trim().split(",").length == 3)// todo: step1、指定事件时间字段,并且数据类型为Long类型.assignTimestampsAndWatermarks(WatermarkStrategy// 暂不考虑数据乱序和延迟.<String>forBoundedOutOfOrderness(Duration.ofSeconds(0))// 指定事件时间字段.withTimestampAssigner(new SerializableTimestampAssigner<String>() {private FastDateFormat format = FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss");@SneakyThrows@Overridepublic long extractTimestamp(String element, long recordTimestamp) {// 2022-04-01 09:00:01,a,1 -> 2022-04-01 09:00:01 -> 1648774801000System.out.println("element -> " + element);// 分割字符串String[] array = element.split(",");// 获取事件时间String eventTime = array[0];// 转换格式Date eventDate = format.parse(eventTime);// z转换Long类型并返回return eventDate.getTime();}}));

默认情况下(不考虑乱序和延迟),当数据事件时间EventTime >= 窗口结束时间,触发窗口数据计算
基于事件时间EventTime窗口分析,如果不考虑数据延迟乱序,当窗口被触发计算以后,延迟乱序到达的数据将不会被计算,而是直接丢弃。

窗口起始时间计算方式:
timestamp - (timestamp - offset + wondowsize)%windowsize
如:00:00:01 窗口大小:5s 乱序时间:0s,则:
1 -(1 - 0 + 5 )% 5 = 0

2.4、乱序和延迟数据处理

  • Watermark 水印机制
    在实际业务数据中,数据乱序到达流处理程序,属于正常现象,原因在于网络延迟导致数据延迟,无法避免的,所以应该可以允许数据乱序达到(在某个时间范围内),依然参与窗口计算。
    Watermark水印机制

  • Allowed Lateness 允许延迟
    默认情况下,当 watermark 超过 end-of-window 之后,再有之前的数据到达时,这些数据会被删除。为了避免有些迟到的数据被删除,因此产生了 allowedLateness 的概念。
    允许延迟

  • 乱序数据:Watermark,窗口数据计算等一下

    • 使用水位线Watermark,给每条数据加上一个时间戳
    • Watermark = 数据事件时间 - 最大允许乱序时间
    • 当数据的Watermark >= 窗口结束时间,并且窗口内有数据,触发窗口数据计算
  • 延迟数据:AllowedLateness,窗口计算状态保存一段时间

    • 设置方法参数:allowedLateness,表示允许延迟数据最多可以迟到多久,还可以进行计算(保存窗口,并且触发窗口计算)
    • 当某个窗口触发计算以后,继续等待多长时间,如果在等待时间范围内,有数据达到时,依然会触发窗口计算。如果到达等待时长以后,没有数据达到,销毁窗口数据信息。

真正迟到的数据默认会被丢弃,可通过侧边流输出到文件:
侧边输出

  • 1、窗口 window 的作用是为了周期性的获取数据
  • 2、watermark 作用是防止数据出现乱序(经常),事件时间内获取不到指定的全部数据,做的一种保险方法;
  • 3、allowLateNess 是将窗口关闭时间再延迟一段时间
  • 4、sideOutPut 是最后兜底操作,所有过期延迟数据,指定窗口已经彻底关闭,就会把数据放到侧输出流

2.5、综合案例

public static void main(String[] args) throws Exception {// 1. 执行环境-envConfiguration configuration = new Configuration();configuration.setString("rest.port", "8081");StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(configuration);env.setParallelism(1) ;// todo: 设置CheckpointsetEnvCheckpoint(env) ;// todo: 设置重启策略env.setRestartStrategy(RestartStrategies.fixedDelayRestart(3, 10000));// 2. 数据源-sourceDataStreamSource<String> inputStream = env.socketTextStream("127.0.0.1", 9999);// 3. 数据转换-transformation/*业务数据:o_101,u_121,11.50,2022-04-05 10:00:023-1. 过滤、解析和封装数据3-2. 设置事假时间字段值和水位线Watermark3-3. 窗口设置及处理数据*/// 3-1. 过滤、解析和封装数据SingleOutputStreamOperator<OrderEvent> orderStream = inputStream.filter(line -> null != line && line.trim().split(",").length == 4).map(new MapFunction<String, OrderEvent>() {@Overridepublic OrderEvent map(String value) throws Exception {// 分割为单次String[] array = value.split(",");// 封装实体类对象OrderEvent orderEvent = new OrderEvent() ;orderEvent.setOrderId(array[0]);orderEvent.setUserId(array[1]);orderEvent.setOrderMoney(Double.parseDouble(array[2]));orderEvent.setOrderTime(array[3]);// 返回实例对象return orderEvent;}});// 3-2. 设置事假时间字段值和水位线WatermarkSingleOutputStreamOperator<OrderEvent> timeStream = orderStream.assignTimestampsAndWatermarks(WatermarkStrategy// 允许最大乱序时间:2秒,等待2秒钟触发窗口计算.<OrderEvent>forBoundedOutOfOrderness(Duration.ofSeconds(2))// 获取订单时间,设置事件事假.withTimestampAssigner(new SerializableTimestampAssigner<OrderEvent>() {private FastDateFormat format = FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss");@SneakyThrows@Overridepublic long extractTimestamp(OrderEvent element, long recordTimestamp) {System.out.println("order -> " + element);// 获取订单时间String orderTime = element.getOrderTime();// 转换为Date日期类型Date orderDate = format.parse(orderTime);// 转换Long并返回return orderDate.getTime();}}));// 3-3. 窗口设置及处理数据OutputTag<OrderEvent> lateOutputTag = new OutputTag<OrderEvent>("late-order"){} ;SingleOutputStreamOperator<OrderReport> windowStream = timeStream// 按照用户分组   event -> event.getUserId().keyBy(OrderEvent::getUserId)// 设置窗口:10s,滚动窗口.window(TumblingEventTimeWindows.of(Time.seconds(10)))// 设置最大允许延迟时间.allowedLateness(Time.seconds(3))// 设置延迟很久数据侧边输出.sideOutputLateData(lateOutputTag)// 设置窗口函数,进行计算.apply(new OrderWindowFunction());// 4. 数据终端-sinkwindowStream.printToErr();// 获取侧边流中延迟数据DataStream<OrderEvent> lateOrderStream = windowStream.getSideOutput(lateOutputTag);lateOrderStream.printToErr("late>");// 5. 触发执行-executeenv.execute("StreamOrderWindowReport");}/*** 流式应用Checkpoint检查点设置*/private static void setEnvCheckpoint(StreamExecutionEnvironment env) {// 1. 启动Checkpointenv.enableCheckpointing(10000) ;// 2.设置StateBackendenv.setStateBackend(new HashMapStateBackend());// 3.设置Checkpoint存储env.getCheckpointConfig().setCheckpointStorage("file:///D:/ckpt/");// 4. 设置相邻Checkpoint至少时间间隔env.getCheckpointConfig().setMinPauseBetweenCheckpoints(500);// 5. 设置Checkpoint最大失败次数env.getCheckpointConfig().setTolerableCheckpointFailureNumber(3);// 6. 设置取消job时Checkpoint是删除还是保留env.getCheckpointConfig().enableExternalizedCheckpoints(CheckpointConfig.ExternalizedCheckpointCleanup.RETAIN_ON_CANCELLATION);// 7.设置Checkpoint超时时间env.getCheckpointConfig().setCheckpointTimeout(10 * 60 * 1000);// 8. 设置Checkpoint最大并发次数env.getCheckpointConfig().setMaxConcurrentCheckpoints(1);// 9. 设置模式env.getCheckpointConfig().setCheckpointingMode(CheckpointingMode.EXACTLY_ONCE);}

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

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

相关文章

【2024秋招】2023-9-14 最右线下后端开发二面

1 OS 1.1 讲讲什么是虚拟内存&#xff0c;怎么实现的 虚拟内存是一种存储器管理能力&#xff0c;它使得一个应用程序似乎有更多的物理内存&#xff08;RAM&#xff09;可用&#xff0c;而实际上&#xff0c;系统使用了一部分硬盘空间来模拟额外的 RAM。通过使用虚拟内存&…

Git(一)Windows下安装及使用Git Bash

目录 一、简介1.1 什么是Git&#xff1f;1.2 Git 的主要特点1.3 什么是 Git Bash&#xff1f; 二、下载三、安装3.1 同意协议3.2 选择安装位置3.3 其他配置&#xff08;【Next】 即可&#xff09;3.4 安装完毕3.5 打开 Git Bash 官网地址&#xff1a; https://www.git-scm.com/…

视频去噪网络BSVD的实现

前些天写了视频去噪网络BSVD论文的理解&#xff0c;详情请点击这里&#xff0c;这两个星期动手实践了一下&#xff0c;本篇就来记录一下这个模型的实现。 这个网络的独特之处在于&#xff0c;它的训练和推理在实现上有所差别。在训练阶段&#xff0c;其使用了TSM&#xff08;T…

基于斑马优化的BP神经网络(分类应用) - 附代码

基于斑马优化的BP神经网络&#xff08;分类应用&#xff09; - 附代码 文章目录 基于斑马优化的BP神经网络&#xff08;分类应用&#xff09; - 附代码1.鸢尾花iris数据介绍2.数据集整理3.斑马优化BP神经网络3.1 BP神经网络参数设置3.2 斑马算法应用 4.测试结果&#xff1a;5.M…

【数据仓库-零】数据仓库知识体系 ing

文章目录 一. 数仓基本概念二. 离线数仓建设方法论三. etl流程四. 数仓规范建设指南四. 数据仓库架构五. 数据可视化 通过熟悉构建数仓整体的过程&#xff0c;可以系统的了解 数仓构建理论&#xff1a;能够站在全局角度看数仓的运行架构&#xff0c;数仓执行流程。了解到构建数…

车道线检测laneatt 学习笔记

目录 图片检测可视化 图片检测可视化 import logging import argparse import os import timeimport cv2 import numpy as np import torchfrom lib.config import Config from lib.runner import Runner from lib.experiment import Experimentdef parse_args():parser = ar…

虚拟机安装centos系统后配置桥接网络

一.桥接网络和nat网络的区别 桥接模式 通过使用物理机网卡 具有单独ip,但是需要手动配置。 在bridged模式下&#xff0c;VMWare虚拟出来的操作系统就像是局域网中的一台独立的主机&#xff0c;它可以访问网内任何一台机器。主机网卡和虚拟网卡的IP地址处于同一个网段&#xff…

App爬虫之强大的Airtest的操作总结

App爬虫之强大的Airtest的操作总结 App爬虫之强大的Airtest的操作总结 # Python使用该框架需要安装的依赖库 pip install airtest pip install poco pip install pocouifrom airtest.core.api import * from airtest.cli.parser import cli_setup from poco.drivers.android.…

Mybatis的SqlRunner执行流程

Mybatis的SqlRunner执行流程 SqlRunner exec new SqlRunner(connection); Map<String, Object> row exec.selectOne("SELECT * FROM PRODUCT WHERE PRODUCTID ?", "FI-SW-01");connection.close();assertEquals("FI-SW-01", row.ge…

【QT开发(10)】QT 进程

文章目录 1.1 运行一个新进程1.2 QProcess 还可以对一些信号进行关联2 进程间通信2.1 使用共享内存实现进程通信2.2 演示 代码仓库参考 1.1 运行一个新进程 使用类 QProcess&#xff0c;允许将一个进程堪称一个顺序IO设备。 在Qt中&#xff0c;QProcess类是用于启动外部进程的…

大模型与知识图谱如何相互助力

目前各行各业在数字化、智能化发展的大势所趋下&#xff0c;信息新技术不断涌现&#xff0c;也在加快深入融合到传统实体行业应用中&#xff0c;比如知识图谱、人工智能、数字孪生等等&#xff0c;特别是基于人工智能的大模型在去年底被chatgpt的带领下涌现出一波又一波的浪潮&…

驱动开发1 概念、内核模块编程、内核消息打印函数printk函数的使用、内核模块传参、内核导出符号

1 驱动相关概念 2 内核模块编程 内核模块编写实例代码注释 #include <linux/init.h> #include <linux/module.h>//入口函数&#xff0c;安装内核模块时执行 static int __init mycdev_init(void) {//static 修饰当前函数只能在本文件使用//int 函数的返回值类型&a…

【Leetcode】【中等】1726.同积元组

力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台备战技术面试&#xff1f;力扣提供海量技术面试资源&#xff0c;帮助你高效提升编程技能&#xff0c;轻松拿下世界 IT 名企 Dream Offer。https://leetcode.cn/problems/tuple-with-same-product/ 给你…

适用于 Mac 电脑的 10 款最佳数据恢复工具集

无论是个人照片还是重要的商业文档&#xff0c;对于那些依赖计算机获取重要文件的人来说&#xff0c;数据丢失都是一场噩梦。 值得庆幸的是&#xff0c;Mac用户可以使用各种数据恢复工具&#xff0c;可以帮助您恢复丢失或意外删除的文件。 在本文中&#xff0c;我们将采用适用于…

Arrays 中的 asList()方法

public static <T> List<T> asList&#xff08; T . . . a &#xff09;{ return new ArrayList<>&#xff08;a&#xff09;&#xff1b; } 返回由指定数组支持的固定大小的 list集合。对数组所做的更改将在返回的 l…

【USRP】通信之有线通信

有线通信&#xff1a; 有线通信是指使用物理线路或媒体&#xff08;例如&#xff0c;铜线、同轴电缆、光纤&#xff09;进行数据、声音和视频传输的通信方式。由于它依赖于实体传输媒介&#xff0c;有线通信通常具有较高的稳定性和可靠性&#xff0c;并能支持长距离的高带宽通…

input框输入中文时,输入未完成触发事件。Vue中文输入法不触发input事件?

前言 在做搜索输入框时&#xff0c;产品期待实时搜索&#xff0c;就是边输入边搜索&#xff0c;然而对于中文输入法出现的效果&#xff0c;不同的产品可能有不同的意见&#xff0c;有的觉得输入未完成也应该触发搜索。但有的却认为应该在中文输入完成后再触发搜索。我发现在vu…

Docker Swarm 集群搭建

Docker Swarm Mode Docker Swarm 集群搭建 Docker Swarm 节点维护 Docker Service 创建 1.准备主机 搭建一个 docker swarm 集群&#xff0c;包含 5 个 swarm 节点。这 5 个 swarm 节点的 IP 与暂 时的角色分配如下&#xff08;注意&#xff0c;搭建完成后会切换角色&#xff…

23年上半年上午题复习

敏捷方法 耦合 软件维护 消息 面向对象测试 面向对象设计原则 包图 原型模式 数据库三级模型 数据库函数依赖 哈夫曼树 左0右1 折半查找 画一个折半查找树&#xff0c;这个树只会往一个方向查找&#xff0c;一个节点不会同时出现左右子树&#xff0c;较小的作为左子树&#…

git将当前分支A强制推送远程分支pro上

前言 开发中基于线上分支pro创建了A分支&#xff0c;开发完成之后。又基于线上分支pro创建了B分支&#xff0c;都以此合并到测试分支&#xff0c;两个分支更改中都动用部分共同的文件&#xff0c;这就导致后续开发合并代码越来越乱&#xff0c;这时你想把本地开发的分支强推到…