目录

  • 窗口
    • 窗口的概念
    • 窗口的分类
      • 滚动窗口(Tumbling Windows)
      • 滑动窗口(Sliding Windows)
      • 会话窗口(Session Windows)
      • 全局窗口(Global Windows)
    • 窗口API概览
    • 窗口函数
      • 增量聚合函数
        • ReduceFunction
        • AggregateFunction
      • 全窗口函数
        • 窗口函数(WindowFunction)
        • 处理窗口函数(ProcessWindowFunction)
      • 增量聚合和全窗口函数的结合使用
    • 其他API
      • 触发器(Trigger)
      • 移除器(Evictor)
  • 窗口_原理简析_划分&触发时机&生命周期

在批处理统计中,我们可以等待一批数据都到齐后,统一处理。但是在实时处理统计中,我们是来一条就得处理一条,那么我们怎么统计最近一段时间内的数据呢?引入“窗口”。
所谓的“窗口”,一般就是划定的一段时间范围,也就是“时间窗”;对在这范围内的数据进行处理,就是所谓的窗口计算。所以窗口和时间往往是分不开的。接下来我们就深入了解一下Flink中的时间语义和窗口的应用。

窗口

窗口的概念

Flink是一种流式计算引擎,主要是来处理无界数据流的,数据源源不断、无穷无尽。想要更加方便高效地处理无界流,一种方式就是将无限数据切割成有限的“数据块”进行处理,这就是所谓的“窗口(Window)。

注意:Flink中窗口并不是静态准备好的,而是动态创建——当有落在这个窗口区间范围的数据达到时,才创建对应的窗口。另外,这里我们认为到达窗口结束时间时,窗口就触发计算并关闭。

窗口的分类

窗口本身是截取有界数据的一种方式,所以窗口一个非常重要的信息其实就是 “怎样截取数据”。换句话说,就是以什么标准来开始和结束数据的截取,我们把它叫作窗口的 “驱动类型”。

(1)时间窗口(Time Window)
时间窗口以时间点来定义窗口的开始(start)和结束(end),所以截取出的就是某一时间段的数据。到达结束时间时,窗口不再收集数据,触发计算输出结果,并将窗口关闭销毁。所以可以说基本思路就是 “定点发车”。

(2)计数窗口(Count Window)
计数窗口基于元素的个数来截取数据,到达固定的个数时就触发计算并关闭窗口。每个窗口截取数据的个数,就是窗口的大小。基本思路是 “人齐发车”。

按照窗口分配数据的规则分类

根据分配数据的规则,窗口的具体实现可以分为4类:滚动窗口(Tumbling Window)、滑动窗口(Sliding Window)、会话窗口(Session Window),以及全局窗口(Global Window)。

滚动窗口(Tumbling Windows)

滚动窗口有固定的大小,是一种对数据进行“均匀切片”的划分方式。窗口之间没有重叠,也不会有间隔,是“首尾相接”的状态。这是最简单的窗口形式,每个数据都会被分配到一个窗口,而且只会属于一个窗口。

滚动窗口可以基于时间定义,也可以基于数据个数定义;需要的参数只有一个,就是窗口的大小(window size)。

比如我们可以定义一个长度为1小时的滚动时间窗口,那么每个小时就会进行一次统计;或者定义一个长度为10的滚动计数窗口,就会每10个数进行一次统计。

滚动窗口应用非常广泛,它可以对每个时间段做聚合统计,很多BI分析指标都可以用它来实现

在这里插入图片描述

滑动窗口(Sliding Windows)

滑动窗口的大小也是固定的。但是窗口之间并不是首尾相接的,而是可以“错开”一定的位置。

定义滑动窗口的参数有两个:除去窗口大小(window size)之外,还有一个“滑动步长”(window slide),它其实就代表了窗口计算的频率。窗口在结束时间触发计算输出结果,那么滑动步长就代表了计算频率。

当滑动步长小于窗口大小时,滑动窗口就会出现重叠,这时数据也可能会被同时分配到多个窗口中。而具体的个数,就由窗口大小和滑动步长的比值(size/slide)来决定。

