笔者的大数据平台XSailboat 中包含以DAG方式可视化的离线分析和实时计算的开发、运维功能。实时计算功能,底层是基于Flink,我们在此基础上开发辅助插件和可视化开发运维套件,我们将其称之为SailFlink。
原生的Flink算子适合在IDE代码环境下用代码开发,并不适合做可视化,所以必须在原生算子基础上做一定的抽象和限制。至于我们是怎么做的,可以参考《Flink的DAG可视化开发实践》。
SailFlink对状态存储器也进行了包装,提供“单值”、“队列”(有界的)、“键值”三种状态存储器。有界队列型状态存储器,后台是ListState。
原生的ListState的方法有:
由上图可见,ListState的操作方法是很少的,要取用其中的状态数据,只能get到迭代器之后,迭代各个元素。这在IDE中用代码去开发有足够的灵活性,还是能接受的,但是在我们的DataStudio中,开发计算管道要求的是轻量化编程,使用的是Aviator表达式语言,再只能用表达式迭代,考虑缓存什么的,这会让轻量化编程变成重度编程,这是不符合可视化开发的目标的。
为此我结合我们通常面对的场景,对它进行了封装。提供出了有界的队列型状态存储器。
队列型状态存储器是一个有容量限制的队列。队列可以看成一个两头没有盖子的竖放的桶,下面是底、上面是顶,元素从顶部压入。当从顶部压入新元素之后,如果队列的容量超出限制,底部将弹出一定数量的元素,使其总体上容量符合限制。
队列型状态存储器中的元素不宜过大。SailFlink虽然优化了一些操作,但其相关操作仍然是耗时的,建议长度不要超过1024。
队列型状态存储器提供了两种容量限制方法:
1)固定大小的容量限制。当队列中的元素个数超过容量大小时,底部元素会被弹出,使得队列的容量恰好等于设定的容量大小。
2)底部限制。底部限制需要提供一个底部判定函数。这个函数在元素被添加(s.queue.add)时被调用,用来寻找新的底。新的底和就的底之间的元素会被删除(不包括新的底)。函数有三个参数:
q,队列,它是一个java.util.ArrayList类型,所以可以用[]来获取元素。例如q[i+1];
e,当前判定元素;
i,元素在队列中的序号
它的返回值是Boolean型,返回true表示当前遍历到的第i个元素e是新的底部。
对Aviator函数定义不了解,可以查看《8.1函数》和《8.2匿名函数和闭包》。推荐fn写法。
相关的操作方法有:
我们提供的转换节点都是按键分区的,如何让缓存下来的数据也是按键分区的,相关的操作方法不用传递分区键,但操作就是在相应分区上进行的?
首先考虑到的是在使用的时候框架设置一下分区键,但发现有的算子的方法,获取分区键有困难,并不是所有算子的方法都能很方便的取得分区键。
最后我们的解决方案是:
下面贴出代码供参考:
package com.cimstech.sailboat.flink.run.base.state;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.function.BiPredicate;
import org.apache.flink.api.common.state.ListState;
import org.apache.flink.api.common.state.ValueState;
import com.cimstech.sailboat.flink.common.StateStoreType;
import com.cimstech.sailboat.flink.common.StateStoreValueType;
import com.cimstech.xfront.common.collection.CS;
import com.cimstech.xfront.common.collection.SRHashMap;
import com.cimstech.xfront.common.excep.WrapException;
public class StateStore_Queue<T> extends StateStore implements IQueueStateStore<T>
{
ListState<T> mStates ;
// UUID,缓存的键
ValueState<String> mKey ;
// 缓存
final Map<String , List<T>> mElesMap = new SRHashMap<String, List<T>>() ;
// 边界控制住方法
IBoundaryControl<T> mBoundaryControl ;
public StateStore_Queue(String aName , StateStoreValueType aValueType , ListState<T> aStates , IBoundaryControl<T> aBoundaryControl
, ValueState<String> aKey)
{
super(StateStoreType.Queue , aName , aValueType , false) ;
mStates = aStates ;
mBoundaryControl = aBoundaryControl ;
mKey = aKey ;
}
public ListState<T> getStates()
{
return mStates ;
}
/**
* 清空队列
*/
@Override
public void clear()
{
mStates.clear();
try
{
List<T> eles = getList() ;
if(eles != null)
eles.clear() ;
}
catch (IOException e)
{
WrapException.wrapThrow(e) ;
}
}
List<T> getList() throws IOException
{
String currentKey = mKey.value() ;
return currentKey == null?null:mElesMap.get(currentKey) ;
}
void cacheList(List<T> aEles) throws IOException
{
String key = mKey.value() ;
if(key == null)
{
key = UUID.randomUUID().toString() ;
mKey.update(key) ;
}
mElesMap.put(key , aEles) ;
}
/**
* 获取并移除底部元素
*/
@Override
public T rmBottom()
{
try
{
List<T> eles = getList() ;
if(eles != null)
{
if(eles.isEmpty())
return null ;
T bottom = eles.remove(0) ;
mStates.update(eles) ;
return bottom ;
}
Iterator<T> it = mStates.get().iterator() ;
T first = null ;
eles = CS.arrayList() ;
while(it.hasNext())
{
if(first == null)
first = it.next() ;
else
eles.add(it.next()) ;
}
mStates.update(eles) ;
cacheList(eles);
return first ;
}
catch (Exception e)
{
WrapException.wrapThrow(e) ;
return null ; // dead code
}
}
/**
* 取得底部元素
*/
@Override
public T getBottom()
{
try
{
List<T> eles = getList() ;
if(eles != null)
return eles.isEmpty()?null:eles.get(0) ;
Iterator<T> it = mStates.get().iterator() ;
return it.hasNext()?it.next():null ;
}
catch (Exception e)
{
WrapException.wrapThrow(e) ;
return null ; // dead code
}
}
/**
* 获取并移除顶部元素
*/
@Override
public T rmTop()
{
try
{
List<T> eles = getList() ;
if(eles != null)
{
if(eles.isEmpty())
return null ;
T top = eles.remove(eles.size() - 1) ;
mStates.update(eles) ;
return top ;
}
Iterator<T> it = mStates.get().iterator() ;
eles = CS.arrayList() ;
while(it.hasNext())
{
eles.add(it.next()) ;
}
if(eles.isEmpty())
return null ;
T top = eles.remove(eles.size() - 1) ;
mStates.update(eles) ;
cacheList(eles) ;
return top ;
}
catch (Exception e)
{
WrapException.wrapThrow(e) ;
return null ; // dead code
}
}
/**
* 取得顶部元素
*/
@Override
public T getTop()
{
try
{
List<T> eles = getList() ;
if(eles != null)
{
if(eles.isEmpty())
return null ;
return eles.get(eles.size() - 1) ;
}
Iterator<T> it = mStates.get().iterator() ;
T top = null ;
eles = CS.arrayList() ;
while(it.hasNext())
{
top = it.next() ;
eles.add(top) ;
}
cacheList(eles);
return top ;
}
catch (Exception e)
{
WrapException.wrapThrow(e) ;
return null ; // dead code
}
}
/**
* 在顶部添加元素
*/
@Override
public void add(T[] aEles)
{
if(aEles == null || aEles.length == 0)
return ;
List<T> addEleList = Arrays.asList(aEles) ;
try
{
List<T> eles = getList() ;
if(eles == null && mBoundaryControl != null)
{
// 有边界控制,需要把元素都加载起来
Iterator<T> it = mStates.get().iterator() ;
eles = CS.arrayList() ;
while(it.hasNext())
{
eles.add(it.next()) ;
}
cacheList(eles) ;
}
if(eles != null)
eles.addAll(addEleList) ;
if(mBoundaryControl != null)
{
int index = mBoundaryControl.apply(eles) ;
if(index > 0)
{
eles = new ArrayList<>(eles.subList(index , eles.size())) ;
mStates.update(eles) ;
cacheList(eles) ;
return ;
}
}
mStates.addAll(addEleList) ;
}
catch (Exception e)
{
WrapException.wrapThrow(e) ;
}
}
/**
* 获取指定位置的元素
*/
@Override
public T get(int aIndex)
{
try
{
List<T> eles = getList() ;
if(eles != null)
{
final int size = eles.size() ;
return aIndex >=0 && aIndex<size ? eles.get(aIndex):null ;
}
Iterator<T> it = mStates.get().iterator() ;
T ele = null ;
int i = 0 ;
while(it.hasNext())
{
ele = it.next() ;
if(i == aIndex)
return ele ;
i++ ;
}
return null ;
}
catch (Exception e)
{
WrapException.wrapThrow(e) ;
return null ; // dead code
}
}
/**
* 元素个数
*/
@Override
public int size()
{
try
{
List<T> eles = getList() ;
if(eles != null)
return eles.size() ;
Iterator<T> it = mStates.get().iterator() ;
eles = CS.arrayList() ;
while(it.hasNext())
{
eles.add(it.next()) ;
}
cacheList(eles) ;
return eles.size() ;
}
catch (Exception e)
{
WrapException.wrapThrow(e) ;
return 0 ; // dead code
}
}
/**
* 遍历元素。
* 第一个参数是元素,第2个参数是序号
*/
@Override
public void forEach(BiPredicate<T, Integer> aPred)
{
try
{
List<T> eles = getList() ;
if(eles != null)
{
int i=0 ;
for(T ele : eles)
{
if(!aPred.test(ele, i++))
return ;
}
return ;
}
Iterator<T> it = mStates.get().iterator() ;
eles = CS.arrayList() ;
T ele = null ;
int i=0 ; ;
while(it.hasNext())
{
ele = it.next() ;
eles.add(ele) ;
if(!aPred.test(ele, i++))
break ;
}
if(!it.hasNext())
{
cacheList(eles);
}
}
catch (Exception e)
{
WrapException.wrapThrow(e) ;
}
}
}