https://beam.apache.org/documentation/programming-guide/
创建驱动程序,定义pipeline,包括输入、转换、输出,以及执行参数(主要包括runner,决定pipeline运行的后端)
抽象出了
PipelineOptions options = PipelineOptionsFactory.fromArgs(args).withValidation().create();
解析命令行获取参数 .withValidation验证必填参数是否存在,参数是否合法
扩展参数
通过getter和setter,用注解提供help说明和默认值
public interface MyOptions extends PipelineOptions {
@Description("Input for the pipeline")
@Default.String("gs://my-bucket/input")
String getInput();
void setInput(String input);
@Description("Output for the pipeline")
@Default.String("gs://my-bucket/output")
String getOutput();
void setOutput(String output);
}
// 注册使用
PipelineOptionsFactory.register(MyOptions.class);
MyOptions options = PipelineOptionsFactory.fromArgs(args)
.withValidation()
.as(MyOptions.class);
TextIO.Read
reads from an external text file and returns a PCollection whose elements are of type String, each String represents one line from the text file
Create.of(Java集合)
setCoder(StringUtf8Coder.of()) 元素编码器
PCollection特性
ParDo(Parallel Do)类似Map/Shuffle/Reduce的Map,对每个元素执行变换函数,输出N个元素。
可以逐个元素进行过滤、格式化/类型转换、提取字段、运算等处理。
DoFn缓存数据时注意不要依赖调用次数,调用次数是无法保证的
lifecyclem没看明白???
Combine 一种聚合操作,将集合中的元素结合,如sum、max、min等
简单的可以只实现1个函数,复杂的需要实现4个函数:
Combine.globally 输出的结果集只有一个元素
.withoutDefaults() 如果输入是空数据集,则输出空的结果集,不会生成缺省值的结果集
Combine 对非全局窗口有两个选项,必须指定一种
.withoutDefaults
.asSingletonView:provide a default value for each empty window when used as a side input没看懂???
Combine.
转换函数会存在多份,同时运行在不同机器上,相互之间独立,没有通信或共享状态
函数对象必须是可序列化的,这样才能发往其他机器
函数对象必须是thread-compatible,因为Beam SDK不是线程安全的
线程安全五个等级:
一个函数对象实例只会被一个worker的单个线程使用,除非自己建线程,这时就要自己负责同步
函数对象最好是幂等的,不知道会调用多少次
是ParDo的另一种形式的输入,每次处理PCollection的元素时,DoFn都可以访问SideInputs
当ParDo处理时需要额外数据,而且该数据不能写死,需要从输入或其他pipeline分支生成时,用SideInputs
对于开窗的PCollection,每个windows有一个PCollectionView
如果主输入和side inputs窗口一致,两者的窗口一一对应,就可以直接找到相应的side input窗口
如果不一致,会根据主输入元素的窗口去找side input的合适的窗口。比如主窗口是1分钟的固定窗口,side input是1小时的固定窗口,则主输入元素对找相应小时的side input窗口
如果主输入的元素在多个窗口中,每个窗口中都会调用一次processElement,每次找到side input窗口可能不一样
如果side input有多个trigger,beam选择最新的一个
// Specify the tag for the main output.
.withOutputTags(wordsBelowCutOffTag,
// Specify the tags for the two additional outputs as a TupleTagList.
TupleTagList.of(wordLengthsAboveCutOffTag)
.and(markedWordsTag)));
DoFn还有其他参数:
@OnTimer 具体怎么用还没看???
支持通配符
如:TextIO.read().from(“protocol://my_bucket/path/to/input-*.csv”));
获取的PCollection的集合(或者元素是List)?再使用Flatten转换为一个PCollection
文件输出默认输出多个文件,可以添加前缀、后缀,中间自动添加数字(应该有格式化的方法)
Schemas provide us a type-system for Beam records that is independent of any specific programming-language type.
如果不同类的字段相同,通过Schema,Beam可以无缝转换这些类的对象。
使用注解
@DefaultSchema(JavaBeanSchema.class)
@SchemaCreate
支持原始类型、集合(ARRAY、ITERABLE、MAP)和嵌套
通过继承LogicalType扩展schema类型,可以做为field的类型
枚举、OneOf(联合union)
使用方便,可以直接用字段名访问,支持内嵌字段、通配
可以增删、重命名schema字段
默认只有一个全局窗口
对于无界数据,至少采用一种措施
设置窗口后,对后面的非窗口类的Transform无效,直到一个需要窗口的Transform才有效
一个元素可能属于多个窗口,比如滑动窗口会创建重叠的窗口
Fixed time窗口是开闭区间,即[开始时间,开始时间+时长)
Sliding time窗口,参数开窗间隔和窗口时长,对周期性统计比较有用
是不是开窗周期和时长相同就和Fixed time一样了?
Session窗口,通过元素间的时间间隔划分窗口
global窗口,一般用于有限数据源
无界不做聚合类操作应该也可以用
.withAllowedLateness可以延长窗口关闭的时间
.outputWithTimestamp增加时间戳
默认在windows时关闭时触发
默认几种触发器
触发器还提供两个额外的能力
AfterWatermark基于事件时间触发,watermark超过窗口结束时触发,然后每次延迟数据到来时再触发
watermark的行为怎么理解???
AfterProcessingTime基于处理时间触发
pastFirstElementInPane 数据到达后的一段时间触发
AfterPane基于数据驱动触发
elementCountAtLeast:元素个数接收到一定个数后触发,不够数量不会触发
pane:每次触发器提交的数据
设置触发器时,必须同时设置窗口的累积模式
withAllowedLateness影响后续转换生成的PCollection,需要显示调用Window.configure().withAllowedLateness()修改
组合触发器
.apply(Window
.configure()
.triggering(AfterWatermark // 基于时间时间
.pastEndOfWindow() // BEAM估计数据已经全到了(watermark超出windows)
.withLateFirings(AfterProcessingTime // 基于处理时间修正延迟的数据
.pastFirstElementInPane() // 接收到数据后,延迟10分钟触发
.plusDelayOf(Duration.standardMinutes(10))))
.withAllowedLateness(Duration.standardDays(2))); // 留2天时间处理延迟数据,2天后彻底关闭窗口
用于提供一些后台信息
指标的名称由命名空间和名字组成,命名空间可以避免重名,也可以查询整个命名空间的指标
每个指标都有它的作用范围,表明在执行pipeline的哪个步骤,哪段代码在运行
指标不需要提前声明,可以在运行时创建
如果后端不支持某个上报指标,可以忽略,不会导致pipeline失败,如果不支持某个查询指标,可以只返回支持的部分
目前有三种指标类型
度量指标可以导出到外部,用MetricsOptions配置,默认5秒输出一次
为开发人员提供手工管理每个key状态,可以在聚合方面提供更细粒度的控制
state API按key存储状态,数据集需要时PCollection
ParDo可以声明状态变量,并赋值及更新,状态只对当前处理的key可见
开窗的情况下, 第一个key读到的状态是空的,当窗口关闭时会进行gc
如果状态处理用于在DoFn内实现状态机,需要注意元素的顺序是不能保证的
状态的类型
state.read()会导致runner阻塞,多个state顺序读取时可能增大延迟
通过@AlwaysFetched预取状态
如果有代码分支不需要state时,@AlwaysFetched会增加不必要的预取,可以通过readLater异步读取,让runnrer在后面一起批量读取
Beam支持per-key定时回调API
一个定时器只能设置一个时间戳,后面设置的覆盖前面设置的
Event-time定时器可以用于基于事件时间的聚合
Processing-time定时器一般用于创建大批数据,也可用于定时触发事件,可以设置绝对时间和相对时间
动态定时器,通过TimerMap可以设置多个不同的定时器,可以根据定时器标签动态选择
Timer output timestamps 没看懂???
state的GC
https://beam.apache.org/documentation/runtime/model
元素的序列化和传输是分布式执行中代价最高的操之一
避免的方法:失败后在本地重新处理,限制输出分发到其他机器
传输元素的原因
元素持久化的原因
并行的尴尬:不能顺序执行(如给PCollection里每个元素顺序编号),不能全量操作(如把所有元素输出或保存检查点状态)
分批处理:由runner划分,流处理选小批,批处理选大批
一批由一个worker执行,多个worker可以并行
如果单个转换失败,可能由其他worker重新执行
如果多个转换失败,一般由当前worker重新执行失败的操作,可以避免转换间持久化的代价
https://beam.apache.org/get-started/wordcount-example/
Pipeline
Pipeline用来定义处理流程,可以通过PipelineOptions定义runner等
其描述了由PCollection为节点,PTransform为边组成的DAG。
PipelineOptions
可以指定runner等
runner有多种:Direct(本地),Spark等
PCollection
PCollection.apply(PTransform),设置处理实例
TextIO.read().from(文件) 读文件生成PCollection,每个元素为一行数据
TextIO.write().to(?)
FlatMapElements.into(结果类型).via(指定函数) 一种PTransform,每个元素执行指定的函数,结果不保持分组,所有元素重新组成集合[kv,kv,kv,kv]
MapElements.into(结果类型).via(指定函数),结果保持输入的分组,[ [kv,kv], [kv, kv]]
结果类型用TypeDescriptors创建,如
TypeDescriptors.strings()
TypeDescriptors.kvs(TypeDescriptors.strings(), TypeDescriptors.integers()))
static class ExtractWordsFn extends DoFn {
...
@ProcessElement
public void processElement(ProcessContext c) {
...
}
}
public static class CountWords extends PTransform,
PCollection>> {
@Override
public PCollection> expand(PCollection lines) {
// Convert lines of text into individual words.
PCollection words = lines.apply(
ParDo.of(new ExtractWordsFn()));
// Count the number of times each word occurs.
PCollection> wordCounts =
words.apply(Count.perElement());
return wordCounts;
}
}
ParDo.of(new DoFn()) 通过DoFn快速创建一个PTransform
调试两张方法
window: 无界数据没有结束,需要定义处理的范围,即将流数据转换为批数据,流变成N个窗口
Window.<数据类型>into(FixedWindows.of(窗口时间))
时间戳:PCollection中每个元素都有时间戳,由创建PCollection的源赋值,可以使用数据自带或处理时间等
https://beam.apache.org/get-started/mobile-gaming-example
skew 时间差,事件产生到处理的时间差
KV.of(gInfo.getKey(field), gInfo.getScore())) 创建KV实例
Sum.integersPerKey() 按key对value(整型)求和
WithTimestamps.of((GameActionInfo i) -> new Instant(i.getTimestamp()))) 为元素增加时间戳
Filter.by(过滤函数) 过滤元素
GlobalWindows 全局窗口能处理从开始到当前的所有数据,其他窗口如FixedWindows只能处理一段时间的数据
Window.triggering(Trigger)
Window.into(new GlobalWindows())
// Get periodic results every ten minutes.
.triggering(
Repeatedly.forever(
AfterProcessingTime.pastFirstElementInPane().plusDelayOf(TEN_MINUTES)))
.accumulatingFiredPanes()
.withAllowedLateness(allowedLateness))
通过触发器调用accumulatingFiredPanes
Repeatedly.forever 一直执行
Create a composite trigger that repeatedly executes the trigger repeated, firing each time it fires and ignoring any indications to finish.
AfterProcessingTime
A Trigger trigger that fires at a specified point in processing time, relative to when input first arrives.
AfterProcessingTime.pastFirstElementInPane().plusDelayOf(TEN_MINUTES)
第一个元素到达后的十分钟,用ProcessingTime计算
accumulatingFiredPanes()
Returns a new Window PTransform that uses the registered WindowFn and Triggering behavior, and that accumulates elements in a pane after they are triggered.
累积触发的窗格,即通过触发器将数据细分为窗格,可以保存之前窗格的数据,与后来的数据累积计算
withAllowedLateness()
默认情况下,当watermark通过end-of-window之后,再有之前的数据到达时,这些数据会被删除。
为了避免有些迟到的数据被删除,因此产生了allowedLateness的概念。
简单来讲,allowedLateness就是针对event time而言,对于watermark超过end-of-window之后,还允许有一段时间(也是以event time来衡量)来等待之前的数据到达,以便再次处理这些数据。
对于trigger是默认的EventTimeTrigger的情况下,allowedLateness会再次触发窗口的计算,而之前触发的数据会buffer起来,直到watermark超过end-of-window + allowedLateness的时间,窗口的数据及元数据信息才会被删除
问题:全局窗口没有watermakr,allowedLateness有什么用???
Window.into(FixedWindows.of(teamWindowDuration))
// We will get early (speculative) results as well as cumulative
// processing of late data.
.triggering(
AfterWatermark.pastEndOfWindow()
.withEarlyFirings(
AfterProcessingTime.pastFirstElementInPane()
.plusDelayOf(FIVE_MINUTES))
.withLateFirings(
AfterProcessingTime.pastFirstElementInPane()
.plusDelayOf(TEN_MINUTES)))
.withAllowedLateness(allowedLateness)
.accumulatingFiredPanes()
AfterWatermark.pastEndOfWindow()
Creates a trigger that fires when the watermark passes the end of the window.
.withEarlyFirings() watermark到达窗口结束前的某个点触发
Creates a new Trigger like the this, except that it fires repeatedly whenever the given Trigger fires before the watermark has passed the end of the window.
.withLateFirings() watermark达到窗口结束后的某个点触发
Creates a new Trigger like the this, except that it fires repeatedly whenever the given Trigger fires after the watermark has passed the end of the window.
withAllowedLateness和accumulatingFiredPanes调用顺序有什么影响??
Values.create() 从KV中提取value
Mean.globally().asSingletonView() 对全部值计算平均值
Returns a PTransform that produces a PCollectionView whose elements are the result of combining elements per-window in the input PCollection.
Mean.globally().withoutDefaults()
Returns a PTransform identical to this, but that does not attempt to provide a default value in the case of empty input.
Metrics.counter(namespace, name) Create a metric that can be incremented and decremented, and is aggregated by taking the sum
withSideInputs(PCollectionView) 会将值(PCollectionView类型)广播给其他需要的worker
c.sideInput(globalMeanScore) 使用sideInput的值
问题:两个数据是异步的,平均值是变化的,多次运行结果可能不一致???
Sessions.withGapDuration(间隔时长)
在间隔时长内新数据则认为会话结束,新开窗口,否则数据继续在原窗口里处理
withTimestampCombiner(TimestampCombiner.END_OF_WINDOW) 用窗口结束时间作为输出的时间戳
Combine.perKey(x -> 0) 不关心其他数据,只要key,value直接填0,(可能是因为只有KV,没有List/Array)
DoFn还有带窗口信息的成员函数void processElement(ProcessContext c, BoundedWindow window)
/** Calculate and output an element's session duration. */
private static class UserSessionInfoFn extends DoFn, Integer> {
@ProcessElement
public void processElement(ProcessContext c, BoundedWindow window) {
IntervalWindow w = (IntervalWindow) window;
int duration = new Duration(w.start(), w.end()).toPeriod().toStandardMinutes().getMinutes();
c.output(duration);
}
}