滚动窗口也可以看作是一种特殊的滑动窗口——窗口大小等于滑动步长(size = slide)。

滑动窗口适合计算结果更新频率非常高的场景

在这里插入图片描述

会话窗口(Session Windows)

会话窗口,是基于“会话”(session)来对数据进行分组的。会话窗口只能基于时间来定义。

会话窗口中,最重要的参数就是会话的超时时间,也就是两个会话窗口之间的最小距离。如果相邻两个数据到来的时间间隔(Gap)小于指定的大小(size),那说明还在保持会话,它们就属于同一个窗口;如果gap大于size,那么新来的数据就应该属于新的会话窗口,而前一个窗口就应该关闭了。

会话窗口的长度不固定,起始和结束时间也是不确定的,各个分区之间窗口没有任何关联。会话窗口之间一定是不会重叠的,而且会留有至少为size的间隔(session gap)。

在一些类似保持会话的场景下,可以使用会话窗口来进行数据的处理统计。

全局窗口(Global Windows)

“全局窗口”,这种窗口全局有效,会把相同key的所有数据都分配到同一个窗口中。这种窗口没有结束的时候,默认是不会做触发计算的。如果希望它能对数据进行计算处理,还需要自定义“触发器”(Trigger)。

全局窗口没有结束的时间点,所以一般在希望做更加灵活的窗口处理时自定义使用。Flink中的计数窗口(Count Window),底层就是用全局窗口实现的。

窗口API概览

窗口函数

定义了窗口分配器,我们只是知道了数据属于哪个窗口,可以将数据收集起来了;至于收集起来到底要做什么,其实还完全没有头绪。所以在窗口分配器之后,必须再接上一个定义窗口如何进行计算的操作,这就是所谓的“窗口函数”(window functions)。

窗口函数定义了要对窗口中收集的数据做的计算操作,根据处理的方式可以分为两类:增量聚合函数和全窗口函数

增量聚合函数

增量聚合:来一条数据,计算一条数据,窗口触发的时候输出计算结果

ReduceFunction
package window;
import env.WaterSensor;
import org.apache.flink.api.common.functions.ReduceFunction;
import org.apache.flink.streaming.api.datastream.KeyedStream;
import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
import org.apache.flink.streaming.api.datastream.WindowedStream;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.windowing.assigners.TumblingProcessingTimeWindows;
import org.apache.flink.streaming.api.windowing.time.Time;
import org.apache.flink.streaming.api.windowing.windows.TimeWindow;
import split.WaterSensorMap;
public class WindowReduceDemo {public static void main(String[] args) throws Exception {StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();env.setParallelism(1);SingleOutputStreamOperator<WaterSensor> source = env.socketTextStream("master", 9999).map(new WaterSensorMap());KeyedStream<WaterSensor, String> waterSensorStringKeyedStream = source.keyBy(sensor -> sensor.getId());WindowedStream<WaterSensor, String, TimeWindow> sensorWS = waterSensorStringKeyedStream.window(TumblingProcessingTimeWindows.of(Time.seconds(20)));SingleOutputStreamOperator<WaterSensor> reduce = sensorWS.reduce(new ReduceFunction<WaterSensor>() {@Overridepublic WaterSensor reduce(WaterSensor value1, WaterSensor value2) throws Exception {System.out.println("调用reduce方法,value1=" + value1 + ",value2=" + value2);return new WaterSensor(value1.getId(), value2.ts, value1.vc + value2.vc);}});reduce.print();env.execute();}
}

在这里插入图片描述

在这里插入图片描述

窗口的reduce:
相同key的第一条数据来的时候,不会调用reduce方法
增量聚合:来一条数据,就会计算一次,但是不会输出
在窗口触发的时候,才会输出窗口的最终计算结果

AggregateFunction

