2021SC@SDUSC
前面三篇博客,我们从创建Tuple的源码出发,逐层分析了构建Tuple的全过程,并对源码进行了解析。我们回忆博客《Pig项目2-Data目录源码分析》讲到的关于Pig的数据模型的内容,字段(field)的集合组成元组(tuple),元组的集合组成包(bag)。本篇我们将分析bag相关源码。
相关资料链接:【Pig源码分析】谈谈Pig的数据模型 -数据库-火龙果软件工程
Data目录下包含bag的文件列表如下:
从上次分析tuple代码推断,创建bag大概率是调用BagFactory,那么如何查找调用语句呢?我这里使用了Ideal的全局查找,看到这里是通过get.Instance()方法创建的
结合参考资料,发现除了这种创建方法,还有另外的一种创建方法
其中,前一种方法是DefaultBagFactory的默认实现,另外一种是使用NonSpillableDataBag
再搞清楚两者有何区别之前,我们先梳理一下DefaultFactory的结构,类图如下
可以看出,使用第二种方式直接创建bag实际上是直接跳过了DefaultBagFactory,直接调用,而NonSpillableDataBag与其他的Bag类实现最大的区别就是它不是继承自DefaultAbstractBag,这么做理由也可以理解,就是实际上NonSpillableDataBag因为不符合DefaultAbstractBag所以不使用DefaultBagFactory调用
于是本篇先从简单的NonSpillableDataBag开始分析Bag的结构
以下为对DataBag的源码分析
/**
* 元组的集合。一个数据包可能适合也可能不适合(fit to)内存.
* DataBag扩展了spillable,这意味着它(registers with)注册了一个内存管理器(memory resister)
*默认情况下,它尝试将所有内容保存在内存中.
*如果内存管理器请求溢出到磁盘(通过调用spill()),它获取内存中的所有内容,打开一个spill文件然后把内容写出来。这种情况可能会发生多次。这个袋子跟踪所有溢出的文件
* DataBag提供了一个Iterator接口,允许调用者读取内容。迭代器知道数据溢出。
*他们必须能够处理从文件读取,因为他们从内存中读取的数据可能已经溢出到磁盘下面。
* DataBag接口假设所有数据都是先写后读的。即DataBag不能作为队列使用。如果数据在读取后写入,则结果是未定义的。由于速度的原因,在每次添加或读取时不会检查此条件。
* 由于溢出是异步的(请求溢出的内存管理器在单独的线程中运行),所有处理mContents Collection(包中包含的元组集合)的操作都必须是同步的。这意味着从DataBag读取数据目前是序列化的。目前这是可以的,因为猪的执行目前是单线程的。我们尝试了ReadWriteLock,但是发现它比使用synchronize关键字慢10倍。
如果pig将其执行模型更改为多线程,我们可能需要回到这个问题,因为同步读取很可能会破坏多线程执行的目的。
数据包(data bag)有几种类型,默认的、排序的和不同的。类型必须预先选择,没有办法在飞行中转换一个包。
默认的数据包不保证检索元组的任何特定顺序,并且可能包含重复的元组。已排序的数据包保证了元组将按顺序检索,其中“in order”由Tuple的默认比较器定义,或由创建数据包时调用者提供的比较器定义。分类后的袋子可能含有重复的东西。不同的包(Distinct bags)不能保证任何特定的检索顺序,但可以保证它们不会包含重复的元组
*/
@InterfaceAudience.Public
@InterfaceStability.Stable
public interface DataBag extends Spillable, WritableComparable, Iterable<Tuple>, Serializable {
/**
* 获取包中元素的数量,包括内存和磁盘上的元素。
* @return number of elements in the bag
*/
long size();
/**
* 看看袋子是否分类了。
* @return true if this is a sorted data bag, false otherwise.
*/
boolean isSorted();
/**
* 看看袋子是否 distinct.
* @return true if the bag is a distinct bag, false otherwise.
*/
boolean isDistinct();
/**
* 获取包的迭代器。对于默认的和不同的包,不保证有特定的顺序。对于已分类的袋子,保证按照所提供的比较器进行排序。
* @return tuple iterator
*/
Iterator<Tuple> iterator();
/**
* 向包中添加一个元组。
* @param t tuple to add.
*/
void add(Tuple t);
/**
* 把袋子里的东西加到袋子里
* @param b bag to add contents of.
*/
void addAll(DataBag b);
/**
*清除包中的内容,包括磁盘和内存中的内容。调用此函数后,任何读取的尝试都会产生未定义的结果.
*/
void clear();
/**
*返回包的哈希码值。袋子的哈希码定义为每个元组哈希码的和。这确保b1.equals(b2)意味着b1.hashCode() == b2. hashcode ()
*
* @return the hash code value for this bag
*/
int hashCode();
/** FuncEvalSpec.FakeDataBag使用这个。
*
* @param stale Set stale state.
*/
@InterfaceAudience.Private
void markStale(boolean stale);
}
接口内容可以总结如下
这里有意义的部分大概就是接口前那段注释了,我们知道了bag有spillable的特性,即内存溢出时会写入磁盘,以及pig实际上是一个单线程的执行模型
接下来我们来看看继承自databag的NonSpillableDataBag,从名字就知道,它是个非溢出的databag因此这也是一种不好的继承,更好的做法是将SpillableDataBag分离,即NonSpillableDataBag、SpillableDataBag继承Databag
接下来我们来看看NonSpillableDataBag的源码
/**
* 具有多个元组(可能)的无序集合。元组存储在数组列表中,因为不关心顺序或区别。隐含的假设是,该类的用户只存储那些能够容纳在内存中的元组——不会将这个包溢出到磁盘。 */
public class NonSpillableDataBag implements DataBag {
// 这个类不扩展DefaultAbstractBag的原因是,我们不想用它不需要的成员来膨胀这个类(DefaultAbstractBag有很多与溢出相关的成员,这里不需要)
private static final long serialVersionUID = 1L;
private List<Tuple> mContents;
public NonSpillableDataBag() {
mContents = new ArrayList<Tuple>();
}
/**
*如果你事先知道要在这个包中放入多少元组,就使用这个构造函数.
* @param tupleCount
*/
public NonSpillableDataBag(int tupleCount){
mContents = new ArrayList<Tuple>(tupleCount);
}
/**
* 这个构造函数通过获得列表的所有权而不是复制列表的内容,从现有的元组列表中创建一个包。
* @param listOfTuples List<Tuple> containing the tuples
*/
public NonSpillableDataBag(List<Tuple> listOfTuples) {
mContents = listOfTuples;
}
public boolean isSorted() {
return false;
}
public boolean isDistinct() {
return false;
}
public Iterator<Tuple> iterator() {
return new NonSpillableDataBagIterator();
}
/**
*处理从包中获取下一个元组的迭代器
*/
private class NonSpillableDataBagIterator implements Iterator<Tuple> {
private int mCntr = 0;
public boolean hasNext() {
return (mCntr < mContents.size());
}
public Tuple next() {
// 这将每1024次报告进展到下一次.
// 这应该比使用mod快得多
if ((mCntr & 0x3ff) == 0) reportProgress();
return mContents.get(mCntr++);
}
/**
* 不可执行
*/
public void remove() { throw new RuntimeException("Cannot remove() from NonSpillableDataBag.iterator()");}
}
/**
*向HDFS报告进度
*/
protected void reportProgress() {
if (PhysicalOperator.getReporter() != null) {
PhysicalOperator.getReporter().progress();
}
}
@Override
public void add(Tuple t) {
mContents.add(t);
}
@Override
public void addAll(DataBag b) {
for (Tuple t : b) {
mContents.add(t);
}
}
@Override
public void clear() {
mContents.clear();
}
@Override
public void markStale(boolean stale) {
throw new RuntimeException("NonSpillableDataBag cannot be marked stale");
}
@Override
public long size() {
return mContents.size();
}
@Override
public long getMemorySize() {
return 0;
}
@Override
public long spill() {
// TODO Auto-generated method stub
return 0;
}
/**
*把包里的东西写到磁盘上。
* @param out DataOutput to write data to.
* @throws IOException (passes it on from underlying calls).
*/
public void write(DataOutput out) throws IOException {
// 我们不关心这个包是否已排序或不同,因为使用迭代器来编写它将确保这些东西正确地出现。在另一端,没有理由浪费时间重新排序或重新应用distinct.
out.writeLong(size());
Iterator<Tuple> it = iterator();
while (it.hasNext()) {
Tuple item = it.next();
item.write(out);
}
}
/**
*从磁盘读取一个包
* @param in DataInput to read data from.
* @throws IOException (passes it on from underlying calls).
*/
public void readFields(DataInput in) throws IOException {
long size = in.readLong();
for (long i = 0; i < size; i++) {
try {
Object o = DataReaderWriter.readDatum(in);
add((Tuple)o);
} catch (ExecException ee) {
throw ee;
}
}
}
/* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if( obj == null) {
return false;
}
return compareTo(obj) == 0;
}
public int hashCode() {
int hash = 0;
for( Tuple t : mContents ) {
hash += t.hashCode();
}
return hash;
}
@SuppressWarnings("unchecked")
@Override
public int compareTo(Object other) {
if (this == other)
return 0;
if (other instanceof DataBag) {
DataBag bOther = (DataBag) other;
if (this.size() != bOther.size()) {
if (this.size() > bOther.size()) return 1;
else return -1;
}
// 这是假的。但我必须知道两个包是否有相同的元组,不管顺序如何。希望在大多数情况下,上面的大小检查可以防止这种情况发生。如果任何一个包还没有分类,创建一个已分类的包,这样我可以保证秩序。
BagFactory factory = BagFactory.getInstance();
DataBag thisClone;
DataBag otherClone;
thisClone = factory.newSortedBag(null);
Iterator<Tuple> i = iterator();
while (i.hasNext()) thisClone.add(i.next());
if (((DataBag) other).isSorted() || ((DataBag) other).isDistinct()) {
otherClone = bOther;
} else {
otherClone = factory.newSortedBag(null);
i = bOther.iterator();
while (i.hasNext()) otherClone.add(i.next());
}
Iterator<Tuple> thisIt = thisClone.iterator();
Iterator<Tuple> otherIt = otherClone.iterator();
while (thisIt.hasNext() && otherIt.hasNext()) {
Tuple thisT = thisIt.next();
Tuple otherT = otherIt.next();
int c = thisT.compareTo(otherT);
if (c != 0) return c;
}
return 0; // 如果我们走了这么远,它们一定是相等的
} else {
return DataType.compare(this, other);
}
}
/**
* Write the bag into a string. */
@Override
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append('{');
Iterator<Tuple> it = iterator();
while ( it.hasNext() ) {
Tuple t = it.next();
String s = t.toString();
sb.append(s);
if (it.hasNext()) sb.append(",");
}
sb.append('}');
return sb.toString();
}
NonSpillableDataBag很容易,看了注释基本都能理解,那么本篇分析就到这里。下期我们将分析"Spillable"的三种DataBag