Key Words: only root when printing schame
, 0 columns when calling show()
背景:spark程序的一段程序运行不正常:
Dataset<Product> hiveDataSet = hiveData.map(new HiveRowProcess(), Encoders.bean(Product.class));
hiveDataSet.printSchema();
hiveDataSet.show(5, false);
System.out.println("hive count: " + hiveDataSet.count());
异常情况:可以正常输出hive count的内容,且数据量符合预期,但是打印的schema只有
root
节点,show的内容是空白的(0列)
root
++
||
++
||
||
||
||
||
||
||
||
||
||
++
only showing top 20 rows
两种猜测:
HiveRowProcess()
过程有问题Product
有问题
验证猜测1
在HiveRowProcess类中打印日志System.err.println("hive transform: " + product);
,然后去spark
的ApplicationMaster
内查看stdout/stderr
,发现可以输出,所以初步判定HiveRowProces
类没有问题。
public class HiveRowProcess implements MapFunction<Row, Product> {
@Override
public Product call(Row value) throws Exception {
Product product = Product.newBuilder()
.id(value.getInt(0))
.serial(Long.parseLong(value.getString(1)))
.name(value.getString(2))
.tags(Arrays.asList(value.getString(3).split(",")))
.build();
System.err.println("hive transform: " + product);
return product;
}
}
验证猜测2
此时Product
的代码是这样的(第一次写建造者模式):
package com.jd.rec.nl.realtimedifftool.domain;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import java.util.List;
public class Product {
private int id;
private long serial;
private String name;
private List<String> tags;
private Product() {
}
private Product(Builder builder){
this.id = builder.id;
this.serial = builder.serial;
this.name = builder.name;
this.tags = builder.tags;
}
public static Builder newBuilder(){
return new Builder();
}
public int getId() {
return id;
}
public long getSerial() {
return serial;
}
public String getName() {
return name;
}
public List<String> getTags() {
return tags;
}
public static class Builder {
private int id;
private long serial;
private String name;
private List<String> tags;
public Builder() {
}
public Builder id(int id) {
this.id = id;
return this;
}
public Builder serial(long serial) {
this.serial = serial;
return this;
}
public Builder name(String name) {
this.name = name;
return this;
}
public Builder tags(List<String> tags) {
this.tags = tags;
return this;
}
public Product build(){
return new Product(this);
}
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Product product = (Product) o;
return new EqualsBuilder().append(id, product.id).append(serial, product.serial).append(name, product.name).append(tags, product.tags).isEquals();
}
@Override
public int hashCode() {
return new HashCodeBuilder(17, 37).append(id).append(serial).append(name).append(tags).toHashCode();
}
@Override
public String toString() {
return "Product{" +
"id=" + id +
", serial=" + serial +
", name='" + name + '\'' +
", tags=" + tags +
'}';
}
}
几种猜测:
难道是忘记了
implements Serializable
?难道Java Bean不能只有
getter
方法还需要setter
方法?难道Spark的Java Bean不支持
Builder
模式?难道Spark的Java Bean不支持
private
的成员变量?难道Spark的Java Bean不支持
List
类型的成员变量?难道Spark的Java Bean不支持
commons-lang3
的equals/hashCode的重写?
事实上,第二点就是正确答案,但我在试验第二点的时候方法写的不对,这一点后面再说明,后面的几点只不过是我在第二点的尝试失败之后给出的多个胡思乱想
反驳第三点:Java Bean与设计模式没有关联,Spark的Java Bean不支持Builder模式有点无法解释的通。
反驳第四点:Spark 2.2.x reference doc
Documentation Page 36的示例可以证明spark bean支持private的成员变量反驳第五点:Encoders (Spark 3.3.0 JavaDoc) Spark的Java API可以正常Spark的Java Bean支持List这种collection type
Spark程序修复正确后,在不重写equals/hashcode和重写equals/hashCode的情况下,spark程序后均可以正确运行,证明与
commons-lang3
无关。参考Spark 2.2.x reference doc
Documentation Page 36, 建议Java Beanimplements Serializable
那既然第二点是正确答案,加一个就是了,那为什么又说试验失败了呢,因为这里有两点要注意:
-
setter
方法要是void setXyz
这种形式的,必须返回void类型,必须setXyz格式,就是IntelliJ IDEA一键生成的样子,不可以是下面这两种样式的:// 返回类型是类 public Product setName(String name) { this.name = name; return this; }
// 非setXyz格式 public Product name(String name) { this.name = name; return this; }
set
方法需要是public
的,如果是private
的,也无法给出正确的schema和show正确内容。
所以,最终Product
的正确模样:
package com.jd.rec.nl.realtimedifftool.domain;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import java.io.Serializable;
import java.util.List;
public class Product implements Serializable {
private int id;
private long serial;
private String name;
private List<String> tags;
private Product() {
}
private Product(Builder builder){
this.id = builder.id;
this.serial = builder.serial;
this.name = builder.name;
this.tags = builder.tags;
}
public static Builder newBuilder(){
return new Builder();
}
public int getId() {
return id;
}
public long getSerial() {
return serial;
}
public String getName() {
return name;
}
public List<String> getTags() {
return tags;
}
public void setId(int id) {
this.id = id;
}
public void setSerial(long serial) {
this.serial = serial;
}
public void setName(String name) {
this.name = name;
}
public void setTags(List<String> tags) {
this.tags = tags;
}
public static class Builder {
private int id;
private long serial;
private String name;
private List<String> tags;
public Builder() {
}
public Builder id(int id) {
this.id = id;
return this;
}
public Builder serial(long serial) {
this.serial = serial;
return this;
}
public Builder name(String name) {
this.name = name;
return this;
}
public Builder tags(List<String> tags) {
this.tags = tags;
return this;
}
public Product build(){
return new Product(this);
}
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Product product = (Product) o;
return new EqualsBuilder().append(id, product.id).append(serial, product.serial).append(name, product.name).append(tags, product.tags).isEquals();
}
@Override
public int hashCode() {
return new HashCodeBuilder(17, 37).append(id).append(serial).append(name).append(tags).toHashCode();
}
@Override
public String toString() {
return "Product{" +
"id=" + id +
", serial=" + serial +
", name='" + name + '\'' +
", tags=" + tags +
'}';
}
}
End:
- 这个问题更深一层的原因现在还不知道,粗略估计与Spark的序列化方案依赖getter/ setter方法有关。
- 这个问题在构建简单的Java Bean(一般五个属性以下,直接构造函数构造、直接get/set的类,本文Product类是为该博客简化的类)时不会遇到,在使用像建造者模式这种并不需要setter方法构建类时,就发生了问题。
总结,Spark Java Bean的几点要求:
- 实体类
implements Serializable
- 类是公共访问的,即
public class
- 类具有一个无参构造器。
- 具有
private
范围的成员变量 - 具备
getXyz
和setXyz
来存取属性值
这基本遵循Java Bean规范。,可参见JavaBean规范、JAVAbean规范_sparrow jc的博客-CSDN博客