ReduceFunction可以解决大多数归约聚合的问题,但是这个接口有一个限制,就是聚合状态的类型、输出结果的类型都必须和输入数据类型一样。
Flink Window API中的aggregate就突破了这个限制,可以定义更加灵活的窗口聚合操作。这个方法需要传入一个AggregateFunction的实现类作为参数。
AggregateFunction可以看作是ReduceFunction的通用版本,这里有三种类型:输入类型(IN)、累加器类型(ACC)和输出类型(OUT)。输入类型IN就是输入流中元素的数据类型;累加器类型ACC则是我们进行聚合的中间状态类型;而输出类型当然就是最终计算结果的类型了。

接口中有四个方法:

  1. createAccumulator():创建一个累加器,这就是为聚合创建了一个初始状态,每个聚合任务只会调用一次。
  2. add():将输入的元素添加到累加器中。
  3. getResult():从累加器中提取聚合的输出结果。
  4. merge():合并两个累加器,并将合并后的状态作为一个累加器返回。

所以可以看到,AggregateFunction的工作原理是:首先调用createAccumulator()为任务初始化一个状态(累加器);而后每来一个数据就调用一次add()方法,对数据进行聚合,得到的结果保存在状态中;等到了窗口需要输出时,再调用getResult()方法得到计算结果。很明显,与ReduceFunction相同,AggregateFunction也是增量式的聚合;而由于输入、中间状态、输出的类型可以不同,使得应用更加灵活方便。

package window;import env.WaterSensor;
import org.apache.flink.api.common.functions.AggregateFunction;
import org.apache.flink.streaming.api.datastream.KeyedStream;
import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
import org.apache.flink.streaming.api.datastream.WindowedStream;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.windowing.assigners.TumblingProcessingTimeWindows;
import org.apache.flink.streaming.api.windowing.time.Time;
import org.apache.flink.streaming.api.windowing.windows.TimeWindow;
import split.WaterSensorMap;public class WindowAggregateDemo {public static void main(String[] args) throws Exception {StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();env.setParallelism(1);SingleOutputStreamOperator<WaterSensor> source = env.socketTextStream("master", 9999).map(new WaterSensorMap());KeyedStream<WaterSensor, String> waterSensorStringKeyedStream = source.keyBy(sensor -> sensor.getId());WindowedStream<WaterSensor, String, TimeWindow> sensorWS = waterSensorStringKeyedStream.window(TumblingProcessingTimeWindows.of(Time.seconds(10)));SingleOutputStreamOperator<String> aggregate = sensorWS.aggregate(new AggregateFunction<WaterSensor, Integer, String>() {@Overridepublic Integer createAccumulator() {System.out.println("初始化累加器");return 0;}@Overridepublic Integer add(WaterSensor value, Integer accumulator) {System.out.println("调用add方法,value=" +value);return accumulator + value.getVc();}@Overridepublic String getResult(Integer accumulator) {System.out.println("调用getResult方法");return accumulator.toString();}@Overridepublic Integer merge(Integer a, Integer b) {System.out.println("调用merge方法");return null;}});aggregate.print();env.execute();}
}

在这里插入图片描述

在这里插入图片描述

属于本窗口的第一条数据来,创建窗口,创建累加器
增量聚合:来一条计算一条,调用add方法
窗口输出时调用一次getResult方法
输入,中间累加器,输出,类型可以不一样,非常灵活

全窗口函数

数据来了不计算,存起来,窗口触发的时候,计算并输出结果
process

有些场景下,我们要做的计算必须基于全部的数据才有效,这时做增量聚合就没什么意义了;另外,输出的结果有可能要包含上下文中的一些信息(比如窗口的起始时间),这是增量聚合函数做不到的。

所以,我们还需要有更丰富的窗口计算方式。窗口操作中的另一大类就是全窗口函数。与增量聚合函数不同,全窗口函数需要先收集窗口中的数据,并在内部缓存起来,等到窗口要输出结果的时候再取出数据进行计算。

在Flink中,全窗口函数也有两种:WindowFunction和ProcessWindowFunction。

窗口函数(WindowFunction)

WindowFunction字面上就是“窗口函数”,它其实是老版本的通用窗口函数接口。我们可以基于WindowedStream调用.apply()方法,传入一个 WindowFunction 的实现类。

stream.keyBy(<key selector>).window(<window assigner>).apply(new MyWindowFunction());

