flink重温笔记(九),2024年最新谈谈大数据开发-Binder机制及AIDL使用


2.2 多并行度与 WaterMark
  • 如果并行度是 n,那么watermark 就有 n 个
  • 触发条件以线程中最小的 watermark 为准

在这里插入图片描述


2.3 KeyBy 分流与 WaterMark
  • 一个程序中有多少个水印和并行度有关,和 keyby 无关

举例:

比如有单词hadoop spark
按照keyby,会分成hadoop组 和spark组
但是这两个组是共用1个水印的
hadoop来的数据满足了触发条件,会将spark组的数据也触发


2.4 水印的生成策略
2.4.1 内置水印生成策略
(1) 固定延迟生成水印

简介:设置最大延迟时间

例子:

DataStream dataStream = … ;
dataStream.assignTimestampsAndWatermarks(WatermarkStrategy.forBoundedOutOfOrderness(Duration.ofSeconds(3)));

(2) 单调递增生成水印

简介:当前时间戳就可以充当 watermark,因为后续到达数据的时间戳不会比当前的小(网络延迟)。

例子:

DataStream dataStream = … ;
dataStream.assignTimestampsAndWatermarks(WatermarkStrategy.forMonotonousTimestamps());

2.4.2 自定义水印生成策略
(1) 周期性 watermark 策略
  • 升序watermark:单调递增生成水印
  • 乱序watermark:固定延迟生成水印

都是基于周期性生成,默认的周期是 200ms,一般不去改,保持在 ms 级别 onPeriodicEmit()

(2) 间歇性 watermark 策略
  • 每一个事件时间都会产生一个watermark

2.5 在非数据源之后使用水印 [重点]
2.5.1 WaterMark 的四种使用情况
(1) 本来有序的 Stream中的 Watermark

例子:以 java bean 的数据输入作为有序事件时间

package cn.itcast.day09.WaterMark;

/**
* @author lql
* @time 2024-03-01 21:11:00
* @description TODO
*/

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.flink.api.common.functions.MapFunction;
import org.apache.flink.streaming.api.TimeCharacteristic;
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.AscendingTimestampExtractor;
import org.apache.flink.streaming.api.functions.windowing.ProcessWindowFunction;
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;

/**
* 使用单调递增水印,解决数据有序的场景(大多数情况都是乱序的数据,因此该场景比较少见)
* 需求:从socket接受数据,进行转换操作,然后应用窗口每隔5秒生成一个窗口,使用水印时间触发窗口计算
*
* 使用水印的前提:
* 1:数据必须要携带事件时间
* 2:指定事件时间作为数据处理的时间
* 3:指定并行度为1
* 4:使用之前版本的api的时候,需要增加时间类型的代码
*
* 测试数据:
* sensor_1,1547718199,35 -》2019-01-17 17:43:19
* sensor_6,1547718201,15 -》2019-01-17 17:43:21
* sensor_6,1547718205,15 -》2019-01-17 17:43:25
* sensor_6,1547718210,15 -》2019-01-17 17:43:30
*
* todo 如果窗口销毁以后,有延迟数据的到达会被丢弃,无法再次触发窗口的计算了
*/
public class MonotonousWaterMark {
public static void main(String[] args) throws Exception {
//todo 1)创建flink流处理的运行环境
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setParallelism(1);

//设置 Flink 程序中流数据时间语义为 EventTime。
// 在处理数据时 Flink 程序会按照数据事件发生的时间进行处理,而不是按照数据到达 Flink 程序的时间进行处理。
env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime);

//todo 2) 接入数据源
SingleOutputStreamOperator lines = env.socketTextStream(“node1”, 9999)
.map(new MapFunction<String, WaterSensor>() {
@Override
public WaterSensor map(String value) throws Exception {
String[] data = value.split(“,”);
return new WaterSensor(data[0], Long.valueOf(data[1]), Integer.parseInt(data[2]));
}
});

//todo 3)添加水印处理
//flink1.12之前版本的api编写(单调递增水印本质上还是周期性水印)
SingleOutputStreamOperator waterMarkStream = lines.assignTimestampsAndWatermarks(new AscendingTimestampExtractor() {
@Override
public long extractAscendingTimestamp(WaterSensor element) {
// 因为我们在转换时间戳,需要毫秒级别!
return element.getTs()*1000L;
}
});

waterMarkStream.print(“数据>>>”);

//todo 4)应用窗口操作,设置窗口长度为5秒
WindowedStream<WaterSensor, String, TimeWindow> sensorWS = waterMarkStream.keyBy(sensor -> sensor.getId()).window(TumblingEventTimeWindows.of(Time.seconds(5)));

//todo 5)定义窗口函数
SingleOutputStreamOperator result = sensorWS.process(new ProcessWindowFunction<WaterSensor, String, String, TimeWindow>() {
@Override
public void process(String s, Context context, Iterable elements, Collector out) throws Exception {
out.collect(“key” + s + “\n” +
“数据为” + elements + “\n” +
“数据条数为” + elements.spliterator().estimateSize() + “\n” +
“窗口时间为” + context.window().getStart() + “->” + context.window().getEnd());
}
});

//todo 6)输出测试
result.print();

//todo 启动运行
env.execute();
}

