对于有状态应用的鲁棒性,我们可以通过配置不同的状态后端,设置不同的状态原语,状态维护几方面来为应用的长期运行提供保障性
首先是选择合适的状态后端,不同的状态后端对于应用状态的维护是不同的.
Flink目前提供了三种状态后端,MemoryStateBackend,FsStateBackend以及RocksDBStateBackend
MemoryStateBackend试讲状态以对象的方式存放在JVM中,MapState的实现就是HashMap对象
虽然延迟很低,但是鲁棒性很低,因为可能会伴随着状态不断增加,而最终导致OOM,而且因为存放在内存中,所以具有易失性,所以建议仅仅将MemoryStateBackend用于开发和调试.
FsStateBackend和MemoryStateBackend一样,保存在JVM中,但是会在创建检查点的时候将状态存入远程持久化系统中,因此FsStateBackend具有一定的容错性,但是也存在着OOM的问题.
RocksDBStateBackend会把全部状态保存在RocksDB实例中,RocksDB是一个嵌入式键指对存储 key-value store 可以将数据保存在起来,虽然速度不是很快,但是具有较高的容错性,而且RocksDB支持增量检查点.
不同状态原语的区别
有状态算子的性能很大一方面取决于使用的状态原语
常见的ValueState ListState MapState之间有着不同的性能
比如在RocksDBStateBackend中ValueState会在更新和访问的时候进行完整的序列化和反序列化,
而ListState会将所有的列表条目进行反序列化,但往ListState中添加一个值的时候,只会序列化新添加的值. MapState则是只有读写的键值才需要进行序列化和反序列化
总体而言,在原语之中,更高级的原语必然经历过更好的优化
MapState[K,V]性能肯定比ValueState[HashMap[K,V]]好
ListState[K]性能肯定比ValueState[List[K]]好
而且建议,在函数中,最好只更新一次状态,而不是更新多次状态,会带来额外的序列化开销.
最后,为了避免应用在长期运行后,状态越来越大,我们需要考虑控制算子状态大小,避免无限制的增长
通常来说,就是键值的状态域是不断变化的,对于时间窗口的算子,这个状态域必然会伴随着时间的增长而过期,但是有些自定义的算子,往往对于状态域的设定是不确定的.故可能导致状态的长期不过期,越来越大.
那么这就意味着,在设计一个有状态算子的时候,需要确保能够清理些无用的状态,这个清理的任务,可以通过注册计时器来完成.在计时器中的回调函数来访问状态,并通过一些判断逻辑来清理状态.
这里就可以使用处理函数来进行计时器的注册.