这个类中可以获取到包含窗口所有数据的可迭代集合(Iterable),还可以拿到窗口(Window)本身的信息。
不过WindowFunction能提供的上下文信息较少,也没有更高级的功能。事实上,它的作用可以被ProcessWindowFunction全覆盖,所以之后可能会逐渐弃用。

处理窗口函数(ProcessWindowFunction)

ProcessWindowFunction是Window API中最底层的通用窗口函数接口。之所以说它“最底层”,是因为除了可以拿到窗口中的所有数据之外,ProcessWindowFunction还可以获取到一个“上下文对象”(Context)。这个上下文对象非常强大,不仅能够获取窗口信息,还可以访问当前的时间和状态信息。这里的时间就包括了处理时间(processing time)和事件时间水位线(event time watermark)。这就使得ProcessWindowFunction更加灵活、功能更加丰富,其实就是一个增强版的WindowFunction。

package window;import env.WaterSensor;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.apache.flink.streaming.api.datastream.KeyedStream;
import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
import org.apache.flink.streaming.api.datastream.WindowedStream;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.functions.windowing.ProcessWindowFunction;
import org.apache.flink.streaming.api.windowing.assigners.TumblingProcessingTimeWindows;
import org.apache.flink.streaming.api.windowing.time.Time;
import org.apache.flink.streaming.api.windowing.windows.TimeWindow;
import org.apache.flink.util.Collector;
import split.WaterSensorMap;public class WindowProcessDemo {public static void main(String[] args) throws Exception {StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();env.setParallelism(1);SingleOutputStreamOperator<WaterSensor> source = env.socketTextStream("master", 9999).map(new WaterSensorMap());KeyedStream<WaterSensor, String> waterSensorStringKeyedStream = source.keyBy(sensor -> sensor.getId());WindowedStream<WaterSensor, String, TimeWindow> sensorWS = waterSensorStringKeyedStream.window(TumblingProcessingTimeWindows.of(Time.seconds(20)));SingleOutputStreamOperator<String> process = sensorWS.process(new ProcessWindowFunction<WaterSensor, String, String, TimeWindow>() {/**** @param s* @param context* @param elements* @param out* @throws Exception*/@Overridepublic void process(String s, Context context, Iterable<WaterSensor> elements, Collector<String> out) throws Exception {long start = context.window().getStart();long end = context.window().getEnd();String windowStart = DateFormatUtils.format(start, "yyyy-MM-dd HH:mm:ss.SSS");String windowEnd = DateFormatUtils.format(end, "yyyy-MM-dd HH:mm:ss.SSS");long count = elements.spliterator().estimateSize();out.collect("key=" + s + "的窗口[" + windowStart + "," + windowEnd + "]包含" + count + "条数据===>" + elements.toString());}});process.print();env.execute();}
}

增量聚合和全窗口函数的结合使用

在实际应用中,我们往往希望兼具这两者的优点,把它们结合在一起使用。Flink的Window API就给我们实现了这样的用法。
我们之前在调用WindowedStream的.reduce()和.aggregate()方法时,只是简单地直接传入了一个ReduceFunction或AggregateFunction进行增量聚合。除此之外,其实还可以传入第二个参数:一个全窗口函数,可以是WindowFunction或者ProcessWindowFunction。