/**
* 水位传感器,用来接受水位数据
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
private static class WaterSensor {
private String id; //传感器id
private long ts; //时间
private Integer vc; //水位
}
}

注意:flink 1.12 版本之后的有序流添加周期水印

//注意:下面的代码使用的是Flink1.12中新的API
SingleOutputStreamOperator sensorDS = lines
//TODO 有序流中的watermark
.assignTimestampsAndWatermarks(
//指定watermark生成(单调递增)
WatermarkStrategy.forMonotonousTimestamps()
.withTimestampAssigner(new SerializableTimestampAssigner() {
@Override
public long extractTimestamp(WaterSensor element, long recordTimestamp) {
//指定如何从数据提取时间戳
return element.getTs() * 1000L;
}));

结果:

情况一:一种类别输入
sensor_6,1547718201,15 -》2019-01-17 17:43:21
sensor_6,1547718205,15 -》2019-01-17 17:43:25
sensor_6,1547718210,15 -》2019-01-17 17:43:30

输出:
数据>>>> MonotonousWaterMark.WaterSensor(id=sensor_6, ts=1547718201, vc=15)
数据>>>> MonotonousWaterMark.WaterSensor(id=sensor_6, ts=1547718205, vc=15)
keysensor_6
数据为[MonotonousWaterMark.WaterSensor(id=sensor_6, ts=1547718201, vc=15)]
数据条数为1
(2019-01-17 17:43:20 - > 2019-01-17 17:43:25)

数据>>>> MonotonousWaterMark.WaterSensor(id=sensor_6, ts=1547718210, vc=15)
keysensor_6
数据为[MonotonousWaterMark.WaterSensor(id=sensor_6, ts=1547718205, vc=15)]
数据条数为1
窗口时间为1547718205000->1547718210000
(2019-01-17 17:43:25 - > 2019-01-17 17:43:30)

情况二:两种类别输入
sensor_1,1547718199,35 -》2019-01-17 17:43:19
sensor_6,1547718201,15 -》2019-01-17 17:43:21
sensor_6,1547718205,15 -》2019-01-17 17:43:25
sensor_6,1547718210,15 -》2019-01-17 17:43:30

输出:
数据>>>> MonotonousWaterMark.WaterSensor(id=sensor_1, ts=1547718199, vc=35)
数据>>>> MonotonousWaterMark.WaterSensor(id=sensor_6, ts=1547718201, vc=15)
数据>>>> MonotonousWaterMark.WaterSensor(id=sensor_6, ts=1547718205, vc=15)
keysensor_1
数据为[MonotonousWaterMark.WaterSensor(id=sensor_1, ts=1547718199, vc=35)]
数据条数为1
窗口时间为1547718195000->1547718200000
(2019-01-17 17:43:15 - > 2019-01-17 17:43:20)

keysensor_6
数据为[MonotonousWaterMark.WaterSensor(id=sensor_6, ts=1547718201, vc=15)]
数据条数为1
窗口时间为1547718200000->1547718205000
(2019-01-17 17:43:20 - > 2019-01-17 17:43:25)

数据>>>> MonotonousWaterMark.WaterSensor(id=sensor_6, ts=1547718210, vc=15)
keysensor_6
数据为[MonotonousWaterMark.WaterSensor(id=sensor_6, ts=1547718205, vc=15)]
数据条数为1
窗口时间为1547718205000->1547718210000
(2019-01-17 17:43:25 - > 2019-01-17 17:43:30)

总结:

  • 1- 体现窗口左闭右开思想(即右端时间重合的数据不参与计算)
  • 2- 有序数据的水印窗口标准开始时间 :时间戳(秒级)// 窗口长度 * 窗口长度 * 1000 (这里的整除可以去掉余数)

// 如果是秒级,而不是时间戳:
1)start = timestamp - (timestamp - offset + windowSize) % windowSize; ​

事件时间 - (事件时间 - 0 + 窗口大小)%窗口大小 ​​​​​​​​​

时间戳按照窗口长度 取整数倍(以1970年1月1日0点为起点 => 伦敦时间) ​

2)end = start + size ​​​​​​​​ 开始时间 + 窗口长度

3)左闭右开: 属于本窗口的最大时间戳 = end -1ms , 所以时间为 end这条数据,不属于本窗口,所以是开区间

  • 3- 有序数据的水印窗口标准结束时间 :标准开始时间 + 窗口长度
  • 4- 此时水位线的变化和事件时间保持一致(因为是有序时间,就不需设置延迟,那么 t 就是 0。

​ watermark = maxtime - 0 = maxtime)

  • 5- 环境并行度设置为 1,方便观察现象
  • 6- flink 1.12 之前版本,需要指定事件时间,env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
  • 7- 转换时间戳时需要毫秒级别
  • 8- window().getStart() 获取窗口标准开始时间,window().getEnd()获取窗口标准结束时间
  • 9- spliterator().estimateSize() 获取窗口内数据条数
  • 10- api版本区别:
  • flink1.12之前:调用 assignTimestampAndwatermarks,new 一个 AscendingTimestampExtractor,重写方法获取时间戳
  • flink1.12之后:调用 assignTimestampAndWatermarks,有序流回调本质周期水印策略
  • WatermarkStrategy.forMonotonousTimestamps.withTimestampAssigner
  • new 一个序列化 SerializableTimestampAssigner,重写方法获取时间戳
  • 11- 应用场景:周期水印解决数据有序场景

(2) 乱序事件中的Watermark

例子:以 java bean 的数据输入作为乱序事件时间

package cn.itcast.day09.WaterMark;

/**
* @author lql
* @time 2024-03-02 15:20:38
* @description TODO:
*/

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.flink.api.common.functions.MapFunction;
import org.apache.flink.streaming.api.TimeCharacteristic;
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.timestamps.BoundedOutOfOrdernessTimestampExtractor;
import org.apache.flink.streaming.api.functions.windowing.ProcessWindowFunction;
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;

