Flink Window分析及Watermark解决乱序数据机制深入剖析-Flink牛刀小试

本文主要是介绍Flink Window分析及Watermark解决乱序数据机制深入剖析-Flink牛刀小试,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

版权声明:本套技术专栏是作者(秦凯新)平时工作的总结和升华,通过从真实商业环境抽取案例进行总结和分享,并给出商业应用的调优建议和集群环境容量规划等内容,请持续关注本套博客。版权声明:禁止转载,欢迎学习。QQ邮箱地址:1120746959@qq.com,如有任何问题,可随时联系。
在这里插入图片描述

本文决心讲清楚这个纠结的水印Watermark问题,Come on !

1 The Time

针对stream数据中的时间,可以分为以下三种:

  • Event Time:事件产生的时间,它通常由事件中的时间戳描述。
  • Ingestion time:事件进入Flink的时间
  • Processing Time:事件被处理时当前系统的时间

  • Flink中,默认Time类似是ProcessingTime ,可以在代码中设置:

1.1 tips(请认真思考下面的话,绝对震聋发溃!)
  • 在水印的处理中,我们一般取事件时间序列的最大值作为水印的生成时间参考。

  • 按照信号发生的顺序,时间是不断增加的,所以在时间序列上若出现事件时间小于时间序列最大值,一般都是延时的事件,时间序列最大值不会改变。

  • 每处理一条事件数据,watermark时间就生成一次,后面窗的触发就是依据水印时间。若设置乱序延时为10s,则生成规则就是:

     final Long maxOutOfOrderness = 10000L;// 最大允许的乱序时间是10snew Watermark(currentMaxTimestamp - maxOutOfOrderness)
    
  • 数据会按照时间进行依次Append,

  • 水印依赖的条件是窗,水印只是决定了窗的触发时间,若设置最大允许的乱序时间是maxOutOfOrderness=10s,则窗的触发时机就是:

      watermark 时间 >= window_end_time
    
  • 窗触发时,数据除了正常的时间序列,同时也包含延时到达的序列。在窗触发前(也就水印时间),计算除了把之前的正常窗数据给触发了,同时还包含了本来也属于该窗的延时数据。

2 窗与水印的世纪谜题

  • 事件时间的最大值,也就是当前的实际事件时间,因此需要以此为参考点。
  • 实际窗:意思就是数据就在那里Append,窗数据已经准备好,等待触发时机。
  • 水印时间不受影响:就是每次来的数据的事件时间最大值,不受延迟数据时间影响。
  • 下面例子中,等水印时间为10:11:33时,满足时间窗 10:11:30 <-> 10:11:33的触发时机,此时需要处理的数据不仅包含正常数据10:11:32 ,同时还包含乱序数据10:11:31。
  • 再次强调:窗时机到来时,会遍历乱序数据和原窗数据。
  • 实际窗在流动,只是暂不触发。
  • 水印也在标记流动
  • 窗时机触发也在流动。
  • watermark 时间 >= window_end_time时,触发历史窗执行。

3 EventTime和Watermarks 水位线理论碰撞

  • 流处理从事件产生,到流经source,再到operator,中间是有一个过程和时间的。虽然大部分情况下,流到operator的数据都是按照事件产生的时间顺序来的,但是也不排除由于网络延迟等原因,导致乱序的产生,特别是使用kafka的话,多个分区的数据无法保证有序。所以在进行window计算的时候,我们又不能无限期的等下去,必须要有个机制来保证一个特定的时间后,必须触发window去进行计算了。这个特别的机制,就是watermark,watermark是用于处理乱序事件的。

  • 通常,在接收到source的数据后,应该立刻生成watermark;但是,也可以在source后,应用简单的map或者filter操作后,再生成watermark。注意:如果指定多次watermark,后面指定的会覆盖前面的值。
    生成方式

  • With Periodic Watermarks

      周期性的触发watermark的生成和发送,默认是100ms,每隔N秒自动向流里注入一个WATERMARK 时间间隔由ExecutionConfig.setAutoWatermarkInterval 决定. 每次调用getCurrentWatermark 方法, 如果得到的WATERMARK不为空并且比之前的大就注入流中 可以定义一个最大允许乱序的时间,这种比较常用实现AssignerWithPeriodicWatermarks接口
    
  • With Punctuated Watermarks

      基于某些事件触发watermark的生成和发送基于事件向流里注入一个WATERMARK,每一个元素都有机会判断是否生成一个WATERMARK. 如果得到的WATERMARK不为空并且比之前的大就注入流中实现AssignerWithPunctuatedWatermarks接口
    
  • 多并行度流的watermarks

    注意:多并行度的情况下,watermark对齐会取所有channel最小的watermark。