package window;
import env.WaterSensor;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.apache.flink.api.common.functions.AggregateFunction;
import org.apache.flink.streaming.api.datastream.KeyedStream;
import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
import org.apache.flink.streaming.api.datastream.WindowedStream;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.functions.windowing.ProcessWindowFunction;
import org.apache.flink.streaming.api.windowing.assigners.TumblingProcessingTimeWindows;
import org.apache.flink.streaming.api.windowing.time.Time;
import org.apache.flink.streaming.api.windowing.windows.TimeWindow;
import org.apache.flink.util.Collector;
import split.WaterSensorMap;public class WindowAggregateAndProcessDemo {public static void main(String[] args) throws Exception {StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();SingleOutputStreamOperator<WaterSensor> source = env.socketTextStream("master", 9999).map(new WaterSensorMap());KeyedStream<WaterSensor, String> waterSensorStringKeyedStream = source.keyBy(sensor -> sensor.getId());WindowedStream<WaterSensor, String, TimeWindow> sensorWS = waterSensorStringKeyedStream.window(TumblingProcessingTimeWindows.of(Time.seconds(10)));SingleOutputStreamOperator<String> result = sensorWS.aggregate(new MyAgg(), new MyProcess());result.print();env.execute();}public static class MyProcess extends ProcessWindowFunction<String,String,String,TimeWindow>{@Overridepublic void process(String s, Context context, Iterable<String> elements, Collector<String> out) throws Exception {long start = context.window().getStart();long end = context.window().getEnd();String windowStart = DateFormatUtils.format(start, "yyyy-MM-dd HH:mm:ss.SSS");String windowEnd = DateFormatUtils.format(end, "yyyy-MM-dd HH:mm:ss.SSS");long count = elements.spliterator().estimateSize();out.collect("key=" + s + "的窗口[" + windowStart + "," + windowEnd + "]包含" + count + "条数据===>" + elements.toString());}}public static class MyAgg implements AggregateFunction<WaterSensor, Integer, String> {@Overridepublic Integer createAccumulator() {return null;}@Overridepublic Integer add(WaterSensor value, Integer accumulator) {return null;}@Overridepublic String getResult(Integer accumulator) {return null;}@Overridepublic Integer merge(Integer a, Integer b) {return null;}}
}

这样调用的处理机制是:基于第一个参数(增量聚合函数)来处理窗口数据,每来一个数据就做一次聚合;等到窗口需要触发计算时,则调用第二个参数(全窗口函数)的处理逻辑输出结果。需要注意的是,这里的全窗口函数就不再缓存所有数据了,而是直接将增量聚合函数的结果拿来当作了Iterable类型的输入。

结合两者的优点:

  1. 增量聚合:来一条计算一条,存储中间的计算结果,占用空间少
  2. 全窗口函数:可以通过上下文实现灵活的功能

其他API

对于一个窗口算子而言,窗口分配器和窗口函数是必不可少的。除此之外,Flink还提供了其他一些可选的API,让我们可以更加灵活地控制窗口行为

触发器(Trigger)

触发器主要是用来控制窗口什么时候触发计算。所谓的“触发计算”,本质上就是执行窗口函数,所以可以认为是计算得到结果并输出的过程。
基于WindowedStream调用.trigger()方法,就可以传入一个自定义的窗口触发器(Trigger)。

stream.keyBy(...).window(...).trigger(new MyTrigger())

移除器(Evictor)

移除器主要用来定义移除某些数据的逻辑。基于WindowedStream调用.evictor()方法,就可以传入一个自定义的移除器(Evictor)。Evictor是一个接口,不同的窗口类型都有各自预实现的移除器。

窗口_原理简析_划分&触发时机&生命周期

以时间类型的滚动窗口为例,分析原理

窗口什么时候触发输出?

时间进展 >= 窗口的最大时间戳(end - 1ms)

窗口是怎么划分的?

在这里插入图片描述

在这里插入图片描述

offset默认为0
timestamp = 13
windowsize = 10
remainder = 3
start = 10timestamp = 27
windowsize = 10
remainder = 7
start = 20timestamp = 35
windowsize = 10
remainder = 5
start = 30

start = 向下取整、取窗口长度的整数倍
end = start + 窗口长度

窗口左闭右开 => 属于本窗口的最大时间戳 = end - 1ms

窗口的生命周期

创建:属于本窗口的第一条数据来的时候,现new的,放入一个singleton单例的集合中
销毁(关窗):时间进展 >= 窗口的最大时间戳(end - 1ms)+ 允许迟到的时间(默认为0)

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

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

相关文章

攻击路径(4):API安全风险导致敏感数据泄漏

本文是《攻防演练 | JS泄露到主机失陷[1]》的学习笔记&#xff0c;欢迎大家阅读原文。攻击路径通过未授权访问攻击获取敏感数据通过SQL注入攻击获取服务器权限通过凭据访问攻击获取数据库权限和敏感数据和应用权限安全风险与加固措施通过未授权访问攻击获取敏感数据、通过SQL注…

