Flink时间语义 | 大数据技术

简单说两句

✨ 正在努力的小叮当~
💖 超级爱分享,分享各种有趣干货!
👩‍💻 提供:模拟面试 | 简历诊断 | 独家简历模板
🌈 感谢关注,关注了你就是我的超级粉丝啦!
🔒 以下内容仅对你可见~

作者:小叮当撩代码CSDN后端领域新星创作者 |阿里云专家博主

CSDN个人主页:小叮当撩代码

🔎GZH哆啦A梦撩代码

🎉欢迎关注🔎点赞👍收藏⭐️留言📝

文章目录

    • ❤️时间语义
      • 💕时间的分类
    • 💛水位线Watermark
      • ✅水位线
      • 🍏分布式环境下水位线的传播
      • 🍊代码实战
      • 🌽自定义水位线生成器
        • 🌶️周期性水位线生成器(Periodic Generator)
        • 🫑断点式水位线生成器(Punctuated Generator)
      • 🧃迟到数据处理
        • 🫖**设置窗口延迟关闭**
        • ☕️**使用侧流接收迟到的数据**

image-20240506222727961

❤️时间语义

image-20240506222754341

💕时间的分类

Flink中,时间通常分为三类

image-20240502214701589

EventTime:事件(数据)时间,是事件/数据真真正正发生时/产生时的时间

IngestionTime:摄入时间,是事件/数据到达流处理系统的时间

ProcessingTime:处理时间,是事件/数据被处理/计算时的系统的时间

image-20240502214730266

💛水位线Watermark

✅水位线

Flink的三种时间语义中,处理时间摄入时间都可以不用设置Watermark。如果我们要使用事件时间Event Time语义,以下两项配置缺一不可:

  • 使用一个时间戳为数据流中每个事件的Event Time赋值
  • 生成Watermark

​ Event Time是每个事件的元数据,如果不设置,Flink并不知道每个事件的发生时间,我们必须要为每个事件的Event Time赋值一个时间戳。

​ 有了Event Time时间戳,我们还必须生成Watermark。Watermark是Flink插入到数据流中的一种特殊的数据结构,它包含一个时间戳,并假设后续不会有小于该时间戳的数据。下图展示了一个乱序数据流,其中方框是单个事件,方框中的数字是其对应的Event Time时间戳,圆圈为Watermark,圆圈中的数字为Watermark对应的时间戳。

一个包含Watermark的乱序数据流

image-20240502233750045

Watermark = 当前最大的事件时间 - 最大允许的延迟时间(或最大允许的乱序度时间)

Watermark 是一个单独计算出来的时间戳
Watermark可以通过改变窗口的触发时机 在 一定程度上解决数据乱序或延迟达到的问题
Watermark >= 窗口结束时间 时 就会触发窗口计算(窗口中得有数据)
延迟或乱序严重的数据还是丢失, 但是可以通过调大最大允许的延迟时间(乱序度) 来解决, 或 使用侧道输出流来单独收集延迟或乱序严重的数据,保证数据不丢失!

🍏分布式环境下水位线的传播

在多并行度下,每个并行有一个水印

比如并行度是6,那么程序中就有6个watermark

分别属于这6个并行度(线程)

那么,触发条件以6个水印中最小的那个为准

平时测试水位线强烈建议将并行度设为1

🍊代码实战

需求

实时模拟生成订单数据,格式为: (订单ID,用户ID,时间戳/事件时间,订单金额)

要求每隔5s,计算5秒内,每个用户的订单总金额

并添加Watermark来解决一定程度上的数据延迟和数据乱序问题。

我们循序渐进先写一版没有Watermark的

代码清单


import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.flink.api.common.RuntimeExecutionMode;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.datastream.KeyedStream;
import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.functions.source.SourceFunction;
import org.apache.flink.streaming.api.windowing.assigners.TumblingProcessingTimeWindows;
import org.apache.flink.streaming.api.windowing.time.Time;

import java.text.SimpleDateFormat;
import java.util.Random;
import java.util.UUID;

/**
 * @author tiancx
 */
public class WatermarkDemo {


    @Data  // set get toString
    @AllArgsConstructor
    @NoArgsConstructor
    public static class OrderInfo {
        //格式化的时间
        private String time;
        private String orderId;
        private int uid;
        private int money;
        private long timeStamp;
    }

