目录:apache beam 个人使用经验总结目录和入门指导(Java)
为什么需要Coder
apache beam在做计算时, 会对数据进行序列化和反序列化,以方便进行分布式节点间的传输。
因此任何PCollection都会指定1个Coder编码,来确定当前数据集里的数据类型,以便在计算时进行正确的序列化操作。
当我们通过apply组装好1个PCollection后,其coder也就需要被指定了。
beam默认识别编码
正常情况下,beam能够自动识别编码,如下例子,我们生成1个字符串的数据集,并转成整数:
// 生成初始的输入数据
// 相当于往管道里塞入了3个自己写的字符串元素
PCollection<String> pcStart = pipeline.apply(
Create.of(
"45654",
"213",
"2233"));
System.out.println("pcStart coder=" + pcStart.getCoder());
// 字符串转整数
PCollection<Integer> pcMid = pcStart.apply(MapElements.via(new SimpleFunction<String, Integer>() {
@Override
public Integer apply(String str) {
return Integer.valueOf(str);
}
}));
System.out.println("pcMid coder=" + pcMid.getCoder());
控制台打印:
各类型对应的编码
java类型 | 编码 |
---|---|
Double | DoubleCoder |
Instant | InstantCoder |
Integer | VarIntCoder |
Iterable | IterableCoder |
KV | KvCoder |
List | ListCoder |
Map | MapCoder |
Long | VarLongCoder |
String | StringUtf8Coder |
TableRow | TableRowJsonCoder |
Void | VoidCoder |
byte | ByteArrayCoder |
TimestampedValue | TimestampedValueCoder |
需要手动setCoder的时机
我们也可以在PCollection进行apply之后, 进行手动setCoder, 有2种情况需要手动setCoder:
- 希望使用带有特殊处理的序列化反序列化编码(例如为了安全考虑做加密)
- beam无法识别当前编码的情况
第2种情况我遇到的比较多, 大部分是因为实际编码只能在运行期去获知的情况,举个例子:
我生成2个Row元素,可以理解为数据库里表的某一行
// 生成2个数据行元素, 第一个字段是整数,第二个字段是字符串
Schema schema = Schema.builder().addInt32Field("id").addStringField("name").build();
Row row1 = Row.withSchema(schema).addValue(1).addValue("tom").build();
Row row2 = Row.withSchema(schema).addValue(2).addValue("jack").build();
PCollection<Row> pcStart = pipeline.apply(Create.of(row1, row2));
接着我们要筛选出某个字段进行打印,筛选DoFn类如下:
static class SelectColumnFn extends SimpleFunction<Row, Object> {
private int index; // 字段序号
public SelectColumnFn(int index) {
this.index = index;
}
@Override
public Object apply(Row row) {
return row.getValue(index);
}
}
其中index来自于程序输入
// 获取需要打印的字段索引
int index = Integer.valueOf(args[0]);
PCollection pcMid = pcStart.apply(MapElements.via(new SelectColumnFn(index)));
// 输出到控制台
pcMid.apply(ParDo.of(new PrintStrFn()));
// 运行结果
pipeline.run().waitUntilFinish();
我们给输入参数置为0, 希望打印id字段,然后运行时却报错了
从提示可以看到beam此时不知道用什么编码了,需要我们来指定
因此我们可以加上一段setCoder的代码:
// 获取需要打印的字段索引
int index = Integer.valueOf(args[0]);
PCollection pcMid = pcStart.apply(MapElements.via(new SelectColumnFn(index)));
// 根据索引,设置不同的编码
if (index == 0) {
pcMid.setCoder(BigEndianIntegerCoder.of());
}
else if(index == 1) {
pcMid.setCoder(StringUtf8Coder.of());
}
// 输出到控制台
pcMid.apply(ParDo.of(new PrintStrFn()));
// 运行结果
pipeline.run().waitUntilFinish();
运行正常:
如何创建自定义编码
如果我们需要1种自己特有的编码方式,或者需要用到一些自定义类,那么就要进行自定义编码。
例如我们要对个人信息对象进行计算,但是在分布式传输中希望一些信息能加密,则我们就需要进行自定义编码
个人信息定义如下:
public class PersonInfo{
String name;
int age;
String idCardNumber; // 身份证号码
public PersonInfo(String name, int age, String idCardNumber) {
this.name = name;
this.age = age;
this.idCardNumber = idCardNumber;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public String getIdCardNumber() {
return idCardNumber;
}
}
接着定义个人信息编码类,在里面指定encode和decode的方式即可。
public class PersonInfoCoder extends StructuredCoder<PersonInfo> {
private final Coder<String> nameCoder = StringUtf8Coder.of();
private final Coder<Integer> ageCoder = BigEndianIntegerCoder.of();
private final Coder<String> idCardNumberCoder = StringUtf8Coder.of();
// 静态创建方法,与beam默认编码保持相同的创建方式
public static Coder<PersonInfo> of() {
return INSTANCE;
}
// 编码操作
@Override
public void encode(PersonInfo value, OutputStream outStream) throws CoderException, IOException {
// 把名字按照原定编码输入到outStream
this.nameCoder.encode(value.getName(), outStream);
// 把年龄按照原定编码输入到outStream
this.ageCoder.encode(value.getAge(), outStream);
String idCard = value.getIdCardNumber();
// 加密后再进行编码
this.idCardNumberCoder.encode(encrypt(idCard), outStream);
}
// 解码操作
@Override
public PersonInfo decode(InputStream inStream) throws CoderException, IOException {
String name = this.nameCoder.decode(inStream);
int age = this.ageCoder.decode(inStream);
// 解码后,要进行解密
String idCard = decrypt(this.idCardNumberCoder.decode(inStream));
return new PersonInfo(name, age, idCard);
}
// 获取编码列表
@Override
public List<? extends Coder<?>> getCoderArguments() {
return Arrays.asList(this.nameCoder, this.ageCoder, this.idCardNumberCoder);
}
// 识别编码是否已被指定,有一些场景下编码可能是运行期指定的。
@Override
public void verifyDeterministic() throws NonDeterministicException {
verifyDeterministic(this, "idCard coder id need be string", this.idCardNumberCoder);
}
}
如何指定自定义编码
完成以上操作后, 通过pcollection.setCoder(coder)去给对应的PCollection指定对应的编码即可。
也可以通过注解
@DefaultCoder(PersonInfoCoder.class)
public class PersonInfo {
...
}