机器学习面试题:请介绍一下你理解的集成学习算法

集成学习&#xff08;Ensemble Learning&#xff09;的核心思想是“集思广益”&#xff0c;它通过构建并结合多个基学习器&#xff08;Base Learner&#xff09;来完成学习任务&#xff0c;从而获得比单一学习器更显著优越的泛化性能。俗话说&#xff0c;“三个臭皮匠&#xff…

Invalid bound statement (not found): com.XXX.XXx.service.xxx无法执行service

org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): com.xxx.xxx.service.CitytownService.selectCitytown 出现无法加载sevice层的时候&#xff0c;如下图所示1&#xff0c;处理方法是&#xff0c;先看下注解MapperScan内的包地址&#xff0c…

泛型(Generics)what why when【前端TS】

我总是提醒自己一定要严谨严谨严谨 目录TypeScript 泛型 (Generics)1. 什么是泛型&#xff1f;2. 为什么需要泛型&#xff1f;3. 泛型常见用法3.1 函数泛型3.2 接口泛型3.3 类泛型3.4 泛型约束3.5 泛型默认值3.6 多个泛型参数4. 泛型应用场景TypeScript 泛型 (Generics) 1. 什…

分布式协议与算法实战-协议和算法篇

05丨Paxos算法&#xff08;一&#xff09;&#xff1a;如何在多个节点间确定某变量的值? 提到分布式算法&#xff0c;就不得不提 Paxos 算法&#xff0c;在过去几十年里&#xff0c;它基本上是分布式共识的代名词&#xff0c;因为当前最常用的一批共识算法都是基于它改进的。比…

9.13 9.15 JavaWeb(事务管理、AOP P172-P182)

事务管理事务概念事务是一组操作的集合&#xff0c;是一个不可分割的工作单位&#xff0c;这些操作要么同时成功&#xff0c;要么同时失败操作开启事务&#xff08;一组操作开始前&#xff0c;开启事务&#xff09;&#xff1a;start transaction / begin提交事务&#xff08;这…

检索融合方法- Distribution-Based Score Fusion (DBSF)

在信息检索&#xff08;IR&#xff09;、推荐系统和多模态检索中&#xff0c;我们常常需要融合来自多个检索器或模型的结果。不同检索器可能对同一文档打出的分数差异很大&#xff0c;如果直接简单加权&#xff0c;很容易出现某个检索器“主导融合结果”的情况。 Distribution…

Oracle体系结构-归档日志文件(Archive Log Files)

核心概念&#xff1a;什么是归档日志文件&#xff1f; 定义&#xff1a; 归档日志文件&#xff08;Archive Log Files&#xff09;是在线重做日志文件&#xff08;Online Redo Log Files&#xff09;在被覆盖之前的一个完整副本。它们由 Oracle 的后台进程 ARCn&#xff08;归档…

GoogLeNet实战:用PyTorch实现经典Inception模块

配套笔记&讲解视频&#xff0c;点击文末名片获取研究背景&#xff08;Background&#xff09; 1.1 领域现状&#xff08;大环境与挑战&#xff09; 想象一下&#xff0c;你和朋友们在看一大堆照片——猫、狗、汽车、蛋糕&#xff0c;大家要把每张照片贴上标签。几年前&…

【开题答辩全过程】以 “旧书驿站”微信小程序的设计与开发为例,包含答辩的问题和答案

个人简介一名14年经验的资深毕设内行人&#xff0c;语言擅长Java、php、微信小程序、Python、Golang、安卓Android等开发项目包括大数据、深度学习、网站、小程序、安卓、算法。平常会做一些项目定制化开发、代码讲解、答辩教学、文档编写、也懂一些降重方面的技巧。感谢大家的…

【办公类-112-01】20250912家园每周沟通指导(Deepseek扩写完善+Python模拟点击鼠标自动发送给家长微信)