    public static class MySource implements SourceFunction<OrderInfo> {
        boolean flag = true;

        @Override
        public void run(SourceFunction.SourceContext ctx) throws Exception {
            // 源源不断的产生数据
            Random random = new Random();
            while (flag) {
                OrderInfo orderInfo = new OrderInfo();
                orderInfo.setOrderId(UUID.randomUUID().toString());
                orderInfo.setUid(random.nextInt(3));
                orderInfo.setMoney(random.nextInt(101));
                orderInfo.setTimeStamp(System.currentTimeMillis());
                long timeStamp = orderInfo.getTimeStamp();
                //转成yyyy-MM-dd HH:mm:ss
                String format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(timeStamp);
                orderInfo.setTime(format);
                System.out.println("数据:" + orderInfo);
                ctx.collect(orderInfo);
                Thread.sleep(1000);// 间隔1s
            }
        }

        // source 停止之前需要干点啥
        @Override
        public void cancel() {
            flag = false;
        }
    }

    public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setRuntimeMode(RuntimeExecutionMode.AUTOMATIC);
        //加载数据
        DataStreamSource<OrderInfo> source = env.addSource(new MySource());
        //keyby分组
        KeyedStream<OrderInfo, Integer> keyBy = source.keyBy(OrderInfo::getUid);
        //开窗计算(滚动窗口)
        SingleOutputStreamOperator<OrderInfo> sum = keyBy.window(TumblingProcessingTimeWindows.of(Time.seconds(5)))
                .sum("money");
        sum.print();
        env.execute();
    }


}

我们再写一版有水位线的

代码清单


import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.commons.lang.time.DateFormatUtils;
import org.apache.flink.api.common.RuntimeExecutionMode;
import org.apache.flink.api.common.eventtime.SerializableTimestampAssigner;
import org.apache.flink.api.common.eventtime.WatermarkStrategy;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.datastream.KeyedStream;
import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.functions.source.SourceFunction;
import org.apache.flink.streaming.api.functions.windowing.WindowFunction;
import org.apache.flink.streaming.api.windowing.assigners.TumblingEventTimeWindows;
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 java.text.SimpleDateFormat;
import java.time.Duration;
import java.util.Random;
import java.util.UUID;

/**
 * @author tiancx
 */
public class WatermarkDemo {


    @Data  // set get toString
    @AllArgsConstructor
    @NoArgsConstructor
    public static class OrderInfo {
        //格式化的时间
        private String time;
        private String orderId;
        private int uid;
        private int money;
        private long timeStamp;
    }

    public static class MySource implements SourceFunction<OrderInfo> {
        boolean flag = true;

        @Override
        public void run(SourceFunction.SourceContext ctx) throws Exception {
            // 源源不断的产生数据
            Random random = new Random();
            while (flag) {
                OrderInfo orderInfo = new OrderInfo();
                orderInfo.setOrderId(UUID.randomUUID().toString());
                orderInfo.setUid(random.nextInt(3));
                orderInfo.setMoney(random.nextInt(101));
                orderInfo.setTimeStamp(System.currentTimeMillis() - 1000 * 2);
                long timeStamp = orderInfo.getTimeStamp();
                //转成yyyy-MM-dd HH:mm:ss
                String format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(timeStamp);
                orderInfo.setTime(format);
//                System.out.println("数据:" + orderInfo);
                ctx.collect(orderInfo);
                Thread.sleep(1000);// 间隔1s
            }
        }

        // source 停止之前需要干点啥
        @Override
        public void cancel() {
            flag = false;
        }
    }

