State概念
state 就是流式计算中持久化了的状态。与Spark Stream中的State类似,但是功能更加强大,能存储的内容更多。Spark Stream的State是一种轻量级的。而Flink中的State则是可以和checkpoint和statebackend配合,可以存储很大一部分数据。
State两种形式
state有两种形式,分别为:OperatorState 以及 KeyedState。主要是看前面有没有keyby操作
OperatorState没有current key概念。KeyedState的数值总是与一个current key对应。
OperatorState只有堆内存一种实现。KeyedState有堆内存和RocksDB两种实现。
OperatorState需要手动实现snapshot和restore方法。KeyedState由backend实现,对用户透明。
State-Backend分类
有三种State-Backend:MemoryStateBackend、FsStateBackend、RocksDBStateBackend。
对于OperatorState来说,三种backend都使用DefaultOperatorStateBackend。将数据存储到内存中。
对于KeyedState来说,MemoryStateBackend和FsStateBackend都会使用HeapKeyedStateBackend,两者的区别点是,MemoryStateBackend会将数据直接传给master节点,数据不落盘,而HeapKeyedStateBackend,如果当数据到达一定量的时候,就会将数据写入文件系统中,然后将路径告诉master节点。RocksDBStateBackend使用的是RocksDBKeyedStateBackend,它会将数据写入RocksDB中,然后将路径告诉master节点。
StateBackend选择
一般来说是在FsStateBackend和RocksDBStateBackend之间选择
FsStateBackend:性能更好,日常存储是在堆内存中,面临着OOM的风险,不支持增量checkpoint
RocksDBStateBackend:将数据序列化成byte数组存储,读的时候还需要反序列,无需担心OOM风险,一般来说,多数是选这种Backend多一点。
RocksDB的state存储
RocksDB中,每个state使用一个Column Family
每个column family使用独占write buffer,整个DB共享一个block cache。
数据是先写入write buffer中,达到一定阈值后,在写入rocksdb中。
state.backend.rocksdb.block.blocksize : 数据块大小,默认4KB 增大会影响减少内存使用,但是会影响读性能。
state.backend.rocksdb.block.cache-size:整个DB的block cache大小,默认8MB,建议调大
state.backend.rocksdb.compaction.level.use-dynamic-size:如果使用LEVEL compaction,在SATA磁盘上,建议配成true,默认false
state.backend.rocksdb.files.open : 最大打开文件数目,-1 意味着没有限制,默认值 5000
state.backend.rocksdb.thread.num:后台flush和compaction的线程数。默认值为1,建议调大。
state.backend.rocksdb.writebuffer.count:每个column family 的writebuffer数目,默认值2,建议调大
state.backend.rocksdb.writebuffer.number-to-merge:写之前的write buffer merge数目,默认值1.建议调大
state.backend.rocksdb.writebuffer.size:每个write buffer的size,默认值4MB,建议调大
State使用心得
operatorState使用建议:
慎重使用长list
在flink中,使用的是long的数组去接受 listValue中offset的位置,如果listValue长度很长,那么这个数组就会很大,就会造成很大的内存使用,并且,这部分内存是传给jobmanager的。
KeyedState使用建议:
如何清空当前state?
state.clear() 只能清理当前key对应的value值
需要借助KeyedStateBackend的 applyToAllKeys方法
当value值很大的极限场景?
受限于JNI bridge API的限制,单个value只支持2^31 bytes(2G)
考虑使用MapState来替代ListState或者ValueState。因为ListState和ValueState是整个都是序列化存储在一个key-value中。而mapstate可以根据key,来存在多个key-value中。
RocksDB的日志可以观察到一些compaction信息,默认存储在flink-io目录下,需要登入到taskmanager里面才能找到。
TTL清理策略
flink state是可以做TTL的,这里不做细讲,这里只说清理策略。
默认情况下,配置了TTL,那么只有在下次读访问的时候,才会触发清理那条过期数据,如果那条数据之后不再被访问,那么该条过期数据也不会被清理。
三种策略:
1. StateTtlConfig.newBuilder(Time.days(7)).cleanupFullSnapshot().bulid(); Full snapshot时候会清理snapshot内容,但是不会清理本地的缓存。
2. StateTtlConfig.newBuilder(Time.days(7)).cleanupIncrementally(10,false).bulid(); Heap state backend的持续清理,这里表示每访问10次状态的数据,就去清理过期数据。如果第二个参数为true,就是,每访问10次record(流中的数据),就去清理过期数据。
3.StateTtlConfig.newBuilder(Time.days(7)).cleanupInRocksdbCompactFilter().build(); RocksDB state backend的持续清理,每次compact的时候清理。
注意:如果使用了Flink SQL,那么清理state会由flink自动接管,当然,如果自定义了UDF,并且在UDF中使用了状态,那么这部分的state,还是要由我们设置TTL来清理。
RocksDBState使用建议
不要创建过多的state,因为:
每个state一个column family,独占write buffer,过多的state会导致占据过多的write buffer。
根本上还是RocksDB StateBackend的native内存无法直接管理
Checkpoint使用建议
一般5min级别足够
checkpoint与record处理共抢一把锁,Checkpoint的同步阶段会影响record的处理
合理设置超时时间
默认的超时时间是10min,如果state规模大,则需要合理配置。最坏情况是创建速度大于删除速度,导致磁盘空间不可用。