4 With Periodic Watermarks案例实战

4.1 最朴实的水印方案(基于事件序列最大值)

 public class StreamingWindowWatermark {public static void main(String[] args) throws Exception {//定义socket的端口号int port = 9010;//获取运行环境StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();//设置使用eventtime,默认是使用processtimeenv.setStreamTimeCharacteristic(TimeCharacteristic.EventTime);//设置并行度为1,默认并行度是当前机器的cpu数量env.setParallelism(1);//连接socket获取输入的数据DataStream<String> text = env.socketTextStream("SparkMaster", port, "\n");//解析输入的数据DataStream<Tuple2<String, Long>> inputMap = text.map(new MapFunction<String, Tuple2<String, Long>>() {@Overridepublic Tuple2<String, Long> map(String value) throws Exception {String[] arr = value.split(",");return new Tuple2<>(arr[0], Long.parseLong(arr[1]));}});//抽取timestamp和生成watermarkDataStream<Tuple2<String, Long>> waterMarkStream = inputMap.assignTimestampsAndWatermarks(new AssignerWithPeriodicWatermarks<Tuple2<String, Long>>() {Long currentMaxTimestamp = 0L;final Long maxOutOfOrderness = 10000L;// 最大允许的乱序时间是10sSimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");@Nullable@Overridepublic Watermark getCurrentWatermark() {return new Watermark(currentMaxTimestamp - maxOutOfOrderness);}//定义如何提取timestamp@Overridepublic long extractTimestamp(Tuple2<String, Long> element, long previousElementTimestamp) {long timestamp = element.f1;currentMaxTimestamp = Math.max(timestamp, currentMaxTimestamp);long id = Thread.currentThread().getId();System.out.println("作者:秦凯新 键值 :"+element.f0+",事件事件:[ "+sdf.format(element.f1)+" ],currentMaxTimestamp:[ "+sdf.format(currentMaxTimestamp)+" ],水印时间:[ "+sdf.format(getCurrentWatermark().getTimestamp())+" ]");return timestamp;}});DataStream<String> window = waterMarkStream.keyBy(0).window(TumblingEventTimeWindows.of(Time.seconds(3)))//按照消息的EventTime分配窗口,和调用TimeWindow效果一样.apply(new WindowFunction<Tuple2<String, Long>, String, Tuple, TimeWindow>() {/*** 对window内的数据进行排序,保证数据的顺序* @param tuple* @param window* @param input* @param out* @throws Exception*/@Overridepublic void apply(Tuple tuple, TimeWindow window, Iterable<Tuple2<String, Long>> input, Collector<String> out) throws Exception {String key = tuple.toString();List<Long> arrarList = new ArrayList<Long>();Iterator<Tuple2<String, Long>> it = input.iterator();while (it.hasNext()) {Tuple2<String, Long> next = it.next();arrarList.add(next.f1);}Collections.sort(arrarList);SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");String result = "\n 作者:秦凯新 键值 : "+ key + "\n              触发窗内数据个数 : " + arrarList.size() + "\n              触发窗起始数据: " + sdf.format(arrarList.get(0)) + "\n              触发窗最后(可能是延时)数据:" + sdf.format(arrarList.get(arrarList.size() - 1))+ "\n              实际窗起始和结束时间: " + sdf.format(window.getStart()) + "《----》" + sdf.format(window.getEnd()) + " \n \n ";out.collect(result);}});//测试-把结果打印到控制台即可window.print();//注意:因为flink是懒加载的,所以必须调用execute方法,上面的代码才会执行env.execute("eventtime-watermark");}
}
  • 执行测试数据

     0001,1538359882000		2018-10-01 10:11:220002,1538359886000		2018-10-01 10:11:260003,1538359892000		2018-10-01 10:11:320004,1538359893000		2018-10-01 10:11:330005,1538359894000		2018-10-01 10:11:340006,1538359896000		2018-10-01 10:11:360007,1538359897000		2018-10-01 10:11:370008,1538359899000		2018-10-01 10:11:390009,1538359891000		2018-10-01 10:11:310010,1538359903000		2018-10-01 10:11:430011,1538359892000		2018-10-01 10:11:320012,1538359891000		2018-10-01 10:11:310010,1538359906000		2018-10-01 10:11:46
    

第一个窗触发:2018-10-01 10:11:21.000《----》2018-10-01 10:11:24.000

第二个窗触发:2018-10-01 10:11:24.000《----》2018-10-01 10:11:27.000

第三个窗触发:2018-10-01 10:11:30.000《----》2018-10-01 10:11:33.000

第四个窗触发:10:11:33.000《----》2018-10-01 10:11:36.000

4.2 最霸道的水印设计(allowedLateness与OutputLateData)

  • 在某些情况下, 我们希望对迟到的数据再提供一个宽容的时间。
    Flink 提供了 allowedLateness 方法可以实现对迟到的数据设置一个延迟时间, 在指定延迟时间内到达的数据还是可以触发 window 执行的。

  • 第二次(或多次)触发的条件是 watermark < window_end_time + allowedLateness 时间内,
    这个窗口有 late 数据到达时。

  • 举例:当 watermark 等于 10:11:34 的时候, 我们输入 eventtime 为 10:11:30、 10:11:31、10:11:32 的数据的时候, 是可以触发的, 因为这些数据的 window_end_time 都是 10:11:33, 也就是10:11:34<10:11:33+2 为 true。

  • 举例:但是当 watermark 等于 10:11:35 的时候,我们再输入 eventtime 为 10:11:30、10:11:31、10:11:32的数据的时候, 这些数据的 window_end_time 都是 10:11:33, 此时, 10:11:35< 10:11:33+2 为false 了。 所以最终这些数据迟到的时间太久了, 就不会再触发 window 执行了,预示着丢弃。

  • 同时注意,对于延迟的数据,我们完全可以把它揪出来作分析。通过设置sideOutputLateData。

      public class StreamingWindowWatermark2 {public static void main(String[] args) throws Exception {//定义socket的端口号int port = 9000;//获取运行环境StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();//设置使用eventtime,默认是使用processtimeenv.setStreamTimeCharacteristic(TimeCharacteristic.EventTime);//设置并行度为1,默认并行度是当前机器的cpu数量env.setParallelism(1);//连接socket获取输入的数据DataStream<String> text = env.socketTextStream("hadoop100", port, "\n");//解析输入的数据DataStream<Tuple2<String, Long>> inputMap = text.map(new MapFunction<String, Tuple2<String, Long>>() {@Overridepublic Tuple2<String, Long> map(String value) throws Exception {String[] arr = value.split(",");return new Tuple2<>(arr[0], Long.parseLong(arr[1]));}});//抽取timestamp和生成watermarkDataStream<Tuple2<String, Long>> waterMarkStream = inputMap.assignTimestampsAndWatermarks(new AssignerWithPeriodicWatermarks<Tuple2<String, Long>>() {Long currentMaxTimestamp = 0L;final Long maxOutOfOrderness = 10000L;// 最大允许的乱序时间是10sSimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");/*** 定义生成watermark的逻辑* 默认100ms被调用一次*/@Nullable@Overridepublic Watermark getCurrentWatermark() {return new Watermark(currentMaxTimestamp - maxOutOfOrderness);}//定义如何提取timestamp@Overridepublic long extractTimestamp(Tuple2<String, Long> element, long previousElementTimestamp) {long timestamp = element.f1;currentMaxTimestamp = Math.max(timestamp, currentMaxTimestamp);System.out.println("key:"+element.f0+",eventtime:["+element.f1+"|"+sdf.format(element.f1)+"],currentMaxTimestamp:["+currentMaxTimestamp+"|"+sdf.format(currentMaxTimestamp)+"],watermark:["+getCurrentWatermark().getTimestamp()+"|"+sdf.format(getCurrentWatermark().getTimestamp())+"]");return timestamp;}});//保存被丢弃的数据OutputTag<Tuple2<String, Long>> outputTag = new OutputTag<Tuple2<String, Long>>("late-data"){};//注意,由于getSideOutput方法是SingleOutputStreamOperator子类中的特有方法,所以这里的类型,不能使用它的父类dataStream。SingleOutputStreamOperator<String> window = waterMarkStream.keyBy(0).window(TumblingEventTimeWindows.of(Time.seconds(3)))//按照消息的EventTime分配窗口,和调用TimeWindow效果一样//.allowedLateness(Time.seconds(2))//允许数据迟到2秒.sideOutputLateData(outputTag).apply(new WindowFunction<Tuple2<String, Long>, String, Tuple, TimeWindow>() {/*** 对window内的数据进行排序,保证数据的顺序* @param tuple* @param window* @param input* @param out* @throws Exception*/@Overridepublic void apply(Tuple tuple, TimeWindow window, Iterable<Tuple2<String, Long>> input, Collector<String> out) throws Exception {String key = tuple.toString();List<Long> arrarList = new ArrayList<Long>();Iterator<Tuple2<String, Long>> it = input.iterator();while (it.hasNext()) {Tuple2<String, Long> next = it.next();arrarList.add(next.f1);}Collections.sort(arrarList);SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");String result = key + "," + arrarList.size() + "," + sdf.format(arrarList.get(0)) + "," + sdf.format(arrarList.get(arrarList.size() - 1))+ "," + sdf.format(window.getStart()) + "," + sdf.format(window.getEnd());out.collect(result);}});//把迟到的数据暂时打印到控制台,实际中可以保存到其他存储介质中DataStream<Tuple2<String, Long>> sideOutput = window.getSideOutput(outputTag);sideOutput.print();//测试-把结果打印到控制台即可window.print();//注意:因为flink是懒加载的,所以必须调用execute方法,上面的代码才会执行env.execute("eventtime-watermark");}}
    

4.3 多并行度下的 watermark触发机制

4.3.1 先领会代码(感谢 github xuwei)
    import org.apache.flink.api.common.functions.MapFunction;import org.apache.flink.api.java.tuple.Tuple;import org.apache.flink.api.java.tuple.Tuple2;import org.apache.flink.streaming.api.TimeCharacteristic;import org.apache.flink.streaming.api.datastream.DataStream;import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;import org.apache.flink.streaming.api.functions.AssignerWithPeriodicWatermarks;import org.apache.flink.streaming.api.functions.windowing.WindowFunction;import org.apache.flink.streaming.api.watermark.Watermark;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 org.apache.flink.util.OutputTag;import javax.annotation.Nullable;import java.text.SimpleDateFormat;import java.util.ArrayList;import java.util.Collections;import java.util.Iterator;import java.util.List;public class StreamingWindowWatermark2 {public static void main(String[] args) throws Exception {//定义socket的端口号int port = 9010;//获取运行环境StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();//设置使用eventtime,默认是使用processtimeenv.setStreamTimeCharacteristic(TimeCharacteristic.EventTime);//设置并行度为1,默认并行度是当前机器的cpu数量env.setParallelism(8);//连接socket获取输入的数据DataStream<String> text = env.socketTextStream("SparkMaster", port, "\n");//解析输入的数据DataStream<Tuple2<String, Long>> inputMap = text.map(new MapFunction<String, Tuple2<String, Long>>() {@Overridepublic Tuple2<String, Long> map(String value) throws Exception {String[] arr = value.split(",");return new Tuple2<>(arr[0], Long.parseLong(arr[1]));}});//抽取timestamp和生成watermarkDataStream<Tuple2<String, Long>> waterMarkStream = inputMap.assignTimestampsAndWatermarks(new AssignerWithPeriodicWatermarks<Tuple2<String, Long>>() {Long currentMaxTimestamp = 0L;final Long maxOutOfOrderness = 10000L;// 最大允许的乱序时间是10sSimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");/*** 定义生成watermark的逻辑* 默认100ms被调用一次*/@Nullable@Overridepublic Watermark getCurrentWatermark() {return new Watermark(currentMaxTimestamp - maxOutOfOrderness);}//定义如何提取timestamp@Overridepublic long extractTimestamp(Tuple2<String, Long> element, long previousElementTimestamp) {long timestamp = element.f1;currentMaxTimestamp = Math.max(timestamp, currentMaxTimestamp);long id = Thread.currentThread().getId();System.out.println("作者:秦凯新 键值 :"+element.f0+"线程验证 :"+  id   +" , 事件事件:[ "+sdf.format(element.f1)+" ],currentMaxTimestamp:[ "+sdf.format(currentMaxTimestamp)+" ],水印时间:[ "+sdf.format(getCurrentWatermark().getTimestamp())+" ]");                return timestamp;}});//保存被丢弃的数据OutputTag<Tuple2<String, Long>> outputTag = new OutputTag<Tuple2<String, Long>>("late-data"){};//注意,由于getSideOutput方法是SingleOutputStreamOperator子类中的特有方法,所以这里的类型,不能使用它的父类dataStream。SingleOutputStreamOperator<String> window = waterMarkStream.keyBy(0).window(TumblingEventTimeWindows.of(Time.seconds(3)))//按照消息的EventTime分配窗口,和调用TimeWindow效果一样//.allowedLateness(Time.seconds(2))//允许数据迟到2秒.sideOutputLateData(outputTag).apply(new WindowFunction<Tuple2<String, Long>, String, Tuple, TimeWindow>() {/*** 对window内的数据进行排序,保证数据的顺序* @param tuple* @param window* @param input* @param out* @throws Exception*/@Overridepublic void apply(Tuple tuple, TimeWindow window, Iterable<Tuple2<String, Long>> input, Collector<String> out) throws Exception {String key = tuple.toString();List<Long> arrarList = new ArrayList<Long>();Iterator<Tuple2<String, Long>> it = input.iterator();while (it.hasNext()) {Tuple2<String, Long> next = it.next();arrarList.add(next.f1);}Collections.sort(arrarList);SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");String result = "\n 作者:秦凯新 键值 : "+ key + "\n              触发窗内数据个数 : " + arrarList.size() + "\n              触发窗起始数据: " + sdf.format(arrarList.get(0)) + "\n              触发窗最后(可能是延时)数据:" + sdf.format(arrarList.get(arrarList.size() - 1))+ "\n              实际窗起始和结束时间: " + sdf.format(window.getStart()) + "《----》" + sdf.format(window.getEnd()) + " \n \n ";out.collect(result);}});//把迟到的数据暂时打印到控制台,实际中可以保存到其他存储介质中DataStream<Tuple2<String, Long>> sideOutput = window.getSideOutput(outputTag);sideOutput.print();//测试-把结果打印到控制台即可window.print();//注意:因为flink是懒加载的,所以必须调用execute方法,上面的代码才会执行env.execute("eventtime-watermark");}}
4.3.2 前面代码中设置了并行度为 1:
    env.setParallelism(1);

如果这里不设置的话, 代码在运行的时候会默认读取本机 CPU 数量设置并行度。

下面我们来验证一下, 把代码中的并行度调整为 2:

    env.setParallelism(2);
  • 发现玄机如下:在第二条事件时,其实已经达到窗的触发时机,但是因为并行度为2,只有等到最小

  • watermark 到的时候才会触发窗计算。发现线程44处理的是001和003 ,线程42处理的是0002,所以只有等到线程42到达后,水印才会起作用执行2018-10-01 10:11:33.000所在的窗。

      0001,1538359890000		2018-10-01 10:11:300002,1538359903000		2018-10-01 10:11:430003,1538359908000		2018-10-01 10:11:48
    

4.3.3 现在代码中设置了并行度为 8:
  • 发现 这 7 条数据都是被不同的线程处理的。 每个线程都有一个 watermark。且每一个线程都是基于自己接收数据的事件时间最大值。

  • 因此,导致到最后现在还没获取到最小的 watermark, 所以 window 无法被触发执行。

  • 只有所有的线程的最小watermark都满足watermark 时间 >= window_end_time时,触发历史窗才会执行。

      0001,1538359882000		2018-10-01 10:11:220002,1538359886000		2018-10-01 10:11:260003,1538359892000		2018-10-01 10:11:320004,1538359893000		2018-10-01 10:11:330005,1538359894000		2018-10-01 10:11:340006,1538359896000		2018-10-01 10:11:360007,1538359897000		2018-10-01 10:11:37
    

  • 当持续发生事件数据时。一旦所有线程都达到最低的窗触发时机时,就会进行窗触发执行了。输入数据如下:

      0007,1538359897000		2018-10-01 10:11:370008,1538359897000		2018-10-01 10:11:370009,1538359897000		2018-10-01 10:11:370010,1538359897000		2018-10-01 10:11:370011,1538359897000		2018-10-01 10:11:370012,1538359897000		2018-10-01 10:11:370013,1538359897000		2018-10-01 10:11:370014,1538359897000		2018-10-01 10:11:370015,1538359897000		2018-10-01 10:11:37
    

5 再下一城

版权声明:本套技术专栏是作者(秦凯新)平时工作的总结和升华,通过从真实商业环境抽取案例进行总结和分享,并给出商业应用的调优建议和集群环境容量规划等内容,请持续关注本套博客。版权声明:禁止转载,欢迎学习。QQ邮箱地址:1120746959@qq.com,如有任何问题,可随时联系。

截止到201811250141时间点,我的个人技术博客已经涵盖:

-《Spark Core商业源码实战系列目录》
-《SparkStreaming商业源码实战系列目录》
-《SparkSQL商业源码实战系列目录》
-《Spark商业应用实战系列目录》
-《Spark商业调优实战系列目录》
-《Spark商业ML实战系列目录》
-《Flink牛刀小试实战系列目录》
-《Hadoop商业环境实战系列目录》
-《kafka商业环境实战系列目录》
-《OLAP商业环境实战系列目录》
-《DW商业环境实战系列目录》

博客原创作品达到64篇高质量的精品,在这里给自己的坚持加油,送上一句豪情万丈。

秦凯新 于深圳

这篇关于Flink Window分析及Watermark解决乱序数据机制深入剖析-Flink牛刀小试的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/441263

相关文章

详谈redis跟数据库的数据同步问题

《详谈redis跟数据库的数据同步问题》文章讨论了在Redis和数据库数据一致性问题上的解决方案,主要比较了先更新Redis缓存再更新数据库和先更新数据库再更新Redis缓存两种方案,文章指出,删除R... 目录一、Redis 数据库数据一致性的解决方案1.1、更新Redis缓存、删除Redis缓存的区别二

oracle数据库索引失效的问题及解决

《oracle数据库索引失效的问题及解决》本文总结了在Oracle数据库中索引失效的一些常见场景,包括使用isnull、isnotnull、!=、、、函数处理、like前置%查询以及范围索引和等值索引... 目录oracle数据库索引失效问题场景环境索引失效情况及验证结论一结论二结论三结论四结论五总结ora

Redis事务与数据持久化方式

《Redis事务与数据持久化方式》该文档主要介绍了Redis事务和持久化机制,事务通过将多个命令打包执行,而持久化则通过快照(RDB)和追加式文件(AOF)两种方式将内存数据保存到磁盘,以防止数据丢失... 目录一、Redis 事务1.1 事务本质1.2 数据库事务与redis事务1.2.1 数据库事务1.

深入理解C++ 空类大小

《深入理解C++空类大小》本文主要介绍了C++空类大小,规定空类大小为1字节,主要是为了保证对象的唯一性和可区分性,满足数组元素地址连续的要求,下面就来了解一下... 目录1. 保证对象的唯一性和可区分性2. 满足数组元素地址连续的要求3. 与C++的对象模型和内存管理机制相适配查看类对象内存在C++中,规

element-ui下拉输入框+resetFields无法回显的问题解决

《element-ui下拉输入框+resetFields无法回显的问题解决》本文主要介绍了在使用ElementUI的下拉输入框时,点击重置按钮后输入框无法回显数据的问题,具有一定的参考价值,感兴趣的... 目录描述原因问题重现解决方案方法一方法二总结描述第一次进入页面,不做任何操作,点击重置按钮,再进行下

解决mybatis-plus-boot-starter与mybatis-spring-boot-starter的错误问题

《解决mybatis-plus-boot-starter与mybatis-spring-boot-starter的错误问题》本文主要讲述了在使用MyBatis和MyBatis-Plus时遇到的绑定异常... 目录myBATis-plus-boot-starpythonter与mybatis-spring-b

Oracle Expdp按条件导出指定表数据的方法实例

《OracleExpdp按条件导出指定表数据的方法实例》:本文主要介绍Oracle的expdp数据泵方式导出特定机构和时间范围的数据,并通过parfile文件进行条件限制和配置,文中通过代码介绍... 目录1.场景描述 2.方案分析3.实验验证 3.1 parfile文件3.2 expdp命令导出4.总结

Redis主从复制实现原理分析

《Redis主从复制实现原理分析》Redis主从复制通过Sync和CommandPropagate阶段实现数据同步,2.8版本后引入Psync指令,根据复制偏移量进行全量或部分同步,优化了数据传输效率... 目录Redis主DodMIK从复制实现原理实现原理Psync: 2.8版本后总结Redis主从复制实

Node.js 中 http 模块的深度剖析与实战应用小结

《Node.js中http模块的深度剖析与实战应用小结》本文详细介绍了Node.js中的http模块,从创建HTTP服务器、处理请求与响应,到获取请求参数,每个环节都通过代码示例进行解析,旨在帮... 目录Node.js 中 http 模块的深度剖析与实战应用一、引言二、创建 HTTP 服务器:基石搭建(一

更改docker默认数据目录的方法步骤

《更改docker默认数据目录的方法步骤》本文主要介绍了更改docker默认数据目录的方法步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一... 目录1.查看docker是否存在并停止该服务2.挂载镜像并安装rsync便于备份3.取消挂载备份和迁