    public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setRuntimeMode(RuntimeExecutionMode.AUTOMATIC);
        env.setParallelism(1);
        //加载数据
        DataStreamSource<OrderInfo> source = env.addSource(new MySource());
        // 在转换算子之前,加载数据之后,添加水印
        // 添加使用event以及watermark进行操作
        SingleOutputStreamOperator<OrderInfo> watermarks = source.assignTimestampsAndWatermarks(
                WatermarkStrategy.<OrderInfo>forBoundedOutOfOrderness(Duration.ofSeconds(3))
                        .withTimestampAssigner(new SerializableTimestampAssigner<OrderInfo>() {
                            @Override
                            public long extractTimestamp(OrderInfo element, long recordTimestamp) {
                                System.out.println("数据:" + element + "系统时间:" + recordTimestamp);
                                return element.getTimeStamp();
                            }
                        }));
        //keyby分组
        KeyedStream<OrderInfo, Integer> keyBy = watermarks.keyBy(OrderInfo::getUid);
        //开窗计算(滚动窗口)
        SingleOutputStreamOperator<String> sum = keyBy.window(TumblingEventTimeWindows.of(Time.seconds(5)))
                .apply(new WindowFunction<OrderInfo, String, Integer, TimeWindow>() {
                    @Override
                    public void apply(Integer key, TimeWindow window, Iterable<OrderInfo> input, Collector<String> out) throws Exception {
                        String startTime = DateFormatUtils.format(window.getStart(), "yyyy-MM-dd HH:mm:ss");
                        String endTime = DateFormatUtils.format(window.getEnd(), "yyyy-MM-dd HH:mm:ss");
                        String waterTime = DateFormatUtils.format(window.maxTimestamp(), "yyyy-MM-dd HH:mm:ss");
                        int sumMoney = 0;
                        for (OrderInfo orderInfo : input) {
                            sumMoney += orderInfo.getMoney();
                        }
                        out.collect("uid=" + key + ",starttime=" + startTime + ",endTime=" + endTime + ",totalMoney=" + sumMoney);
                    }
                });
        sum.print("窗口计算:");
        env.execute();
    }

我们看下运行结果

image-20240504165256836

🌽自定义水位线生成器

我们上面使用的是Flink帮我们内置的

我们还可以使用自定义水位线生成器

🌶️周期性水位线生成器(Periodic Generator)

假如我们想周期性地生成Watermark,这个周期是可以设置的,默认情况下是每200毫秒生成一个Watermark,或者说Flink每200毫秒调用一次生成Watermark的方法。我们可以在执行环境中设置这个周期:

env.getConfig.setAutoWatermarkInterval(1000L)

使用方式

DataStream<MyType> stream = ...

DataStream<MyType> withTimestampsAndWatermarks = stream
        .assignTimestampsAndWatermarks(
            WatermarkStrategy
                .forGenerator(...)
                .withTimestampAssigner(...)
        );

代码清单

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.commons.lang.time.DateFormatUtils;
import org.apache.flink.api.common.RuntimeExecutionMode;
import org.apache.flink.api.common.eventtime.*;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.datastream.KeyedStream;
import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.functions.source.SourceFunction;
import org.apache.flink.streaming.api.functions.windowing.WindowFunction;
import org.apache.flink.streaming.api.windowing.assigners.TumblingEventTimeWindows;
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 java.text.SimpleDateFormat;
import java.util.Random;
import java.util.UUID;

/**
 * @author tiancx
 */
public class WatermarkDemo {


    @Data  // set get toString
    @AllArgsConstructor
    @NoArgsConstructor
    public static class OrderInfo {
        //格式化的时间
        private String time;
        private String orderId;
        private int uid;
        private int money;
        private long timeStamp;
    }

    public static class MySource implements SourceFunction<OrderInfo> {
        boolean flag = true;

        @Override
        public void run(SourceFunction.SourceContext ctx) throws Exception {
            // 源源不断的产生数据
            Random random = new Random();
            while (flag) {
                OrderInfo orderInfo = new OrderInfo();
                orderInfo.setOrderId(UUID.randomUUID().toString());
                orderInfo.setUid(random.nextInt(3));
                orderInfo.setMoney(random.nextInt(101));
                orderInfo.setTimeStamp(System.currentTimeMillis() - 1000 * 2);
                long timeStamp = orderInfo.getTimeStamp();
                //转成yyyy-MM-dd HH:mm:ss
                String format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(timeStamp);
                orderInfo.setTime(format);
//                System.out.println("数据:" + orderInfo);
                ctx.collect(orderInfo);
                Thread.sleep(1000);// 间隔1s
            }
        }

        // source 停止之前需要干点啥
        @Override
        public void cancel() {
            flag = false;
        }
    }