/**
* 使用固定延迟水印,解决数据乱序的场景(大多数情况都是乱序的数据,使用比较多)
* 需求:从socket接受数据,进行转换操作,然后应用窗口每隔5秒生成一个窗口,使用水印时间触发窗口计算
*
* 使用水印的前提:
* 1:数据必须要携带事件时间
* 2:指定事件时间作为数据处理的时间
* 3:指定并行度为1
* 4:使用之前版本的api的时候,需要增加时间类型的代码
*
* 测试数据:
* sensor_1,1547718199,35 -》2019-01-17 17:43:19
* sensor_6,1547718201,15 -》2019-01-17 17:43:21
* sensor_6,1547718205,15 -》2019-01-17 17:43:25
* sensor_6,1547718210,15 -》2019-01-17 17:43:30
*
* todo 固定延迟触发,根据事件时间-最大乱序时间-1得到水印,根据水印时间作为触发窗口的条件
* 触发窗口计算的两个条件:
* 1:时间达到窗口的endtime
* 2:窗口中存在数据
*/
public class OutOfOrdernessWaterMark {
public static void main(String[] args) throws Exception {
// todo 1) 设置流处理环境
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

env.setParallelism(1);
env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime);

// todo 2) 数据源
SingleOutputStreamOperator lines = env.socketTextStream(“node1”, 9999).map(new MapFunction<String, WaterSensor>() {
@Override
public WaterSensor map(String value) throws Exception {
String[] data = value.split(“,”);
return new WaterSensor(data[0], Long.valueOf(data[1]), Integer.parseInt(data[2]));
}
});

// todo 3) 设置水印
//flink1.12之前版本的api编写
SingleOutputStreamOperator waterMarkStream = lines.assignTimestampsAndWatermarks(new BoundedOutOfOrdernessTimestampExtractor(Time.seconds(3)) {
@Override
public long extractTimestamp(WaterSensor element) {
return element.getTs() * 1000L;
}
});

waterMarkStream.print(“数据>>>”);
//todo 4)应用窗口操作
WindowedStream<WaterSensor, String, TimeWindow> sensorWS = waterMarkStream.keyBy(t -> t.getId()).window(TumblingEventTimeWindows.of(Time.seconds(5)));

//todo 5) 自定义窗口
SingleOutputStreamOperator result = sensorWS.process(new ProcessWindowFunction<WaterSensor, String, String, TimeWindow>() {
@Override
public void process(String s, Context context, Iterable elements, Collector collector) throws Exception {
collector.collect("key: " + s + “\n” +
"数据为: " + elements + “\n” +
“条数为:” + elements.spliterator().estimateSize() + “\n” +
“时间窗口为:” + context.window().getStart() + “->” + context.window().getEnd() + “\n”);
}
});

//todo 6) 打印操作
result.print();

//todo 7) 启动程序
env.execute();
}

