接上文: 【翻译】The Broadcast State Pattern(广播状态)
最近尝试了一下Flink 的 Broadcase 功能,在Etl,流表关联场景非常适用:一个流数据量大,一个流数据量小(配置表)需要更新
业务逻辑如下:
注: 正常情况广播流只有一个输出源,更新也在这个源里,这里做了个优化:将广播流的输入源改为两部分配置文件和更新topic(原因:flink 读取文件,读完就结束了无法做更新,而每次从kafka获取全量配置数据,涉及到kafka topic数据的删除时间,除非涉及非常长的删除时间,不然每次读取全量也不太方便),这里不使用flink的CacheFile,因为不能更新
具体业务如下:转码三位城市编码为对应城市中文
1. 自定义输入流,输入三位的城市编码和五位的随机字符串
2. 广播流读取配置文件和配置文件更新topic
3. connect两个流,读取配置文件对应的数据解析数据流输入的数据
自定义输入流如下:
class RadomFunction extends SourceFunction[String]{ var flag = true override def cancel(): Unit = { flag = false } override def run(ctx: SourceFunction.SourceContext[String]): Unit = { while (flag){ for (i <- 0 to 300) { var nu = i.toString while (nu.length < 3) { nu = "0" + nu } ctx.collect(nu + "," + StringUtil.getRandomString(5)) Thread.sleep(2000) } } } }
Etl 代码如下:
import java.io.File import com.venn.flink.util.{StringUtil} import com.venn.index.conf.Common import org.apache.flink.api.common.serialization.SimpleStringSchema import org.apache.flink.api.common.state.MapStateDescriptor import org.apache.flink.api.common.typeinfo.BasicTypeInfo import org.apache.flink.api.scala._ import org.apache.flink.runtime.state.filesystem.FsStateBackend import org.apache.flink.streaming.api.functions.co.BroadcastProcessFunction import org.apache.flink.streaming.api.functions.source.SourceFunction import org.apache.flink.streaming.api.scala.StreamExecutionEnvironment import org.apache.flink.streaming.api.{CheckpointingMode, TimeCharacteristic} import org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumer import org.apache.flink.util.Collector /** * broadcast */ object BroadCastDemo { def main(args: Array[String]): Unit = { val env = StreamExecutionEnvironment.getExecutionEnvironment env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime) if ("/".equals(File.separator)) { val backend = new FsStateBackend(Common.CHECK_POINT_DATA_DIR, true) env.setStateBackend(backend) env.enableCheckpointing(10 * 1000, CheckpointingMode.EXACTLY_ONCE) } else { env.setMaxParallelism(1) env.setParallelism(1) } // 配置更新流 val configSource = new FlinkKafkaConsumer[String]("broad_cast_demo", new SimpleStringSchema, Common.getProp) // 配置流的初始化,可以通过读取配置文件实现 var initFilePath = "" if ("/".equals(File.separator)){ initFilePath = "hdfs:///venn/init_file.txt" }else{ initFilePath = "D:\\idea_out\\broad_cast.txt" } val init = env.readTextFile(initFilePath) val descriptor = new MapStateDescriptor[String, String]("dynamicConfig", BasicTypeInfo.STRING_TYPE_INFO, BasicTypeInfo.STRING_TYPE_INFO) val configStream = env.addSource(configSource).union(init).broadcast(descriptor) val input = env.addSource(new RadomFunction) .connect(configStream) .process(new BroadcastProcessFunction[String, String, String] { override def processBroadcastElement(value: String, ctx: BroadcastProcessFunction[String, String, String]#Context, out: Collector[String]): Unit = { println("new config : " + value) val configMap = ctx.getBroadcastState(descriptor) // process update configMap,读取配置数据,写入广播状态中 val line = value.split(",") configMap.put(line(0), line(1)) } override def processElement(value: String, ctx: BroadcastProcessFunction[String, String, String]#ReadOnlyContext, out: Collector[String]): Unit = { // use give key, return value val configMap = ctx.getBroadcastState(descriptor) // 解析三位城市编码,根据广播状态对应的map,转码为城市对应中文 // println(value) val line = value.split(",") val code = line(0) var va = configMap.get(code) // 不能转码的数据默认输出 中国(code=xxx) if ( va == null){ va = "中国(code="+code+")"; }else{ va = va + "(code="+code+")" } out.collect(va + "," + line(1)) } }) input.print() env.execute("BroadCastDemo") } }
配置数据如下:
001,邯郸市 002,石家庄 003,保定市 004,张家口 005,承德市 006,唐山市 007,廊坊市 008,沧州市 009,衡水市 010,邢台市
数据源数据如下:
001,bGTqQM 002,sCfdSK 003,RWtLNC 004,qkGita 005,fOemDF 006,KRaUmj 007,MNwKdS 008,RgZDlI 009,QbUyeh
转码后输出如下:
邯郸市(code=001),bGTqQM 石家庄(code=002),sCfdSK 保定市(code=003),RWtLNC 张家口(code=004),qkGita 承德市(code=005),fOemDF 唐山市(code=006),KRaUmj 廊坊市(code=007),MNwKdS 沧州市(code=008),RgZDlI 衡水市(code=009),QbUyeh
执行结果如下:
... new config : 047,十堰市 new config : 048,随枣市 new config : 049,荆门市 new config : 050,江汉(仙桃) 邯郸市(code=001),ovLKQN 石家庄(code=002),QTgxXn 保定市(code=003),bIPefX 张家口(code=004),XcdHUd ... 宜昌市(code=045),sQRonA 恩施市(code=046),gfipAY 十堰市(code=047),ASPulh 随枣市(code=048),mqurwg 荆门市(code=049),hfTlue 江汉(仙桃)(code=050),EfiXec 中国(code=051),xGuihq # 不能转码数据 中国(code=052),niMlrb 中国(code=053),fHvIpU 中国(code=054),MdqqCb 中国(code=055),CFgNmM ...
广播流数据更新如下:
new config : 150,xxx # 获取当新配置数据 中国(code=148),fLtwye 中国(code=149),bEJfMP new config : 151,fff xxx(code=150),TTIPii # 新配置数据转码数据 fff(code=151),iJSAjJ 中国(code=152),yBvlUZ new config : 152,ggg
搞定