    public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setRuntimeMode(RuntimeExecutionMode.AUTOMATIC);
        env.setParallelism(1);
        //加载数据
        DataStreamSource<OrderInfo> source = env.addSource(new MySource());
        // 在转换算子之前,加载数据之后,添加水印
        // 添加使用event以及watermark进行操作
        SingleOutputStreamOperator<OrderInfo> watermarks = source.assignTimestampsAndWatermarks(
                WatermarkStrategy.forGenerator(x -> new MyPeriodicGenerator())
                        .withTimestampAssigner(new SerializableTimestampAssigner<OrderInfo>() {
                            @Override
                            public long extractTimestamp(OrderInfo element, long recordTimestamp) {
                                System.out.println("数据:" + element + "系统时间:" + recordTimestamp);
                                return element.getTimeStamp();
                            }
                        }));
        //keyby分组
        KeyedStream<OrderInfo, Integer> keyBy = watermarks.keyBy(OrderInfo::getUid);
        //开窗计算(滚动窗口)
        SingleOutputStreamOperator<String> sum = keyBy.window(TumblingEventTimeWindows.of(Time.seconds(5)))
                .apply(new WindowFunction<OrderInfo, String, Integer, TimeWindow>() {
                    @Override
                    public void apply(Integer key, TimeWindow window, Iterable<OrderInfo> input, Collector<String> out) throws Exception {
                        String startTime = DateFormatUtils.format(window.getStart(), "yyyy-MM-dd HH:mm:ss");
                        String endTime = DateFormatUtils.format(window.getEnd(), "yyyy-MM-dd HH:mm:ss");
                        String waterTime = DateFormatUtils.format(window.maxTimestamp(), "yyyy-MM-dd HH:mm:ss");
                        int sumMoney = 0;
                        for (OrderInfo orderInfo : input) {
                            sumMoney += orderInfo.getMoney();
                        }
                        out.collect("uid=" + key + ",starttime=" + startTime + ",endTime=" + endTime + ",totalMoney=" + sumMoney);
                    }
                });
        sum.print("窗口计算:");
        env.execute();
    }

    public static class MyPeriodicGenerator implements WatermarkGenerator<OrderInfo> {
        private long maxOutOfOrderness = 3000; // 3 seconds
        private long currentMaxTimestamp;

        @Override
        public void onEvent(OrderInfo event, long eventTimestamp, WatermarkOutput output) {
            // 更新currentMaxTimestamp为当前遇到的最大值
            currentMaxTimestamp = Math.max(currentMaxTimestamp, eventTimestamp);
        }

        @Override
        public void onPeriodicEmit(WatermarkOutput output) {
            // Watermark比currentMaxTimestamp最大值慢3秒
            output.emitWatermark(new Watermark(currentMaxTimestamp - maxOutOfOrderness));
        }
    }


}
🫑断点式水位线生成器(Punctuated Generator)

断点式生成器会不停地检测 onEvent()中的事件,当发现带有水位线信息的事件时,就立

即发出水位线。我们把发射水位线的逻辑写在 onEvent 方法当中即可。

🧃迟到数据处理

waterMark和Window机制解决了流式数据的乱序问题,对于因为延迟而顺序有误的数据,可以根据eventTime进行业务处理,对于延迟的数据Flink也有自己的解决办法:

主要的办法是给定一个允许延迟的时间,在该时间范围内仍可以接受处理延迟数据

设置允许延迟的时间是通过allowedLateness(lateness: Time)设置

保存延迟数据则是通过sideOutputLateData(outputTag: OutputTag[T])保存

获取延迟数据是通过DataStream.getSideOutput(tag: OutputTag[X])获取

🫖设置窗口延迟关闭

​ Flink 的窗口,也允许迟到数据。当触发了窗口计算后,会先计算当前的结果,但是此时并不会关闭窗口。

​ 以后每来一条迟到数据,就触发一次这条数据所在窗口计算(增量计算)。直到wartermark 超过了窗口结束时间+推迟时间,此时窗口会真正关闭。

.window(TumblingEventTimeWindows.of(Time.seconds(5)))

.allowedLateness(Time.seconds(3))

【Tips】: 延迟关闭只能用到event time上

☕️使用侧流接收迟到的数据

侧输出机制:可以将错过水印又错过allowedLateness允许的时间的数据,单独的存放到一个DataStream中,然后开发人员可以自定逻辑对这些超级迟到数据进行处理。

处理主要使用两个方式:

对窗口对象调用sideOutputLateData(OutputTag outputTag)方法,将数据存储到一个地方

对DataStream对象调用getSideOutput(OutputTag outputTag)方法,取出这些被单独处理的数据的DataStream