/**
* 水位传感器,用来接受水位数据
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
private static class WaterSensor {
private String id; //传感器id
private long ts; //时间
private Integer vc; //水位
}
}

注意:flink 1.12 版本之后的无序流添加固定延迟水印

SingleOutputStreamOperator waterMarkStream = lines
.assignTimestampsAndWatermarks(
// 固定延迟水印,是 Duration 类型
WatermarkStrategyforBoundedOutOfOrderness(Duration.ofSeconds(3))
.withTimestampAssigner(
new SerializableTimestampAssigner() {
@Override
public long extractTimestamp(WaterSensor waterSensor, long l) {
return waterSensor.getTs() * 1000L;
}}));

结果:

情况一:一种类别输入
sensor_6,1547718201,15 -》2019-01-17 17:43:21
sensor_6,1547718205,15 -》2019-01-17 17:43:25
sensor_6,1547718210,15 -》2019-01-17 17:43:30

输出:
数据>>>> OutOfOrdernessWaterMark.WaterSensor(id=sensor_6, ts=1547718201, vc=15)
数据>>>> OutOfOrdernessWaterMark.WaterSensor(id=sensor_6, ts=1547718205, vc=15)
数据>>>> OutOfOrdernessWaterMark.WaterSensor(id=sensor_6, ts=1547718210, vc=15)
key: sensor_6
数据为: [OutOfOrdernessWaterMark.WaterSensor(id=sensor_6, ts=1547718201, vc=15)]
条数为:1
时间窗口为:1547718200000->1547718205000
(2019-01-17 17:43:20 -> 2019-01-17 17:43:25)

总结:

  • 1- 如果是有 key 类别的差异,触发窗口计算往往在 key 变化时,而不需要两个一样的 key 作为对照
  • 2- 因为设置了延迟,在触发窗口范围的时候,事件时间 - 延迟时间 = 水印时间
  • (例子中打印了 3 条数据,即第 3 条数据触发计算,第3条数据的水印时间的秒级:30 - 3 = 27 >= 窗口的 endTime)
  • 窗口触发两个条件,一是水印时间达到窗口 endTime,二是窗口内有数据
  • 3- api版本区别:
  • flink1.12之前:调用 assignTimestampAndWatermarks,new 一个 BoundedOutofOrdernessTimestampExtractor

注意设置 延迟时间,重写方法获取事件时间

  • flink1.12之后:调用 assignTimestampAndWatermarks,有序流回调固定延迟水印策略
  • WatermarkStrategy.forBoundedOutOfOrderness(Duration 类型延迟时间).withTimestampAssigner
  • new 一个序列化 SerializableTimestampAssigner,重写方法获取时间戳
  • 4- 应用场景:固定延迟水印解决数据乱序场景

(3) 并行数据流中的Watermark

对齐机制会取所有 Channel 中最小的 Watermark,即:

每个并行度中必须都有数据,且都满足触发窗口条件,才会有对齐机制

例子:将并行度设置为2,带有线程号

package cn.itcast.day09.WaterMark;

/**
* @author lql
* @time 2024-03-02 19:27:53
* @description TODO:多并行度下的水印操作演示
*/

import org.apache.flink.api.common.eventtime.*;
import org.apache.flink.api.common.functions.FilterFunction;
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.datastream.SingleOutputStreamOperator;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
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 org.stringtemplate.v4.ST;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.Iterator;