背景需求 孩子刚上小班,家长比较关心孩子情况(情绪、社交、吃饭等) 所以我每周五晚上和家长沟通一下孩子的情况。 操作流程 第一周(9月5日)是“适应周”,我添加了所有孩子的一位家长的微信号 23份全部是手打,足足写了4个小时。第一周案例多,所以写了很多,措辞酝酿后…

Spark专题-第一部分:Spark 核心概述(1)-Spark 是什么?

众所周知&#xff0c;教学文档总该以理论部分作为开篇&#xff0c;于是我们这篇Spark专题同样会以一堆理论和专有名词开始&#xff0c;笔者会尽可能的让专业词汇通俗易懂 第一部分&#xff1a;Spark 核心概述 Spark 是什么&#xff1f; 1. 大数据时代的"超级赛车"…

从零到一上手 Protocol Buffers用 C# 打造可演进的通讯录

一、为什么是 Protobuf&#xff08;而不是 XML/自定义字符串/.NET 二进制序列化&#xff09; 在需要把结构化对象持久化或跨进程/跨语言传输时&#xff0c;常见方案各有痛点&#xff1a; BinaryFormatter 等 .NET 二进制序列化&#xff1a;对类型签名与版本极其脆弱、体积偏大&…

计算机网络(三)网络层

三、网络层网络层是五层模型中的第三层&#xff0c;位于数据链路层和传输层之间。它的核心任务是实现数据包在不同网络之间&#xff08;跨网络&#xff09;的逻辑传输。网络层的数据传输单位是数据报&#xff08;Datagram&#xff09;或数据包&#xff08;Packet&#xff09;。…

互联网大厂Java面试实录:从基础到微服务全栈技术答疑

互联网大厂Java面试实录&#xff1a;从基础到微服务全栈技术答疑 本文以电商场景为背景&#xff0c;展现一场互联网大厂Java开发职位的面试过程。严肃的面试官与搞笑的水货程序员谢飞机展开三轮技术问答&#xff0c;涵盖Java SE、Spring Boot、数据库、微服务、安全以及CI/CD等…

StringBuilder 深度解析:数据结构与扩容机制的底层细节

文章目录 前言 一、数据结构&#xff1a;不止是简单的字符数组 1. 核心成员变量&#xff08;定义在 AbstractStringBuilder 中&#xff09; 2. 构造器与初始容量 二、扩容机制&#xff1a;从 "不够用" 到 "换大容器" 的全过程 步骤 1&#xff1a;计算…

Elasticsearch面试精讲 Day 17:查询性能调优实践

【Elasticsearch面试精讲 Day 17】查询性能调优实践 在“Elasticsearch面试精讲”系列的第17天&#xff0c;我们聚焦于查询性能调优实践。作为全文检索与数据分析的核心引擎&#xff0c;Elasticsearch的查询性能直接影响用户体验和系统吞吐能力。在高并发、大数据量场景下&…

WPF 数据绑定模式详解(TwoWay、OneWay、OneTime、OneWayToSource、Default)

在WPF中&#xff0c;数据绑定模式&#xff08;Binding Mode&#xff09;用于指定数据流的方向。常见的模式有TwoWay、OneWay、OneTime、OneWayToSource和Default。TwoWay&#xff08;双向绑定&#xff09;&#xff1a;数据从源&#xff08;通常是ViewModel或数据上下文&#xf…

使用 NVIDIA Dynamo 部署 PD 分离推理服务

1 Dynamo 介绍 NVIDIA Dynamo 是一个开源的模块化推理框架&#xff0c;用于在分布式环境上实现生成式 AI 模型的服务化部署。Dynamo 通过动态资源调度、智能路由、内存优化与高速数据传输&#xff0c;无缝扩展大型 GPU 集群之间的推理工作负载。 Dynamo 采用推理引擎无关的设…

答题卡识别改分项目

目录 核心思路 分步实现与代码解析 1. 环境准备与工具函数定义 2. 图片预处理 3. 轮廓提取与筛选 3. 轮廓提取与筛选 4. 透视变换&#xff08;矫正倾斜答题卡&#xff09; 5. 阈值处理&#xff08;突出填涂区域&#xff09; 6. 提取选项圆圈轮廓 7. 选项轮廓排序&…