.windowAll(TumblingEventTimeWindows.of(Time.seconds(5)))

.allowedLateness(Time.seconds(3))

.sideOutputLateData(lateWS)

【都看到这了,点点赞点点关注呗,爱你们】😚😚

蓝白色微信公众号大学生校园清新简单纸飞机动态引导关注简洁新媒体分享中文动态引导关注

💬

✨ 正在努力的小叮当~
💖 超级爱分享,分享各种有趣干货!
👩‍💻 提供:模拟面试 | 简历诊断 | 独家简历模板
🌈 感谢关注,关注了你就是我的超级粉丝啦!
🔒 以下内容仅对你可见~

作者:小叮当撩代码CSDN后端领域新星创作者 |阿里云专家博主

CSDN个人主页:小叮当撩代码

🔎GZH哆啦A梦撩代码

🎉欢迎关注🔎点赞👍收藏⭐️留言📝

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

SpringBoot+Vue+Element-UI实现学生综合成绩测评系统

前言介绍 学生成绩是高校人才培养计划的重要组成部分&#xff0c;是实现人才培养目标、培养学生科研能力与创新思维、检验学生综合素质与实践能力的重要手段与综合性实践教学环节。而学生所在学院多采用半手工管理学生成绩的方式&#xff0c;所以有必要开发学生综合成绩测评系…

校园寄取快递代拿小程序源码系统 带完整的安装代码包以及搭建教程

在数字化快速发展的今天&#xff0c;校园生活也在不断地与时俱进&#xff0c;向着更加便捷、高效的方向迈进。为了满足学生们对于快递寄取代拿的便捷需求&#xff0c;小编给大家分享一款校园寄取快递代拿小程序源码系统&#xff0c;该系统不仅提供了完整的安装代码包&#xff0…

矩池云jupyter运行opengait代码 未完成版

文章目录 前言——矩池云的使用技巧1.切换源 一、下载数据集二、下载模型三、环境配置1.查看python、torch、torchvision版本2.查看一些包版本是否过高3.下载包 四、开始训练1.设置环境变量2.遇到的问题&#xff08;1&#xff09;torch.cuda.is_available()返回false&#xff0…

python绘图(pandas)

matplotlib绘图 import pandas as pd abs_path rF:\Python\learn\python附件\pythonCsv\data.csv df pd.read_csv(abs_path, encodinggbk) # apply根据多列生成新的一个列的操作&#xff0c;用apply df[new_score] df.apply(lambda x : x.数学 x.语文, axis1)# 最后几行 …

接口自动化测试拓展:接口Mock的理念与实战场景!

接口自动化测试是软件开发过程中不可或缺的一环。在实际开发中&#xff0c;我们常常会遇到需要依赖其他模块的接口或者服务来完成测试的情况。而在开发初期或者接口尚未完成的情况下&#xff0c;就需要使用接口Mock来模拟未实现的接口功能。接口Mock是一种模拟接口行为的技术&a…

基于树的时间序列预测(LGBM)

在大多数时间序列预测中&#xff0c;尽管有Prophet和NeuralProphet等方便的工具&#xff0c;但是了解基于树的模型仍然具有很高的价值。尤其是在监督学习模型中&#xff0c;仅仅使用单变量时间序列似乎信息有限&#xff0c;预测也比较困难。因此&#xff0c;为了生成足够的特征…

每日两题 / 24. 两两交换链表中的节点 25. K 个一组翻转链表(LeetCode热题100)

24. 两两交换链表中的节点 - 力扣&#xff08;LeetCode&#xff09; 定义三个指针&#xff0c;交换前先保存ntnt指针为next->next&#xff0c;cur和next两个节点&#xff0c;然后将pre->next指向next 若pre为空&#xff0c;说明当前交换的节点为头两个节点&#xff0c;…

《Python编程从入门到实践》day20

#尝试在python3.11文件夹和pycharm中site-packages文件夹中安装&#xff0c;最终在scripts文件夹中新建py文件成功导入pygame运行程序 #今日知识点学习 import sysimport pygameclass AlienInvasion:"""管理游戏资源和行为的类"""def __init__(…

动态规划——背包问题(01,完全,多重)

一、01背包问题 1.题目描述 有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。第 i 件物品的体积是 vi&#xff0c;价值是 wi。 求解将哪些物品装入背包&#xff0c;可使这些物品的总体积不超过背包容量&#xff0c;且总价值最大。输出最大价值。 01背包问题特点&…