/**
* 测试数据:
* 并行度设置为2测试:
* hadoop,1626934802000 ->2021-07-22 14:20:02
* hadoop,1626934805000 ->2021-07-22 14:20:05
* hadoop,1626934806000 ->2021-07-22 14:20:06
*/
public class Watermark_Parallelism {
//定义打印数据的日期格式
final private static SimpleDateFormat sdf = new SimpleDateFormat(“yyyy-MM-dd HH:mm:ss.SSS”);

public static void main(String[] args) throws Exception {
// todo 1) 流式环境
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setParallelism(2);

// todo 2) 数据源
SingleOutputStreamOperator<Tuple2<String, Long>> tupleDataStream = env.socketTextStream(“node1”, 9999).map(new MapFunction<String, Tuple2<String, Long>>() {
@Override
public Tuple2<String, Long> map(String line) throws Exception {
try {
String[] array = line.split(“,”);
return Tuple2.of(array[0], Long.parseLong(array[1]));
} catch (NumberFormatException e) {
System.out.println(“输入的数据格式不正确:” + line);
return Tuple2.of(“null”, 0L);
}
}
}).filter(new FilterFunction<Tuple2<String, Long>>() {
@Override
public boolean filter(Tuple2<String, Long> tuple) throws Exception {
if (!tuple.f0.equals(“null”) && tuple.f1 != 0L) {
return true;
}
return false;
}
});

// todo 3) 水印操作
SingleOutputStreamOperator<Tuple2<String, Long>> waterMarkDataStream = tupleDataStream.assignTimestampsAndWatermarks(
//TODO 自定义watermark生成器
WatermarkStrategy.forGenerator(new WatermarkGeneratorSupplier<Tuple2<String, Long>>() {
@Override
public WatermarkGenerator<Tuple2<String, Long>> createWatermarkGenerator(Context context) {
return new MyWatermarkGenerator<>();
}
}).withTimestampAssigner(new SerializableTimestampAssigner<Tuple2<String, Long>>() {
@Override
public long extractTimestamp(Tuple2<String, Long> element, long recordTimestamp) {
// 获取数据中的 eventTime
Long timestamp = element.f1;

// 定义字符串并打印
System.out.println(“键值:” + element.f0 + “,线程号:” + Thread.currentThread().getId() + “,” +
“事件时间:【” + sdf.format(timestamp) + “】”);
return timestamp;
}
})
);

// todo 4) 分流和窗口
SingleOutputStreamOperator result = waterMarkDataStream
.keyBy(0)
.window(TumblingEventTimeWindows.of(Time.seconds(5)))
.apply(new WindowFunction<Tuple2<String, Long>, String, Tuple, TimeWindow>() {
@Override
public void apply(Tuple tuple, TimeWindow window, Iterable<Tuple2<String, Long>> input, Collector out) throws Exception {
//定义该窗口所有时间字段的集合对象
ArrayList timeArr = new ArrayList<>();
// 首先获取了输入数据流(input)的迭代器
Iterator<Tuple2<String, Long>> iterator = input.iterator();
while (iterator.hasNext()) {
Tuple2<String, Long> tuple2 = iterator.next();
timeArr.add(tuple2.f1);
}
//对保存到集合列表的时间进行排序
Collections.sort(timeArr);
//打印输出该窗口触发的所有数据
String outputData = “” +
“\n 键值:【” + tuple + “】” +
“\n 触发窗口数据的个数:【” + timeArr.size() + “】” +
“\n 触发窗口的数据:” + sdf.format(new Date(timeArr.get(timeArr.size() - 1))) +
“\n 窗口计算的开始时间和结束时间:” + sdf.format(new Date(window.getStart())) + “----->” +
sdf.format(new Date(window.getEnd()));
out.collect(outputData);
}
});

//TODO 6)打印测试
result.printToErr(“触发窗口计算结果>>>”);

//TODO 7)启动作业
env.execute();
}

public static class MyWatermarkGenerator implements WatermarkGenerator{
//定义变量,存储当前窗口中最大的时间戳
private long maxTimestamp = -1L;
/**
* 每条数据都会调用该方法
* @param event
* @param eventTimestamp
* @param output
*/
@Override
public void onEvent(T event, long eventTimestamp, WatermarkOutput output) {
//System.out.println(“on Event…”);
maxTimestamp = Math.max(maxTimestamp, eventTimestamp);
}

/***
* 周期性的执行,默认是200ms调用一次
* @param output
*/
@Override
public void onPeriodicEmit(WatermarkOutput output) {
//System.out.println(“on Periodic…”+System.currentTimeMillis());
//发射watermark
output.emitWatermark(new Watermark(maxTimestamp -1L));

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数大数据工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年大数据全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上大数据开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加VX:vip204888 (备注大数据获取)
img

new Watermark(maxTimestamp -1L));

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数大数据工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年大数据全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
[外链图片转存中…(img-jvnTcgVH-1712517493983)]
[外链图片转存中…(img-B8GzFn3C-1712517493984)]
[外链图片转存中…(img-lpSnKDAN-1712517493984)]
[外链图片转存中…(img-2hJb6iRC-1712517493984)]
[外链图片转存中…(img-X2Tnb8Jx-1712517493985)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上大数据开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加VX:vip204888 (备注大数据获取)
[外链图片转存中…(img-g5hlenQV-1712517493985)]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值