【Flink-scala】DataStream编程模型之水位线
DataStream API编程模型
1.【Flink-Scala】DataStream编程模型之 数据源、数据转换、数据输出
2.【Flink-scala】DataStream编程模型之 窗口的划分-时间概念-窗口计算程序
3.【Flink-scala】DataStream编程模型之 窗口计算-触发器-驱逐器
文章目录
- DataStream API编程模型
- 前言
- 一、水位线
- 1.1 水位线
- 1.1.1 概念
- 1.1.2 水位线如何发挥作用呢?
- 1.1.3 水位线原理
- 1.1.3.1 消息正常到达系统的时间
- 1.1.3.2消息延迟到达系统时的情况
- 1.1.3.3 采用事件时间时的情况
- 1.1.3.4 引入水位线机制的情况
- 1.1.4 水位线的设置方法
- 1.1.4.1水位线生成策略--固定延迟生成水位线
- 1.1.4.2 水位线生成策略-单调递增生成水位线
- 1.1.4.3 自动义生成水位线策略
- 总结
前言
本小节学习水位线和延迟数据处理,再学习状态编程,水位线和延迟数据处理关联性强一点,如果篇幅太长,我就再开一小节写。
开始吧!
水位线,这和实际生活中河流/水库到达哪个水位线就要有什么问题一样,就是达到水位线后做什么处理。
(我这样想:河流里或者是水龙头的水是水流,把水换成数据就是datastream[数据流]。水位线这个概念也能扯上点关系)
一、水位线
1.1 水位线
1.1.1 概念
水位线是一种衡量事件时间进展的机制,它是数据本身的一个隐藏属性,本质上就是一个时间戳。
水位线是配合事件时间来使用的,通常基于事件时间的数据,自身都包含一个水位线用于处理乱序事件。
使用处理时间来处理事件时不会有延迟,因此也不需要水位线,所以水位线只出现在事件时间窗口。
正确地处理乱序事件,通常是结合窗口和水位线这两种机制来实现的。
1.1.2 水位线如何发挥作用呢?
在流处理过程中,从事件产生,到流经数据源,再到流经算子,中间是有一个过程和时间的。
虽然大部分情况下,流到算子的数据都是按照事件产生的时间顺序到达的,但是也不排除由于网络、系统等原因,导致乱序的产生和迟到数据。
但是对于迟到数据而言,我们又不能无限期地等下去,必须要有个机制来保证在经过一个特定的时间后,必须触发窗口去进行计算。
在进行窗口计算时,使用接入时间或处理时间的消息,都是以系统的墙上时间(比如现在是8:50我写的博客,那么这个事件就是8:50,就是生成时间)为准,因此事件都是按序到达的。但在实际应用中,由于网络或者系统等外部因素影响,事件数据往往不能及时到达Flink系统,从而造成数据乱序到达或者延迟到达等问题。针对这两个问题Flink主要采用以水位线为核心的机制来应对。
此时就是水位线发挥作用了,它表示当达到水位线后,在水位线之前的数据已经全部到达(即使后面还有延迟的数据),系统可以触发相应的窗口计算。
只有水位线越过窗口对应的结束时间,窗口才会关闭和进行计算。
一般而言,只有以下两个条件同时成立,才会触发窗口计算:
(1)条件T1:水位线时间 >= 窗口结束时间;
(2)条件T2:在[窗口开始时间,窗口结束时间)中有数据存在。
理想情况下,水位线应该与处理时间一致,并且处理时间与事件时间只相差常数时间甚至为零。
当水位线与处理时间完全重合时,就意味着消息产生后马上被处理,不存在消息迟到的情况。
然而,由于网络拥塞或系统原因,消息常常存在迟到的情况,
因此,在设置水位线时,总是考虑一定的延时,从而给予迟到的数据一些机会。具体的延迟大小根据水位线实现方式的不同而也有所差别
1.1.3 水位线原理
1.1.3.1 消息正常到达系统的时间
window1[5s-15s]
window2 [10s-20s]
window3[15s-25s]
现在假设有一个单词数据流,需要采用基于处理时间的滑动窗口进行实时的词频统计,滑动窗口大小为10s,滑动步长为5s。
假设数据源分别在第12秒、第12秒和第17秒的时候,生成3条内容为单词“a”的消息,这些消息将进入窗口中。
时间<15s, 2条数据时间>15s,1条数据5-15s,3条数据
每个窗口提交后,最后统计值分别是 (a, 2),(a, 3) 和 (a, 1)
1.1.3.2消息延迟到达系统时的情况
正常是12-17s来了3条数据,现在开始有迟到数据。
假设在12s时候出现一条迟到6s的数据(18sde 数据),这条延迟的消息会落入 window2 [10s-20s] 和 window3[15s-25s]。
窗口提交后,最后统计值将分别是 (a, 1),(a, 3) 和 (a, 2)。(正常应该是(a, 2),(a, 3) 和 (a, 1))
可看出这条延迟的消息没有对window2 [10s-20s]的计算结果造成影响,但却影响了window1[5s-15s]和 window3[15s-25s]的计算结果,导致二者计算结果出错。
因为当这条消息在第18秒到达时,window1[5s-15s]计算已结束,这条消息不会被统计到window1[5s-15s]中,而会落入window3[15s-25s],导致被统计window3[15s-25s]
1.1.3.3 采用事件时间时的情况
采用事件时间,则当系统时间行进到第18秒时,这条迟到了6秒的消息会落入 window2 [10s-20s],
因为这条消息的事件生成时间是第12秒,所以就应该属于window1[5s-15s]和window2 [10s-20s],
但是在第18秒时,window1[5s-15s]已经关闭,所以,这条延迟的消息只会落入 window2 [10s-20s]。
最终,三个窗口的计算结果是(a,1),(a, 3) 和 (a, 1),也就是说,window2[10s-20s]和 window3[15s-25s]提交了正确的结果,但是 window1[5s-15s]的结果还是错误的
1.1.3.4 引入水位线机制的情况
就本例而言,水位线本质上就是告诉Flink一条消息可以延迟多久,
因此,这里让水位线等于系统当前时间减去5秒。由于只有水位线越过窗口对应的结束时间,窗口才会关闭和进行计算,
因此,第1个窗口window1[5s-15s]将会在第20秒的时候进行计算,第2个窗口window2[10s-20s]将会在第25秒的时候进行计算,第3个窗口window3[15s-25s]将会在第30秒的时候进行计算。
当系统时间行进到第18秒时,这条迟到了6秒的消息会落入window1[5s-15s])和 window2 [10s-20s],因为这条消息的事件生成时间是第12秒,所以就应该属于window1[5s-15s]和window2 [10s-20s]。
最终,三个窗口提交正确结果,即(a, 2),(a, 3) 和 (a, 1)
1.1.4 水位线的设置方法
水位线事关事件时间,那么就需要知道事件时间戳。
就必须为数据流中的每个元素分配一个时间戳。
在Flink系统中,分配时间戳和生成水位线这两个工作是同时进行的,前者是由TimestampAssigner来实现的,后者则是由WatermarkGenerator来实现的。
当我们构建了一个DataStream之后,可以使用assignTimestampsAndWatermarks方法来分配时间戳和生成水位线,调用该方法时,需要传入一个WatermarkStrategy对象,语法如下:
DataStream.assignTimestampsAndWatermarks(WatermarkStrategy<T>)
一般情况下,Flink要求WatermarkStrategy对象中同时包含了TimestampAssigner对象和WatermarkGenerator对象。
WatermarkStrategy是一个接口,提供了很多静态的方法,对于一些常用的水位线生成策略,我们不需要去实现这个接口,可以直接调用静态方法来生成水位线。
或者,我们也可以通过实现WatermarkStrategy接口中的createWatermarkGenerator方法和createTimestampAssigner方法,来自定义水位线策略。
说到底就是两个,分配时间戳,生成水位线(有的地方叫水印)。
1.1.4.1水位线生成策略–固定延迟生成水位线
固定延迟生成水位线的语法如下:
WatermarkStrategy.forBoundedOutOfOrderness(Duration maxOutOfOrderness)
比如,现在要实现一个延迟3秒的固定延迟水位线,并从消息中获取时间戳,具体语句如下:
val dataStream = ......
dataStream.assignTimestampsAndWatermarks(
WatermarkStrategy
.forBoundedOutOfOrderness[StockPrice](Duration.ofSeconds(3))//这里延迟3s
.withTimestampAssigner(new SerializableTimestampAssigner[StockPrice] {override def extractTimestamp(element: StockPrice, recordTimestamp: Long): Long = element.timeStamp//分配时间戳}
)
)
使用的是这个方法forBoundedOutOfOrderness
1.1.4.2 水位线生成策略-单调递增生成水位线
单调递增生成水位线是通过WatermarkStrategy接口的静态方法forMonotonousTimestamps提供的,语法如下:
WatermarkStrategy.forMonotonousTimestamps()
学习单词:
在程序中按照如下方式使用:
val dataStream = ......
dataStream.assignTimestampsAndWatermarks(
WatermarkStrategy
.forMonotonousTimestamps()
.withTimestampAssigner(new SerializableTimestampAssigner[StockPrice] {override def extractTimestamp(element: StockPrice, recordTimestamp: Long): Long = element.timeStamp}
)
)
1.1.4.3 自动义生成水位线策略
自定义肯定就是实现某个接口的什么方法啦,之前就说过
水位线设置就两个:分配时间戳,生成水位线
这里我们只需要实现WatermarkStrategy接口中的createWatermarkGenerator
方法和createTimestampAssigner
方法就可以了。
水位线策略:
createWatermarkGenerator方法需要返回一个WatermarkGenerator对象。
WatermarkGenerator是一个接口,需要实现这个接口里面的onEvent方法和onPeriodicEmit方法:
(1)onEvent:数据流中的每个元素(或事件)到达以后,都会调用这个方法,如果我们想依赖每个元素生成一个水位线,然后发射到下游,就可以实现这个方法。
(2)onPeriodicEmit:当数据量比较大的时候,为每个元素都生成一个水位线,会影响系统性能,所以Flink还提供了一个周期性生成水位线的方法。这个水位线的生成周期的设置方法是:env.getConfig.setAutoWatermarkInterval(5000L),其中5000L是间隔时间,可以由用户自定义。
在自定义水位线生成策略时,Flink提供了两种不同的方式:
1.定期水位线:在这种机制中,系统会通过onEvent方法对系统中到达的事件进行监控,然后,在系统调用onPeriodicEmit方法时,生成一个水位线。(两个方法都使用)
2.标点水位线:在这种机制中,系统会通过onEvent方法对系统中到达的事件进行监控,并等待具有特定标记的事件到达,一旦监测到特定事件到达,就立即生成一个水位线。通常,这种机制不会调用onPeriodicEmit方法来生成一个水位线。(只使用一个方法)
代码:
import java.text.SimpleDateFormat
import org.apache.flink.api.common.eventtime.{SerializableTimestampAssigner, TimestampAssigner, TimestampAssignerSupplier, Watermark, WatermarkGenerator, WatermarkGeneratorSupplier, WatermarkOutput, WatermarkStrategy}
import org.apache.flink.streaming.api.scala._
import org.apache.flink.streaming.api.TimeCharacteristic
import org.apache.flink.streaming.api.scala.StreamExecutionEnvironment
import org.apache.flink.streaming.api.windowing.assigners.TumblingEventTimeWindows
import org.apache.flink.streaming.api.windowing.time.Timecase class StockPrice(stockId:String,timeStamp:Long,price:Double)
object WatermarkTest { def main(args: Array[String]): Unit = {//设定执行环境
val env = StreamExecutionEnvironment.getExecutionEnvironment//设定时间特性为事件时间
env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)//设定程序并行度
env.setParallelism(1)//创建数据源
val source = env.socketTextStream("localhost", 9999)//指定针对数据流的转换操作逻辑
val stockDataStream = source.map(s => s.split(",")).map(s=>StockPrice(s(0).toString,s(1).toLong,s(2).toDouble))//为数据流分配时间戳和水位线
val watermarkDataStream = stockDataStream.assignTimestampsAndWatermarks(new MyWatermarkStrategy)//执行窗口计算
val sumStream = watermarkDataStream.keyBy("stockId").window(TumblingEventTimeWindows.of(Time.seconds(3))).reduce((s1, s2) => StockPrice(s1.stockId,s1.timeStamp, s1.price + s2.price))//打印输出
sumStream.print("output")//指定名称并触发流计算env.execute("WatermarkTest")}
//指定水位线生成策略class MyWatermarkStrategy extends WatermarkStrategy[StockPrice] {override def createTimestampAssigner(context:TimestampAssignerSupplier.Context):TimestampAssigner[StockPrice]={new SerializableTimestampAssigner[StockPrice] {override def extractTimestamp(element: StockPrice, recordTimestamp: Long): Long = {element.timeStamp //从到达消息中提取时间戳}}}override def createWatermarkGenerator(context:WatermarkGeneratorSupplier.Context): WatermarkGenerator[StockPrice] ={new WatermarkGenerator[StockPrice](){val maxOutOfOrderness = 10000L //设定最大延迟为10秒var currentMaxTimestamp: Long = 0Lvar a: Watermark = nullval format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS")override def onEvent(element: StockPrice, eventTimestamp: Long, output:WatermarkOutput): Unit = { currentMaxTimestamp = Math.max(eventTimestamp, currentMaxTimestamp)a = new Watermark(currentMaxTimestamp - maxOutOfOrderness)output.emitWatermark(a)println("timestamp:" + element.stockId + "," + element.timeStamp + "|" + format.format(element.timeStamp) + "," + currentMaxTimestamp + "|" + format.format(currentMaxTimestamp) + "," + a.toString)}override def onPeriodicEmit(output:WatermarkOutput): Unit = {// 没有使用周期性发送水印,因此这里没有执行任何操作}}}}
}
输入:
s1 stock_1,1602031567000,8.14
s2 stock_1,1602031571000,8.23
s3 stock_1,1602031577000,8.24
s4 stock_1,1602031578000,8.87
s5 stock_1,1602031579000,8.55
s6 stock_1,1602031581000,8.43
s7 stock_1,1602031582000,8.78
然后,在日志终端内,就可以看到如下输出信息:
timestamp:stock_1,1602031567000|2020-10-07 08:46:07.000,1602031567000|2020-10-07 08:46:07.000,Watermark @ 1602031557000 (2020-10-07 08:45:57.000)
timestamp:stock_1,1602031571000|2020-10-07 08:46:11.000,1602031571000|2020-10-07 08:46:11.000,Watermark @ 1602031561000 (2020-10-07 08:46:01.000)
timestamp:stock_1,1602031577000|2020-10-07 08:46:17.000,1602031577000|2020-10-07 08:46:17.000,Watermark @ 1602031567000 (2020-10-07 08:46:07.000)
timestamp:stock_1,1602031578000|2020-10-07 08:46:18.000,1602031578000|2020-10-07 08:46:18.000,Watermark @ 1602031568000 (2020-10-07 08:46:08.000)
timestamp:stock_1,1602031579000|2020-10-07 08:46:19.000,1602031579000|2020-10-07 08:46:19.000,Watermark @ 1602031569000 (2020-10-07 08:46:09.000)
output> StockPrice(stock_1,1602031567000,8.14)
timestamp:stock_1,1602031581000|2020-10-07 08:46:21.000,1602031581000|2020-10-07 08:46:21.000,Watermark @ 1602031571000 (2020-10-07 08:46:11.000)
timestamp:stock_1,1602031582000|2020-10-07 08:46:22.000,1602031582000|2020-10-07 08:46:22.000,Watermark @ 1602031572000 (2020-10-07 08:46:12.000)
output> StockPrice(stock_1,1602031571000,8.23)
为了正确理解水位线的工作原理,下面我们详细解释每个事件到达后水位线的变化情况、各个窗口中的事件分布情况以及窗口触发计算的情况。关于窗口计算,这里要再次强调,只有以下两个条件同时成立,才会触发窗口计算:
(1)条件T1:水位线时间 >= 窗口结束时间;
(2)条件T2:在[窗口开始时间,窗口结束时间)中有数据存在。
1.s1事件到达后
事件s1到达系统以后的水位线的变化情况,可以看出,当前的水位线已经到达了1602031557000(2020-10-07 08:45:57.000)。
s1到达后各个窗口包含事件的情况
水位线是在增长的,在那么增长的呢?
这是我截取上面 的部分代码。最大延迟10s,就是本次到的事件最大时间戳-10s,即为水位线。对应下代码:a = new Watermark(currentMaxTimestamp - maxOutOfOrderness)
val maxOutOfOrderness = 10000L //设定最大延迟为10秒var currentMaxTimestamp: Long = 0Lvar a: Watermark = nullval format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS")override def onEvent(element: StockPrice, eventTimestamp: Long, output:WatermarkOutput): Unit = { currentMaxTimestamp = Math.max(eventTimestamp, currentMaxTimestamp)a = new Watermark(currentMaxTimestamp - maxOutOfOrderness)
水位线增长:每次有新事件到达时,都会检查并更新currentMaxTimestamp,然后根据这个值减去maxOutOfOrderness来生成新的水位线
2.当事件s2到达以后
s2到达系统以后的水位线的变化情况,可以看出,当前的水位线已经到达了1602031561000(2020-10-07 08:46:01.000)。
s2到达以后各个窗口内包含的事件的情况。
3.当事件s3到达以后
事件s3到达系统以后的水位线的变化情况,可以看出,当前的水位线已经到达了1602031567000(2020-10-07 08:46:07.000)。
s3到达以后各个窗口内包含的事件的情况。
4.当事件s4到达以后
事件s4到达系统以后的水位线的变化情况,可以看出,当前的水位线已经到达了1602031568000(2020-10-07 08:46:08.000)。
s4到达以后各个窗口内包含的事件的情况。
回顾一下:
触发窗口计算:
(1)条件T1:水位线时间 >= 窗口结束时间;
(2)条件T2:在[窗口开始时间,窗口结束时间)中有数据存在。
看到水位线事件8:46:08,窗口结束事件是09,那么此时还没有大于等于。
继续
5.当事件s5到达以后
事件s5到达系统以后的水位线的变化情况,可以看出,当前的水位线已经到达了1602031569000(2020-10-07 08:46:09.000)。
当当当,注意啦,看看触发窗口计算条件。
s5到达以后各个窗口内包含的事件的情况。
8:46:09水位线已经大于等于w1窗口结束时间啦,条件1满足,且窗口有数据,条件2满足,w1开始计算!!!
6.当事件s6到达以后
事件s6到达系统以后的水位线的变化情况,可以看出,当前的水位线已经到达了1602031571000(2020-10-07 08:46:11.000)。
s6到达以后各个窗口内包含的事件的情况:
此时再看看条件满足否?
7.当事件s7到达以后
事件s7到达系统以后的水位线的变化情况,可以看出,当前的水位线已经到达了1602031572000(2020-10-07 08:46:12.000)。
s7到达以后各个窗口内包含的事件的情况。
当当当,又注意啦,看看是否满足条件?
满足条件,触发计算,窗口2 计算完成!!
总结
没有想到水位线写了这么多,延迟数据处理还没有写,本小节主要学习水位线的原理和设置方法。
其中自定义的水位线生成策略稍显麻烦,代码需要着重分析。下一小节该写延迟数据处理了。
相关文章:

【Flink-scala】DataStream编程模型之水位线
DataStream API编程模型 1.【Flink-Scala】DataStream编程模型之 数据源、数据转换、数据输出 2.【Flink-scala】DataStream编程模型之 窗口的划分-时间概念-窗口计算程序 3.【Flink-scala】DataStream编程模型之 窗口计算-触发器-驱逐器 文章目录 DataStream API编程模型前言…...

Python导入moviepy找不到editor 视频没有声音设置audio_codec参数
moviepy合成视频出错: 问题一:导入moviepy.editor找不到editor,No module named moviepy.editor问题二:合成的视频没有声音 问题一:导入moviepy.editor找不到editor,No module named moviepy.editor from …...

rsync 是一个非常强大的 Linux 工具,常用于文件和目录的同步、备份和传输。
rsync 是一个非常强大的 Linux 工具,常用于文件和目录的同步、备份和传输。它可以高效地同步本地和远程系统上的文件和目录,并且支持增量传输,仅同步发生变化的部分。rsync 支持压缩传输、删除多余文件、排除特定文件等多种功能,是日常运维、备份和迁移数据的重要工具。 一…...

触发器案例详解
目录 一、定义二、类型三、功能与用途四、创建与调用DML触发器格式示例DDL触发器格式示例登录触发器格式示例五、案例案例一:DML触发器 - 记录更新操作的触发器案例二:DML触发器 - 防止非法工资更新的触发器案例三:DDL触发器 - 记录表结构更改的触发器案例四:DDL触发器 - 防…...

jwt 与 sessionid 的区别及应用场景
在现代 Web 应用中,JWT(JSON Web Token)和SessionID是两种常用的用户认证和状态管理机制。本文从两者的原理、区别、优缺点以及适用场景展开分析,结合常见问题提出了最佳实践建议,帮助开发者更好地选择和使用。 JWT与S…...

tomcat和nginx
Tomcat 和 Nginx 都可以部署 Web 应用,但它们的核心功能和适用场景不同,因此在 2024 年生产环境及未来,是否使用 Tomcat 取决于需求和技术架构的特点。 1. Tomcat 的特点与适用场景 Tomcat 是 Java Servlet 容器,主要用来运行基于…...

服务器带宽与数据安全的重要性与作用
服务器带宽指的是服务器与外部网络通信的能力,即服务器发送和接收数据的速率。带宽越大,服务器在同一时间内能够处理的数据量就越多,数据传输的速度和稳定性也就越高。在数字化时代,企业对于数据的依赖程度日益加深,无…...

JSON数据处理
1. JSON注解 Data NoArgsConstructor AllArgsConstructor JsonIgnoreProperties(ignoreUnknowntrue) JSON注解: JsonIgnoreProperties(ignoreUnknowntrue) Hutool 日期格式化: DatePattern DateTimeFormat(pattern "yyyy-MM-dd HH:mm:ss")…...

FSWIND脉动风-风载时程生成器软件原理
大量风的实测资料表明,在风的时程曲线中,瞬时风速包含两个部分:一部分是自振周期一般在 10 分钟以上的平均风,另一部分是周期一般只有几秒左右的脉动风。平均风由于其周期一般比结构的自振周期大,因而考虑其作用性质相…...

搭建高可用负载均衡系统:Nginx 与云服务的最佳实践
搭建高可用负载均衡系统:Nginx 与云服务的最佳实践 引言 在项目开发过程中,我们通常在开发和测试阶段采用单机架构进行开发和测试。这是因为在这个阶段,系统的主要目的是功能实现和验证,单机架构足以满足开发人员的日常需求&…...

对比git命令与TortoiseGit工具,理解git解决冲突的过程
1 引言 此前用TortoiseSvn习惯了,所以,转到git时也同时选择了TortoiseGit工具。 同时,对比了可视化工具和git命令的操作,来深入理解git解决冲突的方式。 2 使用远程仓库替代本地仓库的内容 2.1 TortoiseGit做法 using “MERGE_…...

guava缓存的get方法的回调函数讲解一下
CacheBuilder.newBuilder()//设置缓存初始大小,应该合理设置,后续会扩容.initialCapacity(10)//最大值.maximumSize(100)//并发数设置.concurrencyLevel(5)//缓存过期时间,写入后10分钟过期.expireAfterWrite(600,TimeUnit.SECONDS)//统计缓存…...

React基础知识四 Hooks
什么是hooks? (coderwhy) hooks是react 16.8(2019年)出的新特性。 react有两种形式来创建组件——类式和函数式。在hooks之前类式组件就是react最主流的编程方式。 这个时候,函数式组件是非常鸡肋的,几乎没什么用。因…...

线性代数中的谱分解
一、谱分解的基本原理 谱分解(Spectral Decomposition)是线性代数中的一个重要概念,特别是在研究矩阵的特征值和特征向量时。它指的是将一个矩阵分解为其特征值和特征向量的组合,从而简化矩阵的运算和分析。谱分解通常适用于对称…...

Redis 数据结结构(一)—字符串、哈希表、列表
Redis(版本7.0)的数据结构主要包括字符串(String)、哈希表(Hash)、列表(List)、集合(Set)、有序集合(Sorted Set)、超日志(…...

【Python】用Python和Paramiko实现远程服务器自动化管理
《Python OpenCV从菜鸟到高手》带你进入图像处理与计算机视觉的大门! 解锁Python编程的无限可能:《奇妙的Python》带你漫游代码世界 在现代IT环境中,远程服务器管理已成为运维工作的常态。随着自动化运维的需求不断增加,如何高效地管理远程服务器,提升操作的灵活性和效率…...

PDF处理的创新工具:福昕低代码平台尝鲜
在当今数字化时代,PDF文件的处理和管理变得越来越重要。福昕低代码平台是新发布的一款创新的工具,旨在简化PDF处理和管理的流程。通过这个平台,用户可以通过简单的拖拽界面上的按钮,轻松完成对Cloud API的调用工作流,而…...

openstack创建浮动IP全过程
1、创建外部网络,即是provider网络,有关provider网络的详细解释请参见我之前的文章openstack中的self-service和provider网络_openstack provider网络不能创建vlan吗-CSDN博客 network create --share --external --provider-physical-network physnet1…...

nginx漏洞修复
漏洞名称:web服务器http信息头公开 解决,在以下各个监听端口加上一行,然后重启****nginx server_tokens off; 漏洞名称:默认的nginx http服务器设置 解决:请求头加上以下参数 add_header Content-Security-Policy “…...

Jackson - 序列化时更改字段名称
在这个简短的教程中,我将向您展示如何在序列化时更改字段名称以映射到另一个JSON属性。 Jackson库提供了JsonProperty注解,用于改变序列化JSON中的属性名称。 依赖项 首先,在pom.xml文件中添加以下依赖项: <dependency>…...

策略模式实战 - 猜拳游戏
**可以整体的替换一套算法,这就是策略模式。**这样对于同一个问题,可以有多种解决方案——算法实现的时候,可以通过策略模式来非常方便的进行算法的整体替换,而各种算法是独立封装好的,不用修改其内部逻辑。 具体的实…...

AWS ECS Task 添加 Prometheus 监控采集配置详细指南
以下是一篇完整的博文,介绍如何在 AWS ECS 环境中实现 JVM 监控。 AWS ECS 环境下的 JVM 监控实践 概述 在 AWS ECS (Elastic Container Service) 环境中监控 Java 应用性能是一项重要任务。本文将详细介绍如何使用 AWS Distro for OpenTelemetry (ADOT) 结合 Spring Boot …...

5. 一分钟读懂“工厂方法模式”
5.1 模式介绍 你可能会发现,简单工厂模式没在经典设计模式里出现,别急,它其实只是个常用的编程技巧,而不是标准的设计模式。简单工厂模式有三个要素:1个产品接口、n个产品类、1个工厂类,工厂类通过if/else来…...

基于 AutoFlow 快速搭建基于 TiDB 向量搜索的本地知识库问答机器人
导读 本文将详细介绍如何通过 PingCAP 开源项目 AutoFlow 实现快速搭建基于 TiDB 的本地知识库问答机器人。如果提前准备好 Docker、TiDB 环境,整个搭建过程估计在 10 分钟左右即可完成,无须开发任何代码。 文中使用一篇 TiDB 文档作为本地数据源作为示…...
C语言学习:速通指针(2)
这里要学习的有以下内容 1. const修饰指针 2. 野指针 3. assert断⾔ 4. 指针的使⽤和传址调⽤ 那么从这里开始 1. const 修饰指针 const修饰变量 首先我们知道变量是可以修改的,如果把变量的地址交给⼀个指针变量,通过指针变量的也可以修改这个变…...

windows 上ffmpeg编译好的版本选择
1. Gyan.dev Gyan.dev 是一个广受信赖的 FFmpeg 预编译库提供者,提供多种版本的 FFmpeg,包括静态和动态链接版本。 下载链接: https://www.gyan.dev/ffmpeg/builds/ 特点: 提供最新稳定版和开发版。 支持静态和共享(动态&…...

Java设计模式笔记(二)
十四、模版方法模式 1、介绍 1)模板方法模式(Template Method Pattern),又叫模板模式(Template Patern),在一个抽象类公开定义了执行它的方法的模板。它的子类可以按需重写方法实现,但调用将以抽象类中定义的方式进行。 2&…...

Vue CLI的作用
Vue CLI(Command Line Interface)是一个基于Vue.js的官方脚手架工具,其主要作用是帮助开发者快速搭建Vue项目的基础结构和开发环境。以下是Vue CLI的具体作用: 1、项目模板与快速生成 Vue CLI提供了一系列预设的项目模板&#x…...

短视频矩阵系统开发|技术源代码部署
短视频矩阵系统通过多账号运营管理、多平台视频智能分发等功能,助力企业实现视频引流、粉丝沉淀和转化。 短视频矩阵系统是一种创新的营销工具,它整合了多账号管理、视频智能分发、数据可视化等多种功能,为企业在短视频领域的发展提供了强大…...

Erlang socket编程(二)
模拟服务器和客户端通信 %%%------------------------------------------------------------------- %%% author Administrator %%% copyright (C) 2024, <COMPANY> %%% doc %%% %%% end %%% Created : 03. 12月 2024 22:28 %%%---------------------------------------…...