数据分析之Tebleau可视化:树状图、日历图、气泡图

树状图&#xff08;适合子分类比较多的&#xff09; 1.基本树状图的绘制 同时选择产品子分类和销售金额----选择智能推荐----选择树状图 2.双层树状图的绘制 将第二个维度地区拖到产品分类的下面---大的划分区域是上面的维度&#xff08;产品分类&#xff09;&#xff0c;看着…

cmake进阶:文件操作

一. 简介 前面几篇文章学习了 cmake的文件操作&#xff0c;写文件&#xff0c;读文件。文章如下&#xff1a; cmake进阶&#xff1a;文件操作之写文件-CSDN博客 cmake进阶&#xff1a;文件操作之读文件-CSDN博客 本文继续学习文件操作。主要学习 文件重命名&#xff0c;删…

C++引用2

什么是引用变量&#xff1f; 引用实际上是已定义变量的别名&#xff0c;使一个变量拥有多个名字 c给&#xff06;符号赋予了另一个意义&#xff0c;将其用来声明引用 int a9;int&ba; 此时b成为a的一个别名&#xff0c;a就是b,b就是a.它们均指向同一片内存 int a99; in…

虚拟键代码

虚拟键代码 虚拟键码 (Winuser.h) - Win32 apps | Microsoft Learn 在Windows操作系统中&#xff0c;虚拟键代码&#xff08;Virtual-Key Codes&#xff09;是一组用来表示键盘上按键的数值。这些代码通常用于Windows API函数&#xff0c;以便程序能够识别和处理键盘输入。 虚拟…

OSEK任务管理

1 前言 RTOS通过任务&#xff08;task&#xff09;来组织应用层程序框架&#xff08;framework&#xff09;&#xff0c;支持任务的并发和同步执行&#xff08;concurrent and asynchronous execution of tasks&#xff09;&#xff0c;并通过调度器&#xff08;scheduler&…

[方法] Unity 实现仿《原神》第三人称跟随相机 v1.1

参考网址&#xff1a;【Unity中文课堂】RPG战斗系统Plus 在Unity游戏引擎中&#xff0c;实现类似《原神》的第三人称跟随相机并非易事&#xff0c;但幸运的是&#xff0c;Unity为我们提供了强大的工具集&#xff0c;其中Cinemachine插件便是实现这一目标的重要工具。Cinemachi…

超分辨率重建——BSRN网络训练自己数据集并推理测试(详细图文教程)

目录 一、BSRN网络总结二、源码包准备三、环境准备3.1 报错KeyError: "No object named BSRN found in arch registry!"3.2 安装basicsr源码包3.3 参考环境 四、数据集准备五、训练5.1 配置文件参数修改5.2 启动训练5.2.1 命令方式训练5.2.2 配置Configuration方式训…

vivado UltraScale 比特流设置

下表所示 UltraScale ™ 器件的器件配置设置可搭配 set_property <Setting> <Value> [current_design] Vivado 工具 Tcl 命令一起使用。

翻译《The Old New Thing》 - What’s the point of DeferWindowPos?

Whats the point of DeferWindowPos? - The Old New Thing (microsoft.com)https://devblogs.microsoft.com/oldnewthing/20050706-26/?p35023 Raymond Chen 在 2005年7月6日 DeferWindowPos 的作用是什么&#xff1f; 简要 文章讨论了 DeferWindowPos 函数的用途&#xff…

LangChain框架学习总结

目录 一、简介 二、概念 三、组件具体介绍 3.1 Models 3.1.1 LLMs 3.1.2 Chat Models 3.1.3 Text Embedding Modesl 3.1.4 总结 3.2 Prompts 3.2.1 LLM Prompt Template 3.2.1.1 自定义PromptTemplate 3.2.1.2 partial PromptTemplate 3.2.1.3 序列化PromptTemplat…

IMEI引起的无法驻网问题

这篇内容没什么意思&#xff0c;仅仅是做个简单记录。 问题不复杂&#xff0c;场景很简单&#xff0c;如上图&#xff0c;UE在进行LTE attach过程时&#xff0c;在送完NAS security mode complete后&#xff0c;就立刻收到了网络attach reject 带cause 6 Illegal ME&#xff0c…