Flink的ListState在小规模场景下的缓存提速和接口优化丰富的实践

1. 背景

笔者的大数据平台XSailboat 中包含以DAG方式可视化的离线分析和实时计算的开发、运维功能。实时计算功能,底层是基于Flink,我们在此基础上开发辅助插件和可视化开发运维套件,我们将其称之为SailFlink。

原生的Flink算子适合在IDE代码环境下用代码开发,并不适合做可视化,所以必须在原生算子基础上做一定的抽象和限制。至于我们是怎么做的,可以参考《Flink的DAG可视化开发实践》
在这里插入图片描述

SailFlink对状态存储器也进行了包装,提供“单值”、“队列”(有界的)、“键值”三种状态存储器。有界队列型状态存储器,后台是ListState。

原生的ListState的方法有:
在这里插入图片描述
由上图可见,ListState的操作方法是很少的,要取用其中的状态数据,只能get到迭代器之后,迭代各个元素。这在IDE中用代码去开发有足够的灵活性,还是能接受的,但是在我们的DataStudio中,开发计算管道要求的是轻量化编程,使用的是Aviator表达式语言,再只能用表达式迭代,考虑缓存什么的,这会让轻量化编程变成重度编程,这是不符合可视化开发的目标的。

为此我结合我们通常面对的场景,对它进行了封装。提供出了有界的队列型状态存储器。

2. 队列型状态存储器

队列型状态存储器是一个有容量限制的队列。队列可以看成一个两头没有盖子的竖放的桶,下面是底、上面是顶,元素从顶部压入。当从顶部压入新元素之后,如果队列的容量超出限制,底部将弹出一定数量的元素,使其总体上容量符合限制。

队列型状态存储器中的元素不宜过大。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写法。
在这里插入图片描述
相关的操作方法有:

  • s.queue.add,添加一个或多个(不超过6个)元素。
  • s.queue.each,遍历队列中的元素。
  • s.queue.size,获得队列中元素的个数。
  • s.queue.getTop,获得顶部元素
  • s.queue.rmTop,获得并移除顶部元素
  • s.queue.getBottom,获得底部元素
  • s.queue.rmBottom,获得并移除底部元素
  • s.queue.clear,清空队列

3. 如何解决分区问题?

我们提供的转换节点都是按键分区的,如何让缓存下来的数据也是按键分区的,相关的操作方法不用传递分区键,但操作就是在相应分区上进行的?

首先考虑到的是在使用的时候框架设置一下分区键,但发现有的算子的方法,获取分区键有困难,并不是所有算子的方法都能很方便的取得分区键。

最后我们的解决方案是:

  1. 缓存在我们的内存中,我使用了一个WRHashMap存储(软引用,避免缓存撑爆内存)。
  2. 构造一个ValueState,用它来存储缓存的键(UUID)。这样就实现了“分区键–>UUID->缓存”的映射关系,间接地实现了“分区键–>缓存”的映射效果,达到分区的目的。

下面贴出代码供参考:

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) ;
		}
	}
}

  • 20
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值