Repository: zhisheng17/flink-learning Branch: master Commit: d731cee76180 Files: 752 Total size: 1.8 MB Directory structure: gitextract_8nri6nxi/ ├── .gitignore ├── Flink-Forward-2020/ │ └── README.md ├── Flink-Forward-Asia-2019-PPT/ │ └── README.md ├── Flink-Forward-Asia-2020-PPT/ │ └── README.md ├── Flink-Forward-Asia-2021-PPT/ │ └── README.md ├── LICENSE ├── README.md ├── books/ │ ├── README.md │ ├── flink-in-action-1.1.md │ ├── flink-in-action-1.2.md │ ├── flink-in-action-1.3.md │ ├── flink-in-action-10.1.md │ ├── flink-in-action-10.2.md │ ├── flink-in-action-11.1.md │ ├── flink-in-action-11.2.md │ ├── flink-in-action-11.3.md │ ├── flink-in-action-11.4.md │ ├── flink-in-action-11.5.md │ ├── flink-in-action-12.1.md │ ├── flink-in-action-12.2.md │ ├── flink-in-action-12.3.md │ ├── flink-in-action-2.1.md │ ├── flink-in-action-2.2.md │ ├── flink-in-action-2.3.md │ ├── flink-in-action-2.4.md │ ├── flink-in-action-3.1.md │ ├── flink-in-action-3.10.md │ ├── flink-in-action-3.11.md │ ├── flink-in-action-3.12.md │ ├── flink-in-action-3.2.md │ ├── flink-in-action-3.3.md │ ├── flink-in-action-3.4.md │ ├── flink-in-action-3.5.md │ ├── flink-in-action-3.6.md │ ├── flink-in-action-3.7.md │ ├── flink-in-action-3.8.md │ ├── flink-in-action-3.9.md │ ├── flink-in-action-4.1.md │ ├── flink-in-action-4.2.md │ ├── flink-in-action-4.3.md │ ├── flink-in-action-5.1.md │ ├── flink-in-action-5.2.md │ ├── flink-in-action-6.1.md │ ├── flink-in-action-6.2.md │ ├── flink-in-action-6.3.md │ ├── flink-in-action-6.4.md │ ├── flink-in-action-6.5.md │ ├── flink-in-action-7.1.md │ ├── flink-in-action-7.2.md │ ├── flink-in-action-8.1.md │ ├── flink-in-action-8.2.md │ ├── flink-in-action-9.1.md │ ├── flink-in-action-9.2.md │ ├── flink-in-action-9.3.md │ ├── flink-in-action-9.4.md │ ├── flink-in-action-9.5.md │ └── flink-in-action-9.6.md ├── flink-learning-basic/ │ ├── README.md │ ├── flink-learning-data-sinks/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── zhisheng/ │ │ │ └── data/ │ │ │ └── sinks/ │ │ │ ├── Main.java │ │ │ ├── Main2.java │ │ │ ├── model/ │ │ │ │ └── Student.java │ │ │ ├── sinks/ │ │ │ │ ├── MySink.java │ │ │ │ └── SinkToMySQL.java │ │ │ └── utils/ │ │ │ └── KafkaUtil.java │ │ └── resources/ │ │ ├── application.properties │ │ ├── logback.xml │ │ └── student.sql │ ├── flink-learning-data-sources/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── zhisheng/ │ │ │ └── data/ │ │ │ └── sources/ │ │ │ ├── Main.java │ │ │ ├── Main2.java │ │ │ ├── ScheduleMain.java │ │ │ ├── model/ │ │ │ │ ├── Rule.java │ │ │ │ └── Student.java │ │ │ ├── sources/ │ │ │ │ └── SourceFromMySQL.java │ │ │ └── utils/ │ │ │ ├── KafkaUtil.java │ │ │ └── MySQLUtil.java │ │ └── resources/ │ │ ├── application.properties │ │ ├── logback.xml │ │ ├── rule.sql │ │ └── student.sql │ ├── flink-learning-libraries/ │ │ ├── README.md │ │ ├── flink-learning-libraries-cep/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── zhisheng/ │ │ │ │ └── libraries/ │ │ │ │ └── cep/ │ │ │ │ ├── CEPMain.java │ │ │ │ ├── CombinePatternMain.java │ │ │ │ ├── IndividualPatternQuantifier.java │ │ │ │ └── model/ │ │ │ │ ├── Alert.java │ │ │ │ ├── Event.java │ │ │ │ └── SubEvent.java │ │ │ └── resources/ │ │ │ ├── application.properties │ │ │ └── logback.xml │ │ ├── flink-learning-libraries-state-processor-api/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── zhisheng/ │ │ │ │ └── libraries/ │ │ │ │ └── stateProcessApi/ │ │ │ │ ├── Main.java │ │ │ │ └── StatefulFunctionWithTime.java │ │ │ └── resources/ │ │ │ ├── application.properties │ │ │ └── logback.xml │ │ └── pom.xml │ ├── flink-learning-metrics/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ └── java/ │ │ └── com/ │ │ └── zhisheng/ │ │ └── metrics/ │ │ └── custom/ │ │ ├── CustomCounterMetrics.java │ │ ├── CustomCounterMetrics2.java │ │ ├── CustomCounterMetrics3.java │ │ ├── CustomGaugeMetrics.java │ │ ├── CustomHistogramMetrics.java │ │ └── CustomMeterMetrics.java │ ├── flink-learning-state/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── zhisheng/ │ │ │ └── state/ │ │ │ ├── Main.java │ │ │ ├── metadata/ │ │ │ │ └── MetadataSerializer.java │ │ │ ├── operator/ │ │ │ │ └── state/ │ │ │ │ ├── UnionListStateExample.java │ │ │ │ └── util/ │ │ │ │ └── UnionListStateUtil.java │ │ │ └── queryablestate/ │ │ │ ├── ClimateLog.java │ │ │ ├── QueryClient.java │ │ │ └── QuerybleStateStream.java │ │ └── resources/ │ │ └── _metadata │ ├── flink-learning-window/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── zhisheng/ │ │ │ │ ├── constant/ │ │ │ │ │ └── WindowConstant.java │ │ │ │ ├── function/ │ │ │ │ │ ├── CustomSource.java │ │ │ │ │ ├── CustomTrigger.java │ │ │ │ │ └── LineSplitter.java │ │ │ │ └── window/ │ │ │ │ ├── CustomTriggerMain.java │ │ │ │ ├── Main.java │ │ │ │ ├── Main2.java │ │ │ │ ├── Main3.java │ │ │ │ ├── Main4.java │ │ │ │ ├── Main5.java │ │ │ │ └── WindowAll.java │ │ │ └── resources/ │ │ │ ├── application.properties │ │ │ └── logback.xml │ │ └── test/ │ │ └── java/ │ │ └── TestWindowSize.java │ └── pom.xml ├── flink-learning-cdc/ │ ├── README.md │ ├── flink-db2-cdc/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ └── java/ │ │ └── com/ │ │ └── zhisheng/ │ │ └── cdc/ │ │ └── db2/ │ │ └── Db2CDCExample.java │ ├── flink-mongodb-cdc/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ └── java/ │ │ └── com/ │ │ └── zhisheng/ │ │ └── cdc/ │ │ └── mongodb/ │ │ └── MongoDBCDCExample.java │ ├── flink-mysql-cdc/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ └── java/ │ │ └── com/ │ │ └── zhisheng/ │ │ └── cdc/ │ │ └── mysql/ │ │ └── MysqlCDCExample.java │ ├── flink-oceanbase-cdc/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ └── java/ │ │ └── com/ │ │ └── zhisheng/ │ │ └── cdc/ │ │ └── oceanbase/ │ │ └── OceanBaseCDCExample.java │ ├── flink-oracle-cdc/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ └── java/ │ │ └── com/ │ │ └── zhisheng/ │ │ └── cdc/ │ │ └── oracle/ │ │ └── OracleCDCExample.java │ ├── flink-postgres-cdc/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ └── java/ │ │ └── com/ │ │ └── zhisheng/ │ │ └── cdc/ │ │ └── postgres/ │ │ └── PostgresCDCExample.java │ ├── flink-sqlserver-cdc/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ └── java/ │ │ └── com/ │ │ └── zhisheng/ │ │ └── cdc/ │ │ └── sqlserver/ │ │ └── SqlServerCDCExample.java │ ├── flink-tidb-cdc/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ └── java/ │ │ └── com/ │ │ └── zhisheng/ │ │ └── cdc/ │ │ └── tidb/ │ │ └── TidbCDCExample.java │ └── pom.xml ├── flink-learning-common/ │ ├── README.md │ ├── pom.xml │ └── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── zhisheng/ │ │ │ └── common/ │ │ │ ├── constant/ │ │ │ │ ├── MachineConstant.java │ │ │ │ └── PropertiesConstants.java │ │ │ ├── model/ │ │ │ │ ├── LogEvent.java │ │ │ │ ├── MetricEvent.java │ │ │ │ ├── OrderEvent.java │ │ │ │ ├── OrderLineEvent.java │ │ │ │ ├── ProductEvent.java │ │ │ │ ├── ShopEvent.java │ │ │ │ ├── UserEvent.java │ │ │ │ └── WordEvent.java │ │ │ ├── schemas/ │ │ │ │ ├── KafkaMetricSchema.java │ │ │ │ ├── LogSchema.java │ │ │ │ ├── MetricSchema.java │ │ │ │ ├── OrderLineSchema.java │ │ │ │ ├── OrderSchema.java │ │ │ │ ├── ProductSchema.java │ │ │ │ ├── ShopSchema.java │ │ │ │ └── UserSchema.java │ │ │ ├── utils/ │ │ │ │ ├── CheckPointUtil.java │ │ │ │ ├── DateUtil.java │ │ │ │ ├── ExecutionEnvUtil.java │ │ │ │ ├── GsonUtil.java │ │ │ │ ├── HttpUtil.java │ │ │ │ └── KafkaConfigUtil.java │ │ │ └── watermarks/ │ │ │ └── MetricWatermark.java │ │ └── resources/ │ │ └── product.sql │ └── test/ │ └── java/ │ └── com/ │ └── zhisheng/ │ └── common/ │ └── utils/ │ └── DateUtilTests.java ├── flink-learning-configuration-center/ │ ├── flink-learning-configuration-center-apollo/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── zhisheng/ │ │ │ └── configuration/ │ │ │ └── apollo/ │ │ │ └── FlinkApolloTest.java │ │ └── resources/ │ │ └── META-INF/ │ │ └── app.properties │ ├── flink-learning-configuration-center-nacos/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ └── java/ │ │ └── com/ │ │ └── zhisheng/ │ │ └── configuration/ │ │ └── nacos/ │ │ ├── FlinkNacosTest.java │ │ └── FlinkNacosTest2.java │ └── pom.xml ├── flink-learning-connectors/ │ ├── README.md │ ├── flink-learning-connectors-activemq/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── zhisheng/ │ │ │ └── connectors/ │ │ │ └── activemq/ │ │ │ └── Main.java │ │ └── resources/ │ │ ├── application.properties │ │ └── logback.xml │ ├── flink-learning-connectors-cassandra/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── zhisheng/ │ │ │ └── connectors/ │ │ │ └── cassandra/ │ │ │ ├── batch/ │ │ │ │ ├── BatchExample.java │ │ │ │ ├── BatchPojoExample.java │ │ │ │ └── CustomCassandraAnnotatedPojo.java │ │ │ └── streaming/ │ │ │ ├── CassandraPojoSinkExample.java │ │ │ ├── CassandraTupleSinkExample.java │ │ │ ├── CassandraTupleWriteAheadSinkExample.java │ │ │ └── Message.java │ │ └── resources/ │ │ ├── application.properties │ │ └── logback.xml │ ├── flink-learning-connectors-clickhouse/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── zhisheng/ │ │ │ └── connectors/ │ │ │ └── clickhouse/ │ │ │ ├── ClickhouseSink.java │ │ │ ├── applied/ │ │ │ │ ├── ClickhouseSinkBuffer.java │ │ │ │ ├── ClickhouseSinkManager.java │ │ │ │ ├── ClickhouseSinkScheduledChecker.java │ │ │ │ └── ClickhouseWriter.java │ │ │ ├── model/ │ │ │ │ ├── ClickhouseClusterSettings.java │ │ │ │ ├── ClickhouseRequestBlank.java │ │ │ │ ├── ClickhouseSinkCommonParams.java │ │ │ │ └── ClickhouseSinkConsts.java │ │ │ └── util/ │ │ │ ├── ConfigUtil.java │ │ │ └── ThreadUtil.java │ │ └── resources/ │ │ ├── application.properties │ │ ├── logback.xml │ │ └── reference.conf │ ├── flink-learning-connectors-es/ │ │ ├── flink-learning-connectors-es-common/ │ │ │ └── pom.xml │ │ ├── flink-learning-connectors-es-universal/ │ │ │ ├── README.md │ │ │ └── pom.xml │ │ ├── flink-learning-connectors-es5/ │ │ │ ├── README.md │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── zhisheng/ │ │ │ │ └── connectors/ │ │ │ │ └── es5/ │ │ │ │ └── Sink2ES5Main.java │ │ │ └── resources/ │ │ │ └── logback.xml │ │ ├── flink-learning-connectors-es6/ │ │ │ ├── README.md │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── zhisheng/ │ │ │ │ └── connectors/ │ │ │ │ └── es6/ │ │ │ │ ├── Sink2ES6Main.java │ │ │ │ └── utils/ │ │ │ │ ├── ESSinkUtil.java │ │ │ │ └── RetryRequestFailureHandler.java │ │ │ └── resources/ │ │ │ ├── application.properties │ │ │ ├── es_index_template.json │ │ │ └── logback.xml │ │ ├── flink-learning-connectors-es7/ │ │ │ ├── README.md │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── zhisheng/ │ │ │ │ └── connectors/ │ │ │ │ └── es7/ │ │ │ │ ├── Sink2ES7Main.java │ │ │ │ └── util/ │ │ │ │ ├── ESSinkUtil.java │ │ │ │ └── RetryRequestFailureHandler.java │ │ │ └── resources/ │ │ │ ├── application.properties │ │ │ ├── es_index_template.json │ │ │ └── logback.xml │ │ └── pom.xml │ ├── flink-learning-connectors-flume/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── zhisheng/ │ │ │ └── connectors/ │ │ │ └── flume/ │ │ │ ├── FlumeEventBuilder.java │ │ │ ├── FlumeSink.java │ │ │ ├── Main.java │ │ │ └── utils/ │ │ │ └── FlumeUtil.java │ │ └── resources/ │ │ ├── application.properties │ │ └── logback.xml │ ├── flink-learning-connectors-gcp-pubsub/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── zhisheng/ │ │ │ └── connectors/ │ │ │ └── gcp/ │ │ │ └── pubsub/ │ │ │ ├── IntegerSerializer.java │ │ │ ├── Main.java │ │ │ └── PubSubPublisherUtil.java │ │ └── resources/ │ │ ├── application.properties │ │ └── logback.xml │ ├── flink-learning-connectors-hbase/ │ │ ├── README.md │ │ ├── flink-learning-connectors-hbase-1.4/ │ │ │ └── pom.xml │ │ ├── flink-learning-connectors-hbase-2.2/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── java/ │ │ │ │ └── hbase/ │ │ │ │ ├── HBaseStreamWriteMain.java │ │ │ │ ├── Main.java │ │ │ │ └── constant/ │ │ │ │ └── HBaseConstant.java │ │ │ └── resources/ │ │ │ ├── application.properties │ │ │ └── logback.xml │ │ └── pom.xml │ ├── flink-learning-connectors-hdfs/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── zhisheng/ │ │ │ └── connectors/ │ │ │ └── hdfs/ │ │ │ └── Main.java │ │ └── resources/ │ │ ├── application.properties │ │ └── logback.xml │ ├── flink-learning-connectors-hive/ │ │ ├── README.md │ │ ├── flink-learning-connectors-hive-1.2.2/ │ │ │ └── pom.xml │ │ ├── flink-learning-connectors-hive-2.2.0/ │ │ │ └── pom.xml │ │ ├── flink-learning-connectors-hive-2.3.6/ │ │ │ └── pom.xml │ │ ├── flink-learning-connectors-hive-3.1.2/ │ │ │ └── pom.xml │ │ └── pom.xml │ ├── flink-learning-connectors-influxdb/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── zhisheng/ │ │ │ └── connectors/ │ │ │ └── influxdb/ │ │ │ ├── InfluxDBConfig.java │ │ │ ├── InfluxDBSink.java │ │ │ └── Main.java │ │ └── resources/ │ │ ├── application.properties │ │ └── logback.xml │ ├── flink-learning-connectors-jdbc/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── zhisheng/ │ │ │ └── connectors/ │ │ │ └── jdbc/ │ │ │ └── Main.java │ │ └── resources/ │ │ ├── application.properties │ │ └── logback.xml │ ├── flink-learning-connectors-kafka/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── zhisheng/ │ │ │ └── connectors/ │ │ │ └── kafka/ │ │ │ ├── FlinkKafkaConsumerTest1.java │ │ │ ├── FlinkKafkaConsumerTest2.java │ │ │ ├── FlinkKafkaProducerTest1.java │ │ │ ├── FlinkKafkaSchemaTest1.java │ │ │ ├── JSONKeyValueDeserializationSchemaTest.java │ │ │ ├── KafkaDeserializationSchemaTest.java │ │ │ └── Main.java │ │ └── resources/ │ │ ├── application.properties │ │ └── logback.xml │ ├── flink-learning-connectors-kudu/ │ │ ├── README.md │ │ └── pom.xml │ ├── flink-learning-connectors-mysql/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── zhisheng/ │ │ │ └── connectors/ │ │ │ └── mysql/ │ │ │ ├── Main.java │ │ │ ├── model/ │ │ │ │ └── Student.java │ │ │ ├── sinks/ │ │ │ │ └── SinkToMySQL.java │ │ │ └── utils/ │ │ │ └── KafkaUtil.java │ │ └── resources/ │ │ ├── application.properties │ │ └── logback.xml │ ├── flink-learning-connectors-netty/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ └── java/ │ │ └── com/ │ │ └── zhisheng/ │ │ └── connectors/ │ │ └── netty/ │ │ └── Main.java │ ├── flink-learning-connectors-nifi/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── zhisheng/ │ │ │ └── connectors/ │ │ │ └── nifi/ │ │ │ ├── NiFiSinkMain.java │ │ │ └── NiFiSourceMain.java │ │ └── resources/ │ │ ├── application.properties │ │ └── logback.xml │ ├── flink-learning-connectors-pulsar/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── zhisheng/ │ │ │ └── connectors/ │ │ │ └── pulsar/ │ │ │ ├── PulsarSinkMain.java │ │ │ └── PulsarSourceMain.java │ │ └── resources/ │ │ ├── application.properties │ │ └── logback.xml │ ├── flink-learning-connectors-rabbitmq/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── zhisheng/ │ │ │ └── connectors/ │ │ │ └── rabbitmq/ │ │ │ ├── Main.java │ │ │ ├── Main1.java │ │ │ ├── model/ │ │ │ │ └── EndPoint.java │ │ │ └── utils/ │ │ │ └── RabbitMQProducerUtil.java │ │ └── resources/ │ │ ├── application.properties │ │ └── logback.xml │ ├── flink-learning-connectors-redis/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── zhisheng/ │ │ │ │ └── connectors/ │ │ │ │ └── redis/ │ │ │ │ ├── Main.java │ │ │ │ └── utils/ │ │ │ │ └── ProductUtil.java │ │ │ └── resources/ │ │ │ ├── application.properties │ │ │ └── logback.xml │ │ └── test/ │ │ └── java/ │ │ └── RedisTest.java │ ├── flink-learning-connectors-rocketmq/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── zhisheng/ │ │ │ └── connectors/ │ │ │ └── rocketmq/ │ │ │ ├── RocketMQConfig.java │ │ │ ├── RocketMQSink.java │ │ │ ├── RocketMQSource.java │ │ │ ├── RocketMQUtils.java │ │ │ ├── RunningChecker.java │ │ │ ├── common/ │ │ │ │ ├── selector/ │ │ │ │ │ ├── DefaultTopicSelector.java │ │ │ │ │ ├── SimpleTopicSelector.java │ │ │ │ │ └── TopicSelector.java │ │ │ │ └── serialization/ │ │ │ │ ├── KeyValueDeserializationSchema.java │ │ │ │ ├── KeyValueSerializationSchema.java │ │ │ │ ├── SimpleKeyValueDeserializationSchema.java │ │ │ │ └── SimpleKeyValueSerializationSchema.java │ │ │ └── example/ │ │ │ ├── RocketMQFlinkExample.java │ │ │ ├── SimpleConsumer.java │ │ │ └── SimpleProducer.java │ │ └── resources/ │ │ ├── application.properties │ │ └── logback.xml │ └── pom.xml ├── flink-learning-core/ │ ├── pom.xml │ └── src/ │ └── main/ │ └── java/ │ └── com/ │ └── zhisheng/ │ └── core/ │ ├── exception/ │ │ └── FlinkRuntimeException.java │ ├── factory/ │ │ ├── DeserializerFactory.java │ │ ├── SerializerFactory.java │ │ ├── SinkFactory.java │ │ └── SourceFactory.java │ └── utils/ │ ├── ArrayUtils.java │ ├── CollectionUtil.java │ ├── ExecutorUtils.java │ ├── StringUtils.java │ └── TimeUtils.java ├── flink-learning-datalake/ │ ├── README.md │ ├── flink-learning-datalake-deltalake/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ └── java/ │ │ └── com/ │ │ └── zhisheng/ │ │ └── datalake/ │ │ └── delta/ │ │ └── DeltaLakeExample.java │ ├── flink-learning-datalake-hudi/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ └── java/ │ │ └── com/ │ │ └── zhisheng/ │ │ └── datalake/ │ │ └── hudi/ │ │ ├── HudiCDCSyncExample.java │ │ ├── HudiDataLakeExample.java │ │ └── HudiStreamingWriteExample.java │ ├── flink-learning-datalake-iceberg/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ └── java/ │ │ └── com/ │ │ └── zhisheng/ │ │ └── datalake/ │ │ └── iceberg/ │ │ ├── IcebergCDCSyncExample.java │ │ ├── IcebergDataLakeExample.java │ │ └── IcebergStreamingWriteExample.java │ ├── flink-learning-paimon/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ └── java/ │ │ └── com/ │ │ └── zhisheng/ │ │ └── datalake/ │ │ └── paimon/ │ │ ├── PaimonCDCSyncExample.java │ │ ├── PaimonDataLakeExample.java │ │ └── PaimonStreamingWriteExample.java │ └── pom.xml ├── flink-learning-examples/ │ ├── README.md │ ├── pom.xml │ └── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── zhisheng/ │ │ │ └── examples/ │ │ │ ├── batch/ │ │ │ │ ├── accumulator/ │ │ │ │ │ ├── Main.java │ │ │ │ │ └── Main2.java │ │ │ │ └── wordcount/ │ │ │ │ └── Main.java │ │ │ ├── streaming/ │ │ │ │ ├── async/ │ │ │ │ │ └── AsyncIOExample.java │ │ │ │ ├── broadcast/ │ │ │ │ │ ├── BroadcastAlertRule.java │ │ │ │ │ ├── DataSetBrocastMain.java │ │ │ │ │ ├── GetAlarmNotifyData.java │ │ │ │ │ ├── Main.java │ │ │ │ │ ├── Main2.java │ │ │ │ │ └── MyBroadcastProcessFunction.java │ │ │ │ ├── chain/ │ │ │ │ │ ├── DefaultChainMain.java │ │ │ │ │ ├── DisableChainMain.java │ │ │ │ │ ├── DisableChainMain1.java │ │ │ │ │ ├── DisableChainMain3.java │ │ │ │ │ ├── ExecutionPlanMain.java │ │ │ │ │ ├── SharingGroupMain.java │ │ │ │ │ └── StartNewChainMain.java │ │ │ │ ├── checkpoint/ │ │ │ │ │ ├── Main.java │ │ │ │ │ ├── PvStatExactlyOnce.java │ │ │ │ │ ├── PvStatLocalKeyByExactlyOnce.java │ │ │ │ │ └── util/ │ │ │ │ │ └── PvStatExactlyOnceKafkaUtil.java │ │ │ │ ├── config/ │ │ │ │ │ ├── ConfigurationMain.java │ │ │ │ │ ├── ConfigurationMain1.java │ │ │ │ │ ├── ParameterToolGetArgsMain.java │ │ │ │ │ ├── ParameterToolGetPropertiesMain.java │ │ │ │ │ └── ParameterToolGetSystemMain.java │ │ │ │ ├── file/ │ │ │ │ │ └── Main.java │ │ │ │ ├── join/ │ │ │ │ │ ├── WindowJoin.java │ │ │ │ │ └── WindowJoinSampleData.java │ │ │ │ ├── ml/ │ │ │ │ │ ├── IncrementalLearningSkeleton.java │ │ │ │ │ └── IncrementalLearningSkeletonData.java │ │ │ │ ├── parallelism/ │ │ │ │ │ └── Main.java │ │ │ │ ├── processFunction/ │ │ │ │ │ ├── KeyedProcessFunctionMain.java │ │ │ │ │ └── ProcessFunctionMain.java │ │ │ │ ├── remote/ │ │ │ │ │ └── Main.java │ │ │ │ ├── restartStrategy/ │ │ │ │ │ ├── AEMain.java │ │ │ │ │ ├── DefaultRestartStrategyMain.java │ │ │ │ │ ├── EnableCheckpointMain.java │ │ │ │ │ ├── FailureRateRestartStrategyMain.java │ │ │ │ │ ├── FixedDelayRestartStrategyMain.java │ │ │ │ │ └── NoRestartStrategyMain.java │ │ │ │ ├── sideoutput/ │ │ │ │ │ ├── FilterEvent.java │ │ │ │ │ ├── Main.java │ │ │ │ │ └── SideOutputEvent.java │ │ │ │ ├── socket/ │ │ │ │ │ ├── LambdaMain.java │ │ │ │ │ └── Main.java │ │ │ │ ├── state/ │ │ │ │ │ └── StateMain.java │ │ │ │ ├── watermark/ │ │ │ │ │ ├── Main.java │ │ │ │ │ ├── Main1.java │ │ │ │ │ ├── Main2.java │ │ │ │ │ ├── Main3.java │ │ │ │ │ ├── Main4.java │ │ │ │ │ ├── Word.java │ │ │ │ │ ├── WordPeriodicWatermark.java │ │ │ │ │ └── WordPunctuatedWatermark.java │ │ │ │ └── wordcount/ │ │ │ │ └── Main.java │ │ │ └── util/ │ │ │ ├── MySQLUtil.java │ │ │ └── ThrottledIterator.java │ │ └── resources/ │ │ ├── alarm-notify.sql │ │ ├── application.properties │ │ └── logback.xml │ └── test/ │ └── java/ │ └── Test1.java ├── flink-learning-extends/ │ ├── FlinkLogKafkaAppender/ │ │ ├── KafkaAppenderCommon/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── zhisheng/ │ │ │ └── flink/ │ │ │ ├── model/ │ │ │ │ └── LogEvent.java │ │ │ └── util/ │ │ │ ├── ExceptionUtil.java │ │ │ └── JacksonUtil.java │ │ ├── Log4j2KafkaAppender/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ ├── main/ │ │ │ │ ├── java/ │ │ │ │ │ └── com/ │ │ │ │ │ └── zhisheng/ │ │ │ │ │ └── log/ │ │ │ │ │ └── appender/ │ │ │ │ │ └── KafkaLog4j2Appender.java │ │ │ │ └── resources/ │ │ │ │ └── log4j2-example.properties │ │ │ └── test/ │ │ │ └── java/ │ │ │ └── ExceptionUtilTest.java │ │ ├── Log4jKafkaAppender/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── zhisheng/ │ │ │ │ └── log/ │ │ │ │ └── appender/ │ │ │ │ └── KafkaLog4jAppender.java │ │ │ └── resources/ │ │ │ └── log4j-example.properties │ │ ├── README.md │ │ └── pom.xml │ ├── README.md │ ├── flink-metrics/ │ │ ├── flink-metrics-kafka/ │ │ │ ├── README.md │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── apache/ │ │ │ │ └── flink/ │ │ │ │ └── metrics/ │ │ │ │ └── kafka/ │ │ │ │ ├── KafkaReporter.java │ │ │ │ ├── KafkaReporterFactory.java │ │ │ │ ├── KafkaReporterOptions.java │ │ │ │ ├── MetricEvent.java │ │ │ │ └── util/ │ │ │ │ └── JacksonUtil.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ └── services/ │ │ │ └── org.apache.flink.metrics.reporter.MetricReporterFactory │ │ ├── flink-metrics-prometheus/ │ │ │ ├── README.md │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ ├── main/ │ │ │ │ ├── java/ │ │ │ │ │ └── org/ │ │ │ │ │ └── apache/ │ │ │ │ │ └── flink/ │ │ │ │ │ └── metrics/ │ │ │ │ │ └── prometheus/ │ │ │ │ │ ├── AbstractPrometheusReporter.java │ │ │ │ │ ├── ClusterMode.java │ │ │ │ │ ├── PrometheusPushGatewayReporter.java │ │ │ │ │ ├── PrometheusPushGatewayReporterFactory.java │ │ │ │ │ ├── PrometheusPushGatewayReporterOptions.java │ │ │ │ │ ├── PrometheusReporter.java │ │ │ │ │ └── PrometheusReporterFactory.java │ │ │ │ └── resources/ │ │ │ │ └── META-INF/ │ │ │ │ ├── NOTICE │ │ │ │ └── services/ │ │ │ │ └── org.apache.flink.metrics.reporter.MetricReporterFactory │ │ │ └── test/ │ │ │ └── resources/ │ │ │ └── log4j2-test.properties │ │ └── pom.xml │ └── pom.xml ├── flink-learning-k8s/ │ ├── README.md │ ├── blogs/ │ │ ├── Flink HA 配置.md │ │ ├── Flink K8s Pod 增加环境变量.md │ │ ├── Kubernetes 入门之知识点梳理.md │ │ ├── Pod 异常问题排查.md │ │ └── 合理设置 Request 与 Limit.md │ ├── dockerfile/ │ │ ├── Dockerfile-Hadoop-Hive │ │ ├── Dockerfile-example-statemachine │ │ ├── Dockerfile-flink-1.12.0-jar │ │ ├── Dockerfile-flink-1.12.0-sql │ │ ├── build_flink_docker_images.sh │ │ ├── build_ingress.sh │ │ ├── docker-entrypoint.sh │ │ └── ingress_template.yaml │ ├── flink-k8s/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── apache/ │ │ │ └── flink/ │ │ │ └── kubernetes/ │ │ │ ├── KubernetesClusterClientFactory.java │ │ │ ├── KubernetesClusterDescriptor.java │ │ │ ├── KubernetesResourceManagerDriver.java │ │ │ ├── KubernetesWorkerNode.java │ │ │ ├── cli/ │ │ │ │ └── KubernetesSessionCli.java │ │ │ ├── configuration/ │ │ │ │ ├── KubernetesConfigOptions.java │ │ │ │ ├── KubernetesConfigOptionsInternal.java │ │ │ │ ├── KubernetesDeploymentTarget.java │ │ │ │ ├── KubernetesHighAvailabilityOptions.java │ │ │ │ ├── KubernetesLeaderElectionConfiguration.java │ │ │ │ └── KubernetesResourceManagerDriverConfiguration.java │ │ │ ├── entrypoint/ │ │ │ │ ├── KubernetesApplicationClusterEntrypoint.java │ │ │ │ ├── KubernetesEntrypointUtils.java │ │ │ │ ├── KubernetesResourceManagerFactory.java │ │ │ │ ├── KubernetesSessionClusterEntrypoint.java │ │ │ │ └── KubernetesWorkerResourceSpecFactory.java │ │ │ ├── executors/ │ │ │ │ ├── KubernetesSessionClusterExecutor.java │ │ │ │ └── KubernetesSessionClusterExecutorFactory.java │ │ │ ├── highavailability/ │ │ │ │ ├── KubernetesCheckpointIDCounter.java │ │ │ │ ├── KubernetesCheckpointRecoveryFactory.java │ │ │ │ ├── KubernetesCheckpointStoreUtil.java │ │ │ │ ├── KubernetesHaServices.java │ │ │ │ ├── KubernetesHaServicesFactory.java │ │ │ │ ├── KubernetesJobGraphStoreUtil.java │ │ │ │ ├── KubernetesLeaderElectionDriver.java │ │ │ │ ├── KubernetesLeaderElectionDriverFactory.java │ │ │ │ ├── KubernetesLeaderRetrievalDriver.java │ │ │ │ ├── KubernetesLeaderRetrievalDriverFactory.java │ │ │ │ ├── KubernetesRunningJobsRegistry.java │ │ │ │ └── KubernetesStateHandleStore.java │ │ │ ├── kubeclient/ │ │ │ │ ├── Endpoint.java │ │ │ │ ├── Fabric8FlinkKubeClient.java │ │ │ │ ├── FlinkKubeClient.java │ │ │ │ ├── FlinkKubeClientFactory.java │ │ │ │ ├── FlinkPod.java │ │ │ │ ├── KubeClientFactory.java │ │ │ │ ├── KubernetesJobManagerSpecification.java │ │ │ │ ├── decorators/ │ │ │ │ │ ├── AbstractKubernetesStepDecorator.java │ │ │ │ │ ├── EnvSecretsDecorator.java │ │ │ │ │ ├── ExternalServiceDecorator.java │ │ │ │ │ ├── FlinkConfMountDecorator.java │ │ │ │ │ ├── HadoopConfMountDecorator.java │ │ │ │ │ ├── HiveConfMountDecorator.java │ │ │ │ │ ├── InitJobManagerDecorator.java │ │ │ │ │ ├── InitTaskManagerDecorator.java │ │ │ │ │ ├── InternalServiceDecorator.java │ │ │ │ │ ├── JavaCmdJobManagerDecorator.java │ │ │ │ │ ├── JavaCmdTaskManagerDecorator.java │ │ │ │ │ ├── KerberosMountDecorator.java │ │ │ │ │ ├── KubernetesStepDecorator.java │ │ │ │ │ └── MountSecretsDecorator.java │ │ │ │ ├── factory/ │ │ │ │ │ ├── KubernetesJobManagerFactory.java │ │ │ │ │ └── KubernetesTaskManagerFactory.java │ │ │ │ ├── parameters/ │ │ │ │ │ ├── AbstractKubernetesParameters.java │ │ │ │ │ ├── KubernetesJobManagerParameters.java │ │ │ │ │ ├── KubernetesParameters.java │ │ │ │ │ └── KubernetesTaskManagerParameters.java │ │ │ │ └── resources/ │ │ │ │ ├── AbstractKubernetesWatcher.java │ │ │ │ ├── KubernetesConfigMap.java │ │ │ │ ├── KubernetesConfigMapWatcher.java │ │ │ │ ├── KubernetesException.java │ │ │ │ ├── KubernetesLeaderElector.java │ │ │ │ ├── KubernetesPod.java │ │ │ │ ├── KubernetesPodsWatcher.java │ │ │ │ ├── KubernetesResource.java │ │ │ │ ├── KubernetesSecretEnvVar.java │ │ │ │ ├── KubernetesService.java │ │ │ │ ├── KubernetesToleration.java │ │ │ │ ├── KubernetesTooOldResourceVersionException.java │ │ │ │ └── KubernetesWatch.java │ │ │ ├── taskmanager/ │ │ │ │ └── KubernetesTaskExecutorRunner.java │ │ │ └── utils/ │ │ │ ├── Constants.java │ │ │ └── KubernetesUtils.java │ │ └── resources/ │ │ └── META-INF/ │ │ ├── NOTICE │ │ └── services/ │ │ ├── org.apache.flink.client.deployment.ClusterClientFactory │ │ └── org.apache.flink.core.execution.PipelineExecutorFactory │ └── pom.xml ├── flink-learning-monitor/ │ ├── README.md │ ├── flink-learning-monitor-alert/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── zhisheng/ │ │ │ │ └── alert/ │ │ │ │ ├── alert/ │ │ │ │ │ ├── AsyncIOAlert.java │ │ │ │ │ ├── BroadcastUpdateAlertRule.java │ │ │ │ │ ├── LogEventAlert.java │ │ │ │ │ └── OutageAlert.java │ │ │ │ ├── function/ │ │ │ │ │ ├── AlertRuleAsyncIOFunction.java │ │ │ │ │ ├── GetAlertRuleSourceFunction.java │ │ │ │ │ └── OutageProcessFunction.java │ │ │ │ ├── model/ │ │ │ │ │ ├── AlertEvent.java │ │ │ │ │ ├── AlertRule.java │ │ │ │ │ ├── AtMobiles.java │ │ │ │ │ ├── BaseMessage.java │ │ │ │ │ ├── Email.java │ │ │ │ │ ├── LinkMessage.java │ │ │ │ │ ├── MarkDownMessage.java │ │ │ │ │ ├── MessageType.java │ │ │ │ │ ├── OutageMetricEvent.java │ │ │ │ │ ├── TextMessage.java │ │ │ │ │ └── WorkNotify.java │ │ │ │ ├── utils/ │ │ │ │ │ ├── DingDingAccessTokenUtil.java │ │ │ │ │ ├── DingDingGroupMsgUtil.java │ │ │ │ │ ├── DingDingWorkspaceNoticeUtil.java │ │ │ │ │ ├── EmailNoticeUtil.java │ │ │ │ │ ├── PhoneNoticeUtil.java │ │ │ │ │ └── SMSNoticeUtil.java │ │ │ │ └── watermark/ │ │ │ │ └── OutageMetricWaterMark.java │ │ │ └── resources/ │ │ │ ├── LogEventDataExample.json │ │ │ ├── application.properties │ │ │ └── logback.xml │ │ └── test/ │ │ └── java/ │ │ ├── BuildLogEventDataUtil.java │ │ ├── BuildMachineMetricDataUtil.java │ │ ├── DingDingMsgTest.java │ │ └── LogEventDataExample.java │ ├── flink-learning-monitor-collector/ │ │ ├── README.md │ │ ├── flink_log_event.json │ │ ├── flink_metrics_event.json │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ └── java/ │ │ └── com/ │ │ └── zhisheng/ │ │ └── collector/ │ │ └── FlinkJobMetricCollect.java │ ├── flink-learning-monitor-common/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ └── java/ │ │ └── com/ │ │ └── zhisheng/ │ │ └── common/ │ │ ├── model/ │ │ │ ├── Job.java │ │ │ ├── JobStatus.java │ │ │ └── Task.java │ │ └── utils/ │ │ └── PropertiesUtil.java │ ├── flink-learning-monitor-dashboard/ │ │ ├── README.md │ │ └── pom.xml │ ├── flink-learning-monitor-log/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── zhisheng/ │ │ │ └── log/ │ │ │ ├── LogAlert.java │ │ │ ├── LogMain.java │ │ │ ├── LogSink2ES.java │ │ │ ├── function/ │ │ │ │ └── OriLog2LogEventFlatMapFunction.java │ │ │ ├── model/ │ │ │ │ └── OriginalLogEvent.java │ │ │ ├── schema/ │ │ │ │ └── OriginalLogEventSchema.java │ │ │ └── utils/ │ │ │ ├── ESSinkUtil.java │ │ │ ├── GrokUtil.java │ │ │ └── RetryRequestFailureHandler.java │ │ └── resources/ │ │ ├── application.properties │ │ └── patterns/ │ │ └── patterns │ ├── flink-learning-monitor-pvuv/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ └── java/ │ │ └── com/ │ │ └── zhisheng/ │ │ └── monitor/ │ │ └── pvuv/ │ │ ├── HyperLogLogUvExample.java │ │ ├── MapStateUvExample.java │ │ ├── RedisSetUvExample.java │ │ ├── model/ │ │ │ └── UserVisitWebEvent.java │ │ └── utils/ │ │ └── UvExampleUtil.java │ ├── flink-learning-monitor-storage/ │ │ ├── README.md │ │ ├── flink_log_2es.sql │ │ ├── flink_metrics_2es.sql │ │ └── pom.xml │ ├── flink_monitor_measurements.md │ └── pom.xml ├── flink-learning-project/ │ ├── README.md │ ├── flink-learning-project-common/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ └── java/ │ │ └── com/ │ │ └── zhisheng/ │ │ └── project/ │ │ └── common/ │ │ ├── constant/ │ │ │ └── ProjectConstants.java │ │ ├── model/ │ │ │ ├── AlertEvent.java │ │ │ ├── AlertRule.java │ │ │ ├── PageAccessEvent.java │ │ │ ├── ServerMetric.java │ │ │ └── TransactionEvent.java │ │ └── utils/ │ │ └── ProjectKafkaUtil.java │ ├── flink-learning-project-deduplication/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ └── java/ │ │ └── com/ │ │ └── zhisheng/ │ │ └── project/ │ │ └── deduplication/ │ │ ├── KeyedStateDeduplication.java │ │ ├── TuningKeyedStateDeduplication.java │ │ ├── model/ │ │ │ └── UserVisitWebEvent.java │ │ └── utils/ │ │ └── DeduplicationExampleUtil.java │ ├── flink-learning-project-flink-job-scaffold/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ └── java/ │ │ └── com/ │ │ └── zhisheng/ │ │ └── project/ │ │ └── scaffold/ │ │ └── FlinkJobScaffold.java │ ├── flink-learning-project-log/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ └── java/ │ │ └── com/ │ │ └── zhisheng/ │ │ └── project/ │ │ └── log/ │ │ ├── ErrorLogAlertJob.java │ │ ├── LogAnalysisJob.java │ │ └── model/ │ │ ├── AppLogEvent.java │ │ └── LogStatistics.java │ ├── flink-learning-project-monitor-alert/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ └── java/ │ │ └── com/ │ │ └── zhisheng/ │ │ └── project/ │ │ └── monitor/ │ │ └── alert/ │ │ ├── DynamicAlertRuleJob.java │ │ └── MetricAggregateAlertJob.java │ ├── flink-learning-project-monitor-dashboard/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ └── java/ │ │ └── com/ │ │ └── zhisheng/ │ │ └── project/ │ │ └── dashboard/ │ │ ├── RealTimeDashboardJob.java │ │ ├── TopNHotPagesJob.java │ │ └── model/ │ │ ├── PageViewStats.java │ │ └── TopNResult.java │ ├── flink-learning-project-real-time-computing-platform/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ └── java/ │ │ └── com/ │ │ └── zhisheng/ │ │ └── project/ │ │ └── platform/ │ │ ├── FlinkSqlPlatformJob.java │ │ └── TableApiExampleJob.java │ ├── flink-learning-project-real-time-data-warehouse/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ └── java/ │ │ └── com/ │ │ └── zhisheng/ │ │ └── project/ │ │ └── warehouse/ │ │ ├── DwsOrderStatsJob.java │ │ ├── OdsToKafkaJob.java │ │ └── model/ │ │ ├── OrderDetail.java │ │ └── OrderStats.java │ ├── flink-learning-project-risk-management/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ └── java/ │ │ └── com/ │ │ └── zhisheng/ │ │ └── project/ │ │ └── risk/ │ │ ├── FraudDetectionCepJob.java │ │ ├── RiskScoreJob.java │ │ └── model/ │ │ └── RiskEvent.java │ └── pom.xml ├── flink-learning-sql/ │ ├── README.md │ ├── flink-learning-sql-blink/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── zhisheng/ │ │ │ │ └── sql/ │ │ │ │ └── blink/ │ │ │ │ └── stream/ │ │ │ │ ├── catalog/ │ │ │ │ │ ├── CatalogAPI.java │ │ │ │ │ └── CatalogTypes.java │ │ │ │ └── example/ │ │ │ │ ├── FlinkSQLDistinctExample.java │ │ │ │ ├── SQLExampleData2PG.java │ │ │ │ ├── SQLExampleKafkaData2ES.java │ │ │ │ ├── SQLExampleKafkaData2HBase.java │ │ │ │ ├── SQLExampleKafkaData2Kafka.java │ │ │ │ ├── SQLExampleKafkaRowData2ES.java │ │ │ │ └── StreamWindowSQLExample.java │ │ │ └── resources/ │ │ │ ├── application.properties │ │ │ └── words.txt │ │ └── test/ │ │ └── java/ │ │ └── test/ │ │ └── TableEnvironmentExample1.java │ ├── flink-learning-sql-client/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── zhisheng/ │ │ │ │ └── sql/ │ │ │ │ ├── SqlSubmit.java │ │ │ │ ├── cli/ │ │ │ │ │ ├── CliOptions.java │ │ │ │ │ ├── CliOptionsParser.java │ │ │ │ │ └── SqlCommandParser.java │ │ │ │ ├── constant/ │ │ │ │ │ ├── Constant.java │ │ │ │ │ └── UnitEnum.java │ │ │ │ ├── exception/ │ │ │ │ │ └── SqlParserException.java │ │ │ │ ├── planner/ │ │ │ │ │ ├── BatchPlanner.java │ │ │ │ │ ├── Planner.java │ │ │ │ │ └── StreamingPlanner.java │ │ │ │ └── utils/ │ │ │ │ ├── CloseableRowIteratorWrapper.java │ │ │ │ ├── Config.java │ │ │ │ └── HttpClient.java │ │ │ └── resources/ │ │ │ ├── dev/ │ │ │ │ ├── conf.properties │ │ │ │ └── logback.xml │ │ │ ├── pre/ │ │ │ │ ├── conf.properties │ │ │ │ └── logback.xml │ │ │ ├── prod/ │ │ │ │ ├── conf.properties │ │ │ │ └── logback.xml │ │ │ └── sql/ │ │ │ └── 124563.sql │ │ └── test/ │ │ ├── java/ │ │ │ └── SqlSubmitTest.java │ │ └── resources/ │ │ ├── dev/ │ │ │ ├── conf.properties │ │ │ └── logback.xml │ │ └── sql/ │ │ └── test.sql │ ├── flink-learning-sql-common/ │ │ ├── README.md │ │ └── pom.xml │ └── pom.xml ├── paper/ │ └── paper.md ├── pom.xml └── tree.md ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ .DS_Store .pampas/ **/.idea/* **/target/* .idea *.iml *.class .project **/.settings/* **/*/dependency-reduced-pom.xml # front dependencies endpoints/**/node_modules # production endpoints/**/dist endpoints/**/public/ endpoints/**/vendor/ endpoints/**/vendor_modules/ endpoints/**/components_vendor/ endpoints/**/components_eevee/ endpoints/**/app/components_vendor endpoints/**/lib/server/ endpoints/**/npm-debug.log* endpoints/**/yarn-error.log* debug.properties /dist/ .classpath .factorypath .vscode/ ================================================ FILE: Flink-Forward-2020/README.md ================================================ Flink Forward 2020 是在线上举办的一次会议 1、《Keynote:Introducing Stateful Functions 2.0: Stream Processing meets Serverless Applications》 Stephan Ewen – Apache Flink PMC,Ververica Co-founder, CTO 讲解嘉宾:李钰(绝顶) – Apache Flink Committer,Apache Flink 1.10 Release Manager,阿里巴巴高级技术专家 2、《Keynote:Stream analytics made real with Pravega and Apache Flink》 Srikanth Satya – VP of Engineering at DellEMC 讲解嘉宾:滕昱 – DellEMC 技术总监 3、《Keynote:Apache Flink – Completing Cloudera’s End to End Streaming Platform》 Marton Balassi – Apache Flink PMC ,Senior Solutions Architect at Cloudera Joe Witt – VP of Engineering at Cloudera 讲解嘉宾:杨克特(鲁尼) – Apache Member, Apache Flink PMC, 阿里巴巴高级技术专家 4、《Keynote:The Evolution of Data Infrastructure at Splunk》 Eric Sammer – Distinguished Engineer at Splunk 讲解嘉宾:王治江(淘江) – 阿里巴巴高级技术专家 5、《Flink SQL 之 2020:舍我其谁》 Fabian Hueske, & Timo Walther 讲解嘉宾:伍翀(云邪),Apache Flink PMC,阿里巴巴技术专家 6、《微博基于 Flink 的机器学习实践》 分享嘉宾: 于茜,微博机器学习研发中心高级算法工程师。多年来致力于使用 Flink 构建实时数据处理和在线机器学习框架,有丰富的社交媒体应用推荐系统的开发经验。 曹富强,微博机器学习研发中心系统工程师。现负责微博机器学习平台数据计算模块。主要涉及实时计算 Flink,Storm,Spark Streaming,离线计算 Hive,Spark 等。目前专注于 Flink 在微博机器学习场景的应用。 于翔,微博机器学习研发中心算法架构工程师。 7、《Flink’s application at Didi》 分享嘉宾:薛康 – 现任滴滴技术专家,实时计算负责人 8、《Alink:提升基于 Flink 的机器学习平台易用性》 分享嘉宾:杨旭(品数) – 阿里巴巴资深技术专家。 9、《Google: 机器学习工作流的分布式处理》 Ahmet Altay & Reza Rokni & Robert Crowe 讲解嘉宾:秦江杰 – Apache Flink PMC,阿里巴巴高级技术专家 10、《Flink + AI Flow:让 AI 易如反掌》 分享嘉宾:秦江杰 – Apache Flink PMC,阿里巴巴高级技术专家 11、《终于等到你:PyFlink + Zeppelin》 分享嘉宾: 孙金城(金竹) – Apache Member,Apache Flink PMC,阿里巴巴高级技术专家 章剑锋(简锋) – Apache Member,Apache Zeppelin PMC,阿里巴巴高级技术专家 12、《Uber :使用 Flink CEP 进行地理情形检测的实践》 Teng (Niel) Hu 讲解嘉宾:付典 – Apache Flink Committer,阿里巴巴技术专家 13、《AWS: 如何在全托管 Apache Flink 服务中提供应用高可用》 Ryan Nienhuis & Tirtha Chatterjee 讲解嘉宾:章剑锋(简锋) – Apache Member,Apache Zeppelin PMC,阿里巴巴高级技术专家 14、《Production-Ready Flink and Hive Integration – what story you can tell now?》 Bowen Li 讲解嘉宾:李锐(天离) – Apache Hive PMC,阿里巴巴技术专家 15、《Data Warehouse, Data Lakes, What’s Next?》 Xiaowei Jiang 讲解嘉宾:金晓军(仙隐) – 阿里巴巴高级技术专家 16、《Netflix 的 Flink 自动扩缩容》 Abhay Amin 讲解嘉宾:吕文龙(龙三),阿里巴巴技术专家 17、《Apache Flink 误用之痛》 Konstantin Knauf 讲解嘉宾:孙金城(金竹) – Apache Member,Apache Flink PMC,阿里巴巴高级技术专家 18、《A deep dive into Flink SQL》 分享嘉宾:伍翀(云邪),Apache Flink PMC,阿里巴巴技术专家 19、《Lyft: 基于Flink的准实时海量数据分析平台》 Ying Xu & Kailash Hassan Dayanand 讲解嘉宾:王阳(亦祺),阿里巴巴技术专家 ### 如何获取上面这些 PPT? 上面的这些 PPT 本人已经整理好了,你可以扫描下面二维码,关注微信公众号:zhisheng,然后在里面回复关键字: **ff2020** 即可获取已放出的 PPT。 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/2019-12-28-144329.jpg) ================================================ FILE: Flink-Forward-Asia-2019-PPT/README.md ================================================ Flink Forward Asia 2019 在北京召开的,有主会场和几个分会场(企业实践、Apache Flink 核心技术、开源大数据生态、实时数仓、人工智能),内容涉及很多,可以查看下面的 PPT。 ### 主会场 1、《Stateful Functions: Building general-purpose Applications and Services on Apache Flink》 2、《Apache Flink Heading Towards A Unified Engine》 3、《Storage Reimagined for a Streaming World》 4、《Lyft 基于 Apache Flink 的大规模准实时数据分析平台》 ### 企业实践 1、《Apache Flink 在字节跳动的实践与优化》 2、《Apache Flink在快手实时多维分析场景的应用》 3、《bilibili 实时平台的架构与实践》 4、《Apache Flink 资源动态调整及其实践》 5、《Apache Flink在滴滴的应用与实践》 6、《Apache Flink 在网易的实践》 7、《Apache Flink 在中国农业银行的探索和实践》 8、《基于 Apache Flink 的爱奇艺实时计算平台建设实践》 9、《实时计算在贝壳的实践》 10、《基于 Apache Flink 构建 CEP(Complex Event Process)引擎的挑战和实践》 ### Apache Flink 核心技术 1、《Pluggable Shuffle Service and Unaligned Checkpoint》 2、《漂移计算 – 跨 DC 跨数据源的高性能 SQL 引擎》 3、《New Source API – Make it Easy! 》 4、《Stateful Functions: Unlocking the next wave of applications with Stream Processing》 5、《Apache Flink新场景——OLAP引擎》 6、《New Feature and Improvements on State Backends in Flink 1.10》 7、《阿里巴巴在 Apache Flink 大规模持久化存储的实践之道》 8、《Using Apache Flink as a Unified Data Processing Platform》 9、《深入探索 Apache Flink SQL 流批统一的查询引擎与最佳实践》 10、《Apache Flink 流批一体的资源管理与任务调度》 ### 开源大数据生态 1、《YuniKorn 对 Apache Flink on K8s 的调度优化》 2、《流处理基准测试》 3、《Apache Flink and the Apache Way》 4、《Delivering stream data reliably with Pravega》 5、《Deep dive into Pyflink & integration with Zeppelin》 6、《Apache Flink 与 Apache Hive 的集成》 7、《趣头条基于 Apache Flink+ClickHouse 构建实时数据分析平台》 8、《基于 Apache Flink 的边缘流式计算》 9、《基于 Apache Pulsar 和 Apache Flink 进行批流一体的弹性数据处理》 10、《The integretion of Apache Flink SQL and Apache Calcite》 ### 实时数仓 1、《美团点评基于 Apache Flink 的实时数仓平台实践》 2、《小米流式平台架构演进与实践》 3、《Netflix:Evolving Keystone to an Open Collaborative Real-time ETL Platform》 4、《菜鸟供应链实时数据技术架构的演进》 5、《OPPO 基于 Apache Flink 的实时数仓实践》 ### 人工智能 1、《Deep Learning On Apache Flink》 2、《在 Apache Flink 上使用 Analytics-Zoo 进行大数据分析与深度学习模型推理的架构与实践》 3、《携程实时智能检测平台实践》 4、《基于Apache Flink的机器学习算法平台实践与开源》 5、《Apache Flink AI生态系统工作》 ### 如何获取上面这些 PPT? 上面的这些 PPT 本人已经整理好了,你可以扫描下面二维码,关注微信公众号:zhisheng,然后在里面回复关键字: **ffa** 即可获取已放出的 PPT。 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/2019-12-28-144329.jpg) ================================================ FILE: Flink-Forward-Asia-2020-PPT/README.md ================================================ Flink Forward Asia 2020 在北京召开的,有主会场和几个分会场(企业实践、Apache Flink 核心技术、开源大数据生态、实时数仓、人工智能),内容涉及很多,可以查看下面图片介绍。 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/2020-12-21-142353.png) ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/2020-12-21-142431.png) ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/2020-12-21-142511.png) ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/2020-12-21-142538.png) ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/2020-12-21-142616.png) ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/2020-12-21-142643.png) ### 如何获取上面这些 PPT? 上面的这些 PPT 本人已经整理好了,你可以扫描下面二维码,关注微信公众号:zhisheng,然后在里面回复关键字: **ffa2020** 即可获取已放出的 PPT。 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/2019-12-28-144329.jpg) ================================================ FILE: Flink-Forward-Asia-2021-PPT/README.md ================================================ Flink Forward Asia 2021 在线上召开的,有企业实践、Apache Flink 核心技术、开源大数据生态、实时数仓、人工智能、流批一体、数据湖等会场,内容涉及很多,可以查看下面图片介绍。 ![](https://tva1.sinaimg.cn/large/008i3skNly1gyr1wen0rnj31g00u0jvn.jpg) ![](https://tva1.sinaimg.cn/large/008i3skNly1gyr1y1zdupj31r50lnjuk.jpg) ![](https://tva1.sinaimg.cn/large/008i3skNly1gyr1yk6277j31s20h376q.jpg) ![](https://tva1.sinaimg.cn/large/008i3skNly1gyr1ytnwgfj31r90kaacx.jpg) ![](https://tva1.sinaimg.cn/large/008i3skNly1gyr1z97yb7j31qh0kkdj0.jpg) ![](https://tva1.sinaimg.cn/large/008i3skNly1gyr1zmyo5ej31q70jmju2.jpg) ![](https://tva1.sinaimg.cn/large/008i3skNly1gyr1zuw6fpj31qb0kq778.jpg) ### 如何获取上面这些 PPT? 上面的这些 PPT 本人已经整理好了,你可以扫描下面二维码,关注微信公众号:zhisheng,然后在里面回复关键字: **ffa2021** 即可获取已放出的 PPT。 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/2019-12-28-144329.jpg) ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: README.md ================================================ # Flink 学习 麻烦路过的各位亲给这个项目点个 star,太不易了,写了这么多,算是对我坚持下来的一种鼓励吧!另外特别感谢 [JetBrains](https://jb.gg/OpenSourceSupport) 公司提供的免费全家桶工具,🙏🙏🙏! ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-05-25-124027.jpg) ## Stargazers over time ![Stargazers over time](https://starchart.cc/zhisheng17/flink-learning.svg) ## 本项目结构 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/2020-01-11-064410.png) ## How to build Maybe your Maven conf file `settings.xml` mirrors can add aliyun central mirror : ```xml alimaven central aliyun maven https://maven.aliyun.com/repository/central ``` then you can run the following command : ``` mvn clean package -Dmaven.test.skip=true ``` you can see following result if build success. ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-09-27-121923.jpg) ## Flink 系统专栏 基于 Flink 1.9 讲解的专栏,涉及入门、概念、原理、实战、性能调优、系统案例的讲解。扫码下面专栏二维码可以订阅该专栏 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-11-05-044731.jpg) 首发地址:[http://www.54tianzhisheng.cn/2019/11/15/flink-in-action/](http://www.54tianzhisheng.cn/2019/11/15/flink-in-action/) 专栏地址:[https://gitbook.cn/gitchat/column/5dad4a20669f843a1a37cb4f](https://gitbook.cn/gitchat/column/5dad4a20669f843a1a37cb4f) ## Change **2022/02/26** 将自己 《Flink 实战与性能优化》专栏放在 GitHub,参见 books 目录 **2021/12/18** 将该项目的 Flink 版本升级至 1.14.2,如果有需要可以去老的分支查看。 **2021/08/15** 将该项目的 Flink 版本升级至 1.13.2,API 发生重大改变,所以代码结构也做了相应的调整(部分代码在 master 分支已经删除,同时将之前的代码切到 [feature/flink-1.10.0](https://github.com/zhisheng17/flink-learning/tree/feature/flink-1.10.0) 上了,如果有需要可以去老的分支查看)。 **2020/02/16** 将该项目的 Flink 版本升级至 1.10,该版本代码都是经过测试成功运行的,尽量以该版本作为参考,如果代码在你们集群测试不成功,麻烦检查 Flink 版本是否一致,或者是否有包冲突问题。 **2019/09/06** 将该项目的 Flink 版本升级到 1.9.0,有一些变动,Flink 1.8.0 版本的代码经群里讨论保存在分支 [feature/flink-1.8.0](https://github.com/zhisheng17/flink-learning/tree/feature/flink-1.8.0) 以便部分同学需要。 **2019/06/08** 四本 Flink 书籍: + [Introduction_to_Apache_Flink_book.pdf]() 这本书比较薄,处于介绍阶段,国内有这本的翻译书籍 + [Learning Apache Flink.pdf]() 这本书比较基础,初学的话可以多看看 + [Stream Processing with Apache Flink.pdf]() 这本书是 Flink PMC 写的 + [Streaming System.pdf]() 这本书评价不是一般的高 **2019/06/09** 新增流处理引擎相关的 Paper,在 paper 目录下: + [流处理引擎相关的 Paper](./paper/paper.md) **【提示】**:关于书籍的下载,因版权问题,不方便提供,所以已经删除,需要的话可以切换到老分支去下载。 ## 博客 1、[Flink 从0到1学习 —— Apache Flink 介绍](http://www.54tianzhisheng.cn/2018/10/13/flink-introduction/) 2、[Flink 从0到1学习 —— Mac 上搭建 Flink 1.6.0 环境并构建运行简单程序入门](http://www.54tianzhisheng.cn/2018/09/18/flink-install) 3、[Flink 从0到1学习 —— Flink 配置文件详解](http://www.54tianzhisheng.cn/2018/10/27/flink-config/) 4、[Flink 从0到1学习 —— Data Source 介绍](http://www.54tianzhisheng.cn/2018/10/28/flink-sources/) 5、[Flink 从0到1学习 —— 如何自定义 Data Source ?](http://www.54tianzhisheng.cn/2018/10/30/flink-create-source/) 6、[Flink 从0到1学习 —— Data Sink 介绍](http://www.54tianzhisheng.cn/2018/10/29/flink-sink/) 7、[Flink 从0到1学习 —— 如何自定义 Data Sink ?](http://www.54tianzhisheng.cn/2018/10/31/flink-create-sink/) 8、[Flink 从0到1学习 —— Flink Data transformation(转换)](http://www.54tianzhisheng.cn/2018/11/04/Flink-Data-transformation/) 9、[Flink 从0到1学习 —— 介绍 Flink 中的 Stream Windows](http://www.54tianzhisheng.cn/2018/12/08/Flink-Stream-Windows/) 10、[Flink 从0到1学习 —— Flink 中的几种 Time 详解](http://www.54tianzhisheng.cn/2018/12/11/Flink-time/) 11、[Flink 从0到1学习 —— Flink 读取 Kafka 数据写入到 ElasticSearch](http://www.54tianzhisheng.cn/2018/12/30/Flink-ElasticSearch-Sink/) 12、[Flink 从0到1学习 —— Flink 项目如何运行?](http://www.54tianzhisheng.cn/2019/01/05/Flink-run/) 13、[Flink 从0到1学习 —— Flink 读取 Kafka 数据写入到 Kafka](http://www.54tianzhisheng.cn/2019/01/06/Flink-Kafka-sink/) 14、[Flink 从0到1学习 —— Flink JobManager 高可用性配置](http://www.54tianzhisheng.cn/2019/01/13/Flink-JobManager-High-availability/) 15、[Flink 从0到1学习 —— Flink parallelism 和 Slot 介绍](http://www.54tianzhisheng.cn/2019/01/14/Flink-parallelism-slot/) 16、[Flink 从0到1学习 —— Flink 读取 Kafka 数据批量写入到 MySQL](http://www.54tianzhisheng.cn/2019/01/15/Flink-MySQL-sink/) 17、[Flink 从0到1学习 —— Flink 读取 Kafka 数据写入到 RabbitMQ](https://t.zsxq.com/uVbi2nq) 18、[Flink 从0到1学习 —— Flink 读取 Kafka 数据写入到 HBase](https://t.zsxq.com/zV7MnuJ) 19、[Flink 从0到1学习 —— Flink 读取 Kafka 数据写入到 HDFS](https://t.zsxq.com/zV7MnuJ) 20、[Flink 从0到1学习 —— Flink 读取 Kafka 数据写入到 Redis](https://t.zsxq.com/zV7MnuJ) 21、[Flink 从0到1学习 —— Flink 读取 Kafka 数据写入到 Cassandra](https://t.zsxq.com/uVbi2nq) 22、[Flink 从0到1学习 —— Flink 读取 Kafka 数据写入到 Flume](https://t.zsxq.com/zV7MnuJ) 23、[Flink 从0到1学习 —— Flink 读取 Kafka 数据写入到 InfluxDB](https://t.zsxq.com/zV7MnuJ) 24、[Flink 从0到1学习 —— Flink 读取 Kafka 数据写入到 RocketMQ](https://t.zsxq.com/zV7MnuJ) 25、[Flink 从0到1学习 —— 你上传的 jar 包藏到哪里去了](https://t.zsxq.com/uniY7mm) 26、[Flink 从0到1学习 —— 你的 Flink job 日志跑到哪里去了](https://t.zsxq.com/zV7MnuJ) ### Flink 源码项目结构 ![](./pics/Flink-code.png) ## 学习资料 另外我自己整理了些 Flink 的学习资料,目前已经全部放到微信公众号了。 你可以加我的微信:**yuanblog_tzs**,然后回复关键字:**Flink** 即可无条件获取到,转载请联系本人获取授权,违者必究。 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-09-17-143454.jpg) 更多私密资料请加入知识星球! ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-07-23-124320.jpg) 有人要问知识星球里面更新什么内容?值得加入吗? 目前知识星球内已更新的系列文章: ### 大数据重磅炸弹 1、[《大数据重磅炸弹——实时计算引擎 Flink》开篇词](https://t.zsxq.com/fqfuVRR​) 2、[你公司到底需不需要引入实时计算引擎?](https://t.zsxq.com/emMBaQN​) 3、[一文让你彻底了解大数据实时计算框架 Flink](https://t.zsxq.com/eM3ZRf2) ​ 4、[别再傻傻的分不清大数据框架Flink、Blink、Spark Streaming、Structured Streaming和Storm之间的区别了](https://t.zsxq.com/eAyRz7Y)​ 5、[Flink 环境准备看这一篇就够了](https://t.zsxq.com/iaMJAe6​)   6、[一文讲解从 Flink 环境安装到源码编译运行](https://t.zsxq.com/iaMJAe6​) 7、[通过 WordCount 程序教你快速入门上手 Flink](https://t.zsxq.com/eaIIiAm)  ​ 8、[Flink 如何处理 Socket 数据及分析实现过程](https://t.zsxq.com/Vnq72jY​)   9、[Flink job 如何在 Standalone、YARN、Mesos、K8S 上部署运行?](https://t.zsxq.com/BiyvFUZ​) 10、[Flink 数据转换必须熟悉的算子(Operator)](https://t.zsxq.com/fufUBiA) 11、[Flink 中 Processing Time、Event Time、Ingestion Time 对比及其使用场景分析](https://t.zsxq.com/r7aYB2V) 12、[如何使用 Flink Window 及 Window 基本概念与实现原理](https://t.zsxq.com/byZbyrb) 13、[如何使用 DataStream API 来处理数据?](https://t.zsxq.com/VzNBi2r) 14、[Flink WaterMark 详解及结合 WaterMark 处理延迟数据](https://t.zsxq.com/Iub6IQf) 15、[基于 Apache Flink 的监控告警系统](https://t.zsxq.com/MniUnqb) 16、[数据仓库、数据库的对比介绍与实时数仓案例分享](https://t.zsxq.com/v7QzNZ3) 17、[使用 Prometheus Grafana 监控 Flink](https://t.zsxq.com/uRN3VfA) ### 源码系列 1、[Flink 源码解析 —— 源码编译运行](https://t.zsxq.com/UZfaYfE) 2、[Flink 源码解析 —— 项目结构一览](https://t.zsxq.com/zZZjaYf) 3、[Flink 源码解析—— local 模式启动流程](https://t.zsxq.com/zV7MnuJ) 4、[Flink 源码解析 —— standalonesession 模式启动流程](https://t.zsxq.com/QZVRZJA) 5、[Flink 源码解析 —— Standalone Session Cluster 启动流程深度分析之 Job Manager 启动](https://t.zsxq.com/u3fayvf) 6、[Flink 源码解析 —— Standalone Session Cluster 启动流程深度分析之 Task Manager 启动](https://t.zsxq.com/MnQRByb) 7、[Flink 源码解析 —— 分析 Batch WordCount 程序的执行过程](https://t.zsxq.com/YJ2Zrfi) 8、[Flink 源码解析 —— 分析 Streaming WordCount 程序的执行过程](https://t.zsxq.com/qnMFEUJ) 9、[Flink 源码解析 —— 如何获取 JobGraph?](https://t.zsxq.com/naaMf6y) 10、[Flink 源码解析 —— 如何获取 StreamGraph?](https://t.zsxq.com/qRFIm6I) 11、[Flink 源码解析 —— Flink JobManager 有什么作用?](https://t.zsxq.com/2VRrbuf) 12、[Flink 源码解析 —— Flink TaskManager 有什么作用?](https://t.zsxq.com/RZbu7yN) 13、[Flink 源码解析 —— JobManager 处理 SubmitJob 的过程](https://t.zsxq.com/zV7MnuJ) 14、[Flink 源码解析 —— TaskManager 处理 SubmitJob 的过程](https://t.zsxq.com/zV7MnuJ) 15、[Flink 源码解析 —— 深度解析 Flink Checkpoint 机制](https://t.zsxq.com/ynQNbeM) 16、[Flink 源码解析 —— 深度解析 Flink 序列化机制](https://t.zsxq.com/JaQfeMf) 17、[Flink 源码解析 —— 深度解析 Flink 是如何管理好内存的?](https://t.zsxq.com/zjQvjeM) 18、[Flink Metrics 源码解析 —— Flink-metrics-core](https://t.zsxq.com/Mnm2nI6) 19、[Flink Metrics 源码解析 —— Flink-metrics-datadog](https://t.zsxq.com/Mnm2nI6) 20、[Flink Metrics 源码解析 —— Flink-metrics-dropwizard](https://t.zsxq.com/Mnm2nI6) 21、[Flink Metrics 源码解析 —— Flink-metrics-graphite](https://t.zsxq.com/Mnm2nI6) 22、[Flink Metrics 源码解析 —— Flink-metrics-influxdb](https://t.zsxq.com/Mnm2nI6) 23、[Flink Metrics 源码解析 —— Flink-metrics-jmx](https://t.zsxq.com/Mnm2nI6) 24、[Flink Metrics 源码解析 —— Flink-metrics-slf4j](https://t.zsxq.com/Mnm2nI6) 25、[Flink Metrics 源码解析 —— Flink-metrics-statsd](https://t.zsxq.com/Mnm2nI6) 26、[Flink Metrics 源码解析 —— Flink-metrics-prometheus](https://t.zsxq.com/Mnm2nI6) ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-07-26-150037.jpg) 26、[Flink Annotations 源码解析](https://t.zsxq.com/f6eAu3J) ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-07-26-145923.jpg) 除了《从1到100深入学习Flink》源码学习这个系列文章,《从0到1学习Flink》的案例文章也会优先在知识星球更新,让大家先通过一些 demo 学习 Flink,再去深入源码学习! 如果学习 Flink 的过程中,遇到什么问题,可以在里面提问,我会优先解答,这里做个抱歉,自己平时工作也挺忙,微信的问题不能做全部做一些解答, 但肯定会优先回复给知识星球的付费用户的,庆幸的是现在星球里的活跃氛围还是可以的,有不少问题通过提问和解答的方式沉淀了下来。 1、[为何我使用 ValueState 保存状态 Job 恢复是状态没恢复?](https://t.zsxq.com/62rZV7q) 2、[flink中watermark究竟是如何生成的,生成的规则是什么,怎么用来处理乱序数据](https://t.zsxq.com/yF2rjmY) 3、[消费kafka数据的时候,如果遇到了脏数据,或者是不符合规则的数据等等怎么处理呢?](https://t.zsxq.com/uzFIeiq) 4、[在Kafka 集群中怎么指定读取/写入数据到指定broker或从指定broker的offset开始消费?](https://t.zsxq.com/Nz7QZBY) 5、[Flink能通过oozie或者azkaban提交吗?](https://t.zsxq.com/7UVBeMj) 6、[jobmanager挂掉后,提交的job怎么不经过手动重新提交执行?](https://t.zsxq.com/mUzRbY7) 7、[使用flink-web-ui提交作业并执行 但是/opt/flink/log目录下没有日志文件 请问关于flink的日志(包括jobmanager、taskmanager、每个job自己的日志默认分别存在哪个目录 )需要怎么配置?](https://t.zsxq.com/Nju7EuV) 8、[通过flink 仪表盘提交的jar 是存储在哪个目录下?](https://t.zsxq.com/6muRz3j) 9、[从Kafka消费数据进行etl清洗,把结果写入hdfs映射成hive表,压缩格式、hive直接能够读取flink写出的文件、按照文件大小或者时间滚动生成文件](https://t.zsxq.com/uvFQvFu) 10、[flink jar包上传至集群上运行,挂掉后,挂掉期间kafka中未被消费的数据,在重新启动程序后,是自动从checkpoint获取挂掉之前的kafka offset位置,自动消费之前的数据进行处理,还是需要某些手动的操作呢?](https://t.zsxq.com/ubIY33f) 11、[flink 启动时不自动创建 上传jar的路径,能指定一个创建好的目录吗](https://t.zsxq.com/UfA2rBy) 12、[Flink sink to es 集群上报 slot 不够,单机跑是好的,为什么?](https://t.zsxq.com/zBMnIA6) 13、[Fllink to elasticsearch如何创建索引文档期时间戳?](https://t.zsxq.com/qrZBAQJ) 14、[blink有没有api文档或者demo,是否建议blink用于生产环境。](https://t.zsxq.com/J2JiIMv) 15、[flink的Python api怎样?bug多吗?](https://t.zsxq.com/ZVVrjuv) 16、[Flink VS Spark Streaming VS Storm VS Kafka Stream ](https://t.zsxq.com/zbybQNf) 17、[你们做实时大屏的技术架构是什么样子的?flume→kafka→flink→redis,然后后端去redis里面捞数据,酱紫可行吗?](https://t.zsxq.com/Zf6meAm) 18、[做一个统计指标的时候,需要在Flink的计算过程中多次读写redis,感觉好怪,星主有没有好的方案?](https://t.zsxq.com/YniI2JQ) 19、[Flink 使用场景大分析,列举了很多的常用场景,可以好好参考一下](https://t.zsxq.com/fYZZfYf) 20、[将kafka中数据sink到mysql时,metadata的数据为空,导入mysql数据不成功???](https://t.zsxq.com/I6eEqR7) 21、[使用了ValueState来保存中间状态,在运行时中间状态保存正常,但是在手动停止后,再重新运行,发现中间状态值没有了,之前出现的键值是从0开始计数的,这是为什么?是需要实现CheckpointedFunction吗?](https://t.zsxq.com/62rZV7q) 22、[flink on yarn jobmanager的HA需要怎么配置。还是说yarn给管理了](https://t.zsxq.com/mQ7YbQJ) 23、[有两个数据流就行connect,其中一个是实时数据流(kafka 读取),另一个是配置流。由于配置流是从关系型数据库中读取,速度较慢,导致实时数据流流入数据的时候,配置信息还未发送,这样会导致有些实时数据读取不到配置信息。目前采取的措施是在connect方法后的flatmap的实现的在open 方法中,提前加载一次配置信息,感觉这种实现方式不友好,请问还有其他的实现方式吗?](https://t.zsxq.com/q3VvB6U) 24、[Flink能通过oozie或者azkaban提交吗?](https://t.zsxq.com/7UVBeMj) 25、[不采用yarm部署flink,还有其他的方案吗? 主要想解决服务器重启后,flink服务怎么自动拉起? jobmanager挂掉后,提交的job怎么不经过手动重新提交执行?](https://t.zsxq.com/mUzRbY7) 26、[在一个 Job 里将同份数据昨晚清洗操作后,sink 到后端多个地方(看业务需求),如何保持一致性?(一个sink出错,另外的也保证不能插入)](https://t.zsxq.com/bYnimQv) 27、[flink sql任务在某个特定阶段会发生tm和jm丢失心跳,是不是由于gc时间过长呢,](https://t.zsxq.com/YvBAyrV) 28、[有这样一个需求,统计用户近两周进入产品详情页的来源(1首页大搜索,2产品频道搜索,3其他),为php后端提供数据支持,该信息在端上报事件中,php直接获取有点困难。 我现在的解决方案 通过flink滚动窗口(半小时),统计用户半小时内3个来源pv,然后按照日期序列化,直接写mysql。php从数据库中解析出来,再去统计近两周占比。 问题1,这个需求适合用flink去做吗? 问题2,我的方案总感觉怪怪的,有没有好的方案?](https://t.zsxq.com/fayf2Vv) 29、[一个task slot 只能同时运行一个任务还是多个任务呢?如果task slot运行的任务比较大,会出现OOM的情况吗?](https://t.zsxq.com/ZFiY3VZ) 30、[你们怎么对线上flink做监控的,如果整个程序失败了怎么自动重启等等](https://t.zsxq.com/Yn2JqB6) 31、[flink cep规则动态解析有接触吗?有没有成型的框架?](https://t.zsxq.com/YFMFeaA) 32、[每一个Window都有一个watermark吗?window是怎么根据watermark进行触发或者销毁的?](https://t.zsxq.com/VZvRrjm) 33、[ CheckPoint与SavePoint的区别是什么?](https://t.zsxq.com/R3ZZJUF) 34、[flink可以在算子中共享状态吗?或者大佬你有什么方法可以共享状态的呢?](https://t.zsxq.com/Aa62Bim) 35、[运行几分钟就报了,看taskmager日志,报的是 failed elasticsearch bulk request null,可是我代码里面已经做过空值判断了呀 而且也过滤掉了,flink版本1.7.2 es版本6.3.1](https://t.zsxq.com/ayFmmMF) 36、[这种情况,我们调并行度 还是配置参数好](https://t.zsxq.com/Yzzzb2b) 37、[大家都用jdbc写,各种数据库增删查改拼sql有没有觉得很累,ps.set代码一大堆,还要计算每个参数的位置](https://t.zsxq.com/AqBUR3f) 38、[关于datasource的配置,每个taskmanager对应一个datasource?还是每个slot? 实际运行下来,每个slot中datasorce线程池只要设置1就行了,多了也用不到?](https://t.zsxq.com/AqBUR3f) 39、[kafka现在每天出现数据丢失,现在小批量数据,一天200W左右, kafka版本为 1.0.0,集群总共7个节点,TOPIC有十六个分区,单条报文1.5k左右](https://t.zsxq.com/AqBUR3f) 40、[根据key.hash的绝对值 对并发度求模,进行分组,假设10各并发度,实际只有8个分区有处理数据,有2个始终不处理,还有一个分区处理的数据是其他的三倍,如截图](https://t.zsxq.com/AqBUR3f) 41、[flink每7小时不知道在处理什么, CPU 负载 每7小时,有一次高峰,5分钟内平均负载超过0.8,如截图](https://t.zsxq.com/AqBUR3f) 42、[有没有Flink写的项目推荐?我想看到用Flink写的整体项目是怎么组织的,不单单是一个单例子](https://t.zsxq.com/M3fIMbu) 43、[Flink 源码的结构图](https://t.zsxq.com/yv7EQFA) 44、[我想根据不同业务表(case when)进行不同的redis sink(hash ,set),我要如何操作?](https://t.zsxq.com/vBAYNJq) 45、[这个需要清理什么数据呀,我把hdfs里面的已经清理了 启动还是报这个](https://t.zsxq.com/b2zbUJa) 46、[ 在流处理系统,在机器发生故障恢复之后,什么情况消息最多会被处理一次?什么情况消息最少会被处理一次呢?](https://t.zsxq.com/QjQFmQr) 47、[我检查点都调到5分钟了,这是什么问题](https://t.zsxq.com/zbQNfuJ) 48、[reduce方法后 那个交易时间 怎么不是最新的,是第一次进入的那个时间,](https://t.zsxq.com/ZrjEauN) 49、[Flink on Yarn 模式,用yarn session脚本启动的时候,我在后台没有看到到Jobmanager,TaskManager,ApplicationMaster这几个进程,想请问一下这是什么原因呢?因为之前看官网的时候,说Jobmanager就是一个jvm进程,Taskmanage也是一个JVM进程](https://t.zsxq.com/VJyr3bM) 50、[Flink on Yarn的时候得指定 多少个TaskManager和每个TaskManager slot去运行任务,这样做感觉不太合理,因为用户也不知道需要多少个TaskManager适合,Flink 有动态启动TaskManager的机制吗。](https://t.zsxq.com/VJyr3bM) 51、[参考这个例子,Flink 零基础实战教程:如何计算实时热门商品 | Jark's Blog, 窗口聚合的时候,用keywindow,用的是timeWindowAll,然后在aggregate的时候用aggregate(new CustomAggregateFunction(), new CustomWindowFunction()),打印结果后,发现窗口中一直使用的重复的数据,统计的结果也不变,去掉CustomWindowFunction()就正常了 ? 非常奇怪](https://t.zsxq.com/UBmUJMv) 52、[用户进入产品预定页面(端埋点上报),并填写了一些信息(端埋点上报),但半小时内并没有产生任何订单,然后给该类用户发送一个push。 1. 这种需求适合用flink去做吗?2. 如果适合,说下大概的思路](https://t.zsxq.com/naQb6aI) 53、[业务场景是实时获取数据存redis,请问我要如何按天、按周、按月分别存入redis里?(比方说过了一天自动换一个位置存redis)](https://t.zsxq.com/AUf2VNz) 54、[有人 AggregatingState 的例子吗, 感觉官方的例子和 官网的不太一样?](https://t.zsxq.com/UJ6Y7m2) 55、[flink-jdbc这个jar有吗?怎么没找到啊?1.8.0的没找到,1.6.2的有](https://t.zsxq.com/r3BaAY3) 56、[现有个关于savepoint的问题,操作流程为,取消任务时设置保存点,更新任务,从保存点启动任务;现在遇到个问题,假设我中间某个算子重写,原先通过state编写,有用定时器,现在更改后,采用窗口,反正就是实现方式完全不一样;从保存点启动就会一直报错,重启,原先的保存点不能还原,此时就会有很多数据重复等各种问题,如何才能保证数据不丢失,不重复等,恢复到停止的时候,现在想到的是记下kafka的偏移量,再做处理,貌似也不是很好弄,有什么解决办法吗](https://t.zsxq.com/jiybIee) 57、[需要在flink计算app页面访问时长,消费Kafka计算后输出到Kafka。第一条log需要等待第二条log的时间戳计算访问时长。我想问的是,flink是分布式的,那么它能否保证执行的顺序性?后来的数据有没有可能先被执行?](https://t.zsxq.com/eMJmiQz) 58、[我公司想做实时大屏,现有技术是将业务所需指标实时用spark拉到redis里存着,然后再用一条spark streaming流计算简单乘除运算,指标包含了各月份的比较。请问我该如何用flink简化上述流程?](https://t.zsxq.com/Y7e6aIu) 59、[flink on yarn 方式,这样理解不知道对不对,yarn-session这个脚本其实就是准备yarn环境的,执行run任务的时候,根据yarn-session初始化的yarnDescription 把 flink 任务的jobGraph提交到yarn上去执行](https://t.zsxq.com/QbIayJ6) 60、[同样的代码逻辑写在单独的main函数中就可以成功的消费kafka ,写在一个spring boot的程序中,接受外部请求,然后执行相同的逻辑就不能消费kafka。你遇到过吗?能给一些查问题的建议,或者在哪里打个断点,能看到为什么消费不到kafka的消息呢?](https://t.zsxq.com/VFMRbYN) 61、[请问下flink可以实现一个流中同时存在订单表和订单商品表的数据 两者是一对多的关系 能实现得到 以订单表为主 一个订单多个商品 这种需求嘛](https://t.zsxq.com/QNvjI6Q) 62、[在用中间状态的时候,如果中间一些信息保存在state中,有没有必要在redis中再保存一份,来做第三方的存储。](https://t.zsxq.com/6ie66EE) 63、[能否出一期flink state的文章。什么场景下用什么样的state?如,最简单的,实时累加update到state。](https://t.zsxq.com/bm6mYjI) 64、[flink的双流join博主有使用的经验吗?会有什么常见的问题吗](https://t.zsxq.com/II6AEe2) 65、[窗口触发的条件问题](https://t.zsxq.com/V7EmUZR) 66、[flink 定时任务怎么做?有相关的demo么?](https://t.zsxq.com/JY3NJam) 67、[流式处理过程中数据的一致性如何保证或者如何检测](https://t.zsxq.com/7YZ3Fuz) 68、[重启flink单机集群,还报job not found 异常。](https://t.zsxq.com/nEEQvzR) 69、[kafka的数据是用 org.apache.kafka.common.serialization.ByteArraySerialize序列化的,flink这边消费的时候怎么通过FlinkKafkaConsumer创建DataStream?](https://t.zsxq.com/qJyvzNj) 70、[现在公司有一个需求,一些用户的支付日志,通过sls收集,要把这些日志处理后,结果写入到MySQL,关键这些日志可能连着来好几条才是一个用户的,因为发起请求,响应等每个环节都有相应的日志,这几条日志综合处理才能得到最终的结果,请问博主有什么好的方法没有?](https://t.zsxq.com/byvnaEi) 71、[flink 支持hadoop 主备么? hadoop主节点挂了 flink 会切换到hadoop 备用节点?](https://t.zsxq.com/qfie6qR) 72、[请教大家: 实际 flink 开发中用 scala 多还是 java多些? 刚入手 flink 大数据 scala 需要深入学习么?](https://t.zsxq.com/ZVZzZv7) 73、[我使用的是flink是1.7.2最近用了split的方式分流,但是底层的SplitStream上却标注为Deprecated,请问是官方不推荐使用分流的方式吗?](https://t.zsxq.com/Qzbi6yn) 74、[KeyBy 的正确理解,和数据倾斜问题的解释](https://t.zsxq.com/Auf2NVR) 75、[用flink时,遇到个问题 checkpoint大概有2G左右, 有背压时,flink会重启有遇到过这个问题吗](https://t.zsxq.com/3vnIm62) 76、[flink使用yarn-session方式部署,如何保证yarn-session的稳定性,如果yarn-session挂了,需要重新部署一个yarn-session,如何恢复之前yarn-session上的job呢,之前的checkpoint还能使用吗?](https://t.zsxq.com/URzVBIm) 77、[我想请教一下关于sink的问题。我现在的需求是从Kafka消费Json数据,这个Json数据字段可能会增加,然后将拿到的json数据以parquet的格式存入hdfs。现在我可以拿到json数据的schema,但是在保存parquet文件的时候不知道怎么处理。一是flink没有专门的format parquet,二是对于可变字段的Json怎么处理成parquet比较合适?](https://t.zsxq.com/MjyN7Uf) 78、[flink如何在较大的数据量中做去重计算。](https://t.zsxq.com/6qBqVvZ) 79、[flink能在没有数据的时候也定时执行算子吗?](https://t.zsxq.com/Eqjyju7) 80、[使用rocksdb状态后端,自定义pojo怎么实现序列化和反序列化的,有相关demo么?](https://t.zsxq.com/i2zVfIi) 81、[check point 老是失败,是不是自定义的pojo问题?到本地可以,到hdfs就不行,网上也有很多类似的问题 都没有一个很好的解释和解决方案](https://t.zsxq.com/vRJujAi) 82、[cep规则如图,当start事件进入时,时间00:00:15,而后进入end事件,时间00:00:40。我发现规则无法命中。请问within 是从start事件开始计时?还是跟window一样根据系统时间划分的?如果是后者,请问怎么配置才能从start开始计时?](https://t.zsxq.com/MVFmuB6) 83、[Flink聚合结果直接写Mysql的幂等性设计问题](https://t.zsxq.com/EybM3vR) 84、[Flink job打开了checkpoint,用的rocksdb,通过观察hdfs上checkpoint目录,为啥算副本总量会暴增爆减](https://t.zsxq.com/62VzNRF) 85、[Flink 提交任务的 jar包可以指定路径为 HDFS 上的吗]() 86、[在flink web Ui上提交的任务,设置的并行度为2,flink是stand alone部署的。两个任务都正常的运行了几天了,今天有个地方逻辑需要修改,于是将任务cancel掉(在命令行cancel也试了),结果taskmanger挂掉了一个节点。后来用其他任务试了,也同样会导致节点挂掉](https://t.zsxq.com/VfimieI) 87、[一个配置动态更新的问题折腾好久(配置用个静态的map变量存着,有个线程定时去数据库捞数据然后存在这个map里面更新一把),本地 idea 调试没问题,集群部署就一直报 空指针异常。下游的算子使用这个静态变量map去get key在集群模式下会出现这个空指针异常,估计就是拿不到 map](https://t.zsxq.com/nee6qRv) 88、[批量写入MySQL,完成HBase批量写入](https://t.zsxq.com/3bEUZfQ) 89、[用flink清洗数据,其中要访问redis,根据redis的结果来决定是否把数据传递到下流,这有可能实现吗?](https://t.zsxq.com/Zb6AM3V) 90、[监控页面流处理的时候这个发送和接收字节为0。](https://t.zsxq.com/RbeYZvb) 91、[sink到MySQL,如果直接用idea的话可以运行,并且成功,大大的代码上面用的FlinkKafkaConsumer010,而我的Flink版本为1.7,kafka版本为2.12,所以当我用FlinkKafkaConsumer010就有问题,于是改为 FlinkKafkaConsumer就可以直接在idea完成sink到MySQL,但是为何当我把该程序打成Jar包,去运行的时候,就是报FlinkKafkaConsumer找不到呢](https://t.zsxq.com/MN7iuZf) 92、[SocketTextStreamWordCount中输入中文统计不出来,请问这个怎么解决,我猜测应该是需要修改一下代码,应该是这个例子默认统计英文](https://t.zsxq.com/e2VNN7Y) 93、[ Flink 应用程序本地 ide 里面运行的时候并行度是怎么算的?](https://t.zsxq.com/RVRn6AE) 94、[ 请问下flink中对于窗口的全量聚合有apply和process两种 他们有啥区别呢](https://t.zsxq.com/rzbIQBi) 95、[不知道大大熟悉Hbase不,我想直接在Hbase中查询某一列数据,因为有重复数据,所以想使用distinct统计实际数据量,请问Hbase中有没有类似于sql的distinct关键字。如果没有,想实现这种可以不?](https://t.zsxq.com/UJIubub) 96、[ 来分析一下现在Flink,Kafka方面的就业形势,以及准备就业该如何准备的这方面内容呢?](https://t.zsxq.com/VFaQn2j) 97、[ 大佬知道flink的dataStream可以转换为dataSet吗?因为数据需要11分钟一个批次计算五六个指标,并且涉及好几步reduce,计算的指标之间有联系,用Stream卡住了。](https://t.zsxq.com/Zn2FEQZ) 98、[1.如何在同一窗口内实现多次的聚合,比如像spark中的这样2.多个实时流的jion可以用window来处理一批次的数据吗?](https://t.zsxq.com/aIqjmQN) 99、[写的批处理的功能,现在本机跑是没问题的,就是在linux集群上出现了问题,就是不知道如果通过本地调用远程jar包然后传参数和拿到结果参数返回本机](https://t.zsxq.com/ZNvb2FM) 100、[我用standalone开启一个flink集群,上传flink官方用例Socket Window WordCount做测试,开启两个parallelism能正常运行,但是开启4个parallelism后出现错误](https://t.zsxq.com/femmiqf) 101、[ 有使用AssignerWithPunctuatedWatermarks 的案例Demo吗?网上找了都是AssignerWithPeriodicWatermarks的,不知道具体怎么使用?](https://t.zsxq.com/YZ3vbY3) 102、[ 有一个datastream(从文件读取的),然后我用flink sql进行计算,这个sql是一个加总的运算,然后通过retractStreamTableSink可以把文件做sql的结果输出到文件吗?这个输出到文件的接口是用什么呢?](https://t.zsxq.com/uzFyVJe) 103、[ 为啥split这个流设置为过期的](https://t.zsxq.com/6QNNrZz) 104、[ 需要使用flink table的水印机制控制时间的乱序问题,这种场景下我就使用水印+窗口了,我现在写的demo遇到了问题,就是在把触发计算的窗口table(WindowedTable)转换成table进行sql操作时发现窗口中的数据还是乱序的,是不是flink table的WindowedTable不支持水印窗口转table-sql的功能](https://t.zsxq.com/Q7YNRBE) 105、[ Flink 对 SQL 的重视性](https://t.zsxq.com/Jmayrbi) 106、[ flink job打开了checkpoint,任务跑了几个小时后就出现下面的错,截图是打出来的日志,有个OOM,又遇到过的没?](https://t.zsxq.com/ZrZfa2Z) 107、[ 本地测试是有数据的,之前该任务放在集群也是有数据的,可能提交过多次,现在读不到数据了 group id 也换过了, 只能重启集群解决么?](https://t.zsxq.com/emaAeyj) 108、[使用flink清洗数据存到es中,直接在flatmap中对处理出来的数据用es自己的ClientInterface类直接将数据存入es当中,不走sink,这样的处理逻辑是不是会有问题。](https://t.zsxq.com/ayBa6am) 108、[ flink从kafka拿数据(即增量数据)与存量数据进行内存聚合的需求,现在有一个方案就是程序启动的时候先用flink table将存量数据加载到内存中创建table中,然后将stream的增量数据与table的数据进行关联聚合后输出结束,不知道这种方案可行么。目前个人认为有两个主要问题:1是增量数据stream转化成append table后不知道能与存量的table关联聚合不,2是聚合后输出的结果数据是否过于频繁造成网络传输压力过大](https://t.zsxq.com/QNvbE62) 109、[ 设置时间时间特性有什么区别呢, 分别在什么场景下使用呢?两种设置时间延迟有什么区别呢 , 分别在什么场景下使用](https://t.zsxq.com/yzjAQ7a) 110、[ flink从rabbitmq中读取数据,设置了rabbitmq的CorrelationDataId和checkpoint为EXACTLY_ONCE;如果flink完成一次checkpoint后,在这次checkpoint之前消费的数据都会从mq中删除。如果某次flink停机更新,那就会出现mq中的一些数据消费但是处于Unacked状态。在flink又重新开启后这批数据又会重新消费。那这样是不是就不能保证EXACTLY_ONCE了](https://t.zsxq.com/qRrJEaa) 111、[1. 在Flink checkpoint 中, 像 operator的状态信息 是在设置了checkpoint 之后自动的进行快照吗 ?2. 上面这个和我们手动存储的 Keyed State 进行快照(这个应该是增量快照)](https://t.zsxq.com/mAqn2RF) 112、[现在有个实时商品数,交易额这种统计需求,打算用 flink从kafka读取binglog日志进行计算,但binglog涉及到insert和update这种操作时 怎么处理才能统计准确,避免那种重复计算的问题?](https://t.zsxq.com/E2BeQ3f) 113、[我这边用flink做实时监控,功能很简单,就是每条消息做keyby然后三分钟窗口,然后做些去重操作,触发阈值则报警,现在问题是同一个时间窗口同一个人的告警会触发两次,集群是三台机器,standalone cluster,初步结果是三个算子里有两个收到了同样的数据](https://t.zsxq.com/vjIeyFI) 114、[在使用WaterMark的时候,默认是每200ms去设置一次watermark,那么每个taskmanager之间,由于得到的数据不同,所以往往产生的最大的watermark不同。 那么这个时候,是各个taskmanager广播这个watermark,得到全局的最大的watermark,还是说各个taskmanager都各自用自己的watermark。主要没看到广播watermark的源码。不知道是自己观察不仔细还是就是没有广播这个变量。](https://t.zsxq.com/unq3FIa) 115、[现在遇到一个需求,需要在job内部定时去读取redis的信息,想请教flink能实现像普通程序那样的定时任务吗?](https://t.zsxq.com/AeUnAyN) 116、[有个触发事件开始聚合,等到数量足够,或者超时则sink推mq 环境 flink 1.6 用了mapState 记录触发事件 1 数据足够这个OK 2 超时state ttl 1.6支持,但是问题来了,如何在超时时候增加自定义处理?](https://t.zsxq.com/z7uZbY3) 117、[请问impala这种mpp架构的sql引擎,为什么稳定性比较差呢?](https://t.zsxq.com/R7UjeUF) 118、[watermark跟并行度相关不是,过于全局了,期望是keyby之后再针对每个keyed stream 打watermark,这个有什么好的实践呢?](https://t.zsxq.com/q7myfAQ) 119、[请问如果把一个文件的内容读取成datastream和dataset,有什么区别吗??他们都是一条数据一条数据的被读取吗?](https://t.zsxq.com/rB6yfeA) 120、[有没有kylin相关的资料,或者调优的经验?](https://t.zsxq.com/j2j6EyJ) 121、[flink先从jdbc读取配置表到流中,另外从kafka中新增或者修改这个配置,这个场景怎么把两个流一份配置流?我用的connect,接着发不成广播变量,再和实体流合并,但在合并时报Exception in thread "main" java.lang.IllegalArgumentException](https://t.zsxq.com/iMjmQVV) 122、[Flink exactly-once,kafka版本为0.11.0 ,sink基于FlinkKafkaProducer 每五分钟一次checkpoint,但是checkpoint开始后系统直接卡死,at-lease-once 一分钟能完成的checkpoint, 现在十分钟无法完成没进度还是0, 不知道哪里卡住了](https://t.zsxq.com/RFQNFIa) 123、[flink的状态是默认存在于内存的(也可以设置为rocksdb或hdfs),而checkpoint里面是定时存放某个时刻的状态信息,可以设置hdfs或rocksdb是这样理解的吗?](https://t.zsxq.com/NJq3rj2) 124、[Flink异步IO中,下图这两种有什么区别?为啥要加 CompletableFuture.supplyAsync,不太明白?](https://t.zsxq.com/NJq3rj2) 125、[flink的状态是默认存在于内存的(也可以设置为rocksdb或hdfs),而checkpoint里面是定时存放某个时刻的状态信息,可以设置hdfs或rocksdb是这样理解的吗?](https://t.zsxq.com/NJq3rj2) 126、[有个计算场景,从kafka消费两个数据源,两个数据结构都有时间段概念,计算需要做的是匹配两个时间段,匹配到了,就生成一条新的记录。请问使用哪个工具更合适,flink table还是cep?请大神指点一下 我这边之前的做法,将两个数据流转为table.两个table over window后join成新的表。结果job跑一会就oom.](https://t.zsxq.com/rniUrjm) 127、[一个互联网公司,或者一个业务系统,如果想做一个全面的监控要怎么做?有什么成熟的方案可以参考交流吗?有什么有什么度量指标吗?](https://t.zsxq.com/vRZ7qJ2) 128、[怎么深入学习flink,或者其他大数据组件,能为未来秋招找一份大数据相关(计算方向)的工作增加自己的竞争力?](https://t.zsxq.com/3vfyJau) 129、[oppo的实时数仓,其中明细层和汇总层都在kafka中,他们的关系库的实时数据也抽取到kafka的ods,那么在构建数仓的,需要join 三四个大业务表,业务表会变化,那么是大的业务表是从kafka的ods读取吗?实时数仓,多个大表join可以吗](https://t.zsxq.com/VBIunun) 130、[Tuple类型有什么方法转换成json字符串吗?现在的场景是,结果在存储到sink中时希望存的是json字符串,这样应用程序获取数据比较好转换一点。如果Tuple不好转换json字符串,那么应该以什么数据格式存储到sink中](https://t.zsxq.com/vnaURzj) 140、[端到端的数据保证,是否意味着中间处理程序中断,也不会造成该批次处理失败的消息丢失,处理程序重新启动之后,会再次处理上次未处理的消息](https://t.zsxq.com/J6eAmYb) 141、[关于flink datastream window相关的。比如我现在使用滚动窗口,统计一周内去重用户指标,按照正常watermark触发计算,需要等到当前周的window到达window的endtime时,才会触发,这样指标一周后才能产出结果。我能不能实现一小时触发一次计算,每次统计截止到当前时间,window中所有到达元素的去重数量。](https://t.zsxq.com/7qBMrBe) 142、[FLIP-16 Loop Fault Tolerance 是讲现在的checkpoint机制无法在stream loop的时候容错吗?现在这个问题解决了没有呀?](https://t.zsxq.com/uJqzBIe) 143、[现在的需求是,统计各个key的今日累计值,一分钟输出一次。如,各个用户今日累计点击次数。这种需求用datastream还是table API方便点?](https://t.zsxq.com/uZnmQzv) 144、[本地idea可以跑的工程,放在standalone集群上,总报错,报错截图如下,大佬请问这是啥原因](https://t.zsxq.com/BqnYRN7) 145、[比如现在用k8s起了一个flink集群,这时候数据源kafka或者hdfs会在同一个集群上吗,还是会单独再起一个hdfs/kafka集群](https://t.zsxq.com/7MJujMb) 146、[flink kafka sink 的FlinkFixedPartitioner 分配策略,在并行度小于topic的partitions时,一个并行实例固定的写消息到固定的一个partition,那么就有一些partition没数据写进去?](https://t.zsxq.com/6U7QFMj) 147、[基于事件时间,每五分钟一个窗口,五秒钟滑动一次,同时watermark的时间同样是基于事件事件时间的,延迟设为1分钟,假如数据流从12:00开始,如果12:07-12:09期间没有产生任何一条数据,即在12:07-12:09这段间的数据流情况为···· (12:07:00,xxx),(12:09:00,xxx)······,那么窗口[12:02:05-12:07:05],[12:02:10-12:07:10]等几个窗口的计算是否意味着只有等到,12:09:00的数据到达之后才会触发](https://t.zsxq.com/fmq3fYF) 148、[使用flink1.7,当消费到某条消息(protobuf格式),报Caused by: org.apache.kafka.common.KafkaException: Record batch for partition Notify-18 at offset 1803009 is invalid, cause: Record is corrupt 这个异常。 如何设置跳过已损坏的消息继续消费下一条来保证业务不终断? 我看了官网kafka connectors那里,说在DeserializationSchema.deserialize(...)方法中返回null,flink就会跳过这条消息,然而依旧报这个异常](https://t.zsxq.com/MRvv3ZV) 149、[是否可以抽空总结一篇Flink 的 watermark 的原理案例?一直没搞明白基于事件时间处理时的数据乱序和数据迟到底咋回事](https://t.zsxq.com/MRJeAuj) 150、[flink中rpc通信的原理,与几个类的讲解,有没有系统详细的文章样,如有求分享,谢谢](https://t.zsxq.com/2rJyNrF) 151、[Flink中如何使用基于事件时间处理,但是又不使用Watermarks? 我在会话窗口中使用遇到一些问题,图一是基于处理时间的,测试结果session是基于keyby(用户)的,图二是基于事件时间的,不知道是我用法不对还是怎么的,测试结果发现并不是基于keyby(用户的),而是全局的session。不知道怎么修改?](https://t.zsxq.com/bM3ZZRf) 152、[flink实时计算平台,yarn模式日志收集怎么做,为什么会checkpoint失败,报警处理,后需要做什么吗?job监控怎么做](https://t.zsxq.com/BMVzzzB) 153、[有flink与jstorm的在不同应用场景下, 性能比较的数据吗? 从网络上能找大部分都是flink与storm的比较. 在jstorm官网上有一份比较的图表, 感觉参考意义不大, 应该是比较早的flink版本.](https://t.zsxq.com/237EAay) 154、[为什么使用SessionWindows.withGap窗口的话,State存不了东西呀,每次加1 ,拿出来都是null, 我换成 TimeWindow就没问题。](https://t.zsxq.com/J6eAmYb) 155、[请问一下,flink datastream流处理怎么统计去重指标? 官方文档中只看到批处理有distinct概念。](https://t.zsxq.com/y3nYZrf) 156、[好全的一篇文章,对比分析 Flink,Spark Streaming,Storm 框架](https://t.zsxq.com/qRjqFY3) 157、[关于 structured_streaming 的 paper](https://t.zsxq.com/Eau7qNB) 158、[zookeeper集群切换领导了,flink集群项目重启了就没有数据的输入和输出了,这个该从哪方面入手解决?](https://t.zsxq.com/rFYbEeq) 159、[我想请教下datastream怎么和静态数据join呢](https://t.zsxq.com/nEAaYNF) 160、[时钟问题导致收到了明天的数据,这时候有什么比较好的处理方法?看到有人设置一个最大的跳跃阈值,如果当前数据时间 - 历史最大时间 超过阈值就不更新。如何合理的设计水印,有没有一些经验呢?](https://t.zsxq.com/IAAeiA6) 161、[大佬们flink怎么定时查询数据库?](https://t.zsxq.com/EuJ2RRf) 162、[现在我们公司有个想法,就是提供一个页面,在页面上选择source sink 填写上sql语句,然后后台生成一个flink的作业,然后提交到集群。功能有点类似于华为的数据中台,就是页面傻瓜式操作。后台能自动根据相应配置得到结果。请问拘你的了解,可以实现吗?如何实现?有什么好的思路。现在我无从下手](https://t.zsxq.com/vzZBmYB) 163、[请教一下 flink on yarn 的 ha机制](https://t.zsxq.com/VRFIMfy) 164、[在一般的流处理以及cep, 都可以对于eventtime设置watermark, 有时可能需要设置相对大一点的值, 这内存压力就比较大, 有没有办法不应用jvm中的内存, 而用堆外内存, 或者其他缓存, 最好有cache机制, 这样可以应对大流量的峰值.](https://t.zsxq.com/FAiiEyr) 165、[请教一个flink sql的问题。我有两个聚合后的流表A和B,A和Bjoin得到C表。在设置state TTL 的时候是直接对C表设置还是,对A表和B表设置比较好?](https://t.zsxq.com/YnI2F66) 166、[spark改写为flink,会不会很复杂,还有这两者在SQL方面的支持差别大吗?](https://t.zsxq.com/unyneEU) 167、[请问flink allowedLateness导致窗口被多次fire,最终数据重复消费,这种问题怎么处理,数据是写到es中](https://t.zsxq.com/RfyZFUR) 168、[设置taskmanager.numberOfTaskSlots: 4的时候没有问题,但是cpu没有压上去,只用了30%左右,于是设置了taskmanager.numberOfTaskSlots: 8,但是就报错误找不到其中一个自定义的类,然后kafka数据就不消费了。为什么?cpu到多少合适?slot是不是和cpu数量一致是最佳配置?kafka分区数多少合适,是不是和slot,parallesim一致最佳?](https://t.zsxq.com/bIAEyFe) 169、[需求是根据每条日志切分出需要9个字段,有五个指标再根据9个字段的不同组合去做计算。 第一个方法是:我目前做法是切分的9个字段开5分钟大小1分钟计算一次的滑动窗口窗口,进行一次reduce去重,然后再map取出需要的字段,然后过滤再开5分钟大小1分钟计算一次的滑动窗口窗口进行计算保存结果,这个思路遇到的问题是上一个滑动窗口会每一分钟会计算5分钟数据,到第二个窗口划定的5分钟范围的数据会有好多重复,这个思路会造成数据重复。 第二个方法是:切分的9个字段开5分钟大小1分钟计算一次的滑动窗口窗口,再pross方法里完成所有的过滤,聚合计算,但是再高峰期每分钟400万条数据,这个思路担心在高峰期flink计算不过来](https://t.zsxq.com/BUNfYnY) 170、[a,b,c三个表,a和c有eventtime,a和c直接join可以,a和b join后再和c join 就会报错,这是怎么回事呢](https://t.zsxq.com/aAqBEY7) 171、[自定义的source是这样的(图一所示) 使用的时候是这样的(图二所示),为什么无论 sum.print().setParallelism(2)(图2所示)的并行度设置成几最后结果都是这样的](https://t.zsxq.com/zZNNRzr) 172、[刚接触flink,如有问的不合适的地方,请见谅。 1、为什么说flink是有状态的计算? 2、这个状态是什么?3、状态存在哪里](https://t.zsxq.com/i6Mz7Yj) 173、[这边用flink 1.8.1的版本,采用flink on yarn,hadoop版本2.6.0。代码是一个简单的滚动窗口统计函数,但启动的时候报错,如下图片。 (2)然后我把flink版本换成1.7.1,重新提交到2.6.0的yarn平台,就能正常运行了。 (3)我们测试集群hadoop版本是3.0,我用flink 1.8.1版本将这个程序再次打包,提交到3.0版本的yarn平台,也能正常运行。 貌似是flink 1.8.1版本与yarn 2.6.0版本不兼容造成的这个问题](https://t.zsxq.com/vNjAIMN) 174、[StateBackend我使用的是MemoryStateBackend, State是怎么释放内存的,例如我在函数中用ValueState存储了历史状态信息。但是历史状态数据我没有手动释放,那么程序会自动释放么?还是一直驻留在内存中](https://t.zsxq.com/2rVbm6Y) 175、[请问老师是否可以提供一些Apachebeam的学习资料 谢谢](https://t.zsxq.com/3bIEAyv) 176、[flink 的 DataSet或者DataStream支持索引查询以及删除吗,像spark rdd,如果不支持的话,该转换成什么](https://t.zsxq.com/yFEyZVB) 177、[关于flink的状态,能否把它当做数据库使用,类似于内存数据库,在处理过程中存业务数据。如果是数据库可以算是分布式数据库吗?是不是使用rocksdb这种存储方式才算是?支持的单库大小是不是只是跟本地机器的磁盘大小相关?如果使用硬盘存储会不会效率性能有影响](https://t.zsxq.com/VNrn6iI) 178、[我这边做了个http sink,想要批量发送数据,不过现在只能用数量控制发送,但最后的几个记录没法触发发送动作,想问下有没有什么办法](https://t.zsxq.com/yfmiUvf) 179、[请问下如何做定时去重计数,就是根据时间分窗口,窗口内根据id去重计数得出结果,多谢。试了不少办法,没有简单直接办法](https://t.zsxq.com/vNvrfmE) 180、[我有个job使用了elastic search sink. 设置了批量5000一写入,但是看es监控显示每秒只能插入500条。是不是bulkprocessor的currentrequest为0有关](https://t.zsxq.com/rzZbQFA) 181、[有docker部署flink的资料吗](https://t.zsxq.com/aIur7ai) 182、[在说明KeyBy的StreamGraph执行过程时,keyBy的ID为啥是6? 根据前面说,ID是一个静态变量,每取一次就递增1,我觉得应该是3啊,是我理解错了吗](https://t.zsxq.com/VjQjqF6) 183、[有没计划出Execution Graph的远码解析](https://t.zsxq.com/BEmAIQv) 184、[可以分享下物理执行图怎样划分task,以及task如何执行,还有他们之间数据如何传递这块代码嘛?](https://t.zsxq.com/vVjiYJQ) 185、[Flink源码和这个学习项目的结构图](https://t.zsxq.com/FyNJQbQ) 186、[请问flink1.8,如何做到动态加载外部udf-jar包呢?](https://t.zsxq.com/qrjmmaU) 187、[同一个Task Manager中不同的Slot是怎么交互的,比如:source处理完要传递给map的时候,如果在不同的Slot中,他们的内存是相互隔离,是怎么交互的呢? 我猜是通过序列化和反序列化对象,并且通过网络来进行交互的](https://t.zsxq.com/ZFQjQnm) 188、[你们有没有这种业务场景。flink从kafka里面取数据,每一条数据里面有mongdb表A的id,这时我会在map的时候采用flink的异步IO连接A表,然后查询出A表的字段1,再根据该字段1又需要异步IO去B表查询字段2,然后又根据字段2去C表查询字段3.....像这样的业务场景,如果多来几种逻辑,我应该用什么方案最好呢](https://t.zsxq.com/YBQFufi) 189、[今天本地运行flink程序,消费socket中的数据,连续只能消费两条,第三条flink就消费不了了](https://t.zsxq.com/vnufYFY) 190、[源数据经过过滤后分成了两条流,然后再分别提取事件时间和水印,做时间窗口,我测试时一条流没有数据,另一条的数据看日志到了窗口操作那边就没走下去,貌似窗口一直没有等到触发](https://t.zsxq.com/me6EmM3) 191、[有做flink cep的吗,有资料没?](https://t.zsxq.com/fubQrvj) 192、[麻烦问一下 BucketingSink跨集群写,如果任务运行在hadoop A集群,从kafka读取数据处理后写到Hadoo B集群,即使把core-site.xml和hdfs-site.xml拷贝到代码resources下,路径使用hdfs://hadoopB/xxx,会提示ava.lang.RuntimeException: Error while creating FileSystem when initializing the state of the BucketingSink.,跨集群写这个问题 flink不支持吗?](https://t.zsxq.com/fEQVjAe) 193、[想咨询下,如何对flink中的datastream和dataset进行数据采样](https://t.zsxq.com/fIMVJ2J) 194、[一个flink作业经常发生oom,可能是什么原因导致的。 处理流程只有15+字段的解析,redis数据读取等操作,TM配置10g。 业务会在夜间刷数据,qps能打到2500左右~](https://t.zsxq.com/7MVjyzz) 195、[我看到flink 1.8的状态过期仅支持Processing Time,那么如果我使用的是Event time那么状态就不会过期吗](https://t.zsxq.com/jA2NVnU) 196、[请问我想每隔一小时统计一个属性从当天零点到当前时间的平均值,这样的时间窗该如何定义?](https://t.zsxq.com/BQv33Rb) 197、[flink任务里面反序列化一个类,报ClassNotFoundException,可是包里面是有这个类的,有遇到这种情况吗?](https://t.zsxq.com/nEAiIea) 198、[在构造StreamGraph,类似PartitionTransformmation 这种类型的 transform,为什么要添加成一个虚拟节点,而不是一个实际的物理节点呢?](https://t.zsxq.com/RnayrVn) 199、[flink消费kafka的数据写入到hdfs中,我采用了BucketingSink 这个sink将operator出来的数据写入到hdfs文件上,并通过在hive中建外部表来查询这个。但现在有个问题,处于in-progress的文件,hive是无法识别出来该文件中的数据,可我想能在hive中实时查询进来的数据,且不想产生很多的小文件,这个该如何处理呢](https://t.zsxq.com/A2fYNFA) 200、[采用Flink单机集群模式一个jobmanager和两个taskmanager,机器是单机是24核,现在做个简单的功能从kafka的一个topic转满足条件的消息到另一个topic,topic的分区是30,我设置了程序默认并发为30,现在每秒消费2w多数据,不够快,请问可以怎么提高job的性能呢?](https://t.zsxq.com/7AurJU3) 201、[Flink Metric 源码分析](https://t.zsxq.com/Mnm2nI6) 202、[请问怎么理解官网的这段话?按官网的例子,难道只keyby之后才有keyed state,才能托管Flink存储状态么?source和map如果没有自定义operator state的话,状态是不会被保存的?](https://t.zsxq.com/iAi6QRb) 203、[想用Flink做业务监控告警,并要能够支持动态添加CEP规则,问下可以直接使用Flink CEP还是siddhi CEP? 有没有相关的资料学习下?谢谢!](https://t.zsxq.com/3rbeuju) 204、[请问一下,有没有关于水印,触发器的Java方面的demo啊](https://t.zsxq.com/eYJUbm6) 205、[老师,最近我们线上偶尔出现这种情况,就是40个并行度,其他有一个并行度CheckPoint一直失败,其他39个并行度都是毫秒级别就可以CheckPoint成功,这个怎么定位问题呢?还有个问题 CheckPoint的时间分为三部分 Checkpoint Duration (Async)和 Checkpoint Duration (Sync),还有个 end to end 减去同步和异步的时间,这三部分 分别指代哪块?如果发现这三者中的任意一个步骤时间长,该怎么去优化](https://t.zsxq.com/QvbAqVB) 206、[我这边有个场景很依赖消费出来的数据的顺序。在源头侧做了很多处理,将kafka修改成一个分区等等很多尝试,最后消费出来的还是乱序的。能不能在flink消费的时候做处理,来保证处理的数据的顺序。](https://t.zsxq.com/JaUZvbY) 207、[有一个类似于实时计算今天的pv,uv需求,采用source->keyby->window->trigger->process后,在process里采用ValueState计算uv ,问题是 这个window内一天的所有数据是都会缓存到flink嘛? 一天的数据量如果大点,这样实现就有问题了, 这个有其他的实现思路嘛?](https://t.zsxq.com/iQfaAeu) 208、[Flink 注解源码解析](https://t.zsxq.com/f6eAu3J) 209、[如何监控 Flink 的 TaskManager 和 JobManager](https://t.zsxq.com/IuRJYne) 210、[问下,在真实流计算过程中,并行度的设置,是与 kafka topic的partition数一样的吗?](https://t.zsxq.com/v7yfEIq) 211、[Flink的日志 如果自己做平台封装在自己的界面中 请问job Manger 和 taskManger 还有用户自己的程序日志 怎么获取呢 有api还是自己需要利用flume 采集到ELK?](https://t.zsxq.com/Zf2F6mM) 212、[我想问下一般用Flink统计pv uv是怎么做的?uv存到redis? 每个uv都存到redis,会不会撑爆?](https://t.zsxq.com/72VzBEy) 213、[Flink的Checkpoint 机制,在有多个source的时候,barrier n 的流将被暂时搁置,从其他流接收的记录将不会被处理,但是会放进一个输入缓存input buffer。如果被缓存的record大小超出了input buffer会怎么样?不可能一直缓存下去吧,如果其中某一条就一直没数据的话,整个过程岂不是卡死了?](https://t.zsxq.com/zBmm2fq) 214、[公司想实时展示订单数据,汇总金额,并需要和前端交互,实时生成数据需要告诉前端,展示成折线图,这种场景的技术选型是如何呢?包括数据的存储,临时汇总数据的存储,何种形式告诉前端](https://t.zsxq.com/ZnIAi2j) 215、[请问下checkpoint中存储了哪些东西?](https://t.zsxq.com/7EIeEyJ) 216、[我这边有个需求是实时计算当前车辆与前车距离,用经纬度求距离。大概6000台车,10秒一条经纬度数据。gps流与自己join的地方在进行checkpoint的时候特别缓,每次要好几分钟。checkpoint 状态后端是rocksDB。有什么比较好的方案吗?自己实现一个类似last_value的函数取车辆最新的经纬再join,或者弄个10秒的滑动窗口输出车辆最新的经纬度再进行join,这样可行吗?](https://t.zsxq.com/euvFaYz) 217、[flink在启动的时候能不能指定一个时间点从kafka里面恢复数据呢](https://t.zsxq.com/YRnEUFe) 218、[我们线上有个问题,很多业务都去读某个hive表,但是当这个hive表正在写数据的时候,偶尔出现过 读到表里数据为空的情况,这个问题怎么解决呢?](https://t.zsxq.com/7QJEEyr) 219、[使用 InfluxDB 和 Grafana 搭建监控 Flink 的平台](https://t.zsxq.com/yVnaYR7) 220、[flink消费kafka两个不同的topic,然后进行join操作,如果使用事件时间,两个topic都要设置watermaker吗,如果只设置了topic A的watermaker,topic B的不设置会有什么影响吗?](https://t.zsxq.com/uvFU7aY) 221、[请教一个问题,我的Flink程序运行一段时间就会报这个错误,定位好多天都没有定位到。checkpoint 时间是5秒,20秒都不行。Caused by: java.io.IOException: Could not flush and close the file system output stream to hdfs://HDFSaaaa/flink/PointWideTable_OffTest_Test2/1eb66edcfccce6124c3b2d6ae402ec39/chk-355/1005127c-cee3-4099-8b61-aef819d72404 in order to obtain the stream state handle](https://t.zsxq.com/NNFYJMn) 222、[Flink的反压机制相比于Storm的反压机制有什么优势呢?问题2: Flink的某一个节点发生故障,是否会影响其他节点的正常工作?还是会通过Checkpoint容错机制吗把任务转移到其他节点去运行呢?](https://t.zsxq.com/yvRNFEI) 223、[我在验证checkpoint的时候遇到给问题,不管是key state 还是operator state,默认和指定uid是可以的恢复state数据的,当指定uidHash时候无法恢复state数据,麻烦大家给解答一样。我操作state是实现了CheckpointedFunction接口,覆写snapshotState和initializeState,再这两个方法里操作的,然后让程序定时抛出异常,观察发现指定uidHash后snapshotState()方法里context.isRestored()为false,不太明白具体是什么原因](https://t.zsxq.com/ZJmiqZz) 224、[kafka 中的每条数据需要和 es 中的所有数据(动态增加)关联,关联之后会做一些额外的操作,这个有什么比较可行的方案?](https://t.zsxq.com/mYV37qF) 225、[flink消费kafka数据,设置1分钟checkpoint一次,假如第一次checkpoint完成以后,还没等到下一次checkpoint,程序就挂了,kafka offset还是第一次checkpoint记录的offset,那么下次重新启动程序,岂不是多消费数据了?那flink的 exactly one消费语义是怎么样的?](https://t.zsxq.com/buFeyZr) 226、[程序频繁发生Heartbeat of TaskManager with id container_e36_1564049750010_5829_01_000024 timed out. 心跳超时,一天大概10次左右。是内存没给够吗?还是网络波动引起的](https://t.zsxq.com/Znyja62) 227、[有没有性能优化方面的指导文章?](https://t.zsxq.com/AA6ma2Z) 228、[flink消费kafka是如何监控消费是否正常的,有啥好办法?](https://t.zsxq.com/a2N37a6) 229、[我按照官方的wordcount案例写了一个例子,然后在main函数中起了一个线程,原本是准备定时去更新某些配置,准备测试一下是否可行,所以直接在线程函数中打印一条语句测试是否可行。现在测试的结果是不可行,貌似这个线程根本就没有执行,请问这是什么原因呢? 按照理解,JobClient中不是反射类执行main函数吗, 执行main函数的时候为什么没有执行这个线程的打印函数呢?](https://t.zsxq.com/m2FeeMf) 230、[请问我想保留最近多个完成的checkpoint数据,是通过设置 state.checkpoints.num-retained 吗?要怎么使用?](https://t.zsxq.com/EyFUb6m) 231、[有没有etl实时数仓相关案例么?比如二十张事实表流join](https://t.zsxq.com/rFeIAeA) 232、[为什么我扔到flink 的stream job,立刻就finished](https://t.zsxq.com/n2RFmyN) 233、[有没有在flink上机器学习算法的一些例子啊,除了官网提供的flink exampke里的和flink ml里已有的](https://t.zsxq.com/iqJiyvN) 234、[如果我想扩展sql的关键词,比如添加一些数据支持,有什么思路,现在想的感觉都要改calcite(刚碰flink感觉难度太大了)](https://t.zsxq.com/uB6aUzZ) 235、[我想实现统计每5秒中每个类型的次数,这个现在不输出,问题出在哪儿啊](https://t.zsxq.com/2BEeu3Z) 236、[我用flink往hbase里写数据,有那种直接批量写hfile的方式的demo没](https://t.zsxq.com/VBA6IUR) 237、[请问怎么监控Kafka消费是否延迟,是否出现消息积压?你有demo吗?这种是用Springboot自己写一个监控,还是咋整啊?](https://t.zsxq.com/IieMFMB) 238、[请问有计算pv uv的例子吗](https://t.zsxq.com/j2fM3BM) 239、[通过控制流动态修改window算子窗口类型和长度要怎么写](https://t.zsxq.com/Rb2Z7uB) 240、[flink的远程调试能出一版么?网上资料坑的多](https://t.zsxq.com/UVbaQfM) 241、[企业里,Flink开发,java用得多,还是scala用得多?](https://t.zsxq.com/AYVjAuB) 242、[flink的任务运行在yarn的环境上,在yarn的resourcemanager在进行主备切换时,所有的flink任务都失败了,而MR的任务可以正常运行。报错信息如下:AM is not registered for known application attempt: appattempt_1565306391442_89321_000001 or RM had restarted after AM registered . AM should re-register 请问这是什么原因,该如何处理呢?](https://t.zsxq.com/j6QfMzf) 243、[请教一个分布式问题,比如在Flink的多个TaskManager上统计指标count,TM1有两条数据,TM2有一条数据,程序是怎么计算出来是3呢?原理是怎么样的](https://t.zsxq.com/IUVZjUv) 244、[现在公司部分sql查询oracle数据特别的慢,因为查询条件很多想问一下有什么方法,例如基于大数据组件可以加快查询速度的吗?](https://t.zsxq.com/7MFEQR3) 245、[想咨询下有没有做过flink同步配置做自定义计算的系统?或者有没有什么好的建议?业务诉求是希望业务用户可以自助配置计算规则做流式计算](https://t.zsxq.com/Mfa6aQB) 246、[我这边有个实时同步数据的任务,白天运行的时候一直是正常的,一到凌晨2点多之后就没有数据sink进mysql。晚上会有一些离线任务和一些dataX任务同步数据到mysql。但是任务一切都是正常的,ck也很快20ms,数据也是正常消费。看了yarn上的日志,没有任何error。自定义的sink里面也设置了日志打印,但是log里没有。这种如何快速定位问题。](https://t.zsxq.com/z3bunyN) 247、[有没有flink处理异常数据的案例资料](https://t.zsxq.com/Y3fe6Mn) 248、[flink中如何传递一个全局变量](https://t.zsxq.com/I2Z7Ybm) 249、[台4核16G的Flink taskmanager配一个单独的Yarn需要一台啥样的服务器?其他功能都不需要就一个调度的东西?](https://t.zsxq.com/iIUZrju) 250、[side-output 的分享](https://t.zsxq.com/m6I2BEE) 251、[使用 InfluxDB + Grafana 监控flink能否配置告警。是不是prometheus更强大点?](https://t.zsxq.com/amURFme) 252、[我们线上遇到一个问题,带状态的算子没有指定 uid,现在代码必须改,那个带状态的算子 不能正常恢复了,有解吗?通过某种方式能获取到系统之前自动生成的uid吗?](https://t.zsxq.com/rZfyZvn) 253、[tableEnv.registerDataStream("Orders", ds, "user, product, amount, proctime.proctime, rowtime.rowtime");请问像这样把流注册成表的时候,这两个rowtime分别是什么意思](https://t.zsxq.com/uZz3Z7Q) 254、[我想问一下 flink on yarn session 模式下提交任务官网给的例子是 flink run -c xxx.MainClass job.jar 这里是怎么知道 yarn 上的哪个是 flink 的 appid 呢?](https://t.zsxq.com/yBiEyf2) 255、[Flink Netty Connector 这个有详细的使用例子? 通过Netty建立的source能直接回复消息吗?还是只能被动接受消息?](https://t.zsxq.com/yBeyfqv) 256、[请问flink sqlclient 提交的作业可以用于生产环境吗?](https://t.zsxq.com/FIEia6M) 257、[flink批处理写回mysql是否没法用tableEnv.sqlUpdate("insert into t2 select * from t1")?作为sink表的t2要如何注册?查跟jdbc相关的就两个TableSink,JDBCAppendTableSink用于BatchTableSink,JDBCUpertTablSink用于StreamTableSink。前者只接受insert into values语法。所以我是先通过select from查询获取到DataSet再JDBCAppendTableSink.emitDataSet(ds)实现的,但这样达不到sql rule any目标](https://t.zsxq.com/ZBIaUvF) 258、[请问在stream模式下,flink的计算结果在不落库的情况下,可以通过什么restful api获取计算结果吗](https://t.zsxq.com/aq3BIU7) 259、[现在我有场景,需要把一定的消息发送给kafka topic指定的partition,该怎么搞?](https://t.zsxq.com/NbYnAYF) 260、[请问我的job作业在idea上运行正常 提交到生产集群里提示Caused by: java.lang.NoSuchMethodError: org.apache.flink.api.java.ClosureCleaner.clean(Ljava/lang/Object;Z)V请问如何解决](https://t.zsxq.com/YfmAMfm) 261、[遇到一个很奇怪的问题,在使用streamingSQL时,发现timestamp在datastream的时候还是正常的,在注册成表print出来的时候就少了八小时,大佬知道是什么原因么?](https://t.zsxq.com/72n6MVb) 262、[请问将flink的产生的一些记录日志异步到kafka中,需要如何配置,配置后必须要重启集群才会生效吗](https://t.zsxq.com/RjQFmIQ) 263、[星主你好,问下flink1.9对维表join的支持怎么样了?有文档吗](https://t.zsxq.com/Q7u3vzR) 264、[请问下 flink slq: SELECT city_name as city_name, count(1) as total, max(create_time) as create_time FROM * 。代码里面设置窗口为: retractStream.timeWindowAll(Time.minutes(5))一个global窗口,数据写入hdfs 结果数据重复 ,存在两条完全重复的数据如下 常州、2283、 1566230703):请问这是为什么](https://t.zsxq.com/aEEA66M) 265、[我用rocksdb存储checkpoint,线上运行一段时间发展checkpoint占用空间越来越大,我是直接存本地磁盘上的,怎么样能让它自动清理呢?](https://t.zsxq.com/YNrfyrj) 266、[flink应该在哪个用户下启动呢,是root的还是在其他的用户呢](https://t.zsxq.com/aAaqFYn) 267、[link可以读取lzo的文件吗](https://t.zsxq.com/2nUBIAI) 268、[怎么快速从es里面便利数据?我们公司现在所有的数据都存在Es里面的;我发现每次从里面scan数据的时候特别慢;你那有没有什么好的办法?](https://t.zsxq.com/beIY7mY) 269、[如果想让数据按照其中一个假如f0进行分区,然后每一个分区做处理的时候并行度都是1怎么设置呢](https://t.zsxq.com/fYnYrR7) 270、[近在写算子的过程中,使用scala语言写flink比较快,而且在process算子中实现ontime方式时,可以使用scala中的listbuff来输出一个top3的记录;那么到了java中,只能用ArrayList将flink中的ListState使用get()方法取出之后放在ArrayList吗?](https://t.zsxq.com/nQFYrBm) 271、[请问老师能否出一些1.9版本维表join的例子 包括async和维表缓存?](https://t.zsxq.com/eyRRv7q) 272、[flink kaka source设置为从组内消费,有个问题是第一次启动任务,我发现kafka中的历史数据不会被消费,而是从当前的数据开始消费,而第二次启动的时候才会从组的offset开始消费,有什么办法可以让第一次启动任务的时候可以消费kafka中的历史数据吗](https://t.zsxq.com/aMRzjMb) 273、[1.使用flink定时处理离线数据,有时间戳字段,如何求出每分钟的最大值,类似于流处理窗口那样,2如果想自己实现批流统一,有什么好的合并方向吗?比如想让流处理使用批处理的一个算子。](https://t.zsxq.com/3ZjiEMv) 274、[flink怎么实现流式数据批量对待?流的数据是自定义的source,读取的redis多个Hash表,需要控制批次的概念](https://t.zsxq.com/AIYnEQN) 275、[有人说不推荐在一个task中开多个线程,这个你怎么看?](https://t.zsxq.com/yJuFEYb) 276、[想做一个运行在hbase+es架构上的sql查询方案,flink sql能做吗,或者有没有其他的解决方案或者思路?](https://t.zsxq.com/3f6YBmu) 277、[正在紧急做第一个用到Flink的项目,咨询一下,Flink 1.8.1写入ES7就是用自带的Sink吗?有没有例子分享一下,我搜到的都是写ES6的。这种要求我知道不适合提,主要是急,自己试几下没成功。T T](https://t.zsxq.com/jIAqVnm) 278、[手动停止任务后,已经保存了最近一次保存点,任务重新启动后,如何使用上一次检查点?](https://t.zsxq.com/2fAiuzf) 279、[批处理使用流环境(为了使用窗口),那如何确定批处理结束,就是我的任务可以知道批文件读取完事,并且处理完数据后关闭任务,如果不能,那批处理如何实现窗口功能](https://t.zsxq.com/BIiImQN) 280、[如果限制只能在window 内进行去重,数据量还比较大,有什么好的方法吗?](https://t.zsxq.com/Mjyzj66) 281、[端到端exactly once有没有出文章](https://t.zsxq.com/yv7Ujme) 282、[流怎么动态加?,流怎么动态删除?,参数怎么动态修改 (广播](https://t.zsxq.com/IqNZFey) 283、[自定义的source数据源实现了有批次的概念,然后Flink将这个一个批次流注册为多个表join操作,有办法知道这个sql什么时候计算完成了?](https://t.zsxq.com/r7AqvBq) 284、[编译 Flink 报错,群主遇到过没,什么原因](https://t.zsxq.com/rvJiyf6) 285、[我现在是flink on yarn用zookeeper做HA现在在zk里查看检查点信息,为什么里面的文件是ip,而不是路径呢?我该如何拿到那个路径。 - 排除rest api 方式获取,因为任务关了restapi就没了 -排除history server,有点不好用](https://t.zsxq.com/nufIaey) 286、[在使用streamfilesink消费kafka之后进行hdfs写入的时候,当直接关闭flink程序的时候,下次再启动程序消费写入hdfs的时候,文件又是从part-0-0开始,这样就跟原来写入的冲突了,该文件就一直处于ingress状态。](https://t.zsxq.com/Fy3RfE6) 287、[现在有一个实时数据分析的需求,数据量不大,但要求sink到mysql,因为是实时更新的,我现在能想到的处理方法就是每次插入一条数据的时候,先从mysql读数据,如果有这条,就执行update,没有的话就insert,但是这样的话每写一条数据就有两次交互了。想问一下老师有没有更好的办法,或者flink有没有内置的api可以执行这种不确定是更新还是插入的操作](https://t.zsxq.com/myNF2zj) 288、[Flink设置了checkpoint,job manage会定期删除check point数据,但是task manage不删除,这个是什么原因](https://t.zsxq.com/ZFiMzrF) 289、[请教一下使用rocksdb作为statebackend ,在哪里可以监控rocksdb io 内存指标呢](https://t.zsxq.com/z3RzJUV) 290、[状态的使用场景,以及用法能出个文章不,这块不太了解](https://t.zsxq.com/AUjE2ZR) 291、[请问一下 Flink 1.9 SQL API中distinct count 是如何实现高效的流式去重的?](https://t.zsxq.com/aaynii6) 292、[在算子内如何获取当前算子并行度以及当前是第几个task](https://t.zsxq.com/mmEyVJA) 293、[有没有flink1.9结合hive的demo。kafka到hive](https://t.zsxq.com/fIqNF6y) 294、[能给讲讲apache calcite吗](https://t.zsxq.com/ne6UZrB) 295、[请问一下像这种窗口操作,怎么保证程序异常重启后保持数据的状态呢?](https://t.zsxq.com/VbUVFMr) 296、[请问一下,我在使用kafkasource的时候,把接过来的Jsonstr转化成自定义的一个类型,用的是gson. fromJson(jsonstr,classOf[Entity])报图片上的错误了,不知道怎么解决,在不转直接打印的情况下是没问题的](https://t.zsxq.com/EMZFyZz) 297、[DataStream读数据库的表,做多表join,能设置时间窗口么,一天去刷一次。流程序会一直拉数据,数据库扛不住了](https://t.zsxq.com/IEieI6a) 298、[请问一下flink支持多路径通配读取吗?例如路径:s3n://pekdc2-deeplink-01/Kinesis/firehose/2019/07/03/*/* ,通配读取找不到路径。是否需要特殊设置](https://t.zsxq.com/IemmiY7) 299、[flink yarn环境部署 但是把容器的url地址删除。就会跳转到的hadoop的首页。怎么屏蔽hadoop的yarn首页地址呢?要不暴露这个地址用户能看到所有任务很危险](https://t.zsxq.com/QvZFUNN) 300、[flink sql怎么写一个流,每秒输出当前时间呢](https://t.zsxq.com/2JiubeM) 301、[因为想通过sql弄一个数据流。哈哈 另外想问一个问题,我把全局设置为根据处理时间的时间窗口,那么我在processAllWindowFunction里面要怎么知道进来的每个元素的处理时间是多少呢?这个元素进入这个时间窗口的依据是什么](https://t.zsxq.com/bQ33BmM) 302、[如何实现一个设备上报的数据存储到同一个hdfs文件中?](https://t.zsxq.com/rB6ybYF) 303、[我自己写的kafka生产者测试,数据格式十分简单(key,i)key是一个固定的不变的字符串,i是自增的,flink consumer这边我开了checkpoint. 并且是exactly once,然后程序很简单,就是flink读取kafka的数据然后直接打印出来,我发现比如我看到打印到key,10的时候我直接关掉程序,然后重新启动程序,按理来说应当是从上次的offset继续消费,也就是key,11,但实际上我看到的可能是从key,9开始,然后依次递增,这是是不是说明是重复消费了,那exactly one需要怎么样去保障?](https://t.zsxq.com/MVfeeiu) 304、[假设有一个数据源在源源不断的产生数据,到Flink的反压来到source端的时候,由于Flink处理数据的速度跟不上数据源产生数据的速度, 问题1: 这个时候在Flink的source端会怎么处理呢?是将处理不完的数据丢弃还是进行缓存呢? 问题2: 如果是缓存,怎么进行缓存呢?](https://t.zsxq.com/meqzJme) 305、[一个stream 在sink多个时,这多个sink是串行 还是并行的。](https://t.zsxq.com/2fEeMny) 306、[我想在流上做一个窗口,触发窗口的条件是固定的时间间隔或者数据量达到预切值,两个条件只要有一个满足就触发,除了重写trigger在,还有什么别的方法吗?](https://t.zsxq.com/NJY76uf) 307、[使用rocksdb作为状态后端,对于使用sql方式对时间字段进行group by,以达到去窗口化,但是这样没办法对之前的数据清理,导致磁盘空间很大,对于这种非编码方式,有什么办法设置ttl,清理以前的数据吗](https://t.zsxq.com/A6UN7eE) 308、[请问什么时间窗为什么会有TimeWindow{start=362160000, end=362220000} 和 TimeWindow{start=1568025300000, end=1568025360000}这两种形式,我都用的是一分钟的TumblingEventTimeWindows,为什么会出现不同的情况?](https://t.zsxq.com/a2fUnEM) 309、[比如我统计一天的订单量。但是某个数据延迟一天才到达。比如2019.08.01这一天订单量应该是1000,但是有个100的单据迟到了,在2019.08.02才到达,那么导致2019.08.01这一天统计的是900.后面怎么纠正这个错误的结果呢](https://t.zsxq.com/Y3jqjuj) 310、[flink streaming 模式下只使用堆内内存么](https://t.zsxq.com/zJaMNne) 311、[如果考虑到集群的迁移,状态能迁移吗](https://t.zsxq.com/EmMrvVb) 312、[我们现在有一个业务场景,数据上报的值是这样的格式(时间,累加值),我们需要这样的格式数据(时间,当前值)。当前值=累加值-前一个数据的累加值。flink如何做到呢,有考虑过state机制,但是服务宕机后,state就被清空了](https://t.zsxq.com/6EUFeqr) 313、[Flink On k8s 与 Flink on Yarn相比的优缺点是什么?那个更适合在生产环境中使用呢](https://t.zsxq.com/y7U7Mzf) 314、[有没有datahub链接flink的 连接器呀](https://t.zsxq.com/zVNbaYn) 315、[单点resourcemanager 挂了,对任务会产生什么影响呢](https://t.zsxq.com/FQRNJ2j) 316、[flink监控binlog,跟另一张维表做join后,sink到MySQL的最终表。对于最终表的增删改操作,需要定义不同的sink么?](https://t.zsxq.com/rnemUN3) 317、[请问窗口是在什么时候合并的呢?例如:数据进入windowoperator的processElement,如果不是sessionwindow,是否会进行窗口合并呢?](https://t.zsxq.com/JaaQFqB) 318、[Flink中一条流能参与多路计算,并多处输出吗?他们之前会不会相互影响?](https://t.zsxq.com/AqNFM33) 319、[keyBy算子定义是将一个流拆分成不相交的分区,每个分区包含具有相同的key的元素。我不明白的地方是: keyBy怎么设置分区数,是给这个算子设置并行度吗? 分区数和slot数量是什么关系?](https://t.zsxq.com/nUzbiYj) 320、[动态cep-pattern,能否详细说下?滴滴方案未公布,您贴出来的几张图片是基于1.7的。或者有什么想法也可以讲解下,谢谢了](https://t.zsxq.com/66URfQb) 321、[问题1:使用常驻型session ./bin/yarn-session.sh -n 10 -s 3 -d启动,这个时候分配的资源是yarn 队列里面的, flink提交任务 flink run xx.jar, 其余机器是怎样获取到flink需要运行时的环境的,因为我只在集群的一台机器上有flink 安装包。](https://t.zsxq.com/maEQ3NR) 322、[flink task manager中slot间的内存隔离,cpu隔离是怎么实现的?flink 设计slot的概念有什么意义,为什么不像spark executor那样,内部没有做隔离?](https://t.zsxq.com/YjEYjQz) 323、[spark和kafka集成,direct模式,spark的一个分区对应kafka的一个主题的一个分区。那flink和kafka集成的时候,怎么消费kafka的数据,假设kafka某个主题5个partition](https://t.zsxq.com/nuzvVzZ) 324、[./bin/flink run -m yarn-cluster 执行的flink job ,作业自己打印的日志通过yarn application的log查看不了,只有集群自身的日志,程序中logger.info打印日志存放在哪,还是我打包的方式问题,打日志用的是slf4j。](https://t.zsxq.com/27u3ZZf) 325、[在物联网平台中,需要对每个key下的数据做越限判断,由于每个key的越限值是不同的,越限值配置在实时数据库中。 若将越限值加载到state中,由于key的量很大(大概3亿左右),会导致state太大,可能造成内存溢出。若在处理数据时从实时数据库中读取越限值,由于网络IO开销,可能造成实时性下降。请问该如何处理?谢谢](https://t.zsxq.com/miuzFY3) 326、[如果我一个flink程序有多个window操作,时间戳和watermark是不是每个window都需要分配,还有就是事件时间是不是一定要在数据源中就存在某个字段](https://t.zsxq.com/amURvZR) 327、[有没有flink1.9刚支持的用ddl链接kafka并写入hbase的资料,我们公司想把离线的数仓逐渐转成实时的,写sql对于我们来说上手更快一些,就想找一些这方面的资料学习一下。](https://t.zsxq.com/eqFuBYz) 328、[flink1.9 进行了数据类型的转化时发生了不匹配的问题, 目前使用的Type被弃用,推荐使用是datatypes 类型,但是之前使用的Type类型的方法 对应的schema typeinformation 目前跟datatypes的返回值不对应,请问下 该怎么去调整适配?](https://t.zsxq.com/yVvR3V3) 329、[link中处理数据其中一条出了异常都会导致整个job挂掉?有没有方法(除了异常捕获)让这条数据记录错误日志就行 下面的数据接着处理呢? 粗略看过一些容错处理,是关于程度挂了重启后从检查点拉取数据,但是如果这条数据本身就问提(特别生产上,这样就导致job直接挂了,影响有点大),那应该怎么过滤掉这条问题数据呢(异常捕获是最后的方法](https://t.zsxq.com/6AIQnEi) 330、[我在一个做日报的统计中使用rabbitmq做数据源,为什么rabbitmq中的数据一直处于unacked状态,每分钟触发一次窗口计算,并驱逐计算过的元素,我在测试环境数据都能ack,但是一到生产环境就不行了,也没有报错,有可能是哪里出了问题啊](https://t.zsxq.com/RBmi2vB) 331、[我们目前数据流向是这样的,kafka source ,etl,redis sink 。这样chk 是否可以保证端到端语义呢?](https://t.zsxq.com/fuNfuBi) 332、[1.在通过 yarn-session 提交 flink job 的时候。flink-core, flink-clients, flink-scala, flink-streaming-scala, scala-library, flink-connector-kafka-0.10 那些应该写 provided scope,那些应该写 compile scope,才是正确、避免依赖冲突的姿势? 2.flink-dist_2.11-1.8.0.jar 究竟包含了哪些依赖?(这个文件打包方式不同于 springboot,无法清楚看到有哪些 jar 依赖)](https://t.zsxq.com/mIeMzvf) 333、[Flink 中使用 count window 会有这样的问题就是,最后有部分数据一直没有达到 count 的值,然后窗口就一直不触发,这里看到个思路,可以将 time window + count window 组合起来](https://t.zsxq.com/AQzj6Qv) 334、[flink流处理时,注册一个流数据为Table后,该流的历史数据也会一直在Table里面么?为什么每次来新数据,历史处理过得数据会重新被执行?](https://t.zsxq.com/VvR3Bai) 335、[available是变化数据,除了最新的数据被插入数据库,之前处理过数据又重新执行了几次](https://t.zsxq.com/jMfyNZv) 336、[这里两天在研究flink的广播变量,发现一个问题,DataSet数据集中获取广播变量,获取的内存地址是一样的(一台机器维护一个广播数据集)。在DataStream中获取广播变量就成了一个task维护一个数据集。(可能是我使用方式有问题) 所以想请教下星主,DataStream中获取一个画面变量可以如DataSet中一台机器维护一个数据吗?](https://t.zsxq.com/m6Yrv7Q) 337、[Flink程序开启checkpoint 机制后,用yarn命令多次killed以后,ckeckpoint目录下有多个job id,再次开辟资源重新启动程序,程序如何找到上一次jobid目录下,而不是找到其他的jobid目录下?默认是最后一个还是需要制定特定的jobid?](https://t.zsxq.com/nqzZrbq) 338、[发展昨天的数据重复插入问题,是把kafka里进来的数据流registerDataStream注册为Table做join时,打印表的长度发现,数据会一直往表里追加,怎样才能来一条处理一条,不往上追加呀](https://t.zsxq.com/RNzfQ7e) 339、[flink1.9 sql 有没有类似分区表那样的处理方式呢?我们现在有一个业务是1个source,但是要分别计算5分钟,10分钟,15分钟的数据。](https://t.zsxq.com/AqRvNNj) 340、[我刚弄了个服务器,在启动基础的命令时候发现task没有启动起来,导致web页是三个0,我看了log也没有报错信息,请问您知道可能是什么问题吗?](https://t.zsxq.com/q3feIuv) 241、[我自定义了个 Sink extends RichSinkFunction,有了 field: private transient Object lock; 这个 lock 我直接初始化 private transient Object lock = new Object(); 就不行,在 invoke 里 使用lock时空指针,如果lock在 自定义 Sink 的 构造器初始化也不行。但是在 open 方法里初始化就可以,为什么?能解释一下 执行原理吗?如果一个slot 运行着5个 sink实例,那么 这个sink对象会new 5个还是1个?](https://t.zsxq.com/EIiyjeU) 342、[请问Kafka的broker 个数怎么估算?](https://t.zsxq.com/aMNnIy3) 343、[flink on yarn如何远程调试](https://t.zsxq.com/BU7iqbi) 344、[目前有个需求:就是源数据是dataA、dataB、DataC通过kafka三个topic获取,然后进行合并。 但是有有几个问题,目前不知道怎么解决: dataA="id:10001,info:***,date:2019-08-01 12:23:33,entry1:1,entryInfo1:***" dataB="id:10001,org:***,entry:1" dataC="id:10001,location:***" (1) 如何将三个流合并? (1) 数据中dataA是有时间的,但是dataB和dataC中都没有时间戳,那么如何解决eventTime及迟到乱序的问题?帮忙看下,谢谢](https://t.zsxq.com/F6U7YbY) 345、[我flink从kafka读json数据,在反序列化后中文部分变成了一串问号,请问如何做才能使中文正常](https://t.zsxq.com/JmIqfaE) 346、[我有好几个Flink程序(独立jar),在线业务数据分析时都会用到同样的一批MySQL中的配置数据(5千多条),现在的实现方法是每一个程序都是独立把这些配置数据装到内存中,便于快速使用,但现在感觉有些浪费资源和结构不够美观,请问这类情况有什么其他的解决方案吗?谢谢](https://t.zsxq.com/3BMZfAM) 347、[Flink checkpoint 选 RocksDBStateBackend 还是 FsStatebackEnd ,我们目前是任务执行一段时间之后 任务就会被卡死。](https://t.zsxq.com/RFMjYZn) 348、[flink on k8s的高可用、扩缩容这块目前还有哪些问题?](https://t.zsxq.com/uVv7uJU) 349、[有个问题问一下,是这样的现在Kafka4个分区每秒钟生产4000多到5000条日志数据,但是在消费者FLINK这边接收我只开了4个solt接收,这边只是接收后做切分存储,现在出现了延迟现象,我不清楚是我这边处切分慢了还是Flink接收kafka的数据慢了?Flink UI界面显示这两个背压高](https://t.zsxq.com/zFq3fqb) 350、[想请问一下,在flink集群模式下,能不能指定某个节点来执行一个task?](https://t.zsxq.com/NbaMjem) + [请问一下aggrefunction 的merge方法什么时候会用到呢,google上有答案说合并相同的key, 但相同的key应该是被hash相同的task上了?这块不是很理解](https://t.zsxq.com/VnEim6m) + [请问flink遇到这种问题怎么解决?1. eventA发起事件,eventB响应事件,每分钟统计事件的响应的成功率。说明,eventA和eventB有相同的commitId关联,eventA到flink的时间早于eventB的时间,但eventB到达的时间也有可能早于eventA。要求是:eventA有A,B,C,D,E五条数据,如果eventB有A',B',C',X',Y'五条数据,成功率是3/5.2. 每分钟统计一次eventC成功率(状态0、1)。但该事件日志会重复报,只统计eventTime最早的一条。上一分钟统计到过的,下一分钟不再统计](https://t.zsxq.com/eMnMrRJ) + [Flink当前版本中Yarn,k8s,standalone的HA设计方案与源码解析请问可以系统性讲讲么](https://t.zsxq.com/EamqrFQ) + [怎么用javaAPI提交job以yarn-cluster模式运行](https://t.zsxq.com/vR76amq) + [有人遇到过流损坏的问题么?不知道怎么着手解决?](https://t.zsxq.com/6iMvjmq) + [从这个日志能看出什么异常的原因吗?我查看了kafka,yarn,zookeeper。这三个组件都没有任何异常](https://t.zsxq.com/uByFUrb) + [为啥flink内部维护两套通信框架,client与jobmanager和jobmanager与taskmanager是akka通信,然而takmanager之间是netty通信?](https://t.zsxq.com/yvBiImq) + [问各位球友一个小问题,flink 的 wordcount ,输出在控制台的时候,前面有个数字 > 是什么意思](https://t.zsxq.com/yzzBMji) + [从kafka的topicA读数据,转换后写入topicB,开启了checkpoint,任务启动后正常运行,新的topic也有数据写入,但是想监控一下消费topicA有没有延迟,使用kafka客户端提供的脚本查看groupid相关信息,提示没有该groupid](https://t.zsxq.com/MNFUVnE) + [将flink分流之后,再进行窗口计算,如何将多个窗口计算的结果汇总起来 作为一个sink,定时输出? 我想将多个流计算的不同实时统计指标,比如每1min对多个指标进行统计(多个指标分布在不同的流里面),然后将多个指标作为一条元组存入mysql中?](https://t.zsxq.com/mUfm2zF) + [Flink最终如何输出到数据大屏上去。](https://t.zsxq.com/nimeA66) + [为什么我keyby 之后,不同key的数据会进入同一个AggregateFunction中吗? 还是说不同key用的AggregateFunction实列是同一个呢?我在AggregateFunction中给一个对象赋值之后,发现其他key的数据会把之前的数据覆盖,这是怎么回事啊?](https://t.zsxq.com/IMzBUFA) + [flink窗口计算的结果怎么和之前的结果聚合在一起](https://t.zsxq.com/yFI2FYv) + [flink on yarn 的任务该如何监控呢,之前自带 influxdb metrics 好像无法采集到flink on yarn 的指标](https://t.zsxq.com/ZZ3FmqF) + [link1.9.0消费kafka0.10.1.1数据时,通过ui监控查看发现部分分区的current offset和commit offset一直显示为负数,随着程序运行也始终不变,麻烦问下这是怎么回事?](https://t.zsxq.com/QvRNjiU) + [flink 1.9 使用rank的时候报,org.apache.flink.table.api.TableException: RANK() on streaming table is not supported currently](https://t.zsxq.com/Y7MBaQb) + [Flink任务能不能动态的变更source源kafka的topic,但是又不用重启任务](https://t.zsxq.com/rzVjMjM) + [1、keyed state 和opeater state 区分点是啥(是否进行了shuffle流程?) 2、CheckpointedFunction 这个接口的作用是啥? 3、何时调用这个snapshotState这个方法?](https://t.zsxq.com/ZVnEyne) + [请教一下各位大佬,日志一般都怎么收集?task manager貌似把不同job的日志都打印在一起,有木有分开打印的办法?](https://t.zsxq.com/AayjeiM) + [最近接到一个需求,统计今天累计在线人数并且要去重,每5秒显示一次结果,请问如何做这个需求?](https://t.zsxq.com/IuJ2FYR) + [目前是flink消费kafka的一个问题。kafka使用的是阿里云的kafka,可以申请consumer。目前在同一个A-test的topic下,使用A1的consumer组进行消费,但是在两个程序里,source端得到的数据量差别很大,图一是目前消费kafka写入到另一个kafka的topic中,目前已知只有100条;图二是消费kafka,写入到hdfs中。两次消费起始偏移量一致(消费后,恢复偏移量到最初再消费)按照时间以及设置从头开始消费的策略也都还是只有100条;后面我把kafka的offset提交到checkpoint选项关掉了,也还是只有100条。很奇怪,所以想问一下,目前这个问题是要从state来出发解决](https://t.zsxq.com/eqBUZFm) + [问一下 grafana的dashboard 有没有推荐的,我们现在用是prometheus pushgateway reporter来收集metric。但是目前来说,到底哪些指标是要重点关注的还是不太清楚](https://t.zsxq.com/EYz7iMV) + [on yarn 1. session 模式提交是不是意味着 多个flink任务会由同一个 jobManager 管理 2. per-job 模式 会启动各自多个jobManager](https://t.zsxq.com/u3vVV3b) + [您在flink里面使用过lettuce连接redis cluster吗,我这里使用时报错,Cannot retrieve initial cluster partitions from initial URIs](https://t.zsxq.com/VNnEQJ6) + [zhisheng你好,我在使用flink滑动窗口时,每10分钟会向redis写入大量的内容,影响了线上性能,这个有什么办法可以控制写redis的速度吗?](https://t.zsxq.com/62ZZJmi) + [flink standalone模式,启动服务的命令为:flink run -c 类名 jar包 。对应的Slots怎么能均匀分布呢?目前遇到问题,一直使用一个机器的Slots,任务多了后直接会把taskjob挂掉。报错信息如二图](https://t.zsxq.com/2zjqVnE) + [zhisheng你好,像standalone与yarn集群,其master与workers相互通信都依赖于ssh协议,请问有哪种不依赖于ssh协议的搭建方式吗?](https://t.zsxq.com/qzrvbaQ) + [官网中,这两种周期性watermaker的产生分别适用什么场景呢?](https://t.zsxq.com/2fUjAQz) + [周期性的watermarke 设置定时产生, ExecutionConfig.setAutoWatermarkInterval(…),这个定时的时间一般怎样去评估呢?](https://t.zsxq.com/7IEAyV3) + [想问一下能否得到flink分配资源的时间?](https://t.zsxq.com/YjqRBq3) + [问下flink向kafka生产数据有时候报错:This server does not host this topic-partition](https://t.zsxq.com/vJyJiMJ) + [flink yarn 模式启动,log4j. properties配置信息见图片,yarn启动页面的taskmanager能看到日志输出到stdout,但是在指定的日志文件夹中就是没有日志文件生成。,本地运行有日志文件的](https://t.zsxq.com/N3ZrZbQ) + [教一个问题。flink2hbase 如何根据hbase中的日期字段,动态按天建表呢?我自定义了hbase sink,在invoke方法中根据数据的时间建表,但是带来了一个问题,每条数据都要去check表是否存在,这样会产生大量的rpc请求。请问星主大大,针对上述这种情况,有什么好的解决办法吗?](https://t.zsxq.com/3rNBubU) + [你好,有关于TM,slots,内存,线程数,进程数,cpu,调度相关的资料吗?比如一个slot起多少线程,为什么,如何起的,task是如何调度的之类的。网上没找到想要的,书上写的也不够细。源码的话刚开始看不太懂,所以想先找找资料看看](https://t.zsxq.com/buBIAMf) + [能否在flink中只新建一个FlinkKafkaConsumer读取多个kafka的topics ,这些topics的处理逻辑都是一样的 最终将数据写入每个topic对应的es表 请问这个实现逻辑是怎样的 ](https://t.zsxq.com/EY37aEm) + [能不能描述一下在窗口中,例如滚动窗口,多个事件进窗口后,事件在内存中保存的形式是怎么样的?会变成一个state?还是多个事件变成一个state?事件跟state的关系?事件时间过了在窗口是怎么清理事件的?如果state backends用的是RocksDBStateBackend,增量checkpoint,怎么清理已保存过期的事件咧?](https://t.zsxq.com/3vzzj62) + [请问一下 Flink的监控是如何做的?比如job挂了能告警通知。目前是想用Prometheus来做监控,但是发现上报的指标没有很符合的我需求。我这边用yarn-session启动的job,一个jobManger会管理多个job。Prometheus还是刚了解阶段可能遗漏了一些上报指标,球主大大有没有好的建议。](https://t.zsxq.com/vJyRnY7) + [ProcessTime和EventTime是否可以一起使用?当任务抛出异常失败的时候,如果配置了重启策略,重启时是不是从最近的checkpoint继续?遇到了一个数据库主键冲突的问题,查看kafka数据源发现该主键的消息只有一条,查看日志发现Redis连接池抛了异常(当时Redis在重启)导致任务失败重试,当时用的ProcessTime](https://t.zsxq.com/BuZJaUb) + [flink-kafka 自定义反序列化中如何更好的处理数据异常呢,有翻到前面一篇提问,如果使用 try-catch 捕获到异常,是抛出异常更好呢?还是return null 更好呢](https://t.zsxq.com/u3niYni) + [现在在用flink做上下游数据的比对,现在遇到了性能瓶颈,一个节点现在最多只能消费50条数据。观察taskmanager日志的gc日志发现最大堆内存有2.7g,但是新生代最大只有300m。能不能设置flink的jvm参数,flink on yarn启动模式](https://t.zsxq.com/rvJYBuB) + [请教一个原理性的问题,side out put和直接把一个流用两种方式处理有啥本质区别?我试了下,把一个流一边写缓存,一边入数据库,两边也都是全量数据](https://t.zsxq.com/Ee27i6a) + [如何定义一个flink window处理方式,1秒钟处理500条,1:kafka中有10000条数据时,仍旧1秒钟处理500条;2,kafka中有20条,每隔1秒处理一次。](https://t.zsxq.com/u7YbyFe) + [问一下大佬,网页UI可以进行savepoint的保存么?还是只能从savepoint启动?](https://t.zsxq.com/YfAqFUj) + [能否指定Kafka某些分区消费拉取消息,其他分区不拉取消息。现在有有很多场景,一个topic上百个分区,但是我只需要其中几个分区的数据](https://t.zsxq.com/AUfEAQB) + [我想过滤kafka 读到的某些数据,过滤条件从redis中拿到(与用户的配置相关,所以需要定时更新),总觉得怪怪的,请问有更好的方案吗?因为不提供redis的source,因此我是用jedis客户端来读取redis数据的,数据也获取不到,请问星主,flink代码在编写的时候,一般是如何调试的呢](https://t.zsxq.com/qr7UzjM) + [flink使用rocksdb状态检查点存在HDFS上,有的任务状态很小但是HDFS一个文件最小128M所以磁盘空间很快就满了,有没有啥配置可以自动清理检查点呢](https://t.zsxq.com/Ufqj2ZR) + [这是实时去重的问题。 举个例子,当发生订单交易的时候,业务中台会把该比订单消息发送到kafka,然后flink消费,统计总金额。如果因为业务中台误操作,发送了多次相同的订单过来(订单id相同),那么统计结果就会多次累加,造成统计的总金额比实际交易金额更多。我需要自定义在source里通过operate state去重,但是operate state是和每个source实例绑定,会造成重复的订单可能发送到不同的source实例,这样取出来的state里面就可能没有上一次已经记录的订单id,那么就会将这条重复的订单金额统计到最后结果中,](https://t.zsxq.com/RzB6E6A) + [双流join的时候,怎么能保证两边来的数据是对应的?举个例子,订单消息和库存消息,按逻辑来说,发生订单的时候,库存也会变,这两个topic都会同时各自发一条消息给我,我拿到这两条消息会根据订单id做join操作。问题是那如果库存消息延迟了5秒或者10秒,订单消息来的时候就join不到库存消息,这时候该怎么办?](https://t.zsxq.com/nunynmI) + [我这有一个比对程序用的是flink,数据源用的是flink-kafka,业务数据分为上下游,需要根据某个字段分组,相同的key上下游数据放一起比对。上下游数据进来的时间不一样,因此我用了一个可以迭代的窗口大小为5分钟window进行比对处理,最大迭代次数为3次。statebackend用的是fsstatebackend。通过监控发现当程序每分钟数据量超过2万条的时候,程序就不消费数据了,虽然webui上显示正常,而且jobmanager和taskmanager的stdout没有异常日志,但是程序就是不消费数据了。](https://t.zsxq.com/nmeE2Fm) + [异步io里面有个容量,是指同时多少个并发还是,假如我每个taskmanager核数设置10个,共10个taskmanager,那我这个数量只能设置100呢](https://t.zsxq.com/vjimeiI) + [有个性能问题想问下有没有相关的经验?一个job从kafka里读一个topic数据,然后进行分流,使用sideout分开之后直接处理,性能影响大吗?比如分开以后有一百多子任务。还有其他什么好的方案进行分流吗?](https://t.zsxq.com/mEeUrZB) + [线上有个作业抛出了一下异常,但是还能正常运行,这个怎么排查,能否提供一下思路](https://t.zsxq.com/Eayzr3R) 等等等,还有很多,复制粘贴的我手累啊 😂 另外里面还会及时分享 Flink 的一些最新的资料(包括数据、视频、PPT、优秀博客,持续更新,保证全网最全,因为我知道 Flink 目前的资料还不多) [关于自己对 Flink 学习的一些想法和建议](https://t.zsxq.com/AybAimM) [Flink 全网最全资料获取,持续更新,点击可以获取](https://t.zsxq.com/iaEiyB2) 再就是星球用户给我提的一点要求:不定期分享一些自己遇到的 Flink 项目的实战,生产项目遇到的问题,是如何解决的等经验之谈! 1、[如何查看自己的 Job 执行计划并获取执行计划图](https://t.zsxq.com/Zz3ny3V) 2、[当实时告警遇到 Kafka 千万数据量堆积该咋办?](https://t.zsxq.com/AIAQrnq) 3、[如何在流数据中比两个数据的大小?多种解决方法](https://t.zsxq.com/QnYjy7M) 4、[kafka 系列文章](https://t.zsxq.com/6Q3vN3b) 5、[Flink环境部署、应用配置及运行应用程序](https://t.zsxq.com/iiYfMBe) 6、[监控平台该有架构是长这样子的](https://t.zsxq.com/yfYrvFA) 7、[《大数据“重磅炸弹”——实时计算框架 Flink》专栏系列文章目录大纲](https://t.zsxq.com/beu7Mvj) 8、[《大数据“重磅炸弹”——实时计算框架 Flink》Chat 付费文章](https://t.zsxq.com/UvrRNJM) 9、[Apache Flink 是如何管理好内存的?](https://t.zsxq.com/zjQvjeM) 10、[Flink On K8s](https://t.zsxq.com/eYNBaAa) 11、[Flink-metrics-core](https://t.zsxq.com/Mnm2nI6) 12、[Flink-metrics-datadog](https://t.zsxq.com/Mnm2nI6) 13、[Flink-metrics-dropwizard](https://t.zsxq.com/Mnm2nI6) 14、[Flink-metrics-graphite](https://t.zsxq.com/Mnm2nI6) 15、[Flink-metrics-influxdb](https://t.zsxq.com/Mnm2nI6) 16、[Flink-metrics-jmx](https://t.zsxq.com/Mnm2nI6) 17、[Flink-metrics-slf4j](https://t.zsxq.com/Mnm2nI6) 18、[Flink-metrics-statsd](https://t.zsxq.com/Mnm2nI6) 19、[Flink-metrics-prometheus](https://t.zsxq.com/Mnm2nI6) 20、[Flink 注解源码解析](https://t.zsxq.com/f6eAu3J) 21、[使用 InfluxDB 和 Grafana 搭建监控 Flink 的平台](https://t.zsxq.com/yVnaYR7) 22、[一文搞懂Flink内部的Exactly Once和At Least Once](https://t.zsxq.com/UVfqfae) 23、[一文让你彻底了解大数据实时计算框架 Flink](https://t.zsxq.com/eM3ZRf2) 当然,除了更新 Flink 相关的东西外,我还会更新一些大数据相关的东西,因为我个人之前不是大数据开发,所以现在也要狂补些知识!总之,希望进来的童鞋们一起共同进步! 1、[Java 核心知识点整理.pdf](https://t.zsxq.com/7I6Iyrf) 2、[假如我是面试官,我会问你这些问题](https://t.zsxq.com/myJYZRF) 3、[Kafka 系列文章和学习视频](https://t.zsxq.com/iUZnamE) 4、[重新定义 Flink 第二期 pdf](https://t.zsxq.com/r7eIeyJ) 5、[GitChat Flink 文章答疑记录](https://t.zsxq.com/ZjiYrVr) 6、[Java 并发课程要掌握的知识点](https://t.zsxq.com/QZVJyz7) 7、[Lightweight Asynchronous Snapshots for Distributed Dataflows](https://t.zsxq.com/VVN7YB2) 8、[Apache Flink™- Stream and Batch Processing in a Single Engine](https://t.zsxq.com/VVN7YB2) 9、[Flink状态管理与容错机制](https://t.zsxq.com/NjAQFi2) 10、[Flink 流批一体的技术架构以及在阿里的实践](https://t.zsxq.com/MvfUvzN) 11、[Flink Checkpoint-轻量级分布式快照](https://t.zsxq.com/QVFqjea) 12、[Flink 流批一体的技术架构以及在阿里的实践](https://t.zsxq.com/MvfUvzN) 13、[Stream Processing with Apache Flink pdf](https://t.zsxq.com/N37mUzB) 14、[Flink 结合机器学习算法的监控平台实践](https://t.zsxq.com/m6EAaQ3) 15、[《大数据重磅炸弹-实时计算Flink》预备篇——大数据实时计算介绍及其常用使用场景 pdf 和视频](https://t.zsxq.com/emMBaQN) 16、[《大数据重磅炸弹-实时计算Flink》开篇词 pdf 和视频](https://t.zsxq.com/fqfuVRR) 17、[四本 Flink 书](https://t.zsxq.com/rVBQFI6) 18、[流处理系统 的相关 paper](https://t.zsxq.com/rVBQFI6) 19、[Apache Flink 1.9 特性解读](https://t.zsxq.com/FyzvRne) 20、[打造基于Flink Table API的机器学习生态](https://t.zsxq.com/FyzvRne) 21、[基于Flink on Kubernetes的大数据平台](https://t.zsxq.com/FyzvRne) 22、[基于Apache Flink的高性能机器学习算法库](https://t.zsxq.com/FyzvRne) 23、[Apache Flink在快手的应用与实践](https://t.zsxq.com/FyzvRne) 24、[Apache Flink-1.9与Hive的兼容性](https://t.zsxq.com/FyzvRne) 25、[打造基于Flink Table API的机器学习生态](https://t.zsxq.com/FyzvRne) 26、[流处理系统的相关 paper](https://t.zsxq.com/rVBQFI6) ================================================ FILE: books/README.md ================================================ ### 《大数据实时计算引擎 Flink 实战与性能优化》 2019 年著,基于 Flink 1.9 讲解的书籍目录大纲,含 Flink 入门、概念、原理、实战、性能调优、源码解析等内容。涉及 Flink Connector、Metrics、Library、DataStream API、Table API & SQL 等内容的学习案例,还有 Flink 落地应用的大型项目案例分享。 ## 专栏介绍 首发地址:[http://www.54tianzhisheng.cn/2019/11/15/flink-in-action/](http://www.54tianzhisheng.cn/2019/11/15/flink-in-action/) 专栏地址:[https://gitbook.cn/gitchat/column/5dad4a20669f843a1a37cb4f](https://gitbook.cn/gitchat/column/5dad4a20669f843a1a37cb4f) 加入知识星球可以获取到专栏所有内容: ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-07-23-124320.jpg) ### 专栏亮点 + 全网首个使用最新版本 **Flink 1.9** 进行内容讲解(该版本更新很大,架构功能都有更新),领跑于目前市面上常见的 Flink 1.7 版本的教学课程。 + 包含大量的**实战案例和代码**去讲解原理,有助于读者一边学习一边敲代码,达到更快,更深刻的学习境界。目前市面上的书籍没有任何实战的内容,还只是讲解纯概念和翻译官网。 + 在专栏高级篇中,根据 Flink 常见的项目问题提供了**排查和解决的思维方法**,并通过这些问题探究了为什么会出现这类问题。 + 在实战和案例篇,围绕大厂公司的**经典需求**进行分析,包括架构设计、每个环节的操作、代码实现都有一一讲解。 ### 为什么要学习 Flink? 随着大数据的不断发展,对数据的及时性要求越来越高,实时场景需求也变得越来越多,主要分下面几大类: ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-11-05-055800.jpg) 为了满足这些实时场景的需求,衍生出不少计算引擎框架。现有市面上的大数据计算引擎的对比如下图所示: ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-11-05-055826.jpg) 可以发现无论从 Flink 的架构设计上,还是从其功能完整性和易用性来讲都是领先的,再加上 Flink 是**阿里巴巴主推**的计算引擎框架,所以从去年开始就越来越火了! 目前,阿里巴巴、腾讯、美团、华为、滴滴出行、携程、饿了么、爱奇艺、有赞、唯品会等大厂都已经将 Flink 实践于公司大型项目中,带起了一波 Flink 风潮,**势必也会让 Flink 人才市场产生供不应求的招聘现象**。 ### 专栏内容 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-11-05-060049.jpg) #### 预备篇 介绍实时计算常见的使用场景,讲解 Flink 的特性,并且对比了 Spark Streaming、Structured Streaming 和 Storm 等大数据处理引擎,然后准备环境并通过两个 Flink 应用程序带大家上手 Flink。 ### 基础篇 深入讲解 Flink 中 Time、Window、Watermark、Connector 原理,并有大量文章篇幅(含详细代码)讲解如何去使用这些 Connector(比如 Kafka、ElasticSearch、HBase、Redis、MySQL 等),并且会讲解使用过程中可能会遇到的坑,还教大家如何去自定义 Connector。 ### 进阶篇 讲解 Flink 中 State、Checkpoint、Savepoint、内存管理机制、CEP、Table/SQL API、Machine Learning 、Gelly。在这篇中不仅只讲概念,还会讲解如何去使用 State、如何配置 Checkpoint、Checkpoint 的流程和如何利用 CEP 处理复杂事件。 ### 高级篇 重点介绍 Flink 作业上线后的监控运维:如何保证高可用、如何定位和排查反压问题、如何合理的设置作业的并行度、如何保证 Exactly Once、如何处理数据倾斜问题、如何调优整个作业的执行效率、如何监控 Flink 及其作业? ### 实战篇 教大家如何分析实时计算场景的需求,并使用 Flink 里面的技术去实现这些需求,比如实时统计 PV/UV、实时统计商品销售额 TopK、应用 Error 日志实时告警、机器宕机告警。这些需求如何使用 Flink 实现的都会提供完整的代码供大家参考,通过这些需求你可以学到 ProcessFunction、Async I/O、广播变量等知识的使用方式。 ### 系统案例篇 讲解大型流量下的真实案例:如何去实时处理海量日志(错误日志实时告警/日志实时 ETL/日志实时展示/日志实时搜索)、基于 Flink 的百亿数据实时去重实践(从去重的通用解决方案 --> 使用 BloomFilter 来实现去重 --> 使用 Flink 的 KeyedState 实现去重)。 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-11-05-060153.jpg) ### 多图讲解 Flink 知识点 ![ Flink 支持多种时间语义](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-11-05-060219.jpg) ![Flink 提供灵活的窗口](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-11-05-060232.jpg) ![Flink On YARN](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-11-05-060310.jpg) ![Flink Checkpoint](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-11-05-060900.jpg) ![Flink 监控](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-11-05-060926.jpg) ### 你将获得什么 + 掌握 Flink 与其他计算框架的区别 + 掌握 Flink Time/Window/Watermark/Connectors 概念和实现原理 + 掌握 Flink State/Checkpoint/Savepoint 状态与容错 + 熟练使用 DataStream/DataSet/Table/SQL API 开发 Flink 作业 + 掌握 Flink 作业部署/运维/监控/性能调优 + 学会如何分析并完成实时计算需求 + 获得大型高并发流量系统案例实战项目经验 ### 适宜人群 + Flink 爱好者 + 实时计算开发工程师 + 大数据开发工程师 + 计算机专业研究生 + 有实时计算场景场景的 Java 开发工程师 ## 目录大纲 ``` 1预备篇 第一章——实时计算引擎 1.1你的公司是否需要引入实时计算引擎 1.1.1 实时计算需求 1.1.2 数据实时采集 1.1.3 数据实时计算 1.1.4 数据实时下发 1.1.5 实时计算场景 1.1.6 离线计算 vs 实时计算 1.1.7 实时计算面临的挑战 1.1.8 小结与反思 1.2彻底了解大数据实时计算框架 Flink 1.2.1 Flink 简介 1.2.2 Flink 整体架构 1.2.3 Flink 的多种方式部署 1.2.4 Flink 分布式运行流程 1.2.5 Flink API 1.2.6 Flink 程序与数据流结构 1.2.7 丰富的 Connector 1.2.8 事件时间&处理时间语义 1.2.9 灵活的窗口机制 1.2.10 并行执行任务机制 1.2.11 状态存储和容错 1.2.12 自己的内存管理机制 1.2.13 多种扩展库 1.2.14 小结与反思 1.3大数据计算框架对比 1.3.1 Flink 1.3.2 Blink 1.3.3 Spark 1.3.4 Spark Streaming 1.3.5 Structured Streaming 1.3.6 Flink VS Spark 1.3.7 Storm 1.3.8 Flink VS Storm 1.3.9 全部对比结果 1.3.10 小结与反思 1.4总结 2第二章——Flink 入门 2.1Flink 环境准备 2.1.1 JDK 安装与配置 2.1.2 Maven 安装与配置 2.1.3 IDE 安装与配置 2.1.4 MySQL 安装与配置 2.1.5 Kafka 安装与配置 2.1.6 ElasticSearch 安装与配置 2.1.7 小结与反思 2.2Flink 环境搭建 2.2.1 Flink 下载与安装 2.2.2 Flink 启动与运行 2.2.3 Flink 目录配置文件解读 2.2.4 Flink 源码下载 2.2.5 Flink 源码编译 2.2.6 将 Flink 源码导入到 IDE 2.2.7 小结与反思 2.3案例1:WordCount 应用程序 2.3.1 使用 Maven 创建项目 2.3.2 使用 IDEA 创建项目 2.3.3 流计算 WordCount 应用程序代码实现 2.3.4 运行流计算 WordCount 应用程序 2.3.5 流计算 WordCount 应用程序代码分析 2.3.6 小结与反思 2.4案例2:实时处理 Socket 数据 2.4.1 使用 IDEA 创建项目 2.4.2 实时处理 Socket 数据应用程序代码实现 2.4.3 运行实时处理 Socket 数据应用程序 2.4.4 实时处理 Socket 数据应用程序代码分析 2.4.5 Flink 中使用 Lambda 表达式 2.4.5 小结与反思 2.5总结 2基础篇 3第三章——Flink 中的流计算处理 3.1Flink 多种时间语义对比 3.1.1 Processing Time 3.1.2 Event Time 3.1.3 Ingestion Time 3.1.4 三种 Time 的对比结果 3.1.5 使用场景分析 3.1.6 Time 策略设置 3.1.7 小结与反思 3.2Flink Window 基础概念与实现原理 3.2.1 Window 简介 3.2.2 Window 有什么作用? 3.2.3 Flink 自带的 Window 3.2.4 Time Window 的用法及源码分析 3.2.5 Count Window 的用法及源码分析 3.2.6 Session Window 的用法及源码分析 3.2.7 如何自定义 Window? 3.2.8 Window 源码分析 3.2.9 Window 组件之 WindowAssigner 的用法及源码分析 3.2.10 Window 组件之 Trigger 的用法及源码分析 3.2.11 Window 组件之 Evictor 的用法及源码分析 3.2.12 小结与反思 3.3必须熟悉的数据转换 Operator(算子) 3.3.1 DataStream Operator 3.3.2 DataSet Operator 3.3.3 流计算与批计算统一的思路 3.3.4 小结与反思 3.4使用 DataStream API 来处理数据 3.4.1 DataStream 的用法及分析 3.4.2 SingleOutputStreamOperator 的用法及分析 3.4.3 KeyedStream 的用法及分析 3.4.4 SplitStream 的用法及分析 3.4.5 WindowedStream 的用法及分析 3.4.6 AllWindowedStream 的用法及分析 3.4.7 ConnectedStreams 的用法及分析 3.4.8 BroadcastStream 的用法及分析 3.4.9 BroadcastConnectedStream 的用法及分析 3.4.10 QueryableStateStream 的用法及分析 3.4.11 小结与反思 3.5Watermark 的用法和结合 Window 处理延迟数据 3.5.1 Watermark 简介 3.5.2 Flink 中的 Watermark 的设置 3.5.3 Punctuated Watermark 3.5.4 Periodic Watermark 3.5.5 每个 Kafka 分区的时间戳 3.5.6 将 Watermark 与 Window 结合起来处理延迟数据 3.5.7 处理延迟数据的三种方法 3.5.8 小结与反思 3.6Flink 常用的 Source Connector 和 Sink Connector 介绍 3.6.1 Data Source 简介 3.6.2 常用的 Data Source 3.6.3 Data Sink 简介 3.6.4 常用的 Data Sink 3.6.5 小结与反思 3.7Flink Connector —— Kafka 的使用和源码分析 3.7.1 准备环境和依赖 3.7.2 将测试数据发送到 Kafka Topic 3.7.3 Flink 如何消费 Kafka 数据? 3.7.4 Flink 如何将计算后的数据发送到 Kafka? 3.7.5 FlinkKafkaConsumer 源码分析 3.7.6 FlinkKafkaProducer 源码分析 3.7.7 使用 Flink-connector-kafka 可能会遇到的问题 3.7.8 小结与反思 3.8自定义 Flink Connector 3.8.1 自定义 Source Connector 3.8.2 RichSourceFunction 的用法及源码分析 3.8.3 自定义 Sink Connector 3.8.4 RichSinkFunction 的用法及源码分析 3.8.5 小结与反思 3.9Flink Connector —— ElasticSearch 的用法和分析 3.9.1 准备环境和依赖 3.9.2 使用 Flink 将数据写入到 ElasticSearch 应用程序 3.9.3 验证数据是否写入 ElasticSearch? 3.9.4 如何保证在海量数据实时写入下 ElasticSearch 的稳定性? 3.9.5 使用 Flink-connector-elasticsearch 可能会遇到的问题 3.9.6 小结与反思 3.10Flink Connector —— HBase 的用法 3.10.1 准备环境和依赖 3.10.2 Flink 使用 TableInputFormat 读取 HBase 批量数据 3.10.3 Flink 使用 TableOutputFormat 向 HBase 写入数据 3.10.4 Flink 使用 HBaseOutputFormat 向 HBase 实时写入数据 3.10.5 项目运行及验证 3.10.6 小结与反思 3.11Flink Connector —— Redis 的用法 3.11.1 安装 Redis 3.11.2 将商品数据发送到 Kafka 3.11.3 Flink 消费 Kafka 中的商品数据 3.11.4 Redis Connector 简介 3.11.5 Flink 写入数据到 Redis 3.11.6 项目运行及验证 3.11.7 小结与反思 3.12使用 Side Output 分流 3.12.1 使用 Filter 分流 3.12.2 使用 Split 分流 3.12.3 使用 Side Output 分流 3.12.4 小结与反思 3.13总结 3进阶篇 4第四章——Flink 中的状态及容错机制 4.1深度讲解 Flink 中的状态 4.1.1 为什么需要 State? 4.1.2 State 的种类 4.1.3 Keyed State 4.1.4 Operator State 4.1.5 Raw and Managed State 4.1.6 如何使用托管的 Keyed State 4.1.7 State TTL(存活时间) 4.1.8 如何使用托管的 Operator State 4.1.9 Stateful Source Functions 4.1.10 Broadcast State 4.1.11 Queryable State 4.1.12 小结与反思 4.2Flink 状态后端存储 4.2.1 State Backends 4.2.2 MemoryStateBackend 的用法及分析 4.2.3 FsStateBackend 的用法及分析 4.2.4 RocksDBStateBackend 的用法及分析 4.2.5 如何选择状态后端存储? 4.2.6 小结与反思 4.3Flink Checkpoint 和 Savepoint 的区别及其配置使用 4.3.1 Checkpoint 简介及使用 4.3.2 Savepoint 简介及使用 4.3.3 Savepoint 与 Checkpoint 的区别 4.3.4 Checkpoint 流程 4.3.5 如何从 Checkpoint 中恢复状态 4.3.6 如何从 Savepoint 中恢复状态 4.3.6 小结与反思 4.4总结 5第五章——Table API & SQL 5.1Flink Table & SQL 概念与通用 API 5.1.1 新增 Blink SQL 查询处理器 5.1.2 为什么选择 Table API & SQL? 5.1.3 Flink Table 项目模块 5.1.4 两种 planner 之间的区别 5.1.5 添加项目依赖 5.1.6 创建一个 TableEnvironment 5.1.7 Table API & SQL 应用程序的结构 5.1.8 Catalog 中注册 Table 5.1.9 注册外部的 Catalog 5.1.10 查询 Table 5.1.11 提交 Table 5.1.12 翻译并执行查询 5.1.13 小结与反思 5.2Flink Table API & SQL 功能 5.2.1 Flink Table 和 SQL 与 DataStream 和 DataSet 集成 5.2.2 查询优化 5.2.3 数据类型 5.2.4 时间属性 5.2.5 SQL Connector 5.2.6 SQL Client 5.2.7 Hive 5.2.8 小结与反思 5.3总结 6第六章——扩展库 6.1Flink CEP 简介及其使用场景 6.1.1 CEP 简介 6.1.2 规则引擎对比 6.1.3 Flink CEP 简介 6.1.4 Flink CEP 动态更新规则 6.1.5 Flink CEP 使用场景分析 6.1.6 小结与反思 6.2使用 Flink CEP 处理复杂事件 6.2.1 准备依赖 6.2.2 Flink CEP 入门应用程序 6.2.3 Pattern API 6.2.4 检测 Pattern 6.2.5 CEP 时间属性 6.2.6 小结与反思 6.3Flink 扩展库——State Processor API 6.3.1 State Processor API 简介 6.3.2 在 Flink 1.9 之前是如何处理状态的? 6.3.3 使用 State Processor API 读写作业状态 6.3.4 使用 DataSet 读取作业状态 6.3.5 为什么要使用 DataSet API? 6.3.6 小结与反思 6.4Flink 扩展库——Machine Learning 6.4.1 Flink-ML 简介 6.4.2 使用 Flink-ML 6.4.3 使用 Flink-ML Pipeline 6.4.4 小结与反思 6.5Flink 扩展库——Gelly 6.5.1 Gelly 简介 6.5.2 使用 Gelly 6.5.3 Gelly API 6.5.4 小结与反思 6.6 总结 4高级篇 7第七章——Flink 作业环境部署 7.1Flink 配置详解及如何配置高可用? 7.1.1 Flink 配置详解 7.1.2 Log 的配置 7.1.3 如何配置 JobManager 高可用? 7.1.4 小结与反思 7.2Flink 作业如何在 Standalone、YARN、Mesos、K8S 上部署运行? 7.2.1 Standalone 7.2.2 YARN 7.2.3 Mesos 7.3.4 Kubernetes 7.2.5 小结与反思 7.3总结 8第八章——Flink 监控 8.1实时监控 Flink 及其作业 8.1.1 监控 JobManager 8.1.2 监控 TaskManager 8.1.3 监控 Flink 作业 8.1.4 最关心的性能指标 8.1.5 小结与反思 8.2搭建一套 Flink 监控系统 8.2.1 利用 API 获取监控数据 8.2.2 Metrics 类型简介 8.2.3 利用 JMXReporter 获取监控数据 8.2.4 利用 PrometheusReporter 获取监控数据 8.2.5 利用 PrometheusPushGatewayReporter 获取监控数据 8.2.6 利用 InfluxDBReporter 获取监控数据 8.2.7 安装 InfluxDB 和 Grafana 8.2.8 配置 Grafana 展示监控数据 8.2.9 小结与反思 8.3总结 9第九章——Flink 性能调优 9.1如何处理 Flink Job Backpressure (反压)问题? 9.1.1 Flink 流处理为什么需要网络流控 9.1.2 Flink 1.5 之前的网络流控机制 9.1.3 基于 Credit 的反压机制 9.1.4 定位产生反压的位置 9.1.5 分析和处理反压问题 9.1.6 小结与反思 9.2如何查看 Flink 作业执行计划? 9.2.1 如何获取执行计划 JSON? 9.2.2 生成执行计划图 9.2.3 深入探究 Flink 作业执行计划 9.2.4 Flink 中算子 chain 起来的条件 9.2.5 如何禁止 Operator chain? 9.2.6 小结与反思 9.3Flink Parallelism 和 Slot 深度理解 9.3.1 Parallelism 简介 9.3.2 如何设置 Parallelism? 9.3.3 Slot 简介 9.3.4 Slot 和 Parallelism 的关系 9.3.5 可能会遇到 Slot 和 Parallelism 的问题 9.3.6 小结与反思 9.4如何合理的设置 Flink 作业并行度? 9.4.1 Source 端并行度的配置 9.4.2 中间 Operator 并行度的配置 9.4.3 Sink 端并行度的配置 9.4.4 Operator Chain 9.4.5 小结与反思 9.5Flink 中如何保证 Exactly Once? 9.5.1 Flink 内部如何保证 Exactly Once? 9.5.2 端对端如何保证 Exactly Once? 9.5.3 分析 FlinkKafkaConsumer 的设计思想 9.5.4 小结与反思 9.6如何处理 Flink 中数据倾斜问题? 9.6.1 数据倾斜简介 9.6.2 判断是否存在数据倾斜 9.6.3 分析和解决数据倾斜问题 9.6.4 小结与反思 9.7总结 10第十章——Flink 最佳实践 10.1如何设置 Flink Job RestartStrategy(重启策略)? 10.1.1 常见错误导致 Flink 作业重启 10.1.2 RestartStrategy 简介 10.1.3 为什么需要 RestartStrategy? 10.1.4 如何配置 RestartStrategy? 10.1.5 RestartStrategy 源码分析 10.1.6 Failover Strategies(故障恢复策略) 10.1.7 小结与反思 10.2如何使用 Flink ParameterTool 读取配置? 10.2.1 Flink Job 配置 10.2.2 ParameterTool 管理配置 10.2.3 ParameterTool 源码分析 10.2.4 小结与反思 10.3总结 5实战篇 11第十一章——Flink 实战 11.1如何统计网站各页面一天内的 PV 和 UV? 11.1.1 统计网站各页面一天内的 PV 11.1.2 统计网站各页面一天内 UV 的三种方案 11.1.3 小结与反思 11.2如何使用 Flink ProcessFunction 处理宕机告警? 11.2.1 ProcessFunction 简介 11.2.2 CoProcessFunction 简介 11.2.3 Timer 简介 11.2.4 如果利用 ProcessFunction 处理宕机告警? 11.2.5 小结与反思 11.3如何利用 Async I/O 读取告警规则? 11.3.1 为什么需要 Async I/O? 11.3.2 Async I/O API 11.3.3 利用 Async I/O 读取告警规则需求分析 11.3.4 如何使用 Async I/O 读取告警规则数据 11.3.5 小结与反思 11.4如何利用广播变量动态更新告警规则? 11.4.1 BroadcastVariable 简介 11.4.2 如何使用 BroadcastVariable ? 11.4.3 利用广播变量动态更新告警规则数据需求分析 11.4.4 读取告警规则数据 11.4.5 监控数据连接规则数据 11.4.6 小结与反思 11.5如何实时将应用 Error 日志告警? 11.5.1 日志处理方案的演进 11.5.2 日志采集工具对比 11.5.3 日志结构设计 11.5.4 异常日志实时告警项目架构 11.5.5 日志数据发送到 Kafka 11.5.6 Flink 实时处理日志数据 11.5.7 处理应用异常日志 11.5.8 小结与反思 11.6总结 6案例篇 12第十二章——Flink 案例 12.1基于 Flink 实时处理海量日志 12.2.1 实时处理海量日志需求分析 12.2.2 实时处理海量日志架构设计 12.2.3 日志实时采集 12.2.4 日志格式统一 12.2.5 日志实时清洗 12.2.6 日志实时告警 12.2.7 日志实时存储 12.2.8 日志实时展示 12.2.9 小结与反思 12.2基于 Flink 的百亿数据实时去重 12.2.1 去重的通用解决方案 12.2.2 使用 BloomFilter 实现去重 12.2.3 使用 HBase 维护全局 Set 实现去重 12.2.4 使用 Flink 的 KeyedState 实现去重 12.2.5 使用 RocksDBStateBackend 的优化方法 12.2.6 小结与反思 12.3基于 Flink 的实时监控告警系统 12.3.1 监控系统的诉求 12.3.2 监控系统包含的内容 12.3.3 Metrics/Trace/Log 数据实时采集 12.3.4 消息队列如何撑住高峰流量 12.3.5 指标数据实时计算 12.3.6 提供及时且准确的根因分析告警 12.3.7 AIOps 智能运维道路探索 12.3.8 如何保障高峰流量实时写入存储系统的稳定性 12.3.9 监控数据使用可视化图表展示 12.3.10 小结与反思 12.4总结 ``` ================================================ FILE: books/flink-in-action-1.1.md ================================================ --- toc: true title: 《Flink 实战与性能优化》——你的公司是否需要引入实时计算引擎? date: 2021-07-04 tags: - Flink - 大数据 - 流式计算 --- # 第一章 —— 实时计算引擎 本章会从公司常见的计算需求去分析该如何实现这些需求,接着会对比分析实时计算与离线计算之间的区别,从而帮大家分析公司是否需要引入实时计算引擎。接着将会详细的介绍目前最火的实时计算引擎 Flink 的特性,让大家知道其优点,最后会对比其他的计算框架,比如 Spark、Storm 等,希望可以从对比结果来分析这几种计算框架的各自优势,从而为你做技术选型提供一点帮助。 ## 1.1 你的公司是否需要引入实时计算引擎 大数据发展至今,数据呈指数倍的增长,对实效性的要求也越来越高,所以你可能接触到的实时计算需求会越来越多。本章节将从实时计算需求开始讲起,然后阐述完成该需求需要做的工作,最后对比实时计算与离线计算。 ### 1.1.1 实时计算需求 在公司里面,你可能会收到领导、产品经理或者运营等提出的如下需求: ``` 小田,你看能不能做个监控大屏实时查看促销活动商品总销售额(GMV)? 小朱,搞促销活动的时候能不能实时统计下网站的 PV/UV 啊? 小鹏,我们现在搞促销活动能不能实时统计销量 Top5 商品啊? 小李,怎么回事啊?现在搞促销活动结果服务器宕机了都没告警,能不能加一个? 小刘,服务器这会好卡,是不是出了什么问题啊,你看能不能做个监控大屏实时查看机器的运行情况? 小赵,我们线上的应用频繁出现 Error 日志,但是只有靠人肉上机器查看才知道情况,能不能在出现错误的时候及时告警通知? 小夏,我们 1 元秒杀促销活动中有件商品被某个用户薅了 100 件,怎么都没有风控啊? 小宋,你看我们搞促销活动能不能根据每个顾客的浏览记录实时推荐不同的商品啊? …… ``` 那上面这些需求分别对应着什么业务场景呢?我们来总结下,大概如下图所示: ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-12-15-071828.png) 初看这些需求,你是不是感觉实现会比较难?那么接下来我们来分析一下该如何实现这些需求?从这些需求来看,最根本的业务都是需要**实时查看数据信息**,那么首先我们得想想如何实时去采集数据,然后将采集到的数据进行实时的计算,最后将计算后的结果下发到第三方。从采集到计算再到下发计算结果的整个过程,必须都得是实时的,这样我们看到的数据才是最接近实时的,这样才能够很完美的完成上面的这些实时计算需求。 ### 1.1.2 数据实时采集 就上面这些需求,我们知道了需要实时去采集数据,但是针对这些需求,我们到底需要采集些什么数据呢?如下就是我们需要采集的数据: + 用户搜索信息 + 用户浏览商品信息 + 用户下单订单信息 + 网站的所有浏览记录 + 机器 CPU/Mem/IO 信息 + 应用日志信息 ### 1.1.3 数据实时计算 采集后的数据实时上报后,需要做实时的计算,那我们怎么实现计算呢? + 计算所有商品的总销售额 + 统计单个商品的销量,最后求 Top5 + 关联用户信息和浏览信息、下单信息 + 统计网站所有的请求 IP 并统计每个 IP 的请求数量 + 计算一分钟内机器 CPU/Mem/IO 的平均值、75 分位数值 + 过滤出 Error 级别的日志信息 ### 1.1.4 数据实时下发 实时计算后的数据,需要及时的下发到下游,这里说的下游代表可能是告警方式(邮件、短信、钉钉、微信)、存储(消息队列、DB、文件系统等)。 (1)告警方式(邮件、短信、钉钉、微信) 在计算层会将计算结果与阈值进行比较,超过阈值触发告警,让运维提前收到通知,告警消息如下图所示,这样运维可以及时做好应对措施,减少故障的损失大小。 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-12-15-072406.png) (2)存储(消息队列、DB、文件系统等) 数据存储后,监控大盘(Dashboard)从存储(ElasticSearch、HBase 等)里面查询对应指标的数据就可以查看实时的监控信息,做到对促销活动的商品销量、销售额,机器 CPU、Mem 等有实时监控,运营、运维、开发、领导都可以实时查看并作出对应的措施。 + 让运营知道哪些商品是爆款,哪些店铺成交额最多,如下图所示,哪些商品成交额最高,哪些商品浏览量最多; ![](https://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/images/wPVXpl.jpg) + 让运维可以时刻了解机器的运行状况,如下图所示,出现宕机或者其他不稳定情况可以及时处理; ![](https://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/images/fQo3Qh.jpg) + 让开发知道自己项目运行的情况,从 Error 日志知道出现了哪些 Bug,如下图所示; ![](https://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/images/RqkWqu.jpg) + 让领导知道这次促销赚了多少 money,如下图所示。 ![](https://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/images/OPZz5t.jpg) **从数据采集到数据计算再到数据下发,如下图所示,整个流程在上面的场景对实时性要求还是很高的,任何一个地方出现问题都将影响最后的效果!** ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-12-15-073733.png) ### 1.1.5 实时计算场景 前面说了这么多场景,这里我们总结一下实时计算常用的场景有哪些呢?比如: + 交通信号灯数据 + 道路上车流量统计(拥堵状况) + 公安视频监控 + 服务器运行状态监控 + 金融证券公司实时跟踪股市波动,计算风险价值 + 数据实时 ETL + 银行或者支付公司涉及金融盗窃的预警 …… 另外自己还做过调研,实时计算框架的使用场景有如下这些: + 业务数据处理,聚合业务数据,统计之类 + 流量日志 + ETL + 安防这块,公安视频结构化数据,用 Flink 做图片搜索 + 风控,主要处理结构化数据 + 业务告警 + 动态数据监控 总结一下大概有下面这四类,如下图所示: ![](https://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/images/zL93nD.jpg) 这四类分别是: + **实时数据存储**:实时数据存储的时候做一些微聚合、过滤某些字段、数据脱敏,组建数据仓库,实时 ETL。 + **实时数据分析**:实时数据接入机器学习框架(TensorFlow)或者一些算法进行数据建模、分析,然后动态的给出商品推荐、广告推荐 + **实时监控告警**:金融相关涉及交易、实时风控、车流量预警、服务器监控告警、应用日志告警 + **实时数据报表**:活动营销时销售额/销售量大屏,TopN 商品 说到实时计算,这里不得不讲一下它与传统的离线计算之间的区别! ### 1.1.6 离线计算 vs 实时计算 再讲离线计算和实时计算这两个区别之前,我们先来看看流处理和批处理。 #### 流处理与批处理 流处理是一种重要的大数据处理手段,其主要特点是其处理的数据是源源不断且实时到来的。批处理历史比较悠久,而且使用的场景比较多,其主要操作的是大容量的静态数据集,并在计算过程完成后返回结果。它们之间的区别如下图所示: ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-12-15-074541.png) 看完流处理与批处理这两者的区别之后,我们来抽象一下前面内容的场景需求计算流程(**实时计算**)如下图所示: ![](https://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/images/SrubtS.jpg) 实时计算需要不断的从 MQ 中读取采集的数据,然后处理计算后往 DB 里存储,在计算这层你无法感知到会有多少数据量过来、要做一些简单的操作(过滤、聚合等)、及时将数据下发。然而传统的**离线计算**却如下图所示: ![](https://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/images/eseUjV.jpg) 在计算这层,它从 DB(不限 MySQL,还有其他的存储介质)里面读取数据,该数据一般就是固定的(前一天、前一星期、前一个月),然后再做一些复杂的计算或者统计分析,最后生成可供直观查看的报表(dashboard)。 #### 离线计算的特点 离线计算一般有下面这些特点: + 数据量大且时间周期长(一天、一星期、一个月、半年、一年) + 在大量数据上进行复杂的批量计算操作 + 数据在计算之前已经固定,不再会发生变化 + 能够方便的查询批量计算的结果 #### 实时计算的特点 在大数据中与离线计算对应的则是实时计算,那么实时计算有什么特点呢?由于应用场景的各不相同,所以这两种计算引擎接收数据的方式也不太一样:离线计算的数据是固定的(不再会发生变化),通常离线计算的任务都是定时的,如:每天晚上 0 点的时候定时计算前一天的数据,生成报表;然而实时计算的数据源却是流式的。 这里我不得不讲讲什么是流式数据呢?我的理解是比如你在淘宝上下单了某个商品或者点击浏览了某件商品,你就会发现你的页面立马就会给你推荐这种商品的广告和类似商品的店铺,这种就是属于实时数据处理然后作出相关推荐,这类数据需要不断的从你在网页上的点击动作中获取数据,之后进行实时分析然后给出推荐。 #### 流式数据的特点 流式数据一般有下面这些特点: + 数据实时到达 + 数据到达次序独立,不受应用系统所控制 + 数据规模大且无法预知容量 + 原始数据一经处理,除非特意保存,否则不能被再次取出处理,或者再次提取数据代价昂贵 通过上面的内容可以总结实时计算与离线计算的对比如下图所示: ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-12-15-075309.png) #### 实时计算的优势 **实时计算一时爽,一直实时计算一直爽**,对于持续生成最新数据的场景,采用流数据处理是非常有利的。例如,再监控服务器的一些运行指标的时候,能根据采集上来的实时数据进行判断,当超出一定阈值的时候发出警报,进行提醒作用。再如通过处理流数据生成简单的报告,如五分钟的窗口聚合数据平均值。复杂的事情还有在流数据中进行数据多维度关联、聚合、筛选,从而找到复杂事件中的根因。更为复杂的是做一些复杂的数据分析操作,如应用机器学习算法,然后根据算法处理后的数据结果提取出有效的信息,作出、给出不一样的推荐内容,让不同的人可以看见不同的网页(千人千面)。 ### 1.1.7 实时计算面临的挑战 虽然实时计算有这么多好处,但是要使用实时计算也会面临很多挑战,比如下面这些: + 数据处理唯一性(如何保证数据只处理一次?至少一次?最多一次?) + 数据处理的及时性(采集的实时数据量太大的话可能会导致短时间内处理不过来,如何保证数据能够及时的处理,不出现数据堆积?) + 数据处理层和存储层的可扩展性(如何根据采集的实时数据量的大小提供动态扩缩容?) + 数据处理层和存储层的容错性(如何保证数据处理层和存储层高可用,出现故障时数据处理层和存储层服务依旧可用?) 因为各种需求,也就造就了现在不断出现实时计算框架,在 1.2 节中将重磅介绍如今最火的实时计算框架 —— Flink,在 1.3 节中会对比介绍 Spark Streaming、Structured Streaming 和 Storm 之间的区别。 ### 1.1.8 小结与反思 本节从实时计算的需求作为切入点,然后分析该如何去完成这种实时计算的需求,从而得知整个过程包括数据采集、数据计算、数据存储等,接着总结了实时计算场景的类型。最后开始介绍离线计算与实时计算的区别,并提出了实时计算可能带来的挑战。你们公司有文中所讲的类似需求吗?你是怎么解决的呢? ================================================ FILE: books/flink-in-action-1.2.md ================================================ --- toc: true title: 《Flink 实战与性能优化》—— 彻底了解大数据实时计算框架 Flink date: 2021-07-05 tags: - Flink - 大数据 - 流式计算 --- ## 1.2 彻底了解大数据实时计算框架 Flink 在 1.1 节中讲解了日常开发常见的实时需求,然后分析了这些需求的实现方式,接着对比了实时计算和离线计算。随着这些年大数据的飞速发展,也出现了不少计算的框架(Hadoop、Storm、Spark、Flink)。在网上有人将大数据计算引擎的发展分为四个阶段。 + 第一代:Hadoop 承载的 MapReduce + 第二代:支持 DAG(有向无环图)框架的计算引擎 Tez 和 Oozie,主要还是批处理任务 + 第三代:支持 Job 内部的 DAG(有向无环图),以 Spark 为代表 + 第四代:大数据统一计算引擎,包括流处理、批处理、AI、Machine Learning、图计算等,以 Flink 为代表 或许会有人不同意以上的分类,笔者觉得其实这并不重要的,重要的是体会各个框架的差异,以及更适合的场景。并进行理解,没有哪一个框架可以完美的支持所有的场景,也就不可能有任何一个框架能完全取代另一个。 本文将对 Flink 的整体架构和 Flink 的多种特性做个详细的介绍!在讲 Flink 之前的话,我们先来看看 **数据集类型** 和 **数据运算模型** 的种类。 #### 数据集类型 数据集类型有分无穷和有界数据集: + 无穷数据集:无穷的持续集成的数据集合 + 有界数据集:有限不会改变的数据集合 那么那些常见的无穷数据集有哪些呢? + 用户与客户端的实时交互数据 + 应用实时产生的日志 + 金融市场的实时交易记录 + … #### 数据运算模型 数据运算模型有分流式处理和批处理: + 流式:只要数据一直在产生,计算就持续地进行 + 批处理:在预先定义的时间内运行计算,当计算完成时释放计算机资源 那么我们再来看看 Flink 它是什么呢? ### 1.2.1 Flink 简介 Flink 是一个针对流数据和批数据的分布式处理引擎,代码主要是由 Java 实现,部分代码是 Scala。它可以处理有界的批量数据集、也可以处理无界的实时数据集,总结如下图所示。对 Flink 而言,其所要处理的主要场景就是流数据,批数据只是流数据的一个极限特例而已,所以 Flink 也是一款真正的流批统一的计算引擎。 ![](https://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/images/pRMhfm.jpg) 如下图所示,Flink 提供了 State、Checkpoint、Time、Window 等,它们为 Flink 提供了基石,本篇文章下面会稍作讲解,具体深度分析后面会有专门的文章来讲解。 ![](https://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/images/vY6T3M.jpg) ### 1.2.2 Flink 整体架构 Flink 整体架构从下至上分为: 1. 部署:Flink 支持本地运行(IDE 中直接运行程序)、能在独立集群(Standalone 模式)或者在被 YARN、Mesos、K8s 管理的集群上运行,也能部署在云上。 2. 运行:Flink 的核心是分布式流式数据引擎,意味着数据以一次一个事件的形式被处理。 3. API:DataStream、DataSet、Table API & SQL。 4. 扩展库:Flink 还包括用于 CEP(复杂事件处理)、机器学习、图形处理等场景。 整体架构如下图所示: ![](https://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/images/Drsi9h.jpg) ### 1.2.3 Flink 的多种方式部署 作为一个计算引擎,如果要做的足够完善,除了它自身的各种特点要包含,还得支持各种生态圈,比如部署的情况,Flink 是支持以 Standalone、YARN、Kubernetes、Mesos 等形式部署的,如下图所示。 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-12-15-034112.png) 每种部署方式介绍如下: + Local:直接在 IDE 中运行 Flink Job 时则会在本地启动一个 mini Flink 集群。 + Standalone:在 Flink 目录下执行 `bin/start-cluster.sh` 脚本则会启动一个 Standalone 模式的集群。 + YARN:YARN 是 Hadoop 集群的资源管理系统,它可以在群集上运行各种分布式应用程序,Flink 可与其他应用并行于 YARN 中,Flink on YARN 的架构如下图所示。 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-12-15-034029.png) + Kubernetes:Kubernetes 是 Google 开源的容器集群管理系统,在 Docker 技术的基础上,为容器化的应用提供部署运行、资源调度、服务发现和动态伸缩等一系列完整功能,提高了大规模容器集群管理的便捷性,Flink 也支持部署在 Kubernetes 上,在 [GitHub](https://github.com/Aleksandr-Filichkin/flink-k8s/blob/master/flow.jpg) 看到有下面这种运行架构的。 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-05-19-071249.jpg) 通常上面四种居多,另外还支持 AWS、MapR、Aliyun OSS 等。 ### 1.2.4 Flink 分布式运行流程 Flink 作业提交架构流程如下图所示: ![](https://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/images/p92UrK.jpg) 具体流程介绍如下: 1. Program Code:我们编写的 Flink 应用程序代码 2. Job Client:Job Client 不是 Flink 程序执行的内部部分,但它是任务执行的起点。 Job Client 负责接受用户的程序代码,然后创建数据流,将数据流提交给 JobManager 以便进一步执行。 执行完成后,Job Client 将结果返回给用户 3. JobManager:主进程(也称为作业管理器)协调和管理程序的执行。 它的主要职责包括安排任务,管理 Checkpoint ,故障恢复等。机器集群中至少要有一个 master,master 负责调度 task,协调 Checkpoints 和容灾,高可用设置的话可以有多个 master,但要保证一个是 leader, 其他是 standby; JobManager 包含 Actor system、Scheduler、Check pointing 三个重要的组件 4. TaskManager:从 JobManager 处接收需要部署的 Task。TaskManager 是在 JVM 中的一个或多个线程中执行任务的工作节点。 任务执行的并行性由每个 TaskManager 上可用的任务槽(Slot 个数)决定。 每个任务代表分配给任务槽的一组资源。 例如,如果 TaskManager 有四个插槽,那么它将为每个插槽分配 25% 的内存。 可以在任务槽中运行一个或多个线程。 同一插槽中的线程共享相同的 JVM。 同一 JVM 中的任务共享 TCP 连接和心跳消息。TaskManager 的一个 Slot 代表一个可用线程,该线程具有固定的内存,注意 Slot 只对内存隔离,没有对 CPU 隔离。默认情况下,Flink 允许子任务共享 Slot,即使它们是不同 task 的 subtask,只要它们来自相同的 job。这种共享可以有更好的资源利用率。 ### 1.2.5 Flink API Flink 提供了不同的抽象级别的 API 以开发流式或批处理应用,如下图所示。 ![](https://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/images/ozmU46.jpg) 这四种 API 功能分别是: + 最底层提供了有状态流。它将通过 Process Function 嵌入到 DataStream API 中。它允许用户可以自由地处理来自一个或多个流数据的事件,并使用一致性、容错的状态。除此之外,用户可以注册事件时间和处理事件回调,从而使程序可以实现复杂的计算。 + DataStream / DataSet API 是 Flink 提供的核心 API ,DataSet 处理有界的数据集,DataStream 处理有界或者无界的数据流。用户可以通过各种方法(map / flatmap / window / keyby / sum / max / min / avg / join 等)将数据进行转换或者计算。 + Table API 是以表为中心的声明式 DSL,其中表可能会动态变化(在表达流数据时)。Table API 提供了例如 select、project、join、group-by、aggregate 等操作,使用起来却更加简洁(代码量更少)。 你可以在表与 DataStream/DataSet 之间无缝切换,也允许程序将 Table API 与 DataStream 以及 DataSet 混合使用。 + Flink 提供的最高层级的抽象是 SQL 。这一层抽象在语法与表达能力上与 Table API 类似,但是是以 SQL查询表达式的形式表现程序。SQL 抽象与 Table API 交互密切,同时 SQL 查询可以直接在 Table API 定义的表上执行。 Flink 除了 DataStream 和 DataSet API,它还支持 Table API & SQL,Flink 也将通过 SQL 来构建统一的大数据流批处理引擎,因为在公司中通常会有那种每天定时生成报表的需求(批处理的场景,每晚定时跑一遍昨天的数据生成一个结果报表),但是也是会有流处理的场景(比如采用 Flink 来做实时性要求很高的需求),于是慢慢的整个公司的技术选型就变得越来越多了,这样开发人员也就要面临着学习两套不一样的技术框架,运维人员也需要对两种不一样的框架进行环境搭建和作业部署,平时还要维护作业的稳定性。当我们的系统变得越来越复杂了,作业越来越多了,这对于开发人员和运维来说简直就是噩梦,没准哪天凌晨晚上就被生产环境的告警电话给叫醒。所以 Flink 系统能通过 SQL API 来解决批流统一的痛点,这样不管是开发还是运维,他们只需要关注一个计算框架就行,从而减少企业的用人成本和后期开发运维成本。 ### 1.2.6 Flink 程序与数据流结构 一个完整的 Flink 应用程序结构如下图所示: ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-12-14-141653.png) 它们的功能分别是: + Source:数据输入,Flink 在流处理和批处理上的 source 大概有 4 类:基于本地集合的 source、基于文件的 source、基于网络套接字的 source、自定义的 source。自定义的 source 常见的有 Apache kafka、Amazon Kinesis Streams、RabbitMQ、Twitter Streaming API、Apache NiFi 等,当然你也可以定义自己的 source。 + Transformation:数据转换的各种操作,有 Map / FlatMap / Filter / KeyBy / Reduce / Fold / Aggregations / Window / WindowAll / Union / Window join / Split / Select / Project 等,操作很多,可以将数据转换计算成你想要的数据。 + Sink:数据输出,Flink 将转换计算后的数据发送的地点 ,你可能需要存储下来,Flink 常见的 Sink 大概有如下几类:写入文件、打印出来、写入 socket 、自定义的 sink 。自定义的 sink 常见的有 Apache kafka、RabbitMQ、MySQL、ElasticSearch、Apache Cassandra、Hadoop FileSystem 等,同理你也可以定义自己的 sink。 代码结构如下图所示: ![](https://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/images/u3RagR.jpg) ### 1.2.7 丰富的 Connector 通过源码可以发现不同版本的 Kafka、不同版本的 ElasticSearch、Cassandra、HBase、Hive、HDFS、RabbitMQ 都是支持的,除了流应用的 Connector 是支持的,另外还支持 SQL,如下图所示。 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-10-10-101956.png) 再就是要考虑计算的数据来源和数据最终存储,因为 Flink 在大数据领域的的定位就是实时计算,它不做存储(虽然 Flink 中也有 State 去存储状态数据,这里说的存储类似于 MySQL、ElasticSearch 等存储),所以在计算的时候其实你需要考虑的是数据源来自哪里,计算后的结果又存储到哪里去。庆幸的是 Flink 目前已经支持大部分常用的组件了,比如在 Flink 中已经支持了如下这些 Connector: + 不同版本的 Kafka + 不同版本的 ElasticSearch + Redis + MySQL + Cassandra + RabbitMQ + HBase + HDFS + ... 这些 Connector 除了支持流作业外,目前还有还有支持 SQL 作业的,除了这些自带的 Connector 外,还可以通过 Flink 提供的接口做自定义 Source 和 Sink(在 3.8 节中)。 ### 1.2.8 事件时间&处理时间语义 Flink 支持多种 Time,比如 Event time、Ingestion Time、Processing Time,如下图所示,后面 3.1 节中会很详细的讲解 Flink 中 Time 的概念。 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-12-14-140502.png) ### 1.2.9 灵活的窗口机制 Flink 支持多种 Window,比如 Time Window、Count Window、Session Window,还支持自定义 Window,如下图所示。后面 3.2 节中会很详细的讲解 Flink 中 Window 的概念。 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-12-15-034900.png) ### 1.2.10 并行执行任务机制 Flink 的程序内在是并行和分布式的,数据流可以被分区成 stream partitions,operators 被划分为 operator subtasks; 这些 subtasks 在不同的机器或容器中分不同的线程独立运行;operator subtasks 的数量在具体的 operator 就是并行计算数,程序不同的 operator 阶段可能有不同的并行数;如下图所示,source operator 的并行数为 2,但最后的 sink operator 为 1: ![](https://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/images/ggMHCK.jpg) ### 1.2.11 状态存储和容错 Flink 是一款有状态的流处理框架,它提供了丰富的状态访问接口,按照数据的划分方式,可以分为 Keyed State 和 Operator State,在 Keyed State 中又提供了多种数据结构: + ValueState + MapState + ListState + ReducingState + AggregatingState 另外状态存储也支持多种方式: + MemoryStateBackend:存储在内存中 + FsStateBackend:存储在文件中 + RocksDBStateBackend:存储在 RocksDB 中 Flink 中支持使用 Checkpoint 来提高程序的可靠性,开启了 Checkpoint 之后,Flink 会按照一定的时间间隔对程序的运行状态进行备份,当发生故障时,Flink 会将所有任务的状态恢复至最后一次发生 Checkpoint 中的状态,并从那里开始重新开始执行。另外 Flink 还支持根据 Savepoint 从已停止作业的运行状态进行恢复,这种方式需要通过命令进行触发。 ### 1.2.12 自己的内存管理机制 Flink 并不是直接把对象存放在堆内存上,而是将对象序列化为固定数量的预先分配的内存段。它采用类似 DBMS 的排序和连接算法,可以直接操作二进制数据,以此将序列化和反序列化开销降到最低。如果需要处理的数据容量超过内存,那么 Flink 的运算符会将部分数据存储到磁盘。Flink 的主动内存管理和操作二进制数据有几个好处: + 保证内存可控,可以防止 OutOfMemoryError + 减少垃圾收集压力 + 节省数据的存储空间 + 高效的二进制操作 Flink 是如何分配内存、将对象进行序列化和反序列化以及对二进制数据进行操作的,可以参考文章 [Flink 是如何管理好内存的?](http://www.54tianzhisheng.cn/2019/03/24/Flink-code-memory-management/) ,该文中讲解了 Flink 的内存管理机制。 ### 1.2.13 多种扩展库 Flink 扩展库中含有机器学习、Gelly 图形处理、CEP 复杂事件处理、State Processing API 等,这些扩展库在一些特殊场景下会比较适用,关于这块内容可以在第六章查看。 ### 1.2.14 小结与反思 本节在开始介绍 Flink 之前先讲解了下数据集类型和数据运算模型,接着开始介绍 Flink 的各种特性,对于这些特性,你是否有和其他的计算框架做过对比? ================================================ FILE: books/flink-in-action-1.3.md ================================================ --- toc: true title: 《Flink 实战与性能优化》—— 大数据计算框架对比 date: 2021-07-06 tags: - Flink - 大数据 - 流式计算 --- ## 1.3 大数据计算框架对比 在 1.2 节中已经跟大家详细介绍了 Flink,那么在本节就主要 Blink、Spark Streaming、Structured Streaming 和 Storm 的区别。 ### 1.3.1 Flink Flink 是一个针对流数据和批数据分布式处理的引擎,在某些对实时性要求非常高的场景,基本上都是采用 Flink 来作为计算引擎,它不仅可以处理有界的批数据,还可以处理无界的流数据,在 Flink 的设计愿想就是将批处理当成是流处理的一种特例。 如下图所示,在 Flink 的母公司 [Data Artisans 被阿里收购](https://www.eu-startups.com/2019/01/alibaba-takes-over-berlin-based-streaming-analytics-startup-data-artisans/)之后,阿里也在开始逐步将内部的 Blink 代码开源出来并合并在 Flink 主分支上。 ![阿里巴巴收购 Data Artisans](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-07-01-143012.jpg) 而 Blink 一个很强大的特点就是它的 Table API & SQL 很强大,社区也在 Flink 1.9 版本将 Blink 开源版本大部分代码合进了 Flink 主分支。 ### 1.3.2 Blink Blink 是早期阿里在 Flink 的基础上开始修改和完善后在内部创建的分支,然后 Blink 目前在阿里服务于阿里集团内部搜索、推荐、广告、菜鸟物流等大量核心实时业务,如下图所示。 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-07-01-160059.jpg) Blink 在阿里内部错综复杂的业务场景中锻炼成长着,经历了内部这么多用户的反馈(各种性能、资源使用率、易用性等诸多方面的问题),Blink 都做了针对性的改进。在 Flink Forward China 峰会上,阿里巴巴集团副总裁周靖人宣布 Blink 在 2019 年 1 月正式开源,同时阿里也希望 Blink 开源后能进一步加深与 Flink 社区的联动, Blink 开源地址:[https://github.com/apache/flink/tree/blink](https://github.com/apache/flink/tree/blink) 开源版本 Blink 的主要功能和优化点: 1、Runtime 层引入 Pluggable Shuffle Architecture,开发者可以根据不同的计算模型或者新硬件的需要实现不同的 shuffle 策略进行适配;为了性能优化,Blink 可以让算子更加灵活的 chain 在一起,避免了不必要的数据传输开销;在 BroadCast Shuffle 模式中,Blink 优化掉了大量的不必要的序列化和反序列化开销;Blink 提供了全新的 JM FailOver 机制,JM 发生错误之后,新的 JM 会重新接管整个 JOB 而不是重启 JOB,从而大大减少了 JM FailOver 对 JOB 的影响;Blink 支持运行在 Kubernetes 上。 2、SQL/Table API 架构上的重构和性能的优化是 Blink 开源版本的一个重大贡献。 3、Hive 的兼容性,可以直接用 Flink SQL 去查询 Hive 的数据,Blink 重构了 Flink catalog 的实现,并且增加了两种 catalog,一个是基于内存存储的 FlinkInMemoryCatalog,另外一个是能够桥接 Hive metaStore 的 HiveCatalog。 4、Zeppelin for Flink 5、Flink Web,更美观的 UI 界面,查看日志和监控 Job 都变得更加方便 对于开源那会看到一个对话让笔者感到很震撼: > Blink 开源后,两个开源项目之间的关系会是怎样的?未来 Flink 和 Blink 也会由不同的团队各自维护吗? > Blink 永远不会成为另外一个项目,如果后续进入 Apache 一定是成为 Flink 的一部分 对话详情如下图所示: ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-07-01-162836.jpg) 在 Blink 开源那会,笔者就将源码自己编译了一份,然后自己在本地一直运行着,感兴趣的可以看看文章 [阿里巴巴开源的 Blink 实时计算框架真香](http://www.54tianzhisheng.cn/2019/02/28/blink/) ,你会发现 Blink 的 UI 还是比较美观和实用的。 如果你还对 Blink 有什么疑问,可以看看下面两篇文章: [阿里重磅开源 Blink:为什么我们等了这么久?](https://www.infoq.cn/article/wZ_b7Hw9polQWp3mTwVh) [重磅!阿里巴巴 Blink 正式开源,重要优化点解读](https://www.infoq.cn/article/ZkOGAl6_vkZDTk8tfbbg) ### 1.3.3 Spark Apache Spark 是一种包含流处理能力的下一代批处理框架。与 Hadoop 的 MapReduce 引擎基于各种相同原则开发而来的 Spark 主要侧重于通过完善的内存计算和处理优化机制加快批处理工作负载的运行速度。Spark 可作为独立集群部署(需要相应存储层的配合),或可与 Hadoop 集成并取代 MapReduce 引擎。 [Spark Streaming](https://spark.apache.org/docs/latest/streaming-programming-guide.html) 是 Spark API 核心的扩展,可实现实时数据的快速扩展,高吞吐量,容错处理。数据可以从很多来源(如 Kafka、Flume、Kinesis 等)中提取,并且可以通过很多函数来处理这些数据,处理完后的数据可以直接存入数据库或者 Dashboard 等,如下两图所示。 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-07-06-154210.jpg) ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-07-06-134257.jpg) **Spark Streaming 的内部实现原理**是接收实时输入数据流并将数据分成批处理,然后由 Spark 引擎处理以批量生成最终结果流,也就是常说的 micro-batch 模式,如下图所示。 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-07-06-134430.jpg) **Spark DStreams** DStreams 是 Spark Streaming 提供的基本的抽象,它代表一个连续的数据流。它要么是从源中获取的输入流,要么是输入流通过转换算子生成的处理后的数据流。在内部实现上,DStream 由连续的序列化 RDD 来表示,每个 RDD 含有一段时间间隔内的数据,如下图所示: ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-07-06-140956.jpg) 任何对 DStreams 的操作都转换成了对 DStreams 隐含的 RDD 的操作。例如 flatMap 操作应用于 lines 这个 DStreams 的每个 RDD,生成 words 这个 DStreams 的 RDD 过程如下图所示: ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-07-06-134718.jpg) 通过 Spark 引擎计算这些隐含 RDD 的转换算子。DStreams 操作隐藏了大部分的细节,并且为了更便捷,为开发者提供了更高层的 API。 **Spark 支持的滑动窗口** 它和 Flink 的滑动窗口类似,支持传入两个参数,一个代表窗口长度,一个代表滑动间隔,如下图所示。 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-07-06-134915.jpg) **Spark 支持更多的 API** 因为 Spark 是使用 Scala 开发的居多,所以从官方文档就可以看得到对 Scala 的 API 支持的很好,而 Flink 源码实现主要以 Java 为主,因此也对 Java API 更友好,从两者目前支持的 API 友好程度,应该是 Spark 更好,它目前也支持 Python API,但是 Flink 新版本也在不断的支持 Python API。 **Spark 支持更多的 Machine Learning Lib** 你可以很轻松的使用 Spark MLlib 提供的机器学习算法,然后将这些这些机器学习算法模型应用在流数据中,目前 Flink Machine Learning 这块的内容还较少,不过阿里宣称会开源些 Flink Machine Learning 算法,保持和 Spark 目前已有的算法一致,我自己在 GitHub 上看到一个阿里开源的仓库,感兴趣的可以看看 [flink-ai-extended](https://github.com/alibaba/flink-ai-extended)。 **Spark Checkpoint** Spark 和 Flink 一样都支持 Checkpoint,但是 Flink 还支持 Savepoint,你可以在停止 Flink 作业的时候使用 Savepoint 将作业的状态保存下来,当作业重启的时候再从 Savepoint 中将停止作业那个时刻的状态恢复起来,保持作业的状态和之前一致。 **Spark SQL** Spark 除了 DataFrames 和 Datasets 外,也还有 SQL API,这样你就可以通过 SQL 查询数据,另外 Spark SQL 还可以用于从 Hive 中读取数据。 从 Spark 官网也可以看到很多比较好的特性,这里就不一一介绍了,如果对 Spark 感兴趣的话也可以去[官网](https://spark.apache.org/docs/latest/index.html)了解一下具体的使用方法和实现原理。 **Spark Streaming 优缺点** 1、优点 + Spark Streaming 内部的实现和调度方式高度依赖 Spark 的 DAG 调度器和 RDD,这就决定了 Spark Streaming 的设计初衷必须是粗粒度方式的,也就无法做到真正的实时处理 + Spark Streaming 的粗粒度执行方式使其确保“处理且仅处理一次”的特性,同时也可以更方便地实现容错恢复机制。 + 由于 Spark Streaming 的 DStream 本质是 RDD 在流式数据上的抽象,因此基于 RDD 的各种操作也有相应的基于 DStream 的版本,这样就大大降低了用户对于新框架的学习成本,在了解 Spark 的情况下用户将很容易使用 Spark Streaming。 2、缺点 + Spark Streaming 的粗粒度处理方式也造成了不可避免的数据延迟。在细粒度处理方式下,理想情况下每一条记录都会被实时处理,而在 Spark Streaming 中,数据需要汇总到一定的量后再一次性处理,这就增加了数据处理的延迟,这种延迟是由框架的设计引入的,并不是由网络或其他情况造成的。 + 使用的是 Processing Time 而不是 Event Time ### 1.3.4 Structured Streaming ### 1.3.5 Storm #### Storm 核心组件 #### Storm 核心概念 #### Storm 数据处理流程图 ### 1.3.6 计算框架对比 #### Flink VS Spark #### Flink VS Storm #### 全部对比结果 加入知识星球可以看到上面文章:https://t.zsxq.com/vVjeMBY ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-09-25-zsxq.jpg) ### 1.3.7 小结与反思 因在 1.2 节中已经对 Flink 的特性做了很详细的讲解,所以本节主要介绍其他几种计算框架(Blink、Spark、Spark Streaming、Structured Streaming、Storm),并对比分析了这几种框架的特点与不同。你对这几种计算框架中的哪个最熟悉呢?了解过它们之间的差异吗?你有压测过它们的处理数据的性能吗? 本章第一节从公司的日常实时计算需求出发,来分析该如何去实现这种实时需求,接着对比了实时计算与离线计算的区别,从而引出了实时计算的优势,接着就在第二节开始介绍本书的重点 —— 实时计算引擎 Flink,把 Flink 的架构、API、特点、优势等方面都做了讲解,在第三节中对比了市面上现有的计算框架,分别对这些框架做了异同点对比,最后还汇总了它们在各个方面的优势和劣势,以供大家公司内部的技术选型。 ================================================ FILE: books/flink-in-action-10.1.md ================================================ --- toc: true title: 《Flink 实战与性能优化》—— 如何设置 Flink Job RestartStrategy(重启策略)? date: 2021-08-12 tags: - Flink - 大数据 - 流式计算 --- # 第十章 —— Flink 最佳实践 本章将介绍两个最佳实践,第一个是如何合理的配置重启策略,笔者通过自己的亲身经历来讲述配置重启策略的重要性,接着介绍了 Flink 中的重启策略和恢复策略的发展实现过程;第二个是如何去管理 Flink 作业的配置。两个实践大家可以参考,不一定要照搬运用在自己的公司,同时也希望你可以思考下自己是否有啥最佳实践可以分享。 ## 10.1 如何设置 Flink Job RestartStrategy(重启策略)? 从使用 Flink 到至今,遇到的 Flink 有很多,解决的问题更多(含帮助微信好友解决问题),所以对于 Flink 可能遇到的问题及解决办法都比较清楚,那么在这章就给大家讲解下几个 Flink 中比较常遇到的问题的解决办法。 ### 10.1.1 常见错误导致 Flink 作业重启 不知道大家是否有遇到过这样的问题:整个 Job 一直在重启,并且还会伴随着一些错误(可以通过 UI 查看 Exceptions 日志),以下三张图片中的错误信息是笔者曾经生产环境遇到过的一些问题。 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-10-04-152844.png) ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-10-06-140519.png) ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-09-26-2019-05-14_00-59-25.png) 笔者就曾因为上图中的一个异常报错,作业一直重启,在深夜线上发版的时候,同事发现这个问题,凌晨两点的时候打电话把我叫醒起来修 BUG,真是惨的教训,哈哈哈,估计这辈子都忘不掉了! 其实遇到上面这种问题比较常见的,比如有时候因为数据的问题(不合规范、为 null 等),这时在处理这些脏数据的时候可能就会遇到各种各样的异常错误,比如空指针、数组越界、数据类型转换错误等。可能你会说只要过滤掉这种脏数据就行了,或者进行异常捕获就不会导致 Job 不断重启的问题了。 确实如此,如果做好了脏数据的过滤和异常的捕获,Job 的稳定性确实有保证,但是复杂的 Job 下每个算子可能都会产生出脏数据(包含源数据可能也会为空或者不合法的数据),你不可能在每个算子里面也用一个大的 try catch 做一个异常捕获,所以脏数据和异常简直就是防不胜防,不过我们还是要尽力的保证代码的健壮性,但是也要配置好 Flink Job 的 RestartStrategy(重启策略)。 ### 10.1.2 RestartStrategy 简介 RestartStrategy,重启策略,在遇到机器或者代码等不可预知的问题时导致 Job 或者 Task 挂掉的时候,它会根据配置的重启策略将 Job 或者受影响的 Task 拉起来重新执行,以使得作业恢复到之前正常执行状态。Flink 中的重启策略决定了是否要重启 Job 或者 Task,以及重启的次数和每次重启的时间间隔。 ### 10.1.3 为什么需要 RestartStrategy? 重启策略会让 Job 从上一次完整的 Checkpoint 处恢复状态,保证 Job 和挂之前的状态保持一致,另外还可以让 Job 继续处理数据,不会出现 Job 挂了导致消息出现大量堆积的问题,合理的设置重启策略可以减少 Job 不可用时间和避免人工介入处理故障的运维成本,因此重启策略对于 Flink Job 的稳定性来说有着举足轻重的作用。 ### 10.1.4 如何配置 RestartStrategy? 既然 Flink 中的重启策略作用这么大,那么该如何配置呢?其实如果 Flink Job 没有单独设置重启重启策略的话,则会使用集群启动时加载的默认重启策略,如果 Flink Job 中单独设置了重启策略则会覆盖默认的集群重启策略。默认重启策略可以在 Flink 的配置文件 `flink-conf.yaml` 中设置,由 `restart-strategy` 参数控制,有 fixed-delay(固定延时重启策略)、failure-rate(故障率重启策略)、none(不重启策略)三种可以选择,如果选择的参数不同,对应的其他参数也不同。下面分别介绍这几种重启策略和如何配置。 #### FixedDelayRestartStrategy(固定延时重启策略) FixedDelayRestartStrategy 是固定延迟重启策略,程序按照集群配置文件中或者程序中额外设置的重启次数尝试重启作业,如果尝试次数超过了给定的最大次数,程序还没有起来,则停止作业,另外还可以配置连续两次重启之间的等待时间,在 `flink-conf.yaml` 中可以像下面这样配置。 ```yaml restart-strategy: fixed-delay restart-strategy.fixed-delay.attempts: 3 #表示作业重启的最大次数,启用 checkpoint 的话是 Integer.MAX_VALUE,否则是 1。 restart-strategy.fixed-delay.delay: 10 s #如果设置分钟可以类似 1 min,该参数表示两次重启之间的时间间隔,当程序与外部系统有连接交互时延迟重启可能会有帮助,启用 checkpoint 的话,延迟重启的时间是 10 秒,否则使用 akka.ask.timeout 的值。 ``` 在程序中设置固定延迟重启策略的话如下: ```java ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment(); env.setRestartStrategy(RestartStrategies.fixedDelayRestart( 3, // 尝试重启的次数 Time.of(10, TimeUnit.SECONDS) // 延时 )); ``` #### FailureRateRestartStrategy(故障率重启策略) FailureRateRestartStrategy 是故障率重启策略,在发生故障之后重启作业,如果固定时间间隔之内发生故障的次数超过设置的值后,作业就会失败停止,该重启策略也支持设置连续两次重启之间的等待时间。 ```yaml restart-strategy: failure-rate restart-strategy.failure-rate.max-failures-per-interval: 3 #固定时间间隔内允许的最大重启次数,默认 1 restart-strategy.failure-rate.failure-rate-interval: 5 min #固定时间间隔,默认 1 分钟 restart-strategy.failure-rate.delay: 10 s #连续两次重启尝试之间的延迟时间,默认是 akka.ask.timeout ``` 可以在应用程序中这样设置来配置故障率重启策略: ```java ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment(); env.setRestartStrategy(RestartStrategies.failureRateRestart( 3, // 固定时间间隔允许 Job 重启的最大次数 Time.of(5, TimeUnit.MINUTES), // 固定时间间隔 Time.of(10, TimeUnit.SECONDS) // 两次重启的延迟时间 )); ``` #### NoRestartStrategy(不重启策略) NoRestartStrategy 作业不重启策略,直接失败停止,在 `flink-conf.yaml` 中配置如下: ```yaml restart-strategy: none ``` 在程序中如下设置即可配置不重启: ```java ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment(); env.setRestartStrategy(RestartStrategies.noRestart()); ``` #### Fallback(备用重启策略) 如果程序没有启用 Checkpoint,则采用不重启策略,如果开启了 Checkpoint 且没有设置重启策略,那么采用固定延时重启策略,最大重启次数为 Integer.MAX_VALUE。 在应用程序中配置好了固定延时重启策略,可以测试一下代码异常后导致 Job 失败后重启的情况,然后观察日志,可以看到 Job 重启相关的日志: ```text [flink-akka.actor.default-dispatcher-5] INFO org.apache.flink.runtime.executiongraph.ExecutionGraph - Try to restart or fail the job zhisheng default RestartStrategy example (a890361aed156610b354813894d02cd0) if no longer possible. [flink-akka.actor.default-dispatcher-5] INFO org.apache.flink.runtime.executiongraph.ExecutionGraph - Job zhisheng default RestartStrategy example (a890361aed156610b354813894d02cd0) switched from state FAILING to RESTARTING. [flink-akka.actor.default-dispatcher-5] INFO org.apache.flink.runtime.executiongraph.ExecutionGraph - Restarting the job zhisheng default RestartStrategy example (a890361aed156610b354813894d02cd0). ``` 最后重启次数达到配置的最大重启次数后 Job 还没有起来的话,则会停止 Job 并打印日志: ```text [flink-akka.actor.default-dispatcher-2] INFO org.apache.flink.runtime.executiongraph.ExecutionGraph - Could not restart the job zhisheng default RestartStrategy example (a890361aed156610b354813894d02cd0) because the restart strategy prevented it. ``` Flink 中几种重启策略的设置如上,大家可以根据需要选择合适的重启策略,比如如果程序抛出了空指针异常,但是你配置的是一直无限重启,那么就会导致 Job 一直在重启,这样无非再浪费机器资源,这种情况下可以配置重试固定次数,每次隔多久重试的固定延时重启策略,这样在重试一定次数后 Job 就会停止,如果对 Job 的状态做了监控告警的话,那么你就会收到告警信息,这样也会提示你去查看 Job 的运行状况,能及时的去发现和修复 Job 的问题。 ### 10.1.5 RestartStrategy 源码分析 再介绍重启策略应用程序代码配置的时候不知道你有没有看到设置重启策略都是使用 RestartStrategies 类,通过该类的方法就可以创建不同的重启策略,在 RestartStrategies 类中提供了五个方法用来创建四种不同的重启策略(有两个方法是创建 FixedDelay 重启策略的,只不过方法的参数不同),如下图所示。 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-10-08-151745.png) 在每个方法内部其实调用的是 RestartStrategies 中的内部静态类,分别是 NoRestartStrategyConfiguration、FixedDelayRestartStrategyConfiguration、FailureRateRestartStrategyConfiguration、FallbackRestartStrategyConfiguration,这四个类都继承自 RestartStrategyConfiguration 抽象类,如下图所示。 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-10-08-151617.png) 上面是定义的四种重启策略的配置类,在 Flink 中是靠 RestartStrategyResolving 类中的 resolve 方法来解析 RestartStrategies.RestartStrategyConfiguration,然后根据配置使用 RestartStrategyFactory 创建 RestartStrategy。RestartStrategy 是一个接口,它有 canRestart 和 restart 两个方法,它有四个实现类: FixedDelayRestartStrategy、FailureRateRestartStrategy、ThrowingRestartStrategy、NoRestartStrategy,如下图所示。 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-10-08-151311.png) ### 10.1.6 Failover Strategies(故障恢复策略) #### 重启所有的任务 #### 基于 Region 的局部故障重启策略 ### 10.1.7 小结与反思 加入知识星球可以看到上面文章:https://t.zsxq.com/RBYj66M ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-09-25-zsxq.jpg) ================================================ FILE: books/flink-in-action-10.2.md ================================================ --- toc: true title: 《Flink 实战与性能优化》—— 如何设置 Flink Job RestartStrategy(重启策略)? date: 2021-08-13 tags: - Flink - 大数据 - 流式计算 --- ## 10.2 如何使用 Flink ParameterTool 读取配置? 在使用 Flink 中不知道你有没有觉得配置的管理很不方便,比如像算子的并行度配置、Kafka 数据源的配置(broker 地址、topic 名、group.id)、Checkpoint 是否开启、状态后端存储路径、数据库地址、用户名和密码等,反正各种各样的配置都杂乱在一起,当然你可能说我就在代码里面写死不就好了,但是你有没有想过你的作业是否可以不修改任何配置就直接在各种环境(开发、测试、预发、生产)运行呢?可能每个环境的这些配置对应的值都是不一样的,如果你是直接在代码里面写死的配置,那这下子就比较痛苦了,每次换个环境去运行测试你的作业,你都要重新去修改代码中的配置,然后编译打包,提交运行,这样你就要花费很多时间在这些重复的劳动力上了。有没有什么办法可以解决这种问题呢? ### 10.2.1 Flink Job 配置 在 Flink 中其实是有几种方法来管理配置,下面分别来讲解一下。 #### 使用 Configuration Flink 提供了 withParameters 方法,它可以传递 Configuration 中的参数给,要使用它,需要实现那些 Rich 函数,比如实现 RichMapFunction,而不是 MapFunction,因为 Rich 函数中有 open 方法,然后可以重写 open 方法通过 Configuration 获取到传入的参数值。 ```java ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment(); // Configuration 类来存储参数 Configuration configuration = new Configuration(); configuration.setString("name", "zhisheng"); env.fromElements(WORDS) .flatMap(new RichFlatMapFunction>() { String name; @Override public void open(Configuration parameters) throws Exception { //读取配置 name = parameters.getString("name", ""); } @Override public void flatMap(String value, Collector> out) throws Exception { String[] splits = value.toLowerCase().split("\\W+"); for (String split : splits) { if (split.length() > 0) { out.collect(new Tuple2<>(split + name, 1)); } } } }).withParameters(configuration) //将参数传递给函数 .print(); ``` 但是要注意这个 withParameters 只在批程序中支持,流程序中是没有该方法的,并且这个 withParameters 要在每个算子后面使用才行,并不是一次使用就所有都可以获取到,如果所有算子都要该配置,那么就重复设置多次就会比较繁琐。 ### 10.2.2 ParameterTool 管理配置 上面通过 Configuration 的局限性很大,其实在 Flink 中还可以通过使用 ParameterTool 类读取配置,它可以读取环境变量、运行参数、配置文件,下面分别讲下每种如何使用。 #### 读取运行参数 我们知道 Flink UI 上是支持为每个 Job 单独传入 arguments(参数)的,它的格式要求是如下这种。 ``` --brokers 127.0.0.1:9200 --username admin --password 123456 ``` 或者这种 ``` -brokers 127.0.0.1:9200 -username admin -password 123456 ``` 然后在 Flink 程序中你可以直接使用 `ParameterTool.fromArgs(args)` 获取到所有的参数,然后如果你要获取某个参数对应的值的话,可以通过 `parameterTool.get("username")` 方法。那么在这个地方其实你就可以将配置放在一个第三方的接口,然后这个参数值中传入一个接口,拿到该接口后就能够通过请求去获取更多你想要的配置。 #### 读取系统属性 ParameterTool 还支持通过 `ParameterTool.fromSystemProperties()` 方法读取系统属性。 #### 读取配置文件 除了上面两种外,ParameterTool 还支持 `ParameterTool.fromPropertiesFile("/application.properties")` 读取 properties 配置文件。你可以将所有要配置的地方(比如并行度和一些 Kafka、MySQL 等配置)都写成可配置的,然后其对应的 key 和 value 值都写在配置文件中,最后通过 ParameterTool 去读取配置文件获取对应的值。 #### ParameterTool 获取值 ParameterTool 类提供了很多便捷方法去获取值,如下图所示。 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-10-09-134119.png) 你可以在应用程序的 main() 方法中直接使用这些方法返回的值,例如:你可以按如下方法来设置一个算子的并行度: ```java ParameterTool parameters = ParameterTool.fromArgs(args); int parallelism = parameters.get("mapParallelism", 2); DataStream> counts = data.flatMap(new Tokenizer()).setParallelism(parallelism); ``` 因为 ParameterTool 是可序列化的,所以你可以将它当作参数进行传递给自定义的函数。 ```java ParameterTool parameters = ParameterTool.fromArgs(args); DataStream> counts = dara.flatMap(new Tokenizer(parameters)); ``` 然后在函数内部使用 ParameterTool 来获取命令行参数,这样就意味着你在作业任何地方都可以获取到参数,而不是像 withParameters 一样需要每次都设置。 #### 注册全局参数 在 ExecutionConfig 中可以将 ParameterTool 注册为全作业参数的参数,这样就可以被 JobManager 的 web 端以及用户自定义函数中以配置值的形式访问。 ```java StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.getConfig().setGlobalJobParameters(ParameterTool.fromArgs(args)); ``` 然后就可以在用户自定义的 Rich 函数中像如下这样获取到参数值了。 ```java env.addSource(new RichSourceFunction() { @Override public void run(SourceContext sourceContext) throws Exception { while (true) { ParameterTool parameterTool = (ParameterTool) getRuntimeContext().getExecutionConfig().getGlobalJobParameters(); sourceContext.collect(System.currentTimeMillis() + parameterTool.get("os.name") + parameterTool.get("user.home")); } } @Override public void cancel() { } }) ``` 在笔者公司内通常是以 Job 运行的环境变量为准,比如我们是运行在 K8s 上面,那么我们会为我们的这个 Flink Job 设置很多环境变量,设置的环境变量的值就得通过 ParameterTool 类去获取,我们是会优先根据环境变量的值为准,如果环境变量的值没有就会去读取应用运行参数,如果应用运行参数也没有才会去读取之前已经写好在配置文件中的配置。大概代码如下: ```java public static ParameterTool createParameterTool(final String[] args) throws Exception { return ParameterTool .fromPropertiesFile(ExecutionEnv.class.getResourceAsStream("/application.properties")) .mergeWith(ParameterTool.fromArgs(args)) .mergeWith(ParameterTool.fromSystemProperties()) .mergeWith(ParameterTool.fromMap(getenv()));// mergeWith 会使用最新的配置 } //获取 Job 设置的环境变量 private static Map getenv() { Map map = new HashMap<>(); for (Map.Entry entry : System.getenv().entrySet()) { map.put(entry.getKey().toLowerCase().replace('_', '.'), entry.getValue()); } return map; } ``` 这样如果 Job 要更改一些配置,直接在 Job 在 K8s 上面的环境变量进行配置就好了,修改配置后然后重启 Job 就可以运行起来了,整个过程都不需要再次将作业重新编译打包的。但是这样其实也有一定的坏处,重启一个作业的代价很大,因为在重启后你又要去保证状态要恢复到之前未重启时的状态,尽管 Flink 中的 Checkpoint 和 Savepoint 已经很强大了,但是对于复杂的它来说我们多一事不如少一事,所以其实更希望能够直接动态的获取配置,如果配置做了更改,作业能够感知到。在 Flink 中有的配置是不能够动态设置的,但是比如应用业务配置却是可以做到动态的配置,这时就需要使用比较强大的广播变量,广播变量在之前 3.4 节已经介绍过了,如果忘记可以再回去查看,另外在 11.4 节中会通过一个实际案例来教你如何使用广播变量去动态的更新配置数据。 ### 10.2.3 ParameterTool 源码分析 ### 10.2.4 自定义配置参数类 ### 10.2.5 小结与反思 加入知识星球可以看到上面文章:https://t.zsxq.com/RBYj66M ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-09-25-zsxq.jpg) 本章讲了两个实践相关的内容,一个是作业的重启策略,从分析真实线上故障来教大家如何去配置重启策略,以及介绍重启策略的种类,另一个是使用 ParameterTool 去管理配置。两个实践都是比较真实且有一定帮助作用的,希望你也可以应用在你的项目中去。 ================================================ FILE: books/flink-in-action-11.1.md ================================================ --- toc: true title: 《Flink 实战与性能优化》—— 如何统计网站各页面一天内的 PV 和 UV? date: 2021-08-14 tags: - Flink - 大数据 - 流式计算 --- # 第十一章 —— Flink 实战 本章主要是 Flink 实战,介绍了一些常见的需求,比如实时统计网站页面的 PV/UV、宕机告警、动态更新配置、应用 Error 日志实时告警等,然后分别去分析这些需求的实现方式,明白该使用 Flink 中的哪些知识点才能够很好的完成这种需求,并提供完整的案例代码供大家参考。在实现完成这些需求之后,笔者还将会更深一步的去讲解下这些知识点背后的实现方式,希望可以加深你对这些知识点的印象,以便后面你可以灵活的处理类似的需求。 ## 11.1 如何统计网站各页面一天内的 PV 和 UV? 大数据开发最常统计的需求可能就是 PV、UV。PV 全拼 PageView,即页面访问量,用户每次对网站的访问均被记录,按照访问量进行累计,假如用户对同一页面访问了 5 次,那该页面的 PV 就应该加 5。UV 全拼为 UniqueVisitor,即独立访问用户数,访问该页面的一台电脑客户端为一个访客,假如用户对同一页面访问了 5 次,那么该页面的 UV 只应该加 1,因为 UV 计算的是去重后的用户数而不是访问次数。当然如果是按天统计,那么当天 0 点到 24 点相同的客户端只被计算一次,如果过了今天 24 点,第二天该用户又访问了该页面,那么第二天该页面的 UV 应该加 1。 概念明白了那如何使用 Flink 来统计网站各页面的 PV 和 UV 呢?通过本节来详细描述。 ### 11.1.1 统计网站各页面一天内的 PV 在 9.5.2 节端对端如何保证 Exactly Once 中的幂等性写入如何保证端对端 Exactly Once 部分已经用案例讲述了如何通过 Flink 的状态来计算 APP 的 PV,并能够保证 Exactly Once。如果在工作中需要计算网站各页面一天内的 PV,只需要将案例中的 APP 替换成各页面的 id 或者各页面的 url 进行统计即可,按照各页面 id 和日期组合做为 key 进行 keyBy,相同页面、相同日期的数据发送到相同的实例中进行 PV 值的累加,每个 key 对应一个 ValueState,将 PV 值维护在 ValueState 即可。如果一些页面属于爆款页面,例如首页或者活动页面访问特别频繁就可能出现某些 subtask 上的数据量特别大,导致各个 subtask 之前出现数据倾斜的问题,关于数据倾斜的解决方案请参考 9.6 节。 ### 11.1.2 统计网站各页面一天内 UV 的三种方案 PV 统计相对来说比较简单,每来一条用户的访问日志只需要从日志中提取出相应的页面 id 和日期,将其对应的 PV 值加一即可。相对而言统计 UV 就有难度了,同一个用户一天内多次访问同一个页面,只能计数一次。所以每来一条日志,日志中对应页面的 UV 值是否需要加一呢?存在两种情况:如果该用户今天第一次访问该页面,那么 UV 应该加一。如果该用户今天不是第一次访问该页面,表示 UV 中已经记录了该用户,UV 要基于用户去重,所以此时 UV 值不应该加一。难点就在于如何判断该用户今天是不是第一次访问该页面呢? 把问题简单化,先不考虑日期,现在统计网站各页面的累积 UV,可以为每个页面维护一个 Set 集合,假如网站有 10 个页面,那么就维护 10 个 Set 集合,集合中存放着所有访问过该页面用户的 user_id。每来一条用户的访问日志,我们都需要从日志中解析出相应的页面 id 和用户 user_id,去该页面 id 对应的 Set 中查找该 user_id 之前有没有访问过该页面,如果 Set 中包含该 user_id 表示该用户之前访问过该页面,所以该页面的 UV 值不应该加一,如果 Set 中不包含该 user_id 表示该用户之前没有访问过该页面,所以该页面的 UV 值应该加一,并且将该 user_id 插入到该页面对应的 Set 中,表示该用户访问过该页面了。要按天去统计各页面 UV,只需要将日期和页面 id 看做一个整体 key,每个 key 对应一个 Set,其他流程与上述类似。具体的程序流程图如下图所示。 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-12-11-151250.png) #### 使用 Redis 的 set 来维护用户集合 每个 key 都需要维护一个 Set,这个 Set 存放在哪里呢?这里每条日志都需要访问一次 Set,对 Set 访问比较频繁,对存储介质的延迟要求比较高,所以可以使用 Redis 的 set 数据结构,Redis 的 set 数据结构也会对数据进行去重。可以将页面 id 和日期拼接做为 Redis 的 key,通过 Redis 的 sadd 命令将 user_id 放到 key 对应的 set 中即可。Redis 的 set 中存放着今天访问过该页面所有用户的 user_id。 在真实的工作中,Flink 任务可能不需要维护一个 UV 值,Flink 任务承担的角色是实时计算,而查询 UV 可能是一个 Java Web 项目。Web 项目只需要去 Redis 查询相应 key 对应的 set 中元素的个数即可,Redis 的 set 数据结构有 scard 命令可以查询 set 中元素个数,这里的元素个数就是我们所要统计的网站各页面每天的 UV 值。所以使用 Redis set 数据结构的方案 Flink 任务的代码很简单,只需要从日志中解析出相应的日期、页面id 和 user_id,将日期和页面 id 组合做为 Redis 的 key,最后将 user_id 通过 sadd 命令添加到 set 中,Flink 任务的工作就结束了,之后 Web 项目就能从 Redis 中查询到实时增加的 UV 了。下面来看详细的代码实现。 用户访问网站页面的日志实体类: ```java public class UserVisitWebEvent { // 日志的唯一 id private String id; // 日期,如:20191025 private String date; // 页面 id private Integer pageId; // 用户的唯一标识,用户 id private String userId; // 页面的 url private String url; } ``` 生成测试数据的核心代码如下: ```java String yyyyMMdd = new DateTime(System.currentTimeMillis()).toString("yyyyMMdd"); int pageId = random.nextInt(10); // 随机生成页面 id int userId = random.nextInt(100); // 随机生成用户 id UserVisitWebEvent userVisitWebEvent = UserVisitWebEvent.builder() .id(UUID.randomUUID().toString()) // 日志的唯一 id .date(yyyyMMdd) // 日期 .pageId(pageId) // 页面 id .userId(Integer.toString(userId)) // 用户 id .url("url/" + pageId) // 页面的 url .build(); // 对象序列化为 JSON 发送到 Kafka ProducerRecord record = new ProducerRecord(topic, null, null, GsonUtil.toJson(userVisitWebEvent)); producer.send(record); ``` 统计 UV 的核心代码如下,对 Redis Connector 不熟悉的请参阅 3.11 节如何使用 Flink Connectors —— Redis: ```java public class RedisSetUvExample { public static void main(String[] args) throws Exception { // 省略了 env初始化及 Checkpoint 相关配置 Properties props = new Properties(); props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, UvExampleUtil.broker_list); props.put(ConsumerConfig.GROUP_ID_CONFIG, "app-uv-stat"); FlinkKafkaConsumerBase kafkaConsumer = new FlinkKafkaConsumer011<>( UvExampleUtil.topic, new SimpleStringSchema(), props) .setStartFromLatest(); FlinkJedisPoolConfig conf = new FlinkJedisPoolConfig .Builder().setHost("192.168.30.244").build(); env.addSource(kafkaConsumer) .map(string -> { // 反序列化 JSON UserVisitWebEvent userVisitWebEvent = GsonUtil.fromJson( string, UserVisitWebEvent.class); // 生成 Redis key,格式为 日期_pageId,如: 20191026_0 String redisKey = userVisitWebEvent.getDate() + "_" + userVisitWebEvent.getPageId(); return Tuple2.of(redisKey, userVisitWebEvent.getUserId()); }) .returns(new TypeHint>(){}) .addSink(new RedisSink<>(conf, new RedisSaddSinkMapper())); env.execute("Redis Set UV Stat"); } // 数据与 Redis key 的映射关系 public static class RedisSaddSinkMapper implements RedisMapper> { @Override public RedisCommandDescription getCommandDescription() { // 这里必须是 sadd 操作 return new RedisCommandDescription(RedisCommand.SADD); } @Override public String getKeyFromData(Tuple2 data) { return data.f0; } @Override public String getValueFromData(Tuple2 data) { return data.f1; } } } ``` Redis 中统计结果如下图所示,左侧展示的 Redis key,20191026_1 表示 2019年10月26日浏览过 pageId 为 1 的页面对应的 key,右侧展示 key 对应的 set 集合,表示 userId 为 [0,6,27,30,66,67,79,88] 的用户在 2019年10月26日浏览过 pageId 为 1 的页面。 要想获取 20191026_1 对应的 UV 值,可通过 scard 命令获取 set 中 user_id 的数量,具体操作如下所示: ```shell redis> scard 20191026_1 8 ``` 通过上述代码即可通过 Redis 的 set 数据结构来统计网站各页面的 UV。 #### 使用 Flink 的 KeyedState 来维护用户集合 #### 使用 Redis 的 HyperLogLog 来统计 UV ### 11.1.3 小结与反思 加入知识星球可以看到上面文章:https://t.zsxq.com/RBYj66M ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-09-25-zsxq.jpg) ================================================ FILE: books/flink-in-action-11.2.md ================================================ --- toc: true title: 《Flink 实战与性能优化》—— 如何使用 Flink ProcessFunction 处理宕机告警? date: 2021-08-15 tags: - Flink - 大数据 - 流式计算 --- ## 11.2 如何使用 Flink ProcessFunction 处理宕机告警? 在 3.3 节中讲解了 Process 算子的概念,本节中将更详细的讲解 Flink ProcessFunction,然后教大家如何使用 ProcessFunction 来解决公司中常见的问题 —— 宕机,这个宕机不仅仅包括机器宕机,还包含应用宕机,通常出现宕机带来的影响是会很大的,所以能及时收到告警会减少损失。 ### 11.2.1 ProcessFunction 简介 在 1.2.5 节中讲了 Flink 的 API 分层,其中可以看见 Flink 的底层 API 就是 ProcessFunction,它是一个低阶的流处理操作,它可以访问流处理程序的基础构建模块:Event、State、Timer。ProcessFunction 可以被认为是一种提供了对 KeyedState 和定时器访问的 FlatMapFunction。每当数据源中接收到一个事件,就会调用来此函数来处理。对于容错的状态,ProcessFunction 可以通过 RuntimeContext 访问 KeyedState。 定时器可以对处理时间和事件时间的变化做一些处理。每次调用 processElement() 都可以获得一个 Context 对象,通过该对象可以访问元素的事件时间戳以及 TimerService。TimerService 可以为尚未发生的事件时间/处理时间实例注册回调。当定时器到达某个时刻时,会调用 onTimer() 方法。在调用期间,所有状态再次限定为定时器创建的 key,允许定时器操作 KeyedState。如果要访问 KeyedState 和定时器,那必须在 KeyedStream 上使用 KeyedProcessFunction,比如在 keyBy 算子之后使用: ```java dataStream.keyBy(...).process(new KeyedProcessFunction<>(){ }) ``` KeyedProcessFunction 是 ProcessFunction 函数的一个扩展,它可以在 onTimer 和 processElement 方法中获取到分区的 Key 值,这对于数据传递是很有帮助的,因为经常有这样的需求,经过 keyBy 算子之后可能还需要这个 key 字段,那么在这里直接构建成一个新的对象(新增一个 key 字段),然后下游的算子直接使用这个新对象中的 key 就好了,而不在需要重复的拼一个唯一的 key。 ```java public void processElement(String value, Context ctx, Collector out) throws Exception { System.out.println(ctx.getCurrentKey()); out.collect(value); } @Override public void onTimer(long timestamp, OnTimerContext ctx, Collector out) throws Exception { System.out.println(ctx.getCurrentKey()); super.onTimer(timestamp, ctx, out); } ``` ### 11.2.2 CoProcessFunction 简介 如果要在两个输入流上进行操作,可以使用 CoProcessFunction,这个函数可以传入两个不同的数据流输入,并为来自两个不同数据源的事件分别调用 processElement1() 和 processElement2() 方法。可以按照下面的步骤来实现一个典型的 Join 操作: + 为一个数据源的数据建立一个状态对象 + 从数据源处有新数据流过来的时候更新这个状态对象 + 在另一个数据源接收到元素时,关联状态对象并对其产生出连接的结果 比如,将监控的 metric 数据和告警规则数据进行一个连接,在流数据的状态中存储了告警规则数据,当有监控数据过来时,根据监控数据的 metric 名称和一些 tag 去找对应告警规则计算表达式,然后通过规则的表达式对数据进行加工处理,判断是否要告警,如果是要告警则会关联构造成一个新的对象,新对象中不仅有初始的监控 metric 数据,还有含有对应的告警规则数据以及通知策略数据,组装成这样一条数据后,下游就可以根据这个数据进行通知,通知还会在状态中存储这个告警状态,表示它在什么时间告过警了,下次有新数据过来的时候,判断新数据是否是恢复的,如果属于恢复则把该状态清除。 ### 11.2.3 Timer 简介 Timer 提供了一种定时触发器的功能,通过 TimerService 接口注册 timer。TimerService 在内部维护两种类型的定时器(处理时间和事件时间定时器)并排队执行。处理时间定时器的触发依赖于 ProcessingTimeService,它负责管理所有基于处理时间的触发器,内部使用 ScheduledThreadPoolExecutor 调度定时任务;事件时间定时器的触发依赖于系统当前的 Watermark。需要注意的一点就是:**Timer 只能在 KeyedStream 中使用**。 TimerService 会删除每个 Key 和时间戳重复的定时器,即每个 Key 在同一个时间戳上最多有一个定时器。如果为同一时间戳注册了多个定时器,则只会调用一次 onTimer() 方法。Flink 会同步调用 onTimer() 和 processElement() 方法,因此不必担心状态的并发修改问题。TimerService 不仅提供了注册和删除 Timer 的功能,还可以通过它来获取当前的系统时间和 Watermark 的值。TimerService 类中的方法如下图所示。 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/2020-01-12-070737.png) #### 容错 定时器具有容错能力,并且会与应用程序的状态一起进行 Checkpoint,如果发生故障重启会从 Checkpoint/Savepoint 中恢复定时器的状态。如果有处理时间定时器原本是要在恢复起来的那个时间之前触发的,那么在恢复的那一刻会立即触发该定时器。定时器始终是异步的进行 Checkpoint(除 RocksDB 状态后端存储、增量的 Checkpoint、基于堆的定时器外)。因为定时器实际上也是一种特殊状态的状态,在 Checkpoint 时会写入快照中,所以如果有大量的定时器,则无非会增加一次 Checkpoint 所需要的时间,必要的话得根据实际情况合并定时器。 #### 合并定时器 由于 Flink 仅为每个 Key 和时间戳维护一个定时器,因此可以通过降低定时器的频率来进行合并以减少定时器的数量。对于频率为 1 秒的定时器(基于事件时间或处理时间),可以将目标时间向下舍入为整秒数,则定时器最多提前 1 秒触发,但不会迟于我们的要求,精确到毫秒。因此,每个键每秒最多有一个定时器。 ```java long coalescedTime = ((ctx.timestamp() + timeout) / 1000) * 1000; ctx.timerService().registerProcessingTimeTimer(coalescedTime); ``` 由于事件时间计时器仅在 Watermark 到达时才触发,因此可以将当前 Watermark 与下一个 Watermark 的定时器一起调度和合并: ```java long coalescedTime = ctx.timerService().currentWatermark() + 1; ctx.timerService().registerEventTimeTimer(coalescedTime); ``` 定时器也可以类似下面这样移除: ```java //删除处理时间定时器 long timestampOfTimerToStop = ... ctx.timerService().deleteProcessingTimeTimer(timestampOfTimerToStop); //删除事件时间定时器 long timestampOfTimerToStop = ... ctx.timerService().deleteEventTimeTimer(timestampOfTimerToStop); ``` 如果没有该时间戳的定时器,则删除定时器无效。 ### 11.2.4 如果利用 ProcessFunction 处理宕机告警? #### 宕机告警需求分析 #### 宕机告警代码实现 ### 11.2.5 小结与反思 加入知识星球可以看到上面文章:https://t.zsxq.com/RBYj66M ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-09-25-zsxq.jpg) ================================================ FILE: books/flink-in-action-11.3.md ================================================ --- toc: true title: 《Flink 实战与性能优化》—— 如何利用 Async I/O 读取告警规则? date: 2021-08-16 tags: - Flink - 大数据 - 流式计算 --- ## 11.3 如何利用 Async I/O 读取告警规则? Async 中文是异步的意思,在流计算中,使用异步 I/O 能够提升作业整体的计算能力,本节中不仅会讲解异步 I/O 的 API 原理,还会通过一个实战需求(读取告警规则)来讲解异步 I/O 的使用。 ### 11.3.1 为什么需要 Async I/O? 在大多数情况下,IO 操作都是一个耗时的过程,尤其在流计算中,如果在具体的算子里面还有和第三方外部系统(比如数据库、Redis、HBase 等存储系统)做交互,比如在一个 MapFunction 中每来一条数据就要去查找 MySQL 中某张表的数据,然后跟查询出来的数据做关联(同步交互)。查询请求到数据库,再到数据库响应返回数据的整个流程的时间对于流作业来说是比较长的。那么该 Map 算子处理数据的速度就会降下来,在大数据量的情况下很可能会导致整个流作业出现反压问题(在 9.1 节中讲过),那么整个作业的消费延迟就会增加,影响作业整体吞吐量和实时性,从而导致最终该作业处于不可用的状态。 这种同步(Sync)的与数据库做交互操作,会因耗时太久导致整个作业延迟,如果换成异步的话,就可以同时处理很多请求并同时可以接收响应,这样的话,等待数据库响应的时间就会与其他发送请求和接收响应的时间重叠,相同的等待时间内会处理多个请求,从而比同步的访问要提高不少流处理的吞吐量。虽然也可以通过增大该算子的并行度去执行查数据库,但是这种解决办法需要消耗更多的资源(并行度增加意味着消费的 slot 个数也会增加),这种方法和使用异步处理的方法对比一下,还是使用异步的查询数据库这种方法值得使用。同步操作(Sync I/O)和异步操作(Async I/O)的处理流程如下图所示。 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-10-12-121929.png) 左侧表示的是在流处理中同步的数据库请求,右侧是异步的数据库请求。假设左侧是数据流中 A 数据来了发送一个查询数据库的请求看是否之前存在 A,然后等待查询结果返回,只有等 A 整个查询请求响应后才会继续开始 B 数据的查询请求,依此继续;而右侧是连续的去数据库查询是否存在 A、B、C、D,后面哪个请求先响应就先处理哪个,不需要和左侧的一样要等待上一个请求全部完成才可以开始下一个请求,所以异步的话吞吐量自然就高起来了。但是得注意的是:使用异步这种方法前提是要数据库客户端支持异步的请求,否则可能需要借助线程池来实现异步请求,但是现在主流的数据库通常都支持异步的操作,所以不用太担心。 ### 11.3.2 Async I/O API Flink 的 Async I/O API 允许用户在数据流处理中使用异步请求,并且还支持超时处理、处理顺序、事件时间、容错。在 Flink 中,如果要使用 Async I/O API,是非常简单的,需要通过下面三个步骤来执行对数据库的异步操作。 + 继承 RichAsyncFunction 抽象类或者实现用来分发请求的 AsyncFunction 接口 + 返回异步请求的结果的 Future + 在 DataStream 上使用异步操作 官网也给出案例如下: ```java class AsyncDatabaseRequest extends RichAsyncFunction> { //数据库的客户端,它可以发出带有 callback 的并发请求 private transient DatabaseClient client; @Override public void open(Configuration parameters) throws Exception { client = new DatabaseClient(host, post, credentials); } @Override public void close() throws Exception { client.close(); } @Override public void asyncInvoke(String key, final ResultFuture> resultFuture) throws Exception{ //发出异步请求,接收 future 的结果 final Future result = client.query(key); //设置客户端请求完成后执行的 callback,callback 只是将结果转发给 ResultFuture CompletableFuture.supplyAsync(new Supplier() { @Override public String get() { try { return result.get(); } catch (InterruptedException | ExecutionException e) { return null; } } }).thenAccept( (String dbResult) -> { resultFuture.complete(Collections.singleton(new Tuple2<>(key, dbResult))); }); } } //原始数据 DataStream stream = ...; //应用异步 I/O 转换 DataStream> resultStream = AsyncDataStream.unorderedWait(stream, new AsyncDatabaseRequest(), 1000, TimeUnit.MILLISECONDS, 100); ``` 注意:ResultFuture 在第一次调用 resultFuture.complete 时就已经完成了,后面所有 resultFuture.complete 的调用都会被忽略。 下面两个参数控制了异步操作: + Timeout:timeout 定义了异步操作过了多长时间后会被丢弃,这个参数是防止了死的或者失败的请求 + Capacity:这个参数定义可以同时处理多少个异步请求。虽然异步请求会带来更好的吞吐量,但是该操作仍然可能成为流作业的性能瓶颈。限制并发请求的数量可确保操作不会不断累积处理请求,一旦超过 Capacity 值,它将触发反压。 #### 超时处理 #### 结果顺序 #### 事件时间 #### 容错性保证 #### 实践技巧 #### 注意点 ### 11.3.3 利用 Async I/O 读取告警规则需求分析 #### 监控数据样例 #### 告警规则表设计 #### 告警规则实体类 ### 11.3.4 如何使用 Async I/O 读取告警规则数据 ### 11.3.5 小结与反思 加入知识星球可以看到上面文章:https://t.zsxq.com/RBYj66M ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-09-25-zsxq.jpg) ================================================ FILE: books/flink-in-action-11.4.md ================================================ --- toc: true title: 《Flink 实战与性能优化》—— 如何利用广播变量动态更新告警规则? date: 2021-08-17 tags: - Flink - 大数据 - 流式计算 --- ## 11.4 如何利用广播变量动态更新告警规则? 一个在生产环境运行的流作业有时候会想变更一些作业的配置或者数据流的配置,然后作业可以读取并使用新的配置,而不是通过修改配置然后重启作业来读取配置,毕竟重启一个有状态的流作业代价挺大,本节将带你熟悉 Broadcast,并通过一个案例来教会你如何去动态的更新作业的配置。 ### 11.4.1 BroadcastVariable 简介 BroadcastVariable 中文意思是广播变量,其实可以理解是一个公共的共享变量(可能是固定不变的数据集合,也可能是动态变化的数据集合),在作业中将该共享变量广播出去,然后下游的所有任务都可以获取到该共享变量,这样就可以不用将这个变量拷贝到下游的每个任务中。之所以设计这个广播变量的原因主要是因为在 Flink 中多并行度的情况下,每个算子或者不同算子运行所在的 Slot 不一致,这就导致它们不会共享同一个内存,也就不可以通过静态变量的方式去获取这些共享变量值。对于这个问题,有不少读者在问过我为啥我设置的静态变量值在本地运行是可以获取到的,在集群环境运行作业就出现空指针啊,该问题其实笔者自己也在生产环境遇到过,所以接下来好好教大家使用! ### 11.4.2 如何使用 BroadcastVariable ? 在 3.4 节中讲过如何 broadcast 算子和 BroadcastStream 如何使用,在 4.1 节中讲解了 Broadcast State 如何使用以及需要注意的地方,注意 BroadcastVariable 只能应用在批作业中,如果要应用在流作业中则需要要使用 BroadcastStream。 在批作业中通过使用 `withBroadcastSet(DataSet, String)` 来广播一个 DataSet 数据集合,并可以给这份数据起个名字,如果要获取数据的时候,可以通过 `getRuntimeContext().getBroadcastVariable(String)` 获取广播出去的变量数据。下面演示一下广播一个 DataSet 变量和获取变量的样例。 ```java final ParameterTool params = ParameterTool.fromArgs(args); final ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment(); //1. 待广播的数据 DataSet toBroadcast = env.fromElements(1, 2, 3); env.fromElements("a", "b") .map(new RichMapFunction() { List broadcastData; @Override public void open(Configuration parameters) throws Exception { // 3. 获取广播的 DataSet 数据 作为一个 Collection broadcastData = getRuntimeContext().getBroadcastVariable("zhisheng"); } @Override public String map(String value) throws Exception { return broadcastData.get(1) + value; } }).withBroadcastSet(toBroadcast, "zhisheng")// 2. 广播 DataSet .print(); ``` 注意广播的时候设置的名称和获取的名称要一致,然后运行的结果如下图所示。 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-10-17-013429.png) 流作业中通常使用 BroadcastStream 的方式将变量集合在数据流中传递,可能数据集合会做修改更新,但是修改后其实并不想重启作业去读取这些新修改的配置,因为对于一个流作业来说重启带来的代价很高(需要考虑数据堆积和如何恢复至重启前的状态等问题),那么这种情况下就可以在广播数据流处定时查询数据,这样就能够获取更改后的数据,通常在这种广播数据处获取数据只需要设置一个并行度就好,时间根据需求来判断及时性,一般 1 分钟内的数据变更延迟都是在容忍范围之内。广播流中的元素保证流所有的元素最终都会发到下游的所有并行实例,但是元素到达下游的并行实例的顺序可能不相同。因此,对广播状态的修改不能依赖于输入数据的顺序。在进行 Checkpoint 时,所有的任务都会 Checkpoint 下它们的广播状态。 另外需要注意的是:广播出去的变量存在于每个节点的内存中,所以这个数据集不能太大,因为广播出去的数据,会一致在内存中存在,除非程序执行结束。个人建议:如果数据集在几十兆或者百兆的时候,可以选择进行广播,如果数据集的大小上 G 的话,就不建议进行广播了。 上面介绍了下广播变量的在批作业的使用方式,下面通过一个案例来教大家如何在流作业中使用广播变量。 ### 11.4.3 利用广播变量动态更新告警规则数据需求分析 在 11.3.3 节中有设计一张简单的告警规则表,通常告警规则是会对外提供接口进行增删改查的,那么随着业务应用上线,开发人员会对其应用服务新增或者修改告警规则(更改之前规则中的阈值),那么更改之后就需要让告警的作业能够去感知到之前的规则发生了变动,所以就需要在作业中想个什么办法去获取到更改后的数据。有两种方式可以让作业知道规则的变更: push 和 pull 模式。 push 模式则需要在更新、删除、新增接口中不仅操作数据库,还需要额外的发送更新、删除、新增规则的事件到消息队列中,然后作业消费消息队列的数据再去做更新、删除、新增规则,这种及时性有保证,但是可能会有数据不统一的风险(如果消息队列的数据丢了,但是在接口中还是将规则的数据变更存储到数据库);pull 模式下就需要作业定时去查找一遍所有的告警规则数据,然后存在作业内存中,这个时间可以设置的比较短,比如 1 分钟,这样就能既保证数据的一致性,时间延迟也是在容忍范围之内。 对于这种动态变化的规则数据,在 Flink 中通常是使用广播流来处理的。那么接下来就演示下如何利用广播变量动态更新告警规则数据,假设我们在数据库中新增告警规则或者修改告警规则指标的阈值,然后看作业中是否会出现相应的变化。 ### 11.4.4 读取告警规则数据 ### 11.4.5 监控数据连接规则数据 ### 11.4.6 小结与反思 加入知识星球可以看到上面文章:https://t.zsxq.com/RBYj66M ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-09-25-zsxq.jpg) ================================================ FILE: books/flink-in-action-11.5.md ================================================ --- toc: true title: 《Flink 实战与性能优化》—— 如何实时将应用 Error 日志告警? date: 2021-08-18 tags: - Flink - 大数据 - 流式计算 --- ## 11.5 如何实时将应用 Error 日志告警? 大数据时代,随着公司业务不断的增长,数据量自然也会跟着不断的增长,那么业务应用和集群服务器的的规模也会逐渐扩大,几百台服务器在一般的公司已经是很常见的了。那么将应用服务部署在如此多的服务器上,对开发和运维人员来说都是一个挑战。一个优秀的系统运维平台是需要将部署在这么多服务器上的应用监控信息汇总成一个统一的数据展示平台,方便运维人员做日常的监测、提升运维效率,还可以及时反馈应用的运行状态给应用开发人员。举个例子,应用的运行日志需要按照时间排序做一个展示,并且提供日志下载和日志搜索等服务,这样如果应用出现问题开发人员首先可以根据应用日志的错误信息进行问题的排查。那么该如何实时的将应用的 Error 日志推送给应用开发人员呢,接下来我们将讲解日志的处理方案。 ### 11.5.1 日志处理方案的演进 日志处理的方案也是有一个演进的过程,要想弄清楚整个过程,我们先来看下日志的介绍。 #### 什么是日志? 日志是带时间戳的基于时间序列的数据,它可以反映系统的运行状态,包括了一些标识信息(应用所在服务器集群名、集群机器 IP、机器设备系统信息、应用名、应用 ID、应用所属项目等) #### 日志处理方案演进 日志处理方案的演进过程: + 日志处理 v1.0: 应用日志分布在很多机器上,需要人肉手动去机器查看日志信息。 + 日志处理 v2.0: 利用离线计算引擎统一的将日志收集,形成一个日志搜索分析平台,提供搜索让用户根据关键字进行搜索和分析,缺点就是及时性比较差。 + 日志处理 v3.0: 利用 Agent 实时的采集部署在每台机器上的日志,然后统一发到日志收集平台做汇总,并提供实时日志分析和搜索的功能,这样从日志产生到搜索分析出结果只有简短的延迟(在用户容忍时间范围之内),优点是快,但是日志数据量大的情况下带来的挑战也大。 ### 11.5.2 日志采集工具对比 上面提到的日志采集,其实现在已经有很多开源的组件支持去采集日志,比如 Logstash、Filebeat、Fluentd、Logagent 等,这里简单做个对比。 #### Logstash Logstash 是一个开源数据收集引擎,具有实时管道功能。Logstash 可以动态地将来自不同数据源的数据统一起来,并将数据标准化到你所选择的目的地。如下图所示,Logstash 将采集到的数据用作分析、监控、告警等。 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-10-13-025214.jpg) **优势**:Logstash 主要的优点就是它的灵活性,它提供很多插件,详细的文档以及直白的配置格式让它可以在多种场景下应用。而且现在 ELK 整个技术栈在很多公司应用的比较多,所以基本上可以在往上找到很多相关的学习资源。 **劣势**:Logstash 致命的问题是它的性能以及资源消耗(默认的堆大小是 1GB)。尽管它的性能在近几年已经有很大提升,与它的替代者们相比还是要慢很多的,它在大数据量的情况下会是个问题。另一个问题是它目前不支持缓存,目前的典型替代方案是将 Redis 或 Kafka 作为中心缓冲池: #### Filebeat 作为 Beats 家族的一员,Filebeat 是一个轻量级的日志传输工具,它的存在正弥补了 Logstash 的缺点,Filebeat 作为一个轻量级的日志传输工具可以将日志推送到 Kafka、Logstash、ElasticSearch、Redis。它的处理流程如下图所示: ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-10-13-030138.jpg) **优势**:Filebeat 只是一个二进制文件没有任何依赖。它占用资源极少,尽管它还十分年轻,正式因为它简单,所以几乎没有什么可以出错的地方,所以它的可靠性还是很高的。它也为我们提供了很多可以调节的点,例如:它以何种方式搜索新的文件,以及当文件有一段时间没有发生变化时,何时选择关闭文件句柄。 **劣势**:Filebeat 的应用范围十分有限,所以在某些场景下我们会碰到问题。例如,如果使用 Logstash 作为下游管道,我们同样会遇到性能问题。正因为如此,Filebeat 的范围在扩大。开始时,它只能将日志发送到 Logstash 和 Elasticsearch,而现在它可以将日志发送给 Kafka 和 Redis,在 5.x 版本中,它还具备过滤的能力。 #### Fluentd Fluentd 创建的初衷主要是尽可能的使用 JSON 作为日志输出,所以传输工具及其下游的传输线不需要猜测子字符串里面各个字段的类型。这样它为几乎所有的语言都提供库,这也意味着可以将它插入到自定义的程序中。它的处理流程如下图所示: ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-10-13-031337.png) **优势**:和多数 Logstash 插件一样,Fluentd 插件是用 Ruby 语言开发的非常易于编写维护。所以它数量很多,几乎所有的源和目标存储都有插件(各个插件的成熟度也不太一样)。这也意味这可以用 Fluentd 来串联所有的东西。 **劣势**:因为在多数应用场景下得到 Fluentd 结构化的数据,它的灵活性并不好。但是仍然可以通过正则表达式来解析非结构化的数据。尽管性能在大多数场景下都很好,但它并不是最好的,它的缓冲只存在与输出端,单线程核心以及 Ruby GIL 实现的插件意味着它大的节点下性能是受限的。 #### Logagent Logagent 是 Sematext 提供的传输工具,它用来将日志传输到 Logsene(一个基于 SaaS 平台的 Elasticsearch API),因为 Logsene 会暴露 Elasticsearch API,所以 Logagent 可以很容易将数据推送到 Elasticsearch 。 **优势**:可以获取 /var/log 下的所有信息,解析各种格式的日志,可以掩盖敏感的数据信息。它还可以基于 IP 做 GeoIP 丰富地理位置信息。同样,它轻量又快速,可以将其置入任何日志块中。Logagent 有本地缓冲,所以在数据传输目的地不可用时不会丢失日志。 **劣势**:没有 Logstash 灵活。 ### 11.5.3 日志结构设计 前面介绍了日志和对比了常用日志采集工具的优势和劣势,通常在不同环境,不同机器上都会部署日志采集工具,然后采集工具会实时的将新的日志采集发送到下游,因为日志数据量毕竟大,所以建议发到 MQ 中,比如 Kafka,这样再想怎么处理这些日志就会比较灵活。假设我们忽略底层采集具体是哪种,但是规定采集好的日志结构化数据如下: ```java public class LogEvent { private String type;//日志的类型(应用、容器、...) private Long timestamp;//日志的时间戳 private String level;//日志的级别(debug/info/warn/error) private String message;//日志内容 //日志的标识(应用 ID、应用名、容器 ID、机器 IP、集群名、...) private Map tags = new HashMap<>(); } ``` 然后上面这种 LogEvent 的数据(假设采集发上来的是这种结构数据的 JSON 串,所以需要在 Flink 中做一个反序列化解析)就会往 Kafka 不断的发送数据,样例数据如下: ```json { "type": "app", "timestamp": 1570941591229, "level": "error", "message": "Exception in thread \"main\" java.lang.NoClassDefFoundError: org/apache/flink/api/common/ExecutionConfig$GlobalJobParameters", "tags": { "cluster_name": "zhisheng", "app_name": "zhisheng", "host_ip": "127.0.0.1", "app_id": "21" } } ``` 那么在 Flink 中如何将应用异常或者错误的日志做实时告警呢? ### 11.5.4 异常日志实时告警项目架构 整个异常日志实时告警项目的架构如下图所示。 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-10-13-035811.png) 应用日志散列在不同的机器,然后每台机器都有部署采集日志的 Agent(可以是上面的 Filebeat、Logstash 等),这些 Agent 会实时的将分散在不同机器、不同环境的应用日志统一的采集发到 Kafka 集群中,然后告警这边是有一个 Flink 作业去实时的消费 Kafka 数据做一个异常告警计算处理。如果还想做日志的搜索分析,可以起另外一个作业去实时的将 Kafka 的日志数据写入进 ElasticSearch,再通过 Kibana 页面做搜索和分析。 ### 11.5.5 日志数据发送到 Kafka 上面已经讲了日志数据 LogEvent 的结构和样例数据,因为要在服务器部署采集工具去采集应用日志数据对于本地测试来说可能稍微复杂,所以在这里就只通过代码模拟构造数据发到 Kafka 去,然后在 Flink 作业中去实时消费 Kafka 中的数据,下面演示构造日志数据发到 Kafka 的工具类,这个工具类主要分两块,构造 LogEvent 数据和发送到 Kafka。 ```java @Slf4j public class BuildLogEventDataUtil { //Kafka broker 和 topic 信息 public static final String BROKER_LIST = "localhost:9092"; public static final String LOG_TOPIC = "zhisheng_log"; public static void writeDataToKafka() { Properties props = new Properties(); props.put("bootstrap.servers", BROKER_LIST); props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer"); props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer"); KafkaProducer producer = new KafkaProducer(props); for (int i = 0; i < 10000; i++) { //模拟构造 LogEvent 对象 LogEvent logEvent = new LogEvent().builder() .type("app") .timestamp(System.currentTimeMillis()) .level(logLevel()) .message(message(i + 1)) .tags(mapData()) .build(); // System.out.println(logEvent); ProducerRecord record = new ProducerRecord(LOG_TOPIC, null, null, GsonUtil.toJson(logEvent)); producer.send(record); } producer.flush(); } public static void main(String[] args) { writeDataToKafka(); } public static String message(int i) { return "这是第 " + i + " 行日志!"; } public static String logLevel() { Random random = new Random(); int number = random.nextInt(4); switch (number) { case 0: return "debug"; case 1: return "info"; case 2: return "warn"; case 3: return "error"; default: return "info"; } } public static String hostIp() { Random random = new Random(); int number = random.nextInt(4); switch (number) { case 0: return "121.12.17.10"; case 1: return "121.12.17.11"; case 2: return "121.12.17.12"; case 3: return "121.12.17.13"; default: return "121.12.17.10"; } } public static Map mapData() { Map map = new HashMap<>(); map.put("app_id", "11"); map.put("app_name", "zhisheng"); map.put("cluster_name", "zhisheng"); map.put("host_ip", hostIp()); map.put("class", "BuildLogEventDataUtil"); map.put("method", "main"); map.put("line", String.valueOf(new Random().nextInt(100))); //add more tag return map; } } ``` 如果之前 Kafka 中没有 zhisheng_log 这个 topic,运行这个工具类之后也会自动创建这个 topic 了。 ### 11.5.6 Flink 实时处理日志数据 ### 11.5.7 处理应用异常日志 加入知识星球可以看到上面文章:https://t.zsxq.com/RBYj66M ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-09-25-zsxq.jpg) 本章属于 Flink 实战篇,前面章节讲了很多 Flink 相关的技术知识点,这章主要是通过技术点来教大家如何去完成一些真实的需求,比如通过 State 去实时统计网站各页面一天的 PV 和 UV、通过 ProcessFunction 去做定时器处理一些延迟的事件(宕机告警)、通过 Async IO 读取告警规则、通过广播变量动态的更新告警规则、如何实时的做到日志告警。 虽然这些需求换到你们公司去可能不一样,但是这些技术知识点是可以运用到你的项目需求中去的,这里介绍的这些需求,你要学会去分析,然后去判断这些需求到底该使用什么技术来实现会更好,这样才可以做到活学活用。 ================================================ FILE: books/flink-in-action-12.1.md ================================================ --- toc: true title: 《Flink 实战与性能优化》—— 基于 Flink 实时处理海量日志 date: 2021-08-19 tags: - Flink - 大数据 - 流式计算 --- # 第十二章 —— Flink 案例 本章将介绍 Flink 在多个场景下落地实现的大型案例,第一个是实时处理海量的日志,将从日志的收集、日志的传输、日志的实时清洗和异常检测、日志存储、日志展示等方面去介绍 Flink 在其中起的作用,希望整个日志处理的架构大家可以灵活的运用在自己的公司;第二个是百亿数据量的情况下如何使用 Flink 实时去重,在这个案例中将对比介绍其他几种常见的去重实现方案;第三个是 Flink 在监控告警系统中的落地实现,在这个案例中同样很详细的介绍了一个监控告警系统的全链路,每一个关节都不可或缺,并且还介绍了 Flink 在未来结合机器学习算法做一些 AIOps 的事情。三个案例都比较典型,如果你也在做类似的项目,希望对你们的技术选型有一定的帮助。 ## 12.1 基于 Flink 实时处理海量日志 在 11.5 节中讲解了 Flink 如何实时处理异常的日志,并且对比分析了几种常用的日志采集工具。我们也知道通常在排查线上异常故障的时候,日志是必不可缺的一部分,通过异常日志我们可以快速的定位到问题的根因。那么通常在公司对于日志处理有哪些需求呢? ### 12.1.1 实时处理海量日志需求分析 现在公司都在流行构建分布式、微服务、云原生的架构,在这类架构下,项目应用的日志都被分散到不同的机器上,日志查询就会比较困难,所以统一的日志收集几乎也是每家公司必不可少的。据笔者调研,不少公司现在是有日志统一的收集,也会去做日志的实时 ETL,利用一些主流的技术比如 ELK 去做日志的展示、搜索和分析,但是却缺少了日志的实时告警。总结来说,大部分公司对于日志这块的现状是: + **日志分布零散**:分布式应用导致日志分布在不同的机器上,人肉登录到机器上操作复杂,需要统一的日志收集工具。 + **异常日志无告警**:出错时无异常日志告警,导致错过最佳定位问题的时机,需要异常错误日志的告警。 + **日志查看不友好**:登录服务器上在终端查看日志不太方便,需要一个操作友好的页面去查看日志。 + **无日志搜索分析**:历史日志文件太多,想找某种日志找不到了,需要一个可以搜索日志的功能。 在本节中,笔者将为大家讲解日志的全链路,包含了日志的实时采集、日志的 ETL、日志的实时监控告警、日志的存储、日志的可视化图表展示与搜索分析等。 ### 12.1.2 实时处理海量日志架构设计 分析完我们这个案例的需求后,接下来对整个项目的架构做一个合理的设计。 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-10-27-145059.png) 整个架构分为五层:日志接入层、日志削峰层、日志处理层、日志存储层、日志展示层。 + 日志接入层:日志采集的话使用的是 Filebeat 组件,需要在每台机器上部署一个 Filebeat。 + 日志削峰层:防止日志流量高峰,使用 Kafka 消息队列做削峰。 + 日志处理层:Flink 作业同时消费 Kafka 数据做日志清洗、ETL、实时告警。 + 日志存储层:使用 ElasticSearch 做日志的存储。 + 日志展示层:使用 Kibana 做日志的展示与搜索查询界面。 ### 12.1.3 日志实时采集 在 11.5.1 中对比了这几种比较流行的日志采集工具(Logstash、Filebeat、Fluentd、Logagent),从功能完整性、性能、成本、使用难度等方面综合考虑后,这里演示使用的是 Filebeat。 #### 安装 Filebeat 在服务器上下载 [Fliebeat 6.3.2](https://artifacts.elastic.co/downloads/beats/filebeat/filebeat-6.3.2-linux-x86_64.tar.gz) 安装包(请根据自己服务器和所需要的版本进行下载),下载后进行解压。 ``` tar xzf filebeat-6.3.2-linux-x86_64.tar.gz ``` #### 配置 Filebeat 配置 Filebeat 需要编辑 Filebeat 的配置文件 `filebeat.yml`,不同安装方式配置文件的存放路径有一些不同,对于解压包安装的方式,配置文件存在解压目录下面;对于 rpm 和 deb 的方式, 配置文件路径的是 `/etc/filebeat/filebeat.yml` 下。 因为 Filebeat 是要实时采集日志的,所以得让 Filebeat 知道日志的路径是在哪里,下面在配置文件中定义一下日志文件的路径。通常建议在服务器上固定存放日志的路径,然后应用的日志都打在这个固定的路径中,这样 Filebeat 的日志路径配置只需要填写一次,其他机器上可以拷贝同样的配置就能将 Filebeat 运行起来,配置如下。 ``` - type: log # 配置为 true 表示开启 enabled: true # 日志的路径 paths: - /var/logs/*.log ``` 上面的配置表示将对 /var/logs 目录下所有以 .log 结尾的文件进行采集,接下来配置日志输出的方式,这里使用的是 Kafka,配置如下。 ``` output.kafka: # 填写 Kafka 地址信息 hosts: ["localhost:9092"] # 数据发到哪个 topic topic: zhisheng-log partition.round_robin: reachable_only: false required_acks: 1 ``` 上面讲解的两个配置,笔者这里将它们写在一个新建的配置文件中 kafka.yml,然后启动 Filebeat 的时候使用该配置。 ```yml filebeat.inputs: - type: log enabled: true paths: - /var/logs/*.log output.kafka: hosts: ["localhost:9092"] topic: zhisheng_log partition.round_robin: reachable_only: false required_acks: 1 ``` #### 启动 Filebeat 日志路径的配置和 Kafka 的配置都写好后,则接下来通过下面命令将 Filebeat 启动: ``` bin/filebeat -e -c kafka.yml ``` 执行完命令后出现的日志如下则表示启动成功了,另外还可以看得到会在终端打印出 metrics 数据出来。 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-10-26-075438.png) #### 验证 Filebeat 是否将日志数据发到 Kafka 那么此时就得去查看是否真正就将这些日志数据发到 Kafka 了呢,你可以通过 Kafka 的自带命令去消费这个 Topic 看是否不断有数据发出来,命令如下: ``` bin/kafka-console-consumer.sh --zookeeper 106.54.248.27:2181 --topic zhisheng_log --from-beginning ``` 如果出现数据则代表是已经有数据发到 Kafka 了,如果你不喜欢使用这种方式验证,可以自己写个 Flink Job 去读取 Kafka 该 Topic 的数据,比如写了个作业运行结果如下就代表着日志数据已经成功发送到 Kafka。 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-10-27-150039.png) #### 发到 Kafka 的日志结构 既然数据都已经发到 Kafka 了,通过消费 Kafka 该 Topic 的数据我们可以发现这些数据的格式否是 JSON,结构如下: ```json { "@timestamp": "2019-10-26T08:18:18.087Z", "@metadata": { "beat": "filebeat", "type": "doc", "version": "6.8.4", "topic": "zhisheng_log" }, "prospector": { "type": "log" }, "input": { "type": "log" }, "beat": { "name": "VM_0_2_centos", "hostname": "VM_0_2_centos", "version": "6.8.4" }, "host": { "name": "VM_0_2_centos" }, "source": "/var/logs/middleware/kafka.log", "offset": 9460, "log": { "file": { "path": "/var/logs/middleware/kafka.log" } }, "message": "2019-10-26 16:18:11 TRACE [Controller id=0] Leader imbalance ratio for broker 0 is 0.0 (kafka.controller.KafkaController)" } ``` 这个日志结构里面包含了很多字段,比如 timestamp、metadata、host、source、message 等,但是其中某些字段我们其实根本不需要的,你可以根据公司的需求丢弃一些字段,把要丢弃的字段也配置在 kafka.yml 中,如下所示。 ``` processors: - drop_fields: fields: ["prospector","input","beat","log","offset","@metadata"] ``` 然后再次启动 Filebeat ,发现上面配置的字段在新的数据中没有了(除 @metadata 之外),另外经笔者验证:不仅 @metadata 字段不能丢弃,如果 @timestamp 这个字段在 drop_fields 中配置了,也是不起作用的,它们两不允许丢弃。通常来说一行日志已经够长了,再加上这么多我们不需要的字段,就会增加数据的大小,对于生产环境的话,日志数据量非常大,那无疑会对后面所有的链路都会造成一定的影响,所以一定要在底层数据源头做好精简。另外还可以在发送 Kafka 的时候对数据进行压缩,可以在配置文件中配置一个 `compression: gzip`。精简后的日志数据结构如下: ```json { "@timestamp": "2019-10-26T09:23:16.848Z", "@metadata": { "beat": "filebeat", "type": "doc", "version": "6.8.4", "topic": "zhisheng_log" }, "host": { "name": "VM_0_2_centos" }, "source": "/var/logs/middleware/kafka.log", "message": "2019-10-26 17:23:11 TRACE [Controller id=0] Leader imbalance ratio for broker 0 is 0.0 (kafka.controller.KafkaController)" } ``` ### 12.1.4 日志格式统一 因为 Filebeat 是在机器上采集的日志,这些日志的种类比较多,常见的有应用程序的运行日志、作业构建编译打包的日志、中间件服务运行的日志等。通常在公司是可以给开发约定日志打印的规则,但是像中间件这类服务的日志是不固定的,如果将 Kafka 中的消息直接存储到 ElasticSearch 的话,后面如果要做区分筛选的话可能会有问题。为了避免这个问题,我们得在日志存入 ElasticSearch 之前做一个数据格式化和清洗的工作,因为 Flink 处理数据的速度比较好,而且可以做到实时,所以选择在 Flink Job 中完成该工作。 在该作业中的要将 message 解析,一般该行日志信息会包含很多信息,比如日志打印时间、日志级别、应用名、唯一性 ID(用来关联各个请求)、请求上下文。那么我们就需要一个新的日志结构对象来统一日志的格式,定义如下: ### 12.1.5 日志实时清洗 ### 12.1.6 日志实时告警 ### 12.1.7 日志实时存储 ### 12.1.8 日志实时展示 ### 12.1.9 小结与反思 加入知识星球可以看到上面文章:https://t.zsxq.com/IeAYbEy ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-09-25-zsxq.jpg) ================================================ FILE: books/flink-in-action-12.2.md ================================================ --- toc: true title: 《Flink 实战与性能优化》—— 基于 Flink 的百亿数据去重实践 date: 2021-08-20 tags: - Flink - 大数据 - 流式计算 --- ## 12.2 基于 Flink 的百亿数据去重实践 在工作中经常会遇到去重的场景,例如基于 APP 的用户行为日志分析系统:用户的行为日志从手机 APP 端上报到 Nginx 服务端,然后通过 Logstash、Flume 或其他工具将日志从 Nginx 写入到 Kafka 中。由于用户手机客户端的网络可能出现不稳定,所以手机 APP 端上传日志的策略是:宁可重复上报,也不能漏报日志,所以导致 Kafka 中可能会出现日志重复的情况,即:同一条日志出现了 2 条或 2 条以上。通常情况下,Flink 任务的数据源都是 Kafka,若 Kafka 中数据出现了重复,在实时 ETL 或者流计算时都需要考虑基于日志主键对日志进行去重,否则会导致流计算结果偏高或结果不准确的问题,例如用户 a 在某个页面只点击了一次,但由于日志重复上报,所以用户 a 在该页面的点击日志在 Kafka 中出现了 2 次,最后统计该页面的点击数时,结果就会偏高。这里只阐述了一种可能造成 Kafka 中数据重复的情况,在生产环境中很多情况都可能造成 Kafka 中数据重复,这里不一一列举,本节主要讲述出现了数据重复后,该如何处理。 ### 12.2.1 去重的通用解决方案 Kafka 中数据出现重复后,各种解决方案都比较类似,一般需要一个全局 Set 集合来维护历史所有数据的主键。当处理新日志时,需要拿到当前日志的主键与历史数据的 Set 集合按照规则进行比较,若 Set 集合中已经包含了当前日志的主键,说明当前日志在之前已经被处理过了,则当前日志应该被过滤掉,否则认为当前日志不应该被过滤应该被处理,而且处理完成后需要将新日志的主键加入到 Set 集合中,Set 集合永远存放着所有已经被处理过的数据。这种去重的通用解决方案的流程图如下图所示。 ![去重的通用解决方案的流程图](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-12-11-151455.png) 处理流程很简单,关键在于如何维护这个 Set 集合,可以简单估算一下这个 Set 集合需要占用多大空间。本小节要解决的问题是百亿数据去重,所以就按照每天 1 百亿的数据量来计算。由于每天数据量巨大,因此主键占用空间通常会比较大,如果主键占用空间小意味着表示的数据范围就比较小,就可能导致主键冲突,例如:4 个字节的 int 类型表示数据范围是为 -2147483648 ~ 2147483647,总共可以表示 42 亿个数,如果这里每天百亿的数据量选用 int 类型做为主键的话,很明显会有大量的主键发生冲突,会将不重复的数据认为是发生了重复。用户的行为日志是在手机客户端生成的,没有全局发号器,一般会选取 UUID 做为日志的主键,UUID 会生成 36 位的字符串,例如:"f106c4a1-4c6f-41c1-9d30-bbb2b271284a"。每个主键占用 36 字节,每天 1 百亿数据,36字节 * 100亿 ≈ 360 GB。这仅仅是一天的数据量,所以该 Set 集合要想存储空间不发生持续地爆炸式增长,必须增加一个功能,那就是给所有的主键增加 TTL(过期时间)。如果不增加 TTL,10 天数据量的主键占用空间就 3.6T,100 天数据量的主键占用空间 36T,所以在设计之初必须考虑为主键设定 TTL。如果要求按天进行去重或者认为日志发生重复上报的时间间隔不可能大于 24 小时,那么为了系统的可靠性 TTL 可以设置为 36 小时。每天数据量 1 百亿,且 Set 集合中存放着 36 小时的数据量,即 100 亿 * 1.5 = 150 亿,所以 Set 集合中需要维护 150 亿的数据量,且 Set 集合中每条数据都增加了 TTL,意味着 Set 集合需要为每条数据再附带保存一个时间戳,来确定该数据什么时候过期。例如 Redis 中为一个 key 设置了 TTL,如果没有为这个 key 附带时间戳,那么根本无法判断该 key 什么时候应该被清理。所以在考虑每条数据占用空间时,不仅要考虑数据本身,还需要考虑是否需要其他附带的存储。主键本身占用 36 字节加上 long 类型的时间戳 8 字节,所以每条数据至少需要占用 44 字节,150 亿 * 44 字节 = 660 GB。所以每天百亿的数据量,如果我们使用 Set 集合的方案来实现,至少需要占用 660 GB 以上的存储空间。 ### 12.2.2 使用 BloomFilter 实现去重 有些流计算的场景对准确性要求并不是很高,例如传统的 Lambda 架构中,都会有离线去矫正实时计算的结果,所以根据业务场景,当业务要求可以接受结果有小量误差时,可以选择使用一些低成本的数据结构。BloomFilter 和 HyperLogLog 都是相对低成本的数据结构,分别有自己的应用场景,且两种数据结构都有一定误差。HyperLogLog 可以估算出 HyperLogLog 中插入了多少个不重复的元素,而不能告诉我们之前是否插入了哪些元素。BloomFilter 则恰好相反,相对而言 BloomFilter 更像是一个 Set 集合,BloomFilter 可以告诉你 BloomFilter 中**肯定不包含**元素 a,或者告诉你 BloomFilter 中**可能包含**元素 b,但 BloomFilter 不能告诉你 BloomFilter 中插入了多少个元素。接下来了解一下 BloomFilter 的实现原理。 #### bitmap 位图 了解 BloomFilter,从 bitmap(位图)开始说起。现在有 1 千万个整数,数据范围在 0 到 2 千万之间。如何快速查找某个整数是否在这 1 千万个整数中呢?可以将这 1 千万个数保存在 HashMap 中,不考虑对象头及其他空间,1000 万个 int 类型数据需要占用大约 1000万 * 4 字节 ≈ 40 MB 存储空间。有没有其他方案呢?因为数据范围是 0 到 2 千万,所以可以申请一个长度为 2000 万、boolean 类型的数组,将这 2 千万个整数作为数组下标,将其对应的数组默认值设置成 false,如下图所示,数组下标为 2、666、999 的位置存储的数据为 true,表示 1 千万个数中包含了 2、666、999 等。当查询某个整数 K 是否在这 1 千万个整数中时,只需要将对应的数组值 `array[K]` 取出来,看是否等于 true。如果等于 true,说明 1 千万整数中包含这个整数 K,否则表示不包含这个整数 K。 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-11-03-103907.jpg) Java 的 boolean 基本类型占用一个字节(8bit)的内存空间,所以上述方案需要申请 2000 万字节。如下图所示,可以通过编程语言用二进制位来模拟布尔类型,二进制的 1 表示true、二进制的 0 表示false。通过二进制模拟布尔类型的方案,只需要申请 2000 万 bit 即可,相比 boolean 类型而言,存储空间占用仅为原来的 1/8。2000 万 bit ≈ 2.4 MB,相比存储原始数据的方案 40 MB 而言,占用的存储空间少了很多。 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-11-03-103905.jpg) 假如这 1 千万个整数的数据范围是 0 到 100 亿,那么就需要申请 100 亿个 bit 约等于 1200 MB,比存储原始数据方案的 40MB 还要大很多。该情况下,直接使用位图使用的存储空间更多了,怎么解决呢?可以只申请 1 亿 bit 的存储空间,对 1000 万个数求hash,映射到 1 亿的二进制位上,最后大约占用 12 MB 的存储空间,但是可能存在 hash 冲突的情况。例如 3 和 100000003(一亿零三)这两个数对一亿求余都为 3,所以映射到长度为 1 亿的位图上,这两个数会占用同一个 bit,就会导致一个问题:1 千万个整数中包含了一亿零三,所以位图中下标为 3 的位置存储着二进制 1。当查询 1 千万个整数中是否包含数字 3 时,同样也是去位图中下标 3 的位置去查找,发现下标为 3 的位置存储着二进制 1,所以误以为 1 千万个整数中包含数字 3。为了减少 hash 冲突,于是诞生了 BloomFilter。 #### BloomFilter 原理介绍 hash 存在 hash 冲突(碰撞)的问题,两个不同的 key 通过同一个 hash 函数得到的值有可能相同。为了减少冲突,可以引入多个 hash 函数,如果通过其中的一个 hash 函数发现某元素不在集合中,那么该元素肯定不在集合中。当所有的 hash 函数告诉我们该元素在集合中时,才能确定该元素存在于集合中,这便是BloomFilter的基本思想。 如下图所示,是往 BloomFilter 中插入元素 a、b 的过程,有 3 个 hash 函数,元素 a 经过 3 个 hash 函数后对应的 2、8、10 这三个二进制位,所以将这三个二进制位置为 1,元素 b 经过 3 个 hash 函数后,对应的 5、10、14 这三个二进制位,将这三个二进制位也置为 1,其中下标为 10 的二进制位被 a、b 元素都涉及到。 如下图所示,是从 BloomFilter 中查找元素 c、d 的过程,同样包含了 3 个 hash 函数,元素 c 经过 3 个 hash 函数后对应的 2、6、9 这三个二进制位,其中下标 6 和 9 对应的二进制位为 0,所以会认为 BloomFilter 中不存在元素 c。元素 d 经过 3 个 hash 函数后对应的 5、8、14 这三个二进制位,这三个位对应的二进制位都为 1,所以会认为 BloomFilter 中存在元素 d,但其实 BloomFilter 中并不存在元素 d,是因为元素 a 和元素 b 也对应到了 5、8、14 这三个二进制位上,所以 BloomFilter 会有误判。但是从实现原理来看,当 BloomFilter 告诉你不包含元素 c 时,BloomFilter 中**肯定不包含**元素 c,当 BloomFilter 告诉你 BloomFilter 中包含元素 d 时,它只是**可能包含**,也有可能不包含。 #### 使用 BloomFilter 实现数据去重 Redis 4.0 之后 BloomFilter 以插件的形式加入到 Redis 中,关于 api 的具体使用这里不多赘述。BloomFilter 在创建时支持设定一个预期容量和误判率,预期容量即预计插入的数据量,误判率即:当 BloomFilter 中插入的数据达到预期容量时,误判的概率,如果 BloomFilter 中插入数据较少的话,误判率会更低。 经笔者测试,申请一个预期容量为 10 亿,误判率为千分之一的 BloomFilter,BloomFilter 会申请约 143 亿个 bit,即:14G左右,相比之前 660G 的存储空间小太多了。但是在使用过程中,需要记录 BloomFilter 中插入元素的个数,当插入元素个数达到 10 亿时,为了保障误差率,可以将当前 BloomFilter 清除,重新申请一个新的 BloomFilter。 通过使用 Redis 的 BloomFilter,我们可以通过相对较小的内存实现百亿数据的去重,但是 BloomFilter 有误差,所以只能使用在那些对结果能承受一定误差的应用场景,对于广告计费等对数据精度要求非常高的场景,极力推荐大家使用精准去重的方案来实现。 ### 12.2.3 使用 HBase 维护全局 Set 实现去重 通过之前分析,我们知道要想实现百亿数据量的精准去重,需要维护 150 亿数据量的 Set 集合,每条数据占用 44 KB,总共需要 660 GB 的存储空间。注意这里说的是存储空间而不是内存空间,为什么呢?因为 660 G 的内存实在是太贵了,660G 的 Redis 云服务一个月至少要 2 万 RMB 以上,俗话说设计架构不考虑成本等于耍流氓。这里使用 Redis 确实可以解决问题,但是成本较高。HBase 基于 RowKey Get 的效率比较高,所以这里可以考虑将这个大的 Set 集合以 HBase RowKey 的形式存放到 HBase 中。HBase 表设置 TTL 为 36 小时,最近 36 小时的 150 亿条日志的主键都存放到 HBase 中,每来一条数据,先拿到主键去 HBase 中查询,如果 HBase 表中存在该主键,说明当前日志已经被处理过了,当前日志应该被过滤。如果 HBase 表中不存在该主键,说明当前日志之前没有被处理过,此时应该被处理,且处理完成后将当前主键 Put 到 HBase 表中。由于数据量比较大,所以一定要提前对 HBase 表进行预分区,将压力分散到各个 RegionServer 上。 #### 使用 HBase RowKey 去重带来的问题 ### 12.2.4 使用 Flink 的 KeyedState 实现去重 下面就教大家如何使用 Flink 的 KeyedState 实现去重。 #### 使用 Flink 状态来维护 Set 集合的优势 #### 如何使用 KeyedState 维护 Set 集合 #### 优化主键来减少状态大小,且提高吞吐量 ### 12.2.5 使用 RocksDBStateBackend 的优化方法 在使用上述方案的过程中,可能会出现吞吐量时高时低,或者吞吐量比笔者的测试性能要低一些,当出现这类问题的时候,可以尝试从以下几个方面进行优化。 #### 设置本地 RocksDB 的数据目录 #### Checkpoint 参数相关配置 #### RocksDB 参数相关配置 ### 12.2.6 小结与反思 加入知识星球可以看到上面文章:https://t.zsxq.com/IeAYbEy ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-09-25-zsxq.jpg) ================================================ FILE: books/flink-in-action-12.3.md ================================================ --- toc: true title: 《Flink 实战与性能优化》—— 基于 Flink 的实时监控告警系统 date: 2021-08-21 tags: - Flink - 大数据 - 流式计算 --- ## 12.3 基于 Flink 的实时监控告警系统 在如今微服务、云原生等技术盛行的时代,当谈到说要从 0 开始构建一个监控系统,大家无非就首先想到三个词:Metrics、Tracing、Logging。 ### 12.3.1 监控系统的诉求 国外一篇比较火的文章 [Metrics, Tracing, and Logging](http://peter.bourgon.org/blog/2017/02/21/metrics-tracing-and-logging.html) 内有个图很好的总结了一个监控系统的诉求,分别是 Metrics、Logging、Tracing,如下图所示。 ![监控系统的诉求](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-12-08-132227.png) Metrics 的特点:它自己提供了五种基本的度量类型 Gauge、Counter、Histogram、Timer、Meter。 Tracing 的特点:提供了一个请求从接收到处理完毕整个生命周期的跟踪路径,通常请求都是在分布式的系统中处理,所以也叫做分布式链路追踪。 Logging 的特点:提供项目应用运行的详细信息,例如方法的入参、运行的异常记录等。 这三者在监控系统中缺一不可,它们之间的关系是:基于 Metrics 的异常告警事件,然后通过 Tracing 定位问题可疑模块,根据模块详细的日志定位到错误根源,最后再返回来调整 Metrics 的告警规则,以便下次更早的预警,提前预防出现此类问题。 ### 12.3.2 监控系统包含的内容 针对提到的三个点,笔者找到国内外的开源监控系统做了对比,发现真正拥有全部功能的比较少,有的系统比较专注于 Logging、有的系统比较专注于 Tracing,而大部分其他的监控系统无非是只是监控系统的一部分,比如是作为一款数据库存储监控数据、作为一个可视化图表的系统去展示各种各样的监控数据信息。 拿 Logging 来说,开源用的最多最火的技术栈是 ELK,Tracing 这块有 Skywalking、Pinpoint 等技术,它们的对比如 [APM 巅峰对决:Skywalking PK Pinpoint](https://mp.weixin.qq.com/s/_XE-gCJnDY3-yEK4xmWiMg) 一文介绍。而存储监控数据的时序数据库那就比较多了,常见的比如 InfluxDB、Prometheus、OpenTSDB 等,它们之间的对比介绍如下图所示。 ![常见时序数据库对比](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-10-31-%E6%97%B6%E5%BA%8F%E6%95%B0%E6%8D%AE%E5%BA%93%E6%AF%94%E8%BE%83.png) 监控可视化图表的开源系统个人觉得最好看的就是 Grafana,在 8.2 节中搭建 Flink 监控系统的数据展示也是用的 Grafana,当然还可以利用 ECharts、BizCharts 等数据图表库做二次开发来适配公司的数据展示图表。 上面说了这么多,这里笔者根据自己的工作经验先谈谈几点自己对监控系统的心得: 1. **告警是监控系统第一入口,图表展示体现监控的价值**:告警是唯一可以第一时间反映运行状态,它承担着系统与人之间的沟通桥梁,通常告警消息又会携带链接跳转到图表展示,它作为第一入口并衔接上了整个监控系统。 2. **数据采集是监控的源泉**:数据采集是监控系统的源泉,如果采集的数据是错误的,将导致后面的链路(告警、数据展示)全处于无效状态,所以千万千万要保证数据采集的准确性和完整性。 3. **数据存储是监控最大挑战**:当机器、系统应用和监控指标等变得越多来多时,采集上来的数据是爆炸性增长的,将海量的监控数据实时存储到任何一个数据库,挑战都是不小的。 说完心得再来讲解到底一个监控系统真正该包含哪些东西呢?笔者觉得首先分 6 层:数据采集层、数据传输层、数据计算层、告警、数据存储、数据展示,如下图所示。 ![监控系统分层](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-12-11-012408.png) 监控系统这六层的主要功能分别是: + 数据采集层:该层主要功能就是去采集各种各样的数据,比如 Metrics、Logging、Tracing 数据。 + 数据传输层:该层主要功能就是传输采集到的监控数据,一般使用消息队列居多,比如 Kafka、RocketMQ 等。 + 数据计算层:该层的主要功能是将采集到的数据进行数据清洗和计算,一般采用 Flink、Spark 等计算引擎来处理。 + 告警:该层其实也属于数据计算层,但是因为告警涉及的内容太多,比如告警规则、告警计算、告警通知等,所以可以单独作为一个重要点来讲。 + 数据存储层:该层的主要功能是存储所有的监控数据,为后面的数据可视化提供数据源。 + 数据展示层:该层的主要功能是将监控数据通过可视化图表来展示出来,通过图表可以知道服务器、应用的运行状态。 ### 12.3.3 Metrics/Tracing/Logging 数据实时采集 在 12.1 节中讲解了日志数据如何采集,那么对于 Metrics 和 Tracing 数据该怎么采集呢? #### Metrics #### Tracing ### 12.3.4 消息队列如何撑住高峰流量 #### RabbitMQ #### Kafka #### RocketMQ ### 12.3.5 指标数据实时计算 ### 12.3.6 提供及时且准确的根因分析告警 #### 告警本质 #### 告警通知对象 #### 告警通知方式 #### 告警规则的设计 #### 根因分析告警 #### 告警事件解耦 #### 告警工单记录告警解决过程 ### 12.3.7 AIOps 智能运维道路探索 ### 12.3.8 如何保障高峰流量实时写入存储系统的稳定性 ### 12.3.9 监控数据使用可视化图表展示 #### Grafana 介绍 ### 12.3.10 小结与反思 加入知识星球可以看到上面文章:https://t.zsxq.com/IeAYbEy ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-09-25-zsxq.jpg) ### 本章总结 本章讲了三个公司常见场景案例:日志处理、去重、监控告警。在日志处理案例中讲解了日志的采集的需求,以及分析了整个日志采集案例的架构设计分层,关于日志采集工具的选型在 11.5 节中有讲过,所以该案例中就直接讲述采集工具的使用和安装,并将采集到的数据发送到 Kafka,然后使用 Flink 清洗日志数据并将异常日志告警通知到相应负责人,接着将数据写入到 ElasticSearch,最后通过 Kibana 可以展示和搜索日志数据。 在百亿数据实时去重案例中通过对比通用解决方法、使用 BloomFilter、使用 HBase 和使用 Flink KeyedState 几种方案来分析实时去重的解决方案,并在该案例中还提及到如何去优化去重的效果。 在实时监控告警案例中讲述了公司通用的监控告警需求,包括 Metrics、Logging、Tracing,然后设计出整个监控告警系统的架构分层和技术选型,其中分层包含数据采集层、数据传输层、数据计算层、数据存储层、数据展示层。关于数据采集工具、消息队列、数据存储中间件、数据展示的技术选型,笔者也分别做了对比介绍,以便大家可以根据自己公司的情况做架构选型。另外针对实时告警这块,笔者也详述了很多,包括告警规则的设计、告警通知对象、告警通知方式、告警消息收敛、告警消息根因分析等,最后还讲了对监控告警的展望,希望能够在 AIOps 做更多的探索,对于这块的内容,笔者在社区钉钉群做过视频直播,大家可以去笔者的博客查看录播的视频。 ================================================ FILE: books/flink-in-action-2.1.md ================================================ --- toc: true title: 《Flink 实战与性能优化》—— Flink 环境准备 date: 2021-07-07 tags: - Flink - 大数据 - 流式计算 --- # 第二章 —— Flink 入门 通过第一章对 Flink 的介绍,相信你对 Flink 的概念和特性有了一定的了解,接下来本章将开始正式进入 Flink 的学习之旅,笔者将带你搭建 Flink 的环境和编写两个案例(WordCount 程序、读取 Socket 数据)来入门 Flink。 ## 2.1 Flink 环境准备 通过前面的章节内容,相信你已经对 Flink 的基础概念等知识已经有一定了解,现在是不是迫切的想把 Flink 给用起来?先别急,我们先把电脑的准备环境给安装好,这样后面才能更愉快地玩耍。 废话不多说了,直奔主题。因为本书后面章节内容会使用 Kafka、MySQL、ElasticSearch 等组件,并且运行 Flink 程序是需要依赖 Java 的,另外就是我们需要使用 IDE 来开发 Flink 应用程序以及使用 Maven 来管理 Flink 应用程序的依赖,所以本节我们提前安装这个几个组件,搭建好本地的环境,后面如果还要安装其他的组件笔者会在对应的章节中补充,如果你的操作系统已经中已经安装过 JDK、Maven、MySQL、IDEA 等,那么你可以跳过对应的内容,直接看你未安装过的。 这里笔者再说下自己电脑的系统环境:macOS High Sierra 10.13.5,后面文章的演示环境不作特别说明的话就是都在这个系统环境中。 ### 2.1.1 JDK 安装与配置 虽然现在 JDK 已经更新到 12 了,但是为了稳定我们还是安装 JDK 8,如果没有安装过的话,可以去[官网](https://www.oracle.com/technetwork/java/javase/downloads/index.html) 的[下载页面](https://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html)下载对应自己操作系统的最新 JDK8 就行。 Mac 系统的是 `jdk-8u211-macosx-x64.dmg` 格式、Linux 系统的是 `jdk-8u211-linux-x64.tar.gz` 格式。Mac 系统安装的话直接双击然后一直按照提示就行了,最后 JDK 的安装目录在 `/Library/Java/JavaVirtualMachines/` ,然后在 `/etc/hosts` 中配置好环境变量(注意:替换你自己电脑本地的路径)。 ``` export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk1.8.0_152.jdk/Contents/Home export CLASSPATH=$JAVA_HOME/lib/tools.jar:$JAVA_HOME/lib/dt.jar: export PATH=$PATH:$JAVA_HOME/bin ``` Linux 系统的话就是在某个目录下直接解压就行了,然后在 `/etc/profile` 添加一下上面的环境变量(注意:替换你自己电脑的路径)。然后执行 `java -version` 命令可以查看是否安装成功! ``` zhisheng@zhisheng ~ java -version java version "1.8.0_152" Java(TM) SE Runtime Environment (build 1.8.0_152-b16) Java HotSpot(TM) 64-Bit Server VM (build 25.152-b16, mixed mode) ``` ### 2.1.2 Maven 安装与配置 安装好 JDK 后我们就可以安装 Maven 了,我们在[官网](http://maven.apache.org/download.cgi)下载二进制包就行,然后在自己本地软件安装目录解压压缩包就行。接下来你需要配置一下环境变量: ``` export M2_HOME=/Users/zhisheng/Documents/maven-3.5.2 export PATH=$PATH:$M2_HOME/bin ``` 然后执行命令 `mvn -v` 可以验证是否安装成功,结果如下: ``` zhisheng@zhisheng ~ /Users mvn -v Apache Maven 3.5.2 (138edd61fd100ec658bfa2d307c43b76940a5d7d; 2017-10-18T15:58:13+08:00) Maven home: /Users/zhisheng/Documents/maven-3.5.2 Java version: 1.8.0_152, vendor: Oracle Corporation Java home: /Library/Java/JavaVirtualMachines/jdk1.8.0_152.jdk/Contents/Home/jre Default locale: zh_CN, platform encoding: UTF-8 OS name: "mac os x", version: "10.13.5", arch: "x86_64", family: "mac" ``` ### 2.1.3 IDE 安装与配置 安装完 JDK 和 Maven 后,就可以安装 IDE 了,大家可以选择你熟练的 IDE 就行,笔者后面演示的代码都是在 IDEA 中运行的,如果想为了后面不出其他的问题的话,建议尽量和笔者的环境保持一致。 IDEA 官网下载地址:[下载页面的地址](https://www.jetbrains.com/idea/download/#section=mac),下载后可以双击后然后按照提示一步步安装,安装完成后需要在 IDEA 中配置 JDK 路径和 Maven 的路径,后面我们开发也都是靠 Maven 来管理项目的依赖。 ### 2.1.4 MySQL 安装与配置 ### 2.1.5 Kafka 安装与配置 ### 2.1.6 ElasticSearch 安装与配置 加入知识星球可以看到上面文章:https://t.zsxq.com/JyRzVnU ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-09-25-zsxq.jpg) ### 2.1.7 小结与反思 本节讲解了下 JDK、Maven、IDE、MySQL、Kafka、ElasticSearch 的安装与配置,因为这些都是后面要用的,所以这里单独抽一篇文章来讲解环境准备的安装步骤,当然这里还并不涉及全,因为后面我们还可能会涉及到 HBase、HDFS 等知识,后面我们用到再看,本书的内容主要讲解 Flink,所以更多的环境准备还是得靠大家自己独立完成。 这里笔者说下笔者自己一般安装环境的选择: xxx 下面章节我们就正式进入 Flink 专题了! ================================================ FILE: books/flink-in-action-2.2.md ================================================ --- toc: true title: 《Flink 实战与性能优化》—— Flink 环境搭建 date: 2021-07-08 tags: - Flink - 大数据 - 流式计算 --- ## 2.2 Flink 环境搭建 在 2.1 节中已经将 Flink 的准备环境已经讲完了,本章节将带大家正式开始接触 Flink,那么我们得先安装一下 Flink。Flink 是可以在多个平台(Windows、Linux、Mac)上安装的。在开始写本书的时候最新版本是 1.8 版本,但是写到一半后更新到 1.9 了(合并了大量 Blink 的新特性),所以笔者又全部更新版本到 1.9,书籍后面也都是基于最新的版本讲解与演示。 Flink 的官网地址是:[https://flink.apache.org/](https://flink.apache.org/) ### 2.2.1 Flink 下载与安装 Flink 在 Mac、Linux、Window 平台上的安装方式如下。 #### 在 Mac 和 Linux 下安装 你可以通过该地址 [https://flink.apache.org/downloads.html](https://flink.apache.org/downloads.html) 下载到最新版本的 Flink。这里我们选择 `Apache Flink 1.9.0 for Scala 2.11` 版本,点击跳转到了一个镜像下载选择的地址,如下图所示,随便选择哪个就行,只是下载速度不一致而已。 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-12-15-080110.png) 下载完后,你就可以直接解压下载的 Flink 压缩包了。接下来我们可以启动一下 Flink,我们进入到 Flink 的安装目录下执行命令 `./bin/start-cluster.sh` 即可,产生的日志如下: ``` zhisheng@zhisheng /usr/local/flink-1.9.0 ./bin/start-cluster.sh Starting cluster. Starting standalonesession daemon on host zhisheng. Starting taskexecutor daemon on host zhisheng. ``` 如果你的电脑是 Mac 的话,那么你也可以通过 Homebrew 命令进行安装。先通过命令 `brew search flink` 查找一下包: ``` zhisheng@zhisheng ~ brew search flink ==> Formulae apache-flink ✔ homebrew/linuxbrew-core/apache-flink ``` 可以发现找得到 Flink 的安装包,但是这样安装的版本可能不是最新的,如果你要安装的话,则使用命令: ``` brew install apache-flink ``` 那么它就会开始进行下载并安装好,安装后的目录应该是在 `/usr/local/Cellar/apache-flink` 下,如下图所示: ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-10-23-030606.png) 你可以通过下面命令检查安装的 Flink 到底是什么版本的: ``` flink --version ``` 结果如下: ``` Version: 1.9.0, Commit ID: ff472b4 ``` 这种的话运行是得进入 `/usr/local/Cellar/apache-flink/1.9.0/libexec/bin` 目录下执行命令 `./start-cluster.sh` 才可以启动 Flink 的。执行命令后的启动日志如下所示: ``` Starting cluster. Starting standalonesession daemon on host zhisheng. Starting taskexecutor daemon on host zhisheng. ``` #### 在 Windows 下安装 如果你的电脑系统是 Windows 的话,那么你就直接双击 Flink 安装目录下面 bin 文件夹里面的 `start-cluster.bat` 就行,同样可以将 Flink 起动成功。 ### 2.2.2 Flink 启动与运行 启动成功后的话,我们可以通过访问地址`http://localhost:8081/` 查看 UI 长啥样了,如下图所示: ### 2.2.3 Flink 目录配置文件解读 ### 2.2.4 Flink 源码下载 ### 2.2.5 Flink 源码编译 ### 2.2.6 将 Flink 源码导入到 IDE 加入知识星球可以看到上面文章:https://t.zsxq.com/JyRzVnU ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-09-25-zsxq.jpg) ### 2.2.7 小结与反思 本节主要讲了 FLink 在不同系统下的安装和运行方法,然后讲了下怎么去下载源码和将源码导入到 IDE 中。不知道你在将源码导入到 IDE 中是否有遇到什么问题呢? ================================================ FILE: books/flink-in-action-2.3.md ================================================ --- toc: true title: 《Flink 实战与性能优化》—— 案例1:WordCount 应用程序 date: 2021-07-09 tags: - Flink - 大数据 - 流式计算 --- ## 2.3 案例1:WordCount 应用程序 在 2.2 节中带大家讲解了下 Flink 的环境安装,这篇文章就开始我们的第一个 Flink 案例实战,也方便大家快速开始自己的第一个 Flink 应用。大数据里学习一门技术一般都是从 WordCount 开始入门的,那么笔者还是不打破常规了,所以这篇文章笔者也将带大家通过 WordCount 程序来初步了解 Flink。 ### 2.3.1 使用 Maven 创建项目 Flink 支持 Maven 直接构建模版项目,你在终端使用该命令: ``` mvn archetype:generate \ -DarchetypeGroupId=org.apache.flink \ -DarchetypeArtifactId=flink-quickstart-java \ -DarchetypeVersion=1.9.0 ``` 在执行的过程中它会提示你输入 groupId、artifactId、和 package 名,你按照要求输入就行,最后就可以成功创建一个项目,如下图所示。 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-10-17-151203.png) 进入到目录你就可以看到已经创建了项目,里面结构如下: ``` zhisheng@zhisheng ~/IdeaProjects/github/Flink-WordCount tree . ├── pom.xml └── src └── main ├── java │   └── com │   └── zhisheng │   ├── BatchJob.java │   └── StreamingJob.java └── resources └── log4j.properties 6 directories, 4 files ``` 该项目中包含了两个类 BatchJob 和 StreamingJob,另外还有一个 log4j.properties 配置文件,然后你就可以将该项目导入到 IDEA 了。你可以在该目录下执行 `mvn clean package` 就可以编译该项目,编译成功后在 target 目录下会生成一个 Job 的 Jar 包,但是这个 Job 还不能执行,因为 StreamingJob 这个类中的 main 方法里面只是简单的创建了 StreamExecutionEnvironment 环境,然后就执行 execute 方法,这在 Flink 中是不算一个可执行的 Job 的,因此如果你提交到 Flink UI 上也是会报错的。 如下图所示,上传作业程序打包编译的 Jar 包: ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-10-17-151434.png) 运行报错结果如下图所示: ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-10-17-152026.png) ``` Server Response Message: Internal server error. ``` 我们查看 Flink JobManager 的日志,可以看见错误信息如下图所示: ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-10-17-152954.png) ``` 2019-04-26 17:27:33,706 ERROR org.apache.flink.runtime.webmonitor.handlers.JarRunHandler - Unhandled exception. org.apache.flink.client.program.ProgramInvocationException: The main method caused an error: No operators defined in streaming topology. Cannot execute. ``` 因为 execute 方法之前我们是需要补充我们 Job 的一些算子操作的,所以报错还是很正常的,本节下面将会提供完整代码。 ### 2.3.2 使用 IDEA 创建项目 一般我们项目可能是由多个 Job 组成,并且代码也都是在同一个工程下面进行管理,上面那种创建方式适合单个 Job 执行,但如果在公司多人合作的时候还是得在同一个工程下面创建项目,每个 Flink Job 对应着一个 module,该 module 负责独立的业务逻辑,比如笔者在 GitHub 的 [https://github.com/zhisheng17/flink-learning](https://github.com/zhisheng17/flink-learning) 项目,它的项目结构如下图所示: ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-11-12-163154.png) 接下来我们需要在父工程的 pom.xml 中加入如下属性(含编码、Flink 版本、JDK 版本、Scala 版本、Maven 编译版本): ```xml UTF-8 1.9.0 1.8 2.11 ${java.version} ${java.version} ``` 然后加入依赖: ```xml org.apache.flink flink-java ${flink.version} provided org.apache.flink flink-streaming-java_${scala.binary.version} ${flink.version} provided org.slf4j slf4j-log4j12 1.7.7 runtime log4j log4j 1.2.17 runtime ``` 上面依赖中 flink-java 和 flink-streaming-java 是我们 Flink 必备的核心依赖,为什么设置 scope 为 provided 呢(默认是 compile)?是因为 Flink 其实在自己的安装目录中 lib 文件夹里的 `lib/flink-dist_2.11-1.9.0.jar` 已经包含了这些必备的 Jar 了,所以我们在给自己的 Flink Job 添加依赖的时候最后打成的 Jar 包可不希望又将这些重复的依赖打进去。有两个好处: + 减小了我们打的 Flink Job Jar 包容量大小 + 不会因为打入不同版本的 Flink 核心依赖而导致类加载冲突等问题 但是问题又来了,我们需要在 IDEA 中调试运行我们的 Job,如果将 scope 设置为 provided 的话,是会报错的: ``` Error: A JNI error has occurred, please check your installation and try again Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/flink/api/common/ExecutionConfig$GlobalJobParameters at java.lang.Class.getDeclaredMethods0(Native Method) at java.lang.Class.privateGetDeclaredMethods(Class.java:2701) at java.lang.Class.privateGetMethodRecursive(Class.java:3048) at java.lang.Class.getMethod0(Class.java:3018) at java.lang.Class.getMethod(Class.java:1784) at sun.launcher.LauncherHelper.validateMainClass(LauncherHelper.java:544) at sun.launcher.LauncherHelper.checkAndLoadMain(LauncherHelper.java:526) Caused by: java.lang.ClassNotFoundException: org.apache.flink.api.common.ExecutionConfig$GlobalJobParameters at java.net.URLClassLoader.findClass(URLClassLoader.java:381) at java.lang.ClassLoader.loadClass(ClassLoader.java:424) at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:338) at java.lang.ClassLoader.loadClass(ClassLoader.java:357) ... 7 more ``` 默认 scope 为 compile 的话,本地调试的话就不会出错了。另外测试到底能够减小多少 Jar 包的大小呢?我这里先写了个 Job 测试。当 scope 为 compile 时,编译后的 target 目录: ``` zhisheng@zhisheng ~/Flink-WordCount/target  master ●✚ ll total 94384 -rw-r--r-- 1 zhisheng staff 45M 4 26 21:23 Flink-WordCount-1.0-SNAPSHOT.jar drwxr-xr-x 4 zhisheng staff 128B 4 26 21:23 classes drwxr-xr-x 3 zhisheng staff 96B 4 26 21:23 generated-sources drwxr-xr-x 3 zhisheng staff 96B 4 26 21:23 maven-archiver drwxr-xr-x 3 zhisheng staff 96B 4 26 21:23 maven-status -rw-r--r-- 1 zhisheng staff 7.2K 4 26 21:23 original-Flink-WordCount-1.0-SNAPSHOT.jar ``` 当 scope 为 provided 时,编译后的 target 目录: ``` zhisheng@zhisheng ~/Flink-WordCount/target  master ●✚ ll total 32 -rw-r--r-- 1 zhisheng staff 7.5K 4 26 21:27 Flink-WordCount-1.0-SNAPSHOT.jar drwxr-xr-x 4 zhisheng staff 128B 4 26 21:27 classes drwxr-xr-x 3 zhisheng staff 96B 4 26 21:27 generated-sources drwxr-xr-x 3 zhisheng staff 96B 4 26 21:27 maven-archiver drwxr-xr-x 3 zhisheng staff 96B 4 26 21:27 maven-status -rw-r--r-- 1 zhisheng staff 7.2K 4 26 21:27 original-Flink-WordCount-1.0-SNAPSHOT.jar ``` 。。。 ### 2.3.3 流计算 WordCount 应用程序代码实现 ### 2.3.4 运行流计算 WordCount 应用程序 #### 本地 IDE 运行 #### UI 运行 Job ### 2.3.5 流计算 WordCount 应用程序代码分析 ### 2.3.6 小结与反思 加入知识星球可以看到上面文章:https://t.zsxq.com/Z7EAmq3 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-09-25-zsxq.jpg) ================================================ FILE: books/flink-in-action-2.4.md ================================================ --- toc: true title: 《Flink 实战与性能优化》—— 案例2:实时处理 Socket 数据 date: 2021-07-10 tags: - Flink - 大数据 - 流式计算 --- ## 2.4 案例2:实时处理 Socket 数据 在 2.3 节中讲解了 Flink 最简单的 WordCount 程序的创建、运行结果查看和代码分析,本节将继续带大家来看一个入门上手的程序:Flink 处理 Socket 数据。 ### 2.4.1 使用 IDEA 创建项目 使用 IDEA 创建新的 module,结构如下: ``` ├── pom.xml └── src ├── main │   ├── java │   │   └── com │   │   └── zhisheng │   │   └── socket │   │   └── Main.java │   └── resources │   └── log4j.properties └── test └── java ``` 项目创建好了后,我们下一步开始编写 Flink Socket Job 的代码。 ### 2.4.2 实时处理 Socket 数据应用程序代码实现 程序代码如下所示: ```java public class Main { public static void main(String[] args) throws Exception { //参数检查 if (args.length != 2) { System.err.println("USAGE:\nSocketTextStreamWordCount "); return; } String hostname = args[0]; Integer port = Integer.parseInt(args[1]); final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); //获取数据 DataStreamSource stream = env.socketTextStream(hostname, port); //计数 SingleOutputStreamOperator> sum = stream.flatMap(new LineSplitter()) .keyBy(0) .sum(1); sum.print(); env.execute("Java WordCount from SocketText"); } public static final class LineSplitter implements FlatMapFunction> { @Override public void flatMap(String s, Collector> collector) { String[] tokens = s.toLowerCase().split("\\W+"); for (String token: tokens) { if (token.length() > 0) { collector.collect(new Tuple2(token, 1)); } } } } } ``` **pom.xml** 添加 build: ```xml org.apache.maven.plugins maven-compiler-plugin 3.1 ${java.version} ${java.version} org.apache.maven.plugins maven-shade-plugin 3.0.0 package shade org.apache.flink:force-shading com.google.code.findbugs:jsr305 org.slf4j:* log4j:* *:* META-INF/*.SF META-INF/*.DSA META-INF/*.RSA com.zhisheng.socket.Main ``` ### 2.4.3 运行实时处理 Socket 数据应用程序 下面分别讲解在 IDE 和 Flink UI 上运行作业。 #### 本地 IDE 运行 #### UI 运行 Job ### 2.4.4 实时处理 Socket 数据应用程序代码分析 加入知识星球可以看到上面文章:https://t.zsxq.com/VBEQv3F ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-09-25-zsxq.jpg) ### 2.4.5 Flink 中使用 Lambda 表达式 因为 Lambda 表达式看起来简洁,所以有时候也是希望在这些 Flink 作业中也可以使用上它,虽然 Flink 中是支持 Lambda,但是个人感觉不太友好。比如上面的应用程序如果将 LineSplitter 该类之间用 Lambda 表达式完成的话则要像下面这样写: ```java stream.flatMap((s, collector) -> { for (String token : s.toLowerCase().split("\\W+")) { if (token.length() > 0) { collector.collect(new Tuple2(token, 1)); } } }) .keyBy(0) .sum(1) .print(); ``` 但是这样写完后,运行作业报错如下: ``` Exception in thread "main" org.apache.flink.api.common.functions.InvalidTypesException: The return type of function 'main(LambdaMain.java:34)' could not be determined automatically, due to type erasure. You can give type information hints by using the returns(...) method on the result of the transformation call, or by letting your function implement the 'ResultTypeQueryable' interface. at org.apache.flink.api.dag.Transformation.getOutputType(Transformation.java:417) at org.apache.flink.streaming.api.datastream.DataStream.getType(DataStream.java:175) at org.apache.flink.streaming.api.datastream.DataStream.keyBy(DataStream.java:318) at com.zhisheng.examples.streaming.socket.LambdaMain.main(LambdaMain.java:41) Caused by: org.apache.flink.api.common.functions.InvalidTypesException: The generic type parameters of 'Collector' are missing. In many cases lambda methods don't provide enough information for automatic type extraction when Java generics are involved. An easy workaround is to use an (anonymous) class instead that implements the 'org.apache.flink.api.common.functions.FlatMapFunction' interface. Otherwise the type has to be specified explicitly using type information. at org.apache.flink.api.java.typeutils.TypeExtractionUtils.validateLambdaType(TypeExtractionUtils.java:350) at org.apache.flink.api.java.typeutils.TypeExtractionUtils.extractTypeFromLambda(TypeExtractionUtils.java:176) at org.apache.flink.api.java.typeutils.TypeExtractor.getUnaryOperatorReturnType(TypeExtractor.java:571) at org.apache.flink.api.java.typeutils.TypeExtractor.getFlatMapReturnTypes(TypeExtractor.java:196) at org.apache.flink.streaming.api.datastream.DataStream.flatMap(DataStream.java:611) at com.zhisheng.examples.streaming.socket.LambdaMain.main(LambdaMain.java:34) ``` 根据上面的报错信息其实可以知道要怎么解决了,该错误是因为 Flink 在用户自定义的函数中会使用泛型来创建 serializer,当使用匿名函数时,类型信息会被保留。但 Lambda 表达式并不是匿名函数,所以 javac 编译的时候并不会把泛型保存到 class 文件里。解决方法:使用 Flink 提供的 returns 方法来指定 flatMap 的返回类型 ```java //使用 TupleTypeInfo 来指定 Tuple 的参数类型 .returns((TypeInformation) TupleTypeInfo.getBasicTupleTypeInfo(String.class, Integer.class)) ``` 在 flatMap 后面加上上面这个 returns 就行了,但是如果算子多了的话,每个都去加一个 returns,其实会很痛苦的,所以通常使用匿名函数或者自定义函数居多。 ### 2.4.5 小结与反思 在第一章中介绍了 Flink 的特性,本章主要是让大家能够快速入门,所以在第一节和第二节中分别讲解了 Flink 的环境准备和搭建,在第三节和第四节中通过两个入门的应用程序(WordCount 应用程序和读取 Socket 数据应用程序)让大家可以快速入门 Flink,两个程序都是需要自己动手实操,所以更能加深大家的印象。 ================================================ FILE: books/flink-in-action-3.1.md ================================================ --- toc: true title: 《Flink 实战与性能优化》—— Flink 中 Processing Time、Event Time、Ingestion Time 对比及其使用场景分析 date: 2021-07-11 tags: - Flink - 大数据 - 流式计算 --- # 第三章 —— Flink 中的流计算处理 通过第二章的入门案例讲解,相信你已经知道了 Flink 程序的开发过程,本章将带你熟悉 Flink 中的各种特性,比如多种时间语义、丰富的窗口机制、流计算中常见的运算操作符、Watermark 机制、丰富的 Connectors(Kafka、ElasticSearch、Redis、HBase 等) 的使用方式。除了介绍这些知识点的原理之外,笔者还将通过案例来教会大家如何去实战使用,最后还会讲解这些原理的源码实现,希望你可以更深刻的理解这些特性。 ## 3.1 Flink 多种时间语义对比 Flink 在流应用程序中支持不同的 **Time** 概念,就比如有 Processing Time、Event Time 和 Ingestion Time。下面我们一起来看看这三个 Time。 ### 3.1.1 Processing Time Processing Time 是指事件被处理时机器的系统时间。 如果我们 Flink Job 设置的时间策略是 Processing Time 的话,那么后面所有基于时间的操作(如时间窗口)都将会使用当时机器的系统时间。每小时 Processing Time 窗口将包括在系统时钟指示整个小时之间到达特定操作的所有事件。 例如,如果应用程序在上午 9:15 开始运行,则第一个每小时 Processing Time 窗口将包括在上午 9:15 到上午 10:00 之间处理的事件,下一个窗口将包括在上午 10:00 到 11:00 之间处理的事件。 Processing Time 是最简单的 "Time" 概念,不需要流和机器之间的协调,它提供了最好的性能和最低的延迟。但是,在分布式和异步的环境下,Processing Time 不能提供确定性,因为它容易受到事件到达系统的速度(例如从消息队列)、事件在系统内操作流动的速度以及中断的影响。 ### 3.1.2 Event Time Event Time 是指事件发生的时间,一般就是数据本身携带的时间。这个时间通常是在事件到达 Flink 之前就确定的,并且可以从每个事件中获取到事件时间戳。在 Event Time 中,时间取决于数据,而跟其他没什么关系。Event Time 程序必须指定如何生成 Event Time 水印,这是表示 Event Time 进度的机制。 完美的说,无论事件什么时候到达或者其怎么排序,最后处理 Event Time 将产生完全一致和确定的结果。但是,除非事件按照已知顺序(事件产生的时间顺序)到达,否则处理 Event Time 时将会因为要等待一些无序事件而产生一些延迟。由于只能等待一段有限的时间,因此就难以保证处理 Event Time 将产生完全一致和确定的结果。 假设所有数据都已到达,Event Time 操作将按照预期运行,即使在处理无序事件、延迟事件、重新处理历史数据时也会产生正确且一致的结果。 例如,每小时事件时间窗口将包含带有落入该小时的事件时间戳的所有记录,不管它们到达的顺序如何(是否按照事件产生的时间)。 ### 3.1.3 Ingestion Time Ingestion Time 是事件进入 Flink 的时间。 在数据源操作处(进入 Flink source 时),每个事件将进入 Flink 时当时的时间作为时间戳,并且基于时间的操作(如时间窗口)会利用这个时间戳。 Ingestion Time 在概念上位于 Event Time 和 Processing Time 之间。 与 Processing Time 相比,成本可能会高一点,但结果更可预测。因为 Ingestion Time 使用稳定的时间戳(只在进入 Flink 的时候分配一次),所以对事件的不同窗口操作将使用相同的时间戳(第一次分配的时间戳),而在 Processing Time 中,每个窗口操作符可以将事件分配给不同的窗口(基于机器系统时间和到达延迟)。 与 Event Time 相比,Ingestion Time 程序无法处理任何无序事件或延迟数据,但程序中不必指定如何生成水印。 在 Flink 中,Ingestion Time 与 Event Time 非常相似,唯一区别就是 Ingestion Time 具有自动分配时间戳和自动生成水印功能。 ### 3.1.4 三种 Time 的对比结果 ### 3.1.5 使用场景分析 ### 3.1.6 Time 策略设置 ### 3.1.7 小结与反思 加入知识星球可以看到上面文章:https://t.zsxq.com/znqnMNB ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-09-25-zsxq.jpg) ================================================ FILE: books/flink-in-action-3.10.md ================================================ --- toc: true title: 《Flink 实战与性能优化》—— Flink Connector —— HBase 的用法 date: 2021-07-21 tags: - Flink - 大数据 - 流式计算 --- ## 3.10 Flink Connector —— HBase 的用法 HBase 是一个分布式的、面向列的开源数据库,同样,很多公司也有使用该技术存储数据的,本节将对 HBase 做些简单的介绍,以及利用 Flink HBase Connector 读取 HBase 中的数据和写入数据到 HBase 中。 ### 3.10.1 准备环境和依赖 下面分别讲解 HBase 的环境安装、配置、常用的命令操作以及添加项目需要的依赖。 #### HBase 安装 如果是苹果系统,可以使用 HomeBrew 命令安装: ``` brew install hbase ``` HBase 最终会安装在路径 `/usr/local/Cellar/hbase/` 下面,安装版本不同,文件名也不同。 #### 配置 HBase 打开 `libexec/conf/hbase-env.sh` 修改里面的 JAVA_HOME: ``` # The java implementation to use. Java 1.7+ required. export JAVA_HOME="/Library/Java/JavaVirtualMachines/jdk1.8.0_152.jdk/Contents/Home" ``` 根据你自己的 JAVA_HOME 来配置这个变量。 打开 `libexec/conf/hbase-site.xml` 配置 HBase 文件存储目录: ```xml hbase.rootdir file:///usr/local/var/hbase hbase.zookeeper.property.clientPort 2181 hbase.zookeeper.property.dataDir /usr/local/var/zookeeper hbase.zookeeper.dns.interface lo0 hbase.regionserver.dns.interface lo0 hbase.master.dns.interface lo0 ``` #### 运行 HBase 执行启动的命令: ``` ./bin/start-hbase.sh ``` 执行后打印出来的日志如: ``` starting master, logging to /usr/local/var/log/hbase/hbase-zhisheng-master-zhisheng.out ``` #### 验证是否安装成功 使用 jps 命令: ``` zhisheng@zhisheng /usr/local/Cellar/hbase/1.2.9/libexec jps 91302 HMaster 62535 RemoteMavenServer 1100 91471 Jps ``` 出现 HMaster 说明安装运行成功。 #### 启动 HBase Shell 执行下面命令: ``` ./bin/hbase shell ``` 运行结果如下图所示: ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-05-04-035328.jpg) #### 停止 HBase 执行下面的命令: ``` ./bin/stop-hbase.sh ``` 运行结果如下图所示: ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-05-04-035513.jpg) #### HBase 常用命令 HBase 中常用的命令有:list(列出已存在的表)、create(创建表)、put(写数据)、get(读数据)、scan(读数据,读全表)、describe(显示表详情),如下图所示。 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-05-04-040821.jpg) 简单使用上诉命令的结果如下: ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-05-04-040230.jpg) #### 添加依赖 在 pom.xml 中添加 HBase 相关的依赖: ```xml org.apache.flink flink-hbase_${scala.binary.version} ${flink.version} org.apache.hadoop hadoop-common 2.7.4 ``` Flink HBase Connector 中,HBase 不仅可以作为数据源,也还可以写入数据到 HBase 中去,我们先来看看如何从 HBase 中读取数据。 ### 3.10.2 Flink 使用 TableInputFormat 读取 HBase 批量数据 这里我们使用 TableInputFormat 来读取 HBase 中的数据,首先准备数据。 #### 准备数据 先往 HBase 中插入五条数据如下: ```jshelllanguage put 'zhisheng', 'first', 'info:bar', 'hello' put 'zhisheng', 'second', 'info:bar', 'zhisheng001' put 'zhisheng', 'third', 'info:bar', 'zhisheng002' put 'zhisheng', 'four', 'info:bar', 'zhisheng003' put 'zhisheng', 'five', 'info:bar', 'zhisheng004' ``` scan 整个 `zhisheng` 表的话,有五条数据,运行结果如下图所示: ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-05-04-073344.jpg) #### Flink Job 代码 Flink 读取 HBase 数据的程序代码如下所示: ```java /** * Desc: 读取 HBase 数据 */ public class HBaseReadMain { //表名 public static final String HBASE_TABLE_NAME = "zhisheng"; // 列族 static final byte[] INFO = "info".getBytes(ConfigConstants.DEFAULT_CHARSET); //列名 static final byte[] BAR = "bar".getBytes(ConfigConstants.DEFAULT_CHARSET); public static void main(String[] args) throws Exception { ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment(); env.createInput(new TableInputFormat>() { private Tuple2 reuse = new Tuple2(); @Override protected Scan getScanner() { Scan scan = new Scan(); scan.addColumn(INFO, BAR); return scan; } @Override protected String getTableName() { return HBASE_TABLE_NAME; } @Override protected Tuple2 mapResultToTuple(Result result) { String key = Bytes.toString(result.getRow()); String val = Bytes.toString(result.getValue(INFO, BAR)); reuse.setField(key, 0); reuse.setField(val, 1); return reuse; } }).filter(new FilterFunction>() { @Override public boolean filter(Tuple2 value) throws Exception { return value.f1.startsWith("zhisheng"); } }).print(); } } ``` 上面代码中将 HBase 中的读取全部读取出来后然后过滤以 `zhisheng` 开头的 value 数据。读取结果如下图所示: ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-12-14-161125.png) 可以看到输出的结果中已经将以 `zhisheng` 开头的四条数据都打印出来了。 ### 3.10.3 Flink 使用 TableOutputFormat 向 HBase 写入数据 #### 添加依赖 #### Flink Job 代码 ### 3.10.4 Flink 使用 HBaseOutputFormat 向 HBase 实时写入数据 #### 读取数据 #### 写入数据 #### 配置文件 ### 3.10.5 项目运行及验证 ### 3.10.6 小结与反思 加入知识星球可以看到上面文章:https://t.zsxq.com/3bimqBM ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-09-25-zsxq.jpg) ================================================ FILE: books/flink-in-action-3.11.md ================================================ --- toc: true title: 《Flink 实战与性能优化》—— Flink Connector —— Redis 的用法 date: 2021-07-22 tags: - Flink - 大数据 - 流式计算 --- ## 3.11 Flink Connector —— Redis 的用法 在生产环境中,通常会将一些计算后的数据存储在 Redis 中,以供第三方的应用去 Redis 查找对应的数据,至于 Redis 的特性笔者不会在本节做过多的讲解。 ### 3.11.1 安装 Redis 首先介绍下 Redis 的的安装和启动运行。 #### 下载安装 先从 [官网](https://redis.io/download) 下载 Redis,然后解压。 ``` wget http://download.redis.io/releases/redis-5.0.4.tar.gz tar xzf redis-5.0.4.tar.gz cd redis-5.0.4 make ``` #### 通过 HomeBrew 安装 ``` brew install redis ``` 如果需要后台运行 Redis 服务,使用命令: ``` brew services start redis ``` 要运行命令,可以直接到 /usr/local/bin 目录下,有: ``` redis-server redis-cli ``` 两个命令,执行 `redis-server` 可以打开服务端,启动后结果如下图所示: ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-12-15-080554.png) 然后另外开一个终端,运行 `redis-cli` 命令可以运行客户端,执行后效果如下图所示: ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-12-15-080617.png) ### 3.11.2 将商品数据发送到 Kafka 这里我打算将从 Kafka 读取到所有到商品的信息,然后将商品信息中的 **商品ID** 和 **商品价格** 提取出来,然后写入到 Redis 中,供第三方服务根据商品 ID 查询到其对应的商品价格。 首先定义我们的商品类 (其中 id 和 price 字段是我们最后要提取的)为: ProductEvent.java ```java /** * Desc: 商品 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ @Data @Builder @AllArgsConstructor @NoArgsConstructor public class ProductEvent { /** * Product Id */ private Long id; /** * Product 类目 Id */ private Long categoryId; /** * Product 编码 */ private String code; /** * Product 店铺 Id */ private Long shopId; /** * Product 店铺 name */ private String shopName; /** * Product 品牌 Id */ private Long brandId; /** * Product 品牌 name */ private String brandName; /** * Product name */ private String name; /** * Product 图片地址 */ private String imageUrl; /** * Product 状态(1(上架),-1(下架),-2(冻结),-3(删除)) */ private int status; /** * Product 类型 */ private int type; /** * Product 标签 */ private List tags; /** * Product 价格(以分为单位) */ private Long price; } ``` 然后写个工具类不断的模拟商品数据发往 Kafka,工具类 `ProductUtil.java` 的代码如下: ```java public class ProductUtil { public static final String broker_list = "localhost:9092"; public static final String topic = "zhisheng"; //kafka topic 需要和 flink 程序用同一个 topic public static final Random random = new Random(); public static void main(String[] args) { Properties props = new Properties(); props.put("bootstrap.servers", broker_list); props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer"); props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer"); KafkaProducer producer = new KafkaProducer(props); for (int i = 1; i <= 10000; i++) { ProductEvent product = ProductEvent.builder().id((long) i) //商品的 id .name("product" + i) //商品 name .price(random.nextLong() / 10000000000000L) //商品价格(以分为单位) .code("code" + i).build(); //商品编码 ProducerRecord record = new ProducerRecord(topic, null, null, GsonUtil.toJson(product)); producer.send(record); System.out.println("发送数据: " + GsonUtil.toJson(product)); } producer.flush(); } } ``` ### 3.11.3 Flink 消费 Kafka 中的商品数据 我们需要在 Flink 中消费 Kafka 数据,然后将商品中的两个数据(商品 id 和 price)取出来。先来看下这段 Flink Job 代码: ```java public class Main { public static void main(String[] args) throws Exception { final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); ParameterTool parameterTool = ExecutionEnvUtil.PARAMETER_TOOL; Properties props = KafkaConfigUtil.buildKafkaProps(parameterTool); SingleOutputStreamOperator> product = env.addSource(new FlinkKafkaConsumer011<>( parameterTool.get(METRICS_TOPIC), //这个 kafka topic 需要和上面的工具类的 topic 一致 new SimpleStringSchema(), props)) .map(string -> GsonUtil.fromJson(string, ProductEvent.class)) //反序列化 JSON .flatMap(new FlatMapFunction>() { @Override public void flatMap(ProductEvent value, Collector> out) throws Exception { //收集商品 id 和 price 两个属性 out.collect(new Tuple2<>(value.getId().toString(), value.getPrice().toString())); } }); product.print(); env.execute("flink redis connector"); } } ``` 然后 IDEA 中启动运行 Job,再运行上面的 ProductUtil 发送 Kafka 数据的工具类(注意:也得提前启动 Kafka),运行结果如下图所示。 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-04-29-product-redult.png) 上图左半部分是工具类发送数据到 Kafka 打印的日志,右半部分是 Job 执行的结果,可以看到它已经将商品的 id 和 price 数据获取到了。 那么接下来我们需要的就是将这种 `Tuple2` 格式的 KV 数据写入到 Redis 中去。要将数据写入到 Redis 的话是需要先添加依赖的。 ### 3.11.4 Redis Connector 简介 Redis Connector 提供用于向 Redis 发送数据的接口的类。接收器可以使用三种不同的方法与不同类型的 Redis 环境进行通信: + 单 Redis 服务器 + Redis 集群 + Redis Sentinel ### 添加依赖 需要添加 Flink Redis Sink 的 Connector,这个 Redis Connector 官方只有老的版本,后面也一直没有更新,所以可以看到网上有些文章都是添加老的版本的依赖。 ```xml org.apache.flink flink-connector-redis_2.10 1.1.5 ``` 包括该部分的文档都是很早之前的啦,可以查看 [flink-docs-release-1.1 redis](https://ci.apache.org/projects/flink/flink-docs-release-1.1/apis/streaming/connectors/redis.html)。 另外在 [flink-streaming-redis](https://bahir.apache.org/docs/flink/current/flink-streaming-redis/) 也看到一个 Flink Redis Connector 的依赖。 ```xml org.apache.bahir flink-connector-redis_2.11 1.0 ``` 两个依赖功能都是一样的,我们还是就用官方的那个 Maven 依赖来进行演示。 ### 3.11.5 Flink 写入数据到 Redis ### 3.11.6 项目运行及验证 ### 3.11.7 小结与反思 加入知识星球可以看到上面文章:https://t.zsxq.com/zr76I66 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-09-25-zsxq.jpg) ================================================ FILE: books/flink-in-action-3.12.md ================================================ --- toc: true title: 《Flink 实战与性能优化》—— 使用 Side Output 分流 date: 2021-07-23 tags: - Flink - 大数据 - 流式计算 --- ## 3.12 使用 Side Output 分流 通常,在 Kafka 的 topic 中会有很多数据,这些数据虽然结构是一致的,但是类型可能不一致,举个例子:Kafka 中的监控数据有很多种:机器、容器、应用、中间件等,如果要对这些数据分别处理,就需要对这些数据流进行一个拆分,那么在 Flink 中该怎么完成这需求呢,有如下这些方法。 ### 3.12.1 使用 Filter 分流 使用 filter 算子根据数据的字段进行过滤分成机器、容器、应用、中间件等。伪代码如下: ```java DataStreamSource data = KafkaConfigUtil.buildSource(env); //从 Kafka 获取到所有的数据流 SingleOutputStreamOperator machineData = data.filter(m -> "machine".equals(m.getTags().get("type"))); //过滤出机器的数据 SingleOutputStreamOperator dockerData = data.filter(m -> "docker".equals(m.getTags().get("type"))); //过滤出容器的数据 SingleOutputStreamOperator applicationData = data.filter(m -> "application".equals(m.getTags().get("type"))); //过滤出应用的数据 SingleOutputStreamOperator middlewareData = data.filter(m -> "middleware".equals(m.getTags().get("type"))); //过滤出中间件的数据 ``` ### 3.12.2 使用 Split 分流 先在 split 算子里面定义 OutputSelector 的匿名内部构造类,然后重写 select 方法,根据数据的类型将不同的数据放到不同的 tag 里面,这样返回后的数据格式是 SplitStream,然后要使用这些数据的时候,可以通过 select 去选择对应的数据类型,伪代码如下: ```java DataStreamSource data = KafkaConfigUtil.buildSource(env); //从 Kafka 获取到所有的数据流 SplitStream splitData = data.split(new OutputSelector() { @Override public Iterable select(MetricEvent metricEvent) { List tags = new ArrayList<>(); String type = metricEvent.getTags().get("type"); switch (type) { case "machine": tags.add("machine"); break; case "docker": tags.add("docker"); break; case "application": tags.add("application"); break; case "middleware": tags.add("middleware"); break; default: break; } return tags; } }); DataStream machine = splitData.select("machine"); DataStream docker = splitData.select("docker"); DataStream application = splitData.select("application"); DataStream middleware = splitData.select("middleware"); ``` 上面这种只分流一次是没有问题的,注意如果要使用它来做连续的分流,那是有问题的,笔者曾经就遇到过这个问题,当时记录了博客 —— [Flink 从0到1学习—— Flink 不可以连续 Split(分流)?](http://www.54tianzhisheng.cn/2019/06/12/flink-split/) ,当时排查这个问题还查到两个相关的 Flink Issue。 + [FLINK-5031 Issue](https://issues.apache.org/jira/browse/FLINK-5031) + [FLINK-11084 Issue](https://issues.apache.org/jira/browse/FLINK-11084) 这两个 Issue 反映的就是连续 split 不起作用,在第二个 Issue 下面的评论就有回复说 Side Output 的功能比 split 更强大, split 会在后面的版本移除(其实在 1.7.x 版本就已经设置为过期),那么下面就来学习一下 Side Output。 ### 3.12.3 使用 Side Output 分流 要使用 Side Output 的话,你首先需要做的是定义一个 OutputTag 来标识 Side Output,代表这个 Tag 是要收集哪种类型的数据,如果是要收集多种不一样类型的数据,那么你就需要定义多种 OutputTag。要完成本节前面的需求,需要定义 4 个 OutputTag,如下: ```java //创建 output tag private static final OutputTag machineTag = new OutputTag("machine") { }; private static final OutputTag dockerTag = new OutputTag("docker") { }; private static final OutputTag applicationTag = new OutputTag("application") { }; private static final OutputTag middlewareTag = new OutputTag("middleware") { }; ``` 定义好 OutputTag 后,可以使用下面几种函数来处理数据: + ProcessFunction + KeyedProcessFunction + CoProcessFunction + ProcessWindowFunction + ProcessAllWindowFunction 在利用上面的函数处理数据的过程中,需要对数据进行判断,将不同种类型的数据存到不同的 OutputTag 中去,如下代码所示: ```java DataStreamSource data = KafkaConfigUtil.buildSource(env); //从 Kafka 获取到所有的数据流 SingleOutputStreamOperator sideOutputData = data.process(new ProcessFunction() { @Override public void processElement(MetricEvent metricEvent, Context context, Collector collector) throws Exception { String type = metricEvent.getTags().get("type"); switch (type) { case "machine": context.output(machineTag, metricEvent); case "docker": context.output(dockerTag, metricEvent); case "application": context.output(applicationTag, metricEvent); case "middleware": context.output(middlewareTag, metricEvent); default: collector.collect(metricEvent); } } }); ``` 好了,既然上面已经将不同类型的数据放到不同的 OutputTag 里面了,那么该如何去获取呢?可以使用 getSideOutput 方法来获取不同 OutputTag 的数据,比如: ```java DataStream machine = sideOutputData.getSideOutput(machineTag); DataStream docker = sideOutputData.getSideOutput(dockerTag); DataStream application = sideOutputData.getSideOutput(applicationTag); DataStream middleware = sideOutputData.getSideOutput(middlewareTag); ``` 这样你就可以获取到 Side Output 数据了,其实在 3.4 和 3.5 节就讲了 Side Output 在 Flink 中的应用(处理窗口的延迟数据),大家如果没有印象了可以再返回去复习一下。 ### 3.12.4 小结与反思 本节讲了下 Flink 中将数据分流的三种方式,完整代码的 GitHub 地址:[sideoutput](https://github.com/zhisheng17/flink-learning/tree/master/flink-learning-examples/src/main/java/com/zhisheng/examples/streaming/sideoutput) 本章全部在介绍 Flink 的技术点,比如多种时间语义的对比分析和应用场景分析、多种灵活的窗口的使用方式及其原理实现、平时开发使用较多的一些算子、深入讲解了 DataStream 中的流类型及其对应的方法实现、如何将 Watermark 与 Window 结合来处理延迟数据、Flink Connector 的使用方式。 因为 Flink 的 Connector 比较多,所以本书只挑选了些平时工作用的比较多的 Connector,比如 Kafka、ElasticSearch、HBase、Redis 等,并教会了大家如何去自定义 Source 和 Sink,这样就算 Flink 中的 Connector 没有你需要的,那么也可以通过这种自定义的方法来实现读取和写入数据。 本章讲解的内容更侧重于在 Flink DataStream 和 Connector 的使用技巧和优化,在讲解这些时还提供了详细的代码实现,目的除了大家可以参考外,其实更期望大家能够自己跟着多动手去实现和优化,这样才可以提高自己的编程水平。另外本章还介绍了自己在使用这些 Connector 时遇到的一些问题,是怎么解决的,也希望大家在工作的时候遇到问题可以静下来自己独立的思考下出现问题的原因是啥,该如何解决,多独立思考后,相信遇到问题后你也能够从容的分析和解决问题。 加入知识星球可以看到更多文章 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-09-25-zsxq.jpg) ================================================ FILE: books/flink-in-action-3.2.md ================================================ --- toc: true title: 《Flink 实战与性能优化》—— 如何使用 Flink Window 及 Window 基本概念与实现原理? date: 2021-07-12 tags: - Flink - 大数据 - 流式计算 --- ## 3.2 Flink Window 基础概念与实现原理 目前有许多数据分析的场景从批处理到流处理的演变, 虽然可以将批处理作为流处理的特殊情况来处理,但是分析无穷集的流数据通常需要思维方式的转变并且具有其自己的术语,例如,“windowing(窗口化)”、“at-least-once(至少一次)”、“exactly-once(只有一次)” 。 对于刚刚接触流处理的人来说,这种转变和新术语可能会非常混乱。 Apache Flink 是一个为生产环境而生的流处理器,具有易于使用的 API,可以用于定义高级流分析程序。Flink 的 API 在数据流上具有非常灵活的窗口定义,使其在其他开源流处理框架中脱颖而出。 在本节将讨论用于流处理的窗口的概念,介绍 Flink 的内置窗口,并解释它对自定义窗口语义的支持。 ### 3.2.1 Window 简介 下面我们结合一个现实的例子来说明。 就拿交通传感器的示例:统计经过某红绿灯的汽车数量之和? 假设在一个红绿灯处,我们每隔 15 秒统计一次通过此红绿灯的汽车数量,如下图所示: ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-10-23-064257.png) 可以把汽车的经过看成一个流,无穷的流,不断有汽车经过此红绿灯,因此无法统计总共的汽车数量。但是,我们可以换一种思路,每隔 15 秒,我们都将与上一次的结果进行 sum 操作(滑动聚合),如下图所示: ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-10-23-064320.png) 这个结果似乎还是无法回答我们的问题,根本原因在于流是无界的,我们不能限制流,但可以在有一个有界的范围内处理无界的流数据。因此,我们需要换一个问题的提法:每分钟经过某红绿灯的汽车数量之和? 这个问题,就相当于一个定义了一个 Window(窗口),Window 的界限是 1 分钟,且每分钟内的数据互不干扰,因此也可以称为翻滚(不重合)窗口,如下图所示: ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-10-23-065851.png) 第一分钟的数量为 18,第二分钟是 28,第三分钟是 24……这样,1 个小时内会有 60 个 Window。 再考虑一种情况,每 30 秒统计一次过去 1 分钟的汽车数量之和,如下图所示: ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-10-23-071008.png) 此时,Window 出现了重合。这样,1 个小时内会有 120 个 Window。 ### 3.2.2 Window 有什么作用? 通常来讲,Window 就是用来对一个无限的流设置一个有限的集合,在有界的数据集上进行操作的一种机制。Window 又可以分为基于时间(Time-based)的 Window 以及基于数量(Count-based)的 window。 ### 3.2.3 Flink 自带的 Window Flink 在 KeyedStream(DataStream 的继承类) 中提供了下面几种 Window: + 以时间驱动的 Time Window + 以事件数量驱动的 Count Window + 以会话间隔驱动的 Session Window 提供上面三种 Window 机制后,由于某些特殊的需要,DataStream API 也提供了定制化的 Window 操作,供用户自定义 Window。 下面将先围绕上面说的三种 Window 来进行分析并教大家如何使用,然后对其原理分析,最后在解析其源码实现。 ### 3.2.4 Time Window 的用法及源码分析 ### 3.2.5 Count Window 的用法及源码分析 ### 3.2.6 Session Window 的用法及源码分析 ### 3.2.7 如何自定义 Window? ### 3.2.8 Window 源码分析 ### 3.2.9 Window 组件之 WindowAssigner 的用法及源码分析 ### 3.2.10 Window 组件之 Trigger 的用法及源码分析 ### 3.2.11 Window 组件之 Evictor 的用法及源码分析 加入知识星球可以看到上面文章:https://t.zsxq.com/qnQRvrf ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-09-25-zsxq.jpg) ### 3.2.12 小结与反思 本节从生活案例来分享关于 Window 方面的需求,进而开始介绍 Window 相关的知识,并把 Flink 中常使用的三种窗口都一一做了介绍,并告诉大家如何使用,还分析了其实现原理。最后还对 Window 的内部组件做了详细的分析,为自定义 Window 提供了方法。 不知道你看完本节后对 Window 还有什么疑问吗?你们是根据什么条件来选择使用哪种 Window 的?在使用的过程中有遇到什么问题吗? ================================================ FILE: books/flink-in-action-3.3.md ================================================ --- toc: true title: 《Flink 实战与性能优化》—— Flink 数据转换必须熟悉的算子(Operator) date: 2021-07-13 tags: - Flink - 大数据 - 流式计算 --- ## 3.3 必须熟悉的数据转换 Operator(算子) 在 Flink 应用程序中,无论你的应用程序是批程序,还是流程序,都是上图这种模型,有数据源(source),有数据下游(sink),我们写的应用程序多是对数据源过来的数据做一系列操作,总结如下。 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-12-14-141653.png) 1、**Source**: 数据源,Flink 在流处理和批处理上的 source 大概有 4 类:基于本地集合的 source、基于文件的 source、基于网络套接字的 source、自定义的 source。自定义的 source 常见的有 Apache kafka、Amazon Kinesis Streams、RabbitMQ、Twitter Streaming API、Apache NiFi 等,当然你也可以定义自己的 source。 2、**Transformation**: 数据转换的各种操作,有 Map / FlatMap / Filter / KeyBy / Reduce / Fold / Aggregations / Window / WindowAll / Union / Window join / Split / Select / Project 等,操作很多,可以将数据转换计算成你想要的数据。 3、**Sink**: 接收器,Sink 是指 Flink 将转换计算后的数据发送的地点 ,你可能需要存储下来。Flink 常见的 Sink 大概有如下几类:写入文件、打印出来、写入 Socket 、自定义的 Sink 。自定义的 sink 常见的有 Apache kafka、RabbitMQ、MySQL、ElasticSearch、Apache Cassandra、Hadoop FileSystem 等,同理你也可以定义自己的 Sink。 那么本文将给大家介绍的就是 Flink 中的批和流程序常用的算子(Operator)。 ### 3.3.1 DataStream Operator 我们先来看看流程序中常用的算子。 #### Map Map 算子的输入流是 DataStream,经过 Map 算子后返回的数据格式是 SingleOutputStreamOperator 类型,获取一个元素并生成一个元素,举个例子: ```java SingleOutputStreamOperator map = employeeStream.map(new MapFunction() { @Override public Employee map(Employee employee) throws Exception { employee.salary = employee.salary + 5000; return employee; } }); map.print(); ``` 新的一年给每个员工的工资加 5000。 #### FlatMap FlatMap 算子的输入流是 DataStream,经过 FlatMap 算子后返回的数据格式是 SingleOutputStreamOperator 类型,获取一个元素并生成零个、一个或多个元素,举个例子: ```java SingleOutputStreamOperator flatMap = employeeStream.flatMap(new FlatMapFunction() { @Override public void flatMap(Employee employee, Collector out) throws Exception { if (employee.salary >= 40000) { out.collect(employee); } } }); flatMap.print(); ``` 将工资大于 40000 的找出来。 #### Filter #### KeyBy #### Reduce #### Aggregations #### Window #### WindowAll #### Union #### Window Join #### Split #### Select ### 3.3.2 DataSet Operator #### First-n ### 3.3.3 流计算与批计算统一的思路 加入知识星球可以看到上面文章:https://t.zsxq.com/iYFMZFA ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-09-25-zsxq.jpg) ### 3.3.4 小结与反思 本节介绍了在开发 Flink 作业中数据转换常使用的算子(包含流作业和批作业),DataStream API 和 DataSet API 中部分算子名字是一致的,也有不同的地方,最后讲解了下 Flink 社区后面流批统一的思路。 你们公司使用 Flink 是流作业居多还是批作业居多? ================================================ FILE: books/flink-in-action-3.4.md ================================================ --- toc: true title: 《Flink 实战与性能优化》—— 使用 DataStream API 来处理数据 date: 2021-07-14 tags: - Flink - 大数据 - 流式计算 --- ## 3.4 使用 DataStream API 来处理数据 在 3.3 节中讲了数据转换常用的 Operators(算子),然后在 3.2 节中也讲了 Flink 中窗口的概念和原理,那么我们这篇文章再来细讲一下 Flink 中的各种 DataStream API。 我们先来看下源码里面的 DataStream 大概有哪些类呢?如下图所示,展示了 1.9 版本中的 DataStream 类。 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-10-23-080701.png) 可以发现其实还是有很多的类,只有熟练掌握了这些 API,我们才能在做数据转换和计算的时候足够灵活的运用开来(知道何时该选用哪种 DataStream?选用哪个 Function?)。那么我们先从 DataStream 开始吧! ### 3.4.1 DataStream 的用法及分析 首先我们来看下 DataStream 这个类的定义吧: ```text A DataStream represents a stream of elements of the same type. A DataStreamcan be transformed into another DataStream by applying a transformation as DataStream#map or DataStream#filter} ``` 大概意思是:DataStream 表示相同类型的元素组成的数据流,一个数据流可以通过 map/filter 等算子转换成另一个数据流。 然后 DataStream 的类结构图如下图所示: ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-10-23-082134.png) 它的继承类有 KeyedStream、SingleOutputStreamOperator 和 SplitStream。这几个类本文后面都会一一给大家讲清楚。下面我们来看看 DataStream 这个类中的属性和方法吧。 它的属性就只有两个: ```java protected final StreamExecutionEnvironment environment; protected final StreamTransformation transformation; ``` 但是它的方法却有很多,并且我们平时写的 Flink Job 几乎离不开这些方法,这也注定了这个类的重要性,所以得好好看下这些方法该如何使用,以及是如何实现的。 #### union 通过合并相同数据类型的数据流,然后创建一个新的数据流,union 方法代码实现如下: ```java public final DataStream union(DataStream... streams) { List> unionedTransforms = new ArrayList<>(); unionedTransforms.add(this.transformation); for (DataStream newStream : streams) { if (!getType().equals(newStream.getType())) { //判断数据类型是否一致 throw new IllegalArgumentException("Cannot union streams of different types: " + getType() + " and " + newStream.getType()); } unionedTransforms.add(newStream.getTransformation()); } //构建新的数据流 return new DataStream<>(this.environment, new UnionTransformation<>(unionedTransforms));//通过使用 UnionTransformation 将多个 StreamTransformation 合并起来 } ``` 那么我们该如何去使用 union 呢(不止连接一个数据流,也可以连接多个数据流)? ```java //数据流 1 和 2 final DataStream stream1 = env.addSource(...); final DataStream stream2 = env.addSource(...); //union stream1.union(stream2) ``` #### split 该方法可以将两个数据流进行拆分,拆分后的数据流变成了 SplitStream(在下文会详细介绍这个类的内部实现),该 split 方法通过传入一个 OutputSelector 参数进行数据选择,方法内部实现就是构造一个 SplitStream 对象然后返回: ```java public SplitStream split(OutputSelector outputSelector) { return new SplitStream<>(this, clean(outputSelector)); } ``` 然后我们该如何使用这个方法呢? ```java dataStream.split(new OutputSelector() { private static final long serialVersionUID = 8354166915727490130L; @Override public Iterable select(Integer value) { List s = new ArrayList(); if (value > 4) { //大于 4 的数据放到 > 这个 tag 里面去 s.add(">"); } else { //小于等于 4 的数据放到 < 这个 tag 里面去 s.add("<"); } return s; } }); ``` 注意:该方法已经不推荐使用了!在 1.7 版本以后建议使用 Side Output 来实现分流操作。 #### connect 通过连接不同或相同数据类型的数据流,然后创建一个新的连接数据流,如果连接的数据流也是一个 DataStream 的话,那么连接后的数据流为 ConnectedStreams(会在下文介绍这个类的具体实现),它的具体实现如下: ```java public ConnectedStreams connect(DataStream dataStream) { return new ConnectedStreams<>(environment, this, dataStream); } ``` 如果连接的数据流是一个 BroadcastStream(广播数据流),那么连接后的数据流是一个 BroadcastConnectedStream(会在下文详细介绍该类的内部实现),它的具体实现如下: ```java public BroadcastConnectedStream connect(BroadcastStream broadcastStream) { return new BroadcastConnectedStream<>( environment, this, Preconditions.checkNotNull(broadcastStream), broadcastStream.getBroadcastStateDescriptor()); } ``` 使用如下: ```java //1、连接 DataStream DataStream> src1 = env.fromElements(new Tuple2<>(0L, 0L)); DataStream> src2 = env.fromElements(new Tuple2<>(0L, 0L)); ConnectedStreams, Tuple2> connected = src1.connect(src2); //2、连接 BroadcastStream DataStream> src1 = env.fromElements(new Tuple2<>(0L, 0L)); final BroadcastStream broadcast = srcTwo.broadcast(utterDescriptor); BroadcastConnectedStream, String> connect = src1.connect(broadcast); ``` #### keyBy keyBy 方法是用来将数据进行分组的,通过该方法可以将具有相同 key 的数据划分在一起组成新的数据流,该方法有四种(它们的参数各不一样): ```java //1、参数是 KeySelector 对象 public KeyedStream keyBy(KeySelector key) { ... return new KeyedStream<>(this, clean(key));//构造 KeyedStream 对象 } //2、参数是 KeySelector 对象和 TypeInformation 对象 public KeyedStream keyBy(KeySelector key, TypeInformation keyType) { ... return new KeyedStream<>(this, clean(key), keyType);//构造 KeyedStream 对象 } //3、参数是 1 至多个字段(用 0、1、2... 表示) public KeyedStream keyBy(int... fields) { if (getType() instanceof BasicArrayTypeInfo || getType() instanceof PrimitiveArrayTypeInfo) { return keyBy(KeySelectorUtil.getSelectorForArray(fields, getType())); } else { return keyBy(new Keys.ExpressionKeys<>(fields, getType()));//调用 private 的 keyBy 方法 } } //4、参数是 1 至多个字符串 public KeyedStream keyBy(String... fields) { return keyBy(new Keys.ExpressionKeys<>(fields, getType()));//调用 private 的 keyBy 方法 } //真正调用的方法 private KeyedStream keyBy(Keys keys) { return new KeyedStream<>(this, clean(KeySelectorUtil.getSelectorForKeys(keys, getType(), getExecutionConfig()))); } ``` 如何使用呢: ```java DataStream dataStream = env.fromElements( new Event(1, "zhisheng01", 1.0), new Event(2, "zhisheng02", 2.0), new Event(3, "zhisheng03", 2.1), new Event(3, "zhisheng04", 3.0), new SubEvent(4, "zhisheng05", 4.0, 1.0), ); //第1种 dataStream.keyBy(new KeySelector() { @Override public Integer getKey(Event value) throws Exception { return value.getId(); } }); //第2种 dataStream.keyBy(new KeySelector() { @Override public Integer getKey(Event value) throws Exception { return value.getId(); } }, Types.STRING); //第3种 dataStream.keyBy(0); //第4种 dataStream.keyBy("zhisheng01", "zhisheng02"); ``` #### partitionCustom 使用自定义分区器在指定的 key 字段上将 DataStream 分区,这个 partitionCustom 有 3 个不同参数的方法,分别要传入的参数有自定义分区 Partitioner 对象、位置、字符和 KeySelector。它们内部也都是调用了私有的 partitionCustom 方法。 #### broadcast broadcast 是将数据流进行广播,然后让下游的每个并行 Task 中都可以获取到这份数据流,通常这些数据是一些配置,一般这些配置数据的数据量不能太大,否则资源消耗会比较大。这个 broadcast 方法也有两个,一个是无参数,它返回的数据是 DataStream;另一种的参数是 MapStateDescriptor,它返回的参数是 BroadcastStream(这个也会在下文详细介绍)。 使用方法: ```java //1、第一种 DataStream> source = env.addSource(...).broadcast(); //2、第二种 final MapStateDescriptor utterDescriptor = new MapStateDescriptor<>( "broadcast-state", BasicTypeInfo.LONG_TYPE_INFO, BasicTypeInfo.STRING_TYPE_INFO ); final DataStream srcTwo = env.fromCollection(expected.values()); final BroadcastStream broadcast = srcTwo.broadcast(utterDescriptor); ``` #### map map 方法需要传入的参数是一个 MapFunction,当然传入 RichMapFunction 也是可以的,它返回的是 SingleOutputStreamOperator(这个类在会在下文详细介绍),该 map 方法里面的实现如下: ```java public SingleOutputStreamOperator map(MapFunction mapper) { TypeInformation outType = TypeExtractor.getMapReturnTypes(clean(mapper), getType(), Utils.getCallLocationName(), true); //调用 transform 方法 return transform("Map", outType, new StreamMap<>(clean(mapper))); } ``` 该方法平时使用的非常频繁,然后我们该如何使用这个方法呢: ```java dataStream.map(new MapFunction() { private static final long serialVersionUID = 1L; @Override public String map(Integer value) throws Exception { return value.toString(); } }) ``` #### flatMap flatMap 方法需要传入一个 FlatMapFunction 参数,当然传入 RichFlatMapFunction 也是可以的,如果你的 Flink Job 里面有连续的 filter 和 map 算子在一起,可以考虑使用 flatMap 一个算子来完成两个算子的工作,它返回的是 SingleOutputStreamOperator,该 flatMap 方法里面的实现如下: ```java public SingleOutputStreamOperator flatMap(FlatMapFunction flatMapper) { TypeInformation outType = TypeExtractor.getFlatMapReturnTypes(clean(flatMapper), getType(), Utils.getCallLocationName(), true); //调用 transform 方法 return transform("Flat Map", outType, new StreamFlatMap<>(clean(flatMapper))); } ``` 该方法平时使用的非常频繁,使用方式如下: ```java dataStream.flatMap(new FlatMapFunction() { @Override public void flatMap(Integer value, Collector out) throws Exception { out.collect(value); } }) ``` #### process 在输入流上应用给定的 ProcessFunction,从而创建转换后的输出流,通过该方法返回的是 SingleOutputStreamOperator,具体代码实现如下: ```java public SingleOutputStreamOperator process(ProcessFunction processFunction) { TypeInformation outType = TypeExtractor.getUnaryOperatorReturnType( processFunction, ProcessFunction.class, 0, 1, TypeExtractor.NO_INDEX, getType(), Utils.getCallLocationName(), true); //调用下面的 process 方法 return process(processFunction, outType); } public SingleOutputStreamOperator process( ProcessFunction processFunction, TypeInformation outputType) { ProcessOperator operator = new ProcessOperator<>(clean(processFunction)); //调用 transform 方法 return transform("Process", outputType, operator); } ``` 使用方法: ```java DataStreamSource data = env.generateSequence(0, 0); //定义的 ProcessFunction ProcessFunction processFunction = new ProcessFunction() { private static final long serialVersionUID = 1L; @Override public void processElement(Long value, Context ctx, Collector out) throws Exception { //具体逻辑 } @Override public void onTimer(long timestamp, OnTimerContext ctx, Collector out) throws Exception { //具体逻辑 } }; DataStream processed = data.keyBy(new IdentityKeySelector()).process(processFunction); ``` #### filter filter 用来过滤数据的,它需要传入一个 FilterFunction,然后返回的数据也是 SingleOutputStreamOperator,该方法的实现是: ```java public SingleOutputStreamOperator filter(FilterFunction filter) { return transform("Filter", getType(), new StreamFilter<>(clean(filter))); } ``` 该方法平时使用非常多: ```java DataStream filter1 = src .filter(new FilterFunction() { @Override public boolean filter(String value) throws Exception { return "zhisheng".equals(value); } }) ``` 上面这些方法是平时写代码时用的非常多的方法,我们这里讲解了它们的实现原理和使用方式,当然还有其他方法,比如 assignTimestampsAndWatermarks、join、shuffle、forward、addSink、rebalance、iterate、coGroup、project、timeWindowAll、countWindowAll、windowAll、print 等,这里由于篇幅的问题就不一一展开来讲了。 ### 3.4.2 SingleOutputStreamOperator 的用法及分析 SingleOutputStreamOperator 这个类继承自 DataStream,所以 DataStream 中有的方法在这里也都有,那么这里就讲解下额外的方法的作用,如下。 + name():该方法可以设置当前数据流的名称,如果设置了该值,则可以在 Flink UI 上看到该值;uid() 方法可以为算子设置一个指定的 ID,该 ID 有个作用就是如果想从 savepoint 恢复 Job 时是可以根据这个算子的 ID 来恢复到它之前的运行状态; + setParallelism() :该方法是为每个算子单独设置并行度的,这个设置优先于你通过 env 设置的全局并行度; + setMaxParallelism() :该为算子设置最大的并行度; + setResources():该方法有两个(参数不同),设置算子的资源,但是这两个方法对外还没开放(是私有的,暂时功能性还不全); + forceNonParallel():该方法强行将并行度和最大并行度都设置为 1; + setChainingStrategy():该方法对给定的算子设置 ChainingStrategy; + disableChaining():该这个方法设置后将禁止该算子与其他的算子 chain 在一起; + getSideOutput():该方法通过给定的 OutputTag 参数从 side output 中来筛选出对应的数据流。 ### 3.4.3 KeyedStream 的用法及分析 KeyedStream 是 DataStream 在根据 KeySelector 分区后的数据流,DataStream 中常用的方法在 KeyedStream 后也可以用(除了 shuffle、forward 和 keyBy 等分区方法),在该类中的属性分别是 KeySelector 和 TypeInformation。 DataStream 中的窗口方法只有 timeWindowAll、countWindowAll 和 windowAll 这三种全局窗口方法,但是在 KeyedStream 类中的种类就稍微多了些,新增了 timeWindow、countWindow 方法,并且是还支持滑动窗口。 除了窗口方法的新增外,还支持大量的聚合操作方法,比如 reduce、fold、sum、min、max、minBy、maxBy、aggregate 等方法(列举的这几个方法都支持多种参数的)。 最后就是它还有 asQueryableState() 方法,能够将 KeyedStream 发布为可查询的 ValueState 实例。 ### 3.4.4 SplitStream 的用法及分析 SplitStream 这个类比较简单,它代表着数据分流后的数据流了,它有一个 select 方法可以选择分流后的哪种数据流了,通常它是结合 split 使用的,对于单次分流来说还挺方便的。但是它是一个被废弃的类(Flink 1.7 后被废弃的,可以看下笔者之前写的一篇文章 [Flink 从0到1学习—— Flink 不可以连续 Split(分流)?](http://www.54tianzhisheng.cn/2019/06/12/flink-split/) ),其实可以用 side output 来代替这种 split,后面文章中我们也会讲通过简单的案例来讲解一下该如何使用 side output 做数据分流操作。 因为这个类的源码比较少,我们可以看下这个类的实现: ```java public class SplitStream extends DataStream { //构造方法 protected SplitStream(DataStream dataStream, OutputSelector outputSelector) { super(dataStream.getExecutionEnvironment(), new SplitTransformation(dataStream.getTransformation(), outputSelector)); } //选择要输出哪种数据流 public DataStream select(String... outputNames) { return selectOutput(outputNames); } //上面那个 public 方法内部调用的就是这个方法,该方法是个 private 方法,对外隐藏了它是如何去找到特定的数据流。 private DataStream selectOutput(String[] outputNames) { for (String outName : outputNames) { if (outName == null) { throw new RuntimeException("Selected names must not be null"); } } //构造了一个 SelectTransformation 对象 SelectTransformation selectTransform = new SelectTransformation(this.getTransformation(), Lists.newArrayList(outputNames)); //构造了一个 DataStream 对象 return new DataStream(this.getExecutionEnvironment(), selectTransform); } } ``` ### 3.4.5 WindowedStream 的用法及分析 虽然 WindowedStream 不是继承自 DataStream,并且我们在 3.1 节中也做了一定的讲解,但是当时没讲里面的 Function,所以在这里刚好一起做一个补充。 在 WindowedStream 类中定义的属性有 KeyedStream、WindowAssigner、Trigger、Evictor、allowedLateness 和 lateDataOutputTag。 + KeyedStream:代表着数据流,数据分组后再开 Window + WindowAssigner:Window 的组件之一 + Trigger:Window 的组件之一 + Evictor:Window 的组件之一(可选) + allowedLateness:用户指定的允许迟到时间长 + lateDataOutputTag:数据延迟到达的 Side output,如果延迟数据没有设置任何标记,则会被丢弃 在 3.1 节中我们讲了上面的三个窗口组件 WindowAssigner、Trigger、Evictor,并教大家该如何使用,那么在这篇文章我就不再重复,那么接下来就来分析下其他几个的使用方式和其实现原理。 先来看下 allowedLateness 这个它可以在窗口后指定允许迟到的时间长,使用如下: ```java dataStream.keyBy(0) .timeWindow(Time.milliseconds(20)) .allowedLateness(Time.milliseconds(2)) ``` lateDataOutputTag 这个它将延迟到达的数据发送到由给定 OutputTag 标识的 side output(侧输出),当水印经过窗口末尾(并加上了允许的延迟后),数据就被认为是延迟了。 对于 keyed windows 有五个不同参数的 reduce 方法可以使用,如下: ```java //1、参数为 ReduceFunction public SingleOutputStreamOperator reduce(ReduceFunction function) { ... return reduce(function, new PassThroughWindowFunction()); } //2、参数为 ReduceFunction 和 WindowFunction public SingleOutputStreamOperator reduce(ReduceFunction reduceFunction, WindowFunction function) { ... return reduce(reduceFunction, function, resultType); } //3、参数为 ReduceFunction、WindowFunction 和 TypeInformation public SingleOutputStreamOperator reduce(ReduceFunction reduceFunction, WindowFunction function, TypeInformation resultType) { ... return input.transform(opName, resultType, operator); } //4、参数为 ReduceFunction 和 ProcessWindowFunction public SingleOutputStreamOperator reduce(ReduceFunction reduceFunction, ProcessWindowFunction function) { ... return reduce(reduceFunction, function, resultType); } //5、参数为 ReduceFunction、ProcessWindowFunction 和 TypeInformation public SingleOutputStreamOperator reduce(ReduceFunction reduceFunction, ProcessWindowFunction function, TypeInformation resultType) { ... return input.transform(opName, resultType, operator); } ``` 除了 reduce 方法,还有六个不同参数的 fold 方法、aggregate 方法;两个不同参数的 apply 方法、process 方法(其中你会发现这两个 apply 方法和 process 方法内部其实都隐式的调用了一个私有的 apply 方法);其实除了前面说的两个不同参数的 apply 方法外,还有四个其他的 apply 方法,这四个方法也是参数不同,但是其实最终的是利用了 transform 方法;还有的就是一些预定义的聚合方法比如 sum、min、minBy、max、maxBy,它们的方法参数的个数不一致,这些预聚合的方法内部调用的其实都是私有的 aggregate 方法,该方法允许你传入一个 AggregationFunction 参数。我们来看一个具体的实现: ```java //max public SingleOutputStreamOperator max(String field) { //内部调用私有的的 aggregate 方法 return aggregate(new ComparableAggregator<>(field, input.getType(), AggregationFunction.AggregationType.MAX, false, input.getExecutionConfig())); } //私有的 aggregate 方法 private SingleOutputStreamOperator aggregate(AggregationFunction aggregator) { //继续调用的是 reduce 方法 return reduce(aggregator); } //该 reduce 方法内部其实又是调用了其他多个参数的 reduce 方法 public SingleOutputStreamOperator reduce(ReduceFunction function) { ... function = input.getExecutionEnvironment().clean(function); return reduce(function, new PassThroughWindowFunction()); } ``` 从上面的方法调用过程,你会发现代码封装的很深,得需要你自己好好跟一下源码才可以了解更深些。 上面讲了这么多方法,你会发现 reduce 方法其实是用的蛮多的之一,那么就来看看该如何使用: ```java dataStream.keyBy(0) .window(TumblingEventTimeWindows.of(Time.seconds(5))) .reduce(new ReduceFunction>() { @Override public Tuple2 reduce(Tuple2 value1, Tuple2 value2) { return value1; } }) .print(); ``` ### 3.4.6 AllWindowedStream 的用法及分析 前面讲完了 WindowedStream,再来看看这个 AllWindowedStream 你会发现它的实现其实无太大区别,该类中的属性和方法都和前面 WindowedStream 是一样的,然后我们就不再做过多的介绍,直接来看看该如何使用呢? AllWindowedStream 这种场景下是不需要让数据流做 keyBy 分组操作,直接就进行 windowAll 操作,然后在 windowAll 方法中传入 WindowAssigner 参数对象即可,然后返回的数据结果就是 AllWindowedStream 了,下面使用方式继续执行了 AllWindowedStream 中的 reduce 方法来返回数据: ```java dataStream.windowAll(SlidingEventTimeWindows.of(Time.of(1, TimeUnit.SECONDS), Time.of(100, TimeUnit.MILLISECONDS))) .reduce(new RichReduceFunction>() { private static final long serialVersionUID = -6448847205314995812L; @Override public Tuple2 reduce(Tuple2 value1, Tuple2 value2) throws Exception { return value1; } }); ``` ### 3.4.7 ConnectedStreams 的用法及分析 ConnectedStreams 这个类定义是表示(可能)两个不同数据类型的数据连接流,该场景如果对一个数据流进行操作会直接影响另一个数据流,因此可以通过流连接来共享状态。比较常见的一个例子就是一个数据流(随时间变化的规则数据流)通过连接其他的数据流,这样另一个数据流就可以利用这些连接的规则数据流。 ConnectedStreams 在概念上可以认为和 Union 数据流是一样的。 在 ConnectedStreams 类中有三个属性:environment、inputStream1 和 inputStream2,该类中的方法如下图所示: ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-10-23-082354.png) 在 ConnectedStreams 中可以通过 getFirstInput 获取连接的第一个流、通过 getSecondInput 获取连接的第二个流,同时它还含有六个 keyBy 方法来将连接后的数据流进行分组,这六个 keyBy 方法的参数各有不同。另外它还含有 map、flatMap、process 方法来处理数据(其中 map 和 flatMap 方法的参数分别使用的是 CoMapFunction 和 CoFlatMapFunction),其实如果你细看其方法里面的实现就会发现都是调用的 transform 方法。 上面讲完了 ConnectedStreams 类的基础定义,接下来我们来看下该类如何使用呢? ```java DataStream> src1 = env.fromElements(new Tuple2<>(0L, 0L)); //流 1 DataStream> src2 = env.fromElements(new Tuple2<>(0L, 0L)); //流 2 ConnectedStreams, Tuple2> connected = src1.connect(src2); //连接流 1 和流 2 //使用连接流的六种 keyBy 方法 ConnectedStreams, Tuple2> connectedGroup1 = connected.keyBy(0, 0); ConnectedStreams, Tuple2> connectedGroup2 = connected.keyBy(new int[]{0}, new int[]{0}); ConnectedStreams, Tuple2> connectedGroup3 = connected.keyBy("f0", "f0"); ConnectedStreams, Tuple2> connectedGroup4 = connected.keyBy(new String[]{"f0"}, new String[]{"f0"}); ConnectedStreams, Tuple2> connectedGroup5 = connected.keyBy(new FirstSelector(), new FirstSelector()); ConnectedStreams, Tuple2> connectedGroup5 = connected.keyBy(new FirstSelector(), new FirstSelector(), Types.STRING); //使用连接流的 map 方法 connected.map(new CoMapFunction, Tuple2, Object>() { private static final long serialVersionUID = 1L; @Override public Object map1(Tuple2 value) { return null; } @Override public Object map2(Tuple2 value) { return null; } }); //使用连接流的 flatMap 方法 connected.flatMap(new CoFlatMapFunction, Tuple2, Tuple2>() { @Override public void flatMap1(Tuple2 value, Collector> out) throws Exception {} @Override public void flatMap2(Tuple2 value, Collector> out) throws Exception {} }).name("testCoFlatMap") //使用连接流的 process 方法 connected.process(new CoProcessFunction, Tuple2, Tuple2>() { @Override public void processElement1(Tuple2 value, Context ctx, Collector> out) throws Exception { if (value.f0 < 3) { out.collect(value); ctx.output(sideOutputTag, "sideout1-" + String.valueOf(value)); } } @Override public void processElement2(Tuple2 value, Context ctx, Collector> out) throws Exception { if (value.f0 >= 3) { out.collect(value); ctx.output(sideOutputTag, "sideout2-" + String.valueOf(value)); } } }); ``` ### 3.4.8 BroadcastStream 的用法及分析 ### 3.4.9 BroadcastConnectedStream 的用法及分析 ### 3.4.10 QueryableStateStream 的用法及分析 ### 3.4.11 小结与反思 加入知识星球可以看到上面文章:https://t.zsxq.com/fy3RnMv ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-09-25-zsxq.jpg) ================================================ FILE: books/flink-in-action-3.5.md ================================================ --- toc: true title: 《Flink 实战与性能优化》—— Watermark 的用法和结合 Window 处理延迟数据 date: 2021-07-15 tags: - Flink - 大数据 - 流式计算 --- ## 3.5 Watermark 的用法和结合 Window 处理延迟数据 在 3.1 节中讲解了 Flink 中的三种 Time 和其对应的使用场景,然后在 3.2 节中深入的讲解了 Flink 中窗口的机制以及 Flink 中自带的 Window 的实现原理和使用方法。如果在进行 Window 计算操作的时候,如果使用的时间是 Processing Time,那么在 Flink 消费数据的时候,它完全不需要关心的数据本身的时间,意思也就是说不需要关心数据到底是延迟数据还是乱序数据。因为 Processing Time 只是代表数据在 Flink 被处理时的时间,这个时间是顺序的。但是如果你使用的是 Event Time 的话,那么你就不得不面临着这么个问题:事件乱序 & 事件延迟。 选择 Event Time 与 Process Time 的实际效果如下图所示: ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-12-14-142659.png) 在理想的情况下,Event Time 和 Process Time 是相等的,数据发生的时间与数据处理的时间没有延迟,但是现实却仍然这么骨感,会因为各种各样的问题(网络的抖动、设备的故障、应用的异常等原因)从而导致如图中曲线一样,Process Time 总是会与 Event Time 有一些延迟。所谓乱序,其实是指 Flink 接收到的事件的先后顺序并不是严格的按照事件的 Event Time 顺序排列的。如下图所示: ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-12-14-142742.png) 然而在有些场景下,其实是特别依赖于事件时间而不是处理时间,比如: + 错误日志的时间戳,代表着发生的错误的具体时间,开发们只有知道了这个时间戳,才能去还原那个时间点系统到底发生了什么问题,或者根据那个时间戳去关联其他的事件,找出导致问题触发的罪魁祸首 + 设备传感器或者监控系统实时上传对应时间点的设备周围的监控情况,通过监控大屏可以实时查看,不错漏重要或者可疑的事件 这种情况下,最有意义的事件发生的顺序,而不是事件到达 Flink 后被处理的顺序。庆幸的是 Flink 支持用户以事件时间来定义窗口(也支持以处理时间来定义窗口),那么这样就要去解决上面所说的两个问题。针对上面的问题(事件乱序 & 事件延迟),Flink 引入了 Watermark 机制来解决。 ### 3.5.1 Watermark 简介 举个例子: 统计 8:00 ~ 9:00 这个时间段打开淘宝 App 的用户数量,Flink 这边可以开个窗口做聚合操作,但是由于网络的抖动或者应用采集数据发送延迟等问题,于是无法保证在窗口时间结束的那一刻窗口中是否已经收集好了在 8:00 ~ 9:00 中用户打开 App 的事件数据,但又不能无限期的等下去?当基于事件时间的数据流进行窗口计算时,最为困难的一点也就是如何确定对应当前窗口的事件已经全部到达。然而实际上并不能百分百的准确判断,因此业界常用的方法就是基于已经收集的消息来估算是否还有消息未到达,这就是 Watermark 的思想。 Watermark 是一种衡量 Event Time 进展的机制,它是数据本身的一个隐藏属性,数据本身携带着对应的 Watermark。Watermark 本质来说就是一个时间戳,代表着比这时间戳早的事件已经全部到达窗口,即假设不会再有比这时间戳还小的事件到达,这个假设是触发窗口计算的基础,只有 Watermark 大于窗口对应的结束时间,窗口才会关闭和进行计算。按照这个标准去处理数据,那么如果后面还有比这时间戳更小的数据,那么就视为迟到的数据,对于这部分迟到的数据,Flink 也有相应的机制(下文会讲)去处理。 下面通过几个图来了解一下 Watermark 是如何工作的!如下图所示,数据是 Flink 从消息队列中消费的,然后在 Flink 中有个 4s 的时间窗口(根据事件时间定义的窗口),消息队列中的数据是乱序过来的,数据上的数字代表着数据本身的 timestamp,`W(4)` 和 `W(9)` 是水印。 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-07-08-154340.jpg) 经过 Flink 的消费,数据 `1`、`3`、`2` 进入了第一个窗口,然后 `7` 会进入第二个窗口,接着 `3` 依旧会进入第一个窗口,然后就有水印了,此时水印过来了,就会发现水印的 timestamp 和第一个窗口结束时间是一致的,那么它就表示在后面不会有比 `4` 还小的数据过来了,接着就会触发第一个窗口的计算操作,如下图所示。 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-07-08-154747.jpg) 那么接着后面的数据 `5` 和 `6` 会进入到第二个窗口里面,数据 `9` 会进入在第三个窗口里面,如下图所示。 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-07-08-155309.jpg) 那么当遇到水印 `9` 时,发现水印比第二个窗口的结束时间 `8` 还大,所以第二个窗口也会触发进行计算,然后以此继续类推下去,如下图所示。 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-07-08-155558.jpg) 相信看完上面几个图的讲解,你已经知道了 Watermark 的工作原理是啥了,那么在 Flink 中该如何去配置水印呢,下面一起来看看。 ### 3.5.2 Flink 中的 Watermark 的设置 在 Flink 中,数据处理中需要通过调用 DataStream 中的 assignTimestampsAndWatermarks 方法来分配时间和水印,该方法可以传入两种参数,一个是 AssignerWithPeriodicWatermarks,另一个是 AssignerWithPunctuatedWatermarks。 ```java public SingleOutputStreamOperator assignTimestampsAndWatermarks(AssignerWithPeriodicWatermarks timestampAndWatermarkAssigner) { final int inputParallelism = getTransformation().getParallelism(); final AssignerWithPeriodicWatermarks cleanedAssigner = clean(timestampAndWatermarkAssigner); TimestampsAndPeriodicWatermarksOperator operator = new TimestampsAndPeriodicWatermarksOperator<>(cleanedAssigner); return transform("Timestamps/Watermarks", getTransformation().getOutputType(), operator).setParallelism(inputParallelism); } public SingleOutputStreamOperator assignTimestampsAndWatermarks(AssignerWithPunctuatedWatermarks timestampAndWatermarkAssigner) { final int inputParallelism = getTransformation().getParallelism(); final AssignerWithPunctuatedWatermarks cleanedAssigner = clean(timestampAndWatermarkAssigner); TimestampsAndPunctuatedWatermarksOperator operator = new TimestampsAndPunctuatedWatermarksOperator<>(cleanedAssigner); return transform("Timestamps/Watermarks", getTransformation().getOutputType(), operator).setParallelism(inputParallelism); } ``` 所以设置 Watermark 是有如下两种方式: + AssignerWithPunctuatedWatermarks:数据流中每一个递增的 EventTime 都会产生一个 Watermark。 在实际的生产环境中,在 TPS 很高的情况下会产生大量的 Watermark,可能在一定程度上会对下游算子造成一定的压力,所以只有在实时性要求非常高的场景才会选择这种方式来进行水印的生成。 + AssignerWithPeriodicWatermarks:周期性的(一定时间间隔或者达到一定的记录条数)产生一个 Watermark。 在实际的生产环境中,通常这种使用较多,它会周期性产生 Watermark 的方式,但是必须结合时间或者积累条数两个维度,否则在极端情况下会有很大的延时,所以 Watermark 的生成方式需要根据业务场景的不同进行不同的选择。 下面再分别详细讲下这两种的实现方式。 ### 3.5.3 Punctuated Watermark AssignerWithPunctuatedWatermarks 接口中包含了 checkAndGetNextWatermark 方法,这个方法会在每次 extractTimestamp() 方法被调用后调用,它可以决定是否要生成一个新的水印,返回的水印只有在不为 null 并且时间戳要大于先前返回的水印时间戳的时候才会发送出去,如果返回的水印是 null 或者返回的水印时间戳比之前的小则不会生成新的水印。 那么该怎么利用这个来定义水印生成器呢? ```java public class WordPunctuatedWatermark implements AssignerWithPunctuatedWatermarks { @Nullable @Override public Watermark checkAndGetNextWatermark(Word lastElement, long extractedTimestamp) { return extractedTimestamp % 3 == 0 ? new Watermark(extractedTimestamp) : null; } @Override public long extractTimestamp(Word element, long previousElementTimestamp) { return element.getTimestamp(); } } ``` 需要注意的是这种情况下可以为每个事件都生成一个水印,但是因为水印是要在下游参与计算的,所以过多的话会导致整体计算性能下降。 ### 3.5.4 Periodic Watermark 通常在生产环境中使用 AssignerWithPeriodicWatermarks 来定期分配时间戳并生成水印比较多,那么先来讲下这个该如何使用。 ```java public class WordPeriodicWatermark implements AssignerWithPeriodicWatermarks { private long currentTimestamp = Long.MIN_VALUE; @Override public long extractTimestamp(Word word, long previousElementTimestamp) { long timestamp = word.getTimestamp(); currentTimestamp = Math.max(timestamp, currentTimestamp); return word.getTimestamp(); } @Nullable @Override public Watermark getCurrentWatermark() { long maxTimeLag = 5000; return new Watermark(currentTimestamp == Long.MIN_VALUE ? Long.MIN_VALUE : currentTimestamp - maxTimeLag); } } ``` 上面的是我根据 Word 数据自定义的水印周期性生成器,在这个类中,有两个方法 extractTimestamp() 和 getCurrentWatermark()。extractTimestamp() 方法是从数据本身中提取 Event Time,然后将当前时间戳与事件时间进行比较,取最大值后赋值给当前时间戳 currentTimestamp,然后返回事件时间。getCurrentWatermark() 方法是获取当前的水位线,通过 `currentTimestamp - maxTimeLag` 得到水印的值,这里有个 maxTimeLag 参数代表数据能够延迟的时间,上面代码中定义的 `long maxTimeLag = 5000;` 表示最大允许数据延迟时间为 5s,超过 5s 的话如果还来了之前早的数据,那么 Flink 就会丢弃了,因为 Flink 的窗口中的数据是要触发的,不可能一直在等着这些迟到的数据(由于网络的问题数据可能一直没发上来)而不让窗口触发结束进行计算操作。 通过定义这个时间,可以避免部分数据因为网络或者其他的问题导致不能够及时上传从而不把这些事件数据作为计算的,那么如果在这延迟之后还有更早的数据到来的话,那么 Flink 就会丢弃了,所以合理的设置这个允许延迟的时间也是一门细活,得观察生产环境数据的采集到消息队列再到 Flink 整个流程是否会出现延迟,统计平均延迟大概会在什么范围内波动。这也就是说明了一个事实那就是 Flink 中设计这个水印的根本目的是来解决部分数据乱序或者数据延迟的问题,而不能真正做到彻底解决这个问题,不过这一特性在相比于其他的流处理框架已经算是非常给力了。 AssignerWithPeriodicWatermarks 这个接口有四个实现类,如下图所示: ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-10-23-082804.png) 这四个实现类的功能和使用方式如下: + BoundedOutOfOrdernessTimestampExtractor:该类用来发出滞后于数据时间的水印,它的目的其实就是和我们上面定义的那个类作用是类似的,你可以传入一个时间代表着可以允许数据延迟到来的时间是多长。该类内部实现如下图所示: ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-10-23-083043.png) 你可以像下面一样使用该类来分配时间和生成水印: ```java //Time.seconds(10) 代表允许延迟的时间大小 dataStream.assignTimestampsAndWatermarks(new BoundedOutOfOrdernessTimestampExtractor(Time.seconds(10)) { //重写 BoundedOutOfOrdernessTimestampExtractor 中的 extractTimestamp()抽象方法 @Override public long extractTimestamp(Event event) { return event.getTimestamp(); } }) ``` + CustomWatermarkExtractor:这是一个自定义的周期性生成水印的类,在这个类里面的数据是 KafkaEvent。 + AscendingTimestampExtractor:时间戳分配器和水印生成器,用于时间戳单调递增的数据流,如果数据流的时间戳不是单调递增,那么会有专门的处理方法,代码如下: ```java public final long extractTimestamp(T element, long elementPrevTimestamp) { final long newTimestamp = extractAscendingTimestamp(element); if (newTimestamp >= this.currentTimestamp) { this.currentTimestamp = ne∏wTimestamp; return newTimestamp; } else { violationHandler.handleViolation(newTimestamp, this.currentTimestamp); return newTimestamp; } } ``` + IngestionTimeExtractor:依赖于机器系统时间,它在 extractTimestamp 和 getCurrentWatermark 方法中是根据 `System.currentTimeMillis()` 来获取时间的,而不是根据事件的时间,如果这个时间分配器是在数据源进 Flink 后分配的,那么这个时间就和 Ingestion Time 一致了,所以命名也取的就是叫 IngestionTimeExtractor。 **注意**: 1、使用这种方式周期性生成水印的话,你可以通过 `env.getConfig().setAutoWatermarkInterval(...);` 来设置生成水印的间隔(每隔 n 毫秒)。 2、通常建议在数据源(source)之后就进行生成水印,或者做些简单操作比如 filter/map/flatMap 之后再生成水印,越早生成水印的效果会更好,也可以直接在数据源头就做生成水印。比如你可以在 source 源头类中的 run() 方法里面这样定义 ```java @Override public void run(SourceContext ctx) throws Exception { while (/* condition */) { MyType next = getNext(); ctx.collectWithTimestamp(next, next.getEventTimestamp()); if (next.hasWatermarkTime()) { ctx.emitWatermark(new Watermark(next.getWatermarkTime())); } } } ``` ### 3.5.5 每个 Kafka 分区的时间戳 ### 3.5.6 将 Watermark 与 Window 结合起来处理延迟数据 ### 3.5.7 处理延迟数据的三种方法 #### 丢弃(默认) #### allowedLateness 再次指定允许数据延迟的时间 #### sideOutputLateData 收集迟到的数据 ### 3.5.8 小结与反思 加入知识星球可以看到上面文章:https://t.zsxq.com/RbufeIA ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-09-25-zsxq.jpg) ================================================ FILE: books/flink-in-action-3.6.md ================================================ --- toc: true title: 《Flink 实战与性能优化》—— Flink 常用的 Source Connector 和 Sink Connector 介绍 date: 2021-07-16 tags: - Flink - 大数据 - 流式计算 --- ## 3.6 Flink 常用的 Source Connector 和 Sink Connector 介绍 通过前面我们可以知道 Flink Job 的大致结构就是 `Source ——> Transformation ——> Sink`。 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-12-14-141653.png) 那么这个 Source 是什么意思呢?我们下面来看看。 ### 3.6.1 Data Source 简介 Data Source 是什么呢?就字面意思其实就可以知道:数据来源。 Flink 做为一款流式计算框架,它可用来做批处理,即处理静态的数据集、历史的数据集;也可以用来做流处理,即处理实时的数据流(做计算操作),然后将处理后的数据实时下发,只要数据源源不断过来,Flink 就能够一直计算下去。 Flink 中你可以使用 `StreamExecutionEnvironment.addSource(sourceFunction)` 来为你的程序添加数据来源。 Flink 已经提供了若干实现好了的 source function,当然你也可以通过实现 SourceFunction 来自定义非并行的 source 或者实现 ParallelSourceFunction 接口或者扩展 RichParallelSourceFunction 来自定义并行的 source。 那么常用的 Data Source 有哪些呢? ### 3.6.2 常用的 Data Source StreamExecutionEnvironment 中可以使用如下图所示的这些已实现的 Stream Source。 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-10-23-083744.png) 总的来说可以分为集合、文件、Socket、自定义四大类。 #### 基于集合 基于集合的有下面五种方法: 1、fromCollection(Collection) - 从 Java 的 Java.util.Collection 创建数据流。集合中的所有元素类型必须相同。 2、fromCollection(Iterator, Class) - 从一个迭代器中创建数据流。Class 指定了该迭代器返回元素的类型。 3、fromElements(T ...) - 从给定的对象序列中创建数据流。所有对象类型必须相同。 ```java StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); DataStream input = env.fromElements( new Event(1, "barfoo", 1.0), new Event(2, "start", 2.0), new Event(3, "foobar", 3.0), ... ); ``` 4、fromParallelCollection(SplittableIterator, Class) - 从一个迭代器中创建并行数据流。Class 指定了该迭代器返回元素的类型。 5、generateSequence(from, to) - 创建一个生成指定区间范围内的数字序列的并行数据流。 #### 基于文件 基于文件的有下面三种方法: 1、readTextFile(path) - 读取文本文件,即符合 TextInputFormat 规范的文件,并将其作为字符串返回。 ```Java final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); DataStream text = env.readTextFile("file:///path/to/file"); ``` 2、readFile(fileInputFormat, path) - 根据指定的文件输入格式读取文件(一次)。 3、readFile(fileInputFormat, path, watchType, interval, pathFilter, typeInfo) - 这是上面两个方法内部调用的方法。它根据给定的 fileInputFormat 和读取路径读取文件。根据提供的 watchType,这个 source 可以定期(每隔 interval 毫秒)监测给定路径的新数据(FileProcessingMode.PROCESS_CONTINUOUSLY),或者处理一次路径对应文件的数据并退出(FileProcessingMode.PROCESS_ONCE)。你可以通过 pathFilter 进一步排除掉需要处理的文件。 ```Java final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); DataStream stream = env.readFile( myFormat, myFilePath, FileProcessingMode.PROCESS_CONTINUOUSLY, 100, FilePathFilter.createDefaultFilter(), typeInfo); ``` **实现:** 在具体实现上,Flink 把文件读取过程分为两个子任务,即目录监控和数据读取。每个子任务都由单独的实体实现。目录监控由单个非并行(并行度为1)的任务执行,而数据读取由并行运行的多个任务执行。后者的并行性等于作业的并行性。单个目录监控任务的作用是扫描目录(根据 watchType 定期扫描或仅扫描一次),查找要处理的文件并把文件分割成切分片(splits),然后将这些切分片分配给下游 reader。reader 负责读取数据。每个切分片只能由一个 reader 读取,但一个 reader 可以逐个读取多个切分片。 **重要注意:** 如果 watchType 设置为 FileProcessingMode.PROCESS_CONTINUOUSLY,则当文件被修改时,其内容将被重新处理。这会打破“exactly-once”语义,因为在文件末尾附加数据将导致其所有内容被重新处理。 如果 watchType 设置为 FileProcessingMode.PROCESS_ONCE,则 source 仅扫描路径一次然后退出,而不等待 reader 完成文件内容的读取。当然 reader 会继续阅读,直到读取所有的文件内容。关闭 source 后就不会再有检查点。这可能导致节点故障后的恢复速度较慢,因为该作业将从最后一个检查点恢复读取。 #### 基于 Socket socketTextStream(String hostname, int port) - 从 socket 读取。元素可以用分隔符切分。 ```java StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); DataStream> dataStream = env .socketTextStream("localhost", 9999) // 监听 localhost 的 9999 端口过来的数据 .flatMap(new Splitter()) .keyBy(0) .timeWindow(Time.seconds(5)) .sum(1); ``` #### 自定义 addSource - 添加一个新的 source function。例如,你可以用 addSource(new FlinkKafkaConsumer011<>(...)) 从 Apache Kafka 读取数据。 **说说上面几种的特点** 1、基于集合:有界数据集,更偏向于本地测试用 2、基于文件:适合监听文件修改并读取其内容 3、基于 Socket:监听主机的 host port,从 Socket 中获取数据 4、自定义 addSource:大多数的场景数据都是无界的,会源源不断过来。比如去消费 Kafka 某个 topic 上的数据,这时候就需要用到这个 addSource,可能因为用的比较多的原因吧,Flink 直接提供了 FlinkKafkaConsumer011 等类可供你直接使用。你可以去看看 FlinkKafkaConsumerBase 这个基础类,它是 Flink Kafka 消费的最根本的类。 ```java StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); DataStream input = env .addSource( new FlinkKafkaConsumer011<>( parameterTool.getRequired("input-topic"), //从参数中获取传进来的 topic new KafkaEventSchema(), parameterTool.getProperties()) .assignTimestampsAndWatermarks(new CustomWatermarkExtractor())); ``` Flink 目前支持的 Source 如下图所示: ![](https://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/images/UTfWCZ.jpg) 如果你想自定义自己的 Source 呢?在后面 3.8 节会讲解。 ### 3.6.3 Data Sink 简介 ### 3.6.4 常用的 Data Sink ### 3.6.5 小结与反思 加入知识星球可以看到上面文章:https://t.zsxq.com/FAayJU3 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-09-25-zsxq.jpg) ================================================ FILE: books/flink-in-action-3.7.md ================================================ --- toc: true title: 《Flink 实战与性能优化》—— Flink Connector —— Kafka 的使用和源码分析 date: 2021-07-17 tags: - Flink - 大数据 - 流式计算 --- ## 3.7 Flink Connector —— Kafka 的使用和源码分析 在前面 3.6 节中介绍了 Flink 中的 Data Source 和 Data Sink,然后还讲诉了自带的一些 Source 和 Sink 的 Connector。本篇文章将讲解一下用的最多的 Connector —— Kafka,带大家利用 Kafka Connector 读取 Kafka 数据,做一些计算操作后然后又通过 Kafka Connector 写入到 kafka 消息队列去,整个案例的执行流程如下图所示。 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-10-23-101054.png) ### 3.7.1 准备环境和依赖 接下来准备 Kafka 环境的安装和添加相关的依赖。 #### 环境安装和启动 如果你已经安装好了 Flink 和 Kafka,那么接下来使用命令运行启动 Flink、Zookepeer、Kafka 就行了。 启动 Flink 的命令如下图所示: ![启动 Flink](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-10-11-042714.png) 启动 Kafka 的命令如下图所示: ![启动 Kafka](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-10-11-142523.png) 执行命令都启动好了后就可以添加依赖了。 #### 添加 Maven 依赖 Flink 里面支持 Kafka 0.8.x 以上的版本,具体采用哪个版本的 Maven 依赖需要根据安装的 Kafka 版本来确定。因为之前我们安装的 Kafka 是 1.1.0 版本,所以这里我们选择的 Kafka Connector 为 `flink-connector-kafka-0.11_2.11` (支持 Kafka 0.11.x 版本及以上,该 Connector 支持 Kafka 事务消息传递,所以能保证 Exactly Once)。Flink Kafka Connector 支持的版本如下图所示: ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-10-11-043040.png) 添加如下依赖: ```xml org.apache.flink flink-connector-kafka-0.11_2.11 ${flink.version} ``` Flink、Kafka、Flink Kafka Connector 三者对应的版本可以根据 [官网](https://ci.apache.org/projects/flink/flink-docs-stable/dev/connectors/kafka.html) 的对比来选择。需要注意的是 `flink-connector-kafka_2.11` 这个版本支持的 Kafka 版本要大于 1.0.0,从 Flink 1.9 版本开始,它使用的是 Kafka 2.2.0 版本的客户端,虽然这些客户端会做向后兼容,但是建议还是按照官网约定的来规范使用 Connector 版本。另外你还要添加的依赖有: ```xml org.apache.flink flink-java ${flink.version} provided org.apache.flink flink-streaming-java_${scala.binary.version} ${flink.version} provided org.slf4j slf4j-log4j12 1.7.7 runtime log4j log4j 1.2.17 runtime com.alibaba fastjson 1.2.51 ``` ### 3.7.2 将测试数据发送到 Kafka Topic 我们模拟一些测试数据,然后将这些测试数据发到 Kafka Topic 中去,数据的结构如下: ```java @Data @AllArgsConstructor @NoArgsConstructor public class Metric { public String name; //指标名 public long timestamp; //时间戳 public Map fields; //指标含有的属性 public Map tags; //指标的标识 } ``` 往 kafka 中写数据工具类 `KafkaUtils.java`,代码如下: ```java /** * 往kafka中写数据,可以使用这个main函数进行测试一下 */ public class KafkaUtils { public static final String broker_list = "localhost:9092"; public static final String topic = "metric"; // kafka topic,Flink 程序中需要和这个统一 public static void writeToKafka() throws InterruptedException { Properties props = new Properties(); props.put("bootstrap.servers", broker_list); props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer"); //key 序列化 props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer"); //value 序列化 KafkaProducer producer = new KafkaProducer(props); Metric metric = new Metric(); metric.setTimestamp(System.currentTimeMillis()); metric.setName("mem"); Map tags = new HashMap<>(); Map fields = new HashMap<>(); tags.put("cluster", "zhisheng"); tags.put("host_ip", "101.147.022.106"); fields.put("used_percent", 90d); fields.put("max", 27244873d); fields.put("used", 17244873d); fields.put("init", 27244873d); metric.setTags(tags); metric.setFields(fields); ProducerRecord record = new ProducerRecord(topic, null, null, JSON.toJSONString(metric)); producer.send(record); System.out.println("发送数据: " + JSON.toJSONString(metric)); producer.flush(); } public static void main(String[] args) throws InterruptedException { while (true) { Thread.sleep(300); writeToKafka(); } } } ``` 运行结果如下图所示: ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-10-23-101504.png) 如果出现如上图标记的,即代表能够不断往 kafka 发送数据的。 ### 3.7.3 Flink 如何消费 Kafka 数据? Flink 消费 Kafka 数据的应用程序如下: ```java public class Main { public static void main(String[] args) throws Exception { final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); Properties props = new Properties(); props.put("bootstrap.servers", "localhost:9092"); props.put("zookeeper.connect", "localhost:2181"); props.put("group.id", "metric-group"); props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer"); //key 反序列化 props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer"); props.put("auto.offset.reset", "latest"); //value 反序列化 DataStreamSource dataStreamSource = env.addSource(new FlinkKafkaConsumer011<>( "metric", //kafka topic new SimpleStringSchema(), // String 序列化 props)).setParallelism(1); dataStreamSource.print(); //把从 kafka 读取到的数据打印在控制台 env.execute("Flink add data source"); } } ``` 运行结果如下图所示(程序可以不断的消费到 Kafka Topic 中的数据): ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-10-23-101832.png) **代码分析** 使用 FlinkKafkaConsumer011 时传入了三个参数: + Kafka topic:这个代表了 Flink 要消费的是 Kafka 哪个 Topic,如果你要同时消费多个 Topic 的话,那么你可以传入一个 Topic List 进去,另外也支持正则表达式匹配 Topic,源码如下图所示。 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-10-23-102157.png) + 序列化:上面代码我们使用的是 SimpleStringSchema。 + 配置属性:将 Kafka 等的一些配置传入 。 前面演示了 Flink 如何消费 Kafak 数据,接下来演示如何把其他 Kafka 集群中 topic 数据原样写入到自己本地起的 Kafka 中去。 ### 3.7.4 Flink 如何将计算后的数据发送到 Kafka? 将 Kafka 集群中 topic 数据写入本地 Kafka 的程序中要填写的配置有消费的 Kafka 集群地址、group.id、将数据写入 Kafka 的集群地址、topic 信息等,将所有的配置提取到配置文件中,如下所示。 ```properties //其他 Kafka 集群配置 kafka.brokers=xxx:9092,xxx:9092,xxx:9092 kafka.group.id=metrics-group-test kafka.zookeeper.connect=xxx:2181 metrics.topic=xxx stream.parallelism=5 kafka.sink.brokers=localhost:9092 kafka.sink.topic=metric-test stream.checkpoint.interval=1000 stream.checkpoint.enable=false stream.sink.parallelism=5 ``` 目前我们先看下本地 Kafka 是否有这个 metric-test topic 呢?需要执行下这个命令: ``` bin/kafka-topics.sh --list --zookeeper localhost:2181 ``` 执行上面命令后的结果如下图所示: ![](https://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/images/6KFHKT.jpg) 可以看到本地的 Kafka 是没有任何 topic 的,如果等下程序运行起来后,再次执行这个命令出现 metric-test topic,那么证明程序确实起作用了,已经将其他集群的 Kafka 数据写入到本地 Kafka 了。 整个 Flink 程序的代码如下: ```java public class Main { public static void main(String[] args) throws Exception{ final ParameterTool parameterTool = ExecutionEnvUtil.createParameterTool(args); StreamExecutionEnvironment env = ExecutionEnvUtil.prepare(parameterTool); DataStreamSource data = KafkaConfigUtil.buildSource(env); data.addSink(new FlinkKafkaProducer011( parameterTool.get("kafka.sink.brokers"), parameterTool.get("kafka.sink.topic"), new MetricSchema() )).name("flink-connectors-kafka") .setParallelism(parameterTool.getInt("stream.sink.parallelism")); env.execute("flink learning connectors kafka"); } } ``` 启动程序,查看运行结果,不断执行查看 topic 列表的命令,观察是否有新的 topic 出来,结果如下图所示: ![](https://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/images/nxqZmZ.jpg) 执行命令可以查看该 topic 的信息: ``` bin/kafka-topics.sh --describe --zookeeper localhost:2181 --topic metric-test ``` 该 topic 信息如下图所示: ![](https://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/images/y5vPRR.jpg) 前面代码使用的 FlinkKafkaProducer011 只传了三个参数:brokerList、topicId、serializationSchema(序列化),其实是支持传入多个参数的,Flink 中的源码如下图所示。 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-10-23-102620.png) ### 3.7.5 FlinkKafkaConsumer 源码分析 ### 3.7.6 FlinkKafkaProducer 源码分析 ### 3.7.7 使用 Flink-connector-kafka 可能会遇到的问题 #### 如何消费多个 Kafka Topic #### 想要获取数据的元数据信息 #### 多种数据类型 #### 序列化失败 #### Kafka 消费 Offset 的选择 #### 如何自动发现 Topic 新增的分区并读取数据 #### 程序消费 Kafka 的 offset 是如何管理的 ### 3.7.8 小结与反思 加入知识星球可以看到上面文章:https://t.zsxq.com/2bmurFy ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-09-25-zsxq.jpg) ================================================ FILE: books/flink-in-action-3.8.md ================================================ --- toc: true title: 《Flink 实战与性能优化》—— 自定义 Flink Connector date: 2021-07-19 tags: - Flink - 大数据 - 流式计算 --- ## 3.8 自定义 Flink Connector 在前面文章 3.6 节中讲解了 Flink 中的 Data Source 和 Data Sink,然后介绍了 Flink 中自带的一些 Source 和 Sink 的 Connector,接着我们还有几篇实战会讲解了如何从 Kafka 处理数据写入到 Kafka、ElasticSearch 等,当然 Flink 还有一些其他的 Connector,我们这里就不一一介绍了,大家如果感兴趣的话可以去官网查看一下,如果对其代码实现比较感兴趣的话,也可以去看看其源码的实现。我们这篇文章来讲解一下如何自定义 Source 和 Sink Connector?这样我们后面再遇到什么样的需求都难不倒我们了。 ### 3.8.1 如何自定义 Source Connector? 这里就演示一下如何自定义 Source 从 MySQL 中读取数据。 **添加依赖** 在 pom.xml 中添加 MySQL 依赖: ```xml mysql mysql-connector-java 5.1.34 ``` **数据库建表** 数据库建表的 SQL 语句如下: ```sql DROP TABLE IF EXISTS `student`; CREATE TABLE `student` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(25) COLLATE utf8_bin DEFAULT NULL, `password` varchar(25) COLLATE utf8_bin DEFAULT NULL, `age` int(10) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8 COLLATE=utf8_bin; ``` **数据库插入数据** 往新建的数据库表中插入 4 条数据的 SQL 语句如下: ```sql INSERT INTO `student` VALUES ('1', 'zhisheng01', '123456', '18'), ('2', 'zhisheng02', '123', '17'), ('3', 'zhisheng03', '1234', '18'), ('4', 'zhisheng04', '12345', '16'); COMMIT; ``` **新建实体类** 对应数据库字段的实体类如下: ```java @Data @AllArgsConstructor @NoArgsConstructor public class Student { public int id; //id public String name; //姓名 public String password; //密码 public int age; //年龄 } ``` **自定义 Source 类** SourceFromMySQL 是自定义的 Source 类,该类继承 RichSourceFunction ,实现里面的 open、close、run、cancel 方法,它的作用是读取 MySQL 中的数据,代码如下所示。 ```java public class SourceFromMySQL extends RichSourceFunction { PreparedStatement ps; private Connection connection; /** * open() 方法中建立连接,这样不用每次 invoke 的时候都要建立连接和释放连接。 * * @param parameters * @throws Exception */ @Override public void open(Configuration parameters) throws Exception { super.open(parameters); connection = getConnection(); String sql = "select * from Student;"; ps = this.connection.prepareStatement(sql); } /** * 程序执行完毕就可以进行,关闭连接和释放资源的动作了 * * @throws Exception */ @Override public void close() throws Exception { super.close(); if (connection != null) { //关闭连接和释放资源 connection.close(); } if (ps != null) { ps.close(); } } /** * DataStream 调用一次 run() 方法用来获取数据 * * @param ctx * @throws Exception */ @Override public void run(SourceContext ctx) throws Exception { ResultSet resultSet = ps.executeQuery(); while (resultSet.next()) { Student student = new Student( resultSet.getInt("id"), resultSet.getString("name").trim(), resultSet.getString("password").trim(), resultSet.getInt("age")); ctx.collect(student); } } @Override public void cancel() { } private static Connection getConnection() { Connection con = null; try { Class.forName("com.mysql.jdbc.Driver"); con = DriverManager.getConnection("jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8", "root", "123456"); } catch (Exception e) { System.out.println("mysql get connection has exception , msg = " + e.getMessage()); } return con; } } ``` **Flink 应用程序代码** 读取 MySQL 数据的代码完成后,接下来 Flink 主程序的代码就可以直接在 `addSource()` 方法中构造一个 SourceFromMySQL 对象作为一个参数传入,具体代码如下所示。 ```java public class Main2 { public static void main(String[] args) throws Exception { final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.addSource(new SourceFromMySQL()).print(); env.execute("Flink add data sourc"); } } ``` 运行 Flink 程序,控制台日志中可以看见打印的 student 信息,结果如下图所示。 ![](https://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/images/cY9WwK.jpg) ### 3.8.2 RichSourceFunction 的用法及源码分析 从上面自定义的 Source 可以看到我们继承的就是这个 RichSourceFunction 类,其实也是可以使用 SourceFunction 函数来自定义 Source。 RichSourceFunction 函数比 SourceFunction 多了 open 方法(可以用来初始化)和获取应用上下文的方法,那么来了解一下该类,它的类结构如下图所示。 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-10-11-020426.png) 它是一个抽象类,继承自 AbstractRichFunction,实现了 SourceFunction 接口,其子类有三个,如下图所示,两个是抽象类,在此基础上提供了更具体的实现,另一个是 ContinuousFileMonitoringFunction。 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-10-11-020702.png) 这三个子类的功能如下: + MessageAcknowledgingSourceBase :它针对的是数据源是消息队列的场景并且提供了基于 ID 的应答机制。 + MultipleIdsMessageAcknowledgingSourceBase : 在 MessageAcknowledgingSourceBase 的基础上针对 ID 应答机制进行了更为细分的处理,支持两种 ID 应答模型:session id 和 unique message id。 + ContinuousFileMonitoringFunction:这是单个(非并行)监视任务,它接受 FileInputFormat,并且根据 FileProcessingMode 和 FilePathFilter,它负责监视用户提供的路径;决定应该进一步读取和处理哪些文件;创建与这些文件对应的 FileInputSplit 拆分,将它们分配给下游任务以进行进一步处理。 除了上面使用 RichSourceFunction 和 SourceFunction 来自定义 Source,还可以继承 RichParallelSourceFunction 抽象类或实现 ParallelSourceFunction 接口来实现自定义 Source 函数。 ### 3.8.3 自定义 Sink Connector 下面将写一个 demo 教大家将从 Kafka Source 的数据 Sink 到 MySQL 中去 #### 工具类 写了一个工具类往 Kafka 的 topic 中发送数据。 ```java /** * 往kafka中写数据,可以使用这个main函数进行测试一下 */ public class KafkaUtils2 { public static final String broker_list = "localhost:9092"; public static final String topic = "student"; //kafka topic 需要和 flink 程序用同一个 topic public static void writeToKafka() throws InterruptedException { Properties props = new Properties(); props.put("bootstrap.servers", broker_list); props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer"); props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer"); KafkaProducer producer = new KafkaProducer(props); for (int i = 1; i <= 100; i++) { Student student = new Student(i, "zhisheng" + i, "password" + i, 18 + i); ProducerRecord record = new ProducerRecord(topic, null, null, JSON.toJSONString(student)); producer.send(record); System.out.println("发送数据: " + JSON.toJSONString(student)); } producer.flush(); } public static void main(String[] args) throws InterruptedException { writeToKafka(); } } ``` #### SinkToMySQL 该类就是 Sink Function,继承了 RichSinkFunction ,然后重写了里面的方法,在 invoke 方法中将数据插入到 MySQL 中。 ```java public class SinkToMySQL extends RichSinkFunction { PreparedStatement ps; private Connection connection; /** * open() 方法中建立连接,这样不用每次 invoke 的时候都要建立连接和释放连接 * * @param parameters * @throws Exception */ @Override public void open(Configuration parameters) throws Exception { super.open(parameters); connection = getConnection(); String sql = "insert into Student(id, name, password, age) values(?, ?, ?, ?);"; ps = this.connection.prepareStatement(sql); } @Override public void close() throws Exception { super.close(); //关闭连接和释放资源 if (connection != null) { connection.close(); } if (ps != null) { ps.close(); } } /** * 每条数据的插入都要调用一次 invoke() 方法 * * @param value * @param context * @throws Exception */ @Override public void invoke(Student value, Context context) throws Exception { //组装数据,执行插入操作 ps.setInt(1, value.getId()); ps.setString(2, value.getName()); ps.setString(3, value.getPassword()); ps.setInt(4, value.getAge()); ps.executeUpdate(); } private static Connection getConnection() { Connection con = null; try { Class.forName("com.mysql.jdbc.Driver"); con = DriverManager.getConnection("jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8", "root", "root123456"); } catch (Exception e) { System.out.println("-----------mysql get connection has exception , msg = "+ e.getMessage()); } return con; } } ``` #### Flink 程序 这里的 source 是从 Kafka 读取数据的,然后 Flink 从 Kafka 读取到数据(JSON)后用阿里 fastjson 来解析成 Student 对象,然后在 addSink 中使用我们创建的 SinkToMySQL,这样就可以把数据存储到 MySQL 了。 ```java public class Main3 { public static void main(String[] args) throws Exception { final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); Properties props = new Properties(); props.put("bootstrap.servers", "localhost:9092"); props.put("zookeeper.connect", "localhost:2181"); props.put("group.id", "metric-group"); props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer"); props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer"); props.put("auto.offset.reset", "latest"); SingleOutputStreamOperator student = env.addSource(new FlinkKafkaConsumer011<>( "student", //这个 kafka topic 需要和上面的工具类的 topic 一致 new SimpleStringSchema(), props)).setParallelism(1) .map(string -> JSON.parseObject(string, Student.class)); //Fastjson 解析字符串成 student 对象 student.addSink(new SinkToMySQL()); //数据 sink 到 mysql env.execute("Flink add sink"); } } ``` #### 结果 ### 3.8.4 RichSinkFunction 的用法及源码分析 ### 3.8.5 小结与反思 加入知识星球可以看到上面文章:https://t.zsxq.com/Y3RBaaQ 批量写 MySQL 可以参考 :https://t.zsxq.com/FAmYFYJ ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-09-25-zsxq.jpg) ================================================ FILE: books/flink-in-action-3.9.md ================================================ --- toc: true title: 《Flink 实战与性能优化》—— Flink Connector —— ElasticSearch 的用法和分析 date: 2021-07-20 tags: - Flink - 大数据 - 流式计算 --- ## 3.9 Flink Connector —— ElasticSearch 的用法和分析 ElasticSearch 现在也是非常火的一门技术,目前很多公司都有使用,本节将介绍 Flink ElasticSearch Connector 的实战使用和可能会遇到的问题。 ### 3.9.1 准备环境和依赖 首先准备 ElasticSearch 的环境和项目的环境依赖。 **ElasticSearch 安装** 因为在 2.1 节中已经讲过 ElasticSearch 的安装,这里就不做过多的重复,需要注意的一点就是 Flink 的 ElasticSearch Connector 是区分版本号的,官方支持的版本如下图所示。 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-10-23-103746.png) 所以添加依赖的时候要区分一下,根据你安装的 ElasticSearch 来选择不一样的版本依赖,另外就是不同版本的 ElasticSearch 还会导致下面的数据写入到 ElasticSearch 中出现一些不同,我们这里使用的版本是 ElasticSearch6,如果你使用的是其他的版本可以参考官网的实现。 **添加依赖** 因为我们在 2.1 节中安装的 ElasticSearch 版本是 6.3.2 版本的,所有这里引入的依赖就选择 `flink-connector-elasticsearch6`,具体依赖如下所示。 ```xml org.apache.flink flink-connector-elasticsearch6_${scala.binary.version} ${flink.version} ``` 上面这个 `scala.binary.version` 和 `flink.version` 版本号需要自己在使用的时候根据使用的版本做相应的改变。 ### 3.9.2 使用 Flink 将数据写入到 ElasticSearch 应用程序 准备好环境和相关的依赖后,接下来开始编写 Flink 程序。 ESSinkUtil 工具类,代码如下所示,这个工具类是笔者封装的,getEsAddresses 方法将传入的配置文件 es 地址解析出来,可以是域名方式,也可以是 ip + port 形式。addSink 方法是利用了 Flink 自带的 ElasticsearchSink 来封装了一层,传入了一些必要的调优参数和 es 配置参数,下面章节还会再讲其他的配置。 ```java public class ESSinkUtil { /** * es sink * * @param hosts es hosts * @param bulkFlushMaxActions bulk flush size * @param parallelism 并行数 * @param data 数据 * @param func * @param */ public static void addSink(List hosts, int bulkFlushMaxActions, int parallelism, SingleOutputStreamOperator data, ElasticsearchSinkFunction func) { ElasticsearchSink.Builder esSinkBuilder = new ElasticsearchSink.Builder<>(hosts, func); esSinkBuilder.setBulkFlushMaxActions(bulkFlushMaxActions); data.addSink(esSinkBuilder.build()).setParallelism(parallelism); } /** * 解析配置文件的 es hosts * * @param hosts * @return * @throws MalformedURLException */ public static List getEsAddresses(String hosts) throws MalformedURLException { String[] hostList = hosts.split(","); List addresses = new ArrayList<>(); for (String host : hostList) { if (host.startsWith("http")) { URL url = new URL(host); addresses.add(new HttpHost(url.getHost(), url.getPort())); } else { String[] parts = host.split(":", 2); if (parts.length > 1) { addresses.add(new HttpHost(parts[0], Integer.parseInt(parts[1]))); } else { throw new MalformedURLException("invalid elasticsearch hosts format"); } } } return addresses; } } ``` Flink 程序会读取到 ElasticSearch 的配置,然后将从 Kafka 读取到的数据写入进 ElasticSearch,具体的写入代码如下所示。 ```java public class Sink2ES6Main { public static void main(String[] args) throws Exception { //获取所有参数 final ParameterTool parameterTool = ExecutionEnvUtil.createParameterTool(args); //准备好环境 StreamExecutionEnvironment env = ExecutionEnvUtil.prepare(parameterTool); //从kafka读取数据 DataStreamSource data = KafkaConfigUtil.buildSource(env); //从配置文件中读取 es 的地址 List esAddresses = ESSinkUtil.getEsAddresses(parameterTool.get(ELASTICSEARCH_HOSTS)); //从配置文件中读取 bulk flush size,代表一次批处理的数量,这个可是性能调优参数,特别提醒 int bulkSize = parameterTool.getInt(ELASTICSEARCH_BULK_FLUSH_MAX_ACTIONS, 40); //从配置文件中读取并行 sink 数,这个也是性能调优参数,特别提醒,这样才能够更快的消费,防止 kafka 数据堆积 int sinkParallelism = parameterTool.getInt(STREAM_SINK_PARALLELISM, 5); //自己再自带的 es sink 上一层封装了下 ESSinkUtil.addSink(esAddresses, bulkSize, sinkParallelism, data, (Metrics metric, RuntimeContext runtimeContext, RequestIndexer requestIndexer) -> { requestIndexer.add(Requests.indexRequest() .index(ZHISHENG + "_" + metric.getName()) //es 索引名 .type(ZHISHENG) //es type .source(GsonUtil.toJSONBytes(metric), XContentType.JSON)); }); env.execute("flink learning connectors es6"); } } ``` 配置文件中包含了 Kafka 和 ElasticSearch 的配置,如下所示,地址都支持集群模式填写,注意用 `,` 分隔。 ```properties kafka.brokers=localhost:9092 kafka.group.id=zhisheng-metrics-group-test kafka.zookeeper.connect=localhost:2181 metrics.topic=zhisheng-metrics stream.parallelism=5 stream.checkpoint.interval=1000 stream.checkpoint.enable=false elasticsearch.hosts=localhost:9200 elasticsearch.bulk.flush.max.actions=40 stream.sink.parallelism=5 ``` ### 3.9.3 验证数据是否写入 ElasticSearch? ### 3.9.4 如何保证在海量数据实时写入下 ElasticSearch 的稳定性? ### 3.9.5 使用 Flink-connector-elasticsearch 可能会遇到的问题 ### 3.9.6 小结与反思 加入知识星球可以看到上面文章:https://t.zsxq.com/Jeqzfem ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-09-25-zsxq.jpg) ================================================ FILE: books/flink-in-action-4.1.md ================================================ --- toc: true title: 《Flink 实战与性能优化》—— 深度讲解 Flink 中的状态 date: 2021-07-24 tags: - Flink - 大数据 - 流式计算 --- # 第四章 —— Flink 中的状态及容错机制 Flink 对比其他的流处理框架最大的特点是其支持状态,本章将深度的讲解 Flink 中的状态分类,如何在不同的场景使用不同的状态,接着会介绍 Flink 中的多种状态存储,最后会介绍 Checkpoint 和 Savepoint 的使用方式以及如何恢复状态。 ## 4.1 深度讲解 Flink 中的状态 在基础篇中的 1.2 节中介绍了 Flink 是一款有状态的流处理框架。那么大家可能有点疑问,这个状态是什么意思?拿 Flink 最简单的 Word Count 程序来说,它需要不断的对 word 出现的个数进行结果统计,那么后一个结果就需要利用前一个的结果然后再做 +1 的操作,这样前一个计算就需要将 word 出现的次数 count 进行存着(这个 count 那么就是一个状态)然后后面才可以进行累加。 ### 4.1.1 为什么需要 State? 对于流处理系统,数据是一条一条被处理的,如果没有对数据处理的进度进行记录,那么如果这个处理数据的 Job 因为机器问题或者其他问题而导致重启,那么它是不知道上一次处理数据是到哪个地方了,这样的情况下如果是批数据,倒是可以很好的解决(重新将这份固定的数据再执行一遍),但是流数据那就麻烦了,你根本不知道什么在 Job 挂的那个时刻数据消费到哪里了?那么你重启的话该从哪里开始重新消费呢?你可以有以下选择(因为你可能也不确定 Job 挂的具体时间): + Job 挂的那个时间之前:如果是从 Job 挂之前开始重新消费的话,那么会导致部分数据(从新消费的时间点到之前 Job 挂的那个时间点之前的数据)重复消费 + Job 挂的那个时间之后:如果是从 Job 挂之后开始消费的话,那么会导致部分数据(从 Job 挂的那个时间点到新消费的时间点产生的数据)丢失,没有消费 上面两种情况用图片描述如下图所示。 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-12-14-030800.png) 为了解决上面两种情况(数据重复消费或者数据没有消费)的发生,那么是不是就得需要个什么东西做个记录将这种数据消费状态,Flink state 就这样诞生了,state 中存储着每条数据消费后数据的消费点(生产环境需要持久化这些状态),当 Job 因为某种错误或者其他原因导致重启时,就能够从 Checkpoint(定时将 state 做一个全局快照,在 Flink 中,为了能够让 Job 在运行的过程中保证容错性,才会对这些 state 做一个快照,在 4.3 节中会详细讲) 中的 state 数据进行恢复。 ### 4.1.2 State 的种类 在 Flink 中有两个基本的 state:Keyed state 和 Operator state,下面来分别介绍一下这两种 State。 ### 4.1.3 Keyed State Keyed State 总是和具体的 key 相关联,也只能在 KeyedStream 的 function 和 operator 上使用。你可以将 Keyed State 当作是 Operator State 的一种特例,但是它是被分区或分片的。每个 Keyed State 分区对应一个 key 的 Operator State,对于某个 key 在某个分区上有唯一的状态。逻辑上,Keyed State 总是对应着一个 二元组,在某种程度上,因为每个具体的 key 总是属于唯一一个具体的 parallel-operator-instance(并行操作实例),这种情况下,那么就可以简化认为是 。Keyed State 可以进一步组织成 Key Group,Key Group 是 Flink 重新分配 Keyed State 的最小单元,所以有多少个并行,就会有多少个 Key Group。在执行过程中,每个 keyed operator 的并行实例会处理来自不同 key 的不同 Key Group。 ### 4.1.4 Operator State 对 Operator State 而言,每个 operator state 都对应着一个并行实例。Kafka Connector 就是一个很好的例子。每个 Kafka consumer 的并行实例都会持有一份topic partition 和 offset 的 map,这个 map 就是它的 Operator State。 当并行度发生变化时,Operator State 可以将状态在所有的并行实例中进行重分配,并且提供了多种方式来进行重分配。 在 Flink 源码中,在 flink-core module 下的 `org.apache.flink.api.common.state` 中可以看到 Flink 中所有和 State 相关的类,如下图所示。 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-10-23-143333.png) ### 4.1.5 Raw State 和 Managed State Keyed State 和 Operator State 都有两种存在形式,即 Raw State(原始状态)和 Managed State(托管状态)。 原始状态是 Operator(算子)保存它们自己的数据结构中的 state,当 Checkpoint 时,原始状态会以字节流的形式写入进 Checkpoint 中。Flink 并不知道 State 的数据结构长啥样,仅能看到原生的字节数组。 托管状态可以使用 Flink runtime 提供的数据结构来表示,例如内部哈希表或者 RocksDB。具体有 ValueState,ListState 等。Flink runtime 会对这些状态进行编码然后将它们写入到 Checkpoint 中。 DataStream 的所有 function 都可以使用托管状态,但是原生状态只能在实现 operator 的时候使用。相对于原生状态,推荐使用托管状态,因为如果使用托管状态,当并行度发生改变时,Flink 可以自动的帮你重分配 state,同时还可以更好的管理内存。 注意:如果你的托管状态需要特殊的序列化,目前 Flink 还不支持。 ### 4.1.6 如何使用托管的 Keyed State 托管的 Keyed State 接口提供对不同类型状态(这些状态的范围都是当前输入元素的 key)的访问,这意味着这种状态只能在通过 stream.keyBy() 创建的 KeyedStream 上使用。 我们首先来看一下有哪些可以使用的状态,然后再来看看它们在程序中是如何使用的: + ValueState: 保存一个可以更新和获取的值(每个 Key 一个 value),可以用 update(T) 来更新 value,可以用 value() 来获取 value。 + ListState: 保存一个值的列表,用 add(T) 或者 addAll(List) 来添加,用 Iterable get() 来获取。 + ReducingState: 保存一个值,这个值是状态的很多值的聚合结果,接口和 ListState 类似,但是可以用相应的 ReduceFunction 来聚合。 + AggregatingState: 保存很多值的聚合结果的单一值,与 ReducingState 相比,不同点在于聚合类型可以和元素类型不同,提供 AggregateFunction 来实现聚合。 + FoldingState: 与 AggregatingState 类似,除了使用 FoldFunction 进行聚合。 + MapState: 保存一组映射,可以将 kv 放进这个状态,使用 put(UK, UV) 或者 putAll(Map) 添加,或者使用 get(UK) 获取。 所有类型的状态都有一个 clear() 方法来清除当前的状态。 注意:FoldingState 已经不推荐使用,可以用 AggregatingState 来代替。 需要注意,上面的这些状态对象仅用来和状态打交道,状态不一定保存在内存中,也可以存储在磁盘或者其他地方。另外,你获取到的状态的值是取决于输入元素的 key,因此如果 key 不同,那么在一次调用用户函数中获得的值可能与另一次调用的值不同。 要使用一个状态对象,需要先创建一个 StateDescriptor,它包含了状态的名字(你可以创建若干个 state,但是它们必须要有唯一的值以便能够引用它们),状态的值的类型,或许还有一个用户定义的函数,比如 ReduceFunction。根据你想要使用的 state 类型,你可以创建 ValueStateDescriptor、ListStateDescriptor、ReducingStateDescriptor、FoldingStateDescriptor 或者 MapStateDescriptor。 状态只能通过 RuntimeContext 来获取,所以只能在 RichFunction 里面使用。RichFunction 中你可以通过 RuntimeContext 用下述方法获取状态: + ValueState getState(ValueStateDescriptor) + ReducingState getReducingState(ReducingStateDescriptor) + ListState getListState(ListStateDescriptor) + AggregatingState getAggregatingState(AggregatingState) + FoldingState getFoldingState(FoldingStateDescriptor) + MapState getMapState(MapStateDescriptor) 上面讲了这么多概念,那么来一个例子来看看如何使用状态: ```java public class CountWindowAverage extends RichFlatMapFunction, Tuple2> { //ValueState 使用方式,第一个字段是 count,第二个字段是运行的和 private transient ValueState> sum; @Override public void flatMap(Tuple2 input, Collector> out) throws Exception { //访问状态的 value 值 Tuple2 currentSum = sum.value(); //更新 count currentSum.f0 += 1; //更新 sum currentSum.f1 += input.f1; //更新状态 sum.update(currentSum); //如果 count 等于 2, 发出平均值并清除状态 if (currentSum.f0 >= 2) { out.collect(new Tuple2<>(input.f0, currentSum.f1 / currentSum.f0)); sum.clear(); } } @Override public void open(Configuration config) { ValueStateDescriptor> descriptor = new ValueStateDescriptor<>( "average", //状态名称 TypeInformation.of(new TypeHint>() {}), //类型信息 Tuple2.of(0L, 0L)); //状态的默认值 sum = getRuntimeContext().getState(descriptor);//获取状态 } } env.fromElements(Tuple2.of(1L, 3L), Tuple2.of(1L, 5L), Tuple2.of(1L, 7L), Tuple2.of(1L, 4L), Tuple2.of(1L, 2L)) .keyBy(0) .flatMap(new CountWindowAverage()) .print(); //结果会打印出 (1,4) 和 (1,5) ``` 这个例子实现了一个简单的计数器,我们使用元组的第一个字段来进行分组(这个例子中,所有的 key 都是 1),这个 CountWindowAverage 函数将计数和运行时总和保存在一个 ValueState 中,一旦计数等于 2,就会发出平均值并清理 state,因此又从 0 开始。请注意,如果在第一个字段中具有不同值的元组,则这将为每个不同的输入 key保存不同的 state 值。 ### 4.1.7 State TTL(存活时间) 随着作业的运行时间变长,作业的状态也会逐渐的变大,那么很有可能就会影响作业的稳定性,这时如果有状态的过期这种功能就可以将历史的一些状态清除,对应在 Flink 中的就是 State TTL,接下来将对其做详细介绍。 #### State TTL 介绍 TTL 可以分配给任何类型的 Keyed state,如果一个状态设置了 TTL,那么当状态过期时,那么之前存储的状态值会被清除。所有的状态集合类型都支持单个入口的 TTL,这意味着 List 集合元素和 Map 集合都支持独立到期。为了使用状态 TTL,首先必须要构建 StateTtlConfig 配置对象,然后可以通过传递配置在 State descriptor 中启用 TTL 功能: ```java import org.apache.flink.api.common.state.StateTtlConfig; import org.apache.flink.api.common.state.ValueStateDescriptor; import org.apache.flink.api.common.time.Time; StateTtlConfig ttlConfig = StateTtlConfig .newBuilder(Time.seconds(1)) .setUpdateType(StateTtlConfig.UpdateType.OnCreateAndWrite) .setStateVisibility(StateTtlConfig.StateVisibility.NeverReturnExpired) .build(); ValueStateDescriptor stateDescriptor = new ValueStateDescriptor<>("zhisheng", String.class); stateDescriptor.enableTimeToLive(ttlConfig); //开启 ttl ``` 上面配置中有几个选项需要注意: 1、newBuilder 方法的第一个参数是必需的,它代表着状态存活时间。 2、UpdateType 配置状态 TTL 更新时(默认为 OnCreateAndWrite): + StateTtlConfig.UpdateType.OnCreateAndWrite: 仅限创建和写入访问时更新 + StateTtlConfig.UpdateType.OnReadAndWrite: 除了创建和写入访问,还支持在读取时更新 3、StateVisibility 配置是否在读取访问时返回过期值(如果尚未清除),默认是 NeverReturnExpired: + StateTtlConfig.StateVisibility.NeverReturnExpired: 永远不会返回过期值 + StateTtlConfig.StateVisibility.ReturnExpiredIfNotCleanedUp: 如果仍然可用则返回 在 NeverReturnExpired 的情况下,过期状态表现得好像它不再存在,即使它仍然必须被删除。该选项对于在 TTL 之后必须严格用于读取访问的数据的用例是有用的,例如,应用程序使用隐私敏感数据. 另一个选项 ReturnExpiredIfNotCleanedUp 允许在清理之前返回过期状态。 注意: + 状态后端会存储上次修改的时间戳以及对应的值,这意味着启用此功能会增加状态存储的消耗,堆状态后端存储一个额外的 Java 对象,其中包含对用户状态对象的引用和内存中原始的 long 值。RocksDB 状态后端存储为每个存储值、List、Map 都添加 8 个字节。 + 目前仅支持参考 processing time 的 TTL + 使用启用 TTL 的描述符去尝试恢复先前未使用 TTL 配置的状态可能会导致兼容性失败或者 StateMigrationException 异常。 + TTL 配置并不是 Checkpoint 和 Savepoint 的一部分,而是 Flink 如何在当前运行的 Job 中处理它的方式。 + 只有当用户值序列化器可以处理 null 值时,具体 TTL 的 Map 状态当前才支持 null 值,如果序列化器不支持 null 值,则可以使用 NullableSerializer 来包装它(代价是需要一个额外的字节)。 #### 清除过期 State 默认情况下,过期值只有在显式读出时才会被删除,例如通过调用 ValueState.value()。 注意:这意味着默认情况下,如果未读取过期状态,则不会删除它,这可能导致状态不断增长,这个特性在 Flink 未来的版本可能会发生变化。 此外,你可以在获取完整状态快照时激活清理状态,这样就可以减少状态的大小。在当前实现下不清除本地状态,但是在从上一个快照恢复的情况下,它不会包括已删除的过期状态,你可以在 StateTtlConfig 中这样配置: ```java import org.apache.flink.api.common.state.StateTtlConfig; import org.apache.flink.api.common.time.Time; StateTtlConfig ttlConfig = StateTtlConfig .newBuilder(Time.seconds(1)) .cleanupFullSnapshot() .build(); ``` 此配置不适用于 RocksDB 状态后端中的增量 Checkpoint。对于现有的 Job,可以在 StateTtlConfig 中随时激活或停用此清理策略,例如,从保存点重启后。 除了在完整快照中清理外,你还可以在后台激活清理。如果使用的后端支持以下选项,则会激活 StateTtlConfig 中的默认后台清理: ```java import org.apache.flink.api.common.state.StateTtlConfig; StateTtlConfig ttlConfig = StateTtlConfig .newBuilder(Time.seconds(1)) .cleanupInBackground() .build(); ``` 要在后台对某些特殊清理进行更精细的控制,可以按照下面的说明单独配置它。目前,堆状态后端依赖于增量清理,RocksDB 后端使用压缩过滤器进行后台清理。 我们再来看看 TTL 对应着的类 StateTtlConfig 类中的具体实现,这样我们才能更加的理解其使用方式。 在该类中的属性如下图所示: ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-10-23-143816.png) 这些属性的功能如下: + DISABLED:它默认创建了一个 UpdateType 为 Disabled 的 StateTtlConfig + UpdateType:这个是一个枚举,包含 Disabled(代表 TTL 是禁用的,状态不会过期)、OnCreateAndWrite、OnReadAndWrite 可选 + StateVisibility:这也是一个枚举,包含了 ReturnExpiredIfNotCleanedUp、NeverReturnExpired + TimeCharacteristic:这是时间特征,其实是只有 ProcessingTime 可选 + Time:设置 TTL 的时间,这里有两个参数 unit 和 size + CleanupStrategies:TTL 清理策略,在该类中有字段 isCleanupInBackground(是否在后台清理) 和相关的清理 strategies(包含 FULL_STATE_SCAN_SNAPSHOT、INCREMENTAL_CLEANUP 和 ROCKSDB_COMPACTION_FILTER),同时该类中还有 CleanupStrategy 接口,它的实现类有 EmptyCleanupStrategy(不清理,为空)、IncrementalCleanupStrategy(增量的清除)、RocksdbCompactFilterCleanupStrategy(在 RocksDB 中自定义压缩过滤器),该类和其实现类如下图所示。 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-10-23-144111.png) 如果对 State TTL 还有不清楚的可以看看 Flink 源码 flink-runtime module 中的 state ttl 相关的实现类,如下图所示: ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-10-23-144324.png) ### 4.1.8 如何使用托管的 Operator State #### CheckpointedFunction #### ListCheckpointed ### 4.1.9 Stateful Source Functions ### 4.1.10 Broadcast State Flink 中的 Broadcast State 在很多场景下也有使用,下面来讲解下其使用方式。 #### Broadcast State 如何使用 #### 使用 Broadcast state 需要注意 ### 4.1.11 Queryable State 加入知识星球可以看到上面文章: https://t.zsxq.com/ZVByvzN ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-09-25-zsxq.jpg) ### 4.1.12 小结与反思 本节一开始讲解了 State 出现的原因,接着讲解了 Flink 中的 State 分类,然后对 Flink 中的每种 State 做了详细的讲解,希望可以好好消化这节的内容。你对本节的内容有什么不理解的地方吗?在使用 State 的过程中有遇到什么问题吗? ================================================ FILE: books/flink-in-action-4.2.md ================================================ --- toc: true title: 《Flink 实战与性能优化》—— Flink 状态后端存储 date: 2021-07-25 tags: - Flink - 大数据 - 流式计算 --- ## 4.2 Flink 状态后端存储 在 4.1 节中介绍了 Flink 中的状态,那么在生产环境中,随着作业的运行时间变长,状态会变得越来越大,那么如何将这些状态存储也是 Flink 要解决的一大难点,本节来讲解下 Flink 中不同类型的状态后端存储。 ### 4.2.1 State Backends 当需要对具体的某一种 State 做 Checkpoint 时,此时就需要具体的状态后端存储,刚好 Flink 内置提供了不同的状态后端存储,用于指定状态的存储方式和位置。状态可以存储在 Java 堆内存中或者堆外,在 Flink 安装路径下 conf 目录中的 flink-conf.yaml 配置文件中也有状态后端存储相关的配置,为此在 Flink 源码中还特有一个 CheckpointingOptions 类来控制 state 存储的相关配置,该类中有如下配置: + state.backend: 用于存储和进行状态 Checkpoint 的状态后端存储方式,无默认值 + state.checkpoints.num-retained: 要保留的已完成 Checkpoint 的最大数量,默认值为 1 + state.backend.async: 状态后端是否使用异步快照方法,默认值为 true + state.backend.incremental: 状态后端是否创建增量检查点,默认值为 false + state.backend.local-recovery: 状态后端配置本地恢复,默认情况下,本地恢复被禁用 + taskmanager.state.local.root-dirs: 定义存储本地恢复的基于文件的状态的目录 + state.savepoints.dir: 存储 savepoints 的目录 + state.checkpoints.dir: 存储 Checkpoint 的数据文件和元数据 + state.backend.fs.memory-threshold: 状态数据文件的最小大小,默认值是 1024 虽然配置这么多,但是,Flink 还支持基于每个 Job 单独设置状态后端存储,方法如下: ```java StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.setStateBackend(new MemoryStateBackend()); //设置堆内存存储 //env.setStateBackend(new FsStateBackend(checkpointDir, asyncCheckpoints)); //设置文件存储 //env.setStateBackend(new RocksDBStateBackend(checkpointDir, incrementalCheckpoints)); //设置 RocksDB 存储 ``` StateBackend 接口的三种实现类如下图所示: ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-10-17-141800.png) 上面三种方式取一种就好了。但是有三种方式,我们该如何去挑选用哪种去存储状态呢?下面讲讲这三种的特点以及该如何选择。 ### 4.2.2 MemoryStateBackend 的用法及分析 如果 Job 没有配置指定状态后端存储的话,就会默认采取 MemoryStateBackend 策略。如果你细心的话,可以从你的 Job 中看到类似日志如下: ```text 2019-04-28 00:16:41.892 [Sink: zhisheng (1/4)] INFO org.apache.flink.streaming.runtime.tasks.StreamTask - No state backend has been configured, using default (Memory / JobManager) MemoryStateBackend (data in heap memory / checkpoints to JobManager) (checkpoints: 'null', savepoints: 'null', asynchronous: TRUE, maxStateSize: 5242880) ``` 上面日志的意思就是说如果没有配置任何状态存储,使用默认的 MemoryStateBackend 策略,这种状态后端存储把数据以内部对象的形式保存在 TaskManagers 的内存(JVM 堆)中,当应用程序触发 Checkpoint 时,会将此时的状态进行快照然后存储在 JobManager 的内存中。因为状态是存储在内存中的,所以这种情况会有点限制,比如: + 不太适合在生产环境中使用,仅用于本地测试的情况较多,主要适用于状态很小的 Job,因为它会将状态最终存储在 JobManager 中,如果状态较大的话,那么会使得 JobManager 的内存比较紧张,从而导致 JobManager 会出现 OOM 等问题,然后造成连锁反应使所有的 Job 都挂掉,所以 Job 的状态与之前的 Checkpoint 的数据所占的内存要小于 JobManager 的内存。 + 每个单独的状态大小不能超过最大的 DEFAULT_MAX_STATE_SIZE(5MB),可以通过构造 MemoryStateBackend 参数传入不同大小的 maxStateSize。 + Job 的操作符状态和 keyed 状态加起来都不要超过 RPC 系统的默认配置 10 MB,虽然可以修改该配置,但是不建议去修改。 另外就是 MemoryStateBackend 支持配置是否是异步快照还是同步快照,它有一个字段 asynchronousSnapshots 来表示,可选值有: + TRUE(表示使用异步的快照,这样可以避免因快照而导致数据流处理出现阻塞等问题) + FALSE(同步) + UNDEFINED(默认值) 在构造 MemoryStateBackend 的默认函数时是使用的 UNDEFINED,而不是异步: ```java public MemoryStateBackend() { this(null, null, DEFAULT_MAX_STATE_SIZE, TernaryBoolean.UNDEFINED);//使用的是 UNDEFINED } ``` 网上有人说默认是异步的,这里给大家解释清楚一下,从上面的那条日志打印的确实也是表示异步,但是前提是你对 State 无任何操作,笔者跟了下源码,当你没有配置任何的 state 时,它是会在 StateBackendLoader 类中通过 MemoryStateBackendFactory 来创建的 state 的,如下图所示。 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-10-17-142223.png) 继续跟进 MemoryStateBackendFactory 可以发现他这里创建了一个 MemoryStateBackend 实例并通过 configure 方法进行配置,大概流程代码是: ```java //MemoryStateBackendFactory 类 public MemoryStateBackend createFromConfig(Configuration config, ClassLoader classLoader) { return new MemoryStateBackend().configure(config, classLoader); } //MemoryStateBackend 类中的 config 方法 public MemoryStateBackend configure(Configuration config, ClassLoader classLoader) { return new MemoryStateBackend(this, config, classLoader); } //私有的构造方法 private MemoryStateBackend(MemoryStateBackend original, Configuration configuration, ClassLoader classLoader) { ... this.asynchronousSnapshots = original.asynchronousSnapshots.resolveUndefined( configuration.getBoolean(CheckpointingOptions.ASYNC_SNAPSHOTS)); } //根据 CheckpointingOptions 类中的 ASYNC_SNAPSHOTS 参数进行设置的 public static final ConfigOption ASYNC_SNAPSHOTS = ConfigOptions .key("state.backend.async") .defaultValue(true) //默认值就是 true,代表异步 .withDescription(...) ``` 可以发现最终是通过读取 `state.backend.async` 参数的默认值(true)来配置是否要异步的进行快照,但是如果你手动配置 MemoryStateBackend 的话,利用无参数的构造方法,那么就不是默认异步,如果想使用异步的话,需要利用下面这个构造函数(需要传入一个 boolean 值,true 代表异步,false 代表同步): ```java public MemoryStateBackend(boolean asynchronousSnapshots) { this(null, null, DEFAULT_MAX_STATE_SIZE, TernaryBoolean.fromBoolean(asynchronousSnapshots)); } ``` 如果你再细看了这个 MemoryStateBackend 类的话,那么你可能会发现这个构造函数: ```java public MemoryStateBackend(@Nullable String checkpointPath, @Nullable String savepointPath) { this(checkpointPath, savepointPath, DEFAULT_MAX_STATE_SIZE, TernaryBoolean.UNDEFINED);//需要你传入 checkpointPath 和 savepointPath } ``` 这个也是用来创建一个 MemoryStateBackend 的,它需要传入的参数是两个路径(checkpointPath、savepointPath),其中 checkpointPath 是写入 Checkpoint 元数据的路径,savepointPath 是写入 savepoint 的路径。 这个来看看 MemoryStateBackend 的继承关系图可以更明确的知道它是继承自 AbstractFileStateBackend,然后 AbstractFileStateBackend 这个抽象类就是为了能够将状态存储中的数据或者元数据进行文件存储的,如下图所示。 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-10-17-142403.png) 所以 FsStateBackend 和 MemoryStateBackend 都会继承该类。 ### 4.2.3 FsStateBackend 的用法及分析 这种状态后端存储也是将工作状态存储在 TaskManager 中的内存(JVM 堆)中,但是 Checkpoint 的时候,它和 MemoryStateBackend 不一样,它是将状态存储在文件(可以是本地文件,也可以是 HDFS)中,这个文件具体是哪种需要配置,比如:"hdfs://namenode:40010/flink/checkpoints" 或 "file://flink/checkpoints" (通常使用 HDFS 比较多,如果是使用本地文件,可能会造成 Job 恢复的时候找不到之前的 checkkpoint,因为 Job 重启后如果由调度器重新分配在不同的机器的 TaskManager 执行时就会导致这个问题,所以还是建议使用 HDFS 或者其他的分布式文件系统)。 同样 FsStateBackend 也是支持通过 asynchronousSnapshots 字段来控制是使用异步还是同步来进行 Checkpoint 的,异步可以避免在状态 Checkpoint 时阻塞数据流的处理,然后还有一点的就是在 FsStateBackend 有个参数 fileStateThreshold,如果状态大小比 MAX_FILE_STATE_THRESHOLD(1MB) 小的话,那么会将状态数据直接存储在 meta data 文件中,而不是存储在配置的文件中(避免出现很小的状态文件),如果该值为 "-1" 表示尚未配置,在这种情况下会使用默认值(1024,该默认值可以通过 `state.backend.fs.memory-threshold` 来配置)。 那么我们该什么时候使用 FsStateBackend 呢? + 如果你要处理大状态,长窗口等有状态的任务,那么 FsStateBackend 就比较适合 + 使用分布式文件系统,如 HDFS 等,这样 failover 时 Job 的状态可以恢复 使用 FsStateBackend 需要注意的地方有什么呢? + 工作状态仍然是存储在 TaskManager 中的内存中,虽然在 Checkpoint 的时候会存在文件中,所以还是得注意这个状态要保证不超过 TaskManager 的内存 ### 4.2.4 RocksDBStateBackend 的用法及分析 ### 4.2.5 如何选择状态后端存储? ### 4.2.6 小结与反思 加入知识星球可以看到上面文章: https://t.zsxq.com/RNJeqFy ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-09-25-zsxq.jpg) ================================================ FILE: books/flink-in-action-4.3.md ================================================ --- toc: true title: 《Flink 实战与性能优化》—— Flink Checkpoint 和 Savepoint 的区别及其配置使用 date: 2021-07-26 tags: - Flink - 大数据 - 流式计算 --- ## 4.3 Flink Checkpoint 和 Savepoint 的区别及其配置使用 Checkpoint 在 Flink 中是一个非常重要的 Feature,Checkpoint 使 Flink 的状态具有良好的容错性,通过 Checkpoint 机制,Flink 可以对作业的状态和计算位置进行恢复。本节主要讲述在 Flink 中 Checkpoint 和 Savepoint 的使用方式及它们之间的区别。 ### 4.3.1 Checkpoint 简介及使用 在 Flink 任务运行过程中,为了保障故障容错,Flink 需要对状态进行快照。Flink 可以从 Checkpoint 中恢复流的状态和位置,从而使得应用程序发生故障后能够得到与无故障执行相同的语义。 Flink 的 Checkpoint 有以下先决条件: - 需要具有持久性且支持重放一定时间范围内数据的数据源。例如:Kafka、RabbitMQ 等。这里为什么要求支持重放一定时间范围内的数据呢?因为 Flink 的容错机制决定了,当 Flink 任务失败后会自动从最近一次成功的 Checkpoint 处恢复任务,此时可能需要把任务失败前消费的部分数据再消费一遍,所以必须要求数据源支持重放。假如一个Flink 任务消费 Kafka 并将数据写入到 MySQL 中,任务从 Kafka 读取到数据,还未将数据输出到 MySQL 时任务突然失败了,此时如果 Kafka 不支持重放,就会造成这部分数据永远丢失了。支持重放数据的数据源可以保障任务消费失败后,能够重新消费来保障任务不丢数据。 - 需要一个能保存状态的持久化存储介质,例如:HDFS、S3 等。当 Flink 任务失败后,自动从 Checkpoint 处恢复,但是如果 Checkpoint 时保存的状态信息快照全丢了,那就会影响 Flink 任务的正常恢复。就好比我们看书时经常使用书签来记录当前看到的页码,当下次看书时找到书签的位置继续阅读即可,但是如果书签三天两头经常丢,那我们就无法通过书签来恢复阅读。 Flink 中 Checkpoint 是默认关闭的,对于需要保障 At Least Once 和 Exactly Once 语义的任务,强烈建议开启 Checkpoint,对于丢一小部分数据不敏感的任务,可以不开启 Checkpoint,例如:一些推荐相关的任务丢一小部分数据并不会影响推荐效果。下面来介绍 Checkpoint 具体如何使用。 首先调用 StreamExecutionEnvironment 的方法 enableCheckpointing(n) 来开启 Checkpoint,参数 n 以毫秒为单位表示 Checkpoint 的时间间隔。Checkpoint 配置相关的 Java 代码如下所示: ```java StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutinEnvironment(); // 开启 Checkpoint,每 1000毫秒进行一次 Checkpoint env.enableCheckpointing(1000); // Checkpoint 语义设置为 EXACTLY_ONCE env.getCheckpointConfig().setCheckpointingMode(CheckpointingMode.EXACTLY_ONCE); // CheckPoint 的超时时间 env.getCheckpointConfig().setCheckpointTimeout(60000); // 同一时间,只允许 有 1 个 Checkpoint 在发生 env.getCheckpointConfig().setMaxConcurrentCheckpoints(1); // 两次 Checkpoint 之间的最小时间间隔为 500 毫秒 env.getCheckpointConfig().setMinPauseBetweenCheckpoints(500); // 当 Flink 任务取消时,保留外部保存的 CheckPoint 信息 env.getCheckpointConfig().enableExternalizedCheckpoints(ExternalizedCheckpointCleanup.RETAIN_ON_CANCELLATION); // 当有较新的 Savepoint 时,作业也会从 Checkpoint 处恢复 env.getCheckpointConfig().setPreferCheckpointForRecovery(true); // 作业最多允许 Checkpoint 失败 1 次(flink 1.9 开始支持) env.getCheckpointConfig().setTolerableCheckpointFailureNumber(1); // Checkpoint 失败后,整个 Flink 任务也会失败(flink 1.9 之前) env.getCheckpointConfig.setFailTasksOnCheckpointingErrors(true) ``` 以上 Checkpoint 相关的参数描述如下所示: - Checkpoint 语义: EXACTLY_ONCE 或 AT_LEAST_ONCE,EXACTLY_ONCE 表示所有要消费的数据被恰好处理一次,即所有数据既不丢数据也不重复消费;AT_LEAST_ONCE 表示要消费的数据至少处理一次,可能会重复消费。 - Checkpoint 超时时间:如果 Checkpoint 时间超过了设定的超时时间,则 Checkpoint 将会被终止。 - 同时进行的 Checkpoint 数量:默认情况下,当一个 Checkpoint 在进行时,JobManager 将不会触发下一个 Checkpoint,但 Flink 允许多个 Checkpoint 同时在发生。 - 两次 Checkpoint 之间的最小时间间隔:从上一次 Checkpoint 结束到下一次 Checkpoint 开始,中间的间隔时间。例如,env.enableCheckpointing(60000) 表示 1 分钟触发一次 Checkpoint,同时再设置两次 Checkpoint 之间的最小时间间隔为 30 秒,假如任务运行过程中一次 Checkpoint 就用了50s,那么等 Checkpoint 结束后,理论来讲再过 10s 就要开始下一次 Checkpoint 了,但是由于设置了最小时间间隔为30s,所以需要再过 30s 后,下次 Checkpoint 才开始。注:如果配置了该参数就决定了同时进行的 Checkpoint 数量只能为 1。 - 当任务被取消时,外部 Checkpoint 信息是否被清理:Checkpoint 在默认的情况下仅用于恢复运行失败的 Flink 任务,当任务手动取消时 Checkpoint 产生的状态信息并不保留。当然可以通过该配置来保留外部的 Checkpoint 状态信息,这些被保留的状态信息在作业手动取消时不会被清除,这样就可以使用该状态信息来恢复 Flink 任务,对于需要从状态恢复的任务强烈建议配置为外部 Checkpoint 状态信息不清理。可选择的配置项为: - ExternalizedCheckpointCleanup.RETAIN_ON_CANCELLATION:当作业手动取消时,保留作业的 Checkpoint 状态信息。注意,这种情况下,需要手动清除该作业保留的 Checkpoint 状态信息,否则这些状态信息将永远保留在外部的持久化存储中。 - ExternalizedCheckpointCleanup.DELETE_ON_CANCELLATION:当作业取消时,Checkpoint 状态信息会被删除。仅当作业失败时,作业的 Checkpoint 才会被保留用于任务恢复。 - 任务失败,当有较新的 Savepoint 时,作业是否回退到 Checkpoint 进行恢复:默认情况下,当 Savepoint 比 Checkpoint 较新时,任务会从 Savepoint 处恢复。 - 作业可以容忍 Checkpoint 失败的次数:默认值为 0,表示不能接受 Checkpoint 失败。 关于 Checkpoint 时,状态后端相关的配置请参阅本书 4.2 节。 ### 4.3.2 Savepoint 简介及使用 Savepoint 与 Checkpoint 类似,同样需要把状态信息存储到外部介质,当作业失败时,可以从外部存储中恢复。强烈建议在程序中给算子分配 Operator ID,以便来升级程序。主要通过 `uid(String)` 方法手动指定算子的 ID ,这些 ID 将用于恢复每个算子的状态。 ```java DataStream stream = env. // Stateful source (e.g. Kafka) with ID .addSource(new StatefulSource()) .uid("source-id") // ID for the source operator .shuffle() // Stateful mapper with ID .map(new StatefulMapper()) .uid("mapper-id") // ID for the mapper // Stateless printing sink .print(); // Auto-generated ID ``` 如果不为算子手动指定 ID,Flink 会为算子自动生成 ID。当 Flink 任务从 Savepoint 中恢复时,是按照 Operator ID 将快照信息与算子进行匹配的,只要这些 ID 不变,Flink 任务就可以从 Savepoint 中恢复。自动生成的 ID 取决于代码的结构,并且对代码更改比较敏感,因此强烈建议给程序中所有有状态的算子手动分配 Operator ID。如下图所示,一个 Flink 任务包含了 算子 A 和 算子 B,代码中都未指定 Operator ID,所以 Flink 为 Task A 自动生成了 Operator ID 为 aaa,为 Task B 自动生成了 Operator ID 为 bbb,且 Savepoint 成功完成。但是在代码改动后,任务并不能从 Savepoint 中正常恢复,因为 Flink 为算子生成的 Operator ID 取决于代码结构,代码改动后可能会把算子 B 的 Operator ID 改变成 ccc,导致任务从 Savepoint 恢复时,SavePoint 中只有 Operator ID 为 aaa 和 bbb 的状态信息,算子 B 找不到 Operator ID 为 ccc 的状态信息,所以算子 B 不能正常恢复。这里如果在写代码时通过 `uid(String)` 手动指定了 Operator ID,就不会存在 上述问题了。 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-10-19-020528.jpg) Savepoint 需要用户手动去触发,触发 Savepoint 的方式如下所示: ```shell bin/flink savepoint :jobId [:targetDirectory] ``` 这将触发 ID 为 `:jobId` 的作业进行 Savepoint,并返回创建的 Savepoint 路径,用户需要此路径来还原和删除 Savepoint 。 使用 YARN 触发 Savepoint 的方式如下所示: ``` bin/flink savepoint :jobId [:targetDirectory] -yid :yarnAppId ``` 这将触发 ID 为 `:jobId` 和 YARN 应用程序 ID `:yarnAppId` 的作业进行 Savepoint,并返回创建的 Savepoint 路径。 使用 Savepoint 取消 Flink 任务: ``` bin/flink cancel -s [:targetDirectory] :jobId ``` 这将自动触发 ID 为 `:jobid` 的作业进行 Savepoint,并在 Checkpoint 结束后取消该任务。此外,可以指定一个目标文件系统目录来存储 Savepoint 的状态信息,也可以在 flink 的 conf 目录下 flink-conf.yaml 中配置 state.savepoints.dir 参数来指定 Savepoint 的默认目录,触发 Savepoint 时,如果不指定目录则使用该默认目录。无论使用哪种方式配置,都需要保障配置的目录能被所有的 JobManager 和 TaskManager 访问。 ### 4.3.3 Savepoint 与 Checkpoint 的区别 前面分别介绍了 Savepoint 和 Checkpoint,可以发现它们有不少相似之处,下面来看下它们之间的区别。 | Checkpoint | Savepoint | | :----------------------------------------------------------: | :----------------------------------------------------------: | | 由 Flink 的 JobManager 定时自动触发并管理 | 由用户手动触发并管理 | | 主要用于任务发生故障时,为任务提供给自动恢复机制 | 主要用户升级 Flink 版本、修改任务的逻辑代码、调整算子的并行度,且必须手动恢复 | | 当使用 RocksDBStateBackend 时,支持增量方式对状态信息进行快照 | 仅支持全量快照 | | Flink 任务停止后,Checkpoint 的状态快照信息默认被清除 | 一旦触发 Savepoint,状态信息就被持久化到外部存储,除非用户手动删除 | | Checkpoint 设计目标:轻量级且尽可能快地恢复任务 | Savepoint 的生成和恢复成本会更高一些,Savepoint 更多地关注代码的可移植性和兼容任务的更改操作 | ### 4.3.4 Checkpoint 流程 Flink 任务 Checkpoint 的详细流程如下面流程所示。 1. JobManager 端的 CheckPointCoordinator 会定期向所有 SourceTask 发送 CheckPointTrigger,Source Task 会在数据流中安插 Checkpoint barrier,如下图所示。 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-11-06-021819.png) 2. 当 task 收到上游所有实例的 barrier 后,向自己的下游继续传递 barrier,然后自身同步进行快照,并将自己的状态异步写入到持久化存储中,如下图所示。 - 如果是增量 Checkpoint,则只是把最新的一部分更新写入到外部持久化存储中 - 为了下游尽快进行 Checkpoint,所以 task 会先发送 barrier 到下游,自身再同步进行快照 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-11-06-021846.png) > 注:Task B 必须接收到上游 Task A 所有实例发送的 barrier 时,Task B 才能开始进行快照,这里有一个 barrier 对齐的概念,关于 barrier 对齐的详细介绍请参阅 9.5.1 节 Flink 内部如何保证 Exactly Once 中的 barrier 对齐部分 3. 当 task 将状态信息完成备份后,会将备份数据的地址(state handle)通知给 JobManager 的CheckPointCoordinator,如果 Checkpoint 的持续时长超过了 Checkpoint 设定的超时时间CheckPointCoordinator 还没有收集完所有的 State Handle,CheckPointCoordinator 就会认为本次 Checkpoint 失败,会把这次 Checkpoint 产生的所有状态数据全部删除 4. 如果 CheckPointCoordinator 收集完所有算子的 State Handle,CheckPointCoordinator 会把整个 StateHandle 封装成 completed Checkpoint Meta,写入到外部存储中,Checkpoint 结束,如下图所示。 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-11-06-021900.png) 如果对上述 Checkpoint 过程不理解,在后续 9.5 节 Flink 如何保障 Exactly Once 中会详细介绍 Flink 的 Checkpoint 过程以及为什么这么做。 #### 基于 RocksDB 的增量 Checkpoint 实现原理 当使用 RocksDBStateBackend 时,增量 Checkpoint 是如何实现的呢?RocksDB 是一个基于 LSM 实现的 KV 数据库。LSM 全称 Log Structured Merge Trees,LSM 树本质是将大量的磁盘随机写操作转换成磁盘的批量写操作来极大地提升磁盘数据写入效率。一般 LSM Tree 实现上都会有一个基于内存的 MemTable 介质,所有的增删改操作都是写入到 MemTable 中,当 MemTable 足够大以后,将 MemTable 中的数据 flush 到磁盘中生成不可变且内部有序的 ssTable(Sorted String Table)文件,全量数据保存在磁盘的多个 ssTable 文件中。HBase 也是基于 LSM Tree 实现的,HBase 磁盘上的 HFile 就相当于这里的 ssTable 文件,每次生成的 HFile 都是不可变的而且内部有序的文件。基于 ssTable 不可变的特性,才实现了增量 Checkpoint,具体流程如下图所示。 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-11-06-021910.png) 第一次 Checkpoint 时生成的状态快照信息包含了两个 sstable 文件:sstable1 和 sstable2 及 Checkpoint1 的元数据文件 MANIFEST-chk1,所以第一次 Checkpoint 时需要将 sstable1、sstable2 和 MANIFEST-chk1 上传到外部持久化存储中。第二次 Checkpoint 时生成的快照信息为 sstable1、sstable2、sstable3 及元数据文件 MANIFEST-chk2,由于 sstable 文件的不可变特性,所以状态快照信息的 sstable1、sstable2 这两个文件并没有发生变化,sstable1、sstable2 这两个文件不需要重复上传到外部持久化存储中,因此第二次 Checkpoint 时,只需要将 sstable3 和 MANIFEST-chk2 文件上传到外部持久化存储中即可。这里只将新增的文件上传到外部持久化存储,也就是所谓的增量 Checkpoint。 基于 LSM Tree 实现的数据库为了提高查询效率,都需要定期对磁盘上多个 sstable 文件进行合并操作,合并时会将删除的、过期的以及旧版本的数据进行清理,从而降低 sstable 文件的总大小。图中可以看到第三次 Checkpoint 时生成的快照信息为sstable3、sstable4、sstable5 及元数据文件 MANIFEST-chk3, 其中新增了 sstable4 文件且 sstable1 和 sstable2 文件合并成 sstable5 文件,因此第三次 Checkpoint 时只需要向外部持久化存储上传 sstable4、sstable5 及元数据文件 MANIFEST-chk3。 基于 RocksDB 的增量 Checkpoint 从本质上来讲每次 Checkpoint 时只将本次 Checkpoint 新增的快照信息上传到外部的持久化存储中,依靠的是 LSM Tree 中 sstable 文件不可变的特性。对 LSM Tree 感兴趣的同学可以深入研究 RocksDB 或 HBase 相关原理及实现。 ### 4.3.5 如何从 Checkpoint 中恢复状态 ### 4.3.6 如何从 Savepoint 中恢复状态 ### 4.3.7 如何优雅地删除 Checkpoint 目录 ### 4.3.8 小结与反思 加入知识星球可以看到上面文章:https://t.zsxq.com/RNJeqFy ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-09-25-zsxq.jpg) 本章属于属于本书的进阶篇,在第一节中讲解了 State 的各种类型,包括每种 State 的使用方式、实现原理和部分源码剖析,在第二节中介绍 Flink 中的状态后端存储的使用方式,对比分析了现有的几种方案的使用场景,在第三节中介绍了 Checkpoint 和 Savepoint 的区别,如果从状态中恢复作业,以及整个作业的 Checkpoint 流程。 本章的内容是 Flink 作业开发的重点,通常线上遇到 State、Checkpoint、Savepoint 的问题比较多,所以希望你能好好的弄清楚它们的原理,这样在出问题后才能够对症下药去解决问题。 ================================================ FILE: books/flink-in-action-5.1.md ================================================ --- toc: true title: 《Flink 实战与性能优化》—— Flink Table & SQL 概念与通用 API date: 2021-07-27 tags: - Flink - 大数据 - 流式计算 --- # 第五章 —— Table API & SQL Flink 中除了 DataStream 和 DataSet API,还有比较高级的 Table API & SQL,它可以帮助我们简化开发的过程,能够快读的运用 Flink 去完成一些需求,本章将对 Flink Table API & SQL 进行讲解,并将与其他的 API 结合对比分析。 ## 5.1 Flink Table & SQL 概念与通用 API 前面的内容都是讲解 DataStream 和 DataSet API 相关的,在 1.2.5 节中讲解 Flink API 时提及到 Flink 的高级 API —— Table API & SQL,本节将开始 Table & SQL 之旅。 ### 5.1.1 新增 Blink SQL 查询处理器 在 Flink 1.9 版本中,合进了阿里巴巴开源的 Blink 版本中的大量代码,其中最重要的贡献就是 Blink SQL 了。在 Blink 捐献给 Apache Flink 之后,社区就致力于为 Table API & SQL 集成 Blink 的查询优化器和 runtime。先来看下 1.8 版本的 Flink Table 项目结构如下图所示。 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-10-30-130607.png) 1.9 版本的 Flink Table 项目结构图如下图所示: ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-10-30-130751.png) 可以发现新增了 `flink-sql-parser`、`flink-table-planner-blink`、`flink-table-runtime-blink`、`flink-table-uber-blink` 模块,对 Flink Table 模块的重构详细内容可以参考 [FLIP-32](https://cwiki.apache.org/confluence/display/FLINK/FLIP-32%3A+Restructure+flink-table+for+future+contributions)。这样对于 Java 和 Scala API 模块、优化器以及 runtime 模块来说,分层更清楚,接口更明确。 另外 `flink-table-planner-blink` 模块中实现了新的优化器接口,所以现在有两个插件化的查询处理器来执行 Table API & SQL:1.9 以前的 Flink 处理器和新的基于 Blink 的处理器。基于 Blink 的查询处理器提供了更好的 SQL 覆盖率、支持更广泛的查询优化、改进了代码生成机制、通过调优算子的实现来提升批处理查询的性能。除此之外,基于 Blink 的查询处理器还提供了更强大的流处理能力,包括了社区一些非常期待的新功能(如维表 Join、TopN、去重)和聚合场景缓解数据倾斜的优化,以及内置更多常用的函数,具体可以查看 `flink-table-runtime-blink` 代码。目前整个模块的结构如下图所示: ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-10-30-124512.png) 注意:两个查询处理器之间的语义和功能大部分是一致的,但未完全对齐,因为基于 Blink 的查询处理器还在优化中,所以在 1.9 版本中默认查询处理器还是 1.9 之前的版本。如果你想使用 Blink 处理器的话,可以在创建 TableEnvironment 时通过 EnvironmentSettings 配置启用。被选择的处理器必须要在正在执行的 Java 进程的类路径中。对于集群设置,默认两个查询处理器都会自动地加载到类路径中。如果要在 IDE 中运行一个查询,需要在项目中添加 planner 依赖。 ### 5.1.2 为什么选择 Table API & SQL? 在 1.2 节中介绍了 Flink 的 API 是包含了 Table API & SQL,在 1.3 节中也介绍了在 Flink 1.9 中阿里开源的 Blink 分支中的很强大的 SQL 功能合并进 Flink 主分支,另外通过阿里 Blink 相关的介绍,可以知道阿里在 SQL 功能这块是做了很多的工作。从前面章节的内容可以发现 Flink 的 DataStream/DataSet API 的功能已经很全并且很强大了,常见复杂的数据处理问题也都可以处理,那么社区为啥还在一直推广 Table API & SQL 呢? 其实通过观察其它的大数据组件,就不会好奇了,比如 Spark、Storm、Beam、Hive 、KSQL(面向 Kafka 的 SQL 引擎)、Elasticsearch、Phoenix(使用 SQL 进行 HBase 数据的查询)等,可以发现 SQL 已经成为各个大数据组件必不可少的数据查询语言,那么 Flink 作为一个大数据实时处理引擎,笔者对其支持 SQL 查询流数据也不足为奇了,但是还是来稍微介绍一下 Table API & SQL。 Table API & SQL 是一种关系型 API,用户可以像操作数据库一样直接操作流数据,而不再需要通过 DataStream API 来写很多代码完成计算需求,更不用手动去调优你写的代码,另外 SQL 最大的优势在于它是一门学习成本很低的语言,普及率很高,用户基数大,和其他的编程语言相比,它的入门相对简单。 除了上面的原因,还有一个原因是:可以借助 Table API & SQL 统一流处理和批处理,因为在 DataStream/DataSet API 中,用户开发流作业和批作业需要去了解两种不同的 API,这对于公司有些开发能力不高的数据分析师来说,学习成本有点高,他们其实更擅长写 SQL 来分析。Table API & SQL 做到了批与流上的查询具有同样的语法语义,因此不用改代码就能同时在批和流上执行。 总结来说,为什么选择 Table API & SQL: + 声明式语言表达业务逻辑 + 无需代码编程 —— 易于上手 + 查询能够被有效的优化 + 查询可以高效的执行 ### 5.1.3 Flink Table 项目模块 在上文中提及到 Flink Table 在 1.8 和 1.9 的区别,这里还是要再讲解一下这几个依赖,因为只有了解清楚了之后,我们在后面开发的时候才能够清楚挑选哪种依赖。它有如下几个模块: + flink-table-common:table 中的公共模块,可以用于通过自定义 function,format 等来扩展 Table 生态系统 + flink-table-api-java:支持使用 Java 语言,纯 Table&SQL API + flink-table-api-scala:支持使用 Scala 语言,纯 Table&SQL API + flink-table-api-java-bridge:支持使用 Java 语言,包含 DataStream/DataSet API 的 Table&SQL API(推荐使用) + flink-table-api-scala-bridge:支持使用 Scala 语言,带有 DataStream/DataSet API 的 Table&SQL API(推荐使用) + flink-sql-parser:SQL 语句解析层,主要依赖 calcite + flink-table-planner:Table 程序的 planner 和 runtime + flink-table-uber:将上诉模块打成一个 fat jar,在 lib 目录下 + flink-table-planner-blink:Blink 的 Table 程序的 planner(阿里开源的版本) + flink-table-runtime-blink:Blink 的 Table 程序的 runtime(阿里开源的版本) + flink-table-uber-blink:将 Blink 版本的 planner 和 runtime 与前面模块(除 flink-table-planner 模块)打成一个 fat jar,在 lib 目录下,如下图所示。 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-11-02-164352.png) + flink-sql-client:SQL 客户端 ### 5.1.4 两种 planner 之间的区别 上面讲了两种不同的 planner 之间包含的模块有点区别,但是具体有什么区别如下所示: + Blink planner 将批处理作业视为流的一种特殊情况。因此不支持 Table 和 DataSet 之间的转换,批处理作业会转换成 DataStream 程序,而不会转换成 DataSet 程序,流作业还是转换成 DataStream 程序。 + Blink planner 不支持 BatchTableSource,而是使用有界的(bounded) StreamTableSource 代替它。 + Blink planner 仅支持全新的 Catalog,不支持已经废弃的 ExternalCatalog。 + 以前的 planner 中 FilterableTableSource 的实现与现在的 Blink planner 有冲突,在以前的 planner 中是叠加 PlannerExpressions(在未来的版本中会移除),而在 Blink planner 中是 Expressions。 + 基于字符串的 KV 键值配置选项仅可以在 Blink planner 中使用。 + PlannerConfig 的实现(CalciteConfig)在两种 planner 中不同。 + Blink planner 会将多个 sink 优化在同一个 DAG 中(只在 TableEnvironment 中支持,StreamTableEnvironment 中不支持),而以前的 planner 是每个 sink 都有一个 DAG 中,相互独立的。 + 以前的 planner 不支持 catalog 统计,而 Blink planner 支持。 在了解到了两种 planner 的区别后,接下来开始 Flink Table API & SQL 之旅。 ### 5.1.5 添加项目依赖 因为在 Flink 1.9 版本中有两个 planner,所以得根据你使用的 planner 来选择对应的依赖,假设你选择的是最新的 Blink 版本,那么添加下面的依赖: ```xml org.apache.flink flink-table-planner-blink_${scala.binary.version} ${flink.version} ``` 如果是以前的 planner,则使用下面这个依赖: ```xml org.apache.flink flink-table-planner_${scala.binary.version} ${flink.version} ``` 如果要自定义 format 格式或者自定义 function,则需要添加 `flink-table-common` 依赖: ```xml org.apache.flink flink-table-common ${flink.version} ``` ### 5.1.6 创建一个 TableEnvironment TableEnvironment 是 Table API 和 SQL 的统称,它负责的内容有: + 在内部的 catalog 注册 Table + 注册一个外部的 catalog + 执行 SQL 查询 + 注册用户自定义的 function + 将 DataStream 或者 DataSet 转换成 Table + 保持对 ExecutionEnvironment 和 StreamExecutionEnvironment 的引用 Table 总是会绑定在一个指定的 TableEnvironment,不能在同一个查询中组合不同 TableEnvironment 的 Table,比如 join 或 union 操作。你可以使用下面的几种静态方法创建 TableEnvironment。 ```java //创建 StreamTableEnvironment static StreamTableEnvironment create(StreamExecutionEnvironment executionEnvironment) { return create(executionEnvironment, EnvironmentSettings.newInstance().build()); } static StreamTableEnvironment create(StreamExecutionEnvironment executionEnvironment, EnvironmentSettings settings) { return StreamTableEnvironmentImpl.create(executionEnvironment, settings, new TableConfig()); } /** @deprecated */ @Deprecated static StreamTableEnvironment create(StreamExecutionEnvironment executionEnvironment, TableConfig tableConfig) { return StreamTableEnvironmentImpl.create(executionEnvironment, EnvironmentSettings.newInstance().build(), tableConfig); } //创建 BatchTableEnvironment static BatchTableEnvironment create(ExecutionEnvironment executionEnvironment) { return create(executionEnvironment, new TableConfig()); } static BatchTableEnvironment create(ExecutionEnvironment executionEnvironment, TableConfig tableConfig) { // } ``` 你需要根据你的程序来使用对应的 TableEnvironment,是 BatchTableEnvironment 还是 StreamTableEnvironment。默认两个 planner 都是在 Flink 的安装目录下 lib 文件夹中存在的,所以应该在你的程序中指定使用哪种 planner。 ```java // Flink Streaming query import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.table.api.EnvironmentSettings; import org.apache.flink.table.api.java.StreamTableEnvironment; EnvironmentSettings fsSettings = EnvironmentSettings.newInstance().useOldPlanner().inStreamingMode().build(); StreamExecutionEnvironment fsEnv = StreamExecutionEnvironment.getExecutionEnvironment(); StreamTableEnvironment fsTableEnv = StreamTableEnvironment.create(fsEnv, fsSettings); //或者 TableEnvironment fsTableEnv = TableEnvironment.create(fsSettings); // Flink Batch query import org.apache.flink.api.java.ExecutionEnvironment; import org.apache.flink.table.api.java.BatchTableEnvironment; ExecutionEnvironment fbEnv = ExecutionEnvironment.getExecutionEnvironment(); BatchTableEnvironment fbTableEnv = BatchTableEnvironment.create(fbEnv); // Blink Streaming query import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.table.api.EnvironmentSettings; import org.apache.flink.table.api.java.StreamTableEnvironment; StreamExecutionEnvironment bsEnv = StreamExecutionEnvironment.getExecutionEnvironment(); EnvironmentSettings bsSettings = EnvironmentSettings.newInstance().useBlinkPlanner().inStreamingMode().build(); StreamTableEnvironment bsTableEnv = StreamTableEnvironment.create(bsEnv, bsSettings); //或者 TableEnvironment bsTableEnv = TableEnvironment.create(bsSettings); // Blink Batch query import org.apache.flink.table.api.EnvironmentSettings; import org.apache.flink.table.api.TableEnvironment; EnvironmentSettings bbSettings = EnvironmentSettings.newInstance().useBlinkPlanner().inBatchMode().build(); TableEnvironment bbTableEnv = TableEnvironment.create(bbSettings); ``` 如果在 lib 目录下只存在一个 planner,则可以使用 useAnyPlanner 来创建指定的 EnvironmentSettings。 ### 5.1.7 Table API & SQL 应用程序的结构 批处理和流处理的 Table API & SQL 作业都有相同的模式,它们的代码结构如下: ```java //根据前面内容创建一个 TableEnvironment,指定是批作业还是流作业 TableEnvironment tableEnv = ...; //用下面的其中一种方式注册一个 Table tableEnv.registerTable("table1", ...) tableEnv.registerTableSource("table2", ...); tableEnv.registerExternalCatalog("extCat", ...); //注册一个 TableSink tableEnv.registerTableSink("outputTable", ...); //根据一个 Table API 查询创建一个 Table Table tapiResult = tableEnv.scan("table1").select(...); //根据一个 SQL 查询创建一个 Table Table sqlResult = tableEnv.sqlQuery("SELECT ... FROM table2 ... "); //将 Table API 或者 SQL 的结果发送给 TableSink tapiResult.insertInto("outputTable"); //运行 tableEnv.execute("java_job"); ``` ### 5.1.8 Catalog 中注册 Table Table 有两种类型,输入表和输出表,可以在 Table API & SQL 查询中引用输入表并提供输入数据,输出表可以用于将 Table API & SQL 的查询结果发送到外部系统。输出表可以通过 TableSink 来注册,输入表可以从各种数据源进行注册: + 已经存在的 Table 对象,通过是 Table API 或 SQL 查询的结果 + 连接了外部系统的 TableSource,比如文件、数据库、MQ + 从 DataStream 或 DataSet 程序中返回的 DataStream 和 DataSet #### 注册 Table 在 TableEnvironment 中可以像下面这样注册一个 Table: ```java //创建一个 TableEnvironment TableEnvironment tableEnv = ...; // see "Create a TableEnvironment" section //projTable 是一个简单查询的结果 Table projTable = tableEnv.scan("X").select(...); //将 projTable 表注册为 projectedTable 表 tableEnv.registerTable("projectedTable", projTable); ``` #### 注册 TableSource #### 注册 TableSink ### 5.1.9 注册外部的 Catalog ### 5.1.10 查询 Table #### Table API #### SQL #### Table API & SQL ### 5.1.11 提交 Table ### 5.1.12 翻译并执行查询 加入知识星球可以看到上面文章:https://t.zsxq.com/MNBEYvf ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-09-25-zsxq.jpg) ### 5.1.13 小结与反思 本节介绍了 Flink 新的 planner,然后详细的和之前的 planner 做了对比,然后对 Table API & SQL 中的概念做了介绍,还通过样例去介绍了它们的通用 API。 ================================================ FILE: books/flink-in-action-5.2.md ================================================ --- toc: true title: 《Flink 实战与性能优化》—— Flink Table API & SQL 功能 date: 2021-07-27 tags: - Flink - 大数据 - 流式计算 --- ## 5.2 Flink Table API & SQL 功能 在 5.1 节中对 Flink Table API & SQL 的概述和常见 API 都做了介绍,这篇文章先来看下其与 DataStream 和 DataSet API 的集成。 ### 5.2.1 Flink Table 和 SQL 与 DataStream 和 DataSet 集成 两个 planner 都可以与 DataStream API 集成,只有以前的 planner 才可以集成 DataSet API,所以下面讨论 DataSet API 都是和以前的 planner 有关。 Table API & SQL 查询与 DataStream 和 DataSet 程序集成是非常简单的,比如可以通过 Table API 或者 SQL 查询外部表数据,进行一些预处理后,然后使用 DataStream 或 DataSet API 继续处理一些复杂的计算,另外也可以将 DataStream 或 DataSet 处理后的数据利用 Table API 或者 SQL 写入到外部表去。总而言之,它们之间互相转换或者集成比较容易。 #### Scala 的隐式转换 Scala Table API 提供了 DataSet、DataStream 和 Table 类的隐式转换,可以通过导入 `org.apache.flink.table.api.scala._` 或者 `org.apache.flink.api.scala._` 包来启用这些转换。 #### 将 DataStream 或 DataSet 注册为 Table DataStream 或者 DataSet 可以注册为 Table,结果表的 schema 取决于已经注册的 DataStream 和 DataSet 的数据类型。你可以像下面这种方式转换: ```java StreamTableEnvironment tableEnv = ...; DataStream> stream = ... //将 DataStream 注册为 myTable 表 tableEnv.registerDataStream("myTable", stream); //将 DataStream 注册为 myTable2 表(表中的字段为 myLong、myString) tableEnv.registerDataStream("myTable2", stream, "myLong, myString"); ``` #### 将 DataStream 或 DataSet 转换为 Table 除了可以将 DataStream 或 DataSet 注册为 Table,还可以将它们转换为 Table,代码如下所示,转换之后再去使用 Table API 查询就比较方便了。 ```java StreamTableEnvironment tableEnv = ...; DataStream> stream = ... //将 DataStream 转换成 Table Table table1 = tableEnv.fromDataStream(stream); //将 DataStream 转换成 Table Table table2 = tableEnv.fromDataStream(stream, "myLong, myString"); ``` #### 将 Table 转换成 DataStream 或 DataSet Table 可以转换为 DataStream 或 DataSet,这样就可以在 Table API 或 SQL 查询的结果上运行自定义的 DataStream 或 DataSet 程序。当将一个 Table 转换成 DataStream 或 DataSet 时,需要指定结果 DataStream 或 DataSet 的数据类型,最方便的数据类型是 Row,下面几个数据类型表示不同的功能: + Row:字段按位置映射,任意数量的字段,支持 null 值,没有类型安全访问。 + POJO:字段按名称映射,POJO 属性必须按照 Table 中的属性来命名,任意数量的字段,支持 null 值,类型安全访问 + Case Class:字段按位置映射,不支持 null 值,类型安全访问。 + Tuple:按位置映射字段,限制为 22(Scala)或 25(Java)字段,不支持 null 值,类型安全访问。 + 原子类型:Table 必须具有单个字段,不支持 null 值,类型安全访问。 ##### 将 Table 转换成 DataStream 流查询的结果表会动态更新,即每个新的记录到达输入流时结果就会发生变化。所以在将 Table 转换成 DataStream 就需要对表的更新进行编码,有两种将 Table 转换为 DataStream 的模式: + 追加模式(Append Mode):这种模式只能在动态表仅通过 INSERT 更改修改时才能使用,即仅追加,之前发出的结果不会更新。 + 撤回模式(Retract Mode):任何时刻都可以使用此模式,它使用一个 boolean 标志来编码 INSERT 和 DELETE 的更改。 两种模式的代码如下所示: ```java StreamTableEnvironment tableEnv = ...; //有两个字段(name、age) 的 Table Table table = ... //通过指定类,将表转换为一个 append DataStream DataStream dsRow = tableEnv.toAppendStream(table, Row.class); //将表转换为 Tuple2 的 append DataStream TupleTypeInfo> tupleType = new TupleTypeInfo<>(Types.STRING(), Types.INT()); DataStream> dsTuple = tableEnv.toAppendStream(table, tupleType); //将表转换为一个 Retract DataStream Row DataStream> retractStream = tableEnv.toRetractStream(table, Row.class); ``` ##### 将 Table 转换成 DataSet 将 Table 转换成 DataSet 的样例如下: ```java BatchTableEnvironment tableEnv = BatchTableEnvironment.create(env); //有两个字段(name、age) 的 Table Table table = ... //通过指定一个类将表转换为一个 Row DataSet DataSet dsRow = tableEnv.toDataSet(table, Row.class); //将表转换为 Tuple2 的 DataSet TupleTypeInfo> tupleType = new TupleTypeInfo<>(Types.STRING(), Types.INT()); DataSet> dsTuple = tableEnv.toDataSet(table, tupleType); ``` ### 5.2.2 查询优化 Flink 使用 Calcite 来优化和翻译查询,以前的 planner 不会去优化 join 的顺序,而是按照查询中定义的顺序去执行。通过提供一个 CalciteConfig 对象来调整在不同阶段应用的优化规则集,这个可以通过调用 `CalciteConfig.createBuilder()` 获得的 builder 来创建,并且可以通过调用`tableEnv.getConfig.setCalciteConfig(calciteConfig)` 来提供给 TableEnvironment。而在 Blink planner 中扩展了 Calcite 来执行复杂的查询优化,这包括一系列基于规则和成本的优化,比如: + 基于 Calcite 的子查询去相关性 + Project pruning + Partition pruning + Filter push-down + 删除子计划中的重复数据以避免重复计算 + 重写特殊的子查询,包括两部分: - 将 IN 和 EXISTS 转换为 left semi-joins - 将 NOT IN 和 NOT EXISTS 转换为 left anti-join + 重排序可选的 join - 通过启用 table.optimizer.join-reorder-enabled 注意:IN/EXISTS/NOT IN/NOT EXISTS 目前只支持子查询重写中的连接条件。 #### 解释 Table Table API 提供了一种机制来解释计算 Table 的逻辑和优化查询计划。你可以通过 `TableEnvironment.explain(table)` 或者 `TableEnvironment.explain()` 方法来完成。`explain(table)` 会返回给定计划的 Table,`explain()` 会返回多路 Sink 计划的结果(主要用于 Blink planner)。它返回一个描述三个计划的字符串: + 关系查询的抽象语法树,即未优化的逻辑查询计划 + 优化的逻辑查询计划 + 实际执行计划 以下代码演示了一个 Table 示例: ```java StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); StreamTableEnvironment tEnv = StreamTableEnvironment.create(env); DataStream> stream1 = env.fromElements(new Tuple2<>(1, "hello")); DataStream> stream2 = env.fromElements(new Tuple2<>(1, "hello")); Table table1 = tEnv.fromDataStream(stream1, "count, word"); Table table2 = tEnv.fromDataStream(stream2, "count, word"); Table table = table1.where("LIKE(word, 'F%')").unionAll(table2); System.out.println(tEnv.explain(table)); ``` 通过 `explain(table)` 方法返回的结果: ``` == Abstract Syntax Tree == LogicalUnion(all=[true]) LogicalFilter(condition=[LIKE($1, _UTF-16LE'F%')]) FlinkLogicalDataStreamScan(id=[1], fields=[count, word]) FlinkLogicalDataStreamScan(id=[2], fields=[count, word]) == Optimized Logical Plan == DataStreamUnion(all=[true], union all=[count, word]) DataStreamCalc(select=[count, word], where=[LIKE(word, _UTF-16LE'F%')]) DataStreamScan(id=[1], fields=[count, word]) DataStreamScan(id=[2], fields=[count, word]) == Physical Execution Plan == Stage 1 : Data Source content : collect elements with CollectionInputFormat Stage 2 : Data Source content : collect elements with CollectionInputFormat Stage 3 : Operator content : from: (count, word) ship_strategy : REBALANCE Stage 4 : Operator content : where: (LIKE(word, _UTF-16LE'F%')), select: (count, word) ship_strategy : FORWARD Stage 5 : Operator content : from: (count, word) ship_strategy : REBALANCE ``` ### 5.2.3 数据类型 ### 5.2.4 时间属性 #### Processing Time #### Event time ### 5.2.5 SQL Connector **使用代码** **使用 YAML 文件** **使用 DDL** ### 5.2.6 SQL Client ### 5.2.7 Hive 加入知识星球可以看到上面文章:https://t.zsxq.com/MNBEYvf ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-09-25-zsxq.jpg) ### 5.2.8 小结与反思 本章节继续介绍了 Flink Table API & SQL 中的部分 API,然后讲解了 Flink 之前的 planner 和 Blink planner 在某些特性上面的区别,还讲解了 SQL Connector,最后介绍了 SQL Client 和 Hive。 本章讲解了 Flink Table API & SQL 相关的概述,另外还介绍了它们的 API 使用方式,除此之外还对 Connectors、SQL Client、Hive 做了一定的讲解。 ================================================ FILE: books/flink-in-action-6.1.md ================================================ --- toc: true title: 《Flink 实战与性能优化》—— Flink CEP 简介及其使用场景 date: 2021-07-28 tags: - Flink - 大数据 - 流式计算 --- # 第六章 —— 扩展库 Flink 源码中有单独的 `flink-libraries` 模块用来存放一些扩展库,比如 CEP、Gelly、Machine Learning、State Processor API。本章将分别介绍这几种扩展库以及如何应用在我们的项目中。 ## 6.1 Flink CEP 简介及其使用场景 Flink CEP 是一套极具通用性、易于使用的实时流式事件处理方案,它可以在实时数据中匹配复杂事件,用处很广,本节将带大家了解其功能和应用场景。 ### 6.1.1 CEP 简介? CEP 的英文全称是 Complex Event Processing,翻译成中文为复杂事件处理。它可以用于处理实时数据并在事件流到达时从事件流中提取信息,并根据定义的规则来判断事件是否匹配,如果匹配则会触发新的事件做出响应。除了支持单个事件的简单无状态的模式匹配(例如基于事件中的某个字段进行筛选过滤),也可以支持基于关联/聚合/时间窗口等多个事件的复杂有状态模式的匹配(例如判断用户下单事件后 30 分钟内是否有支付事件)。 因为这种事件匹配通常是根据提前制定好的规则去匹配的,而这些规则一般来说不仅多,而且复杂,所以就会引入一些规则引擎来处理这种复杂事件匹配。市面上常用的规则引擎有如下这些。 ### 6.1.2 规则引擎对比 目前开源的规则引擎有很多种,接下来将对比一下 Drools、Aviator、EasyRules、Esper、Flink CEP 之间的优势和劣势。 #### Drools Drools 是一款使用 Java 编写的开源规则引擎,通常用来解决业务代码与业务规则的分离,它内置的 Drools Fusion 模块也提供 CEP 的功能。 优势: + 功能较为完善,具有如系统监控、操作平台等功能。 + 规则支持动态更新。 劣势: + 以内存实现时间窗功能,无法支持较长跨度的时间窗。 + 无法有效支持定时触达(如用户在浏览发生一段时间后触达条件判断)。 #### Aviator Aviator 是一个高性能、轻量级的 Java 语言实现的表达式求值引擎,主要用于各种表达式的动态求值。 优势: + 支持大部分运算操作符。 + 支持函数调用和自定义函数。 + 支持正则表达式匹配。 + 支持传入变量并且性能优秀。 劣势: + 没有 if else、do while 等语句,没有赋值语句,没有位运算符。 #### EasyRules EasyRules 集成了 MVEL 和 SpEL 表达式的一款轻量级规则引擎。 优势: + 轻量级框架,学习成本低。 + 基于 POJO。 + 为定义业务引擎提供有用的抽象和简便的应用 + 支持从简单的规则组建成复杂规则 #### Esper Esper 设计目标为 CEP 的轻量级解决方案,可以方便的嵌入服务中,提供 CEP 功能。 优势: + 轻量级可嵌入开发,常用的 CEP 功能简单好用。 + EPL 语法与 SQL 类似,学习成本较低。 劣势: + 单机全内存方案,需要整合其他分布式和存储。 + 以内存实现时间窗功能,无法支持较长跨度的时间窗。 + 无法有效支持定时触达(如用户在浏览发生一段时间后触达条件判断)。 #### Flink CEP Flink 是一个流式系统,具有高吞吐低延迟的特点,Flink CEP 是一套极具通用性、易于使用的实时流式事件处理方案。 优势: + 继承了 Flink 高吞吐的特点 + 事件支持存储到外部,可以支持较长跨度的时间窗。 + 可以支持定时触达(用 followedBy + PartternTimeoutFunction 实现) 劣势: + 无法动态更新规则(痛点) ### 6.1.3 Flink CEP 简介 ### 6.1.4 Flink CEP 动态更新规则 ### 6.1.5 Flink CEP 使用场景分析 #### 实时反作弊和风控 #### 实时营销 #### 实时网络攻击检测 ### 6.1.6 小结与反思 加入知识星球可以看到上面文章:https://t.zsxq.com/nMR7ufq ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-09-25-zsxq.jpg) ================================================ FILE: books/flink-in-action-6.2.md ================================================ --- toc: true title: 《Flink 实战与性能优化》—— 使用 Flink CEP 处理复杂事件 date: 2021-07-29 tags: - Flink - 大数据 - 流式计算 --- ## 6.2 使用 Flink CEP 处理复杂事件 6.1 节中介绍 Flink CEP 和其使用场景,本节将详细介绍 Flink CEP 的 API,教会大家如何去使用 Flink CEP。 ### 6.2.1 准备依赖 要开发 Flink CEP 应用程序,首先你得在项目的 `pom.xml` 中添加依赖。 ```xml org.apache.flink flink-cep_${scala.binary.version} ${flink.version} ``` 这个依赖有两种,一个是 Java 版本的,一个是 Scala 版本,你可以根据项目的开发语言自行选择。 ### 6.2.2 Flink CEP 入门应用程序 准备好依赖后,我们开始第一个 Flink CEP 应用程序,这里我们只做一个简单的数据流匹配,当匹配成功后将匹配的两条数据打印出来。首先定义实体类 Event 如下: ```java public class Event { private Integer id; private String name; } ``` 然后构造读取 Socket 数据流将数据进行转换成 Event,代码如下: ```java SingleOutputStreamOperator eventDataStream = env.socketTextStream("127.0.0.1", 9200) .flatMap(new FlatMapFunction() { @Override public void flatMap(String s, Collector collector) throws Exception { if (StringUtil.isNotEmpty(s)) { String[] split = s.split(","); if (split.length == 2) { collector.collect(new Event(Integer.valueOf(split[0]), split[1])); } } } }); ``` 接着就是定义 CEP 中的匹配规则了,下面的规则表示第一个事件的 id 为 42,紧接着的第二个事件 id 要大于 10,满足这样的连续两个事件才会将这两条数据进行打印出来。 ```java Pattern pattern = Pattern.begin("start").where( new SimpleCondition() { @Override public boolean filter(Event event) { log.info("start {}", event.getId()); return event.getId() == 42; } } ).next("middle").where( new SimpleCondition() { @Override public boolean filter(Event event) { log.info("middle {}", event.getId()); return event.getId() >= 10; } } ); CEP.pattern(eventDataStream, pattern).select(new PatternSelectFunction() { @Override public String select(Map> p) throws Exception { StringBuilder builder = new StringBuilder(); log.info("p = {}", p); builder.append(p.get("start").get(0).getId()).append(",").append(p.get("start").get(0).getName()).append("\n") .append(p.get("middle").get(0).getId()).append(",").append(p.get("middle").get(0).getName()); return builder.toString(); } }).print();//打印结果 ``` 然后笔者在终端开启 Socket,输入的两条数据如下: ``` 42,zhisheng 20,zhisheng ``` 作业打印出来的日志如下图所示: ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-10-29-072247.png) 整个作业 print 出来的结果如下图所示: ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-10-29-072320.png) 好了,一个完整的 Flink CEP 应用程序如上,相信你也能大概理解上面的代码,接着来详细的讲解一下 Flink CEP 中的 Pattern API。 ### 6.2.3 Pattern API 你可以通过 Pattern API 去定义从流数据中匹配事件的 Pattern,每个复杂 Pattern 都是由多个简单的 Pattern 组成的,拿前面入门的应用来讲,它就是由 `start` 和 `middle` 两个简单的 Pattern 组成的,在其每个 Pattern 中都只是简单的处理了流数据。在处理的过程中需要标示该 Pattern 的名称,以便后续可以使用该名称来获取匹配到的数据,如 `p.get("start").get(0)` 它就可以获取到 Pattern 中匹配的第一个事件。接下来我们先来看下简单的 Pattern 。 #### 单个 Pattern **数量** **条件** #### 组合 Pattern #### Group Pattern #### 事件匹配跳过策略 ### 6.2.4 检测 Pattern #### 选择 Pattern ### 6.2.5 CEP 时间属性 #### 根据事件时间处理延迟数据 #### 时间上下文 加入知识星球可以看到上面文章:https://t.zsxq.com/nMR7ufq ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-09-25-zsxq.jpg) ### 6.2.6 小结与反思 本节开始通过一个 Flink CEP 案例教大家上手,后面通过讲解 Flink CEP 的 Pattern API,更多详细的还是得去看官网文档,其实也建议大家好好的跟着官网的文档过一遍所有的 API,并跟着敲一些样例来实现,这样在开发需求的时候才能够及时的想到什么场景下该使用哪种 API,接着教了大家如何将 Pattern 与数据流结合起来匹配并获取匹配的数据,最后讲了下 CEP 中的时间概念。 你公司有使用 Flink CEP 吗?通常使用哪些 API 居多? ================================================ FILE: books/flink-in-action-6.3.md ================================================ --- toc: true title: 《Flink 实战与性能优化》—— Flink 扩展库——State Processor API date: 2021-07-30 tags: - Flink - 大数据 - 流式计算 --- ## 6.3 Flink 扩展库——State Processor API State Processor API 功能是在 1.9 版本中新增加的一个功能,本节将带你了解一下其功能和如何使用? ### 6.3.1 State Processor API 简介 能够从外部访问 Flink 作业的状态一直用户迫切需要的功能之一,在 Apache Flink 1.9.0 中新引入了 State Processor API,该 API 让用户可以通过 Flink DataSet 作业来灵活读取、写入和修改 Flink 的 Savepoint 和 Checkpoint。 ### 6.3.2 在 Flink 1.9 之前是如何处理状态的? 一般来说,大多数的 Flink 作业都是有状态的,并且随着作业运行的时间越来越久,就会累积越多越多的状态,如果因为故障导致作业崩溃可能会导致作业的状态都丢失,那么对于比较重要的状态来说,损失就会很大。为了保证作业状态的一致性和持久性,Flink 从一开始使用的就是 Checkpoint 和 Savepoint 来保存状态,并且可以从 Savepoint 中恢复状态。在 Flink 的每个新 Release 版本中,Flink 社区添加了越来越多与状态相关的功能以提高 Checkpoint 的速度和恢复速度。 有的时候,用户可能会有这些需求场景,比如从第三方外部系统访问作业的状态、将作业的状态信息迁移到另一个应用程序等,目前现有支持查询作业状态的功能 Queryable State,但是在 Flink 中目前该功能只支持根据 Key 查找,并且不能保证返回值的一致性。另外该功能不支持添加和修改作业的状态,所以适用的场景还是比较有限。 ### 6.3.3 使用 State Processor API 读写作业状态 在 1.9 版本中的 State Processor API,它完全和之前不一致,该功能使用 InputFormat 和 OutputFormat 扩展了 DataSet API 以读取和写入 Checkpoint 和 Savepoint 数据。由于 DataSet 和 Table API 的互通性,所以也可以使用 Table API 或者 SQL 查询和分析状态的数据。例如,再获取到正在运行的流作业状态的 Checkpoint 后,可以使用 DataSet 批处理程序对其进行分析,以验证该流作业的运行是否正确。另外 State Processor API 还可以修复不一致的状态信息,它提供了很多方法来开发有状态的应用程序,这些方法在以前的版本中因为设计的问题导致作业在启动后不能再修改,否则状态可能会丢失。现在,你可以任意修改状态的数据类型、调整算子的最大并行度、拆分或合并算子的状态、重新分配算子的 uid 等。 如果要使用 State Processor API 去读写作业的状态,你需要添加下面的依赖: ```xml org.apache.flink flink-state-processor-api_${scala.binary.version} ${flink.version} ``` ### 6.3.4 使用 DataSet 读取作业状态 State Processor API 将作业的状态映射到一个或多个可以单独处理的数据集,为了能够使用该 API,需要先了解这个映射的工作方式,首先来看下有状态的 Flink 作业是什么样子的。Flink 作业是由很多算子组成,通常是一个或多个数据源(Source)、一些实际处理数据的算子(比如 Map/Filter/FlatMap 等)和一个或者多个 Sink。每个算子会在一个或者多个任务中并行运行(取决于并行度),并且可以使用不同类型的状态,算子可能会有零个、一个或多个 Operator State,这些状态会组成一个以算子任务为范围的列表。如果是算子应用在 KeyedStream,它还有零个、一个或者多个 Keyed State,它们的作用域范围是从每个已处理数据中提取 Key,可以将 Keyed State 看作是一个分布式的 Map。 State Processor API 现在提供了读取、新增和修改 Savepoint 数据的方法,比如从已加载的 Savepoint 中读取数据集,然后将数据集转换为状态并将其保存到 Savepoint。下面分别讲解下这三种方法该如何使用。 #### 读取现有的 Savepoint 读取状态首先需要指定一个 Savepoint(或者 Checkpoint) 的路径和状态后端存储的类型。 ```java ExecutionEnvironment bEnv = ExecutionEnvironment.getExecutionEnvironment(); ExistingSavepoint savepoint = Savepoint.load(bEnv, "hdfs://path/", new RocksDBStateBackend()); ``` 读取 Operator State 时,只需指定算子的 uid、状态名称和类型信息。 ```java DataSet listState = savepoint.readListState("zhisheng-uid", "list-state", Types.INT); DataSet unionState = savepoint.readUnionState("zhisheng-uid", "union-state", Types.INT); DataSet> broadcastState = savepoint.readBroadcastState("zhisheng-uid", "broadcast-state", Types.INT, Types.INT); ``` 如果在状态描述符(StateDescriptor)中使用了自定义类型序列化器 TypeSerializer,也可以指定它: ```java DataSet listState = savepoint.readListState( "zhisheng-uid", "list-state", Types.INT, new MyCustomIntSerializer()); ``` #### 写入新的 Savepoint #### 修改现有的 Savepoint ### 6.3.5 为什么要使用 DataSet API? ### 6.3.6 小结与反思 加入知识星球可以看到上面文章:https://t.zsxq.com/nMR7ufq ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-09-25-zsxq.jpg) ================================================ FILE: books/flink-in-action-6.4.md ================================================ --- toc: true title: 《Flink 实战与性能优化》—— Flink 扩展库——Machine Learning date: 2021-08-01 tags: - Flink - 大数据 - 流式计算 --- ## 6.4 Flink 扩展库——Machine Learning 随着人工智能的火热,机器学习这门技术也变得异常重要,Flink 作为一个数据处理的引擎,虽然目前在该方面还较弱,但是在 Flink Forward Asia 2019 北京站后,阿里开源了 [Alink](https://github.com/alibaba/Alink) 平台的核心代码,并上传了一系列的算法库,该项目是基于 Flink 的通用算法平台,开发者和数据分析师可以利用 Alink 提供的一系列算法来构建软件功能,例如统计分析、机器学习、实时预测、个性化推荐和异常检测。相信未来 Flink 的机器学习库将会应用到更多的场景去,本节将带你了解一下 Flink 中的机器学习库。 ### 6.4.1 Flink-ML 简介 ML 是 Machine Learning 的简称,Flink-ML 是 Flink 的机器学习类库。在 Flink 1.9 之前该类库是存在 `flink-libraries` 模块下的,但是在 Flink 1.9 版本中,为了支持 [FLIP-39](https://cwiki.apache.org/confluence/display/FLINK/FLIP-39+Flink+ML+pipeline+and+ML+libs) ,所以该类库被移除了。 建立 FLIP-39 的目的主要是增强 Flink-ML 的可伸缩性和易用性。通常使用机器学习的有两类人,一类是机器学习算法库的开发者,他们需要一套标准的 API 来实现算法,每个机器学习算法会在这些 API 的基础上实现;另一类用户是直接利用这些现有的机器学习算法库去训练数据模型,整个训练是要通过很多转换或者算法才能完成的,所以如果能够提供 ML Pipeline,那么对于后一类用户来说绝对是一种福音。虽然在 1.9 中移除了之前的 Flink-ML 模块,但是在 Flink 项目下出现了一个 `flink-ml-parent` 的模块,该模块有两个子模块 `flink-ml-api` 和 `flink-ml-lib`。 `flink-ml-api` 模块增加了 ML Pipeline 和 MLLib 的接口,它的类结构图如下图所示。 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-10-22-124512.png) 主要接口如下所示: + Transformer: Transformer 是一种可以将一个表转换成另一个表的算法 + Model: Model 是一种特别的 Transformer,它继承自 Transformer。它通常是由 Estimator 生成,Model 用于推断,输入一个数据表会生成结果表。 + Estimator: Estimator 是一个可以根据一个数据表生成一个模型的算法。 + Pipeline: Pipeline 描述的是机器学习的工作流,它将很多 Transformer 和 Estimator 连接在一起成一个工作流。 + PipelineStage: PipelineStage 是 Pipeline 的基础节点,Transformer 和 Estimator 两个都继承自 PipelineStage 接口。 + Params: Params 是一个参数容器。 + WithParams: WithParams 有一个保存参数的 Params 容器。通常会使用在 PipelineStage 里面,因为几乎所有的算法都需要参数。 Flink-ML 的 pipeline 流程如下图所示: ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-10-22-135555.png) `flink-ml-lib` 模块包括了 DenseMatrix、DenseVector、SparseVector 等类的基本操作。这两个模块是 Flink-ML 的基础模块,相信社区在后面的稳定版本一定会带来更加完善的 Flink-ML 库。 ### 6.4.2 使用 Flink-ML 虽然在 Flink 1.9 中已经移除了 Flink-ML 模块,但是在之前的版本还是支持的,如果你们公司使用的是低于 1.9 的版本,那么还是可以使用的,在使用之前引入依赖(假设使用的是 Flink 1.8 版本): ```xml org.apache.flink flink-ml_2.11 1.8.0 ``` 另外如果是要运行的话还是要将 opt 目录下的 flink-ml_2.11-1.8.0.jar 移到 lib 目录下。下面演示下如何训练多元线性回归模型: ```Scala //带标签的特征向量 val trainingData: DataSet[LabeledVector] = ... val testingData: DataSet[Vector] = ... val dataSet: DataSet[LabeledVector] = ... //使用 Splitter 将数据集拆分成训练数据和测试数据 val trainTestData: DataSet[TrainTestDataSet] = Splitter.trainTestSplit(dataSet) val trainingData: DataSet[LabeledVector] = trainTestData.training val testingData: DataSet[Vector] = trainTestData.testing.map(lv => lv.vector) val mlr = MultipleLinearRegression() .setStepsize(1.0) .setIterations(100) .setConvergenceThreshold(0.001) mlr.fit(trainingData) //已经形成的模型可以用来预测数据了 val predictions: DataSet[LabeledVector] = mlr.predict(testingData) ``` ### 6.4.3 使用 Flink-ML Pipeline ### 6.4.4 Alink 介绍 ### 6.4.5 小结与反思 加入知识星球可以看到上面文章:https://t.zsxq.com/nMR7ufq ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-09-25-zsxq.jpg) ================================================ FILE: books/flink-in-action-6.5.md ================================================ --- toc: true title: 《Flink 实战与性能优化》—— Flink 扩展库——Gelly date: 2021-08-02 tags: - Flink - 大数据 - 流式计算 --- ## 6.5 Flink 扩展库——Gelly 在 1.9 版本中还剩最后一个扩展库就是 Gelly,本节将带你了解一下 Gelly 的功能以及如何使用。 ### 6.5.1 Gelly 简介 Gelly 是 Flink 的图 API 库,它包含了一组旨在简化 Flink 中图形分析应用程序开发的方法和实用程序。在 Gelly 中,可以使用类似于批处理 API 提供的高级函数来转换和修改图。Gelly 提供了创建、转换和修改图的方法以及图算法库。 ### 6.5.2 使用 Gelly 因为 Gelly 是 Flink 项目中库的一部分,它本身不在 Flink 的二进制包中,所以运行 Gelly 项目(Java 应用程序)是需要将 `opt/flink-gelly_2.11-1.9.0.jar` 移动到 `lib` 目录中,如果是 Scala 应用程序则需要将 `opt/flink-gelly-scala_2.11-1.9.0.jar` 移动到 `lib` 中,接着运行下面的命令就可以运行一个 flink-gelly-examples 项目。 ``` ./bin/flink run examples/gelly/flink-gelly-examples_2.11-1.9.0.jar \ --algorithm GraphMetrics --order directed \ --input RMatGraph --type integer --scale 20 --simplify directed \ --output print ``` 接下来可以在 UI 上看到运行的结果如下图所示: ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-10-19-155600.png) 如果是自己创建的 Gelly Java 应用程序,则需要添加如下依赖: ```xml org.apache.flink flink-gelly_${scala.binary.version} ${flink.version} ``` 如果是 Gelly Scala 应用程序,添加下面的依赖: ```xml org.apache.flink flink-gelly-scala_${scala.binary.version} ${flink.version} ``` ### 6.5.3 Gelly API 引入好依赖后,接着将介绍一下 Gelly 该如何使用。 #### Graph 介绍 在 Gelly 中,一个图(Graph)由顶点的数据集(DataSet)和边的数据集(DataSet)组成。图中的顶点由 Vertex 类型来表示,一个 Vertex 由唯一的 ID 和一个值来表示。其中 Vertex 的 ID 必须是全局唯一的值,且实现了 Comparable 接口。如果节点不需要由任何值,则该值类型可以声明成 NullValue 类型。 ```java //创建一个 Vertex Vertex v = new Vertex(1L, "foo"); //创建一个 Vertex Vertex v = new Vertex(1L, NullValue.getInstance()); ``` Graph 中的边由 Edge 类型来表示,一个 Edge 通常由源顶点的 ID,目标顶点的 ID 以及一个可选的值来表示。其中源顶点和目标顶点的类型必须与 Vertex 的 ID 类型相同。同样的,如果边不需要由任何值,则该值类型可以声明成 NullValue 类型。 ```java Edge e = new Edge(1L, 2L, 0.5); //反转此 edge 的源和目标 Edge reversed = e.reverse(); Double weight = e.getValue(); // weight = 0.5 ``` 在 Gelly 中,一个 Edge 总是从源顶点指向目标顶点。如果图中每条边都能匹配一个从目标顶点到源顶点的 Edge,那么这个图可能是个无向图。同样地,无向图可以用这个方式来表示。 #### 创建 Graph 可以通过以下几种方式创建一个 Graph: + 从一个 Edge 数据集合和一个 Vertex 数据集合中创建图。 ```java ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment(); DataSet> vertices = ... DataSet> edges = ... Graph graph = Graph.fromDataSet(vertices, edges, env); ``` + 从一个表示边的 Tuple2 数据集合中创建图。Gelly 会将每个 Tuple2 转换成一个 Edge,其中第一个元素表示源顶点的 ID,第二个元素表示目标顶点的 ID,图中的顶点和边的 value 值均被设置为 NullValue。 ```java ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment(); DataSet> edges = ... Graph graph = Graph.fromTuple2DataSet(edges, env); ``` + 从一个 Tuple3 数据集和一个可选的 Tuple2 数据集中生成图。在这种情况下,Gelly 会将每个 Tuple3 转换成 Edge,其中第一个元素域是源顶点 ID,第二个域是目标顶点 ID,第三个域是边的值。同样的,每个 Tuple2 会转换成一个顶点 Vertex,其中第一个域是顶点的 ID,第二个域是顶点的 value。 ```java ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment(); DataSet> vertexTuples = env.readCsvFile("path/to/vertex/input").types(String.class, Long.class); DataSet> edgeTuples = env.readCsvFile("path/to/edge/input").types(String.class, String.class, Double.class); Graph graph = Graph.fromTupleDataSet(vertexTuples, edgeTuples, env); ``` + 从一个表示边数据的CSV文件和一个可选的表示节点的CSV文件中生成图。在这种情况下,Gelly会将表示边的CSV文件中的每一行转换成一个Edge,其中第一个域表示源顶点ID,第二个域表示目标顶点ID,第三个域表示边的值。同样的,表示节点的CSV中的每一行都被转换成一个Vertex,其中第一个域表示顶点的ID,第二个域表示顶点的值。为了通过GraphCsvReader生成图,需要指定每个域的类型,可以使用 types、edgeTypes、vertexTypes、keyType 中的方法。 ```java //创建一个具有字符串 Vertex id、Long Vertex 和双边缘的图 Graph graph = Graph.fromCsvReader("path/to/vertex/input", "path/to/edge/input", env) .types(String.class, Long.class, Double.class); //创建一个既没有顶点值也没有边值的图 Graph simpleGraph = Graph.fromCsvReader("path/to/edge/input", env).keyType(Long.class); ``` + 从一个边的集合和一个可选的顶点的集合中生成图。如果在图创建的时候顶点的集合没有传入,Gelly 会依据数据的边数据集合自动地生成一个 Vertex 集合。这种情况下,创建的节点是没有值的。或者也可以像下面一样,在创建图的时候提供一个 MapFunction 方法来初始化节点的值。 ```java List> vertexList = new ArrayList... List> edgeList = new ArrayList... Graph graph = Graph.fromCollection(vertexList, edgeList, env); //将顶点值初始化为顶点ID Graph graph = Graph.fromCollection(edgeList, new MapFunction() { public Long map(Long value) { return value; } }, env); ``` #### Graph 属性 Gelly 提供了下列方法来查询图的属性和指标: ```java DataSet> getVertices() //获取边缘数据集 DataSet> getEdges() //获取顶点的 id 数据集 DataSet getVertexIds() DataSet> getEdgeIds() DataSet> inDegrees() DataSet> outDegrees() DataSet> getDegrees() long numberOfVertices() long numberOfEdges() DataSet> getTriplets() ``` #### Graph 转换 Graph 转换方式有下面几种方式: + Map:Gelly 提供了专门的用于转换顶点值和边值的方法。mapVertices 和 mapEdges 会返回一个新图,图中的每个顶点和边的 ID 不会改变,但是顶点和边的值会根据用户自定义的映射方法进行修改。这些映射方法同时也可以修改顶点和边的值的类型。 + Translate:Gelly 还提供了专门用于根据用户定义的函数转换顶点和边的 ID 和值的值及类型的方法(translateGraphIDs/translateVertexValues/translateEdgeValues),是Map 功能的升级版,因为 Map 操作不支持修订顶点和边的 ID。 + Filter:Gelly 支持在图中的顶点上或边上执行一个用户指定的 filter 转换。filterOnEdges 会根据提供的在边上的断言在原图的基础上生成一个新的子图,注意,顶点的数据不会被修改。同样的 filterOnVertices 在原图的顶点上进行 filter 转换,不满足断言条件的源节点或目标节点会在新的子图中移除。该子图方法支持同时对顶点和边应用 filter 函数,如下图所示。 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-10-19-165050.jpg) + Reverse:Gelly中得reverse()方法用于在原图的基础上,生成一个所有边方向与原图相反的新图。 + Undirected:在前面的内容中,我们提到过,Gelly中的图通常都是有向的,而无向图可以通过对所有边添加反向的边来实现,出于这个目的,Gelly提供了getUndirected()方法,用于获取原图的无向图。 + Union:Gelly的union()操作用于联合当前图和指定的输入图,并生成一个新图,在输出的新图中,相同的节点只保留一份,但是重复的边会保留。如下图所示: ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-10-19-165224.jpg) + Difference:Gelly提供了difference()方法用于发现当前图与指定的输入图之间的差异。 + Intersect:Gelly提供了intersect()方法用于发现两个图中共同存在的边,并将相同的边以新图的方式返回。相同的边指的是具有相同的源顶点,相同的目标顶点和相同的边值。返回的新图中,所有的节点没有任何值,如果需要节点值,可以使用joinWithVertices()方法去任何一个输入图中检索。 #### Graph 变化 Gelly 内置下列方法以支持对一个图进行节点和边的增加/移除操作: ```java Graph addVertex(final Vertex vertex) Graph addVertices(List> verticesToAdd) Graph addEdge(Vertex source, Vertex target, EV edgeValue) Graph addEdges(List> newEdges) Graph removeVertex(Vertex vertex) Graph removeVertices(List> verticesToBeRemoved) Graph removeEdge(Edge edge) Graph removeEdges(List> edgesToBeRemoved) ``` #### Neighborhood Methods #### Graph 验证 ### 6.5.4 小结与反思 加入知识星球可以看到上面文章:https://t.zsxq.com/nMR7ufq ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-09-25-zsxq.jpg) 本章所讲的内容属于 Flink 的扩展库,包含了 CEP 复杂事件处理、State Processor API、Machine Learning 和 Gelly,各种都有讲解一些样例,但是没有过多深入的讲,但还是希望你可以在书本外自己去扩充这些内容的知识点。 ================================================ FILE: books/flink-in-action-7.1.md ================================================ --- toc: true title: 《Flink 实战与性能优化》—— Flink 配置详解及如何配置高可用? date: 2021-08-03 tags: - Flink - 大数据 - 流式计算 --- # 第七章 —— Flink 作业环境部署 在第一章中介绍过 Flink 是可以以多种方式部署的,比如 Standalone、YARN、Mesos、K8S。本章将先对 Flink 中的所有配置文件做一个详细的讲解,接下来将讲解 JobManager 高可用部署相关的配置,最后会分别讲解如何在不同的平台上部署运行 Flink 作业。虽然在你们公司可能只会用到其中的一种,但是仍然建议你将每种方式都熟悉一下。 ## 7.1 Flink 配置详解及如何配置高可用? 在讲解如何部署 Flink 作业(在 7.2 节中会讲)之前,先来详细的看一下 Flink 中的所有配置文件以及文件中的各种配置代表的内容,这样对于后面部署和调优 Flink 作业有一定的帮助。 ### 7.1.1 Flink 配置详解 先来看下 Flink 配置文件目录中最重要的配置文件 `flink-conf.yaml` 的配置。 #### flink-conf.yaml 基础配置如下所示: ``` # jobManager 的IP地址 jobmanager.rpc.address: localhost # JobManager 的端口号 jobmanager.rpc.port: 6123 # JobManager JVM heap 内存大小 jobmanager.heap.size: 1024m # TaskManager JVM heap 内存大小 taskmanager.heap.size: 1024m # 每个 TaskManager 提供的任务 slots 数量大小 taskmanager.numberOfTaskSlots: 1 # 程序默认并行计算的个数 parallelism.default: 1 # 文件系统来源 # fs.default-scheme ``` 高可用性相关的配置如下所示: ``` # 可以选择 'NONE' 或者 'zookeeper'. # high-availability: zookeeper # 文件系统路径,让 Flink 在高可用性设置中持久保存元数据 # high-availability.storageDir: hdfs:///flink/ha/ # zookeeper 集群中仲裁者的机器 ip 和 port 端口号 # high-availability.zookeeper.quorum: localhost:2181 # 默认是 open,如果 zookeeper security 启用了该值会更改成 creator # high-availability.zookeeper.client.acl: open ``` 容错和 Checkpoint 相关的配置如下所示: ``` # 用于存储和检查点状态 # state.backend: filesystem # 存储检查点的数据文件和元数据的默认目录 # state.checkpoints.dir: hdfs://namenode-host:port/flink-checkpoints # savepoints 的默认目标目录(可选) # state.savepoints.dir: hdfs://namenode-host:port/flink-checkpoints # 用于启用/禁用增量 checkpoints 的标志 # state.backend.incremental: false ``` Web 前端相关的配置如下所示: ``` # 基于 Web 的运行时监视器侦听的地址. #jobmanager.web.address: 0.0.0.0 # Web 的运行时监视器端口 rest.port: 8081 # 是否从基于 Web 的 jobmanager 启用作业提交 # jobmanager.web.submit.enable: false ``` 高级配置如下所示: ``` # io.tmp.dirs: /tmp # 是否应在 TaskManager 启动时预先分配 TaskManager 管理的内存 # taskmanager.memory.preallocate: false # 类加载解析顺序,是先检查用户代码 jar(“child-first”)还是应用程序类路径(“parent-first”)。 默认设置指示首先从用户代码 jar 加载类 # classloader.resolve-order: child-first # 用于网络缓冲区的 JVM 内存的分数。 这决定了 TaskManager 可以同时拥有多少流数据交换通道以及通道缓冲的程度。 如果作业被拒绝或者您收到系统没有足够缓冲区的警告,请增加此值或下面的最小/最大值。 另请注意,“taskmanager.network.memory.min”和“taskmanager.network.memory.max”可能会覆盖此分数 # taskmanager.network.memory.fraction: 0.1 # taskmanager.network.memory.min: 67108864 # taskmanager.network.memory.max: 1073741824 ``` Flink 集群安全配置如下所示: ``` # 指示是否从 Kerberos ticket 缓存中读取 # security.kerberos.login.use-ticket-cache: true # 包含用户凭据的 Kerberos 密钥表文件的绝对路径 # security.kerberos.login.keytab: /path/to/kerberos/keytab # 与 keytab 关联的 Kerberos 主体名称 # security.kerberos.login.principal: flink-user # 以逗号分隔的登录上下文列表,用于提供 Kerberos 凭据(例如,`Client,KafkaClient`使用凭证进行 ZooKeeper 身份验证和 Kafka 身份验证) # security.kerberos.login.contexts: Client,KafkaClient ``` Zookeeper 安全配置如下所示: ``` # 覆盖以下配置以提供自定义 ZK 服务名称 # zookeeper.sasl.service-name: zookeeper # 该配置必须匹配 "security.kerberos.login.contexts" 中的列表(含有一个) # zookeeper.sasl.login-context-name: Client ``` HistoryServer 相关的配置如下所示: ``` # 你可以通过 bin/historyserver.sh (start|stop) 命令启动和关闭 HistoryServer # 将已完成的作业上传到的目录 # jobmanager.archive.fs.dir: hdfs:///completed-jobs/ # 基于 Web 的 HistoryServer 的地址 # historyserver.web.address: 0.0.0.0 # 基于 Web 的 HistoryServer 的端口号 # historyserver.web.port: 8082 # 以逗号分隔的目录列表,用于监视已完成的作业 # historyserver.archive.fs.dir: hdfs:///completed-jobs/ # 刷新受监控目录的时间间隔(以毫秒为单位) # historyserver.archive.fs.refresh-interval: 10000 ``` #### masters masters 配置文件中以 host:port 构成就行,如下所示: ``` localhost:8081 ``` #### slaves slaves 文件里面是每个 worker 节点的 IP/Hostname,每一个 worker 结点之后都会运行一个 TaskManager,一个一行,如下所示。 ``` localhost ``` ### 7.1.2 Log 的配置 在 Flink 的日志配置文件(`logback.xml` 或 `log4j.properties`)中有配置日志存储的地方,`logback.xml` 配置日志存储的路径是: ```xml ${log.file} false %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{60} %X{sourceThread} - %msg%n ``` `log4j.properties` 和 `log4j-cli.properties` 的配置日志存储的路径是: ```properties log4j.appender.file.file=${log.file} ``` 从上面两个配置可以看到日志的路径都是由 `log.file` 变量控制的,如果系统变量没有配置的话,则会使用 `bin/flink` 脚本里配置的值。 ``` log=$FLINK_LOG_DIR/flink-$FLINK_IDENT_STRING-client-$HOSTNAME.log log_setting=(-Dlog.file="$log" -Dlog4j.configuration=file:"$FLINK_CONF_DIR"/log4j-cli.properties -Dlogback.configurationFile=file:"$FLINK_CONF_DIR"/logback.xml) ``` 从上面可以看到 log 里配置的 FLINK_LOG_DIR 变量是在 bin 目录下的 config.sh 里初始化的。 ```sh DEFAULT_FLINK_LOG_DIR=$FLINK_HOME_DIR_MANGLED/log KEY_ENV_LOG_DIR="env.log.dir" if [ -z "${FLINK_LOG_DIR}" ]; then FLINK_LOG_DIR=$(readFromConfig ${KEY_ENV_LOG_DIR} "${DEFAULT_FLINK_LOG_DIR}" "${YAML_CONF}") fi ``` 从上面可以知道日志默认就是在 Flink 的 log 目录下,你可以通过在 `flink-conf.yaml` 配置文件中配置 `env.log.dir` 参数来更改保存日志的目录。另外通过源码可以发现,如果找不到 `log.file` 环境变量,则会去找 `web.log.path` 的配置,但是该配置在 Standalone 下是不起作用的,日志依旧是会在 `log` 目录,在 YARN 下是会起作用的。 ```java public static LogFileLocation find(Configuration config) { final String logEnv = "log.file"; String logFilePath = System.getProperty(logEnv); if (logFilePath == null) { LOG.warn("Log file environment variable '{}' is not set.", logEnv); logFilePath = config.getString(WebOptions.LOG_PATH); //该值为 web.log.path } // not configured, cannot serve log files if (logFilePath == null || logFilePath.length() < 4) { LOG.warn("JobManager log files are unavailable in the web dashboard. " + "Log file location not found in environment variable '{}' or configuration key '{}'.", logEnv, WebOptions.LOG_PATH); return new LogFileLocation(null, null); } String outFilePath = logFilePath.substring(0, logFilePath.length() - 3).concat("out"); LOG.info("Determined location of main cluster component log file: {}", logFilePath); LOG.info("Determined location of main cluster component stdout file: {}", outFilePath); return new LogFileLocation(resolveFileLocation(logFilePath), resolveFileLocation(outFilePath)); } /** * The log file location (may be in /log for standalone but under log directory when using YARN). */ public static final ConfigOption LOG_PATH = key("web.log.path") .noDefaultValue() .withDeprecatedKeys("jobmanager.web.log.path") .withDescription("Path to the log file (may be in /log for standalone but under log directory when using YARN)."); ``` 另外可能会在本地 IDE 中运行作业出不来日志的情况,这时请检查是否有添加日志的依赖。 ```xml org.slf4j slf4j-api 1.7.25 org.slf4j slf4j-simple 1.7.25 ``` ### 7.1.3 如何配置 JobManager 高可用? JobManager 协调每个 Flink 作业的部署,它负责调度和资源管理。默认情况下,每个 Flink 集群只有一个 JobManager 实例,这样就可能会产生单点故障,如果 JobManager 崩溃,则无法提交新作业且运行中的作业也会失败。如果保证 JobManager 的高可用,则可以避免这个问题。下面分别下如何搭建 Standalone 集群和 YARN 集群高可用的 JobManager。 #### 搭建 Standalone 集群高可用 JobManager Standalone 集群的 JobManager 高可用性的概念是:任何时候只有一个主 JobManager 和多个备 JobManager,以便在主节点失败时有新的 JobManager 接管集群。这样就保证了没有单点故障,一旦备 JobManager 接管集群,作业就可以依旧正常运行。主备 JobManager 实例之间没有明确的区别,每个 JobManager 都可以充当主备节点。例如,请考虑以下三个 JobManager 实例的设置。 ![](https://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/images/lsiuRC.jpg) **如何配置** 要启用 JobManager 高可用性功能,首先必须在配置文件 flink-conf.yaml 中将高可用性模式设置为 ZooKeeper,配置 ZooKeeper quorum,将所有 JobManager 主机及其 Web UI 端口写入配置文件。每个 ip:port 都是一个 ZooKeeper 服务器的 ip 及其端口,Flink 可以通过指定的地址和端口访问 ZooKeeper。另外就是高可用存储目录,JobManager 元数据保存在 `high-availability.storageDir` 指定的文件系统中,在 ZooKeeper 中仅保存了指向此状态的指针, 推荐这个目录是 HDFS、S3、Ceph、NFS 等,该文件系统中保存了 JobManager 恢复状态需要的所有元数据。 ```xml high-availability: zookeeper high-availability.zookeeper.quorum: ip1:2181 [,...],ip2:2181 high-availability.storageDir: hdfs:///flink/ha/ ``` Flink 利用 ZooKeeper 在所有正在运行的 JobManager 实例之间进行分布式协调。ZooKeeper 是独立于 Flink 的服务,通过 leader 选举和轻量级一致性状态存储提供高可靠的分布式协调服务。Flink 包含用于 Bootstrap ZooKeeper 安装的脚本。 它在我们的 Flink 安装路径下面 /conf/zoo.cfg 。 #### 搭建 YARN 集群高可用 JobManager ### 7.1.4 小结与反思 加入知识星球可以看到上面文章:https://t.zsxq.com/f66iAMz ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-09-25-zsxq.jpg) ================================================ FILE: books/flink-in-action-7.2.md ================================================ --- toc: true title: 《Flink 实战与性能优化》—— Flink 作业如何在 Standalone、YARN、Mesos、K8S 上部署运行? date: 2021-08-04 tags: - Flink - 大数据 - 流式计算 --- ## 7.2 Flink 作业如何在 Standalone、YARN、Mesos、K8S 上部署运行? 前面章节已经有很多学习案列带大家使用 Flink,不仅有讲将 Flink 应用程序在 IDEA 中运行,也有讲将 Flink Job 编译打包上传到 Flink UI 上运行,在这 UI 背后可能是通过 Standalone、YARN、Mesos、Kubernetes 等运行启动的 Flink。那么这节就系统讲下如何部署和运行我们的 Flink Job,大家可以根据自己公司的场景进行选择使用哪种方式进行部署 Flink 作业! ### 7.2.1 Standalone 第一种方式就是 Standalone 模式,这种模式笔者在前面 2.2 节里面演示的就是这种,我们通过执行命令:`./bin/start-cluster.sh` 启动一个 Flink Standalone 集群。 ``` zhisheng@zhisheng /usr/local/flink-1.9.0 ./bin/start-cluster.sh Starting cluster. Starting standalonesession daemon on host zhisheng. Starting taskexecutor daemon on host zhisheng. ``` 默认的话是启动一个 JobManager 和一个 TaskManager,我们可以通过 `jps` 查看进程有: ``` 65425 Jps 51572 TaskManagerRunner 51142 StandaloneSessionClusterEntrypoint ``` 其中上面的 TaskManagerRunner 代表的是 TaskManager 进程,StandaloneSessionClusterEntrypoint 代表的是 JobManager 进程。上面运行产生的只有一个 JobManager 和一个 TaskManager,如果是生产环境的话,这样的配置肯定是不够运行多个 Job 的,那么我们该如何在生产环境中配置 standalone 模式的集群呢?我们就需要修改 Flink 安装目录下面的 conf 文件夹里面配置: ``` flink-conf.yaml masters slaves ``` 将 slaves 中再添加一个 `localhost`,这样就可以启动两个 TaskManager 了。接着启动脚本 `start-cluster.sh`,启动日志显示如下图所示。 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-10-23-161333.png) 可以看见有两个 TaskManager 启动了,再看下 UI 显示的也是有两个 TaskManager,如下图所示。 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-10-23-161431.png) 那么如果还想要添加一个 JobManager 或者 TaskManager 怎么办?总不能再次重启修改配置文件后然后再重启吧!这里你可以这样操作。 **增加一个 JobManager**: ``` bin/jobmanager.sh ((start|start-foreground) [host] [webui-port])|stop|stop-all ``` 但是注意 Standalone 下一台机器最多只能运行一个 JobManager。 **增加一个 TaskManager**: ``` bin/taskmanager.sh start|start-foreground|stop|stop-all ``` 比如我执行了 `./bin/taskmanager.sh start` 命令后,运行结果如下图所示。 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-10-23-161657.png) Standalone 模式下可以先对 Flink Job 通过 `mvn clean package` 编译打包,得到 Jar 包后,可以在 UI 上直接上传 Jar 包,然后点击 Submit 就可以运行了。 ### 7.2.2 YARN ### 7.2.3 Mesos #### Session 集群 #### Per Job 集群 ### 7.3.4 Kubernetes ### 7.2.5 小结与反思 加入知识星球可以看到上面文章:https://t.zsxq.com/f66iAMz ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-09-25-zsxq.jpg) 本章讲解了 Flink 中所有的配置文件,每个配置文件中的配置有啥作用,并且如果在不同环境下配置 JobManager 的高可用,另外还介绍了 Flink 的部署问题,因为 Flink 本身是支持在不同的环境下部署的,比如 Standalone、K8S、YARN、Mesos 等,其中在调度平台上又有 Session 模式和 Per Job 模式,每种模式都有自己的特点,所以你可能需要根据公司的情况来做一定的选型,每种的部署也可能会有点不一样,遇到问题的化还得根据特殊情况进行特殊处理,希望你可以在公司灵活的处理这种问题。 ================================================ FILE: books/flink-in-action-8.1.md ================================================ --- toc: true title: 《Flink 实战与性能优化》—— 如何实时监控 Flink 及其作业? date: 2021-08-05 tags: - Flink - 大数据 - 流式计算 --- # 第八章 —— Flink 监控 Flink 相关的组件和作业的稳定性通常是比较关键的,所以得需要对它们进行监控,如果有异常,则需要及时告警通知。本章先会教会教会大家如何利用现有 Flink UI 上面的信息去发现和排查问题,会指明一些比较重要和我们非常关心的指标,通过这些指标我们能够立马定位到问题的根本原因。接着笔者会教大家如何去利用现有的 Metrics Reporter 去构建一个 Flink 的监控系统,它可以收集到所有作业的监控指标,并会存储这些监控指标数据,最后还会有一个监控大盘做数据可视化,通过这个大盘可以方便排查问题。 ## 8.1 实时监控 Flink 及其作业 当将 Flink JobManager、TaskManager 都运行起来了,并且也部署了不少 Flink Job,那么它到底是否还在运行、运行的状态如何、资源 TaskManager 和 Slot 的个数是否足够、Job 内部是否出现异常、计算速度是否跟得上数据生产的速度 等这些问题其实对我们来说是比较关注的,所以就很迫切的需要一个监控系统帮我们把整个 Flink 集群的运行状态给展示出来。通过监控系统我们能够很好的知道 Flink 内部的整个运行状态,然后才能够根据项目生产环境遇到的问题 ‘对症下药’。下面分别来讲下 JobManager、TaskManager、Flink Job 的监控以及最关心的一些监控指标。 ### 8.1.1 监控 JobManager 我们知道 JobManager 是 Flink 集群的中控节点,类似于 Apache Storm 的 Nimbus 以及 Apache Spark 的 Driver 的角色。它负责作业的调度、作业 Jar 包的管理、Checkpoint 的协调和发起、与 TaskManager 之间的心跳检查等工作。如果 JobManager 出现问题的话,就会导致作业 UI 信息查看不了,TaskManager 和所有运行的作业都会受到一定的影响,所以这也是为啥在 7.1 节中强调 JobManager 的高可用问题。 在 Flink 自带的 UI 上 JobManager 那个 Tab 展示的其实并没有显示其对应的 Metrics,那么对于 JobManager 来说常见比较关心的监控指标有哪些呢? #### 基础指标 因为 Flink JobManager 其实也是一个 Java 的应用程序,那么它自然也会有 Java 应用程序的指标,比如内存、CPU、GC、类加载、线程信息等。 + 内存:内存又分堆内存和非堆内存,在 Flink 中还有 Direct 内存,每种内存又有初始值、使用值、最大值等指标,因为在 JobManager 中的工作其实相当于 TaskManager 来说比较少,也不存储事件数据,所以通常 JobManager 占用的内存不会很多,在 Flink JobManager 中自带的内存 Metrics 指标有: ``` jobmanager_Status_JVM_Memory_Direct_Count jobmanager_Status_JVM_Memory_Direct_MemoryUsed jobmanager_Status_JVM_Memory_Direct_TotalCapacity jobmanager_Status_JVM_Memory_Heap_Committed jobmanager_Status_JVM_Memory_Heap_Max jobmanager_Status_JVM_Memory_Heap_Used jobmanager_Status_JVM_Memory_Mapped_Count jobmanager_Status_JVM_Memory_Mapped_MemoryUsed jobmanager_Status_JVM_Memory_Mapped_TotalCapacity jobmanager_Status_JVM_Memory_NonHeap_Committed jobmanager_Status_JVM_Memory_NonHeap_Max jobmanager_Status_JVM_Memory_NonHeap_Used ``` + CPU:JobManager 分配的 CPU 使用情况,如果使用类似 K8S 等资源调度系统,则需要对每个容器进行设置资源,比如 CPU 限制不能超过多少,在 Flink JobManager 中自带的 CPU 指标有: ``` jobmanager_Status_JVM_CPU_Load jobmanager_Status_JVM_CPU_Time ``` + GC:GC 信息对于 Java 应用来说是避免不了的,每种 GC 都有时间和次数的指标可以供参考,提供的指标有: ``` jobmanager_Status_JVM_GarbageCollector_PS_MarkSweep_Count jobmanager_Status_JVM_GarbageCollector_PS_MarkSweep_Time jobmanager_Status_JVM_GarbageCollector_PS_Scavenge_Count jobmanager_Status_JVM_GarbageCollector_PS_Scavenge_Time ``` #### Checkpoint 指标 因为 JobManager 负责了作业的 Checkpoint 的协调和发起功能,所以 Checkpoint 相关的指标就有表示 Checkpoint 执行的时间、Checkpoint 的时间长短、完成的 Checkpoint 的次数、Checkpoint 失败的次数、Checkpoint 正在执行 Checkpoint 的个数等,其对应的指标如下: ``` jobmanager_job_lastCheckpointAlignmentBuffered jobmanager_job_lastCheckpointDuration jobmanager_job_lastCheckpointExternalPath jobmanager_job_lastCheckpointRestoreTimestamp jobmanager_job_lastCheckpointSize jobmanager_job_numberOfCompletedCheckpoints jobmanager_job_numberOfFailedCheckpoints jobmanager_job_numberOfInProgressCheckpoints jobmanager_job_totalNumberOfCheckpoints ``` #### 重要的指标 另外还有比较重要的指标就是 Flink UI 上也提供的,类似于 Slot 总共个数、Slot 可使用的个数、TaskManager 的个数(通过查看该值可以知道是否有 TaskManager 发生异常重启)、正在运行的作业数量、作业运行的时间和完成的时间、作业的重启次数,对应的指标如下: ``` jobmanager_job_uptime jobmanager_numRegisteredTaskManagers jobmanager_numRunningJobs jobmanager_taskSlotsAvailable jobmanager_taskSlotsTotal jobmanager_job_downtime jobmanager_job_fullRestarts jobmanager_job_restartingTime ``` ### 8.1.2 监控 TaskManager .... ``` taskmanager_Status_JVM_CPU_Load taskmanager_Status_JVM_CPU_Time taskmanager_Status_JVM_ClassLoader_ClassesLoaded taskmanager_Status_JVM_ClassLoader_ClassesUnloaded taskmanager_Status_JVM_GarbageCollector_G1_Old_Generation_Count taskmanager_Status_JVM_GarbageCollector_G1_Old_Generation_Time taskmanager_Status_JVM_GarbageCollector_G1_Young_Generation_Count taskmanager_Status_JVM_GarbageCollector_G1_Young_Generation_Time taskmanager_Status_JVM_Memory_Direct_Count taskmanager_Status_JVM_Memory_Direct_MemoryUsed taskmanager_Status_JVM_Memory_Direct_TotalCapacity taskmanager_Status_JVM_Memory_Heap_Committed taskmanager_Status_JVM_Memory_Heap_Max taskmanager_Status_JVM_Memory_Heap_Used taskmanager_Status_JVM_Memory_Mapped_Count taskmanager_Status_JVM_Memory_Mapped_MemoryUsed taskmanager_Status_JVM_Memory_Mapped_TotalCapacity taskmanager_Status_JVM_Memory_NonHeap_Committed taskmanager_Status_JVM_Memory_NonHeap_Max taskmanager_Status_JVM_Memory_NonHeap_Used taskmanager_Status_JVM_Threads_Count taskmanager_Status_Network_AvailableMemorySegments taskmanager_Status_Network_TotalMemorySegments taskmanager_Status_Shuffle_Netty_AvailableMemorySegments taskmanager_Status_Shuffle_Netty_TotalMemorySegments ``` ### 8.1.3 监控 Flink 作业 ... ``` taskmanager_job_task_Shuffle_Netty_Input_Buffers_outPoolUsage taskmanager_job_task_Shuffle_Netty_Input_Buffers_outputQueueLength taskmanager_job_task_Shuffle_Netty_Output_Buffers_inPoolUsage taskmanager_job_task_Shuffle_Netty_Output_Buffers_inputExclusiveBuffersUsage taskmanager_job_task_Shuffle_Netty_Output_Buffers_inputFloatingBuffersUsage taskmanager_job_task_Shuffle_Netty_Output_Buffers_inputQueueLength taskmanager_job_task_Shuffle_Netty_Output_numBuffersInLocal taskmanager_job_task_Shuffle_Netty_Output_numBuffersInLocalPerSecond taskmanager_job_task_Shuffle_Netty_Output_numBuffersInRemote taskmanager_job_task_Shuffle_Netty_Output_numBuffersInRemotePerSecond taskmanager_job_task_Shuffle_Netty_Output_numBytesInLocal taskmanager_job_task_Shuffle_Netty_Output_numBytesInLocalPerSecond taskmanager_job_task_Shuffle_Netty_Output_numBytesInRemote taskmanager_job_task_Shuffle_Netty_Output_numBytesInRemotePerSecond taskmanager_job_task_buffers_inPoolUsage taskmanager_job_task_buffers_inputExclusiveBuffersUsage taskmanager_job_task_buffers_inputFloatingBuffersUsage taskmanager_job_task_buffers_inputQueueLength taskmanager_job_task_buffers_outPoolUsage taskmanager_job_task_buffers_outputQueueLength taskmanager_job_task_checkpointAlignmentTime taskmanager_job_task_currentInputWatermark taskmanager_job_task_numBuffersInLocal taskmanager_job_task_numBuffersInLocalPerSecond taskmanager_job_task_numBuffersInRemote taskmanager_job_task_numBuffersInRemotePerSecond taskmanager_job_task_numBuffersOut taskmanager_job_task_numBuffersOutPerSecond taskmanager_job_task_numBytesIn taskmanager_job_task_numBytesInLocal taskmanager_job_task_numBytesInLocalPerSecond taskmanager_job_task_numBytesInPerSecond taskmanager_job_task_numBytesInRemote taskmanager_job_task_numBytesInRemotePerSecond taskmanager_job_task_numBytesOut taskmanager_job_task_numBytesOutPerSecond taskmanager_job_task_numRecordsIn taskmanager_job_task_numRecordsInPerSecond taskmanager_job_task_numRecordsOut taskmanager_job_task_numRecordsOutPerSecond taskmanager_job_task_operator_currentInputWatermark taskmanager_job_task_operator_currentOutputWatermark taskmanager_job_task_operator_numLateRecordsDropped taskmanager_job_task_operator_numRecordsIn taskmanager_job_task_operator_numRecordsInPerSecond taskmanager_job_task_operator_numRecordsOut taskmanager_job_task_operator_numRecordsOutPerSecond ``` ### 8.1.4 最关心的性能指标 #### JobManager #### TaskManager #### Flink Job ### 8.1.5 小结与反思 加入知识星球可以看到上面文章:https://t.zsxq.com/f66iAMz ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-09-25-zsxq.jpg) ================================================ FILE: books/flink-in-action-8.2.md ================================================ --- toc: true title: 《Flink 实战与性能优化》—— 如何搭建一套 Flink 监控系统? date: 2021-08-06 tags: - Flink - 大数据 - 流式计算 --- ## 8.2 搭建一套 Flink 监控系统 8.1 节中讲解了 JobManager、TaskManager 和 Flink Job 的监控,以及需要关注的监控指标有哪些。本节带大家讲解一下如何搭建一套完整的 Flink 监控系统,如果你所在的公司没有专门的监控平台,那么可以根据本节的内容来为公司搭建一套属于自己公司的 Flink 监控系统。 ### 8.2.1 利用 API 获取监控数据 熟悉 Flink 的朋友都知道 Flink 的 UI 上面已经详细地展示了很多监控指标的数据,并且这些指标还是比较重要的,所以如果不想搭建额外的监控系统,那么直接利用 Flink 自身的 UI 就可以获取到很多重要的监控信息。这里要讲的是这些监控信息其实也是通过 Flink 自身的 Rest API 来获取数据的,所以其实要搭建一个粗糙的监控平台,也是可以直接利用现有的接口定时去获取数据,然后将这些指标的数据存储在某种时序数据库中,最后用些可视化图表做个展示,这样一个完整的监控系统就做出来了。 这里通过 Chrome 浏览器的控制台来查看一下有哪些 REST API 是用来提供监控数据的。 1.在 Chrome 浏览器中打开 `http://localhost:8081/overview` 页面,可以获取到整个 Flink 集群的资源信息:TaskManager 个数(TaskManagers)、Slot 总个数(Total Task Slots)、可用 Slot 个数(Available Task Slots)、Job 运行个数(Running Jobs)、Job 运行状态(Finished 0 Canceled 0 Failed 0)等,如下图所示。 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-10-03-161007.png) 2.通过 `http://localhost:8081/taskmanagers` 页面查看 TaskManager 列表,可以知道该集群下所有 TaskManager 的信息(数据端口号(Data Port)、上一次心跳时间(Last Heartbeat)、总共的 Slot 个数(All Slots)、空闲的 Slot 个数(Free Slots)、以及 CPU 和内存的分配使用情况,如下图所示。 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-10-03-161422.png) 3.通过 `http://localhost:8081/taskmanagers/tm_id` 页面查看 TaskManager 的具体情况(这里的 tm_id 是个随机的 UUID 值)。在这个页面上,除了上一条的监控信息可以查看,还可以查看该 TaskManager 的 JVM(堆和非堆)、Direct 内存、网络、GC 次数和时间,如下图所示。内存和 GC 这些信息非常重要,很多时候 TaskManager 频繁重启的原因就是 JVM 内存设置得不合理,导致频繁的 GC,最后使得 OOM 崩溃,不得不重启。 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-10-03-162532.png) 另外如果你在 `/taskmanagers/tm_id` 接口后面加个 `/log` 就可以查看该 TaskManager 的日志,注意,在 Flink 中的日志和平常自己写的应用中的日志是不一样的。在 Flink 中,日志是以 TaskManager 为概念打印出来的,而不是以单个 Job 打印出来的,如果你的 Job 在多个 TaskManager 上运行,那么日志就会在多个 TaskManager 中打印出来。如果一个 TaskManager 中运行了多个 Job,那么它里面的日志就会很混乱,查看日志时会发现它为什么既有这个 Job 打出来的日志,又有那个 Job 打出来的日志,如果你之前有这个疑问,那么相信你看完这里,就不会有疑问了。 对于这种设计是否真的好,不同的人有不同的看法,在 Flink 的 Issue 中就有人提出了该问题,Issue 中的描述是希望日志可以是 Job 与 Job 之间的隔离,这样日志更方便采集和查看,对于排查问题也会更快。对此国内有公司也对这一部分做了改进,不知道正在看本书的你是否有什么好的想法可以解决 Flink 的这一痛点。 4.通过 `http://localhost:8081/#/job-manager/config` 页面可以看到可 JobManager 的配置信息,另外通过 `http://localhost:8081/jobmanager/log` 页面可以查看 JobManager 的日志详情。 5.通过 `http://localhost:8081/jobs/job_id` 页面可以查看 Job 的监控数据,如下图所示,由于指标(包括了 Job 的 Task 数据、Operator 数据、Exception 数据、Checkpoint 数据等)过多,大家可以自己在本地测试查看。 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-10-03-164158.png) 上面列举了几个 REST API(不是全部),主要是为了告诉大家,其实这些接口我们都知道,那么我们也可以利用这些接口去获取对应的监控数据,然后绘制出更酷炫的图表,用更直观的页面将这些数据展示出来,这样就能更好地控制。 除了利用 Flink UI 提供的接口去定时获取到监控数据,其实 Flink 还提供了很多的 reporter 去上报监控数据,比如 JMXReporter、PrometheusReporter、PrometheusPushGatewayReporter、InfluxDBReporter、StatsDReporter 等,这样就可以根据需求去定制获取到 Flink 的监控数据,下面教大家使用几个常用的 reporter。 相关 Rest API 可以查看官网链接:[rest-api-integration](https://ci.apache.org/projects/flink/flink-docs-stable/monitoring/metrics.html#rest-api-integration) ### 8.2.2 Metrics 类型简介 可以在继承自 RichFunction 的函数中通过 `getRuntimeContext().getMetricGroup()` 获取 Metric 信息,常见的 Metrics 的类型有 Counter、Gauge、Histogram、Meter。 #### Counter #### Gauge #### Histogram #### Meter ### 8.2.3 利用 JMXReporter 获取监控数据 ### 8.2.4 利用 PrometheusReporter 获取监控数据 ### 8.2.5 利用 PrometheusPushGatewayReporter 获取监控数据 ### 8.2.6 利用 InfluxDBReporter 获取监控数据 ### 8.2.7 安装 InfluxDB 和 Grafana #### 安装 InfluxDB #### 安装 Grafana ### 8.2.8 配置 Grafana 展示监控数据 加入知识星球可以看到上面文章:https://t.zsxq.com/f66iAMz ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-09-25-zsxq.jpg) ### 8.2.9 小结与反思 本节讲了如何利用 API 去获取监控数据,对 Metrics 的类型进行介绍,然后还介绍了怎么利用 Reporter 去将 Metrics 数据进行上报,并通过 InfluxDB + Grafana 搭建了一套 Flink 的监控系统。另外你还可以根据公司的需要使用其他的存储方案来存储监控数据,Grafana 也支持不同的数据源,你们公司的监控系统架构是怎么样的,是否可以直接接入这套监控系统? 作业部署上线后的监控尤其重要,虽说 Flink UI 自身提供了不少的监控信息,但是个人觉得还是比较弱,还是得去搭建一套完整的监控系统去监控 Flink 中的 JobManager、TaskManager 和作业。本章中讲解了 Flink UI 上获取监控数据的方式,还讲解了如何利用 Flink 自带的 Metrics Reporter 去采集各种监控数据,从而利用时序数据库存储这些监控数据,最后用 Grafana 这种可视化比较好的去展示这些监控数据,从而达到作业真正的监控运维效果。 整套监控系统也希望你可以运用在你们公司,当然你不一定非得选用相同的存储时序数据库,这样可以为你们节省不少作业出问题后的排查时间。 ================================================ FILE: books/flink-in-action-9.1.md ================================================ --- toc: true title: 《Flink 实战与性能优化》—— 如何处理 Flink Job BackPressure (反压)问题? date: 2021-08-07 tags: - Flink - 大数据 - 流式计算 --- # 第九章 —— Flink 性能调优 通过第八章的监控图表信息,我们可以发现问题,在发现问题后,需要去分析为什么会发生这些问题以及我们该如何去解决这些问题。本章将会介绍很多 Flink 生产环境遇到的问题,比如作业出现反压、作业并行度配置不合理、作业数据倾斜等,除了引出这种常见问题之外,笔者还将和你一起去分析这种问题造成的原因以及如何去优化作业。比如合理的配置并行度、让作业算子尽可能的 chain 在一起已达到最优等。希望通过本章的内容,你可以将这些解决方法运用在你的公司,帮助公司解决类似的问题。 ## 9.1 如何处理 Flink Job BackPressure (反压)问题? 反压(BackPressure)机制被广泛应用到实时流处理系统中,流处理系统需要能优雅地处理反压问题。反压通常产生于这样的场景:短时间的负载高峰导致系统接收数据的速率远高于它处理数据的速率。许多日常问题都会导致反压,例如,垃圾回收停顿可能会导致流入的数据快速堆积,或遇到大促、秒杀活动导致流量陡增。反压如果不能得到正确的处理,可能会导致资源耗尽甚至系统崩溃。反压机制是指系统能够自己检测到被阻塞的 Operator,然后自适应地降低源头或上游数据的发送速率,从而维持整个系统的稳定。Flink 任务一般运行在多个节点上,数据从上游算子发送到下游算子需要网络传输,若系统在反压时想要降低数据源头或上游算子数据的发送速率,那么肯定也需要网络传输。所以下面先来了解一下 Flink 的网络流控(Flink 对网络数据流量的控制)机制。 ### 9.1.1 Flink 流处理为什么需要网络流控 下图是一个简单的 Flink 流任务执行图:任务首先从 Kafka 中读取数据、通过 map 算子对数据进行转换、keyBy 按照指定 key 对数据进行分区(key 相同的数据经过 keyBy 后分到同一个 subtask 实例中),keyBy 后对数据进行 map 转换,然后使用 Sink 将数据输出到外部存储。 简单的Flink流任务执行图.png 众所周知,在大数据处理中,无论是批处理还是流处理,单点处理的性能总是有限的,我们的单个 Job 一般会运行在多个节点上,通过多个节点共同配合来提升整个系统的处理性能。图中,任务被切分成 4 个可独立执行的 subtask 分别是 A0、A1、B0、B1,在数据处理过程中就会存在 shuffle。例如,subtask A0 处理完的数据经过 keyBy 后被发送到 subtask B0、B1 所在节点去处理。那么问题来了,subtask A0 应该以多快的速度向 subtask B0、B1 发送数据呢?把上述问题抽象化,如下图所示,将 subtask A0 当作 Producer,subtask B0 当做 Consumer,上游 Producer 向下游 Consumer 发送数据,在发送端和接收端有相应的 Send Buffer 和 Receive Buffer,但是上游 Producer 生产数据的速率比下游 Consumer 消费数据的速率大,Producer 生产数据的速率为 2MB/s, Consumer 消费数据速率为 1MB/s,Receive Buffer 容量只有 5MB,所以过了 5 秒后,接收端的 Receive Buffer 满了。 网络流控存在的问题.png 下游消费速率慢,且接收区的 Receive Buffer 有限,如果上游一直有源源不断的数据,那么将会面临着以下两种情况: + 下游消费者的缓冲区放不下数据,导致下游消费者会丢弃新到达的数据 + 为了不丢弃数据,所以下游消费者的 Receive Buffer 持续扩张,最后耗尽消费者的内存,导致 OOM 程序挂掉 常识告诉我们,这两种情况在生产环境下都是不能接受的,第一种会丢数据、第二种会把应用程序挂掉。所以,该问题的解决方案不应该是下游 Receive Buffer 一直累积数据,而是上游 Producer 发现下游 Consumer 消费比较慢的时候,应该在 Producer 端做出限流的策略,防止在下游 Consumer 端无限制地堆积数据。那上游 Producer 端该如何做限流呢?可以采用如下图所示的静态限流策略: 网络流控-静态限速.png 静态限速的思想就是,提前已知下游 Consumer 端的消费速率,然后在上游 Producer 端使用类似令牌桶的思想,限制 Producer 端生产数据的速率,从而控制上游 Producer 端向下游 Consumer 端发送数据的速率。但是静态限速会存在问题: + 通常无法事先预估下游 Consumer 端能承受的最大速率 + 就算通过某种方式预估出下游 Consumer 端能承受的最大速率,在运行过程中也可能会因为网络抖动、 CPU 共享竞争、内存紧张、IO阻塞等原因造成下游 Consumer 的吞吐量降低,但是上游 Producer 的吞吐量正常,然后又会出现之前所说的下游接收区的 Receive Buffer 有限,上游一直有源源不断的数据发送到下游的问题,还是会造成下游要么丢数据,要么为了不丢数据 buffer 不断扩充导致下游 OOM 的问题 综上所述,我们发现了,上游 Producer 端必须有一个限流的策略,且静态限流是不可靠的,于是就需要一个动态限流的策略。可以采用如下图所示的动态反馈策略: 网络流控-动态限速.png 下游 Consumer 端会频繁地向上游 Producer 端进行动态反馈,告诉 Producer 下游 Consumer 的负载能力,从而使 Producer 端可以动态调整向下游 Consumer 发送数据的速率,以实现 Producer 端的动态限流。当 Consumer 端处理较慢时,Consumer 将负载反馈到 Producer 端,Producer 端会根据反馈适当降低 Producer 自身从上游或者 Source 端读数据的速率来降低向下游 Consumer 发送数据的速率。当 Consumer 处理负载能力提升后,又及时向 Producer 端反馈,Producer 会通过提升自身从上游或 Source 端读数据的速率来提升向下游发送数据的速率,通过动态反馈的策略来动态调整系统整体的吞吐量。 读到这里,应该知道 Flink 为什么需要网络流控机制了,并且知道 Flink 的网络流控机制必须是一个动态反馈的策略。但是还有以下几个问题: + Flink 中数据具体是怎么从上游 Producer 端发送到下游 Consumer 端的? + Flink 的动态限流具体是怎么实现的?下游的负载能力和压力是如何传递给上游的? 带着这两个问题,学习下面的 Flink 网络流控与反压机制。 ### 9.1.2 Flink 1.5 之前网络流控机制介绍 在 Flink 1.5 之前,Flink 没有使用任何复杂的机制来解决反压问题,因为根本不需要那样的方案!Flink 利用自身作为纯数据流引擎的优势来优雅地响应反压问题。下面我们会深入分析 Flink 是如何在 Task 之间传输数据的,以及数据流如何实现自然降速的。 如下图所示,Job 分为 Task A、B、C,Task A 是 Source Task、Task B 处理转换数据、Task C 是 Sink Task。Task A 从外部 Source 端读取到数据后将数据序列化放到 Send Buffer 中,再由 Task A 的 Send Buffer 发送到 Task B 的 Receive Buffer,Task B 的算子从 Task B 的 Receive Buffer 中将数据反序列后进行处理,将处理后数据序列化放到 Task B 的 Send Buffer 中,再由 Task B 的 Send Buffer 发送到 Task C 的 Receive Buffer,Task C 再从 Task C 的 Receive Buffer 中将数据反序列后输出到外部 Sink 端,这就是所有数据的传输和处理流程。 简单的3个Task数据传输示意图.png Flink 中,动态反馈策略原理比较简单,假如 Task C 由于各种原因吞吐量急剧降低,那么肯定会造成 Task C 的 Receive Buffer 中堆积大量数据,此时 Task B 还在给 Task C 发送数据,但是毕竟内存是有限的,持续一段时间后 Task C 的 Receive Buffer 满了,此时 Task B 发现 Task C 的 Receive Buffer 满了后,就不会再往 Task C 发送数据了,Task B 处理完的数据就开始往 Task B 的 Send Buffer 积压,一段时间后 Task B 的 Send Buffer 也满了,Task B 的处理就会被阻塞,这时 Task A 还在往 Task B 的 Receive Buffer 发送数据。同样的道理,Task B 的 Receive Buffer 很快满了,导致 Task A 不再往 Task B 发送数据,Task A 的 Send Buffer 也会被用完,Task A 是 Source Task 没有上游,所以 Task A 直接降低从外部 Source 端读取数据的速率甚至完全停止读取数据。通过以上原理,Flink 将下游的压力传递给上游。如果下游 Task C 的负载能力恢复后,如何将负载提升的信息反馈给上游呢?实际上 Task B 会一直向 Task C 发送探测信号,检测 Task C 的 Receive Buffer 是否有足够的空间,当 Task C 的负载能力恢复后,Task C 会优先消费 Task C Receive Buffer 中的数据,Task C Receive Buffer 中有足够的空间时,Task B 会从 Send Buffer 继续发送数据到 Task C 的 Receive Buffer,Task B 的 Send Buffer 有足够空间后,Task B 又开始正常处理数据,很快 Task B 的 Receive Buffer 中也会有足够空间,同理,Task A 会从 Send Buffer 继续发送数据到 Task B 的 Receive Buffer,Task A 的 Receive Buffer 有足够空间后,Task A 就可以从外部的 Source 端开始正常读取数据了。通过以上原理,Flink 将下游负载过低的消息传递给上游。所以说 Flink 利用自身纯数据流引擎的优势优雅地响应反压问题,并没有任何复杂的机制来解决反压。上述流程,就是 Flink 动态限流(反压机制)的简单描述,可以看到 Flink 的反压是从下游往上游传播的,一直往上传播到 Source Task 后,Source Task 最终会降低或提升从外部 Source 端读取数据的速率。 如下图所示,对于一个 Flink 任务,动态反馈要考虑如下两种情况: 简单的3个Task反压图示.png 1.跨 Task,动态反馈具体如何从下游 Task 的 Receive Buffer 反馈给上游 Task 的 Send Buffer - 当下游 Task C 的 Receive Buffer 满了,如何告诉上游 Task B 应该降低数据发送速率 - 当下游 Task C 的 Receive Buffer 空了,如何告诉上游 Task B 应该提升数据发送速率 > 注:这里又分了两种情况,Task B 和 Task C 可能在同一个 TaskManager 上运行,也有可能不在同一个 TaskManager 上运行 > 1. Task B 和 Task C 在同一个 TaskManager 运行指的是:一个 TaskManager 包含了多个 Slot,Task B 和 Task C 都运行在这个 TaskManager 上。此时 Task B 给 Task C 发送数据实际上是同一个 JVM 内的数据发送,所以**不存在网络通信** > 2. Task B 和 Task C 不在同一个 TaskManager 运行指的是:Task B 和 Task C 运行在不同的 TaskManager 中。此时 Task B 给 Task C 发送数据是跨节点的,所以**会存在网络通信** 2.Task 内,动态反馈如何从内部的 Send Buffer 反馈给内部的 Receive Buffer - 当 Task B 的 Send Buffer 满了,如何告诉 Task B 内部的 Receive Buffer,自身的 Send Buffer 已经满了?要让 Task B 的 Receive Buffer 感受到压力,才能把下游的压力传递到 Task A - 当 Task B 的 Send Buffer 空了,如何告诉 Task B 内部的 Receive Buffer 下游 Send Buffer 空了,并把下游负载很低的消息传递给 Task A 到目前为止,动态反馈的具体细节抽象成了三个问题: - 跨 Task 且 Task 不在同一个 TaskManager 内,动态反馈具体如何从下游 Task 的 Receive Buffer 反馈给上游 Task 的 Send Buffer - 跨 Task 且 Task 在同一个 TaskManager 内,动态反馈具体如何从下游 Task 的 Receive Buffer 反馈给上游 Task 的 Send Buffer - Task 内,动态反馈具体如何从 Task 内部的 Send Buffer 反馈给内部的 Receive Buffer #### TaskManager 之间网络传输相关组件 TaskManager 之间数据传输流向如下图所示,可以看到 Source Task 给 Task B 发送数据,Source Task 做为 Producer,Task B 做为 Consumer,Producer 端产生的数据最后通过网络发送给 Consumer 端。Producer 端 Operator 实例对一条条的数据进行处理,处理完的数据首先缓存到 ResultPartition 内的 ResultSubPartition 中。ResultSubPartition 中一个 Buffer 写满或者超时后,就会触发将 ResultSubPartition 中的数据拷贝到 Producer 端 Netty 的 Buffer 中,之后又把数据拷贝到 Socket 的 Send Buffer 中,这里有一个从用户态拷贝到内核态的过程,最后通过 Socket 发送网络请求,把 Send Buffer 中的数据发送到 Consumer 端的 Receive Buffer。数据到达 Consumer 端后,再依次从 Socket 的 Receive Buffer 拷贝到 Netty 的 Buffer,再拷贝到 Consumer Operator InputGate 内的 InputChannel 中,最后 Consumer Operator 就可以读到数据进行处理了。这就是两个 TaskManager 之间的数据传输过程,我们可以看到发送方和接收方各有三层的 Buffer。当 Task B 往下游发送数据时,整个流程与 Source Task 给 Task B 发送数据的流程类似。 TaskManager 之间数据传输流向.png 根据上述流程,下表中对 Flink 通信相关的一些术语进行介绍: | 概念/术语 | 解释 | | :----------------- | ------------------------------------------------------------ | | ResultPartition | 生产者生产的数据首先写入到 ResultPartition 中,一个 Operator 实例对应一个ResultPartition。 | | ResultSubpartition | 一个 ResultPartition 是由多个 ResultSubpartition 组成。当 Producer Operator 实例生产的数据要发送给下游 Consumer Operator n 个实例时,那么该 Producer Operator 实例对应的 ResultPartition 中就包含 n 个 ResultSubpartition。 | | InputGate | 消费者消费的数据来自于 InputGate 中,一个 Operator 实例对应一个InputGate。网络中传输的数据会写入到 Task 的 InputGate。 | | InputChannel | 一个 InputGate 是由多个 InputChannel 组成。当 Consumer Operator 实例读取的数据来自于上游 Producer Operator n 个实例时,那么该 Consumer Operator 实例对应的 InputGate 中就包含 n 个 InputChannel。 | | RecordReader | 用于将记录从Buffer中读出。 | | RecordWriter | 用于将记录写入Buffer。 | | LocalBufferPool | 为 ResultPartition 或 InputGate 分配内存,每一个 ResultPartition 或 InputGate分别对应一个 LocalBufferPool。 | | NetworkBufferPool | 为 LocalBufferPool 分配内存,NetworkBufferPool 是 Task 之间共享的,每个 TaskManager 只会实例化一个。 | InputGate 和 ResultPartition 的内存是如何申请的呢?如下图所示,了解一下 Flink 网络传输相关的内存管理。在 TaskManager 初始化时,Flink 会在 NetworkBufferPool 中生成一定数量的内存块 MemorySegment,内存块的总数量就代表了网络传输中所有可用的内存。 NetworkBufferPool 是 Task 之间共享的,每个 TaskManager 只会实例化一个。Task 线程启动时,会为 Task 的 InputChannel 和 ResultSubPartition 分别创建一个 LocalBufferPool。InputGate 或 ResultPartition 需要写入数据时,会向相对应的 LocalBufferPool 申请内存(图中①),当 LocalBufferPool 没有足够的内存且还没到达 LocalBufferPool 设置的上限时,就会向 NetworkBufferPool 申请内存(图中②),并将内存分配给相应的 InputChannel 或 ResultSubPartition (图③④)。虽然可以申请,但是必须明白内存申请肯定是有限制的,不可能无限制的申请,我们在启动任务时可以指定该任务最多可能申请多大的内存空间用于 NetworkBufferPool。当 InputChannel 的内存块被 Operator 读取消费掉或 ResultSubPartition 的内存块已经被写入到了 Netty 中,那么 InputChannel 和 ResultSubPartition 中的内存块就可以还给 LocalBufferPool 了(图中⑤),如果 LocalBufferPool 中有较多空闲的内存块,就会还给 NetworkBufferPool (图中⑥)。 Flink 网络传输相关的内存管理.png 了解了 Flink 网络传输相关的内存管理,我们来分析 3 种动态反馈的具体细节。 #### 跨 Task 且 Task 不在同一个 TaskManager 内时,反压如何向上游传播 #### 跨 Task 且 Task 在同一个 TaskManager 内,反压如何向上游传播 #### Task 内部,反压如何向上游传播 ### 9.1.3 基于 Credit 的反压机制 #### Flink 1.5 之前反压机制存在的问题 #### 基于 Credit 的反压机制原理 ### 9.1.4 Flink 如何定位产生反压的位置? #### Flink 反压监控原理介绍 #### 利用 Flink Web UI 定位产生反压的位置 #### 利用 Flink Metrics 定位产生反压的位置 ### 9.1.5 定位到反压来源后,该如何处理? #### 系统资源 #### 垃圾收集(GC) #### CPU/线程瓶颈 #### 线程竞争 #### 负载不平衡 #### 外部依赖 ### 9.1.6 小结与反思 加入知识星球可以看到上面文章:https://t.zsxq.com/f66iAMz ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-09-25-zsxq.jpg) ================================================ FILE: books/flink-in-action-9.2.md ================================================ --- toc: true title: 《Flink 实战与性能优化》—— 如何查看 Flink 作业执行计划? date: 2021-08-08 tags: - Flink - 大数据 - 流式计算 --- ## 9.2 如何查看 Flink 作业执行计划? 当一个应用程序需求比较简单的情况下,数据转换涉及的 operator(算子)可能不多,但是当应用的需求变得越来越复杂时,可能在一个 Job 里面算子的个数会达到几十个、甚至上百个,在如此多算子的情况下,整个应用程序就会变得非常复杂,所以在编写 Flink Job 的时候要是能够随时知道 Job 的执行计划那就很方便了。 刚好,Flink 是支持可以获取到整个 Job 的执行计划的,另外 Flink 官网还提供了一个可视化工具 visualizer(可以将执行计划 JSON 绘制出执行图),如下图所示。 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-12-15-082247.png) ### 9.2.1 如何获取执行计划 JSON? 既然知道了将执行计划 JSON 绘制出可查看的执行图的工具,那么该如何获取执行计划 JSON 呢?方法很简单,你只需要在你的 Flink Job 的 Main 方法 里面加上这么一行代码: ```java System.out.println(env.getExecutionPlan()); ``` 然后就可以在 IDEA 中右键 Run 一下你的 Flink Job,从打印的日志里面可以查看到执行计划的 JSON 串,例如下面这种: ```json {"nodes":[{"id":1,"type":"Source: Custom Source","pact":"Data Source","contents":"Source: Custom Source","parallelism":5},{"id":2,"type":"Sink: flink-connectors-kafka","pact":"Data Sink","contents":"Sink: flink-connectors-kafka","parallelism":5,"predecessors":[{"id":1,"ship_strategy":"FORWARD","side":"second"}]}]} ``` IDEA 中运行打印出来的执行计划的 JSON 串如下图所示: ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-12-15-082318.png) ### 9.2.2 生成执行计划图 获取到执行计划 JSON 了,那么利用 Flink 自带的工具来绘出执行计划图,将获得到的 JSON 串复制粘贴到刚才那网址去,如下图所示。 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-12-15-082318.png) 点击上图的 `Draw` 按钮,就会生成如下图所示的执行流程图了。 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-12-15-082351.png) 从图中我们可以看到哪些内容呢? + operator name(算子):比如 source、sink + 每个 operator 的并行度:比如 Parallelism: 5 + 数据下发的类型:比如 FORWARD 你还可以点击下图中的 `Data Source(ID = 1)` 查看具体详情,如下图所示: ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-12-15-082424.png) 随着需求的不段增加,可能算子的个数会增加,所以执行计划也会变得更为复杂,如下图所示。 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-12-15-082457.png) 看到上图是不是觉得就有点很复杂了,笔者相信可能你自己的业务需求比这还会复杂得更多,不过从这图我们可以看到比上面那个简单的执行计划图多了一种数据下发类型就是 HASH。但是大家可能会好奇的说:为什么我平时从 Flink UI 上查看到的 Job ”执行计划图“ 却不是这样子的呀? 这里我们复现一下这个问题,我们把这个稍微复杂的 Flink Job 提交到 Flink UI 上去查看一下到底它在 UI 上的执行计划图是个什么样子?我们提交 Jar 包后不运行,直接点击 show plan 的结果如下图所示。 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-08-27-093209.jpg) 我们再运行一下,查看运行的时候的展示的 “执行计划图” 又是不一样的,如下图所示。 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-12-15-082540.png) ### 9.2.3 深入探究 Flink 作业执行计划 我们可以发现这两个 “执行计划图” 都和在 Flink 官网提供的 visualizer 工具生成的执行计划图是不一样的。粗略观察可以发现:在 Flink UI 上面的 “执行计划图” 变得更加简洁了,有些算子合在一起了,所以整体看起来就没这么复杂了。其实,这是 Flink 内部做的一个优化。我们先来看下 env.getExecutionPlan() 这段代码它背后的逻辑: ```java /** * Creates the plan with which the system will execute the program, and * returns it as a String using a JSON representation of the execution data * flow graph. Note that this needs to be called, before the plan is * executed. * * @return The execution plan of the program, as a JSON String. */ public String getExecutionPlan() { return getStreamGraph().getStreamingPlanAsJSON(); } ``` 代码注释的大概意思是: > 创建程序执行计划,并将执行数据流图的 JSON 作为 String 返回,请注意,在执行计划之前需要调用此方法。 这个 getExecutionPlan 方法有两步操作: 1、获取到 Job 的 StreamGraph 关于如何获取到 StreamGraph,笔者在博客里面写了篇源码解析 [如何获取 StreamGraph?](https://t.zsxq.com/qRFIm6I) 。 2、将 StreamGraph 转换成 JSON ```java public String getStreamingPlanAsJSON() { try { return new JSONGenerator(this).getJSON(); } catch (Exception e) { throw new RuntimeException("JSON plan creation failed", e); } } ``` 跟进 getStreamingPlanAsJSON 方法看见它构造了一个 JSONGenerator 对象(含参 StreamGraph),然后调用 getJSON 方法,我们来看下这个方法: ```java public String getJSON() { ObjectNode json = mapper.createObjectNode(); ArrayNode nodes = mapper.createArrayNode(); json.put("nodes", nodes); List operatorIDs = new ArrayList(streamGraph.getVertexIDs()); Collections.sort(operatorIDs, new Comparator() { @Override public int compare(Integer idOne, Integer idTwo) { boolean isIdOneSinkId = streamGraph.getSinkIDs().contains(idOne); boolean isIdTwoSinkId = streamGraph.getSinkIDs().contains(idTwo); // put sinks at the back ... } }); visit(nodes, operatorIDs, new HashMap()); return json.toString(); } ``` 一开始构造外部的对象,然后调用 visit 方法继续构造内部的对象,visit 方法如下: ```java private void visit(ArrayNode jsonArray, List toVisit, Map edgeRemapings) { Integer vertexID = toVisit.get(0); StreamNode vertex = streamGraph.getStreamNode(vertexID); if (streamGraph.getSourceIDs().contains(vertexID) || Collections.disjoint(vertex.getInEdges(), toVisit)) { ObjectNode node = mapper.createObjectNode(); decorateNode(vertexID, node); if (!streamGraph.getSourceIDs().contains(vertexID)) { ArrayNode inputs = mapper.createArrayNode(); node.put(PREDECESSORS, inputs); for (StreamEdge inEdge : vertex.getInEdges()) { int inputID = inEdge.getSourceId(); Integer mappedID = (edgeRemapings.keySet().contains(inputID)) ? edgeRemapings .get(inputID) : inputID; decorateEdge(inputs, inEdge, mappedID); } } jsonArray.add(node); toVisit.remove(vertexID); } else { Integer iterationHead = -1; for (StreamEdge inEdge : vertex.getInEdges()) { int operator = inEdge.getSourceId(); if (streamGraph.vertexIDtoLoopTimeout.containsKey(operator)) { iterationHead = operator; } } ObjectNode obj = mapper.createObjectNode(); ArrayNode iterationSteps = mapper.createArrayNode(); obj.put(STEPS, iterationSteps); obj.put(ID, iterationHead); obj.put(PACT, "IterativeDataStream"); obj.put(PARALLELISM, streamGraph.getStreamNode(iterationHead).getParallelism()); obj.put(CONTENTS, "Stream Iteration"); ArrayNode iterationInputs = mapper.createArrayNode(); obj.put(PREDECESSORS, iterationInputs); toVisit.remove(iterationHead); visitIteration(iterationSteps, toVisit, iterationHead, edgeRemapings, iterationInputs); jsonArray.add(obj); } if (!toVisit.isEmpty()) { visit(jsonArray, toVisit, edgeRemapings); } } ``` 最后就将这个 StreamGraph 构造成一个 JSON 串返回出去,所以其实这里返回的执行计划图就是 Flink Job 的 StreamGraph,然而我们在 Flink UI 上面看到的 "执行计划图" 是对应 Flink 中的 JobGraph,同样,笔者在博客里面也写了篇源码解析的文章 [源码解析——如何获取 JobGraph?](https://t.zsxq.com/naaMf6y)。 ### 9.2.4 Flink 中算子链接(chain)起来的条件 Flink 在内部会将多个算子串在一起作为一个 operator chain(执行链)来执行,每个执行链会在 TaskManager 上的一个独立线程中执行,这样不仅可以减少线程的数量及线程切换带来的资源消耗,还能降低数据在算子之间传输序列化与反序列化带来的消耗。 举个例子,拿一个 Flink Job (算子的并行度都设置为 5)生成的 StreamGraph JSON 渲染出来的执行流程图如下图所示。 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-12-15-082620.png) 提交到 Flink UI 上的 JobGraph 如下图所示。 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-12-15-082641.png) 可以看到 Flink 它内部将三个算子(source、filter、sink)都串成在一个执行链里。但是我们修改一下 filter 这个算子的并行度为 4,我们再次提交到 Flink UI 上运行,效果如下图所示。 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-12-15-082702.png) 你会发现它竟然拆分成三个了,我们继续将 sink 的并行度也修改成 4,继续打包运行后的效果如下图所示。 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-12-15-082742.png) 神奇不,它变成了 2 个了,将 filter 和 sink 算子串在一起了执行了。经过简单的测试,我们可以发现其实如果想要把两个不一样的算子串在一起执行确实还不是那么简单的,的确,它背后的条件可是比较复杂的,这里笔者给出源码出来,感兴趣的可以独自阅读下源码。 ```java public static boolean isChainable(StreamEdge edge, StreamGraph streamGraph) { //获取StreamEdge的源和目标StreamNode StreamNode upStreamVertex = edge.getSourceVertex(); StreamNode downStreamVertex = edge.getTargetVertex(); //获取源和目标StreamNode中的StreamOperator StreamOperator headOperator = upStreamVertex.getOperator(); StreamOperator outOperator = downStreamVertex.getOperator(); return downStreamVertex.getInEdges().size() == 1 && outOperator != null && headOperator != null && upStreamVertex.isSameSlotSharingGroup(downStreamVertex) && outOperator.getChainingStrategy() == ChainingStrategy.ALWAYS && (headOperator.getChainingStrategy() == ChainingStrategy.HEAD || headOperator.getChainingStrategy() == ChainingStrategy.ALWAYS) && (edge.getPartitioner() instanceof ForwardPartitioner) && upStreamVertex.getParallelism() == downStreamVertex.getParallelism() && streamGraph.isChainingEnabled(); } ``` 从源码最后的 return 可以看出它有九个条件: ... 所以看到上面的这九个条件,你是不是在想如果我们代码能够合理的写好,那么就有可能会将不同的算子串在一个执行链中,这样也就可以提高代码的执行效率了。 ### 9.2.5 如何禁止 Operator chain? 加入知识星球可以看到上面文章:https://t.zsxq.com/uFEEYzJ ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-09-25-zsxq.jpg) ### 9.2.6 小结与反思 本节内容从查看作业的执行计划来分析作业的执行情况,接着分析了作业算子 chain 起来的条件,并通过程序演示来验证,最后讲解了如何禁止算子 chain 起来。 本节代码地址:[chain](https://github.com/zhisheng17/flink-learning/tree/master/flink-learning-examples/src/main/java/com/zhisheng/examples/streaming/chain) [Job visualizer 工具](https://flink.apache.org/visualizer/) [源码解析——如何获取 StreamGraph](http://www.54tianzhisheng.cn/2019/03/20/Flink-code-StreamGraph/) ================================================ FILE: books/flink-in-action-9.3.md ================================================ --- toc: true title: 《Flink 实战与性能优化》—— Flink Parallelism 和 Slot 深度理解 date: 2021-08-09 tags: - Flink - 大数据 - 流式计算 --- ## 9.3 Flink Parallelism 和 Slot 深度理解 相信使用过 Flink 的你或多或少遇到过下面这个问题(笔者自己的项目曾经也出现过这样的问题),错误信息如下: ``` Caused by: akka.pattern.AskTimeoutException: Ask timed out on [Actor[akka://flink/user/taskmanager_0#15608456]] after [10000 ms]. Sender[null] sent message of type "org.apache.flink.runtime.rpc.messages.LocalRpcInvocation". ``` 错误信息的完整截图如下图所示。 ![](https://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/images/FkaM6A.jpg) 跟着这问题在 Flink 的 Issue 列表里看到了一个类似的问题:[FLINK-9056 issues](https://issues.apache.org/jira/browse/FLINK-9056),看到该 Issue 下面的评论说出现该问题的原因是因为 TaskManager 的 Slot 数量不足导致的 Job 提交失败,在 Flink 1.63 中已经修复了,变成抛出异常了,修复的代码如下图所示。 ![](https://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/images/p4Tr9Z.jpg) 竟然知道了是因为 Slot 不足的原因了,那么我们就要先了解下 Slot 是什么呢?不过在了解 Slot 之前这里先介绍下 Parallelism。 ### 9.3.1 Parallelism 简介 Parallelism 翻译成中文是并行的意思,在 Flink 作业里面代表算子的并行度,适当的提高并行度可以大大提高 Job 的执行效率,比如你的 Job 消费 Kafka 数据过慢,适当调大可能就消费正常了。那么在 Flink 中怎么设置并行度呢? ### 9.3.2 如何设置 Parallelism? 在 Flink 配置文件中默认并行度是 1,你可以通过下面的命令查看到配置文件中的默认并行度: ``` cat flink-conf.yaml | grep parallelism ``` 结果如下图所示: ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-10-06-055925.png) 所以如果在你的 Flink Job 里面不设置任何 Parallelism 的话,那么它也会有一个默认的 Parallelism(默认为 1),那也意味着可以修改这个配置文件的默认并行度来提高 Job 的执行效率。如果是使用命令行启动你的 Flink Job,那么你也可以这样设置并行度(使用 -p n 参数): ``` ./bin/flink run -p 10 /Users/zhisheng/word-count.jar ``` 你也可以在作业中通过 `env.setParallelism(n)` 代码来设置整个作业程序的并行度。 ``` StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.setParallelism(10); ``` 注意:这样设置的并行度是整个程序的并行度,那么后面如果每个算子不单独设置并行度覆盖的话,那么后面每个算子的并行度就都是以这里设置的并行度为准了。如何给每个算子单独设置并行度呢? ```java data.keyBy(new xxxKey()) .flatMap(new XxxFlatMapFunction()).setParallelism(5) .map(new XxxMapFunction).setParallelism(5) .addSink(new XxxSink()).setParallelism(1) ``` 如上就是给每个算子单独设置并行度,这样的话,就算程序设置了 `env.setParallelism(10)` 也是会被覆盖的。这也说明优先级是:算子设置并行度 > env 设置并行度 > 配置文件默认并行度。 并行度讲到这里应该都懂了,下面就继续讲什么是 Slot? ### 9.3.3 Slot 简介 其实 Slot 的概念在 1.2 节中已经提及到,这里再细讲一点。Flink 的作业提交的架构流程如下图所示: ![](https://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/images/r19yJh.jpg) 图中 TaskManager 是从 JobManager 处接收需要部署的 Task,任务能配置的最大并行度由 TaskManager 上可用的 Slot 决定。每个任务代表分配给任务槽的一组资源,Slot 在 Flink 里面可以认为是资源组,Flink 将每个任务分成子任务并且将这些子任务分配到 Slot 中,这样就可以并行的执行程序。 例如,如果 TaskManager 有四个 Slot,那么它将为每个 Slot 分配 25% 的内存。 可以在一个 Slot 中运行一个或多个线程。 同一 Slot 中的线程共享相同的 JVM。 同一 JVM 中的任务共享 TCP 连接和心跳消息。TaskManager 的一个 Slot 代表一个可用线程,该线程具有固定的内存,注意 Slot 只对内存隔离,没有对 CPU 隔离。默认情况下,Flink 允许子任务共享 Slot,即使它们是不同 Task 的 subtask,只要它们来自相同的 Job,这种共享模式可以大大的提高资源利用率。 如下图所示,有两个 TaskManager,每个 TaskManager 有三个 Slot,这样我们的算子最大并行度那么就可以达到 6 个,在同一个 Slot 里面可以执行 1 至多个子任务。那么再看下图,source/map/keyby/window/apply 算子最大可以设置 6 个并行度,sink 只设置了 1 个并行度。 ![](https://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/images/ECv5y2.jpg) 每个 Flink TaskManager 在集群中提供 Slot,Slot 的数量通常与每个 TaskManager 的可用 CPU 内核数成比例(一般情况下 Slot 个数是每个 TaskManager 的 CPU 核数)。Flink 配置文件中设置的一个 TaskManager 默认的 Slot 是 1,配置如下图所示。 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-10-06-062913.png) `taskmanager.numberOfTaskSlots: 1` 该参数可以根据实际情况做一定的修改。 ### 9.3.4 Slot 和 Parallelism 的关系 ### 9.3.5 可能会遇到 Slot 和 Parallelism 的问题 ### 9.3.6 小结与反思 加入知识星球可以看到上面文章:https://t.zsxq.com/uFEEYzJ ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-09-25-zsxq.jpg) ================================================ FILE: books/flink-in-action-9.4.md ================================================ --- toc: true title: 《Flink 实战与性能优化》—— 如何合理的设置 Flink 作业并行度? date: 2021-08-10 tags: - Flink - 大数据 - 流式计算 --- ## 9.4 如何合理的设置 Flink 作业并行度? 在 9.2 节中讲解了 Flink Job 中的执行计划,并详细分析了 Flink 中的 operator chain 在一起的各种条件,在 9.3 节中也通过真实生产环境的案例来分享并行度与 Slot 的概念与关系。相信大家也都有一定的理解,但是有时候生产环境如果 Job 突然消费不及时了,或者 Job 就根本不在消费数据了,那么该怎么办?首先得看下相关的监控查看 Job 是否在正常运行,是否出现反压的情况,是否这会生产数据量过大然而并行度却是根据之前数据量设置的,种种原因都需要一个个排查一下,然后找到根因才能够对应的去解决。这节来讲解下遇到这种问题后如何合理配置并行度呢? ### 9.4.1 Source 端并行度的配置 假设数据源端是 Kafka,在出现作业消费不及时的时候,首先看下 Kafka 的监控是不是现在生产者生产的数据上涨速度较快,从而导致作业目前的消费速度就是跟不上 Kafka 生产者的生产速度,如果是这样的话,那么就得查看作业的并行度和 Kafka 的分区数是否一致,如果小于 Kafka 的分区数,那么可以增大并行度至 Kafka 的分区数,然后再观察作业消费速度是否可以跟上数据生产速度;如果已经等于 Kafka 的分区数了,那得考虑下是否 Kafka 要扩大分区,但是这样可能会带来 Kafka 其他的问题,这个操作需要谨慎。 Kafka 中数据出现堆积的话,还可以分析下数据的类型,如果数据不重要,但是又要保证数据的及时性,可以修改作业让作业始终从最新的数据开始消费,丢弃之前堆积的数据,这样就可以保证数据的及时性。举个例子,假如一个实时告警作业它突然消费不及时,Kafka 中堆积了几亿条数据(数据延迟几个小时),那么如果作业调高并行度重启后,它还是从上一次提交的 offset 处开始消费的话,这样告警作业即使现在消费速度跟的上了,但是它要处理掉之前堆积的几亿条数据也是要一段时间的,那么就意味着这个作业仍将有段时间处于 ‘不可用’。因为即使判断出来要告警,可能这个告警信息的原数据已经是几个小时前的了,没准这个告警此时已经恢复了,但是还发出来告警这就意味着延迟性比较大,还会对告警消息接收者造成一定的干扰,所以这种场景下建议重启作业就直接开始从最新的数据开始消费。当然不同的场景可能不一样,如果金融行业的交易数据,那么是肯定不能允许这样丢数据的,即使堆积了,也要慢慢的去消费堆积的数据,直到后面追平至最新的数据。 在 Source 端设置并行度的话,如果数据源是 Kafka 的话,建议并行度不要超过 Kafka 的分区数,因为一个并行度会去处理一至多个分区的数据,如果设置过多的话,会出现部分并行度空闲。如果是其他的数据源,可以根据实际情况合理增大并行度来提高作业的处理数据能力。 ### 9.4.2 中间 Operator 并行度的配置 数据从 Source 端流入后,通常会进行一定的数据转换、聚合才能够满足需求,在数据转换中可能会和第三方系统进行交互,在交互的过程中可能会因为网络原因或者第三方服务原因导致有一定的延迟,从而导致这个数据交互的算子处理数据的吞吐量会降低,可能会造成反压,从而会影响上游的算子的消费。那么在这种情况下这些与第三方系统有交互的算子得稍微提高并行度,防止出现这种反压问题(当然反压问题不一定就这样可以解决,具体如何处理参见 9.1 节)。 除了这种与第三方服务做交互的外,另外可能的性能瓶颈也会出现在这类算子中,比如你 Kafka 过来的数据是 JSON 串的 String,然后需要转换成对象,在大数据量的情况下这个转换也是比较耗费性能的。 所以数据转换中间过程的算子也是非常重要的,如果哪一步算子的并行度设置的不合理,可能就会造成各种各样的问题出现。 ### 9.4.3 Sink 端并行度的配置 Sink 端是数据流向下游的地方,可以根据 Sink 端的数据量进行评估,可能有的作业是 Source 端的数据量最大,然后数据量不断的变少,最后到 Sink 端的数据就一点点了,比较常见的就是监控告警的场景。Source 端的数据是海量的,但是通过逐层的过滤和转换,到最后判断要告警的数据其实已经减少很多很多了,那么在最后的这个地方就可以将并行度设置的小一些。 当然也可能会有这样的情况,在 Source 端的数据量是最小的,拿到 Source 端流过来的数据后做了细粒度的拆分,那么数据量就不断的增加了,到 Sink 端的数据量就非常非常的大了。那么在 Sink 到下游的存储中间件的时候就需要提高并行度。 另外 Sink 端也是要与下游的服务进行交互,并行度还得根据下游的服务抗压能力来设置,如果在 Flink Sink 这端的数据量过大的话,然后在 Sink 处并行度也设置的很大,但是下游的服务完全撑不住这么大的并发写入,也是可能会造成下游服务直接被写挂的,下游服务可能还要对外提供一些其他的服务,如果稳定性不能保证的话,会造成很大的影响,所以最终还是要在 Sink 处的并行度做一定的权衡。 ### 9.4.4 Operator Chain 对于一般的作业(无特殊耗性能处),可以尽量让算子的并行度从 Source 端到 Sink 端都保持一致,这样可以尽可能的让 Job 中的算子进行 chain 在一起,形成链,数据在链中可以直接传输,而不需要再次进行序列化与反序列化,这样带来的性能消耗就会得到降低。在 9.2 节中具体讲解了算子 chain 在一起的条件,忘记的话可以去回顾一下。 ### 9.4.5 小结与反思 本节讲了作业执行过程中 Source 端、中间算子和 Sink 端的并行度设置的一些技巧。并行度修改后(增大或者减小)重启 Job,如果是减小并行度,之前原有的并行度的状态该怎么办;如果是新增并行度,如何确保和原来的并行度状态保持一致? 加入知识星球可以看到上面文章:https://t.zsxq.com/uFEEYzJ ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-09-25-zsxq.jpg) ================================================ FILE: books/flink-in-action-9.5.md ================================================ --- toc: true title: 《Flink 实战与性能优化》—— Flink 中如何保证 Exactly Once? date: 2021-08-11 tags: - Flink - 大数据 - 流式计算 --- ## 9.5 Flink 中如何保证 Exactly Once? 在分布式场景下,我们的应用程序随时可能出现任何形式的故障,例如:机器硬件故障、程序 OOM 等。当应用程序出现故障时,Flink 为了保证数据消费的 Exactly Once,需要有相应的故障容错能力。Flink 是通过周期性 Checkpoint 的方式来实现故障容错,这里使用的是基于 Chandy-Lamport 改进的算法。本节会介绍 Flink 内部如何保证 Exactly Once 以及端对端如何保证 Exactly Once。 ### 9.5.1 Flink 内部如何保证 Exactly Once? Flink 官网的定义是 Stateful Computations over Data Streams(数据流上的有状态计算),那到底什么是状态呢?举一个无状态计算的例子,比如:我们只是进行一个字符串拼接,输入a,输出a_666,输入b,输出 b_666。无状态表示计算输出的结果跟之前的状态没关系,符合幂等性。幂等性就是用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生副作用。而计算 PV、UV 就属于有状态计算。实时计算 PV 时,每次都需要从某个存储介质的结果表中拿到之前的 PV 值,+1 后 set 到结果表中。有状态计算表示输出的结果跟之前的状态有关系,不符合幂等性,访问多次,PV 会增加。 #### Flink的 Checkpoint 功能简介 Flink Checkpoint 机制的存在就是为了解决 Flink 任务在运行过程中由于各种原因导致任务失败后,能够正常恢复任务。那 Checkpoint 具体做了哪些功能,为什么任务挂掉之后,通过 Checkpoint 机制能使得任务恢复呢?Checkpoint 是通过给程序做快照的方式使得将整个程序某些时刻的状态保存下来,当任务挂掉之后,默认从最近一次保存的完整快照处进行恢复任务。问题来了,快照是什么东西?SnapShot翻译为快照,是指将程序中某些信息存一份,后期可以用这些信息来恢复任务。对于一个 Flink 任务来讲,快照里面到底保存着什么信息呢?理论知识一般比较晦涩难懂,我们分析一个案例,用案例辅助大家理解快照里面到底存储什么信息。计算各个 app 的 PV,使用 Flink 该怎么统计呢? 可以把要统计的 app_id 做为 key,对应的 PV 值做为 value,将统计的结果放到一个 Map 集合中,这个 Map 集合可以是内存里的 HashMap 或其他 kv 数据库,例如放到Redis 的 key、value 结构中。从 Kafka 读取到一条条日志,由于要统计各 app 的 PV,所以我们需要从日志中解析出 app_id 字段,每来一条日志,只需要从 Map 集合将相应 app_id 的 PV 值拿出来,+1 后 put 到 Map 中,这样我们的 Map 中永远保存着所有 app 最新的 PV 数据。详细流程如下图所示: flink任务task图.png 图中包含三部分:第一个是 Kafka 的一个名为 test 的 Topic,我们的数据来源于这个 Topic,第二个是 Flink 的 Source Task,是 Flink 应用程序读取数据的 Task,第三个是计算 PV 的 Flink Task,用于统计各个 app 的 PV 值,并将 PV 结果输出到 Map 集合。 Flink 的 Source Task 记录了当前消费到 test Topic 所有 partition 的 offset,为了方便理解 Checkpoint 的作用,这里先用一个 partition 进行讲解,假设名为 test 的 Topic只有一个partition0。例:(0,60000)表示0号partition 目前消费到 offset 为 60000 的数据。Flink 的 PV task 记录了当前计算的各 app 的 PV 值,为了方便讲解,这里假设有两个app:app1、app2。例:(app1,50000)(app2,10000)表示 app1 当前 PV 值为50000、app2 当前 PV 值为 10000。计算过程中,每来一条数据,只需要确定相应 app_id,将相应的 PV 值 +1 后 put 到 map 中即可。 该案例中,Checkpoint 到底记录了什么信息呢?记录的其实就是第 n 次 Checkpoint 消费的 offset 信息和各app 的 PV 值信息,记录下发生 Checkpoint 当前的状态信息,并将该状态信息保存到相应的状态后端。(注:**状态后端是保存状态的地方**,决定状态如何保存,如何保证状态高可用,我们只需要知道,我们能从状态后端拿到 offset 信息和 PV 信息即可。状态后端必须是高可用的,否则我们的状态后端经常出现故障,会导致无法通过 Checkpoint 来恢复我们的应用程序)。下面列出了第 100 次 Checkpoint 的时候,状态后端保存的状态信息: ``` chk-100 - offset:(0,60000) - PV:(app1,50000)(app2,10000) ``` 该状态信息表示第 100 次 Checkpoint 的时候, partition 0 offset 消费到了 60000,PV 统计结果为(app1,50000)(app2,10000) 。如果任务挂了,如何恢复? 假如我们设置了一分钟进行一次 Checkpoint,第 100 次 Checkpoint 成功后,过了十秒钟,offset已经消费到 (0,60100),PV 统计结果变成了(app1,50080)(app2,10020),突然任务挂了,怎么办?其实很简单,Flink 只需要从最近一次成功的 Checkpoint,也就是从第 100 次 Checkpoint 保存的 offset(0,60000)处接着消费即可,当然 PV 值也要从第 100 次 Checkpoint 里保存的 PV 值(app1,50000)(app2,10000)进行累加,不能从(app1,50080)(app2,10020)处进行累加,因为 **partition 0 offset消费到 60000 时,对应的 PV 统计结果为(app1,50000)(app2,10000)**。当然如果你想从offset (0,60100)PV(app1,50080)(app2,10020)这个状态恢复,也是做不到的,因为那个时刻程序突然挂了,这个状态根本没有保存下来,只有在 Checkpoint 的时候,才会把这些完整的状态保存到状态后端,供我们恢复任务。我们能做的最高效方式就是从最近一次成功的 Checkpoint 处恢复,也就是一直所说的 chk-100。以上基本就是 Checkpoint 承担的工作,为了方便理解,描述的业务场景比较简单。 补充两个问题:计算 PV 的 task 在一直运行,它怎么知道什么时候去做 Checkpoint 呢?计算 PV 的 task 怎么保证它自己计算的 PV 值(app1,50000)(app2,10000)就是offset(0,60000)那一刻的统计结果呢?Flink 在数据中加了一个叫做 barrier(栅栏) 的东西,如下图所示,用圈标注的就是 barrier。 barrier 从 Source Task 处生成,一直流到 Sink Task,期间所有的 Task 只要碰到 barrier,就会触发自身进行快照。如上图所示,Checkpoint barrier n-1 处做的快照就是指 Job 从开始处理到 barrier n-1 所有的状态数据,barrier n 处做的快照就是指从 Job 开始到处理到 barrier n 所有的状态数据。对应到 PV 案例中就是,Source Task 接收到 JobManager 的编号为 chk-100 的 Checkpoint 触发请求后,发现自己恰好接收到 kafka offset(0,60000)处的数据,所以会往 offset(0,60000)数据之后 offset(0,60001)数据之前插入一个barrier,然后自己开始做快照,也就是将offset(0,60000)保存到状态后端 chk-100 中。然后,Source Task 会把 barrier 和我们要处理的数据一块往下游发送,当统计 PV 的 task 接收到 barrier 后,意味着 barrier 之前的数据已经被 PV task 处理完了,此时也会暂停处理 barrier 之后的数据,将自己内存中保存的 PV 信息(app1,50000)(app2,10000)保存到状态后端 chk-100 中。Flink 大概就是通过以上过程来保存快照的。 上述过程中,barrier 的作用就是为了把数据区分开,barrier 之前的数据是本次 Checkpoint 之前必须处理完的数据,barrier 之后的数据在本次 Checkpoint 之前不能被处理。Checkpoint 过程中有一个同步做快照的环节不能处理 barrier 之后的数据,为什么呢?如果做快照的同时,也在处理数据,那么处理的数据可能会修改快照内容,所以先暂停处理数据,把内存中快照保存好后,再处理数据。结合案例来讲就是,PV task 在对(app1,50000)(app2,10000)做快照的同时,如果 barrier 之后的数据还在处理,可能会导致状态信息还没保存到磁盘,状态已经变成了(app1,50001)(app2,10001),导致我们最后快照里保存的 PV 值变成了(app1,50001)(app2,10001),这样如果从 Checkpoint 恢复任务时,我们从 offset 60000 开始消费,PV 值从 (app1,50001)(app2,10001) 开始累加,就会造成计算的 PV 结果偏高,结果不准确,就不能保证 Exactly Once。所以,Checkpoint 同步做快照的过程中,不能处理 barrier 之后的数据。Checkpoint 将快照信息写入到磁盘后,为了保证快照信息的高可用,需要将快照上传到 HDFS,这个上传快照到 HDFS 的过程是异步进行的,这个过程也可以处理 barrier 之后的数据,处理 barrier 之后的数据不会影响到磁盘上的快照信息。 从 PV 案例再分析 Flink 是如何做 Checkpoint 并从 Checkpoint 处恢复任务的,首先 JobManager 端会向所有 SourceTask 发送 Checkpoint,Source Task 会在数据流中安插 Checkpoint barrier,如下图所示。 单并行度 PV 案例 Checkpoint 过程图示1 Source Task 安插好 barrier 后,会将 barrier 跟数据一块发送给下游,然后自身开始做快照,并将快照信息 offset (0,60000) 发送到高可用的持久化存储介质,例如 HDFS 上,发送流程如下图所示。 单并行度 PV 案例 Checkpoint 过程图示2 下游的 PV task 接收到 barrier 后,也会做快照,并将快照信息 PV:(app1,50000) (app2,10000) 发送到 HDFS 上,如下图所示。 假设第 100 次 Checkpoint 完成后,一段时间后任务挂了,Flink 任务会自动从状态后端恢复任务。Source Task 去读取自己需要的状态信息 offset (0,60000) ,并从 offset 为 60000 的位置接着开始消费数据,PV task 也会去读取需要的状态信息 PV:(app1,50000) (app2,10000),并在该状态值的基础上,往上累积计算 PV 值,流程如下图所示。 #### 多并行度、多 Operator 情况下,Checkpoint 的过程 上一节中讲述了单并行度情况下 Checkpoint 的过程,但是生产环境中,一般都是多并行度,而且算子也会比较多,这种情况下 Checkpoint 的过程就会变得复杂。分布式状态容错面临的问题与挑战: - 如何确保状态拥有**精确一次**的容错保证? - 如何在分布式场景下替多个拥有本地状态的算子产生**一个全域一致的快照**? - 如何在**不中断运算**的前提下产生快照? 多并行度、多 Operator 实例的情况下,如何做全域一致的快照?所有的 Operator 运行过程中接收到所有上游算子发送 barrier 后,对自身的状态进行一次快照,保存到相应状态后端,流程如下图所示。 当任务从状态恢复时,每个 Operator 从状态后端读取自己相应的状态信息,数据源会从状态中保存的位置开始重新消费,后续的其他算子也会基于 Checkpoint 中保存的状态进行计算,如下图所示。 多并行度下,任务从 Checkpoint 恢复图示 整个 Checkpoint 的过程跟之前单并行度类似,图中有 4 个带状态的 Operator 实例,相应的状态后端就可以想象成 4 个格子。整个 Checkpoint 的过程可以当做 Operator 实例填自己格子的过程,Operator 实例将自身的状态写到状态后端中相应的格子,当所有的格子填满可以简单的认为一次完整的 Checkpoint 做完了。 上面只是快照的过程,Checkpoint 执行过程如下: 1、JobManager 端的 CheckPointCoordinator 向所有 Source Task 发送 CheckPointTrigger,Source Task会在数据流中安插 Checkpoint barrier 2、当 task 收到所有的 barrier 后,向自己的下游继续传递 barrier,然后自身执行快照,并将自己的状态**异步写入到持久化存储**中 - 增量 CheckPoint 只是把最新的一部分数据更新写入到外部存储 - 为了下游尽快开始做 CheckPoint,所以会先发送 barrier 到下游,自身再同步进行快照 3、当 task 对状态的快照信息完成备份后,会将备份数据的地址(state handle)通知给 JobManager 的 CheckPointCoordinator 如果 Checkpoint 的持续时长超过了 Checkpoint 设定的超时时间,CheckPointCoordinator 还没有收集完所有的 State Handle,CheckPointCoordinator就会认为本次 Checkpoint 失败,会把这次 Checkpoint 产生的所有 状态数据全部删除 4、CheckPointCoordinator 把整个 StateHandle 封装成 Completed Checkpoint Meta,写入到 HDFS,整个 Checkpoint 结束 #### barrier 对齐 什么是 barrier 对齐?如图所示,当前的 Operator 实例接收上游两个流的数据,一个是字母流,一个是数字流。 当 Checkpoint 时,上游字母流和数字流都会往 Operator 实例发送 Checkpoint barrier,但是由于每个算子的执行速率不同,所以不可能保证上游两个流的 barrier 同时到达 Operator 实例,那图中的 Operator 实例到底什么时候进行快照呢?接收到任意一个 barrier 就可以开始进行快照了吗,还是接收到所有的 barrier 才能开始进行快照呢?答案是:当一个 Operator 实例有多个输入流时,Operator 实例需要在做快照之前进行 barrier 对齐,等待所有输入流的 barrier 都到达。barrier 对齐的详细过程如下所示: 1、对于一个有多个输入流的 Operator 实例,当 Operator 实例从其中一个输入流接收到 Checkpoint barrier n 时,就不能处理来自该流的任何数据记录了,直到它从其他所有输入流接收到 barrier n为止,否则 **Operator 实例 Checkpoint n 的快照会混入快照 n 的记录和快照 n + 1 的记录**。如上图中第 1 个小图所示,数字流的 barrier 先到达了。 2、接收到 barrier n 的流暂时被搁置,从这些流接收的记录不会被处理,而是放入输入缓冲区。图 2 中,我们可以看到虽然数字流对应的 barrier 已经到达了,但是barrier之后的 1、2、3 这些数据只能放到缓冲区中,等待字母流的barrier到达。 3、一旦最后所有输入流都接收到 barrier n,Operator 实例就会把 barrier 之前所有已经处理完成的数据和 barrier n 一块发送给下游。然后 Operator 实例就可以对状态信息进行快照。如图 3 所示,Operator 实例接收到上游所有流的 barrier n,此时 Operator 实例就可以将 barrier 和 barrier 之前的数据发送到下游,然后自身状态进行快照。 4、快照做完后,Operator 实例将继续处理缓冲区的记录,然后就可以处理输入流的数据。如图 4 所示,先处理完缓冲区数据,就可以正常处理输入流的数据了。 上面的过程就是 Flink 在 Operator 实例有多个输入流的情况下,整个 barrier 对齐的过程。那什么是 barrier 不对齐呢?barrier 不对齐是指当还有其他流的 barrier 还没到达时,为了提高 Operator 实例的处理性能,Operator 实例会直接处理 barrier 之后的数据,等到所有流的 barrier 都到达后,就可以对该 Operator 做 Checkpoint 快照了。对应到图中就是,barrier 不对齐时会直接把 barrier 之后的数据 1、2、3 直接处理掉,而**不是**放到缓冲区中等待其他的输入流的 barrier 到达,当所有输入流的 barrier 都到达后,才开始对 Operator 实例的状态信息进行快照,这样会导致做快照之前,Operator 实例已经处理了一些 barrier n 之后的数据。Checkpoint 的目的是为了保存快照信息,如果 barrier 不对齐,那么 Operator 实例在做第 n 次 Checkpoint 之前,已经处理了一些 barrier n 之后的数据,当程序从第 n 次 Checkpoint 恢复任务时,程序会从第 n 次 Checkpoint 保存的 offset 位置开始消费数据,就会导致一些数据被处理了两次,就出现了重复消费。如果进行 barrier 对齐,就不会出现这种重复消费的问题,所以 **barrier 对齐就可以实现 Exactly Once,barrier 不对齐就变成了At Least Once。** 再结合计算 PV 的案例来证明一下,为什么 barrier 对齐就可以实现 Exactly Once,barrier 不对齐就变成了 At Least Once。之前的案例为了简单,描述的 kafka topic 只有 1 个 partition,这里为了讲述 barrier 对齐,假设 topic 有 2 个 partittion,且计算的是我们平台的总 PV,也就是说不需要区分 app,每条一条数据,我们都需要将其 PV 值 +1 即可。如下图所示,Flink 应用程序有两个 Source Task,一个计算 PV 的 Task,这里计算 PV 的 Task 就出现了存在多个输入流的情况。 假设 barrier 不对齐,那么 Checkpoint 过程是怎么样呢?如下图所示: 如上图左部分所示,Source Subtask 0 和 Subtask 1 已经完成了快照操作,他们的状态信息为 offset(0,10000)(1,10005) 表示 partition0 消费到 offset 为 10000 的位置,partition 1 消费到 offset 为 10005 的位置。当 Source Subtask 1 的 barrier 到达 PV task 时,计算的 PV 结果为 20002,但 PV task 还没有接收到 Source Subtask 0 发送的 barrier,所以 PV task 还不能对自身状态信息进行快照。由于设置的 barrier 不对齐,所以此时 PV task 会继续处理 Source Subtask 0 和 Source Subtask 1 传来的数据。很快,如上图右部分所示,PV task 接收到 Source Subtask 0 发来的 barrier,但是 PV task 已经处理了 Source Subtask 1 barrier 之后的三条数据,所以 PV 值目前已经为 20008了,这里的 PV=20008 实际上已经处理到 partition 1 offset 为 10008 的位置,此时 PV task 会对自身的状态信息(PV = 20008)做快照,整体的快照信息为 offset(0,10000)(1,10005) PV=20008。 接着程序在继续运行,过了 10 秒,由于某个服务器故障,导致我们的 Operator 实例有一个挂了,所以 Flink 会从最近一次 Checkpoint 保存的状态恢复。那具体是怎么恢复的呢?Flink 同样会起三个 Operator 实例,我还称他们是 Source Subtask 0 、Source Subtask 1 和 PV task。三个 Operator 会从状态后端读取保存的状态信息。Source Subtask 0 会从 partition 0 offset 为 10000 的位置开始消费,Source Subtask 1 会从 partition 1 offset 为 10005 的位置开始消费,PV task 会基于 PV=20008 进行累加统计。然后就会发现的 PV 值 20008 实际上已经包含了 partition 1 的 offset 10005~10008 的数据,所以 partition 1 从 offset 10005 恢复任务时,partition1 的 offset 10005~10008 的数据被消费了两次,出现了重复消费的问题,所以 barrier 不对齐只能保证 At Least Once。 如果设置为 barrier 对齐,这里能保证 Exactly Once 吗?如下图所示,当 PV task 接收到 Source Subtask 1 的 barrier 后,并不会处理 Source Subtask 1 barrier 之后的数据,而是把这些数据放到 PV task 的输入缓冲区中,直到等到 Source Subtask 0 的 barrier 到达后,PV task 才会对自身状态信息进行快照,此时 PV task 会把 PV=20005 保存到快照信息中,整体的快照状态信息为 offset(0,10000)(1,10005) PV=20005,当任务从 Checkpoint 恢复时,Source Subtask 0 会从 partition 0 offset 为 10000 的位置开始消费,Source Subtask 1 会从 partition 1 offset 为 10005 的位置开始消费,PV task 会基于 PV=20005 进行累加统计,所以 barrier 对齐能保证 Flink 内部的 Exactly Once。在 Flink 应用程序中,当 Checkpoint 语义设置 Exactly Once 或 At Least Once 时,唯一的区别就是 barrier 对不对齐。当设置为 Exactly Once 时,就会 barrier 对齐,当设置为 At Least Once 时,就会 barrier 不对齐。 通过本案例,我们应该发现了 barrier 在 Flink 的 Checkpoint 中起着非常大的作用。barrier 告诉 Flink 应用程序,Checkpoint 之前哪些数据不应该被处理,barrier 对齐的过程其实就是为了防止 Flink 应用程序处理重复的数据。总结一下,满足哪些条件时,会出现 barrier 对齐?在代码中设置了 Flink 的 Checkpoint 语义是 Exactly Once,其次 Operator 实例必须有多个输入流才会出现 barrier 对齐。对齐,汉语词汇,释义为使两个以上事物配合或接触得整齐。由汉语解释可得对齐肯定需要两个以上事物,所以必须有多个输入流才可能存在对齐。barrier 对齐就是上游多个流配合使得数据对齐的过程。言外之意:如果 Operator 实例只有一个输入流,就根本不存在 barrier 对齐,自己跟自己默认永远都是对齐的,所以当我们的应用程序从 Source 到 Sink 所有算子的并行度都是 1 的话,就算设置的 At Least Once,无形中也实现了 barrier 对齐,此时 Checkpoint 设置成 Exactly Once 和 At Least Once 一点区别都没有,都可以保证 Exactly Once。看到这里你应该已经知道了哪种情况会出现重复消费了,也应该要掌握为什么 barrier 对齐就能保证 Exactly Once,为什么 barrier 不对齐就是 At Least Once。 barrier 对齐其实是要付出代价的,从 barrier 对齐的过程可以看出,PV task 明明可以更高效的处理数据,但因为 barrier 对齐,导致 Source Subtask 1 barrier 之后的数据被放到缓冲区中,暂时性地没有被处理,假如生产环境中,Source Subtask 0 的 barrier 迟迟没有到达,比 Source Subtask 1 延迟了 30 秒,那么这 30 秒期间,Source Subtask 1 barrier 之后的数据不能被处理,所以 PV task 相当于被闲置了。所以,当我们的一些业务场景对 Exactly Once 要求不高时,我们可以设置 Flink 的 Checkpoint 语义是 At Least Once 来小幅度的提高应用程序的执行效率。Flink Web UI 的 Checkpoint 选项卡中可以看到 barrier 对齐的耗时,如果发现耗时比较长,且对 Exactly Once 语义要求不高时,可以考虑使用该优化方案。 前面提到如何在不中断运算的前提下产生快照?在 Flink 的 Checkpoint 过程中,无论下游算子有没有做完快照,只要上游算子将 barrier 发送到下游且上游算子自身已经做完快照时,那么上游算子就可以处理 barrier 之后的数据了,从而使得整个系统 Checkpoint 的过程影响面尽量缩到最小,来提升系统整体的吞吐量。 在整个 Checkpoint 的过程中,还存在一个问题,假设我们设置的 10 分钟一次 Checkpoint。在第 n 次 Checkpoint 成功后,过了 9 分钟,任务突然挂了,我们需要从最近一次成功的 Checkpoint 处恢复任务,也就是从 9 分钟之前的状态恢复任务,就需要把这 9分钟的数据全部再消费一次,成本比较大。有的同学可能会想,那可以不可以设置为 100 ms就做一次 Checkpoint 呢?这样的话,当任务出现故障时,就不需要从 9 分钟前的状态进行恢复了,直接从 100 ms之前的状态恢复即可,恢复就会很快,不需要处理大量重复数据了。但是,这样做会导致应用程序频繁的访问状态后端,一般我们为了高可用,会把状态里的数据比如 offset:(0,60000)PV:(app1,50000)(app2,10000) 信息保存到 HDFS 中,如果频繁访问 HDFS,肯定会造成吞吐量下降,所以一般我们的 Checkpoint 时间间隔可以设置为分钟级别,例如 1 分钟、3 分钟,对于状态很大的任务每次 Checkpoint 访问 HDFS 比较耗时,我们甚至可以设置为 5 分钟一次 Checkpoint,毕竟我们的应用程序挂的概率并不高,偶尔一次从 5 分钟前的状态恢复,我们是可以接受的。可以根据业务场景合理地调节 Checkpoint 的间隔时长,对于状态很小的 Job Checkpoint 会很快,我们可以调小时间间隔,对于状态比较大的 Job Checkpoint 会比较慢,我们可以调大 Checkpoint 时间间隔。 有的同学可能还有疑问,明明说好的 Exactly Once,但在 Checkpoint 成功后 10s 发生了故障,从最近一次成功的 Checkpoint 处恢复时,由于发生故障前的 10s Flink 也在处理数据,所以 Flink 应用程序肯定是把一些数据重复处理了呀。在面对任意故障时,不可能保证每个算子中用户定义的逻辑在每个事件中只执行一次,因为用户代码被部分执行的可能性是永远存在的。那么,当引擎声明 Exactly Once 处理语义时,它们能保证什么呢?如果不能保证用户逻辑只执行一次,那么哪些逻辑只执行一次?当引擎声明 Exactly Once 处理语义时,它们实际上是在说,它们可以保证引擎管理的状态更新只提交一次到持久的后端存储。换言之,无论以什么维度计算 PV、无论 Flink 应用程序发生多少次故障导致重启从 Checkpoint 恢复,Flink 都可以保证 PV 结果是准确的,不会因为各种任务重启而导致 PV 值计算偏高。 为了下游尽快做 Checkpoint,所以会先发送 barrier 到下游,自身再同步进行快照。这一步,如果向下发送barrier后,自己同步快照慢怎么办?下游已经同步好了,自己还没?可能会出现下游比上游快照还早的情况,但是这不影响快照结果,只是下游做快照更及时了,我只要保证下游把barrier之前的数据都处理了,并且不处理 barrier 之后的数据,然后做快照,那么下游也同样支持 Exactly Once。这个问题不要从全局思考,单独思考上游和下游的实例,你会发现上下游的状态都是准确的,既没有丢,也没有重复计算。这里需要注意,如果有一个Operator 的 Checkpoint 失败了或者因为 Checkpoint 超时也会导致失败,那么 JobManager 会认为整个 Checkpoint 失败。失败的 Checkpoint 是不能用来恢复任务的,必须所有的算子的 Checkpoint 都成功,那么这次 Checkpoint 才能认为是成功的,才能用来恢复任务。对应到 PV 案例就是,PV task 做快照速度较快,PV=20005 较早地写入到了 HDFS,但是 offset(0,10000)(1,10005) 过了几秒才写入到 HDFS,这种情况就算出现了,也不会影响计算结果,因为我们的快照信息是完全正确的。 再分享一个案例,Flink 的 Checkpoint 语义设置了 Exactly Once,程序中设置了 1 分钟 1 次 Checkpoint,5 秒向 MySQL 写一次数据,并commit。最后发现 MySQL 中数据重复了。为什么会重复呢?Flink要求端对端的 Exactly Once 都必须实现 TwoPhaseCommitSinkFunction。如果你的 Checkpoint 成功了,过了30秒突然程序挂了,由于 5 秒 commit 一次,所以在应用程序挂之前的 30 秒实际上已经写入了 6 批数据进入 MySQL。从 Checkpoint 处恢复时,之前提交的 6 批数据就会重复写入,所以出现了重复消费。Flink 的 Exactly Once 有两种情况,一个是我们本节所讲的 Flink 内部的 Exactly Once,一个是端对端的 Exactly Once。关于端对端如何保证 Exactly Once,我们在下一节中深入分析。 ### 9.5.2 端对端如何保证 Exactly Once? Flink 与外部存储介质之间进行数据交互统称为端对端或 end to end 数据传输。上一节讲述了 Flink 内部如何保证 Exactly Once,这一节来分析端对端的 Exactly Once。正如上述 Flink 写 MySQL 的案例所示,在第 n 次 Checkpoint 结束后,第 n+1 次 Checkpoint 之前,如果 Flink 应用程序已经向外部的存储介质中成功写入并提交了一些数据后,Flink 应用程序由于某些原因挂了,导致任务从第 n 次 Checkpoint 处恢复。这种情况下,就会导致第 n 次 Checkpoint 结束后且任务失败之前往外部存储介质中写入的那一部分数据重复写入两次,可能会导致相同的数据在存储介质中存储了两份,从而端对端的一致性语义保证从 Exactly Once 退化为 At Least Once。这里只考虑了数据重复的情况,为什么不考虑丢数据的情况呢?在写数据时可以对异常进行捕获增加重试策略,如果重试多次还没有成功可以让 Flink 任务失败,Flink 任务就会从最近一次成功的 Checkpoint 处恢复,就不会出现丢数据的情况,所以我们本节内容主要用来解决数据重复的问题。 针对上述端对端 Exactly Once 的问题,我们可以使用以下方案来解决: 1、假如我们使用的存储介质支持按照全局主键去重,那么比较容易实现 Exactly Once,无论相同的数据往外部存储中写入了几次,外部存储都会进行去重,只保留一条数据。例如,app1 的 PV 值为 10,现在把 (key=app1,value=10) 往 Redis 中写入 10 次,只是说把 value 值覆盖了 10次,并不会导致结果错误,这种方案属于幂等性写入。 2、我们上述案例中为什么会导致重复写入数据到外部存储呢?是因为在下一次 Checkpoint 之前如果任务失败时,一些数据已经成功写入到了外部存储中,没办法删除那些数据。既然问题是这样,那可以想办法把 "向外部存储中提交数据" 与 "Checkpoint" 强关联,两次 Checkpoint 之间不允许向外部存储介质中提交数据,Checkpoint 的时候再向外部存储提交。如果提交成功,则 Checkpoint 成功,提交失败,则 Checkpoint 也失败。这样在下一次 Checkpoint 之前,如果任务失败,也没有重复数据被提交到外部存储。这里只是描述一下大概思想,好多细节这里并没有详细描述,会在下文中详细描述。基于上述思想,Flink 实现了 TwoPhaseCommitSinkFunction,它提取了两阶段提交协议的通用逻辑,使得通过 Flink 来构建端到端的Exactly Once 程序成为可能。它提供了一个抽象层,用户只需要实现少数方法就能实现端到端的 Exactly Once 语义。不过这种方案必须要求我们的输出端 (Sink 端) 必须支持事务。 下面我们通过两部分来详细介绍上述两种方案。 #### 幂等性写入如何保证端对端的 Exactly Once 实时 ETL 当 HBase 做为 Sink 端时,就是典型的应用场景。把日志中的主键做为 HBase 的 RowKey,就可以保证数据不重复,实现比较简单,这里不多赘述。 继续探讨实时计算各 app PV 的案例,将统计结果以普通键值对的形式保存到 Redis 中供业务方查询。到底如何实现,才能保证 Redis 中的结果是精准的呢?在之前 Strom 或 Spark Streaming 的方案中,将统计的 PV 结果保存在 Redis 中,每来一条数据,从 Redis 中获取相应 app 对应的 PV 值然后内存中进行 +1 后,再将 PV 值 put 到 Redis 中。例如:Redis 中保存 app1 的 PV 为 10,现在来了一条 app1 的日志,首先从 Redis 中获取 app1 的 PV 值=10,内存中 10+1=11,将 (app1,11) put 到 Redis 中,这里的 11 就是我们统计的 app1 的 PV 结果。可以将这种方案优化为 incr 或 incrby,直接对 Redis 中的 10 进行累加,不需要手动在内存中进行累加操作。当然 Flink 也可以用上述的这种方案来统计各 app 的 PV,但是上述方案并不能保证 Exactly Once,为什么呢?当第 n 次 Checkpoint 时,app1 的 PV 结果为 10000,第 n 次 Checkpoint 结束后运行了 10 秒,Redis 中 app1 的 PV 结果已经累加到了 10200。此时如果任务挂了,从第 n 次 Checkpoint 恢复任务时,会继续按照 Redis 中保存的 PV=10200 进行累加,但是正确的结果应该是从 PV=10000 开始累加。如果按照上面的方案统计 PV,就可能会出现统计值偏高的情况。这里也证实了一点:并不是说 Flink 程序的 Checkpoint 语义设置为 Exactly Once,就能保证我们的统计结果或者各种输出结果都能满足 Exactly Once。为了编写真正满足 Exactly Once 的代码,我们需要对 Flink 的 Checkpoint 原理做一些了解,编写对 Exactly Once 友好的代码。 那如何编写代码才能使得最后在 Redis 中保存的 PV 结果满足 Exactly Once 呢?上一节中,讲述了 Flink 内部状态可以保证 Exactly Once,这里可以将统计的 PV 结果保存在 Flink 内部的状态里,每次基于状态进行累加操作,并将累加到的结果 put 到 Redis 中,这样当任务从 Checkpoint 处恢复时,并不是基于 Redis 中实时统计的 PV 值进行累加,而是基于 Checkpoint 中保存的 PV 值进行累加,Checkpoint 中会保存每次 Checkpoint 时对应的 PV 快照信息,例如:第 n 次 Checkpoint 会把当时 pv=10000 保存到快照信息里,同时状态后端还保存着一份实时的状态信息用于实时累加。示例代码如下所示: ```java final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); // 1 分钟一次Checkpoint env.enableCheckpointing(TimeUnit.MINUTES.toMillis(1)); CheckpointConfig checkpointConf = env.getCheckpointConfig(); // Checkpoint 语义 EXACTLY ONCE checkpointConf.setCheckpointingMode(CheckpointingMode.EXACTLY_ONCE); checkpointConf.enableExternalizedCheckpoints(CheckpointConfig.ExternalizedCheckpointCleanup.RETAIN_ON_CANCELLATION); Properties props = new Properties(); props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092"); props.put(ConsumerConfig.GROUP_ID_CONFIG, "app-pv-stat"); DataStreamSource appInfoSource = env.addSource(new FlinkKafkaConsumer011<>( // kafka topic, String 序列化 "app-topic", new SimpleStringSchema(), props)); // 按照 appId 进行 keyBy appInfoSource.keyBy((KeySelector) appId -> appId) .map(new RichMapFunction>() { private ValueState pvState; private long pv = 0; @Override public void open(Configuration parameters) throws Exception { super.open(parameters); // 初始化状态 pvState = getRuntimeContext().getState( new ValueStateDescriptor<>("pvStat", TypeInformation.of(new TypeHint() {}))); } @Override public Tuple2 map(String appId) throws Exception { // 从状态中获取该 app 的 PV 值,+1后,update 到状态中 if(null == pvState.value()){ pv = 1; } else { pv = pvState.value(); pv += 1; } pvState.update(pv); return new Tuple2<>(appId, pv); } }) .print(); env.execute("Flink PV stat"); ``` 代码中设置 1 分钟一次 Checkpoint,Checkpoint 语义 EXACTLY ONCE,从 Kafka 中读取数据,这里为了简化代码,所以 Kafka 中读取的直接就是 String 类型的 appId,按照 appId KeyBy 后,执行 RichMapFunction,RichMapFunction 的 open 方法中会初始化 ValueState 类型的 pvState,pvState 就是上文一直强调的状态信息,每次 Checkpoint 的时候,会把 pvState 的状态信息快照一份到 HDFS 来提供恢复。这里按照 appId 进行 keyBy,所以每一个 appId 都会对应一个 pvState,pvState 里存储着该 appId 对应的 pv 值。每来一条数据都会执行一次 map 方法,当这条数据对应的 appId 是新 app 时,pvState 里就没有存储这个 appId 当前的 pv 值,将 pv 值赋值为 1,当 pvState 里存储的 value 不为 null 时,拿出 pv 值 +1后 update 到 pvState 里。map 方法再将 appId 和 pv 值发送到下游算子,下游直接调用了 print 进行输出,这里完全可以替换成相应的 RedisSink 或 HBaseSink。本案例中计算 pv 的工作交给了 Flink 内部的 ValueState,不依赖外部存储介质进行累加,外部介质承担的角色仅仅是提供数据给业务方查询,所以无论下游使用什么形式的 Sink,只要 Sink 端能够按照主键去重,该统计方案就可以保证 Exactly Once。本案例使用的 ValueState,关于 State 的详细使用请参阅第3.1节。 #### TwoPhaseCommitSinkFunction 如何保证端对端的 Exactly Once Flink 的源码中有这么一段注释:This is a recommended base class for all of the {@link SinkFunction} that intend to implement exactly-once semantic。意思是对于打算实现 Exactly Once 语义的所有 SinkFunction 都推荐继承该抽象类。在介绍 TwoPhaseCommitSinkFunction 之前,先了解一下 2PC 分布式一致性协议。 在分布式系统中,每一个机器节点虽然都能明确地知道自己在进行事务操作过程中的结果是成功或失败,但无法直接获取到其他分布式节点的操作结果。因此,当一个事务操作需要跨越多个分布式节点的时候,为了让每个节点都能够获取到其他节点的事务执行状况,需要引入一个"协调者(Coordinator)"节点来统一调度所有分布式节点的执行逻辑,这些被调度的分布式节点被称为"参与者(Participant)"。协调者负责调度参与者的行为,并最终决定这些参与者是否要把事务真正的提交。 普通的事务可以保证单个事务内所有操作要么全部成功,要么全部失败,而分布式系统中具体如何保证多台节点上执行的事务要么所有节点事务都成功,要么所有节点事务都失败呢?先了解一下 2PC 一致性协议。 2PC 是 Two-Phase Commit 的缩写,即两阶段提交。2PC 将分布式事务分为了两个阶段,分别是提交事务请求(投票)和执行事务提交。协调者会根据参与者在第一阶段的投票结果,来决定第二阶段是否真正的执行事务,具体流程如下。 ##### 提交事务请求(投票)阶段 提交事务请求阶段如下所示: + 协调者向所有参与者发送 prepare 请求与事务内容,询问是否可以准备事务提交,并等待参与者的响应 + 各参与者执行事务操作,并记录 Undo日志(用于回滚)和 Redo日志(用于重放),但不真正提交 + 参与者向协调者返回事务操作的执行结果,执行成功返回 Yes,否则返回 No ##### 执行事务提交阶段 分为成功与失败两种情况: - 若第一阶段所有参与者都返回 Yes,说明事务可以提交 + 协调者向所有参与者发送 Commit 请求 + 参与者收到 Commit 请求后,会正式执行事务提交操作,并在提交完成后释放事务资源 + 完成事务提交后,向协调者发送 Ack 消息 + 协调者收到所有参与者的 Ack 消息,完成事务 事务提交成功的流程如下图所示: - 若第一阶段有参与者返回 No 或者超时未返回,说明事务中断,需要回滚 + 协调者向所有参与者发送 Rollback 请求 + 参与者收到 Rollback 请求后,根据 Undo日志回滚到事务执行前的状态,释放占用的事务资源 + 参与者在完成事务回滚后,向协调者返回 Ack + 协调者收到所有参与者的 Ack 消息,事务回滚完成 事务提交中断,需要回滚的流程如下图所示: 简单来讲,2PC 将一个事务的处理过程分为了投票和执行两个阶段,其核心是每个事务都采用先尝试后提交的处理方式。2PC 的优缺点如下所示: 优点:原理简单,实现方便 缺点: - 协调者单点问题:协调者在整个 2PC 协议中非常重要,一旦协调者故障,则 2PC 将无法运转 - 过于保守:在 2PC 的阶段一,如果参与者出现故障而导致协调者无法获取到参与者的响应信息,这时协调者只能依靠自身的超时机制来判断是否需要中断事务,这种策略比较保守。换言之,2PC 没有涉及较为完善的容错机制,任意一个节点失败都会导致整个事务的失败 - 同步阻塞:执行过程是完全同步的,各个参与者在等待其他参与者投票响应的的过程中,将无法进行其他任何操作 - 数据不一致:在二阶段提交协议的阶段二,当协调者向所有的参与者发送 Commit 请求后,出现了局部网络异常或局部参与者机器故障等因素导致一部分的参与者执行了 Commit 操作,而发生故障的参与者没有执行 Commit,于是整个分布式系统便出现了数据不一致现象 Flink 的 TwoPhaseCommitSinkFunction 是基于 2PC 实现的。Flink 的 JobManager 对应到 2PC 中的协调者,Operator 实例对应到 2PC 中的参与者。TwoPhaseCommitSinkFunction 实现了 CheckpointedFunction 和 CheckpointListener 接口。CheckpointedFunction 接口中有两个方法 snapshotState 和 initializeState,snapshotState 方法会在 Checkpoint 时且做快照之前被调用,initializeState 方法会在自定义 Function 初始化恢复状态时被调用。CheckpointListener 接口中有一个 notifyCheckpointComplete 方法,Operator 实例的 Checkpoint 成功后,会反馈给 JobManager,当 JobManager 接收到所有 Operator 实例 Checkpoint 成功的通知后,就认为本次 Checkpoint 成功了,会给所有 Operator 实例发送一个 Checkpoint 完成的通知,Operator 实例接收到通知后,就会调用 notifyCheckpointComplete 方法。 TwoPhaseCommitSinkFunction定义了如下 5 个抽象方法: ```java // 处理每一条数据 protected abstract void invoke(TXN transaction, IN value, Context context) throws Exception; // 开始一个事务,返回事务信息的句柄 protected abstract TXN beginTransaction() throws Exception; // 预提交(即提交请求)阶段的逻辑 protected abstract void preCommit(TXN transaction) throws Exception; // 正式提交阶段的逻辑 protected abstract void commit(TXN transaction); // 取消事务,Rollback 相关的逻辑 protected abstract void abort(TXN transaction); ``` ### 9.5.3 分析 FlinkKafkaConsumer 的设计思想 #### kafka offset 存储及如何实现 Consumer 实例消费 partition 的负载均衡 #### Source 端并行度改变了,如何来恢复 offset #### 如何实现自动发现当前消费 topic 下新增的 partition ### 9.5.4 小结与反思 加入知识星球可以看到上面文章:https://t.zsxq.com/uFEEYzJ ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-09-25-zsxq.jpg) ================================================ FILE: books/flink-in-action-9.6.md ================================================ --- toc: true title: 《Flink 实战与性能优化》—— 如何处理 Flink 中数据倾斜问题? date: 2021-08-11 tags: - Flink - 大数据 - 流式计算 --- ## 9.6 如何处理 Flink 中数据倾斜问题? 在大数据计算场景,无论使用 MapReduce、Spark 还是 Flink 计算框架,无论是批处理还是流处理都存在数据倾斜的问题,通过本节学习产生数据倾斜的原因及如何在生产环境解决数据倾斜。 ### 9.6.1 数据倾斜简介 分析一个计算各 app PV 的案例,如下图所示,圆球表示 app1 的日志,方块表示 app2 的日志,Source 端从外部系统读取用户上报的各 app 行为日志,要计算各 app 的 PV,所以按照 app 进行 keyBy,相同 app 的数据发送到同一个 Operator 实例中处理,keyBy 后对 app 的 PV 值进行累加来,最后将计算的 PV 结果输出到外部 Sink 端。 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-11-12-004442.jpg) 可以看到在任务运行过程中,计算 Count 的算子有两个并行度,其中一个并行度处理 app1 的数据,另一个并行度处理 app2 的数据。由于 app1 比较热门,所以 app1 的日志量远大于 app2 的日志量,造成计算 app1 PV 的并行度压力过大成为整个系统的瓶颈,而计算 app2 PV 的并行度数据量较少所以 CPU、内存以及网络资源的使用率整体都比较低,这就是产生数据倾斜的案例。 随着业务的不断发展,如果 app1 的日志量暴增,单个节点的单个并行度已经承担不了计算 app1 PV 的任务,此时如何来解决呢?对于不了解数据倾斜的同学看到 Flink 任务出现了延迟,结合之前学习的反压章节,定位整个 Flink 任务的瓶颈在于 Count 算子,所以认为 Count 算子的并行度不够,于是解决思路就是调大 Count 算子的并行度至 4 来提高 Count 算子的计算能力,调大并行度以后发现 Flink 任务的吞吐量并没有提升,而且通过反压机制定位到系统的瓶颈还在于 Count 算子,难道 Count 算子的并行度需要从 2 调大到 10 吗?No,上述情况就算把并行度调大到 100,依然不能解决任务瓶颈。为什么出现这种情况呢?要计算各 app 的 PV 数据,那么相同 app 的数据必然要发送到相同的 Operator 实例去处理,现在只有两个 app,最多只能分配到两个并行度上去执行,如果 Count 算子的并行度大于 2,意味着肯定有一些并行度分配不到数据,所以上述情况调大 Count 算子的并行度不能解决问题。那使用 Flink 如何来解决数据倾斜呢,我们先学习 Flink 中如何来判断是否发生了数据倾斜。 ### 9.6.2 判断是否存在数据倾斜 这里再通过一个案例来讲述 Flink 任务如何来判断是否存在数据倾斜,如下图所示,是 Flink Web UI Job 页面展示的任务执行计划,可以看到任务经过 Operator Chain 后,总共有两个 Task,上游 Task 将数据 keyBy 后发送到下游 Task,如何判断第二个 Task 计算的数据是否存在数据呢? ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-11-12-004443.jpg) 如下图所示,通过 Flink Web UI 中 Job 页面的第一个 Subtasks 选项卡,可以看到任务的两个 Task,点击 Task,可以看到 Task 相应的 Subtask 详情。例如 Subtask 的启动时间、结束时间、持续时长、接收数据量的字节数以及接收数据的个数。图中可以看到,相同 Task 的多个 Subtask 中,有的 Subtask 接收到 1.69 TB 的数据量,有的 Subtask 接收到 17.6 TB 的数据量,通过 Flink Web UI 可以精确地看到每个 Subtask 处理了多少数据,即可判断出 Flink 任务是否存在数据倾斜,接下来学习 Flink 中如何来解决数据倾斜。 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-11-12-004431.jpg) ### 9.6.3 分析和解决数据倾斜问题 在 Flink 中,很多因素都会导致数据倾斜,例如 9.6.1 节描述的 keyBy 后的聚合操作存在数据倾斜。keyBy 之前的数据直接来自于数据源,一般不会出现数据倾斜,除非数据源中的数据发生了数据倾斜。本小节将从多个角度来解决数据倾斜。 ##### keyBy 后的聚合操作存在数据倾斜 ##### keyBy 之前发生数据倾斜 ### 9.6.4 小结与反思 加入知识星球可以看到上面文章:https://t.zsxq.com/uFEEYzJ ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-09-25-zsxq.jpg) 在前一章中讲解了 Flink 监控系统的重要性,这章主要讲解 Flink 作业的性能调优。当作业出现各种各样的问题时,其实这时就体现了前面章节提到的监控的重要性,所以本章的内容也比较依赖于监控系统,然后才能够更好的去排查问题,然后去解决问题。 在本章中讲解的反压问题、并行度设置问题、数据倾斜问题等都是开发作业时要注意的点,本章不仅讲解了这些问题出现后的解决方案,还深入的剖析了这些问题为啥会出现,只有知其原因后,后面开发新的作业时才会去注意这些问题。本章的内容属于高阶玩家要掌握的,希望你也能够好好理解,在你们公司遇到同样问题的时候可以站出来去解决。 ================================================ FILE: flink-learning-basic/README.md ================================================ ### Flink-learning-basic 该项目存放 Flink 基础功能的一些学习案例 ================================================ FILE: flink-learning-basic/flink-learning-data-sinks/README.md ================================================ ## Flink data sink [http://www.54tianzhisheng.cn/2018/10/29/flink-sink/](http://www.54tianzhisheng.cn/2018/10/29/flink-sink/) [http://www.54tianzhisheng.cn/2018/10/31/flink-create-sink/](http://www.54tianzhisheng.cn/2018/10/31/flink-create-sink/) ================================================ FILE: flink-learning-basic/flink-learning-data-sinks/pom.xml ================================================ flink-learning-basic com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 flink-learning-data-sinks com.zhisheng.flink flink-learning-common ${project.version} mysql mysql-connector-java 5.1.34 org.apache.maven.plugins maven-shade-plugin 3.1.0 false package shade com.zhisheng.data.sinks.Main reference.conf *:*:*:* META-INF/*.SF META-INF/*.DSA META-INF/*.RSA ================================================ FILE: flink-learning-basic/flink-learning-data-sinks/src/main/java/com/zhisheng/data/sinks/Main.java ================================================ package com.zhisheng.data.sinks; import com.zhisheng.common.utils.ExecutionEnvUtil; import com.zhisheng.common.utils.GsonUtil; import com.zhisheng.common.utils.KafkaConfigUtil; import com.zhisheng.data.sinks.model.Student; import com.zhisheng.data.sinks.sinks.SinkToMySQL; import org.apache.flink.api.common.serialization.SimpleStringSchema; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumer; import java.util.Properties; import static com.zhisheng.common.constant.PropertiesConstants.METRICS_TOPIC; /** * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class Main { public static void main(String[] args) throws Exception{ final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); ParameterTool parameterTool = ExecutionEnvUtil.PARAMETER_TOOL; Properties props = KafkaConfigUtil.buildKafkaProps(parameterTool); SingleOutputStreamOperator student = env.addSource(new FlinkKafkaConsumer<>( parameterTool.get(METRICS_TOPIC), //这个 kafka topic 需要和上面的工具类的 topic 一致 new SimpleStringSchema(), props)).setParallelism(1) .map(string -> GsonUtil.fromJson(string, Student.class)); //博客里面用的是 fastjson,这里用的是gson解析,解析字符串成 student 对象 student.addSink(new SinkToMySQL()); //数据 sink 到 mysql env.execute("Flink data sink"); } } ================================================ FILE: flink-learning-basic/flink-learning-data-sinks/src/main/java/com/zhisheng/data/sinks/Main2.java ================================================ package com.zhisheng.data.sinks; import com.zhisheng.data.sinks.sinks.MySink; import org.apache.flink.streaming.api.datastream.DataStreamSource; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; /** * Desc: test RichSink function construction method and open function * https://t.zsxq.com/EIiyjeU * Created by zhisheng on 2019-09-26 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class Main2 { public static void main(String[] args) throws Exception { final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); DataStreamSource source = env.socketTextStream("127.0.0.1", 9000); source.addSink(new MySink("6")).setParallelism(5); env.execute("xxxx"); } } ================================================ FILE: flink-learning-basic/flink-learning-data-sinks/src/main/java/com/zhisheng/data/sinks/model/Student.java ================================================ package com.zhisheng.data.sinks.model; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; /** * Desc: * Created by zhisheng on 2019-02-17 * Blog: http://www.54tianzhisheng.cn/tags/Flink/ */ @Data @AllArgsConstructor @NoArgsConstructor public class Student { public int id; public String name; public String password; public int age; } ================================================ FILE: flink-learning-basic/flink-learning-data-sinks/src/main/java/com/zhisheng/data/sinks/sinks/MySink.java ================================================ package com.zhisheng.data.sinks.sinks; import org.apache.flink.configuration.Configuration; import org.apache.flink.streaming.api.functions.sink.RichSinkFunction; /** * Desc: * Created by zhisheng on 2019-09-26 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class MySink extends RichSinkFunction { private String tx; public MySink(String tx) { System.out.println("+++++++++++++" + tx); this.tx = tx; } @Override public void open(Configuration parameters) throws Exception { tx = "5"; System.out.println("========"); super.open(parameters); } @Override public void invoke(String value, Context context) throws Exception { System.out.println(value + " " + tx); } } ================================================ FILE: flink-learning-basic/flink-learning-data-sinks/src/main/java/com/zhisheng/data/sinks/sinks/SinkToMySQL.java ================================================ package com.zhisheng.data.sinks.sinks; import com.zhisheng.data.sinks.model.Student; import lombok.extern.slf4j.Slf4j; import org.apache.flink.configuration.Configuration; import org.apache.flink.streaming.api.functions.sink.RichSinkFunction; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; /** * Desc: sink 数据到 mysql * Created by yuanblog_tzs on 2019-02-17 * Blog: http://www.54tianzhisheng.cn/tags/Flink/ */ @Slf4j public class SinkToMySQL extends RichSinkFunction { PreparedStatement ps; private Connection connection; /** * open() 方法中建立连接,这样不用每次 invoke 的时候都要建立连接和释放连接 * * @param parameters * @throws Exception */ @Override public void open(Configuration parameters) throws Exception { super.open(parameters); connection = getConnection(); String sql = "insert into Student(id, name, password, age) values(?, ?, ?, ?);"; if (connection != null) { ps = this.connection.prepareStatement(sql); } } @Override public void close() throws Exception { super.close(); //关闭连接和释放资源 if (connection != null) { connection.close(); } if (ps != null) { ps.close(); } } /** * 每条数据的插入都要调用一次 invoke() 方法 * * @param value * @param context * @throws Exception */ @Override public void invoke(Student value, Context context) throws Exception { if (ps == null) { return; } //组装数据,执行插入操作 ps.setInt(1, value.getId()); ps.setString(2, value.getName()); ps.setString(3, value.getPassword()); ps.setInt(4, value.getAge()); ps.executeUpdate(); } private static Connection getConnection() { Connection con = null; try { Class.forName("com.mysql.jdbc.Driver"); //注意,替换成自己本地的 mysql 数据库地址和用户名、密码 con = DriverManager.getConnection("jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8", "root", "root123456"); } catch (Exception e) { log.error("-----------mysql get connection has exception , msg = {}", e.getMessage()); } return con; } } ================================================ FILE: flink-learning-basic/flink-learning-data-sinks/src/main/java/com/zhisheng/data/sinks/utils/KafkaUtil.java ================================================ package com.zhisheng.data.sinks.utils; import com.zhisheng.common.utils.GsonUtil; import com.zhisheng.data.sinks.model.Student; import org.apache.kafka.clients.producer.KafkaProducer; import org.apache.kafka.clients.producer.ProducerRecord; import java.util.Properties; /** * Desc: 往kafka中写数据,可以使用这个main函数进行测试 * Created by zhisheng on 2019-02-17 * Blog: http://www.54tianzhisheng.cn/tags/Flink/ */ public class KafkaUtil { public static final String broker_list = "localhost:9092"; public static final String topic = "student"; //kafka topic 需要和 flink 程序用同一个 topic public static void writeToKafka() throws InterruptedException { Properties props = new Properties(); props.put("bootstrap.servers", broker_list); props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer"); props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer"); KafkaProducer producer = new KafkaProducer(props); for (int i = 1; i <= 100; i++) { Student student = new Student(i, "zhisheng" + i, "password" + i, 18 + i); ProducerRecord record = new ProducerRecord(topic, null, null, GsonUtil.toJson(student)); producer.send(record); System.out.println("发送数据: " + GsonUtil.toJson(student)); } producer.flush(); } public static void main(String[] args) throws InterruptedException { writeToKafka(); } } ================================================ FILE: flink-learning-basic/flink-learning-data-sinks/src/main/resources/application.properties ================================================ kafka.brokers=localhost:9092 kafka.group.id=metrics-group kafka.zookeeper.connect=localhost:2181 metrics.topic=student stream.parallelism=5 stream.checkpoint.interval=1000 stream.checkpoint.enable=false ================================================ FILE: flink-learning-basic/flink-learning-data-sinks/src/main/resources/logback.xml ================================================ %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n ================================================ FILE: flink-learning-basic/flink-learning-data-sinks/src/main/resources/student.sql ================================================ DROP TABLE IF EXISTS `student`; CREATE TABLE `student` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(25) COLLATE utf8_bin DEFAULT NULL, `password` varchar(25) COLLATE utf8_bin DEFAULT NULL, `age` int(10) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8 COLLATE=utf8_bin; ================================================ FILE: flink-learning-basic/flink-learning-data-sources/README.md ================================================ ## Flink data source [http://www.54tianzhisheng.cn/2018/10/28/flink-sources/](http://www.54tianzhisheng.cn/2018/10/28/flink-sources/) [http://www.54tianzhisheng.cn/2018/10/30/flink-create-source/](http://www.54tianzhisheng.cn/2018/10/30/flink-create-source/) 定时任务捞取 MySQL 数据:可以查看 ScheduleMain 类的实现 ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-05-24-124853.jpg) ================================================ FILE: flink-learning-basic/flink-learning-data-sources/pom.xml ================================================ flink-learning-basic com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 flink-learning-data-sources com.zhisheng.flink flink-learning-common ${project.version} mysql mysql-connector-java 5.1.34 org.apache.maven.plugins maven-shade-plugin 3.1.0 false package shade com.zhisheng.data.sources.Main reference.conf *:*:*:* META-INF/*.SF META-INF/*.DSA META-INF/*.RSA ================================================ FILE: flink-learning-basic/flink-learning-data-sources/src/main/java/com/zhisheng/data/sources/Main.java ================================================ package com.zhisheng.data.sources; import org.apache.flink.api.common.serialization.SimpleStringSchema; import org.apache.flink.streaming.api.datastream.DataStreamSource; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumer; import java.util.Properties; /** * 利用 flink kafka 自带的 source 读取 kafka 里面的数据 */ public class Main { public static void main(String[] args) throws Exception{ final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); Properties props = new Properties(); props.put("bootstrap.servers", "localhost:9092"); props.put("zookeeper.connect", "localhost:2181"); props.put("group.id", "metric-group"); props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer"); //key 反序列化 props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer"); props.put("auto.offset.reset", "latest"); //value 反序列化 DataStreamSource dataStreamSource = env.addSource(new FlinkKafkaConsumer<>( "metric", //kafka topic new SimpleStringSchema(), // String 序列化 props)).setParallelism(1); dataStreamSource.print(); //把从 kafka 读取到的数据打印在控制台 env.execute("Flink add data source"); } } ================================================ FILE: flink-learning-basic/flink-learning-data-sources/src/main/java/com/zhisheng/data/sources/Main2.java ================================================ package com.zhisheng.data.sources; import com.zhisheng.data.sources.sources.SourceFromMySQL; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; /** * Desc: 自定义 source,从 mysql 中读取数据 * Created by zhisheng on 2019-02-17 * Blog: http://www.54tianzhisheng.cn/tags/Flink/ */ public class Main2 { public static void main(String[] args) throws Exception { final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.addSource(new SourceFromMySQL()).print(); env.execute("Flink add data sourc"); } } ================================================ FILE: flink-learning-basic/flink-learning-data-sources/src/main/java/com/zhisheng/data/sources/ScheduleMain.java ================================================ package com.zhisheng.data.sources; import com.zhisheng.common.model.MetricEvent; import com.zhisheng.common.utils.ExecutionEnvUtil; import com.zhisheng.common.utils.KafkaConfigUtil; import com.zhisheng.data.sources.model.Rule; import com.zhisheng.data.sources.utils.MySQLUtil; import lombok.extern.slf4j.Slf4j; import org.apache.flink.api.common.functions.MapFunction; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.streaming.api.datastream.DataStreamSource; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; /** * Desc: 定时捞取告警规则 * Created by zhisheng on 2019-05-24 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ @Slf4j public class ScheduleMain { public static List rules; public static void main(String[] args) throws Exception { //定时捞取规则,每隔一分钟捞一次 ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(1); threadPool.scheduleAtFixedRate(new GetRulesJob(), 0, 1, TimeUnit.MINUTES); final ParameterTool parameterTool = ExecutionEnvUtil.createParameterTool(args); StreamExecutionEnvironment env = ExecutionEnvUtil.prepare(parameterTool); DataStreamSource source = KafkaConfigUtil.buildSource(env); source.map(new MapFunction() { @Override public MetricEvent map(MetricEvent value) throws Exception { if (rules.size() <= 2) { System.out.println("===========2"); } else { System.out.println("===========3"); } return value; } }).print(); env.execute("schedule"); } static class GetRulesJob implements Runnable { @Override public void run() { try { rules = getRules(); } catch (SQLException e) { log.error("get rules from mysql has an error {}", e.getMessage()); } } } private static List getRules() throws SQLException { System.out.println("-----get rule"); String sql = "select * from rule"; Connection connection = MySQLUtil.getConnection("com.mysql.jdbc.Driver", "jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8", "root", "root123456"); PreparedStatement ps = connection.prepareStatement(sql); ResultSet resultSet = ps.executeQuery(); List list = new ArrayList<>(); while (resultSet.next()) { list.add(Rule.builder() .id(resultSet.getString("id")) .name(resultSet.getString("name")) .type(resultSet.getString("type")) .measurement(resultSet.getString("measurement")) .threshold(resultSet.getString("threshold")) .level(resultSet.getString("level")) .targetType(resultSet.getString("target_type")) .targetId(resultSet.getString("target_id")) .webhook(resultSet.getString("webhook")) .build() ); } return list; } } ================================================ FILE: flink-learning-basic/flink-learning-data-sources/src/main/java/com/zhisheng/data/sources/model/Rule.java ================================================ package com.zhisheng.data.sources.model; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; /** * Desc: 规则 * Created by zhisheng on 2019-05-24 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ @Data @AllArgsConstructor @NoArgsConstructor @Builder public class Rule { /** * rule id */ private String id; /** * rule name */ private String name; /** * rule type */ private String type; /** * monitor measurement */ private String measurement; /** * rule expression */ private String expression; /** * measurement threshold */ private String threshold; /** * alert level */ private String level; /** * rule targetType */ private String targetType; /** * rule targetId */ private String targetId; /** * notice webhook, only DingDing group rebot here * TODO: more notice ways */ private String webhook; } ================================================ FILE: flink-learning-basic/flink-learning-data-sources/src/main/java/com/zhisheng/data/sources/model/Student.java ================================================ package com.zhisheng.data.sources.model; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; /** * Desc: * Created by zhisheng on 2019-02-17 * Blog: http://www.54tianzhisheng.cn/tags/Flink/ */ @Data @AllArgsConstructor @NoArgsConstructor public class Student { public int id; public String name; public String password; public int age; } ================================================ FILE: flink-learning-basic/flink-learning-data-sources/src/main/java/com/zhisheng/data/sources/sources/SourceFromMySQL.java ================================================ package com.zhisheng.data.sources.sources; import com.zhisheng.data.sources.model.Student; import com.zhisheng.data.sources.utils.MySQLUtil; import org.apache.flink.configuration.Configuration; import org.apache.flink.streaming.api.functions.source.RichSourceFunction; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; /** * Desc: 自定义 source,从 mysql 中读取数据 * Created by zhisheng on 2019-02-17 * Blog: http://www.54tianzhisheng.cn/tags/Flink/ */ public class SourceFromMySQL extends RichSourceFunction { PreparedStatement ps; private Connection connection; /** * open() 方法中建立连接,这样不用每次 invoke 的时候都要建立连接和释放连接。 * * @param parameters * @throws Exception */ @Override public void open(Configuration parameters) throws Exception { super.open(parameters); connection = MySQLUtil.getConnection("com.mysql.jdbc.Driver", "jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8", "root", "root123456"); String sql = "select * from Student;"; ps = this.connection.prepareStatement(sql); } /** * 程序执行完毕就可以进行,关闭连接和释放资源的动作了 * * @throws Exception */ @Override public void close() throws Exception { super.close(); if (connection != null) { //关闭连接和释放资源 connection.close(); } if (ps != null) { ps.close(); } } /** * DataStream 调用一次 run() 方法用来获取数据 * * @param ctx * @throws Exception */ @Override public void run(SourceContext ctx) throws Exception { ResultSet resultSet = ps.executeQuery(); while (resultSet.next()) { Student student = new Student( resultSet.getInt("id"), resultSet.getString("name").trim(), resultSet.getString("password").trim(), resultSet.getInt("age")); ctx.collect(student); } } @Override public void cancel() { } } ================================================ FILE: flink-learning-basic/flink-learning-data-sources/src/main/java/com/zhisheng/data/sources/utils/KafkaUtil.java ================================================ package com.zhisheng.data.sources.utils; import com.zhisheng.common.model.MetricEvent; import com.zhisheng.common.utils.GsonUtil; import org.apache.kafka.clients.producer.KafkaProducer; import org.apache.kafka.clients.producer.ProducerRecord; import java.util.HashMap; import java.util.Map; import java.util.Properties; /** * Desc: 往kafka中写数据,可以使用这个main函数进行测试 * Created by zhisheng on 2019-02-17 * Blog: http://www.54tianzhisheng.cn/tags/Flink/ */ public class KafkaUtil { public static final String broker_list = "localhost:9092"; public static final String topic = "metric"; // kafka topic,Flink 程序中需要和这个统一 public static void writeToKafka() throws InterruptedException { Properties props = new Properties(); props.put("bootstrap.servers", broker_list); props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer"); //key 序列化 props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer"); //value 序列化 KafkaProducer producer = new KafkaProducer(props); MetricEvent metric = new MetricEvent(); metric.setTimestamp(System.currentTimeMillis()); metric.setName("mem"); Map tags = new HashMap<>(); Map fields = new HashMap<>(); tags.put("cluster", "zhisheng"); tags.put("host_ip", "101.147.022.106"); fields.put("used_percent", 90d); fields.put("max", 27244873d); fields.put("used", 17244873d); fields.put("init", 27244873d); metric.setTags(tags); metric.setFields(fields); ProducerRecord record = new ProducerRecord(topic, null, null, GsonUtil.toJson(metric)); producer.send(record); System.out.println("发送数据: " + GsonUtil.toJson(metric)); producer.flush(); } public static void main(String[] args) throws InterruptedException { while (true) { Thread.sleep(300); writeToKafka(); } } } ================================================ FILE: flink-learning-basic/flink-learning-data-sources/src/main/java/com/zhisheng/data/sources/utils/MySQLUtil.java ================================================ package com.zhisheng.data.sources.utils; import java.sql.Connection; import java.sql.DriverManager; /** * Desc: MySQL 工具类 * Created by zhisheng on 2019-05-24 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class MySQLUtil { public static Connection getConnection(String driver, String url, String user, String password) { Connection con = null; try { Class.forName(driver); //注意,这里替换成你自己的mysql 数据库路径和用户名、密码 con = DriverManager.getConnection(url, user, password); } catch (Exception e) { System.out.println("-----------mysql get connection has exception , msg = "+ e.getMessage()); } return con; } } ================================================ FILE: flink-learning-basic/flink-learning-data-sources/src/main/resources/application.properties ================================================ kafka.brokers=localhost:9092 kafka.group.id=metrics-group kafka.zookeeper.connect=localhost:2181 metrics.topic=alert-metrics stream.parallelism=5 stream.checkpoint.interval=1000 stream.checkpoint.enable=false ================================================ FILE: flink-learning-basic/flink-learning-data-sources/src/main/resources/logback.xml ================================================ %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n ================================================ FILE: flink-learning-basic/flink-learning-data-sources/src/main/resources/rule.sql ================================================ # ************************************************************ # Host: 127.0.0.1 (MySQL 5.7.22) # Database: zhisheng # Generation Time: 2019-05-24 09:37:57 +0000 # ************************************************************ /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; /*!40101 SET NAMES utf8 */; /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; # Dump of table rule # ------------------------------------------------------------ DROP TABLE IF EXISTS `rule`; CREATE TABLE `rule` ( `id` varchar(255) COLLATE utf8_bin NOT NULL DEFAULT '', `name` varchar(255) COLLATE utf8_bin DEFAULT NULL, `type` varchar(50) COLLATE utf8_bin DEFAULT NULL, `measurement` varchar(255) COLLATE utf8_bin DEFAULT NULL, `expression` varchar(1023) COLLATE utf8_bin DEFAULT NULL, `threshold` varchar(10) COLLATE utf8_bin DEFAULT NULL, `level` varchar(12) COLLATE utf8_bin DEFAULT NULL, `target_type` varchar(12) COLLATE utf8_bin DEFAULT NULL, `target_id` varchar(24) COLLATE utf8_bin DEFAULT NULL, `webhook` varchar(255) COLLATE utf8_bin DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin; LOCK TABLES `rule` WRITE; /*!40000 ALTER TABLE `rule` DISABLE KEYS */; INSERT INTO `rule` (`id`, `name`, `type`, `measurement`, `expression`, `threshold`, `level`, `target_type`, `target_id`, `webhook`) VALUES (X'31',X'6370755F616C657274',X'637075',X'7573655F70657263656E74',NULL,X'3830',X'4552524F52',X'6D616368696E65',X'7A68697368656E67',X'68747470733A2F2F6F6170692E64696E6774616C6B2E636F6D2F726F626F742F73656E643F6163636573735F746F6B656E3D64386336636431626434663132616436303235653430313965623631613933373233326630396566363336643636316636333431383836393666343136616362'), (X'32',X'6D656D5F616C657274',X'6D656D',X'7573655F70657263656E74',NULL,X'3835',X'4552524F52',X'6D616368696E65',X'7A68697368656E67',X'68747470733A2F2F6F6170692E64696E6774616C6B2E636F6D2F726F626F742F73656E643F6163636573735F746F6B656E3D64386336636431626434663132616436303235653430313965623631613933373233326630396566363336643636316636333431383836393666343136616362'); /*!40000 ALTER TABLE `rule` ENABLE KEYS */; UNLOCK TABLES; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; ================================================ FILE: flink-learning-basic/flink-learning-data-sources/src/main/resources/student.sql ================================================ DROP TABLE IF EXISTS `student`; CREATE TABLE `student` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(25) COLLATE utf8_bin DEFAULT NULL, `password` varchar(25) COLLATE utf8_bin DEFAULT NULL, `age` int(10) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8 COLLATE=utf8_bin; INSERT INTO `student` VALUES ('1', 'zhisheng01', '123456', '18'), ('2', 'zhisheng02', '123', '17'), ('3', 'zhisheng03', '1234', '18'), ('4', 'zhisheng04', '12345', '16'); COMMIT; ================================================ FILE: flink-learning-basic/flink-learning-libraries/README.md ================================================ ### flink-learning-libraries + [CEP](flink-learning-libraries-cep) + [Gelly](./flink-learning-libraries-gelly) + [Machine Learning](flink-learning-libraries-machine-learning) + [State Processor API](flink-learning-libraries-state-processor-api) ================================================ FILE: flink-learning-basic/flink-learning-libraries/flink-learning-libraries-cep/pom.xml ================================================ flink-learning-libraries com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 flink-learning-libraries-cep org.apache.flink flink-cep ${flink.version} org.apache.maven.plugins maven-shade-plugin 3.1.0 false package shade com.zhisheng.libraries.cep.CEPMain reference.conf *:*:*:* META-INF/*.SF META-INF/*.DSA META-INF/*.RSA ================================================ FILE: flink-learning-basic/flink-learning-libraries/flink-learning-libraries-cep/src/main/java/com/zhisheng/libraries/cep/CEPMain.java ================================================ package com.zhisheng.libraries.cep; import com.zhisheng.common.utils.ExecutionEnvUtil; import com.zhisheng.libraries.cep.model.Event; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.apache.flink.api.common.eventtime.WatermarkStrategy; import org.apache.flink.api.common.functions.FlatMapFunction; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.cep.CEP; import org.apache.flink.cep.PatternFlatSelectFunction; import org.apache.flink.cep.PatternSelectFunction; import org.apache.flink.cep.pattern.Pattern; import org.apache.flink.cep.pattern.conditions.SimpleCondition; import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.util.Collector; import java.time.Duration; import java.util.Date; import java.util.List; import java.util.Map; /** * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ @Slf4j public class CEPMain { public static void main(String[] args) throws Exception { final ParameterTool parameterTool = ExecutionEnvUtil.createParameterTool(args); StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.getConfig().setGlobalJobParameters(parameterTool); env.setParallelism(1); SingleOutputStreamOperator eventDataStream = env.socketTextStream("127.0.0.1", 9200) .flatMap(new FlatMapFunction() { @Override public void flatMap(String s, Collector collector) throws Exception { if (StringUtils.isNoneEmpty(s)) { String[] split = s.split(","); if (split.length == 2) { collector.collect(new Event(Integer.valueOf(split[0]), split[1])); } } } }).assignTimestampsAndWatermarks( WatermarkStrategy .forBoundedOutOfOrderness(Duration.ofSeconds(5)) .withTimestampAssigner((element, recordTimestamp) -> new Date().getTime()) ); //42,zhisheng //20,zhisheng Pattern pattern = Pattern.begin("start").where( new SimpleCondition() { @Override public boolean filter(Event event) { log.info("start {}", event.getId()); return event.getId() == 42; } } ).next("middle").where( new SimpleCondition() { @Override public boolean filter(Event event) { log.info("middle {}", event.getId()); return event.getId() >= 10; } } ); CEP.pattern(eventDataStream, pattern).select(new PatternSelectFunction() { @Override public String select(Map> p) throws Exception { StringBuilder builder = new StringBuilder(); log.info("p = {}", p); builder.append(p.get("start").get(0).getId()).append(",").append(p.get("start").get(0).getName()).append("\n") .append(p.get("middle").get(0).getId()).append(",").append(p.get("middle").get(0).getName()); return builder.toString(); } }).print(); CEP.pattern(eventDataStream, pattern).flatSelect(new PatternFlatSelectFunction() { @Override public void flatSelect(Map> map, Collector collector) throws Exception { for (Map.Entry> entry : map.entrySet()) { collector.collect(entry.getKey() + " " + entry.getValue().get(0).getId() + "," + entry.getValue().get(0).getName()); } } }).print(); env.execute("flink learning cep"); } } ================================================ FILE: flink-learning-basic/flink-learning-libraries/flink-learning-libraries-cep/src/main/java/com/zhisheng/libraries/cep/CombinePatternMain.java ================================================ package com.zhisheng.libraries.cep; import com.zhisheng.common.utils.ExecutionEnvUtil; import lombok.extern.slf4j.Slf4j; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.cep.CEP; import org.apache.flink.cep.PatternSelectFunction; import org.apache.flink.cep.pattern.Pattern; import org.apache.flink.cep.pattern.conditions.SimpleCondition; import org.apache.flink.streaming.api.datastream.DataStreamSource; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.api.windowing.time.Time; import java.util.List; import java.util.Map; /** * 组合 pattern * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ @Slf4j public class CombinePatternMain { public static void main(String[] args) throws Exception { final ParameterTool parameterTool = ExecutionEnvUtil.createParameterTool(args); StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.getConfig().setGlobalJobParameters(parameterTool); env.setParallelism(1); //数据顺序 a c b b DataStreamSource data = env.socketTextStream("127.0.0.1", 9200); Pattern pattern = Pattern.begin("start") .where(new SimpleCondition() { @Override public boolean filter(String s) throws Exception { return "a".equals(s); } }) // .next("middle").where(new SimpleCondition() { // @Override // public boolean filter(String s) throws Exception { // return "b".equals(s); // } // }); // .followedBy("middle").where(new SimpleCondition() { // @Override // public boolean filter(String s) throws Exception { // return "b".equals(s); // } // }); .followedByAny("middle").where(new SimpleCondition() { @Override public boolean filter(String s) throws Exception { return "b".equals(s); } }).within(Time.seconds(10)); CEP.pattern(data, pattern) .select(new PatternSelectFunction() { @Override public String select(Map> map) throws Exception { log.info(map.toString()); StringBuilder builder = new StringBuilder(); return builder.append(map.get("start").get(0)).append(" ") .append(map.get("middle").get(0)).toString(); } }).print(); env.execute("flink learning cep"); } } ================================================ FILE: flink-learning-basic/flink-learning-libraries/flink-learning-libraries-cep/src/main/java/com/zhisheng/libraries/cep/IndividualPatternQuantifier.java ================================================ package com.zhisheng.libraries.cep; import com.zhisheng.common.utils.ExecutionEnvUtil; import lombok.extern.slf4j.Slf4j; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.cep.CEP; import org.apache.flink.cep.PatternSelectFunction; import org.apache.flink.cep.pattern.Pattern; import org.apache.flink.cep.pattern.conditions.SimpleCondition; import org.apache.flink.streaming.api.datastream.DataStreamSource; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import java.util.List; import java.util.Map; /** * Desc: 单个 pattern 数量 * Created by zhisheng on 2019/10/30 上午12:48 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ @Slf4j public class IndividualPatternQuantifier { public static void main(String[] args) throws Exception { final ParameterTool parameterTool = ExecutionEnvUtil.createParameterTool(args); StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.getConfig().setGlobalJobParameters(parameterTool); env.setParallelism(1); DataStreamSource data = env.socketTextStream("127.0.0.1", 9200); Pattern pattern = Pattern.begin("start") .where(new SimpleCondition() { @Override public boolean filter(String s) throws Exception { return "a".equals(s); } }) .times(5).optional(); CEP.pattern(data, pattern) .select(new PatternSelectFunction() { @Override public String select(Map> map) throws Exception { log.info(map.toString()); return map.get("start").get(0); } }).print(); env.execute("flink learning cep Individual Pattern Quantifier"); } } ================================================ FILE: flink-learning-basic/flink-learning-libraries/flink-learning-libraries-cep/src/main/java/com/zhisheng/libraries/cep/model/Alert.java ================================================ package com.zhisheng.libraries.cep.model; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; /** * Desc: * Created by zhisheng on 2019/10/29 上午10:34 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ @Data @NoArgsConstructor @AllArgsConstructor @Builder public class Alert { private String message; } ================================================ FILE: flink-learning-basic/flink-learning-libraries/flink-learning-libraries-cep/src/main/java/com/zhisheng/libraries/cep/model/Event.java ================================================ package com.zhisheng.libraries.cep.model; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; /** * Desc: * Created by zhisheng on 2019/10/29 上午10:33 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ @Data @AllArgsConstructor @NoArgsConstructor @Builder public class Event { private Integer id; private String name; } ================================================ FILE: flink-learning-basic/flink-learning-libraries/flink-learning-libraries-cep/src/main/java/com/zhisheng/libraries/cep/model/SubEvent.java ================================================ package com.zhisheng.libraries.cep.model; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; /** * Desc: * Created by zhisheng on 2019/10/29 上午10:34 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ @Data @NoArgsConstructor @AllArgsConstructor @Builder public class SubEvent { private Integer volume; } ================================================ FILE: flink-learning-basic/flink-learning-libraries/flink-learning-libraries-cep/src/main/resources/application.properties ================================================ kafka.brokers=localhost:9092 kafka.group.id=zhisheng kafka.zookeeper.connect=localhost:2181 metrics.topic=zhisheng_metrics stream.parallelism=1 stream.checkpoint.enable=false ================================================ FILE: flink-learning-basic/flink-learning-libraries/flink-learning-libraries-cep/src/main/resources/logback.xml ================================================ %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n ================================================ FILE: flink-learning-basic/flink-learning-libraries/flink-learning-libraries-state-processor-api/pom.xml ================================================ flink-learning-libraries com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 flink-learning-libraries-state-processor-api org.apache.flink flink-state-processor-api ${flink.version} org.apache.maven.plugins maven-shade-plugin 3.1.0 false package shade com.zhisheng.libraries.stateProcessApi.Main reference.conf *:*:*:* META-INF/*.SF META-INF/*.DSA META-INF/*.RSA ================================================ FILE: flink-learning-basic/flink-learning-libraries/flink-learning-libraries-state-processor-api/src/main/java/com/zhisheng/libraries/stateProcessApi/Main.java ================================================ package com.zhisheng.libraries.stateProcessApi; import org.apache.flink.api.common.typeinfo.Types; import org.apache.flink.api.java.DataSet; import org.apache.flink.api.java.ExecutionEnvironment; import org.apache.flink.api.java.tuple.Tuple2; import org.apache.flink.contrib.streaming.state.RocksDBStateBackend; import org.apache.flink.state.api.ExistingSavepoint; import org.apache.flink.state.api.Savepoint; /** * Desc: * Created by zhisheng on 2019/10/19 下午9:50 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class Main { public static void main(String[] args) throws Exception { ExecutionEnvironment bEnv = ExecutionEnvironment.getExecutionEnvironment(); ExistingSavepoint savepoint = Savepoint.load(bEnv, "hdfs://path/", new RocksDBStateBackend("")); DataSet listState = savepoint.readListState("zhisheng-uid", "list-state", Types.INT); DataSet unionState = savepoint.readUnionState("zhisheng-uid", "union-state", Types.INT); DataSet> broadcastState = savepoint.readBroadcastState("zhisheng-uid", "broadcast-state", Types.INT, Types.INT); // DataSet listState = savepoint.readListState( // "zhisheng-uid", "list-state", // Types.INT, new MyCustomIntSerializer()); } } ================================================ FILE: flink-learning-basic/flink-learning-libraries/flink-learning-libraries-state-processor-api/src/main/java/com/zhisheng/libraries/stateProcessApi/StatefulFunctionWithTime.java ================================================ package com.zhisheng.libraries.stateProcessApi; import org.apache.flink.api.common.state.ValueState; import org.apache.flink.api.common.state.ValueStateDescriptor; import org.apache.flink.api.common.typeinfo.Types; import org.apache.flink.configuration.Configuration; import org.apache.flink.streaming.api.functions.KeyedProcessFunction; import org.apache.flink.util.Collector; /** * Desc: * Created by zhisheng on 2020-01-06 17:33 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class StatefulFunctionWithTime extends KeyedProcessFunction { ValueState state; @Override public void open(Configuration parameters) { ValueStateDescriptor stateDescriptor = new ValueStateDescriptor<>("state", Types.INT); state = getRuntimeContext().getState(stateDescriptor); } @Override public void processElement(Integer value, Context ctx, Collector out) throws Exception { state.update(value + 1); } } ================================================ FILE: flink-learning-basic/flink-learning-libraries/flink-learning-libraries-state-processor-api/src/main/resources/application.properties ================================================ kafka.brokers=localhost:9092 kafka.group.id=metrics-group kafka.zookeeper.connect=localhost:2181 metrics.topic=alert-metrics stream.parallelism=5 stream.checkpoint.interval=1000 stream.checkpoint.enable=false ================================================ FILE: flink-learning-basic/flink-learning-libraries/flink-learning-libraries-state-processor-api/src/main/resources/logback.xml ================================================ %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n ================================================ FILE: flink-learning-basic/flink-learning-libraries/pom.xml ================================================ flink-learning-basic com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 flink-learning-libraries pom flink-learning-libraries-cep flink-learning-libraries-state-processor-api com.zhisheng.flink flink-learning-common ${project.version} ================================================ FILE: flink-learning-basic/flink-learning-metrics/README.md ================================================ ### flink-learning-metrics **Metrics 类型**: + Counter + Gauge + Histogram + Meter 在 `com.zhisheng.metrics.custom` 包下面有上面四种 Metrics 类型的自定义测试类,其中自定义 Histogram 和 Meter 需要引入依赖: ```xml org.apache.flink flink-metrics-dropwizard ${flink.version} ``` 关于 Flink Metrics 的源码解析可以参考我的博客: [Flink Metrics 源码解析](http://www.54tianzhisheng.cn/2019/07/02/Flink-code-metrics/) ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-07-26-150037.jpg) ================================================ FILE: flink-learning-basic/flink-learning-metrics/pom.xml ================================================ flink-learning-basic com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 flink-learning-metrics com.zhisheng.flink flink-learning-common ${project.version} org.apache.flink flink-metrics-dropwizard ${flink.version} org.apache.maven.plugins maven-shade-plugin 3.1.0 false package shade com.zhisheng.metrics.custom.CustomCounterMetrics reference.conf *:*:*:* META-INF/*.SF META-INF/*.DSA META-INF/*.RSA ================================================ FILE: flink-learning-basic/flink-learning-metrics/src/main/java/com/zhisheng/metrics/custom/CustomCounterMetrics.java ================================================ package com.zhisheng.metrics.custom; import org.apache.flink.api.common.functions.RichMapFunction; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.configuration.Configuration; import org.apache.flink.metrics.Counter; import org.apache.flink.metrics.SimpleCounter; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.api.functions.source.SourceFunction; /** * Desc: custom Counter * Created by zhisheng on 2019-11-16 19:08 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class CustomCounterMetrics { public static void main(String[] args) throws Exception { //创建流运行环境 StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.getConfig().setGlobalJobParameters(ParameterTool.fromArgs(args)); env.addSource(new SourceFunction() { private volatile boolean isRunning = true; @Override public void run(SourceContext out) throws Exception { while (isRunning) { out.collect(String.valueOf(Math.round(Math.random() * 100))); Thread.sleep(10000); } } @Override public void cancel() { isRunning = false; } }).map(new RichMapFunction() { Counter counter1; Counter counter2; Counter counter3; int index; @Override public void open(Configuration parameters) throws Exception { super.open(parameters); index = getRuntimeContext().getIndexOfThisSubtask(); counter1 = getRuntimeContext().getMetricGroup() .addGroup("flink-metrics-test") .counter("mapTest" + index); counter2 = getRuntimeContext().getMetricGroup() .addGroup("flink-metrics-test") .counter("filterTest" + index); counter3 = getRuntimeContext().getMetricGroup() .addGroup("flink-metrics-test") .counter("mapCounter", new SimpleCounter()); } @Override public String map(String s) throws Exception { System.out.println("index = " + (index + 1) + " counter1 = " + counter1.getCount() + " counter2 = " + counter2.getCount()); counter1.inc(); counter3.inc(); if ("50".equals(s) || "20".equals(s)) { counter2.inc(); } return s; } }).print(); env.execute("Flink custom Counter Metrics"); } } ================================================ FILE: flink-learning-basic/flink-learning-metrics/src/main/java/com/zhisheng/metrics/custom/CustomCounterMetrics2.java ================================================ package com.zhisheng.metrics.custom; import org.apache.flink.api.common.functions.RichMapFunction; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.configuration.Configuration; import org.apache.flink.metrics.Counter; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.api.functions.source.SourceFunction; /** * Desc: custom Counter * Created by zhisheng on 2019-11-16 19:08 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class CustomCounterMetrics2 { public static void main(String[] args) throws Exception { //创建流运行环境 StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.getConfig().setGlobalJobParameters(ParameterTool.fromArgs(args)); env.addSource(new SourceFunction() { private volatile boolean isRunning = true; @Override public void run(SourceContext out) throws Exception { while (isRunning) { out.collect(String.valueOf(Math.round(Math.random() * 100))); Thread.sleep(15000); } } @Override public void cancel() { isRunning = false; } }).map(new RichMapFunction() { Counter counter1; Counter counter2; int index; @Override public void open(Configuration parameters) throws Exception { super.open(parameters); index = getRuntimeContext().getIndexOfThisSubtask(); counter1 = getRuntimeContext().getMetricGroup() .addGroup("flink-metrics-test") .counter("mapTest"); counter2 = getRuntimeContext().getMetricGroup() .addGroup("flink-metrics-test") .counter("filterTest"); } @Override public String map(String s) throws Exception { System.out.println("index = " + (index + 1) + " counter1 = " + counter1.getCount() + " counter2 = " + counter2.getCount()); counter1.inc(); if ("50".equals(s) || "20".equals(s)) { counter2.inc(); } return s; } }).print(); env.execute("Flink custom Counter Metrics"); } } ================================================ FILE: flink-learning-basic/flink-learning-metrics/src/main/java/com/zhisheng/metrics/custom/CustomCounterMetrics3.java ================================================ package com.zhisheng.metrics.custom; import com.zhisheng.common.utils.ExecutionEnvUtil; import org.apache.flink.api.common.functions.RichMapFunction; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.configuration.Configuration; import org.apache.flink.metrics.Counter; import org.apache.flink.metrics.SimpleCounter; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.api.functions.ProcessFunction; import org.apache.flink.streaming.api.functions.source.SourceFunction; import org.apache.flink.util.Collector; /** * Desc: custom Counter * Created by zhisheng on 2019-11-16 19:08 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class CustomCounterMetrics3 { public static void main(String[] args) throws Exception { System.out.println(Integer.MAX_VALUE); //创建流运行环境 StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); ParameterTool parameterTool = ExecutionEnvUtil.createParameterTool(args); env.getConfig().setGlobalJobParameters(parameterTool); env.setParallelism(parameterTool.getInt("stream.parallelism")); env.addSource(new SourceFunction() { private volatile boolean isRunning = true; @Override public void run(SourceContext out) throws Exception { while (isRunning) { out.collect(String.valueOf(Math.round(Math.random() * 100))); Thread.sleep(10000); } } @Override public void cancel() { isRunning = false; } }).setParallelism(2).slotSharingGroup("process") // .keyBy(k -> k) .process(new ProcessFunction() { Counter counter; int index; @Override public void open(Configuration parameters) throws Exception { counter = getRuntimeContext().getMetricGroup().addGroup("flink-metrics-test") .counter("processTest"); index = getRuntimeContext().getIndexOfThisSubtask() + 1; super.open(parameters); } @Override public void processElement(String s, Context context, Collector collector) throws Exception { counter.inc(); System.out.println("process index = " + (index + 1) + " counter1 = " + counter.getCount()); collector.collect(s); } }).slotSharingGroup("process") .map(new RichMapFunction() { Counter counter1; Counter counter2; Counter counter3; int index; @Override public void open(Configuration parameters) throws Exception { super.open(parameters); index = getRuntimeContext().getIndexOfThisSubtask(); counter1 = getRuntimeContext().getMetricGroup() .addGroup("flink-metrics-test") .counter("mapTest" + index); counter2 = getRuntimeContext().getMetricGroup() .addGroup("flink-metrics-test") .counter("filterTest" + index); counter3 = getRuntimeContext().getMetricGroup() .addGroup("flink-metrics-test") .counter("mapCounter", new SimpleCounter()); } @Override public String map(String s) throws Exception { System.out.println("map index = " + (index + 1) + " counter1 = " + counter1.getCount() + " counter2 = " + counter2.getCount()); counter1.inc(); counter3.inc(); if ("50".equals(s) || "20".equals(s)) { counter2.inc(); } return s; } }).print(); env.execute("Flink custom Counter Metrics"); } } ================================================ FILE: flink-learning-basic/flink-learning-metrics/src/main/java/com/zhisheng/metrics/custom/CustomGaugeMetrics.java ================================================ package com.zhisheng.metrics.custom; import org.apache.flink.api.common.functions.RichMapFunction; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.configuration.Configuration; import org.apache.flink.metrics.Gauge; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.api.functions.source.SourceFunction; /** * Desc: custom Gauge * Created by zhisheng on 2019-11-16 19:08 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class CustomGaugeMetrics { public static void main(String[] args) throws Exception { //创建流运行环境 StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.getConfig().setGlobalJobParameters(ParameterTool.fromArgs(args)); env.addSource(new SourceFunction() { private volatile boolean isRunning = true; @Override public void run(SourceContext out) throws Exception { while (isRunning) { out.collect(String.valueOf(Math.round(Math.random() * 100))); Thread.sleep(15000); } } @Override public void cancel() { isRunning = false; } }).map(new RichMapFunction() { private transient int value = 0; @Override public void open(Configuration parameters) throws Exception { super.open(parameters); getRuntimeContext().getMetricGroup() .addGroup("flink-metrics-test") .gauge("gaugeTest", new Gauge() { @Override public Integer getValue() { return value; } }); } @Override public String map(String s) throws Exception { value++; return s; } }).print(); env.execute("Flink custom Gauge Metrics"); } } ================================================ FILE: flink-learning-basic/flink-learning-metrics/src/main/java/com/zhisheng/metrics/custom/CustomHistogramMetrics.java ================================================ package com.zhisheng.metrics.custom; import com.codahale.metrics.SlidingWindowReservoir; import org.apache.flink.api.common.functions.RichMapFunction; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.configuration.Configuration; import org.apache.flink.dropwizard.metrics.DropwizardHistogramWrapper; import org.apache.flink.metrics.Histogram; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.api.functions.source.SourceFunction; /** * Desc: custom Histogram * Created by zhisheng on 2019-11-16 19:08 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class CustomHistogramMetrics { public static void main(String[] args) throws Exception { //创建流运行环境 StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.getConfig().setGlobalJobParameters(ParameterTool.fromArgs(args)); env.setParallelism(1); env.addSource(new SourceFunction() { private volatile boolean isRunning = true; @Override public void run(SourceContext out) throws Exception { while (isRunning) { out.collect(Long.valueOf(Math.round(Math.random() * 100))); Thread.sleep(1000); } } @Override public void cancel() { isRunning = false; } }).map(new RichMapFunction() { Histogram histogram; int index; @Override public void open(Configuration parameters) throws Exception { super.open(parameters); com.codahale.metrics.Histogram dropwizardHistogram = new com.codahale.metrics.Histogram(new SlidingWindowReservoir(500)); index = getRuntimeContext().getIndexOfThisSubtask() + 1; histogram = getRuntimeContext().getMetricGroup() .addGroup("flink-metrics-test") .histogram("histogramTest", new DropwizardHistogramWrapper(dropwizardHistogram)); } @Override public Long map(Long s) throws Exception { histogram.update(s); System.out.println("index = " + " count = " + histogram.getCount() + " max= " + histogram.getStatistics().getMax() + " min = " + histogram.getStatistics().getMin() + " mean = " + histogram.getStatistics().getMean() + " 75% = " + histogram.getStatistics().getQuantile(0.75)); return s; } }).print(); env.execute("Flink custom Histogram Metrics"); } } ================================================ FILE: flink-learning-basic/flink-learning-metrics/src/main/java/com/zhisheng/metrics/custom/CustomMeterMetrics.java ================================================ package com.zhisheng.metrics.custom; import org.apache.flink.api.common.functions.RichMapFunction; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.configuration.Configuration; import org.apache.flink.dropwizard.metrics.DropwizardMeterWrapper; import org.apache.flink.metrics.Meter; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.api.functions.source.SourceFunction; /** * Desc: custom Meter * Created by zhisheng on 2019-11-16 19:08 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class CustomMeterMetrics { public static void main(String[] args) throws Exception { //创建流运行环境 StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.getConfig().setGlobalJobParameters(ParameterTool.fromArgs(args)); // env.setParallelism(1); env.addSource(new SourceFunction() { private volatile boolean isRunning = true; @Override public void run(SourceContext out) throws Exception { while (isRunning) { out.collect(String.valueOf(Math.round(Math.random() * 100))); Thread.sleep(1000); } } @Override public void cancel() { isRunning = false; } }).map(new RichMapFunction() { Meter meter; int index; @Override public void open(Configuration parameters) throws Exception { super.open(parameters); com.codahale.metrics.Meter dropwizardMeter = new com.codahale.metrics.Meter(); index = getRuntimeContext().getIndexOfThisSubtask() + 1; meter = getRuntimeContext().getMetricGroup() .addGroup("flink-metrics-test") .meter("meterTest", new DropwizardMeterWrapper(dropwizardMeter)); } @Override public String map(String s) throws Exception { meter.markEvent(); System.out.println("index = " + index + " rate = " + meter.getRate() + " count = " + meter.getCount()); return s; } }).print(); env.execute("Flink custom Meter Metrics"); } } ================================================ FILE: flink-learning-basic/flink-learning-state/README.md ================================================ 模版项目,不做任何代码编写,方便创建新的 module 时复制 ================================================ FILE: flink-learning-basic/flink-learning-state/pom.xml ================================================ flink-learning-basic com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 flink-learning-state org.apache.flink flink-queryable-state-client-java ${flink.version} com.zhisheng.flink flink-learning-common ${project.version} ================================================ FILE: flink-learning-basic/flink-learning-state/src/main/java/com/zhisheng/state/Main.java ================================================ package com.zhisheng.state; /** * Desc: * Created by zhisheng on 2019-04-18 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class Main { public static void main(String[] args) { //file:///netdata/addon/flink/data/state/log } } ================================================ FILE: flink-learning-basic/flink-learning-state/src/main/java/com/zhisheng/state/metadata/MetadataSerializer.java ================================================ //package com.zhisheng.state.metadata; // //import org.apache.flink.core.fs.Path; //import org.apache.flink.runtime.checkpoint.Checkpoints; //import org.apache.flink.runtime.checkpoint.OperatorState; //import org.apache.flink.runtime.checkpoint.OperatorSubtaskState; //import org.apache.flink.runtime.state.*; //import org.apache.flink.runtime.state.filesystem.FileStateHandle; // //import java.io.*; //import java.util.Map; // ///** // * @author fanrui // * @time 2019-12-08 17:06:10 // */ //public class MetadataSerializer { // // // public static void main(String[] args) throws IOException { // // 读取元数据文件 // File f = new File("flink-learning-state/src/main/resources/_metadata"); // // 第二步,建立管道,FileInputStream文件输入流类用于读文件 // FileInputStream fis = new FileInputStream(f); // BufferedInputStream bis = new BufferedInputStream(fis); // DataInputStream dis = new DataInputStream(bis); // // // 通过 Flink 的 Checkpoints 类解析元数据文件 // Savepoint savepoint = Checkpoints.loadCheckpointMetadata(dis, MetadataSerializer.class.getClassLoader()); // // 打印当前的 CheckpointId // System.out.println(savepoint.getCheckpointId()); // // // 遍历 OperatorState,这里的每个 OperatorState 对应一个 Flink 任务的 Operator 算子 // // 不要与 OperatorState 和 KeyedState 混淆,不是一个层级的概念 // for (OperatorState operatorState : savepoint.getOperatorStates()) { // System.out.println(operatorState); // // 当前算子的状态大小为 0 ,表示算子不带状态,直接退出 // if (operatorState.getStateSize() == 0) { // continue; // } // // // 遍历当前算子的所有 subtask // for (OperatorSubtaskState operatorSubtaskState : operatorState.getStates()) { // // 解析 operatorSubtaskState 的 ManagedKeyedState // parseManagedKeyedState(operatorSubtaskState); // // 解析 operatorSubtaskState 的 ManagedOperatorState // parseManagedOperatorState(operatorSubtaskState); // } // } // } // // // /** // * 解析 operatorSubtaskState 的 ManagedKeyedState // * // * @param operatorSubtaskState operatorSubtaskState // */ // private static void parseManagedKeyedState(OperatorSubtaskState operatorSubtaskState) { // // 遍历当前 subtask 的 KeyedState // for (KeyedStateHandle keyedStateHandle : operatorSubtaskState.getManagedKeyedState()) { // // 本案例针对 Flink RocksDB 的增量 Checkpoint 引发的问题, // // 因此仅处理 IncrementalRemoteKeyedStateHandle // if (keyedStateHandle instanceof IncrementalRemoteKeyedStateHandle) { // // 获取 RocksDB 的 sharedState // Map sharedState = // ((IncrementalRemoteKeyedStateHandle) keyedStateHandle).getSharedState(); // // 遍历 sharedState 中所有的 sst 文件,key 为 sst 文件名,value 为对应的 hdfs 文件 Handle // for (Map.Entry entry : sharedState.entrySet()) { // // 打印 sst 文件名 // System.out.println("sstable 文件名:" + entry.getKey()); // if (entry.getValue() instanceof FileStateHandle) { // Path filePath = ((FileStateHandle) entry.getValue()).getFilePath(); // // 打印 sst 文件对应的 hdfs 文件位置 // System.out.println("sstable 文件对应的 hdfs 位置:" + filePath.getPath()); // } // } // } // } // } // // // /** // * 解析 operatorSubtaskState 的 ManagedOperatorState // * // * @param operatorSubtaskState operatorSubtaskState // */ // private static void parseManagedOperatorState(OperatorSubtaskState operatorSubtaskState) { // // 遍历当前 subtask 的 OperatorState // for (OperatorStateHandle operatorStateHandle : operatorSubtaskState.getManagedOperatorState()) { // StreamStateHandle delegateStateHandle = operatorStateHandle.getDelegateStateHandle(); // if (delegateStateHandle instanceof FileStateHandle) { // Path filePath = ((FileStateHandle) delegateStateHandle).getFilePath(); // System.out.println(filePath.getPath()); // } // } // } // //} ================================================ FILE: flink-learning-basic/flink-learning-state/src/main/java/com/zhisheng/state/operator/state/UnionListStateExample.java ================================================ package com.zhisheng.state.operator.state; import com.zhisheng.state.operator.state.util.UnionListStateUtil; import org.apache.flink.api.common.serialization.SimpleStringSchema; import org.apache.flink.api.common.state.ListState; import org.apache.flink.api.common.state.ListStateDescriptor; import org.apache.flink.api.common.typeinfo.TypeHint; import org.apache.flink.api.common.typeinfo.TypeInformation; import org.apache.flink.api.java.tuple.Tuple2; import org.apache.flink.configuration.Configuration; import org.apache.flink.runtime.state.FunctionInitializationContext; import org.apache.flink.runtime.state.FunctionSnapshotContext; import org.apache.flink.streaming.api.CheckpointingMode; import org.apache.flink.streaming.api.checkpoint.CheckpointedFunction; import org.apache.flink.streaming.api.environment.CheckpointConfig; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.api.functions.sink.RichSinkFunction; import org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumer; import org.apache.kafka.clients.consumer.ConsumerConfig; import java.util.Properties; import java.util.TreeMap; import java.util.concurrent.TimeUnit; /** * @author fanrui * @date 2019-10-10 11:30:15 * @desc 通过本案例可能看到 getUnionListState() 和 getListState() 的区别 * 通过 UnionListStateUtil 类来生产数据 */ public class UnionListStateExample { public static void main(String[] args) throws Exception { final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); // 1 分钟一次CheckPoint env.enableCheckpointing(TimeUnit.SECONDS.toMillis(15)); env.setParallelism(3); CheckpointConfig checkpointConf = env.getCheckpointConfig(); // CheckPoint 语义 EXACTLY ONCE checkpointConf.setCheckpointingMode(CheckpointingMode.EXACTLY_ONCE); checkpointConf.enableExternalizedCheckpoints(CheckpointConfig.ExternalizedCheckpointCleanup.RETAIN_ON_CANCELLATION); Properties props = new Properties(); props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, UnionListStateUtil.broker_list); props.put(ConsumerConfig.GROUP_ID_CONFIG, "app-pv-stat"); FlinkKafkaConsumer kafkaConsumer011 = new FlinkKafkaConsumer<>( // kafka topic, String 序列化 UnionListStateUtil.topic, new SimpleStringSchema(), props); env.addSource(kafkaConsumer011) .uid(UnionListStateUtil.topic) .addSink(new MySink()) .uid("MySink") .name("MySink"); env.execute("Flink unionListState"); } } /** * 当并行度改变后,getListState 恢复策略是均匀分配, * 将 ListState 中保存的所有元素均匀地分配到所有并行度中,每个 subtask 获取到其中一部分状态信息。 * getUnionListState 策略是将所有的状态信息合并后,每个 subtask 都获取到全量的状态信息。 */ class MySink extends RichSinkFunction implements CheckpointedFunction { private ListState> unionListState; private ListState> listState; private int subtaskIndex = 0; @Override public void open(Configuration parameters) throws Exception { super.open(parameters); subtaskIndex = getRuntimeContext().getIndexOfThisSubtask(); } @Override public void invoke(Object value, Context context) throws Exception { } @Override public void snapshotState(FunctionSnapshotContext context) throws Exception { unionListState.clear(); unionListState.add(Tuple2.of(subtaskIndex, context.getCheckpointId())); listState.clear(); listState.add(Tuple2.of(subtaskIndex, context.getCheckpointId())); System.out.println("snapshotState subtask: " + subtaskIndex + " -- CheckPointId: " + context.getCheckpointId()); } @Override public void initializeState(FunctionInitializationContext context) throws Exception { subtaskIndex = getRuntimeContext().getIndexOfThisSubtask(); // 通过 getUnionListState 获取 ListState unionListState = context.getOperatorStateStore().getUnionListState( new ListStateDescriptor<>("unionListState", TypeInformation.of(new TypeHint>() { }))); // 通过 getListState 获取 ListState listState = context.getOperatorStateStore().getListState( new ListStateDescriptor<>("listState", TypeInformation.of(new TypeHint>() { }))); System.out.println("subtask: " + subtaskIndex + " start restore state"); if (context.isRestored()) { TreeMap restoredUnionListState = new TreeMap<>(); for (Tuple2 indexOfSubtaskState : unionListState.get()) { restoredUnionListState.put(indexOfSubtaskState.f0, indexOfSubtaskState.f1); System.out.println("restore UnionListState currentSubtask: " + subtaskIndex + " restoreSubtask " + indexOfSubtaskState.f0 + " restoreCheckPointId " + indexOfSubtaskState.f1); } TreeMap restoredListState = new TreeMap<>(); for (Tuple2 indexOfSubtaskState : listState.get()) { restoredListState.put(indexOfSubtaskState.f0, indexOfSubtaskState.f1); System.out.println("restore ListState currentSubtask: " + subtaskIndex + " restoreSubtask " + indexOfSubtaskState.f0 + " restoreCheckPointId " + indexOfSubtaskState.f1); } } System.out.println("subtask: " + subtaskIndex + " complete restore"); } } ================================================ FILE: flink-learning-basic/flink-learning-state/src/main/java/com/zhisheng/state/operator/state/util/UnionListStateUtil.java ================================================ package com.zhisheng.state.operator.state.util; import org.apache.kafka.clients.producer.KafkaProducer; import org.apache.kafka.clients.producer.ProducerRecord; import java.util.Properties; import java.util.Random; /** * @author fanrui * @date 2019-10-10 12:29:47 * @desc 用于给 UnionListStateExample 生成数据 */ public class UnionListStateUtil { public static final String broker_list = "192.168.30.215:9092,192.168.30.216:9092,192.168.30.220:9092"; /** * kafka topic,Flink 程序中需要和这个统一 */ public static final String topic = "app-topic"; public static void writeToKafka() throws InterruptedException { Properties props = new Properties(); props.put("bootstrap.servers", broker_list); props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer"); props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer"); KafkaProducer producer = new KafkaProducer(props); // 生成 0~9 的随机数做为 appId for(int i = 0; i<5; i++){ String value = "" + new Random().nextInt(10); ProducerRecord record = new ProducerRecord(topic, null, null, value); producer.send(record); } System.out.println("发送数据: " ); producer.flush(); } public static void main(String[] args) throws InterruptedException { while (true) { Thread.sleep(1000); writeToKafka(); } } } ================================================ FILE: flink-learning-basic/flink-learning-state/src/main/java/com/zhisheng/state/queryablestate/ClimateLog.java ================================================ package com.zhisheng.state.queryablestate; import lombok.AllArgsConstructor; import lombok.Data; /** * Desc: * Created by zhisheng on 2019-07-05 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ @Data @AllArgsConstructor public class ClimateLog { private String country; private String state; private float temperature; private float humidity; } ================================================ FILE: flink-learning-basic/flink-learning-state/src/main/java/com/zhisheng/state/queryablestate/QueryClient.java ================================================ package com.zhisheng.state.queryablestate; /** * Desc: QueryClient * Created by zhisheng on 2019-07-05 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class QueryClient { public static void main(String[] args) { } } ================================================ FILE: flink-learning-basic/flink-learning-state/src/main/java/com/zhisheng/state/queryablestate/QuerybleStateStream.java ================================================ package com.zhisheng.state.queryablestate; import lombok.extern.slf4j.Slf4j; import org.apache.flink.api.common.functions.FlatMapFunction; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.util.Collector; /** * Desc: QuerybleStateStream * Created by zhisheng on 2019-07-05 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ @Slf4j public class QuerybleStateStream { public static void main(String[] args) { final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.setParallelism(1); // DataStreamSource socket = env.socketTextStream("localhost", 9002); env.socketTextStream("localhost", 9002) .flatMap(new FlatMapFunction() { @Override public void flatMap(String value, Collector out) throws Exception { try { String[] split = value.split(","); out.collect(new ClimateLog(split[0], split[1], Float.parseFloat(split[2]), Float.parseFloat(split[3]))); } catch (Exception e) { //如果数据有问题的话,允许把这条有问题的数据丢掉 log.warn("解析 socket 数据异常, value = {}, err = {}", value, e.getMessage()); } } }); } } ================================================ FILE: flink-learning-basic/flink-learning-window/README.md ================================================ ### Flink-learning-window Flink Window 机制学习:https://t.zsxq.com/byZbyrb ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-11-06-133449.png) #### Time Window #### Count Window #### Session Window ================================================ FILE: flink-learning-basic/flink-learning-window/pom.xml ================================================ flink-learning-basic com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 flink-learning-window com.zhisheng.flink flink-learning-common ${project.version} ================================================ FILE: flink-learning-basic/flink-learning-window/src/main/java/com/zhisheng/constant/WindowConstant.java ================================================ package com.zhisheng.constant; /** * Desc: Flink Window 案例用到的常量 * Created by zhisheng on 2019-05-14 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class WindowConstant { public static final String HOST_NAME = "hostName"; public static final String PORT = "port"; } ================================================ FILE: flink-learning-basic/flink-learning-window/src/main/java/com/zhisheng/function/CustomSource.java ================================================ package com.zhisheng.function; import com.zhisheng.common.model.WordEvent; import org.apache.flink.configuration.Configuration; import org.apache.flink.streaming.api.functions.source.RichSourceFunction; /** * Desc: * Created by zhisheng on 2019-08-07 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class CustomSource extends RichSourceFunction { private volatile boolean isRunning = true; @Override public void open(Configuration parameters) throws Exception { super.open(parameters); } @Override public void run(SourceContext ctx) throws Exception { while (isRunning) { ctx.collect(new WordEvent(word(), count(), System.currentTimeMillis())); Thread.sleep(1000); } } @Override public void close() throws Exception { super.close(); isRunning = false; } @Override public void cancel() { isRunning = false; } private String word() { String[] strs = new String[]{"A", "B", "C", "D", "E", "F"}; int index = (int) (Math.random() * strs.length); return "zhisheng" + strs[index]; } private int count() { int[] strs = new int[]{1, 2, 3, 4, 5, 6}; int index = (int) (Math.random() * strs.length); return strs[index]; } } ================================================ FILE: flink-learning-basic/flink-learning-window/src/main/java/com/zhisheng/function/CustomTrigger.java ================================================ package com.zhisheng.function; import com.zhisheng.common.model.WordEvent; import lombok.extern.slf4j.Slf4j; import org.apache.flink.api.common.state.ReducingState; import org.apache.flink.streaming.api.windowing.triggers.Trigger; import org.apache.flink.streaming.api.windowing.triggers.TriggerResult; import org.apache.flink.streaming.api.windowing.windows.TimeWindow; /** * Desc: https://stackoverrun.com/cn/q/10143011 * https://www.jianshu.com/p/1dacf2f84325 * * TriggerResult 四种可能: * * 1、CONTINUE: 什么也不做 * 2、FIRE: 触发计算 * 3、PURGE: 清除窗口中的数据 * 4、FIRE_AND_PURGE: 触发计算并清除窗口中的数据 * * Created by zhisheng on 2019-08-06 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ @Slf4j public class CustomTrigger extends Trigger { ReducingState stateDesc; private long interval; private CustomTrigger(long interval) { this.interval = interval; } public CustomTrigger() { super(); } //每个元素被添加到窗口时都会调用该方法 @Override public TriggerResult onElement(WordEvent element, long timestamp, TimeWindow window, TriggerContext ctx) throws Exception { log.info("======onElement====window start = {}, window end = {}", window.getStart(), window.getEnd()); return TriggerResult.CONTINUE; // ReducingState fireTimestamp = ctx.getPartitionedState(stateDesc); // // timestamp = ctx.getCurrentProcessingTime(); // // if (fireTimestamp.get() == null) { // long start = timestamp - (timestamp % interval); // long nextFireTimestamp = start + interval; // // ctx.registerProcessingTimeTimer(nextFireTimestamp); // // fireTimestamp.add(nextFireTimestamp); // return TriggerResult.CONTINUE; // } // return TriggerResult.CONTINUE; } //当一个已注册的 ProcessingTime 计时器启动时调用 @Override public TriggerResult onProcessingTime(long time, TimeWindow window, TriggerContext ctx) throws Exception { System.out.println("======onProcessingTime===="); return null; // ReducingState fireTimestamp = ctx.getPartitionedState(stateDesc); // // if (fireTimestamp.get().equals(time)) { // fireTimestamp.clear(); // fireTimestamp.add(time + interval); // ctx.registerProcessingTimeTimer(time + interval); // return TriggerResult.FIRE; // } else if(window.maxTimestamp() == time) { // return TriggerResult.FIRE; // } // return TriggerResult.CONTINUE; } //当一个已注册的 EventTime 计时器启动时调用 @Override public TriggerResult onEventTime(long time, TimeWindow window, TriggerContext ctx) throws Exception { System.out.println("======onEventTime===="); return null; // ReducingState fireTimestamp = ctx.getPartitionedState(stateDesc); // // if (fireTimestamp.get().equals(time)) { // fireTimestamp.clear(); // fireTimestamp.add(time + interval); // ctx.registerProcessingTimeTimer(time + interval); // return TriggerResult.FIRE; // } else if(window.maxTimestamp() == time) { // return TriggerResult.FIRE; // } // return TriggerResult.CONTINUE; } //与状态性触发器相关,当使用会话窗口时,两个触发器对应的窗口合并时,合并两个触发器的状态 @Override public void onMerge(TimeWindow window, OnMergeContext ctx) throws Exception { super.onMerge(window, ctx); } //执行任何需要清除的相应窗口 @Override public void clear(TimeWindow window, TriggerContext ctx) throws Exception { } public static CustomTrigger creat() { return new CustomTrigger(); } } ================================================ FILE: flink-learning-basic/flink-learning-window/src/main/java/com/zhisheng/function/LineSplitter.java ================================================ package com.zhisheng.function; import lombok.extern.slf4j.Slf4j; import org.apache.flink.api.common.functions.FlatMapFunction; import org.apache.flink.api.java.tuple.Tuple2; import org.apache.flink.util.Collector; /** * Desc: * Created by zhisheng on 2019-08-06 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ @Slf4j public class LineSplitter implements FlatMapFunction> { @Override public void flatMap(String s, Collector> collector) { String[] tokens = s.split(" "); if (tokens.length >= 2 && isValidLong(tokens[0])) { collector.collect(new Tuple2<>(Long.valueOf(tokens[0]), tokens[1])); } } private static boolean isValidLong(String str) { try { long _v = Long.parseLong(str); return true; } catch (NumberFormatException e) { log.info("the str = {} is not a number", str); return false; } } } ================================================ FILE: flink-learning-basic/flink-learning-window/src/main/java/com/zhisheng/window/CustomTriggerMain.java ================================================ package com.zhisheng.window; import com.zhisheng.common.model.WordEvent; import com.zhisheng.common.utils.ExecutionEnvUtil; import com.zhisheng.function.CustomSource; import com.zhisheng.function.CustomTrigger; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.streaming.api.datastream.DataStream; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.api.functions.AssignerWithPeriodicWatermarks; import org.apache.flink.streaming.api.watermark.Watermark; import org.apache.flink.streaming.api.windowing.time.Time; import javax.annotation.Nullable; /** * Desc: * 操作:在终端执行 nc -l 9000 ,然后输入 long text 类型的数据 * Created by zhisheng on 2019-08-06 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class CustomTriggerMain { public static void main(String[] args) throws Exception { final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); //如果不指定时间的话,默认是 ProcessingTime,但是如果指定为事件事件的话,需要事件中带有时间或者添加时间水印 // env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime); ParameterTool parameterTool = ExecutionEnvUtil.PARAMETER_TOOL; DataStream data = env.addSource(new CustomSource()) .assignTimestampsAndWatermarks(new AssignerWithPeriodicWatermarks() { private long currentTimestamp = Long.MIN_VALUE; private final long maxTimeLag = 5000; @Nullable @Override public Watermark getCurrentWatermark() { return new Watermark(currentTimestamp == Long.MIN_VALUE ? Long.MIN_VALUE : currentTimestamp - maxTimeLag); } @Override public long extractTimestamp(WordEvent element, long previousElementTimestamp) { long timestamp = element.getTimestamp(); currentTimestamp = Math.max(timestamp, currentTimestamp); return timestamp; } }); data.keyBy(WordEvent::getWord) .timeWindow(Time.seconds(10)) .trigger(CustomTrigger.creat()) .sum("count") .print(); env.execute("zhisheng custom Trigger Window demo"); } } ================================================ FILE: flink-learning-basic/flink-learning-window/src/main/java/com/zhisheng/window/Main.java ================================================ package com.zhisheng.window; import com.zhisheng.common.utils.ExecutionEnvUtil; import com.zhisheng.function.LineSplitter; import lombok.extern.slf4j.Slf4j; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.streaming.api.datastream.DataStreamSource; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.api.windowing.assigners.ProcessingTimeSessionWindows; import org.apache.flink.streaming.api.windowing.time.Time; import static com.zhisheng.constant.WindowConstant.HOST_NAME; import static com.zhisheng.constant.WindowConstant.PORT; /** * Desc: Flink Window 学习 * 操作:在终端执行 nc -l 9000 ,然后输入 long text 类型的数据 * Created by zhisheng on 2019-05-14 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ @Slf4j public class Main { public static void main(String[] args) throws Exception { final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); //如果不指定时间的话,默认是 ProcessingTime,但是如果指定为事件事件的话,需要事件中带有时间或者添加时间水印 // env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime); ParameterTool parameterTool = ExecutionEnvUtil.PARAMETER_TOOL; DataStreamSource data = env.socketTextStream(parameterTool.get(HOST_NAME), parameterTool.getInt(PORT)); //基于时间窗口 /* data.flatMap(new LineSplitter()) .keyBy(1) .timeWindow(Time.seconds(30)) .sum(0) .print();*/ //基于滑动时间窗口 /* data.flatMap(new LineSplitter()) .keyBy(1) .timeWindow(Time.seconds(60), Time.seconds(30)) .sum(0) .print();*/ //基于事件数量窗口 /* data.flatMap(new LineSplitter()) .keyBy(1) .countWindow(3) .sum(0) .print();*/ //基于事件数量滑动窗口 /* data.flatMap(new LineSplitter()) .keyBy(1) .countWindow(4, 3) .sum(0) .print();*/ //基于会话时间窗口 data.flatMap(new LineSplitter()) .keyBy(1) .window(ProcessingTimeSessionWindows.withGap(Time.seconds(5))) //表示如果 5s 内没出现数据则认为超出会话时长,然后计算这个窗口的和 .sum(0) .print(); env.execute("zhisheng —— flink window example"); } } ================================================ FILE: flink-learning-basic/flink-learning-window/src/main/java/com/zhisheng/window/Main2.java ================================================ package com.zhisheng.window; import com.zhisheng.common.model.WordEvent; import com.zhisheng.common.utils.ExecutionEnvUtil; import com.zhisheng.function.CustomSource; import lombok.extern.slf4j.Slf4j; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.streaming.api.TimeCharacteristic; import org.apache.flink.streaming.api.datastream.DataStream; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.api.functions.AssignerWithPeriodicWatermarks; import org.apache.flink.streaming.api.functions.windowing.WindowFunction; import org.apache.flink.streaming.api.watermark.Watermark; import org.apache.flink.streaming.api.windowing.assigners.TumblingEventTimeWindows; import org.apache.flink.streaming.api.windowing.time.Time; import org.apache.flink.streaming.api.windowing.windows.TimeWindow; import org.apache.flink.util.Collector; import javax.annotation.Nullable; /** * Desc: Flink Window & Watermark * Created by zhisheng on 2019-05-14 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ @Slf4j public class Main2 { public static void main(String[] args) throws Exception { final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); //如果不指定时间的话,默认是 ProcessingTime,但是如果指定为事件事件的话,需要事件中带有时间或者添加时间水印 env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime); env.setParallelism(1); ParameterTool parameterTool = ExecutionEnvUtil.PARAMETER_TOOL; DataStream data = env.addSource(new CustomSource()) .assignTimestampsAndWatermarks(new AssignerWithPeriodicWatermarks() { private long currentTimestamp = Long.MIN_VALUE; private final long maxTimeLag = 5000; @Nullable @Override public Watermark getCurrentWatermark() { return new Watermark(currentTimestamp == Long.MIN_VALUE ? Long.MIN_VALUE : currentTimestamp - maxTimeLag); } @Override public long extractTimestamp(WordEvent element, long previousElementTimestamp) { long timestamp = element.getTimestamp(); currentTimestamp = Math.max(timestamp, currentTimestamp); return timestamp; } }); // data.print(); data.keyBy(WordEvent::getWord) .window(TumblingEventTimeWindows.of(Time.seconds(10))) .apply(new WindowFunction() { @Override public void apply(String s, TimeWindow window, Iterable input, Collector out) throws Exception { System.out.println(window.getStart() + " " + window.getEnd() + " w"); for (WordEvent word : input) { out.collect(word); } } }) // .sum("count") .print(); env.execute("zhisheng —— flink window example"); } } ================================================ FILE: flink-learning-basic/flink-learning-window/src/main/java/com/zhisheng/window/Main3.java ================================================ package com.zhisheng.window; import com.zhisheng.common.model.WordEvent; import com.zhisheng.function.CustomSource; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.api.functions.windowing.WindowFunction; import org.apache.flink.streaming.api.windowing.assigners.TumblingProcessingTimeWindows; import org.apache.flink.streaming.api.windowing.time.Time; import org.apache.flink.streaming.api.windowing.windows.TimeWindow; import org.apache.flink.util.Collector; /** * Desc: * Created by zhisheng on 2019-12-12 21:29 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class Main3 { public static void main(String[] args) throws Exception { final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.setParallelism(1); env.addSource(new CustomSource()) .keyBy(WordEvent::getWord) .window(TumblingProcessingTimeWindows.of(Time.seconds(10))) .apply(new WindowFunction() { @Override public void apply(String s, TimeWindow window, Iterable input, Collector out) throws Exception { System.out.println(window.getStart() + " " + window.getEnd()); for (WordEvent word : input) { out.collect(word); } } }) .print(); env.execute(); } } ================================================ FILE: flink-learning-basic/flink-learning-window/src/main/java/com/zhisheng/window/Main4.java ================================================ package com.zhisheng.window; import org.apache.flink.api.common.functions.MapFunction; import org.apache.flink.api.java.tuple.Tuple2; import org.apache.flink.streaming.api.TimeCharacteristic; import org.apache.flink.streaming.api.datastream.DataStreamSource; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.api.functions.AssignerWithPeriodicWatermarks; import org.apache.flink.streaming.api.watermark.Watermark; import org.apache.flink.streaming.api.windowing.assigners.EventTimeSessionWindows; import org.apache.flink.streaming.api.windowing.time.Time; import javax.annotation.Nullable; /** * Desc: window event time */ public class Main4 { public static void main(String[] args) throws Exception { final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime); env.setParallelism(2); DataStreamSource data = env.socketTextStream("localhost", 9001); data.map(new MapFunction>() { @Override public Tuple2 map(String s) throws Exception { String[] split = s.split(","); return new Tuple2<>(split[0], Long.valueOf(split[1])); } }).assignTimestampsAndWatermarks(new AssignerWithPeriodicWatermarks>() { private long currentTimestamp; @Nullable @Override public Watermark getCurrentWatermark() { return new Watermark(currentTimestamp); } @Override public long extractTimestamp(Tuple2 tuple2, long l) { long timestamp = tuple2.f1; currentTimestamp = Math.max(timestamp, currentTimestamp); return timestamp; } }).keyBy(0) .window(EventTimeSessionWindows.withGap(Time.minutes(5))) .sum(1) .print("session "); System.out.println(env.getExecutionPlan()); env.execute(); } } ================================================ FILE: flink-learning-basic/flink-learning-window/src/main/java/com/zhisheng/window/Main5.java ================================================ package com.zhisheng.window; import org.apache.flink.streaming.api.datastream.DataStreamSource; import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.api.functions.source.SourceFunction; import org.apache.flink.streaming.api.functions.windowing.ProcessAllWindowFunction; import org.apache.flink.streaming.api.windowing.assigners.TumblingProcessingTimeWindows; import org.apache.flink.streaming.api.windowing.time.Time; import org.apache.flink.streaming.api.windowing.windows.TimeWindow; import org.apache.flink.util.Collector; /** * Desc: for test https://t.zsxq.com/3RjY3FU * Created by zhisheng on 2020-04-21 23:45 */ public class Main5 { public static void main(String[] args) throws Exception { final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.setParallelism(1); DataStreamSource source1 = env.addSource(new SourceFunction() { @Override public void run(SourceContext sourceContext) throws Exception { while (true) { sourceContext.collect("11"); Thread.sleep(200); } } @Override public void cancel() { } }); DataStreamSource source2 = env.addSource(new SourceFunction() { @Override public void run(SourceContext sourceContext) throws Exception { while (true) { sourceContext.collect("22"); Thread.sleep(400); } } @Override public void cancel() { } }); SingleOutputStreamOperator window1 = source1.timeWindowAll(Time.seconds(10)) .process(new ProcessAllWindowFunction() { @Override public void process(Context context, Iterable iterable, Collector collector) throws Exception { for (String s : iterable) { collector.collect(s); } } }); SingleOutputStreamOperator window2 = source2.timeWindowAll(Time.seconds(10)) .process(new ProcessAllWindowFunction() { @Override public void process(Context context, Iterable iterable, Collector collector) throws Exception { for (String s : iterable) { collector.collect(s); } } }); // window1.union(window2).print(); window1.union(window2) .windowAll(TumblingProcessingTimeWindows.of(Time.seconds(10))) .process(new ProcessAllWindowFunction() { @Override public void process(Context context, Iterable iterable, Collector collector) throws Exception { for (String s : iterable) { // System.out.println(s); collector.collect(s); } } }).print(); env.execute("test"); } } ================================================ FILE: flink-learning-basic/flink-learning-window/src/main/java/com/zhisheng/window/WindowAll.java ================================================ package com.zhisheng.window; import com.zhisheng.common.utils.ExecutionEnvUtil; import com.zhisheng.function.LineSplitter; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.streaming.api.datastream.DataStreamSource; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.api.windowing.time.Time; import static com.zhisheng.constant.WindowConstant.HOST_NAME; import static com.zhisheng.constant.WindowConstant.PORT; /** * Desc: timeWindow All * Created by zhisheng on 2019-06-18 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class WindowAll { public static void main(String[] args) throws Exception { final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); //如果不指定时间的话,默认是 ProcessingTime,但是如果指定为事件事件的话,需要事件中带有时间或者添加时间水印 // env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime); ParameterTool parameterTool = ExecutionEnvUtil.PARAMETER_TOOL; DataStreamSource data = env.socketTextStream(parameterTool.get(HOST_NAME), parameterTool.getInt(PORT)); data.flatMap(new LineSplitter()) .keyBy(0) .timeWindowAll(Time.seconds(10)); env.execute("zhisheng —— flink windowAll example"); } } ================================================ FILE: flink-learning-basic/flink-learning-window/src/main/resources/application.properties ================================================ hostName: localhost port: 9000 ================================================ FILE: flink-learning-basic/flink-learning-window/src/main/resources/logback.xml ================================================ %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n ================================================ FILE: flink-learning-basic/flink-learning-window/src/test/java/TestWindowSize.java ================================================ import org.apache.flink.api.common.time.Time; /** * Desc: * Created by zhisheng on 2019/11/4 上午12:11 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class TestWindowSize { public static void main(String[] args) { long l = System.currentTimeMillis(); //timestamp - (timestamp - offset + slide) % slide; System.out.println(l - (l + 60 * 1000) % 60000); long size = Time.hours(24).toMilliseconds(); long slide = Time.hours(1).toMilliseconds(); long lastStart = (1572794063000l - (1572794063000l + slide) % slide); for (long start = lastStart; start > 1572794063000l - size; start -= slide) { System.out.println(start + " " + (start + size)); } } } ================================================ FILE: flink-learning-basic/pom.xml ================================================ flink-learning com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 flink-learning-basic pom flink-learning-data-sources flink-learning-data-sinks flink-learning-window flink-learning-state flink-learning-metrics flink-learning-libraries ================================================ FILE: flink-learning-cdc/README.md ================================================ ### Flink-learning-cdc [Flink CDC](https://ccgithub.com/ververica/flink-cdc-connectors) 基础、原理、实战、应用、源码相关的内容 ### Flink CDC 学习资料 + [Flink CDC 文档](https://ververica.github.io/flink-cdc-connectors/master/) ### Flink CDC 相关论文 + [DBLog: A Watermark Based Change-Data-Capture Framework](https://arxiv.org/pdf/2010.12597v1.pdf) ### Flink 实战案例: + [https://github.com/morsapaes/flink-sql-CDC]() ================================================ FILE: flink-learning-cdc/flink-db2-cdc/pom.xml ================================================ flink-learning-cdc com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 flink-db2-cdc jar flink-db2-cdc com.ververica flink-sql-connector-db2-cdc ${flink-cdc.version} org.apache.flink flink-table-planner_${scala.binary.version} ${flink.version} provided ================================================ FILE: flink-learning-cdc/flink-db2-cdc/src/main/java/com/zhisheng/cdc/db2/Db2CDCExample.java ================================================ package com.zhisheng.cdc.db2; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.table.api.EnvironmentSettings; import org.apache.flink.table.api.bridge.java.StreamTableEnvironment; /** * Desc: Flink DB2 CDC Example * 通过 Flink CDC 实时捕获 IBM DB2 数据库的变更数据,并将结果打印到控制台 * *

使用前需要: * 1. 开启 DB2 的 CDC 功能 * 2. 创建 PRODUCTS 表并插入测试数据 * *

 * CREATE TABLE DB2INST1.PRODUCTS (
 *     ID INTEGER NOT NULL PRIMARY KEY,
 *     NAME VARCHAR(255),
 *     DESCRIPTION VARCHAR(512),
 *     WEIGHT DECIMAL(10, 2)
 * );
 * 
* * Created by zhisheng */ public class Db2CDCExample { public static void main(String[] args) throws Exception { StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.setParallelism(1); env.enableCheckpointing(5000); EnvironmentSettings settings = EnvironmentSettings.newInstance() .inStreamingMode() .build(); StreamTableEnvironment tEnv = StreamTableEnvironment.create(env, settings); // 创建 DB2 CDC 源表 String sourceDDL = "CREATE TABLE db2_products (\n" + " id INT NOT NULL,\n" + " name STRING,\n" + " description STRING,\n" + " weight DECIMAL(10, 2),\n" + " PRIMARY KEY (id) NOT ENFORCED\n" + ") WITH (\n" + " 'connector' = 'db2-cdc',\n" + " 'hostname' = 'localhost',\n" + " 'port' = '50000',\n" + " 'username' = 'db2inst1',\n" + " 'password' = '123456',\n" + " 'database-name' = 'mydb',\n" + " 'schema-name' = 'DB2INST1',\n" + " 'table-name' = 'PRODUCTS'\n" + ")"; // 创建 print 结果表 String sinkDDL = "CREATE TABLE print_sink (\n" + " id INT NOT NULL,\n" + " name STRING,\n" + " description STRING,\n" + " weight DECIMAL(10, 2),\n" + " PRIMARY KEY (id) NOT ENFORCED\n" + ") WITH (\n" + " 'connector' = 'print'\n" + ")"; tEnv.executeSql(sourceDDL); tEnv.executeSql(sinkDDL); // 将 CDC 数据写入到 print sink tEnv.executeSql("INSERT INTO print_sink SELECT * FROM db2_products"); } } ================================================ FILE: flink-learning-cdc/flink-mongodb-cdc/pom.xml ================================================ flink-learning-cdc com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 flink-mongodb-cdc jar flink-mongodb-cdc com.ververica flink-sql-connector-mongodb-cdc ${flink-cdc.version} org.apache.flink flink-table-planner_${scala.binary.version} ${flink.version} provided ================================================ FILE: flink-learning-cdc/flink-mongodb-cdc/src/main/java/com/zhisheng/cdc/mongodb/MongoDBCDCExample.java ================================================ package com.zhisheng.cdc.mongodb; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.table.api.EnvironmentSettings; import org.apache.flink.table.api.bridge.java.StreamTableEnvironment; /** * Desc: Flink MongoDB CDC Example * 通过 Flink CDC 实时捕获 MongoDB 数据库的变更数据,并将结果打印到控制台 * *

使用前需要: * 1. 开启 MongoDB 的 Replica Set(副本集)模式 * 2. 创建 products 集合并插入测试数据 * *

 * db.products.insertMany([
 *     { _id: "1", name: "scooter", description: "Big 2-wheel scooter", weight: 5.18 },
 *     { _id: "2", name: "car battery", description: "12V car battery", weight: 8.1 }
 * ]);
 * 
* * Created by zhisheng */ public class MongoDBCDCExample { public static void main(String[] args) throws Exception { StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.setParallelism(1); env.enableCheckpointing(5000); EnvironmentSettings settings = EnvironmentSettings.newInstance() .inStreamingMode() .build(); StreamTableEnvironment tEnv = StreamTableEnvironment.create(env, settings); // 创建 MongoDB CDC 源表 String sourceDDL = "CREATE TABLE mongodb_products (\n" + " _id STRING NOT NULL,\n" + " name STRING,\n" + " description STRING,\n" + " weight DECIMAL(10, 2),\n" + " PRIMARY KEY (_id) NOT ENFORCED\n" + ") WITH (\n" + " 'connector' = 'mongodb-cdc',\n" + " 'hosts' = 'localhost:27017',\n" + " 'username' = 'flinkuser',\n" + " 'password' = 'flinkpw',\n" + " 'database' = 'mydb',\n" + " 'collection' = 'products'\n" + ")"; // 创建 print 结果表 String sinkDDL = "CREATE TABLE print_sink (\n" + " _id STRING NOT NULL,\n" + " name STRING,\n" + " description STRING,\n" + " weight DECIMAL(10, 2),\n" + " PRIMARY KEY (_id) NOT ENFORCED\n" + ") WITH (\n" + " 'connector' = 'print'\n" + ")"; tEnv.executeSql(sourceDDL); tEnv.executeSql(sinkDDL); // 将 CDC 数据写入到 print sink tEnv.executeSql("INSERT INTO print_sink SELECT * FROM mongodb_products"); } } ================================================ FILE: flink-learning-cdc/flink-mysql-cdc/pom.xml ================================================ flink-learning-cdc com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 flink-mysql-cdc jar com.ververica flink-sql-connector-mysql-cdc ${flink-cdc.version} org.apache.flink flink-table-planner_${scala.binary.version} ${flink.version} provided ================================================ FILE: flink-learning-cdc/flink-mysql-cdc/src/main/java/com/zhisheng/cdc/mysql/MysqlCDCExample.java ================================================ package com.zhisheng.cdc.mysql; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.table.api.EnvironmentSettings; import org.apache.flink.table.api.bridge.java.StreamTableEnvironment; /** * Desc: Flink MySQL CDC Example * 通过 Flink CDC 实时捕获 MySQL 数据库的变更数据,并将结果打印到控制台 * *

使用前需要: * 1. 开启 MySQL 的 binlog * 2. 创建 products 表并插入测试数据 * *

 * CREATE TABLE products (
 *     id INT PRIMARY KEY,
 *     name VARCHAR(255),
 *     description VARCHAR(512),
 *     weight DECIMAL(10, 2)
 * );
 * 
* * Created by zhisheng */ public class MysqlCDCExample { public static void main(String[] args) throws Exception { StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.setParallelism(1); env.enableCheckpointing(5000); EnvironmentSettings settings = EnvironmentSettings.newInstance() .inStreamingMode() .build(); StreamTableEnvironment tEnv = StreamTableEnvironment.create(env, settings); // 创建 MySQL CDC 源表 String sourceDDL = "CREATE TABLE mysql_products (\n" + " id INT NOT NULL,\n" + " name STRING,\n" + " description STRING,\n" + " weight DECIMAL(10, 2),\n" + " PRIMARY KEY (id) NOT ENFORCED\n" + ") WITH (\n" + " 'connector' = 'mysql-cdc',\n" + " 'hostname' = 'localhost',\n" + " 'port' = '3306',\n" + " 'username' = 'root',\n" + " 'password' = '123456',\n" + " 'database-name' = 'mydb',\n" + " 'table-name' = 'products'\n" + ")"; // 创建 print 结果表 String sinkDDL = "CREATE TABLE print_sink (\n" + " id INT NOT NULL,\n" + " name STRING,\n" + " description STRING,\n" + " weight DECIMAL(10, 2),\n" + " PRIMARY KEY (id) NOT ENFORCED\n" + ") WITH (\n" + " 'connector' = 'print'\n" + ")"; tEnv.executeSql(sourceDDL); tEnv.executeSql(sinkDDL); // 将 CDC 数据写入到 print sink tEnv.executeSql("INSERT INTO print_sink SELECT * FROM mysql_products"); } } ================================================ FILE: flink-learning-cdc/flink-oceanbase-cdc/pom.xml ================================================ flink-learning-cdc com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 flink-oceanbase-cdc jar flink-oceanbase-cdc com.ververica flink-sql-connector-oceanbase-cdc ${flink-cdc.version} org.apache.flink flink-table-planner_${scala.binary.version} ${flink.version} provided ================================================ FILE: flink-learning-cdc/flink-oceanbase-cdc/src/main/java/com/zhisheng/cdc/oceanbase/OceanBaseCDCExample.java ================================================ package com.zhisheng.cdc.oceanbase; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.table.api.EnvironmentSettings; import org.apache.flink.table.api.bridge.java.StreamTableEnvironment; /** * Desc: Flink OceanBase CDC Example * 通过 Flink CDC 实时捕获 OceanBase 数据库的变更数据,并将结果打印到控制台 * *

使用前需要: * 1. 部署 OceanBase 集群和 oblogproxy 日志代理服务 * 2. 创建 products 表并插入测试数据 * *

 * CREATE TABLE products (
 *     id INT PRIMARY KEY,
 *     name VARCHAR(255),
 *     description VARCHAR(512),
 *     weight DECIMAL(10, 2)
 * );
 * 
* * Created by zhisheng */ public class OceanBaseCDCExample { public static void main(String[] args) throws Exception { StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.setParallelism(1); env.enableCheckpointing(5000); EnvironmentSettings settings = EnvironmentSettings.newInstance() .inStreamingMode() .build(); StreamTableEnvironment tEnv = StreamTableEnvironment.create(env, settings); // 创建 OceanBase CDC 源表 String sourceDDL = "CREATE TABLE oceanbase_products (\n" + " id INT NOT NULL,\n" + " name STRING,\n" + " description STRING,\n" + " weight DECIMAL(10, 2),\n" + " PRIMARY KEY (id) NOT ENFORCED\n" + ") WITH (\n" + " 'connector' = 'oceanbase-cdc',\n" + " 'scan.startup.mode' = 'initial',\n" + " 'username' = 'user@my_tenant',\n" + " 'password' = 'pswd',\n" + " 'tenant-name' = 'my_tenant',\n" + " 'database-name' = 'mydb',\n" + " 'table-name' = 'products',\n" + " 'hostname' = 'localhost',\n" + " 'port' = '2881',\n" + " 'logproxy.host' = 'localhost',\n" + " 'logproxy.port' = '2983',\n" + " 'rootserver-list' = '127.0.0.1:2882:2881'\n" + ")"; // 创建 print 结果表 String sinkDDL = "CREATE TABLE print_sink (\n" + " id INT NOT NULL,\n" + " name STRING,\n" + " description STRING,\n" + " weight DECIMAL(10, 2),\n" + " PRIMARY KEY (id) NOT ENFORCED\n" + ") WITH (\n" + " 'connector' = 'print'\n" + ")"; tEnv.executeSql(sourceDDL); tEnv.executeSql(sinkDDL); // 将 CDC 数据写入到 print sink tEnv.executeSql("INSERT INTO print_sink SELECT * FROM oceanbase_products"); } } ================================================ FILE: flink-learning-cdc/flink-oracle-cdc/pom.xml ================================================ flink-learning-cdc com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 flink-oracle-cdc jar com.ververica flink-sql-connector-oracle-cdc ${flink-cdc.version} org.apache.flink flink-table-planner_${scala.binary.version} ${flink.version} provided ================================================ FILE: flink-learning-cdc/flink-oracle-cdc/src/main/java/com/zhisheng/cdc/oracle/OracleCDCExample.java ================================================ package com.zhisheng.cdc.oracle; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.table.api.EnvironmentSettings; import org.apache.flink.table.api.bridge.java.StreamTableEnvironment; /** * Desc: Flink Oracle CDC Example * 通过 Flink CDC 实时捕获 Oracle 数据库的变更数据,并将结果打印到控制台 * *

使用前需要: * 1. 开启 Oracle 的 LogMiner * 2. 创建 products 表并插入测试数据 * *

 * CREATE TABLE products (
 *     id NUMBER(10) PRIMARY KEY,
 *     name VARCHAR2(255),
 *     description VARCHAR2(512),
 *     weight NUMBER(10, 2)
 * );
 * 
* * Created by zhisheng */ public class OracleCDCExample { public static void main(String[] args) throws Exception { StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.setParallelism(1); env.enableCheckpointing(5000); EnvironmentSettings settings = EnvironmentSettings.newInstance() .inStreamingMode() .build(); StreamTableEnvironment tEnv = StreamTableEnvironment.create(env, settings); // 创建 Oracle CDC 源表 String sourceDDL = "CREATE TABLE oracle_products (\n" + " id INT NOT NULL,\n" + " name STRING,\n" + " description STRING,\n" + " weight DECIMAL(10, 2),\n" + " PRIMARY KEY (id) NOT ENFORCED\n" + ") WITH (\n" + " 'connector' = 'oracle-cdc',\n" + " 'hostname' = 'localhost',\n" + " 'port' = '1521',\n" + " 'username' = 'flinkuser',\n" + " 'password' = 'flinkpw',\n" + " 'database-name' = 'XE',\n" + " 'schema-name' = 'flinkuser',\n" + " 'table-name' = 'products'\n" + ")"; // 创建 print 结果表 String sinkDDL = "CREATE TABLE print_sink (\n" + " id INT NOT NULL,\n" + " name STRING,\n" + " description STRING,\n" + " weight DECIMAL(10, 2),\n" + " PRIMARY KEY (id) NOT ENFORCED\n" + ") WITH (\n" + " 'connector' = 'print'\n" + ")"; tEnv.executeSql(sourceDDL); tEnv.executeSql(sinkDDL); // 将 CDC 数据写入到 print sink tEnv.executeSql("INSERT INTO print_sink SELECT * FROM oracle_products"); } } ================================================ FILE: flink-learning-cdc/flink-postgres-cdc/pom.xml ================================================ flink-learning-cdc com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 flink-postgres-cdc jar com.ververica flink-sql-connector-postgres-cdc ${flink-cdc.version} org.apache.flink flink-table-planner_${scala.binary.version} ${flink.version} provided ================================================ FILE: flink-learning-cdc/flink-postgres-cdc/src/main/java/com/zhisheng/cdc/postgres/PostgresCDCExample.java ================================================ package com.zhisheng.cdc.postgres; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.table.api.EnvironmentSettings; import org.apache.flink.table.api.bridge.java.StreamTableEnvironment; /** * Desc: Flink PostgreSQL CDC Example * 通过 Flink CDC 实时捕获 PostgreSQL 数据库的变更数据,并将结果打印到控制台 * *

使用前需要: * 1. PostgreSQL 设置 wal_level = logical * 2. 创建 products 表并插入测试数据 * *

 * CREATE TABLE products (
 *     id INTEGER PRIMARY KEY,
 *     name VARCHAR(255),
 *     description VARCHAR(512),
 *     weight NUMERIC(10, 2)
 * );
 * 
* * Created by zhisheng */ public class PostgresCDCExample { public static void main(String[] args) throws Exception { StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.setParallelism(1); env.enableCheckpointing(5000); EnvironmentSettings settings = EnvironmentSettings.newInstance() .inStreamingMode() .build(); StreamTableEnvironment tEnv = StreamTableEnvironment.create(env, settings); // 创建 PostgreSQL CDC 源表 String sourceDDL = "CREATE TABLE postgres_products (\n" + " id INT NOT NULL,\n" + " name STRING,\n" + " description STRING,\n" + " weight DECIMAL(10, 2),\n" + " PRIMARY KEY (id) NOT ENFORCED\n" + ") WITH (\n" + " 'connector' = 'postgres-cdc',\n" + " 'hostname' = 'localhost',\n" + " 'port' = '5432',\n" + " 'username' = 'postgres',\n" + " 'password' = '123456',\n" + " 'database-name' = 'postgres',\n" + " 'schema-name' = 'public',\n" + " 'table-name' = 'products',\n" + " 'slot.name' = 'flink'\n" + ")"; // 创建 print 结果表 String sinkDDL = "CREATE TABLE print_sink (\n" + " id INT NOT NULL,\n" + " name STRING,\n" + " description STRING,\n" + " weight DECIMAL(10, 2),\n" + " PRIMARY KEY (id) NOT ENFORCED\n" + ") WITH (\n" + " 'connector' = 'print'\n" + ")"; tEnv.executeSql(sourceDDL); tEnv.executeSql(sinkDDL); // 将 CDC 数据写入到 print sink tEnv.executeSql("INSERT INTO print_sink SELECT * FROM postgres_products"); } } ================================================ FILE: flink-learning-cdc/flink-sqlserver-cdc/pom.xml ================================================ flink-learning-cdc com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 flink-sqlserver-cdc jar flink-sqlserver-cdc com.ververica flink-sql-connector-sqlserver-cdc ${flink-cdc.version} org.apache.flink flink-table-planner_${scala.binary.version} ${flink.version} provided ================================================ FILE: flink-learning-cdc/flink-sqlserver-cdc/src/main/java/com/zhisheng/cdc/sqlserver/SqlServerCDCExample.java ================================================ package com.zhisheng.cdc.sqlserver; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.table.api.EnvironmentSettings; import org.apache.flink.table.api.bridge.java.StreamTableEnvironment; /** * Desc: Flink SQL Server CDC Example * 通过 Flink CDC 实时捕获 SQL Server 数据库的变更数据,并将结果打印到控制台 * *

使用前需要: * 1. 开启 SQL Server 的 CDC 功能 * 2. 创建 products 表并插入测试数据 * *

 * CREATE TABLE products (
 *     id INT PRIMARY KEY,
 *     name VARCHAR(255),
 *     description VARCHAR(512),
 *     weight DECIMAL(10, 2)
 * );
 * EXEC sys.sp_cdc_enable_table @source_schema = 'dbo', @source_name = 'products', @role_name = NULL;
 * 
* * Created by zhisheng */ public class SqlServerCDCExample { public static void main(String[] args) throws Exception { StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.setParallelism(1); env.enableCheckpointing(5000); EnvironmentSettings settings = EnvironmentSettings.newInstance() .inStreamingMode() .build(); StreamTableEnvironment tEnv = StreamTableEnvironment.create(env, settings); // 创建 SQL Server CDC 源表 String sourceDDL = "CREATE TABLE sqlserver_products (\n" + " id INT NOT NULL,\n" + " name STRING,\n" + " description STRING,\n" + " weight DECIMAL(10, 2),\n" + " PRIMARY KEY (id) NOT ENFORCED\n" + ") WITH (\n" + " 'connector' = 'sqlserver-cdc',\n" + " 'hostname' = 'localhost',\n" + " 'port' = '1433',\n" + " 'username' = 'sa',\n" + " 'password' = 'Password!',\n" + " 'database-name' = 'inventory',\n" + " 'schema-name' = 'dbo',\n" + " 'table-name' = 'products'\n" + ")"; // 创建 print 结果表 String sinkDDL = "CREATE TABLE print_sink (\n" + " id INT NOT NULL,\n" + " name STRING,\n" + " description STRING,\n" + " weight DECIMAL(10, 2),\n" + " PRIMARY KEY (id) NOT ENFORCED\n" + ") WITH (\n" + " 'connector' = 'print'\n" + ")"; tEnv.executeSql(sourceDDL); tEnv.executeSql(sinkDDL); // 将 CDC 数据写入到 print sink tEnv.executeSql("INSERT INTO print_sink SELECT * FROM sqlserver_products"); } } ================================================ FILE: flink-learning-cdc/flink-tidb-cdc/pom.xml ================================================ flink-learning-cdc com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 flink-tidb-cdc jar flink-tidb-cdc com.ververica flink-sql-connector-tidb-cdc ${flink-cdc.version} org.apache.flink flink-table-planner_${scala.binary.version} ${flink.version} provided ================================================ FILE: flink-learning-cdc/flink-tidb-cdc/src/main/java/com/zhisheng/cdc/tidb/TidbCDCExample.java ================================================ package com.zhisheng.cdc.tidb; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.table.api.EnvironmentSettings; import org.apache.flink.table.api.bridge.java.StreamTableEnvironment; /** * Desc: Flink TiDB CDC Example * 通过 Flink CDC 实时捕获 TiDB 数据库的变更数据,并将结果打印到控制台 * *

使用前需要: * 1. 部署 TiDB 集群(包含 TiKV 和 PD 组件) * 2. 创建 products 表并插入测试数据 * *

 * CREATE TABLE products (
 *     id INT PRIMARY KEY,
 *     name VARCHAR(255),
 *     description VARCHAR(512),
 *     weight DECIMAL(10, 2)
 * );
 * 
* * Created by zhisheng */ public class TidbCDCExample { public static void main(String[] args) throws Exception { StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.setParallelism(1); env.enableCheckpointing(5000); EnvironmentSettings settings = EnvironmentSettings.newInstance() .inStreamingMode() .build(); StreamTableEnvironment tEnv = StreamTableEnvironment.create(env, settings); // 创建 TiDB CDC 源表 String sourceDDL = "CREATE TABLE tidb_products (\n" + " id INT NOT NULL,\n" + " name STRING,\n" + " description STRING,\n" + " weight DECIMAL(10, 2),\n" + " PRIMARY KEY (id) NOT ENFORCED\n" + ") WITH (\n" + " 'connector' = 'tidb-cdc',\n" + " 'tikv.grpc.timeout_in_ms' = '20000',\n" + " 'pd-addresses' = 'localhost:2379',\n" + " 'database-name' = 'mydb',\n" + " 'table-name' = 'products'\n" + ")"; // 创建 print 结果表 String sinkDDL = "CREATE TABLE print_sink (\n" + " id INT NOT NULL,\n" + " name STRING,\n" + " description STRING,\n" + " weight DECIMAL(10, 2),\n" + " PRIMARY KEY (id) NOT ENFORCED\n" + ") WITH (\n" + " 'connector' = 'print'\n" + ")"; tEnv.executeSql(sourceDDL); tEnv.executeSql(sinkDDL); // 将 CDC 数据写入到 print sink tEnv.executeSql("INSERT INTO print_sink SELECT * FROM tidb_products"); } } ================================================ FILE: flink-learning-cdc/pom.xml ================================================ flink-learning com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 flink-learning-cdc pom flink-mysql-cdc flink-postgres-cdc flink-oracle-cdc flink-tidb-cdc flink-sqlserver-cdc flink-oceanbase-cdc flink-mongodb-cdc flink-db2-cdc ================================================ FILE: flink-learning-common/README.md ================================================ ### Flink-learning-common 这个模块存放通用的代码(实体类、工具类、常量类) ================================================ FILE: flink-learning-common/pom.xml ================================================ flink-learning com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 flink-learning-common com.google.guava guava 30.1-jre com.google.code.gson gson 2.8.5 org.apache.httpcomponents httpclient 4.5.13 org.projectlombok lombok 1.18.36 com.jayway.jsonpath json-path 2.9.0 joda-time joda-time 2.9.9 junit junit 4.12 test org.apache.flink flink-statebackend-rocksdb ${flink.version} ================================================ FILE: flink-learning-common/src/main/java/com/zhisheng/common/constant/MachineConstant.java ================================================ package com.zhisheng.common.constant; /** * Desc: * Created by zhisheng on 2019/10/15 上午12:20 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class MachineConstant { public static final String CLUSTER_NAME = "cluster_name"; public static final String HOST_IP = "host_ip"; public static final String LOAD5 = "load5"; public static final String USED_PERCENT = "usedPercent"; public static final String CPU = "cpu"; public static final String MEM = "mem"; public static final String LOAD = "load"; public static final String SWAP = "swap"; } ================================================ FILE: flink-learning-common/src/main/java/com/zhisheng/common/constant/PropertiesConstants.java ================================================ package com.zhisheng.common.constant; /** * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class PropertiesConstants { public static final String ZHISHENG = "zhisheng"; public static final String KAFKA_BROKERS = "kafka.brokers"; public static final String DEFAULT_KAFKA_BROKERS = "localhost:9092"; public static final String KAFKA_ZOOKEEPER_CONNECT = "kafka.zookeeper.connect"; public static final String DEFAULT_KAFKA_ZOOKEEPER_CONNECT = "localhost:2181"; public static final String KAFKA_GROUP_ID = "kafka.group.id"; public static final String DEFAULT_KAFKA_GROUP_ID = "zhisheng"; public static final String METRICS_TOPIC = "metrics.topic"; public static final String CONSUMER_FROM_TIME = "consumer.from.time"; public static final String STREAM_PARALLELISM = "stream.parallelism"; public static final String STREAM_SINK_PARALLELISM = "stream.sink.parallelism"; public static final String STREAM_CHECKPOINT_ENABLE = "stream.checkpoint.enable"; public static final String STREAM_CHECKPOINT_DIR = "stream.checkpoint.dir"; public static final String STREAM_CHECKPOINT_TYPE = "stream.checkpoint.type"; public static final String STREAM_CHECKPOINT_INTERVAL = "stream.checkpoint.interval"; public static final String PROPERTIES_FILE_NAME = "/application.properties"; public static final String CHECKPOINT_MEMORY = "memory"; public static final String CHECKPOINT_FS = "fs"; public static final String CHECKPOINT_ROCKETSDB = "rocksdb"; //es config public static final String ELASTICSEARCH_BULK_FLUSH_MAX_ACTIONS = "elasticsearch.bulk.flush.max.actions"; public static final String ELASTICSEARCH_HOSTS = "elasticsearch.hosts"; //mysql public static final String MYSQL_DATABASE = "mysql.database"; public static final String MYSQL_HOST = "mysql.host"; public static final String MYSQL_PASSWORD = "mysql.password"; public static final String MYSQL_PORT = "mysql.port"; public static final String MYSQL_USERNAME = "mysql.username"; } ================================================ FILE: flink-learning-common/src/main/java/com/zhisheng/common/model/LogEvent.java ================================================ package com.zhisheng.common.model; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import java.util.HashMap; import java.util.Map; /** * Desc: log event * Created by zhisheng on 2019/10/13 上午10:07 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ @Data @NoArgsConstructor @AllArgsConstructor @Builder public class LogEvent { //the type of log(app、docker、...) private String type; // the timestamp of log private Long timestamp; //the level of log(debug/info/warn/error) private String level; //the message of log private String message; //the tag of log(appId、dockerId、machine hostIp、machine clusterName、...) private Map tags = new HashMap<>(); } ================================================ FILE: flink-learning-common/src/main/java/com/zhisheng/common/model/MetricEvent.java ================================================ package com.zhisheng.common.model; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import java.util.Map; /** * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ @Data @Builder @AllArgsConstructor @NoArgsConstructor public class MetricEvent { /** * Metric name */ private String name; /** * Metric timestamp */ private Long timestamp; /** * Metric fields */ private Map fields; /** * Metric tags */ private Map tags; } ================================================ FILE: flink-learning-common/src/main/java/com/zhisheng/common/model/OrderEvent.java ================================================ package com.zhisheng.common.model; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; /** * Desc: 单个店铺的订单 * Created by zhisheng on 2019-04-18 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ @Data @Builder @NoArgsConstructor @AllArgsConstructor public class OrderEvent { /** * Order Id */ private Long id; /** * 购物单 Id */ private Long purchaseOrderId; /** * Order device source(1:IOS、2:PC、3:Android) */ private int deviceSource; /** * 买家 Id */ private Long buyerId; /** * 买家 name */ private String buyerName; /** * 店铺 Id */ private String shopId; /** * 店铺 name */ private String shopName; /** * 支付状态(0:未支付、1:已支付) */ private int payStatus; /** * 支付完成时间(timestamp) */ private Long payAt; /** * 实际支付金额(以分为单位) */ private Long payAmount; /** * 发货状态(0:未发货、1:待发货、2:已发货) */ private int deliveryStatus; /** * 签收状态(0:未签收、1:已签收) */ private int receiveStatus; /** * 退货状态(0:未退货、1:已退货) */ private int reverseStatus; /** * 发货时间(timestamp) */ private Long shippingAt; /** * 确认收货时间(timestamp) */ private Long confirmAt; /** * 买家留言 */ private String buyerNotes; } ================================================ FILE: flink-learning-common/src/main/java/com/zhisheng/common/model/OrderLineEvent.java ================================================ package com.zhisheng.common.model; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import java.util.List; /** * Desc: * Created by zhisheng on 2019-04-18 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ @Data @Builder @AllArgsConstructor @NoArgsConstructor public class OrderLineEvent { /** * 订单行 Id */ private Long orderLineId; /** * 购物单 Id */ private Long purchaseOrderId; /** * 店铺订单 Id */ private Long orderId; /** * 买家 Id */ private Long buyerId; /** * 买家 name */ private String buyerName; /** * 店铺 Id */ private String shopId; /** * 店铺 name */ private String shopName; /** * 支付状态(0:未支付、1:已支付) */ private int payStatus; /** * 支付完成时间(timestamp) */ private Long payAt; /** * 发货状态(0:未发货、1:待发货、2:已发货) */ private int deliveryStatus; /** * 签收状态(0:未签收、1:已签收) */ private int receiveStatus; /** * 退货状态(0:未退货、1:已退货) */ private int reverseStatus; /** * 发货时间(timestamp) */ private Long shippingAt; /** * 确认收货时间(timestamp) */ private Long confirmAt; /** * 买家留言 */ private String buyerNotes; /** * 订单来源(1:IOS、2:PC、3:Android) */ private int deviceSource; /** * 商品 name */ private String name; /** * 商品 id */ private Long id; /** * 商品标签 */ private List tags; /** * 购买数量 */ private int count; /** * 实际支付金额 */ private Long payAmount; } ================================================ FILE: flink-learning-common/src/main/java/com/zhisheng/common/model/ProductEvent.java ================================================ package com.zhisheng.common.model; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import java.util.List; /** * Desc: 商品 * Created by zhisheng on 2019-04-18 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ @Data @Builder @AllArgsConstructor @NoArgsConstructor public class ProductEvent { /** * Product Id */ private Long id; /** * Product 类目 Id */ private Long categoryId; /** * Product 编码 */ private String code; /** * Product 店铺 Id */ private Long shopId; /** * Product 店铺 name */ private String shopName; /** * Product 品牌 Id */ private Long brandId; /** * Product 品牌 name */ private String brandName; /** * Product name */ private String name; /** * Product 图片地址 */ private String imageUrl; /** * Product 状态(1(上架),-1(下架),-2(冻结),-3(删除)) */ private int status; /** * Product 类型 */ private int type; /** * Product 标签 */ private List tags; /** * Product 价格(以分为单位) */ private Long price; } ================================================ FILE: flink-learning-common/src/main/java/com/zhisheng/common/model/ShopEvent.java ================================================ package com.zhisheng.common.model; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; /** * Desc: Shop * Created by zhisheng on 2019-04-18 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ @Data @Builder @NoArgsConstructor @AllArgsConstructor public class ShopEvent { /** * Shop Id */ private Long id; /** * Shop name */ private String name; /** * shop owner Id */ private Long ownerId; /** * shop owner name */ private String ownerName; /** * shop status: (1:正常, -1:关闭, -2:冻结) */ private int status; /** * shop type: (1:门店 2:商家 3:出版社) */ private int type; /** * shop phone */ private String phone; /** * shop email */ private String email; /** * shop address */ private String address; /** * shop image url */ private String imageUrl; } ================================================ FILE: flink-learning-common/src/main/java/com/zhisheng/common/model/UserEvent.java ================================================ package com.zhisheng.common.model; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; /** * Desc: User * Created by zhisheng on 2019-04-18 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ @Data @Builder @AllArgsConstructor @NoArgsConstructor public class UserEvent { /** * user Id */ private Long id; /** * User Name */ private String userName; /** * User email */ private String email; /** * User phone number */ private String phoneNumber; /** * User 真实姓名 */ private String realName; /** * User 展示名称 */ private String displayName; /** * User 头像 Url */ private String avatarUrl; /** * User 密码(加密后) */ private String password; /** * User 地址 */ private String address; /** * User 注册来源(1:IOS、2:PC、3:Android) */ private int deviceSource; } ================================================ FILE: flink-learning-common/src/main/java/com/zhisheng/common/model/WordEvent.java ================================================ package com.zhisheng.common.model; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; /** * Desc: * Created by zhisheng on 2019-08-07 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ @Data @AllArgsConstructor @NoArgsConstructor @Builder public class WordEvent { private String word; private int count; private long timestamp; } ================================================ FILE: flink-learning-common/src/main/java/com/zhisheng/common/schemas/KafkaMetricSchema.java ================================================ package com.zhisheng.common.schemas; import org.apache.flink.api.common.typeinfo.TypeInformation; import org.apache.flink.shaded.jackson2.com.fasterxml.jackson.databind.ObjectMapper; import org.apache.flink.shaded.jackson2.com.fasterxml.jackson.databind.node.ObjectNode; import org.apache.flink.streaming.connectors.kafka.KafkaDeserializationSchema; import org.apache.kafka.clients.consumer.ConsumerRecord; import static org.apache.flink.api.java.typeutils.TypeExtractor.getForClass; /** * Desc: 实现 KafkaDeserializationSchema 接口的反序列化类,可以获取数据的元数据, * 注意这种和 JSONKeyValueDeserializationSchema 有个区别就是,本类是不需要数据源的数据是 JSON * Created by zhisheng on 2019-09-23 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class KafkaMetricSchema implements KafkaDeserializationSchema { private static final long serialVersionUID = 1509391548173891955L; private final boolean includeMetadata; private ObjectMapper mapper; public KafkaMetricSchema(boolean includeMetadata) { this.includeMetadata = includeMetadata; } @Override public boolean isEndOfStream(ObjectNode metricEvent) { return false; } @Override public ObjectNode deserialize(ConsumerRecord consumerRecord) throws Exception { if (mapper == null) { mapper = new ObjectMapper(); } ObjectNode node = mapper.createObjectNode(); if (consumerRecord.key() != null) { node.put("key", new String(consumerRecord.key())); } if (consumerRecord.value() != null) { node.put("value", new String(consumerRecord.value())); } if (includeMetadata) { node.putObject("metadata") .put("offset", consumerRecord.offset()) .put("topic", consumerRecord.topic()) .put("partition", consumerRecord.partition()); } return node; } @Override public TypeInformation getProducedType() { return getForClass(ObjectNode.class); } } ================================================ FILE: flink-learning-common/src/main/java/com/zhisheng/common/schemas/LogSchema.java ================================================ package com.zhisheng.common.schemas; import com.google.gson.Gson; import com.zhisheng.common.model.LogEvent; import org.apache.flink.api.common.serialization.DeserializationSchema; import org.apache.flink.api.common.serialization.SerializationSchema; import org.apache.flink.api.common.typeinfo.TypeInformation; import java.io.IOException; import java.nio.charset.StandardCharsets; /** * Log Schema ,支持序列化和反序列化 * * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng * */ public class LogSchema implements DeserializationSchema, SerializationSchema { private static final Gson gson = new Gson(); @Override public LogEvent deserialize(byte[] bytes) throws IOException { return gson.fromJson(new String(bytes), LogEvent.class); } @Override public boolean isEndOfStream(LogEvent logEvent) { return false; } @Override public byte[] serialize(LogEvent logEvent) { return gson.toJson(logEvent).getBytes(StandardCharsets.UTF_8); } @Override public TypeInformation getProducedType() { return TypeInformation.of(LogEvent.class); } } ================================================ FILE: flink-learning-common/src/main/java/com/zhisheng/common/schemas/MetricSchema.java ================================================ package com.zhisheng.common.schemas; import com.google.gson.Gson; import com.zhisheng.common.model.MetricEvent; import org.apache.flink.api.common.serialization.DeserializationSchema; import org.apache.flink.api.common.serialization.SerializationSchema; import org.apache.flink.api.common.typeinfo.TypeInformation; import java.io.IOException; import java.nio.charset.StandardCharsets; /** * Metric Schema ,支持序列化和反序列化 *

* blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class MetricSchema implements DeserializationSchema, SerializationSchema { private static final Gson gson = new Gson(); @Override public MetricEvent deserialize(byte[] bytes) throws IOException { return gson.fromJson(new String(bytes), MetricEvent.class); } @Override public boolean isEndOfStream(MetricEvent metricEvent) { return false; } @Override public byte[] serialize(MetricEvent metricEvent) { return gson.toJson(metricEvent).getBytes(StandardCharsets.UTF_8); } @Override public TypeInformation getProducedType() { return TypeInformation.of(MetricEvent.class); } } ================================================ FILE: flink-learning-common/src/main/java/com/zhisheng/common/schemas/OrderLineSchema.java ================================================ package com.zhisheng.common.schemas; import com.google.gson.Gson; import com.zhisheng.common.model.OrderLineEvent; import org.apache.flink.api.common.serialization.DeserializationSchema; import org.apache.flink.api.common.serialization.SerializationSchema; import org.apache.flink.api.common.typeinfo.TypeInformation; import java.io.IOException; import java.nio.charset.StandardCharsets; /** * OrderLine Schema ,支持序列化和反序列化 *

* blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class OrderLineSchema implements DeserializationSchema, SerializationSchema { private static final Gson gson = new Gson(); @Override public OrderLineEvent deserialize(byte[] bytes) throws IOException { return gson.fromJson(new String(bytes), OrderLineEvent.class); } @Override public boolean isEndOfStream(OrderLineEvent orderLineEvent) { return false; } @Override public byte[] serialize(OrderLineEvent orderLineEvent) { return gson.toJson(orderLineEvent).getBytes(StandardCharsets.UTF_8); } @Override public TypeInformation getProducedType() { return TypeInformation.of(OrderLineEvent.class); } } ================================================ FILE: flink-learning-common/src/main/java/com/zhisheng/common/schemas/OrderSchema.java ================================================ package com.zhisheng.common.schemas; import com.google.gson.Gson; import com.zhisheng.common.model.OrderEvent; import org.apache.flink.api.common.serialization.DeserializationSchema; import org.apache.flink.api.common.serialization.SerializationSchema; import org.apache.flink.api.common.typeinfo.TypeInformation; import java.io.IOException; import java.nio.charset.StandardCharsets; /** * order Schema ,支持序列化和反序列化 *

* blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class OrderSchema implements DeserializationSchema, SerializationSchema { private static final Gson gson = new Gson(); @Override public OrderEvent deserialize(byte[] bytes) throws IOException { return gson.fromJson(new String(bytes), OrderEvent.class); } @Override public boolean isEndOfStream(OrderEvent orderEvent) { return false; } @Override public byte[] serialize(OrderEvent orderEvent) { return gson.toJson(orderEvent).getBytes(StandardCharsets.UTF_8); } @Override public TypeInformation getProducedType() { return TypeInformation.of(OrderEvent.class); } } ================================================ FILE: flink-learning-common/src/main/java/com/zhisheng/common/schemas/ProductSchema.java ================================================ package com.zhisheng.common.schemas; import com.google.gson.Gson; import com.zhisheng.common.model.ProductEvent; import org.apache.flink.api.common.serialization.DeserializationSchema; import org.apache.flink.api.common.serialization.SerializationSchema; import org.apache.flink.api.common.typeinfo.TypeInformation; import java.io.IOException; import java.nio.charset.StandardCharsets; /** * Product Schema ,支持序列化和反序列化 *

* blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class ProductSchema implements DeserializationSchema, SerializationSchema { private static final Gson gson = new Gson(); @Override public ProductEvent deserialize(byte[] bytes) throws IOException { return gson.fromJson(new String(bytes), ProductEvent.class); } @Override public boolean isEndOfStream(ProductEvent productEvent) { return false; } @Override public byte[] serialize(ProductEvent productEvent) { return gson.toJson(productEvent).getBytes(StandardCharsets.UTF_8); } @Override public TypeInformation getProducedType() { return TypeInformation.of(ProductEvent.class); } } ================================================ FILE: flink-learning-common/src/main/java/com/zhisheng/common/schemas/ShopSchema.java ================================================ package com.zhisheng.common.schemas; import com.google.gson.Gson; import com.zhisheng.common.model.ShopEvent; import org.apache.flink.api.common.serialization.DeserializationSchema; import org.apache.flink.api.common.serialization.SerializationSchema; import org.apache.flink.api.common.typeinfo.TypeInformation; import java.io.IOException; import java.nio.charset.StandardCharsets; /** * Shop Schema ,支持序列化和反序列化 *

* blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class ShopSchema implements DeserializationSchema, SerializationSchema { private static final Gson gson = new Gson(); @Override public ShopEvent deserialize(byte[] bytes) throws IOException { return gson.fromJson(new String(bytes), ShopEvent.class); } @Override public boolean isEndOfStream(ShopEvent shopEvent) { return false; } @Override public byte[] serialize(ShopEvent shopEvent) { return gson.toJson(shopEvent).getBytes(StandardCharsets.UTF_8); } @Override public TypeInformation getProducedType() { return TypeInformation.of(ShopEvent.class); } } ================================================ FILE: flink-learning-common/src/main/java/com/zhisheng/common/schemas/UserSchema.java ================================================ package com.zhisheng.common.schemas; import com.google.gson.Gson; import com.zhisheng.common.model.UserEvent; import org.apache.flink.api.common.serialization.DeserializationSchema; import org.apache.flink.api.common.serialization.SerializationSchema; import org.apache.flink.api.common.typeinfo.TypeInformation; import java.io.IOException; import java.nio.charset.StandardCharsets; /** * User Schema ,支持序列化和反序列化 *

* blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class UserSchema implements DeserializationSchema, SerializationSchema { private static final Gson gson = new Gson(); @Override public UserEvent deserialize(byte[] bytes) throws IOException { return gson.fromJson(new String(bytes), UserEvent.class); } @Override public boolean isEndOfStream(UserEvent userEvent) { return false; } @Override public byte[] serialize(UserEvent userEvent) { return gson.toJson(userEvent).getBytes(StandardCharsets.UTF_8); } @Override public TypeInformation getProducedType() { return TypeInformation.of(UserEvent.class); } } ================================================ FILE: flink-learning-common/src/main/java/com/zhisheng/common/utils/CheckPointUtil.java ================================================ package com.zhisheng.common.utils; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.contrib.streaming.state.RocksDBStateBackend; import org.apache.flink.runtime.state.StateBackend; import org.apache.flink.runtime.state.filesystem.FsStateBackend; import org.apache.flink.runtime.state.memory.MemoryStateBackend; import org.apache.flink.streaming.api.CheckpointingMode; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import java.net.URI; import static com.zhisheng.common.constant.PropertiesConstants.*; /** * Desc: Checkpoint 工具类 * Created by zhisheng on 2019-09-06 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class CheckPointUtil { public static StreamExecutionEnvironment setCheckpointConfig(StreamExecutionEnvironment env, ParameterTool parameterTool) throws Exception{ if (parameterTool.getBoolean(STREAM_CHECKPOINT_ENABLE, false) && CHECKPOINT_MEMORY.equals(parameterTool.get(STREAM_CHECKPOINT_TYPE).toLowerCase())) { //1、state 存放在内存中,默认是 5M StateBackend stateBackend = new MemoryStateBackend(5 * 1024 * 1024 * 100); env.enableCheckpointing(parameterTool.getLong(STREAM_CHECKPOINT_INTERVAL, 60000)); env.setStateBackend(stateBackend); } if (parameterTool.getBoolean(STREAM_CHECKPOINT_ENABLE, false) && CHECKPOINT_FS.equals(parameterTool.get(STREAM_CHECKPOINT_TYPE).toLowerCase())) { StateBackend stateBackend = new FsStateBackend(new URI(parameterTool.get(STREAM_CHECKPOINT_DIR)), 0); env.enableCheckpointing(parameterTool.getLong(STREAM_CHECKPOINT_INTERVAL, 60000)); env.setStateBackend(stateBackend); } if (parameterTool.getBoolean(STREAM_CHECKPOINT_ENABLE, false) && CHECKPOINT_ROCKETSDB.equals(parameterTool.get(STREAM_CHECKPOINT_TYPE).toLowerCase())) { RocksDBStateBackend rocksDBStateBackend = new RocksDBStateBackend(parameterTool.get(STREAM_CHECKPOINT_DIR)); env.setStateBackend(rocksDBStateBackend); } //设置 checkpoint 周期时间 env.enableCheckpointing(parameterTool.getLong(STREAM_CHECKPOINT_INTERVAL, 60000)); //高级设置(这些配置也建议写成配置文件中去读取,优先环境变量) // 设置 exactly-once 模式 env.getCheckpointConfig().setCheckpointingMode(CheckpointingMode.EXACTLY_ONCE); // 设置 checkpoint 最小间隔 500 ms env.getCheckpointConfig().setMinPauseBetweenCheckpoints(500); // 设置 checkpoint 必须在1分钟内完成,否则会被丢弃 env.getCheckpointConfig().setCheckpointTimeout(60000); // 设置 checkpoint 失败时,任务不会 fail,该 checkpoint 会被丢弃 env.getCheckpointConfig().setFailOnCheckpointingErrors(false); // 设置 checkpoint 的并发度为 1 env.getCheckpointConfig().setMaxConcurrentCheckpoints(1); return env; } } ================================================ FILE: flink-learning-common/src/main/java/com/zhisheng/common/utils/DateUtil.java ================================================ package com.zhisheng.common.utils; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormatter; import java.util.Calendar; import java.util.Date; /** * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class DateUtil { public static DateTimeFormatter YYYY_MM_DD = DateTimeFormat.forPattern("yyyy-MM-dd"); public static DateTimeFormatter YYYYMMDD = DateTimeFormat.forPattern("yyyyMMdd"); public static DateTimeFormatter YYYYMMDDHHMMSS = DateTimeFormat.forPattern("yyyyMMddHHmmss"); public static DateTimeFormatter YYYY_MM_DD_HH_MM_SS = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss"); public static DateTimeFormatter YYYY_MM_DD_HH_MM_SS_SSS = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss.SSS"); public static DateTimeFormatter YYYY_MM_DD_HH_MM_SS_SSSZ = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss.SSSZ"); public static DateTimeFormatter YYYY_MM_DD_HH_MM_SS_0 = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss.0"); public static DateTimeFormatter YYYY_MM_DD_HH_MM = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm"); public static DateTimeFormatter YYYYMMDD_HH_MM_SS = DateTimeFormat.forPattern("yyyy/MM/dd HH:mm:ss"); public static String format(Date date, DateTimeFormatter dateFormatter) { DateTime dateTime = new DateTime(date); return dateTime.toString(dateFormatter); } public static String format(Date date, DateTimeZone timeZone, DateTimeFormatter dateFormatter) { DateTime dateTime = new DateTime(date, timeZone); return dateTime.toString(dateFormatter); } public static String format(long timeStamp, DateTimeFormatter dateFormatter) { return format(timeStamp, "Asia/Shanghai", dateFormatter); } public static String format(long timeStamp, String timeZoneId, DateTimeFormatter dateFormatter) { DateTimeZone timeZone = DateTimeZone.forID(timeZoneId); DateTime dateTime = new DateTime(timeStamp, timeZone); return dateTime.toString(dateFormatter); } /** * 格式化日期 * * @param time * @param dateFormatter * @return */ public static long format(String time, DateTimeFormatter dateFormatter) { if (YYYY_MM_DD_HH_MM_SS_0.equals(dateFormatter)) { return YYYY_MM_DD_HH_MM_SS_0.parseMillis(time); } else if (YYYY_MM_DD_HH_MM_SS.equals(dateFormatter)) { return YYYY_MM_DD_HH_MM_SS.parseMillis(time); } else if (YYYY_MM_DD_HH_MM.equals(dateFormatter)) { return YYYY_MM_DD_HH_MM.parseMillis(time); } else if (YYYY_MM_DD.equals(dateFormatter)) { return YYYY_MM_DD.parseMillis(time); } else if (YYYY_MM_DD_HH_MM_SS_SSS.equals(dateFormatter)) { return YYYY_MM_DD_HH_MM_SS_SSS.parseMillis(time); } else if (YYYY_MM_DD_HH_MM_SS_SSSZ.equals(dateFormatter)) { return YYYY_MM_DD_HH_MM_SS_SSSZ.parseMillis(time); } return YYYY_MM_DD_HH_MM_SS.parseMillis(time); } public static long format(Date date) { return YYYY_MM_DD_HH_MM_SS.parseMillis(format(date, YYYY_MM_DD_HH_MM_SS)); } /** * 判断时间是否有效 * * @param value * @param formatter * @return */ public static Boolean isValidDate(String value, DateTimeFormatter formatter) { try { formatter.parseDateTime(value); return true; } catch (Exception e) { return false; } } /** * 根据传入值的时间字符串和格式,输出Date类型 * * @param value * @param formatter * @return */ public static Date toDate(String value, DateTimeFormatter formatter) { return formatter.parseDateTime(value).toDate(); } /** * 获取当天的开始时间的字符串 * * @param date 当天日期 * @return 当天的开始时间 */ public static String withTimeAtStartOfDay(Date date, DateTimeFormatter formatter) { return new DateTime(date).withTimeAtStartOfDay().toString(formatter); } /** * 获取当天的开始时间的字符串 * * @param date 当天日期 * @return 当天的开始时间 */ public static String withTimeAtStartOfDay(DateTime date, DateTimeFormatter formatter) { return date.withTimeAtStartOfDay().toString(formatter); } /** * 获取当天的结束时间的字符串 * * @param date 当天日期 * @return 当天的开始时间 */ public static String withTimeAtEndOfDay(Date date, DateTimeFormatter formatter) { return new DateTime(date).withTimeAtStartOfDay().plusDays(1).minusSeconds(1).toString(formatter); } /** * 获取当天的结束时间的字符串 * * @param date 当天日期 * @return 当天的开始时间 */ public static String withTimeAtEndOfDay(DateTime date, DateTimeFormatter formatter) { return date.withTimeAtStartOfDay().plusDays(1).minusSeconds(1).toString(formatter); } /** * 获取Now的开始时间的字符串 * 格式默认YYYYMMDDHHMMSS * * @return 当天的开始时间 */ public static String withTimeAtStartOfNow() { return DateTime.now().withTimeAtStartOfDay().toString(YYYYMMDDHHMMSS); } /** * 获取Now的结束时间的字符串 * 格式默认YYYYMMDDHHMMSS * * @return 当天的开始时间 */ public static String withTimeAtEndOfNow() { return DateTime.now().withTimeAtStartOfDay().plusDays(1).minusSeconds(1).toString(YYYYMMDDHHMMSS); } /** * 根据指定的时间戳获取前 l 或者后 l 天的时间戳 * * @param timestamp * @param l * @return */ public static Long getPastTime(Long timestamp, int l) { if (timestamp == null) { timestamp = System.currentTimeMillis(); } Calendar calendar = Calendar.getInstance(); calendar.setTimeInMillis(timestamp); calendar.set(Calendar.DATE, calendar.get(Calendar.DATE) + l); return format(calendar.getTime()); } } ================================================ FILE: flink-learning-common/src/main/java/com/zhisheng/common/utils/ExecutionEnvUtil.java ================================================ package com.zhisheng.common.utils; import com.zhisheng.common.constant.PropertiesConstants; import org.apache.flink.api.common.restartstrategy.RestartStrategies; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.streaming.api.TimeCharacteristic; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import java.io.IOException; /** * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class ExecutionEnvUtil { public static ParameterTool createParameterTool(final String[] args) throws Exception { return ParameterTool .fromPropertiesFile(ExecutionEnvUtil.class.getResourceAsStream(PropertiesConstants.PROPERTIES_FILE_NAME)) .mergeWith(ParameterTool.fromArgs(args)) .mergeWith(ParameterTool.fromSystemProperties()); } public static final ParameterTool PARAMETER_TOOL = createParameterTool(); private static ParameterTool createParameterTool() { try { return ParameterTool .fromPropertiesFile(ExecutionEnvUtil.class.getResourceAsStream(PropertiesConstants.PROPERTIES_FILE_NAME)) .mergeWith(ParameterTool.fromSystemProperties()); } catch (IOException e) { e.printStackTrace(); } return ParameterTool.fromSystemProperties(); } public static StreamExecutionEnvironment prepare(ParameterTool parameterTool) throws Exception { StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.setParallelism(parameterTool.getInt(PropertiesConstants.STREAM_PARALLELISM, 5)); env.getConfig().setRestartStrategy(RestartStrategies.fixedDelayRestart(4, 60000)); if (parameterTool.getBoolean(PropertiesConstants.STREAM_CHECKPOINT_ENABLE, true)) { env.enableCheckpointing(parameterTool.getLong(PropertiesConstants.STREAM_CHECKPOINT_INTERVAL, 10000)); } env.getConfig().setGlobalJobParameters(parameterTool); env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime); return env; } } ================================================ FILE: flink-learning-common/src/main/java/com/zhisheng/common/utils/GsonUtil.java ================================================ package com.zhisheng.common.utils; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import java.lang.reflect.Type; import java.nio.charset.Charset; /** * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class GsonUtil { private final static Gson gson = new Gson(); private final static Gson disableHtmlEscapingGson = new GsonBuilder().disableHtmlEscaping().create(); public static T fromJson(String value, Class type) { return gson.fromJson(value, type); } public static T fromJson(String value, Type type) { return gson.fromJson(value, type); } public static String toJson(Object value) { return gson.toJson(value); } public static String toJsonDisableHtmlEscaping(Object value) { return disableHtmlEscapingGson.toJson(value); } public static byte[] toJSONBytes(Object value) { return gson.toJson(value).getBytes(Charset.forName("UTF-8")); } } ================================================ FILE: flink-learning-common/src/main/java/com/zhisheng/common/utils/HttpUtil.java ================================================ package com.zhisheng.common.utils; import org.apache.commons.codec.binary.Base64; import org.apache.http.HttpEntity; import org.apache.http.HttpStatus; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPut; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.util.EntityUtils; import java.io.IOException; import java.nio.charset.Charset; /** * HTTP 工具类 * * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class HttpUtil { //构造 Basic Auth 的用户名和密码 private static final String USER_NAME = ""; private static final String PASSWORD = ""; private static final CloseableHttpClient httpClient = HttpClients.createDefault(); public static String doPostString(String url, String jsonParams) throws Exception { CloseableHttpResponse response = null; HttpPost httpPost = new HttpPost(url); String httpStr; try { StringEntity entity = new StringEntity(jsonParams, "UTF-8"); entity.setContentEncoding("UTF-8"); entity.setContentType("application/json"); httpPost.setEntity(entity); httpPost.setHeader("content-type", "application/json"); //如果要设置 Basic Auth 的话 // httpPost.setHeader("Authorization", getHeader()); response = httpClient.execute(httpPost); httpStr = EntityUtils.toString(response.getEntity(), "UTF-8"); } finally { if (response != null) { EntityUtils.consume(response.getEntity()); response.close(); } } return httpStr; } /** * 通过GET方式发起http请求 */ public static String doGet(String url) { HttpGet get = new HttpGet(url); get.setHeader("content-type", "application/json"); //如果要设置 Basic Auth 的话 // get.setHeader("Authorization", getHeader()); CloseableHttpResponse httpResponse = null; try { httpResponse = httpClient.execute(get); if (httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { HttpEntity entity = httpResponse.getEntity(); if (null != entity) { return EntityUtils.toString(httpResponse.getEntity()); } } } catch (IOException e) { e.printStackTrace(); } finally { try { if (httpResponse != null) httpResponse.close(); } catch (IOException e) { e.printStackTrace(); } } return null; } public static String doPutString(String url, String jsonParams) throws Exception { CloseableHttpResponse response = null; HttpPut httpPut = new HttpPut(url); String httpStr; try { StringEntity entity = new StringEntity(jsonParams, "UTF-8"); entity.setContentEncoding("UTF-8"); entity.setContentType("application/json"); httpPut.setEntity(entity); httpPut.setHeader("content-type", "application/json"); //如果要设置 Basic Auth 的话 // httpPut.setHeader("Authorization", getHeader()); response = httpClient.execute(httpPut); httpStr = EntityUtils.toString(response.getEntity(), "UTF-8"); } finally { if (response != null) { EntityUtils.consume(response.getEntity()); response.close(); } } return httpStr; } /** * 发送 POST 请求(HTTP),JSON形式 * * @param url 调用的地址 * @param jsonParams 调用的参数 * @return * @throws Exception */ public static CloseableHttpResponse doPostResponse(String url, String jsonParams) throws Exception { CloseableHttpResponse response = null; HttpPost httpPost = new HttpPost(url); try { StringEntity entity = new StringEntity(jsonParams, "UTF-8"); entity.setContentEncoding("UTF-8"); entity.setContentType("application/json"); httpPost.setEntity(entity); httpPost.setHeader("content-type", "application/json"); //如果要设置 Basic Auth 的话 // httpPost.setHeader("Authorization", getHeader()); response = httpClient.execute(httpPost); } finally { if (response != null) { EntityUtils.consume(response.getEntity()); } } return response; } /** * 构造Basic Auth认证头信息 * * @return */ private static String getHeader() { String auth = USER_NAME + ":" + PASSWORD; byte[] encodedAuth = Base64.encodeBase64(auth.getBytes(Charset.forName("US-ASCII"))); String authHeader = "Basic " + new String(encodedAuth); return authHeader; } } ================================================ FILE: flink-learning-common/src/main/java/com/zhisheng/common/utils/KafkaConfigUtil.java ================================================ package com.zhisheng.common.utils; import com.zhisheng.common.constant.PropertiesConstants; import com.zhisheng.common.model.MetricEvent; import com.zhisheng.common.schemas.MetricSchema; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.streaming.api.datastream.DataStreamSource; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumer; import org.apache.flink.streaming.connectors.kafka.internals.KafkaTopicPartition; import org.apache.kafka.clients.consumer.KafkaConsumer; import org.apache.kafka.clients.consumer.OffsetAndTimestamp; import org.apache.kafka.common.PartitionInfo; import org.apache.kafka.common.TopicPartition; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import static com.zhisheng.common.constant.PropertiesConstants.*; /** * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class KafkaConfigUtil { /** * 设置基础的 Kafka 配置 * * @return */ public static Properties buildKafkaProps() { return buildKafkaProps(ParameterTool.fromSystemProperties()); } /** * 设置 kafka 配置 * * @param parameterTool * @return */ public static Properties buildKafkaProps(ParameterTool parameterTool) { Properties props = parameterTool.getProperties(); props.put("bootstrap.servers", parameterTool.get(PropertiesConstants.KAFKA_BROKERS, DEFAULT_KAFKA_BROKERS)); props.put("zookeeper.connect", parameterTool.get(PropertiesConstants.KAFKA_ZOOKEEPER_CONNECT, DEFAULT_KAFKA_ZOOKEEPER_CONNECT)); props.put("group.id", parameterTool.get(PropertiesConstants.KAFKA_GROUP_ID, DEFAULT_KAFKA_GROUP_ID)); props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer"); props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer"); props.put("auto.offset.reset", "latest"); return props; } public static DataStreamSource buildSource(StreamExecutionEnvironment env) throws IllegalAccessException { ParameterTool parameter = (ParameterTool) env.getConfig().getGlobalJobParameters(); String topic = parameter.getRequired(PropertiesConstants.METRICS_TOPIC); Long time = parameter.getLong(PropertiesConstants.CONSUMER_FROM_TIME, 0L); return buildSource(env, topic, time); } /** * @param env * @param topic * @param time 订阅的时间 * @return * @throws IllegalAccessException */ public static DataStreamSource buildSource(StreamExecutionEnvironment env, String topic, Long time) throws IllegalAccessException { ParameterTool parameterTool = (ParameterTool) env.getConfig().getGlobalJobParameters(); Properties props = buildKafkaProps(parameterTool); FlinkKafkaConsumer consumer = new FlinkKafkaConsumer<>( topic, new MetricSchema(), props); //重置offset到time时刻 if (time != 0L) { Map partitionOffset = buildOffsetByTime(props, parameterTool, time); consumer.setStartFromSpecificOffsets(partitionOffset); } return env.addSource(consumer); } private static Map buildOffsetByTime(Properties props, ParameterTool parameterTool, Long time) { props.setProperty("group.id", "query_time_" + time); KafkaConsumer consumer = new KafkaConsumer(props); List partitionsFor = consumer.partitionsFor(parameterTool.getRequired(PropertiesConstants.METRICS_TOPIC)); Map partitionInfoLongMap = new HashMap<>(); for (PartitionInfo partitionInfo : partitionsFor) { partitionInfoLongMap.put(new TopicPartition(partitionInfo.topic(), partitionInfo.partition()), time); } Map offsetResult = consumer.offsetsForTimes(partitionInfoLongMap); Map partitionOffset = new HashMap<>(); offsetResult.forEach((key, value) -> partitionOffset.put(new KafkaTopicPartition(key.topic(), key.partition()), value.offset())); consumer.close(); return partitionOffset; } } ================================================ FILE: flink-learning-common/src/main/java/com/zhisheng/common/watermarks/MetricWatermark.java ================================================ package com.zhisheng.common.watermarks; import com.zhisheng.common.model.MetricEvent; import org.apache.flink.streaming.api.functions.AssignerWithPeriodicWatermarks; import org.apache.flink.streaming.api.watermark.Watermark; import javax.annotation.Nullable; /** * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class MetricWatermark implements AssignerWithPeriodicWatermarks { private long currentTimestamp = Long.MIN_VALUE; @Override public long extractTimestamp(MetricEvent metricEvent, long previousElementTimestamp) { long timestamp = metricEvent.getTimestamp(); currentTimestamp = Math.max(timestamp, currentTimestamp); return timestamp; } @Nullable @Override public Watermark getCurrentWatermark() { long maxTimeLag = 5000; return new Watermark(currentTimestamp == Long.MIN_VALUE ? Long.MIN_VALUE : currentTimestamp - maxTimeLag); } } ================================================ FILE: flink-learning-common/src/main/resources/product.sql ================================================ CREATE TABLE `uc_users` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id', `username` varchar(40) DEFAULT NULL COMMENT '用户名', `connection_id` bigint(20) DEFAULT NULL COMMENT '废弃', `email` varchar(254) CHARACTER SET utf8 DEFAULT NULL COMMENT '邮箱', `email_verified` tinyint(1) NOT NULL DEFAULT '0' COMMENT '邮箱是否验证通过', `phone_number` varchar(64) CHARACTER SET utf8 DEFAULT NULL COMMENT '手机号', `phone_number_verified` tinyint(1) NOT NULL DEFAULT '0' COMMENT '手机号是否验证通过', `real_name` varchar(40) DEFAULT NULL COMMENT '真实姓名', `display_name` varchar(40) CHARACTER SET utf8 DEFAULT NULL COMMENT '展示名称', `nickname` varchar(40) DEFAULT NULL COMMENT '昵称', `given_name` varchar(40) CHARACTER SET utf8 DEFAULT NULL COMMENT '名', `family_name` varchar(40) CHARACTER SET utf8 DEFAULT NULL COMMENT '姓', `middle_name` varchar(40) CHARACTER SET utf8 DEFAULT NULL COMMENT 'middle name', `avatar_url` varchar(2000) CHARACTER SET utf8 DEFAULT NULL COMMENT '头像', `password` varchar(255) CHARACTER SET utf8 DEFAULT NULL COMMENT '密码', `password_strength` int(11) DEFAULT NULL COMMENT '密码强度', `enabled` tinyint(1) NOT NULL DEFAULT '1' COMMENT '是否启用', `locked` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否冻结', `type` smallint(6) NOT NULL COMMENT '暂不支持', `source` varchar(100) CHARACTER SET utf8 DEFAULT NULL COMMENT '注册渠道', `last_login_at` timestamp NULL DEFAULT NULL COMMENT '上次登录时间', `gender` varchar(10) CHARACTER SET utf8 DEFAULT NULL COMMENT '性别', `birthday` timestamp NULL DEFAULT NULL COMMENT '创建时间', `zone_info` varchar(20) CHARACTER SET utf8 DEFAULT NULL COMMENT '时区', `locale` varchar(20) CHARACTER SET utf8 DEFAULT NULL COMMENT '预留信息', `website` varchar(2000) CHARACTER SET utf8 DEFAULT NULL COMMENT '个人主页', `address` varchar(1000) CHARACTER SET utf8 DEFAULT NULL COMMENT '地址', `extra` varchar(5000) CHARACTER SET utf8 DEFAULT NULL COMMENT '扩展字段', `created_at` timestamp NULL DEFAULT NULL COMMENT '创建时间', `updated_at` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', `external_source` varchar(255) CHARACTER SET utf8 DEFAULT NULL COMMENT '外部来源', `external_id` varchar(255) CHARACTER SET utf8 DEFAULT NULL COMMENT '外部来源id', `reg_client_id` varchar(64) CHARACTER SET utf8 DEFAULT NULL COMMENT 'reg client id', `version` int(11) DEFAULT NULL COMMENT '版本', `is_delete` tinyint(1) NOT NULL DEFAULT '0' COMMENT '逻辑删除delete flag (0:not,1:yes)', `register_ip` varchar(64) CHARACTER SET utf8 DEFAULT NULL COMMENT '注册IP', `invitation_code` varchar(255) CHARACTER SET utf8 DEFAULT NULL COMMENT '扩展字段', `sign_in_type` varchar(255) CHARACTER SET utf8 DEFAULT NULL, `authorities` varchar(1024) CHARACTER SET utf8 DEFAULT NULL, `referrer_id` bigint(20) DEFAULT NULL, `channel` varchar(64) DEFAULT NULL, `combination` varchar(512) DEFAULT NULL, `group` varchar(64) DEFAULT 'default', `metadata` varchar(2048) DEFAULT NULL, `sign_up_type` varchar(64) DEFAULT NULL, `updated_by` varchar(128) DEFAULT NULL, `tenant_id` int(10) DEFAULT NULL, PRIMARY KEY (`id`) USING BTREE, UNIQUE KEY `uk_username` (`username`) USING BTREE, UNIQUE KEY `uk_phone_number` (`phone_number`) USING BTREE, UNIQUE KEY `un_uc_users_phone` (`phone_number`,`group`), UNIQUE KEY `un_uc_users_username` (`username`,`group`), KEY `idx_uc_users_nickname` (`nickname`), KEY `idx_uc_users_group` (`group`) ) ENGINE=InnoDB AUTO_INCREMENT=217214 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC COMMENT='用户信息表'; CREATE TABLE `order` ( `order_id` bigint(20) NOT NULL COMMENT '主键', `tenant_id` int(10) NOT NULL COMMENT '租户id', `out_id` varchar(32) DEFAULT '' COMMENT '外部订单id', `purchase_order_id` bigint(19) NOT NULL COMMENT '购物单id', `device_source` varchar(16) DEFAULT NULL COMMENT '来源: IOS/PC...', `biz_code` varchar(32) NOT NULL DEFAULT '' COMMENT '业务类型', `buyer_id` bigint(19) NOT NULL COMMENT '买家id', `buyer_name` varchar(64) NOT NULL DEFAULT '' COMMENT '买家姓名', `shop_name` varchar(64) DEFAULT NULL COMMENT '卖家姓名', `shop_id` bigint(20) NOT NULL COMMENT '卖家id', `enable_status` smallint(5) NOT NULL COMMENT '是否可见, 1: 可见; 0: 不可见', `pay_status` varchar(64) NOT NULL, `delivery_status` varchar(64) NOT NULL, `receive_status` varchar(32) NOT NULL, `reverse_status` varchar(64) NOT NULL, `pay_at` datetime DEFAULT NULL COMMENT '支付完成时间', `shipping_at` datetime DEFAULT NULL COMMENT '发货时间', `confirm_at` datetime DEFAULT NULL COMMENT '确认收货时间', `paid_amount` bigint(20) NOT NULL COMMENT '实际支付金额', `sku_origin_total_amount` bigint(20) NOT NULL, `sku_adjust_amount` bigint(20) NOT NULL, `sku_discount_total_amount` bigint(20) NOT NULL, `ship_fee_origin_amount` bigint(20) NOT NULL, `ship_fee_adjust_amount` bigint(20) NOT NULL, `ship_fee_discount_total_amount` bigint(20) NOT NULL, `tax_fee_origin_amount` bigint(20) NOT NULL, `tax_fee_adjust_amount` bigint(20) NOT NULL, `tax_fee_discount_total_amount` bigint(20) NOT NULL, `discount_detail` varchar(1024) DEFAULT NULL, `buyer_notes` varchar(127) DEFAULT NULL COMMENT '买家留言', `delivery_address` varchar(1024) DEFAULT NULL COMMENT '收货地址, json格式,包含详细地址和各级id,以及type', `shop_notes` varchar(127) DEFAULT NULL COMMENT '卖家留言', `tag` bigint(19) DEFAULT NULL COMMENT '打标', `extra_json` varchar(1024) DEFAULT NULL COMMENT '拓展字段', `version` int(10) NOT NULL COMMENT '乐观锁', `updated_by` varchar(64) DEFAULT NULL, `created_at` datetime NOT NULL, `updated_at` datetime NOT NULL, `accomplish_at` datetime DEFAULT NULL COMMENT '订单完成时间', PRIMARY KEY (`order_id`), KEY `idx_order_tenant_id` (`tenant_id`), KEY `idx_order_purchase_order_id` (`purchase_order_id`), KEY `idx_order_biz_code` (`biz_code`), KEY `idx_order_buyer_id` (`buyer_id`), KEY `idx_order_shop_id` (`shop_id`), KEY `idx_order_out_id` (`out_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='店铺订单'; CREATE TABLE `order_line` ( `order_line_id` bigint(20) NOT NULL COMMENT '订单行id', `tenant_id` int(10) DEFAULT NULL COMMENT '租户id', `out_id` varchar(32) DEFAULT NULL COMMENT '外部订单号', `biz_code` varchar(32) NOT NULL DEFAULT '' COMMENT '业务类型', `purchase_order_id` bigint(20) NOT NULL COMMENT '购物单id', `order_id` bigint(20) NOT NULL COMMENT '店铺订单id', `buyer_id` bigint(20) NOT NULL COMMENT '买家id', `buyer_name` varchar(64) NOT NULL DEFAULT '' COMMENT '买家姓名', `shop_id` bigint(20) NOT NULL COMMENT '店铺id', `shop_name` varchar(128) DEFAULT '' COMMENT '店铺名称', `warehouse_code_plan` varchar(32) DEFAULT NULL COMMENT '计划发货门店id', `warehouse_code_actual` varchar(32) DEFAULT NULL COMMENT '实际发货门店id', `pay_status` varchar(32) NOT NULL, `delivery_status` varchar(32) NOT NULL, `receive_status` varchar(32) NOT NULL, `reverse_status` varchar(64) NOT NULL, `enable_status` smallint(5) NOT NULL COMMENT '是否可见. 1: 可见. 0: 不可见', `refund_at` datetime DEFAULT NULL COMMENT '退款完成时间', `confirm_at` datetime DEFAULT NULL COMMENT '确认收获时间', `sku_id` bigint(19) DEFAULT NULL COMMENT 'sku id', `sku_code` varchar(64) DEFAULT NULL COMMENT 'sku code', `sku_name` varchar(512) NOT NULL DEFAULT '' COMMENT 'sku 名称', `sku_image` varchar(512) DEFAULT '' COMMENT 'sku 缩率图', `sku_attr` varchar(1024) DEFAULT NULL COMMENT 'sku 销售属性', `quantity` int(10) NOT NULL COMMENT '数量', `paid_amount` bigint(20) NOT NULL COMMENT '实际支付金额', `sku_origin_total_amount` bigint(20) NOT NULL, `sku_adjust_amount` bigint(20) NOT NULL, `sku_discount_total_amount` bigint(20) NOT NULL, `ship_fee_origin_amount` bigint(20) NOT NULL, `ship_fee_adjust_amount` bigint(20) NOT NULL, `ship_fee_discount_total_amount` bigint(20) NOT NULL, `tax_fee_origin_amount` bigint(20) NOT NULL, `tax_fee_adjust_amount` bigint(20) NOT NULL, `tax_fee_discount_total_amount` bigint(20) NOT NULL, `discount_detail` varchar(2048) DEFAULT NULL, `device_source` varchar(16) NOT NULL COMMENT '来源: IOS/PC.....', `tag` bigint(19) NOT NULL COMMENT '打标', `master_id` bigint(19) DEFAULT NULL COMMENT '组合商品的主商品id, 如果该商品为主商品, 则为null', `extra_json` varchar(1024) DEFAULT NULL COMMENT '拓展字段', `version` int(10) unsigned NOT NULL COMMENT '乐观锁', `updated_by` varchar(32) DEFAULT NULL, `created_at` datetime NOT NULL, `updated_at` datetime NOT NULL, `sku_extra` varchar(1024) DEFAULT NULL COMMENT 'sku和item的一些冗余信息, item_id, item_name, outerSkuCode 等', `item_id` bigint(20) NOT NULL COMMENT '商品id', `item_name` varchar(255) DEFAULT NULL COMMENT '商品名称', `sku_line_id` bigint(20) NOT NULL COMMENT '可以近似理解为order按sku维度拆分的Line id', `bundle_id` varchar(64) DEFAULT NULL, PRIMARY KEY (`order_line_id`), KEY `idx_order_line_tenant_id` (`tenant_id`), KEY `idx_order_line_biz_code` (`biz_code`), KEY `idx_order_line_purchase_order_id` (`purchase_order_id`), KEY `idx_order_line_order_id` (`order_id`), KEY `idx_order_line_buyer_id` (`buyer_id`), KEY `idx_order_line_shop_id` (`shop_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单行'; CREATE TABLE `parana_shop` ( `id` bigint(20) NOT NULL, `tenant_id` bigint(20) NOT NULL COMMENT '租户ID', `outer_id` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '外部店铺编码', `user_id` bigint(20) DEFAULT NULL COMMENT '商家id', `user_name` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '商家名称', `name` varchar(512) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '店铺名称', `status` tinyint(1) NOT NULL COMMENT '状态 1:正常, -1:关闭, -2:冻结', `type` tinyint(1) NOT NULL COMMENT '店铺类型 1:门店 2:商家 3:出版社', `phone` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '联系电话', `email` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '电子邮件', `image_url` varchar(128) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '店铺图片url', `address` varchar(128) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '店铺地址', `extra_json` text COLLATE utf8mb4_unicode_ci COMMENT '店铺额外信息,建议json字符串', `tags` bigint(20) DEFAULT NULL COMMENT '店铺标签', `created_at` datetime NOT NULL COMMENT '创建时间', `updated_at` datetime NOT NULL COMMENT '更新时间', `updated_by` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新者', PRIMARY KEY (`id`) USING BTREE, KEY `idx_name` (`name`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC COMMENT='店铺表'; CREATE TABLE `parana_item` ( `id` bigint(20) NOT NULL, `extension_type` smallint(6) DEFAULT '0' COMMENT '扩展类型', `spu_id` bigint(20) DEFAULT NULL, `tenant_id` bigint(20) DEFAULT NULL COMMENT '租户id', `category_id` bigint(20) NOT NULL COMMENT '类目id', `item_code` varchar(45) DEFAULT NULL COMMENT '商品编码', `shop_id` bigint(20) NOT NULL COMMENT '店铺id', `shop_name` varchar(128) DEFAULT '' COMMENT '店铺名', `brand_id` bigint(20) DEFAULT NULL COMMENT '品牌id', `brand_name` varchar(128) DEFAULT NULL COMMENT '品牌名', `delivery_fee_temp_id` bigint(20) NOT NULL COMMENT '运费模板id', `name` varchar(1024) NOT NULL DEFAULT '' COMMENT '商品名', `advertise` mediumtext COMMENT '广告', `main_image` varchar(512) NOT NULL DEFAULT '' COMMENT '主图地址', `video_url` varchar(512) DEFAULT NULL COMMENT '视频地址', `status` smallint(6) NOT NULL COMMENT '商品状态:1(上架),-1(下架),-2(冻结),-3(删除)', `type` smallint(6) NOT NULL COMMENT '商品类型', `sku_attributes_json` varchar(4096) DEFAULT NULL COMMENT 'sku销售属性集合', `other_attributes_json` varchar(4096) DEFAULT NULL COMMENT '其它属性', `extra_json` mediumtext COMMENT '其它内容', `tags` bigint(20) DEFAULT NULL COMMENT '标签', `md5_info` varchar(45) DEFAULT NULL, `version` int(10) NOT NULL COMMENT '信息版本号', `created_at` datetime NOT NULL COMMENT '创建时间', `updated_at` datetime NOT NULL COMMENT '最后更新时间', `updated_by` varchar(32) DEFAULT NULL COMMENT '更新者', `high_price` int(11) DEFAULT NULL, `low_price` int(11) DEFAULT NULL, PRIMARY KEY (`id`) USING BTREE, KEY `fk_item_spu1_idx` (`spu_id`) USING BTREE, KEY `idx_parana_item_updated_at` (`updated_at`), KEY `idx_parana_item_category_id` (`category_id`) USING BTREE, KEY `idx_parana_item_item_code` (`item_code`) USING BTREE, KEY `idx_parana_item_shop_id` (`shop_id`) USING BTREE, KEY `idx_parana_item_shop_id_create_at_status` (`shop_id`,`created_at`,`status`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC; CREATE TABLE `parana_sku` ( `id` bigint(20) unsigned NOT NULL, `extension_type` smallint(6) DEFAULT '0' COMMENT '扩展类型', `tenant_id` smallint(6) DEFAULT NULL COMMENT '租户id', `item_id` bigint(20) NOT NULL COMMENT '商品id', `sku_template_id` bigint(20) DEFAULT NULL COMMENT 'sku模板id', `sku_code` varchar(40) DEFAULT NULL COMMENT 'SKU 编码 (标准库存单位编码)', `barcode` varchar(32) DEFAULT NULL COMMENT '商品条码', `shape_code` varchar(128) DEFAULT NULL, `outer_id` varchar(40) DEFAULT NULL COMMENT '外部id', `shop_id` bigint(20) NOT NULL, `name` varchar(1024) DEFAULT NULL COMMENT '名称', `image` varchar(1024) DEFAULT NULL COMMENT '图片url', `price` int(11) DEFAULT NULL COMMENT '实际售卖价格', `status` tinyint(1) NOT NULL COMMENT 'sku状态, 1: 上架, -1:下架, -2:冻结, -3:删除', `audit_status` smallint(6) DEFAULT NULL COMMENT '审核状态', `type` smallint(6) NOT NULL COMMENT '商品类型', `inventory_type` smallint(6) DEFAULT NULL COMMENT '库存类型', `sku_ids_json` varchar(512) DEFAULT NULL, `price_json` varchar(255) DEFAULT NULL COMMENT 'sku其他各种价格的json表示形式', `ios_price_json` varchar(255) DEFAULT NULL COMMENT '渠道iOS价格', `attrs_json` varchar(1024) DEFAULT NULL COMMENT 'json存储的sku属性键值对', `version` int(10) NOT NULL COMMENT 'sku信息版本', `extra_json` text COMMENT 'sku额外信息', `tags` bigint(20) DEFAULT NULL COMMENT '标签', `created_at` datetime NOT NULL COMMENT '创建时间', `updated_at` datetime NOT NULL COMMENT '最后更新时间', `updated_by` varchar(32) DEFAULT NULL COMMENT '更新者', PRIMARY KEY (`id`) USING BTREE, KEY `idx_item_id` (`item_id`) USING BTREE, KEY `idx_parana_sku_updated_at` (`updated_at`), KEY `idx_parana_sku_shop_id` (`shop_id`), KEY `idx_parana_item_barcode` (`barcode`), KEY `idx_parana_sku_sku_code` (`sku_code`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='商品SKU表'; ================================================ FILE: flink-learning-common/src/test/java/com/zhisheng/common/utils/DateUtilTests.java ================================================ package com.zhisheng.common.utils; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormatter; import org.junit.Assert; import org.junit.Test; import java.text.SimpleDateFormat; import java.util.Date; public class DateUtilTests { @Test public void testFormat() { Date date1 = new Date(1556665200000L); Assert.assertEquals(date1.getTime(), DateUtil.format(date1)); Assert.assertEquals("2019-05-01", DateUtil.format( date1.getTime(), DateUtil.YYYY_MM_DD)); Assert.assertEquals("2019-05-01", DateUtil.format( date1, DateTimeZone.getDefault(), DateUtil.YYYY_MM_DD)); Assert.assertEquals(date1.getTime(), DateUtil.format( "2019-05-01", DateUtil.YYYY_MM_DD)); Assert.assertEquals(1556723220000L, DateUtil.format( "2019-05-01 16:07", DateUtil.YYYY_MM_DD_HH_MM)); Assert.assertEquals(1556723235000L, DateUtil.format( "2019-05-01 16:07:15", DateUtil.YYYY_MM_DD_HH_MM_SS)); Assert.assertEquals(1556723235000L, DateUtil.format( "2019-05-01 16:07:15.0", DateUtil.YYYY_MM_DD_HH_MM_SS_0)); } @Test public void testIsValidDate() { Assert.assertTrue(DateUtil.isValidDate( "2019-05-01", DateUtil.YYYY_MM_DD)); Assert.assertFalse(DateUtil.isValidDate( "01-05-2019", DateUtil.YYYY_MM_DD)); } @Test public void testToDate() { Assert.assertEquals(new Date(1556665200000L), DateUtil.toDate("2019-05-01", DateUtil.YYYY_MM_DD)); } @Test public void testWithTimeAtStartOfDay() { DateTimeFormatter dtf = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss"); Date date = new Date(1557874800000L); DateTime dt = new DateTime(1557874800000L); Assert.assertEquals("2019-05-15 00:00:00", DateUtil.withTimeAtStartOfDay(date, dtf)); Assert.assertEquals("2019-05-15 00:00:00", DateUtil.withTimeAtStartOfDay(dt, dtf)); } @Test public void testWithTimeAtEndOfDay() { DateTimeFormatter dtf = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss"); Date date = new Date(1557961199000L); DateTime dt = new DateTime(1557961199000L); Assert.assertEquals("2019-05-15 23:59:59", DateUtil.withTimeAtEndOfDay(date, dtf)); Assert.assertEquals("2019-05-15 23:59:59", DateUtil.withTimeAtEndOfDay(dt, dtf)); } @Test public void testWithTimeAtStartOfNow() { Date date = new Date(); date.setHours(0); date.setMinutes(0); date.setSeconds(0); String data = new SimpleDateFormat("yyyyMMddHHmmss").format(date); Assert.assertEquals(data, DateUtil.withTimeAtStartOfNow()); } @Test public void testWithTimeAtEndOfNow() { Date date = new Date(); date.setHours(23); date.setMinutes(59); date.setSeconds(59); String data = new SimpleDateFormat("yyyyMMddHHmmss").format(date); Assert.assertEquals(data, DateUtil.withTimeAtEndOfNow()); } } ================================================ FILE: flink-learning-configuration-center/flink-learning-configuration-center-apollo/pom.xml ================================================ flink-learning-configuration-center com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 flink-learning-configuration-center-apollo com.ctrip.framework.apollo apollo-client 1.5.1 ================================================ FILE: flink-learning-configuration-center/flink-learning-configuration-center-apollo/src/main/java/com/zhisheng/configuration/apollo/FlinkApolloTest.java ================================================ package com.zhisheng.configuration.apollo; import com.ctrip.framework.apollo.Config; import com.ctrip.framework.apollo.ConfigChangeListener; import com.ctrip.framework.apollo.ConfigService; import com.ctrip.framework.apollo.model.ConfigChange; import com.ctrip.framework.apollo.model.ConfigChangeEvent; import lombok.extern.slf4j.Slf4j; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.configuration.Configuration; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.api.functions.source.RichSourceFunction; /** * Desc: flink Apollo * Created by zhisheng on 2020/2/23 下午7:37 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ @Slf4j public class FlinkApolloTest { public static void main(String[] args) throws Exception { StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.getConfig().setGlobalJobParameters(ParameterTool.fromArgs(args)); env.setParallelism(1); env.addSource(new RichSourceFunction() { private Config config; @Override public void open(Configuration parameters) throws Exception { super.open(parameters); config = ConfigService.getAppConfig(); config.addChangeListener(new ConfigChangeListener() { @Override public void onChange(ConfigChangeEvent configChangeEvent) { for (String key : configChangeEvent.changedKeys()) { ConfigChange change = configChangeEvent.getChange(key); log.info("Change - key: {}, oldValue: {}, newValue: {}, changeType: {}", change.getPropertyName(), change.getOldValue(), change.getNewValue(), change.getChangeType()); } } }); } @Override public void run(SourceContext ctx) throws Exception { while (true) { ctx.collect(config.getProperty("name", "zhisheng")); Thread.sleep(3000); } } @Override public void cancel() { } }).print(); env.execute("zhisheng flink Apollo"); } } ================================================ FILE: flink-learning-configuration-center/flink-learning-configuration-center-apollo/src/main/resources/META-INF/app.properties ================================================ # test #app.id=flink-learning-configration-center-apollo app.id=SampleApp ================================================ FILE: flink-learning-configuration-center/flink-learning-configuration-center-nacos/pom.xml ================================================ flink-learning-configuration-center com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 flink-learning-configuration-center-nacos com.alibaba.nacos nacos-client 1.1.4 com.alibaba.nacos nacos-common 1.4.5 ================================================ FILE: flink-learning-configuration-center/flink-learning-configuration-center-nacos/src/main/java/com/zhisheng/configuration/nacos/FlinkNacosTest.java ================================================ package com.zhisheng.configuration.nacos; import com.alibaba.nacos.api.NacosFactory; import com.alibaba.nacos.api.config.ConfigService; import com.alibaba.nacos.api.config.listener.Listener; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.configuration.Configuration; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.api.functions.source.RichSourceFunction; import java.util.Properties; import java.util.concurrent.Executor; /** * Desc: flink nacos 整合测试 * Created by zhisheng on 2020/2/16 下午5:45 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class FlinkNacosTest { public static void main(String[] args) throws Exception { StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.getConfig().setGlobalJobParameters(ParameterTool.fromArgs(args)); env.setParallelism(1); String serverAddr = "localhost"; String dataId = "test"; String group = "DEFAULT_GROUP"; Properties properties = new Properties(); properties.put("serverAddr", serverAddr); ConfigService configService = NacosFactory.createConfigService(properties); String content = configService.getConfig(dataId, group, 5000); System.out.println("main " + content); env.addSource(new RichSourceFunction() { ConfigService configService; String config; String dataId = "test"; String group = "DEFAULT_GROUP"; @Override public void open(Configuration parameters) throws Exception { super.open(parameters); String serverAddr = "localhost"; Properties properties = new Properties(); properties.put("serverAddr", serverAddr); configService = NacosFactory.createConfigService(properties); config = configService.getConfig(dataId, group, 5000); configService.addListener(dataId, group, new Listener() { @Override public Executor getExecutor() { return null; } @Override public void receiveConfigInfo(String configInfo) { config = configInfo; System.out.println("open Listener receive : " + configInfo); } }); } @Override public void run(SourceContext ctx) throws Exception { // config = configService.getConfig(dataId, group, 5000); while (true) { Thread.sleep(3000); System.out.println("run config = " + config); ctx.collect(String.valueOf(System.currentTimeMillis())); } } @Override public void cancel() { } }).print(); env.execute("zhisheng flink nacos"); } } ================================================ FILE: flink-learning-configuration-center/flink-learning-configuration-center-nacos/src/main/java/com/zhisheng/configuration/nacos/FlinkNacosTest2.java ================================================ package com.zhisheng.configuration.nacos; import com.alibaba.nacos.api.NacosFactory; import com.alibaba.nacos.api.config.ConfigService; import com.alibaba.nacos.api.config.listener.Listener; import org.apache.flink.api.java.tuple.Tuple2; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.streaming.api.CheckpointingMode; import org.apache.flink.streaming.api.environment.CheckpointConfig; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.api.functions.source.SourceFunction; import java.util.Properties; import java.util.concurrent.Executor; /** * Desc: 测试 nacos 动态更改 Checkpoint 配置后,Flink 是否可以获取到更改后的值,并生效? * * 结论是:不生效,因为 Flink 是 Lazy Evaluation(延迟执行),当程序的 main 方法执行时,数据源加载数据和数据转换等算子不会立马执行, * 这些操作会被创建并添加到程序的执行计划中去,只有当执行环境 env 的 execute 方法被显示地触发执行时,整个程序才开始执行实际的操作,所以 * 在一开始初始化后等程序执行 execute 方法后再修改 env 的配置其实就不起作用了。 * * Created by zhisheng on 2020/2/29 下午3:27 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class FlinkNacosTest2 { public static void main(String[] args) throws Exception { StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.getConfig().setGlobalJobParameters(ParameterTool.fromArgs(args)); env.setParallelism(1); String serverAddr = "localhost"; String dataId = "test"; String group = "DEFAULT_GROUP"; Properties properties = new Properties(); properties.put("serverAddr", serverAddr); ConfigService configService = NacosFactory.createConfigService(properties); final String[] content = {configService.getConfig(dataId, group, 5000)}; configService.addListener(dataId, group, new Listener() { @Override public Executor getExecutor() { return null; } @Override public void receiveConfigInfo(String configInfo) { System.out.println("==============="); content[0] = configInfo; env.getCheckpointConfig().setCheckpointInterval(Long.valueOf(content[0])); System.out.println("----------"); System.out.println(env.getCheckpointConfig().getCheckpointInterval()); } }); System.out.println(content[0]); env.getCheckpointConfig().setCheckpointInterval(Long.valueOf(content[0])); env.getCheckpointConfig().setCheckpointTimeout(1000); env.getCheckpointConfig().setCheckpointingMode(CheckpointingMode.EXACTLY_ONCE); env.getCheckpointConfig().enableExternalizedCheckpoints(CheckpointConfig.ExternalizedCheckpointCleanup.DELETE_ON_CANCELLATION); env.addSource(new SourceFunction>() { @Override public void run(SourceContext> ctx) throws Exception { while (true) { ctx.collect(new Tuple2<>("zhisheng", System.currentTimeMillis())); Thread.sleep(800); } } @Override public void cancel() { } }).print(); env.execute(); } } ================================================ FILE: flink-learning-configuration-center/pom.xml ================================================ flink-learning com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 flink-learning-configuration-center pom flink-learning-configuration-center-nacos flink-learning-configuration-center-apollo com.zhisheng.flink flink-learning-common ${project.version} ================================================ FILE: flink-learning-connectors/README.md ================================================ ### Flink connectors 暂时有这些 Cooncetor,其中这些并不是 Flink 自带的,需要自己定义,另外提供这些 Connector 的使用案例,大家可以参考。欢迎补充和点赞 ```text . ├── README.md ├── flink-learning-connectors-activemq ├── flink-learning-connectors-akka ├── flink-learning-connectors-cassandra ├── flink-learning-connectors-clickhouse ├── flink-learning-connectors-es2 ├── flink-learning-connectors-es5 ├── flink-learning-connectors-es6 ├── flink-learning-connectors-es7 ├── flink-learning-connectors-flume ├── flink-learning-connectors-gcp-pubsub ├── flink-learning-connectors-hbase ├── flink-learning-connectors-hdfs ├── flink-learning-connectors-hive ├── flink-learning-connectors-influxdb ├── flink-learning-connectors-kafka ├── flink-learning-connectors-kinesis ├── flink-learning-connectors-kudu ├── flink-learning-connectors-mysql ├── flink-learning-connectors-netty ├── flink-learning-connectors-nifi ├── flink-learning-connectors-pulsar ├── flink-learning-connectors-rabbitmq ├── flink-learning-connectors-redis └── flink-learning-connectors-rocketmq ``` ================================================ FILE: flink-learning-connectors/flink-learning-connectors-activemq/README.md ================================================ ### Flink connector ActiveMQ ================================================ FILE: flink-learning-connectors/flink-learning-connectors-activemq/pom.xml ================================================ flink-learning-connectors com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 flink-learning-connectors-activemq org.apache.maven.plugins maven-shade-plugin 3.1.0 false package shade com.zhisheng.connectors.activemq.Main reference.conf *:*:*:* META-INF/*.SF META-INF/*.DSA META-INF/*.RSA ================================================ FILE: flink-learning-connectors/flink-learning-connectors-activemq/src/main/java/com/zhisheng/connectors/activemq/Main.java ================================================ package com.zhisheng.connectors.activemq; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; public class Main { public static void main(String[] args) throws Exception { final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.execute("flink learning project template"); } } ================================================ FILE: flink-learning-connectors/flink-learning-connectors-activemq/src/main/resources/application.properties ================================================ kafka.brokers=localhost:9092 kafka.group.id=zhisheng kafka.zookeeper.connect=localhost:2181 metrics.topic=zhisheng stream.parallelism=4 stream.sink.parallelism=4 stream.default.parallelism=4 stream.checkpoint.interval=1000 stream.checkpoint.enable=false ================================================ FILE: flink-learning-connectors/flink-learning-connectors-activemq/src/main/resources/logback.xml ================================================ %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n ================================================ FILE: flink-learning-connectors/flink-learning-connectors-cassandra/README.md ================================================ ### flink-learning-connectors-cassandra ================================================ FILE: flink-learning-connectors/flink-learning-connectors-cassandra/pom.xml ================================================ flink-learning-connectors com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 flink-learning-connectors-cassandra org.apache.flink flink-connector-cassandra_${scala.binary.version} ${flink-connector-cassandra.version} org.apache.flink flink-streaming-scala_${scala.binary.version} ${flink.version} provided org.apache.maven.plugins maven-shade-plugin 3.1.0 false package shade com.zhisheng.connectors.cassandra.batch.BatchPojoExample reference.conf *:*:*:* META-INF/*.SF META-INF/*.DSA META-INF/*.RSA ================================================ FILE: flink-learning-connectors/flink-learning-connectors-cassandra/src/main/java/com/zhisheng/connectors/cassandra/batch/BatchExample.java ================================================ package com.zhisheng.connectors.cassandra.batch; import com.datastax.driver.core.Cluster; import org.apache.flink.api.common.typeinfo.TypeHint; import org.apache.flink.api.java.DataSet; import org.apache.flink.api.java.ExecutionEnvironment; import org.apache.flink.api.java.tuple.Tuple2; import org.apache.flink.api.java.typeutils.TupleTypeInfo; import org.apache.flink.batch.connectors.cassandra.CassandraInputFormat; import org.apache.flink.batch.connectors.cassandra.CassandraTupleOutputFormat; import org.apache.flink.streaming.connectors.cassandra.ClusterBuilder; import java.util.ArrayList; /** * Desc: Cassandra Input-/OutputFormats batch api * *

The example assumes that a table exists in a local cassandra database, according to the following queries: * CREATE KEYSPACE IF NOT EXISTS test WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '1'}; * CREATE TABLE IF NOT EXISTS test.zhisheng (number int, strings text, PRIMARY KEY(number, strings)); * * Created by zhisheng on 2019-08-04 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class BatchExample { private static final String INSERT_QUERY = "INSERT INTO test.zhisheng (number, strings) VALUES (?,?);"; private static final String SELECT_QUERY = "SELECT number, strings FROM test.zhisheng;"; public static void main(String[] args) throws Exception { ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment(); env.setParallelism(1); ArrayList> collection = new ArrayList<>(20); for (int i = 0; i < 20; i++) { collection.add(new Tuple2<>(i, "string " + i)); } env.fromCollection(collection) .output(new CassandraTupleOutputFormat>(INSERT_QUERY, new ClusterBuilder() { @Override protected Cluster buildCluster(Cluster.Builder builder) { return builder.addContactPoints("127.0.0.1").build(); } })); env.execute("zhisheng"); DataSet> inputDS = env .createInput(new CassandraInputFormat>(SELECT_QUERY, new ClusterBuilder() { @Override protected Cluster buildCluster(Cluster.Builder builder) { return builder.addContactPoints("127.0.0.1").build(); } }), TupleTypeInfo.of(new TypeHint>() { })); inputDS.print(); } } ================================================ FILE: flink-learning-connectors/flink-learning-connectors-cassandra/src/main/java/com/zhisheng/connectors/cassandra/batch/BatchPojoExample.java ================================================ package com.zhisheng.connectors.cassandra.batch; import com.datastax.driver.core.Cluster; import com.datastax.driver.core.ConsistencyLevel; import com.datastax.driver.mapping.Mapper; import org.apache.flink.api.java.DataSet; import org.apache.flink.api.java.ExecutionEnvironment; import org.apache.flink.batch.connectors.cassandra.CassandraPojoInputFormat; import org.apache.flink.batch.connectors.cassandra.CassandraPojoOutputFormat; import org.apache.flink.streaming.connectors.cassandra.ClusterBuilder; import java.util.List; import java.util.UUID; import java.util.stream.Collectors; import java.util.stream.IntStream; /** * Desc: CassandraPojoInputFormat/CassandraPojoOutputFormat batch api * *

The example assumes that a table exists in a local cassandra database, according to the following queries: * CREATE KEYSPACE IF NOT EXISTS flink WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '1'}; * CREATE TABLE IF NOT EXISTS flink.batches (id text, counter int, batch_id int, PRIMARY KEY(id, counter, batchId)); * * Created by zhisheng on 2019-08-04 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class BatchPojoExample { private static final String SELECT_QUERY = "SELECT id, counter, batch_id FROM flink.batches;"; public static void main(String[] args) throws Exception { ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment(); env.setParallelism(1); List customCassandraAnnotatedPojos = IntStream.range(0, 20) .mapToObj(x -> new CustomCassandraAnnotatedPojo(UUID.randomUUID().toString(), x, 0)) .collect(Collectors.toList()); DataSet dataSet = env.fromCollection(customCassandraAnnotatedPojos); ClusterBuilder clusterBuilder = new ClusterBuilder() { private static final long serialVersionUID = -1754532803757154795L; @Override protected Cluster buildCluster(Cluster.Builder builder) { return builder.addContactPoints("127.0.0.1").build(); } }; dataSet.output(new CassandraPojoOutputFormat<>(clusterBuilder, CustomCassandraAnnotatedPojo.class, () -> new Mapper.Option[]{Mapper.Option.saveNullFields(true)})); env.execute("zhisheng"); /* * This is for the purpose of showing an example of creating a DataSet using CassandraPojoInputFormat. */ DataSet inputDS = env .createInput(new CassandraPojoInputFormat<>( SELECT_QUERY, clusterBuilder, CustomCassandraAnnotatedPojo.class, () -> new Mapper.Option[]{Mapper.Option.consistencyLevel(ConsistencyLevel.ANY)} )); inputDS.print(); } } ================================================ FILE: flink-learning-connectors/flink-learning-connectors-cassandra/src/main/java/com/zhisheng/connectors/cassandra/batch/CustomCassandraAnnotatedPojo.java ================================================ package com.zhisheng.connectors.cassandra.batch; import com.datastax.driver.mapping.annotations.Column; import com.datastax.driver.mapping.annotations.Table; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; /** * Desc: * Created by zhisheng on 2019-08-04 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ @Table(name = CustomCassandraAnnotatedPojo.TABLE_NAME, keyspace = "flink") @NoArgsConstructor @AllArgsConstructor @Data public class CustomCassandraAnnotatedPojo { public static final String TABLE_NAME = "zhisheng"; @Column(name = "id") private String id; @Column(name = "counter") private Integer counter; @Column(name = "batch_id") private Integer batchId; } ================================================ FILE: flink-learning-connectors/flink-learning-connectors-cassandra/src/main/java/com/zhisheng/connectors/cassandra/streaming/CassandraPojoSinkExample.java ================================================ package com.zhisheng.connectors.cassandra.streaming; import com.datastax.driver.core.Cluster; import com.datastax.driver.mapping.Mapper; import org.apache.flink.streaming.api.datastream.DataStreamSource; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.connectors.cassandra.CassandraSink; import org.apache.flink.streaming.connectors.cassandra.ClusterBuilder; import java.util.ArrayList; /** * Desc: Pojo Cassandra Sink in the Streaming API * * CREATE KEYSPACE IF NOT EXISTS test WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '1'}; * CREATE TABLE IF NOT EXISTS test.message(body txt PRIMARY KEY) * * Created by zhisheng on 2019-08-04 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class CassandraPojoSinkExample { private static final ArrayList messages = new ArrayList<>(20); static { for (long i = 0; i < 20; i++) { messages.add(new Message("cassandra-" + i)); } } public static void main(String[] args) throws Exception { StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); DataStreamSource source = env.fromCollection(messages); CassandraSink.addSink(source) .setClusterBuilder(new ClusterBuilder() { @Override protected Cluster buildCluster(Cluster.Builder builder) { return builder.addContactPoint("127.0.0.1").build(); } }) .setMapperOptions(() -> new Mapper.Option[]{Mapper.Option.saveNullFields(true)}) .build(); env.execute("Cassandra Sink example"); } } ================================================ FILE: flink-learning-connectors/flink-learning-connectors-cassandra/src/main/java/com/zhisheng/connectors/cassandra/streaming/CassandraTupleSinkExample.java ================================================ package com.zhisheng.connectors.cassandra.streaming; import com.datastax.driver.core.Cluster; import org.apache.flink.api.java.tuple.Tuple2; import org.apache.flink.streaming.api.datastream.DataStreamSource; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.connectors.cassandra.CassandraSink; import org.apache.flink.streaming.connectors.cassandra.ClusterBuilder; import java.util.ArrayList; /** * Desc: Tuple Cassandra Sink in streaming api * * CREATE KEYSPACE IF NOT EXISTS test WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '1'}; * CREATE TABLE IF NOT EXISTS zhisheng.writetuple(element1 text PRIMARY KEY, element2 int) * * Created by zhisheng on 2019-08-04 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class CassandraTupleSinkExample { private static final String INSERT = "INSERT INTO zhisheng.writetuple (element1, element2) VALUES (?, ?)"; private static final ArrayList> collection = new ArrayList<>(20); static { for (int i = 0; i < 20; i++) { collection.add(new Tuple2<>("cassandra-" + i, i)); } } public static void main(String[] args) throws Exception { StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); DataStreamSource> source = env.fromCollection(collection); CassandraSink.addSink(source) .setQuery(INSERT) .setClusterBuilder(new ClusterBuilder() { @Override protected Cluster buildCluster(Cluster.Builder builder) { return builder.addContactPoint("127.0.0.1").build(); } }) .build(); env.execute("WriteTupleIntoCassandra"); } } ================================================ FILE: flink-learning-connectors/flink-learning-connectors-cassandra/src/main/java/com/zhisheng/connectors/cassandra/streaming/CassandraTupleWriteAheadSinkExample.java ================================================ package com.zhisheng.connectors.cassandra.streaming; import com.datastax.driver.core.Cluster; import org.apache.flink.api.common.restartstrategy.RestartStrategies; import org.apache.flink.api.java.tuple.Tuple2; import org.apache.flink.runtime.state.filesystem.FsStateBackend; import org.apache.flink.streaming.api.checkpoint.ListCheckpointed; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.api.functions.source.SourceFunction; import org.apache.flink.streaming.connectors.cassandra.CassandraSink; import org.apache.flink.streaming.connectors.cassandra.ClusterBuilder; import java.util.Collections; import java.util.List; import java.util.UUID; /** * Desc: use Cassandra Sink in streaming api * *

The example assumes that a table exists in a local cassandra database, according to the following queries: * CREATE KEYSPACE IF NOT EXISTS example WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '1'}; * CREATE TABLE zhisheng.values (id text, count int, PRIMARY KEY(id)); * *

Important things to note are that checkpointing is enabled, a StateBackend is set and the enableWriteAheadLog() call * when creating the CassandraSink. * * Created by zhisheng on 2019-08-04 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class CassandraTupleWriteAheadSinkExample { public static void main(String[] args) throws Exception { StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.setParallelism(1); env.enableCheckpointing(1000); env.setRestartStrategy(RestartStrategies.fixedDelayRestart(1, 1000)); env.setStateBackend(new FsStateBackend("file:///" + System.getProperty("java.io.tmpdir") + "/flink/backend")); CassandraSink> sink = CassandraSink.addSink(env.addSource(new MySource())) .setQuery("INSERT INTO zhisheng.values (id, counter) values (?, ?);") .enableWriteAheadLog() .setClusterBuilder(new ClusterBuilder() { private static final long serialVersionUID = 2793938419775311824L; @Override public Cluster buildCluster(Cluster.Builder builder) { return builder.addContactPoint("127.0.0.1").build(); } }) .build(); sink.name("Cassandra Sink").disableChaining().setParallelism(1).uid("hello"); env.execute(); } private static class MySource implements SourceFunction>, ListCheckpointed { private static final long serialVersionUID = 4022367939215095610L; private int counter = 0; private boolean stop = false; @Override public void run(SourceContext> ctx) throws Exception { while (!stop) { Thread.sleep(50); ctx.collect(new Tuple2<>("" + UUID.randomUUID(), 1)); counter++; if (counter == 100) { stop = true; } } } @Override public void cancel() { stop = true; } @Override public List snapshotState(long checkpointId, long timestamp) throws Exception { return Collections.singletonList(this.counter); } @Override public void restoreState(List state) throws Exception { if (state.isEmpty() || state.size() > 1) { throw new RuntimeException("Test failed due to unexpected recovered state size " + state.size()); } this.counter = state.get(0); } } } ================================================ FILE: flink-learning-connectors/flink-learning-connectors-cassandra/src/main/java/com/zhisheng/connectors/cassandra/streaming/Message.java ================================================ package com.zhisheng.connectors.cassandra.streaming; import com.datastax.driver.mapping.annotations.Column; import com.datastax.driver.mapping.annotations.Table; import lombok.AllArgsConstructor; /** * Desc: * Created by zhisheng on 2019-08-04 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ @Table(keyspace = "test", name = "message") @AllArgsConstructor public class Message { private static final long serialVersionUID = 1123119384361005680L; @Column(name = "body") private String message; public Message() { this(null); } public String getMessage() { return message; } public void setMessage(String word) { this.message = word; } public boolean equals(Object other) { if (other instanceof Message) { Message that = (Message) other; return this.message.equals(that.message); } return false; } @Override public int hashCode() { return message.hashCode(); } } ================================================ FILE: flink-learning-connectors/flink-learning-connectors-cassandra/src/main/resources/application.properties ================================================ kafka.brokers=localhost:9092 kafka.group.id=metrics-group kafka.zookeeper.connect=localhost:2181 metrics.topic=alert-metrics stream.parallelism=5 stream.checkpoint.interval=1000 stream.checkpoint.enable=false ================================================ FILE: flink-learning-connectors/flink-learning-connectors-cassandra/src/main/resources/logback.xml ================================================ %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n ================================================ FILE: flink-learning-connectors/flink-learning-connectors-clickhouse/README.md ================================================ ### Flink connector Clickhouse https://github.com/ivi-ru/flink-clickhouse-sink ================================================ FILE: flink-learning-connectors/flink-learning-connectors-clickhouse/pom.xml ================================================ flink-learning-connectors com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 flink-learning-connectors-clickhouse 2.0.39 1.3.3 org.asynchttpclient async-http-client ${async.client.version} com.typesafe config ${typesafe.config.version} ================================================ FILE: flink-learning-connectors/flink-learning-connectors-clickhouse/src/main/java/com/zhisheng/connectors/clickhouse/ClickhouseSink.java ================================================ package com.zhisheng.connectors.clickhouse; /** * Desc: * Created by zhisheng on 2019/9/28 上午12:38 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ import com.zhisheng.connectors.clickhouse.applied.ClickhouseSinkBuffer; import com.zhisheng.connectors.clickhouse.applied.ClickhouseSinkManager; import org.apache.flink.configuration.Configuration; import org.apache.flink.streaming.api.functions.sink.RichSinkFunction; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Map; import java.util.Properties; /** * Desc: * Created by zhisheng on 2019/9/28 上午12:38 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class ClickhouseSink extends RichSinkFunction { private static final Logger logger = LoggerFactory.getLogger(ClickhouseSink.class); private static final Object DUMMY_LOCK = new Object(); private final Properties localProperties; private volatile static transient ClickhouseSinkManager sinkManager; private transient ClickhouseSinkBuffer clickhouseSinkBuffer; public ClickhouseSink(Properties properties) { this.localProperties = properties; } @Override public void open(Configuration config) { if (sinkManager == null) { synchronized (DUMMY_LOCK) { if (sinkManager == null) { Map params = getRuntimeContext() .getExecutionConfig() .getGlobalJobParameters() .toMap(); sinkManager = new ClickhouseSinkManager(params); } } } clickhouseSinkBuffer = sinkManager.buildBuffer(localProperties); } /** * Add csv to buffer * * @param recordAsCSV csv-event */ @Override public void invoke(String recordAsCSV, Context context) { try { clickhouseSinkBuffer.put(recordAsCSV); } catch (Exception e) { logger.error("Error while sending data to Clickhouse, record = {}", recordAsCSV, e); throw new RuntimeException(e); } } @Override public void close() throws Exception { if (clickhouseSinkBuffer != null) { clickhouseSinkBuffer.close(); } if (sinkManager != null) { if (!sinkManager.isClosed()) { synchronized (DUMMY_LOCK) { if (!sinkManager.isClosed()) { sinkManager.close(); } } } } super.close(); } } ================================================ FILE: flink-learning-connectors/flink-learning-connectors-clickhouse/src/main/java/com/zhisheng/connectors/clickhouse/applied/ClickhouseSinkBuffer.java ================================================ package com.zhisheng.connectors.clickhouse.applied; import com.google.common.base.Preconditions; import com.zhisheng.connectors.clickhouse.model.ClickhouseRequestBlank; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.TimeUnit; /** * Desc: * Created by zhisheng on 2019/9/28 上午10:08 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class ClickhouseSinkBuffer implements AutoCloseable { private static final Logger logger = LoggerFactory.getLogger(ClickhouseSinkBuffer.class); private final ClickhouseWriter writer; private final String targetTable; private final int maxFlushBufferSize; private final long timeoutMillis; private final List localValues; private volatile long lastAddTimeMillis = 0L; private ClickhouseSinkBuffer( ClickhouseWriter chWriter, long timeout, int maxBuffer, String table ) { writer = chWriter; localValues = new ArrayList<>(); timeoutMillis = timeout; maxFlushBufferSize = maxBuffer; targetTable = table; logger.info("Instance Clickhouse Sink, target table = {}, buffer size = {}", this.targetTable, this.maxFlushBufferSize); } String getTargetTable() { return targetTable; } public void put(String recordAsCSV) { tryAddToQueue(); localValues.add(recordAsCSV); lastAddTimeMillis = System.currentTimeMillis(); } synchronized void tryAddToQueue() { if (flushCondition()) { addToQueue(); } } private void addToQueue() { List deepCopy = buildDeepCopy(localValues); ClickhouseRequestBlank params = ClickhouseRequestBlank.Builder .aBuilder() .withValues(deepCopy) .withTargetTable(targetTable) .build(); logger.debug("Build blank with params: buffer size = {}, target table = {}", params.getValues().size(), params.getTargetTable()); writer.put(params); localValues.clear(); } private boolean flushCondition() { return localValues.size() > 0 && (checkSize() || checkTime()); } private boolean checkSize() { return localValues.size() >= maxFlushBufferSize; } private boolean checkTime() { if (lastAddTimeMillis == 0) { return false; } long current = System.currentTimeMillis(); return current - lastAddTimeMillis > timeoutMillis; } private static List buildDeepCopy(List original) { return Collections.unmodifiableList(new ArrayList<>(original)); } @Override public void close() { if (localValues != null && localValues.size() > 0) { addToQueue(); } } public static final class Builder { private String targetTable; private int maxFlushBufferSize; private int timeoutSec; private Builder() { } public static Builder aClickhouseSinkBuffer() { return new Builder(); } public Builder withTargetTable(String targetTable) { this.targetTable = targetTable; return this; } public Builder withMaxFlushBufferSize(int maxFlushBufferSize) { this.maxFlushBufferSize = maxFlushBufferSize; return this; } public Builder withTimeoutSec(int timeoutSec) { this.timeoutSec = timeoutSec; return this; } public ClickhouseSinkBuffer build(ClickhouseWriter writer) { Preconditions.checkNotNull(targetTable); Preconditions.checkArgument(maxFlushBufferSize > 0); Preconditions.checkArgument(timeoutSec > 0); return new ClickhouseSinkBuffer( writer, TimeUnit.SECONDS.toMillis(this.timeoutSec), this.maxFlushBufferSize, this.targetTable ); } } } ================================================ FILE: flink-learning-connectors/flink-learning-connectors-clickhouse/src/main/java/com/zhisheng/connectors/clickhouse/applied/ClickhouseSinkManager.java ================================================ package com.zhisheng.connectors.clickhouse.applied; import com.google.common.base.Preconditions; import com.zhisheng.connectors.clickhouse.model.ClickhouseSinkCommonParams; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Map; import java.util.Properties; import static com.zhisheng.connectors.clickhouse.model.ClickhouseSinkConsts.MAX_BUFFER_SIZE; import static com.zhisheng.connectors.clickhouse.model.ClickhouseSinkConsts.TARGET_TABLE_NAME; /** * Desc: * Created by zhisheng on 2019/9/28 上午10:12 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class ClickhouseSinkManager implements AutoCloseable { private static final Logger logger = LoggerFactory.getLogger(ClickhouseSinkManager.class); private final ClickhouseWriter clickhouseWriter; private final ClickhouseSinkScheduledChecker clickhouseSinkScheduledChecker; private final ClickhouseSinkCommonParams sinkParams; private volatile boolean isClosed = false; public ClickhouseSinkManager(Map globalParams) { sinkParams = new ClickhouseSinkCommonParams(globalParams); clickhouseWriter = new ClickhouseWriter(sinkParams); clickhouseSinkScheduledChecker = new ClickhouseSinkScheduledChecker(sinkParams); logger.info("Build sink writer's manager. params = {}", sinkParams.toString()); } public ClickhouseSinkBuffer buildBuffer(Properties localProperties) { String targetTable = localProperties.getProperty(TARGET_TABLE_NAME); int maxFlushBufferSize = Integer.valueOf(localProperties.getProperty(MAX_BUFFER_SIZE)); return buildBuffer(targetTable, maxFlushBufferSize); } public ClickhouseSinkBuffer buildBuffer(String targetTable, int maxBufferSize) { Preconditions.checkNotNull(clickhouseSinkScheduledChecker); Preconditions.checkNotNull(clickhouseWriter); ClickhouseSinkBuffer clickhouseSinkBuffer = ClickhouseSinkBuffer.Builder .aClickhouseSinkBuffer() .withTargetTable(targetTable) .withMaxFlushBufferSize(maxBufferSize) .withTimeoutSec(sinkParams.getTimeout()) .build(clickhouseWriter); clickhouseSinkScheduledChecker.addSinkBuffer(clickhouseSinkBuffer); return clickhouseSinkBuffer; } public boolean isClosed() { return isClosed; } @Override public void close() throws Exception { clickhouseWriter.close(); clickhouseSinkScheduledChecker.close(); isClosed = true; } } ================================================ FILE: flink-learning-connectors/flink-learning-connectors-clickhouse/src/main/java/com/zhisheng/connectors/clickhouse/applied/ClickhouseSinkScheduledChecker.java ================================================ package com.zhisheng.connectors.clickhouse.applied; import com.zhisheng.connectors.clickhouse.model.ClickhouseSinkCommonParams; import com.zhisheng.connectors.clickhouse.util.ThreadUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; /** * Desc: * Created by zhisheng on 2019/9/28 上午10:13 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class ClickhouseSinkScheduledChecker implements AutoCloseable { private static final Logger logger = LoggerFactory.getLogger(ClickhouseSinkScheduledChecker.class); private final ScheduledExecutorService scheduledExecutorService; private final List clickhouseSinkBuffers; private final ClickhouseSinkCommonParams params; public ClickhouseSinkScheduledChecker(ClickhouseSinkCommonParams props) { clickhouseSinkBuffers = new ArrayList<>(); params = props; ThreadFactory factory = ThreadUtil.threadFactory("clickhouse-writer-checker"); scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(factory); scheduledExecutorService.scheduleWithFixedDelay(getTask(), params.getTimeout(), params.getTimeout(), TimeUnit.SECONDS); logger.info("Build Sink scheduled checker, timeout (sec) = {}", params.getTimeout()); } public void addSinkBuffer(ClickhouseSinkBuffer clickhouseSinkBuffer) { synchronized (this) { clickhouseSinkBuffers.add(clickhouseSinkBuffer); } logger.debug("Add sinkBuffer, target table = {}", clickhouseSinkBuffer.getTargetTable()); } private Runnable getTask() { return () -> { synchronized (this) { logger.debug("Start checking buffers. Current count of buffers = {}", clickhouseSinkBuffers.size()); clickhouseSinkBuffers.forEach(ClickhouseSinkBuffer::tryAddToQueue); } }; } @Override public void close() throws Exception { ThreadUtil.shutdownExecutorService(scheduledExecutorService); } } ================================================ FILE: flink-learning-connectors/flink-learning-connectors-clickhouse/src/main/java/com/zhisheng/connectors/clickhouse/applied/ClickhouseWriter.java ================================================ package com.zhisheng.connectors.clickhouse.applied; import com.google.common.collect.Lists; import com.zhisheng.connectors.clickhouse.model.ClickhouseRequestBlank; import com.zhisheng.connectors.clickhouse.model.ClickhouseSinkCommonParams; import com.zhisheng.connectors.clickhouse.util.ThreadUtil; import io.netty.handler.codec.http.HttpHeaders; import org.asynchttpclient.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.io.PrintWriter; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.List; import java.util.concurrent.*; /** * Desc: * Created by zhisheng on 2019/9/28 上午10:09 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class ClickhouseWriter implements AutoCloseable { private static final Logger logger = LoggerFactory.getLogger(ClickhouseWriter.class); private ExecutorService service; private ExecutorService callbackService; private List tasks; private BlockingQueue commonQueue; private AsyncHttpClient asyncHttpClient; private ClickhouseSinkCommonParams sinkParams; public ClickhouseWriter(ClickhouseSinkCommonParams sinkParams) { this.sinkParams = sinkParams; initDirAndExecutors(); } private void initDirAndExecutors() { try { initDir(sinkParams.getFailedRecordsPath()); buildComponents(); } catch (Exception e) { logger.error("Error while starting CH writer", e); throw new RuntimeException(e); } } private static void initDir(String pathName) throws IOException { Path path = Paths.get(pathName); Files.createDirectories(path); } private void buildComponents() { asyncHttpClient = Dsl.asyncHttpClient(); int numWriters = sinkParams.getNumWriters(); commonQueue = new LinkedBlockingQueue<>(sinkParams.getQueueMaxCapacity()); ThreadFactory threadFactory = ThreadUtil.threadFactory("clickhouse-writer"); service = Executors.newFixedThreadPool(sinkParams.getNumWriters(), threadFactory); ThreadFactory callbackServiceFactory = ThreadUtil.threadFactory("clickhouse-writer-callback-executor"); int cores = Runtime.getRuntime().availableProcessors(); int coreThreadsNum = Math.max(cores / 4, 2); callbackService = new ThreadPoolExecutor( coreThreadsNum, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(), callbackServiceFactory); tasks = Lists.newArrayList(); for (int i = 0; i < numWriters; i++) { WriterTask task = new WriterTask(i, asyncHttpClient, commonQueue, sinkParams, callbackService); tasks.add(task); service.submit(task); } } public void put(ClickhouseRequestBlank params) { try { commonQueue.put(params); } catch (InterruptedException e) { logger.error("Interrupted error while putting data to queue", e); Thread.currentThread().interrupt(); throw new RuntimeException(e); } } private void stopWriters() { if (tasks != null && tasks.size() > 0) { tasks.forEach(WriterTask::setStopWorking); } } @Override public void close() throws Exception { logger.info("Closing clickhouse-writer..."); stopWriters(); ThreadUtil.shutdownExecutorService(service); ThreadUtil.shutdownExecutorService(callbackService); asyncHttpClient.close(); logger.info("{} is closed", ClickhouseWriter.class.getSimpleName()); } static class WriterTask implements Runnable { private static final Logger logger = LoggerFactory.getLogger(WriterTask.class); private static final int HTTP_OK = 200; private final BlockingQueue queue; private final ClickhouseSinkCommonParams sinkSettings; private final AsyncHttpClient asyncHttpClient; private final ExecutorService callbackService; private final int id; private volatile boolean isWorking; WriterTask(int id, AsyncHttpClient asyncHttpClient, BlockingQueue queue, ClickhouseSinkCommonParams settings, ExecutorService callbackService ) { this.id = id; this.sinkSettings = settings; this.queue = queue; this.callbackService = callbackService; this.asyncHttpClient = asyncHttpClient; } @Override public void run() { try { isWorking = true; logger.info("Start writer task, id = {}", id); while (isWorking || queue.size() > 0) { ClickhouseRequestBlank blank = queue.poll(300, TimeUnit.MILLISECONDS); if (blank != null) { send(blank); } } } catch (Exception e) { logger.error("Error while inserting data", e); throw new RuntimeException(e); } finally { logger.info("Task id = {} is finished", id); } } private void send(ClickhouseRequestBlank requestBlank) { Request request = buildRequest(requestBlank); logger.debug("Ready to load data to {}, size = {}", requestBlank.getTargetTable(), requestBlank.getValues().size()); ListenableFuture whenResponse = asyncHttpClient.executeRequest(request); Runnable callback = responseCallback(whenResponse, requestBlank); whenResponse.addListener(callback, callbackService); } private Request buildRequest(ClickhouseRequestBlank requestBlank) { String resultCSV = String.join(" , ", requestBlank.getValues()); String query = String.format("INSERT INTO %s VALUES %s", requestBlank.getTargetTable(), resultCSV); String host = sinkSettings.getClickhouseClusterSettings().getRandomHostUrl(); BoundRequestBuilder builder = asyncHttpClient .preparePost(host) .setHeader(HttpHeaders.Names.CONTENT_TYPE, "text/plain; charset=utf-8") .setBody(query); if (sinkSettings.getClickhouseClusterSettings().isAuthorizationRequired()) { builder.setHeader(HttpHeaders.Names.AUTHORIZATION, "Basic " + sinkSettings.getClickhouseClusterSettings().getCredentials()); } return builder.build(); } private Runnable responseCallback(ListenableFuture whenResponse, ClickhouseRequestBlank requestBlank) { return () -> { Response response = null; try { response = whenResponse.get(); if (response.getStatusCode() != HTTP_OK) { handleUnsuccessfulResponse(response, requestBlank); } else { logger.info("Successful send data to Clickhouse, batch size = {}, target table = {}, current attempt = {}", requestBlank.getValues().size(), requestBlank.getTargetTable(), requestBlank.getAttemptCounter()); } } catch (Exception e) { logger.error("Error while executing callback, params = {}", sinkSettings, e); try { handleUnsuccessfulResponse(response, requestBlank); } catch (Exception error) { logger.error("Error while handle unsuccessful response", error); } } }; } private void handleUnsuccessfulResponse(Response response, ClickhouseRequestBlank requestBlank) throws Exception { int currentCounter = requestBlank.getAttemptCounter(); if (currentCounter > sinkSettings.getMaxRetries()) { logger.warn("Failed to send data to Clickhouse, cause: limit of attempts is exceeded. Clickhouse response = {}. Ready to flush data on disk", response); logFailedRecords(requestBlank); } else { requestBlank.incrementCounter(); logger.warn("Next attempt to send data to Clickhouse, table = {}, buffer size = {}, current attempt num = {}, max attempt num = {}, response = {}", requestBlank.getTargetTable(), requestBlank.getValues().size(), requestBlank.getAttemptCounter(), sinkSettings.getMaxRetries(), response); queue.put(requestBlank); } } private void logFailedRecords(ClickhouseRequestBlank requestBlank) throws Exception { String filePath = String.format("%s/%s_%s", sinkSettings.getFailedRecordsPath(), requestBlank.getTargetTable(), System.currentTimeMillis()); try (PrintWriter writer = new PrintWriter(filePath)) { List records = requestBlank.getValues(); records.forEach(writer::println); writer.flush(); } logger.info("Successful send data on disk, path = {}, size = {} ", filePath, requestBlank.getValues().size()); } void setStopWorking() { isWorking = false; } } } ================================================ FILE: flink-learning-connectors/flink-learning-connectors-clickhouse/src/main/java/com/zhisheng/connectors/clickhouse/model/ClickhouseClusterSettings.java ================================================ package com.zhisheng.connectors.clickhouse.model; import com.google.common.base.Preconditions; import com.zhisheng.connectors.clickhouse.util.ConfigUtil; import org.apache.commons.lang3.StringUtils; import java.util.Arrays; import java.util.Base64; import java.util.List; import java.util.Map; import java.util.concurrent.ThreadLocalRandom; import java.util.stream.Collectors; /** * Desc: * Created by zhisheng on 2019/9/28 上午12:40 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class ClickhouseClusterSettings { public static final String CLICKHOUSE_HOSTS = "clickhouse.access.hosts"; public static final String CLICKHOUSE_USER = "clickhouse.access.user"; public static final String CLICKHOUSE_PASSWORD = "clickhouse.access.password"; private final List hostsWithPorts; private final String user; private final String password; private final String credentials; private final boolean authorizationRequired; private int currentHostId = 0; public ClickhouseClusterSettings(Map parameters) { Preconditions.checkNotNull(parameters); String hostsString = parameters.get(CLICKHOUSE_HOSTS); Preconditions.checkNotNull(hostsString); hostsWithPorts = buildHostsAndPort(hostsString); Preconditions.checkArgument(hostsWithPorts.size() > 0); String usr = parameters.get(CLICKHOUSE_USER); String pass = parameters.get(CLICKHOUSE_PASSWORD); if (StringUtils.isNotEmpty(usr) && StringUtils.isNotEmpty(pass)) { user = parameters.get(CLICKHOUSE_USER); password = parameters.get(CLICKHOUSE_PASSWORD); credentials = buildCredentials(user, password); authorizationRequired = true; } else { // avoid NPE credentials = ""; password = ""; user = ""; authorizationRequired = false; } } private static List buildHostsAndPort(String hostsString) { return Arrays.stream(hostsString .split(ConfigUtil.HOST_DELIMITER)) .map(ClickhouseClusterSettings::checkHttpAndAdd) .collect(Collectors.toList()); } private static String checkHttpAndAdd(String host) { String newHost = host.replace(" ", ""); if (!newHost.contains("http")) { return "http://" + newHost; } return newHost; } private static String buildCredentials(String user, String password) { Base64.Encoder x = Base64.getEncoder(); String credentials = String.join(":", user, password); return new String(x.encode(credentials.getBytes())); } public String getRandomHostUrl() { currentHostId = ThreadLocalRandom.current().nextInt(hostsWithPorts.size()); return hostsWithPorts.get(currentHostId); } public String getNextHost() { if (currentHostId >= hostsWithPorts.size() - 1) { currentHostId = 0; } else { currentHostId += 1; } return hostsWithPorts.get(currentHostId); } public List getHostsWithPorts() { return hostsWithPorts; } public String getUser() { return user; } public String getPassword() { return password; } public String getCredentials() { return credentials; } public boolean isAuthorizationRequired() { return authorizationRequired; } @Override public String toString() { return "ClickhouseClusterSettings{" + "hostsWithPorts=" + hostsWithPorts + ", credentials='" + credentials + '\'' + ", authorizationRequired=" + authorizationRequired + ", currentHostId=" + currentHostId + '}'; } } ================================================ FILE: flink-learning-connectors/flink-learning-connectors-clickhouse/src/main/java/com/zhisheng/connectors/clickhouse/model/ClickhouseRequestBlank.java ================================================ package com.zhisheng.connectors.clickhouse.model; import java.util.List; /** * Desc: * Created by zhisheng on 2019/9/28 上午10:14 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class ClickhouseRequestBlank { private final List values; private final String targetTable; private int attemptCounter; public ClickhouseRequestBlank(List values, String targetTable) { this.values = values; this.targetTable = targetTable; this.attemptCounter = 0; } public List getValues() { return values; } public void incrementCounter() { this.attemptCounter++; } public int getAttemptCounter() { return attemptCounter; } public String getTargetTable() { return targetTable; } public static final class Builder { private List values; private String targetTable; private Builder() { } public static Builder aBuilder() { return new Builder(); } public Builder withValues(List values) { this.values = values; return this; } public Builder withTargetTable(String targetTable) { this.targetTable = targetTable; return this; } public ClickhouseRequestBlank build() { return new ClickhouseRequestBlank(values, targetTable); } } @Override public String toString() { return "ClickhouseRequestBlank{" + "values=" + values + ", targetTable='" + targetTable + '\'' + ", attemptCounter=" + attemptCounter + '}'; } } ================================================ FILE: flink-learning-connectors/flink-learning-connectors-clickhouse/src/main/java/com/zhisheng/connectors/clickhouse/model/ClickhouseSinkCommonParams.java ================================================ package com.zhisheng.connectors.clickhouse.model; import com.google.common.base.Preconditions; import java.util.Map; import static com.zhisheng.connectors.clickhouse.model.ClickhouseSinkConsts.*; /** * Desc: * Created by zhisheng on 2019/9/28 上午10:05 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class ClickhouseSinkCommonParams { private final ClickhouseClusterSettings clickhouseClusterSettings; private final String failedRecordsPath; private final int numWriters; private final int queueMaxCapacity; private final int timeout; private final int maxRetries; public ClickhouseSinkCommonParams(Map params) { this.clickhouseClusterSettings = new ClickhouseClusterSettings(params); this.numWriters = Integer.valueOf(params.get(NUM_WRITERS)); this.queueMaxCapacity = Integer.valueOf(params.get(QUEUE_MAX_CAPACITY)); this.maxRetries = Integer.valueOf(params.get(NUM_RETRIES)); this.timeout = Integer.valueOf(params.get(TIMEOUT_SEC)); this.failedRecordsPath = params.get(FAILED_RECORDS_PATH); Preconditions.checkNotNull(failedRecordsPath); Preconditions.checkArgument(queueMaxCapacity > 0); Preconditions.checkArgument(numWriters > 0); Preconditions.checkArgument(timeout > 0); Preconditions.checkArgument(maxRetries > 0); } public int getNumWriters() { return numWriters; } public int getQueueMaxCapacity() { return queueMaxCapacity; } public ClickhouseClusterSettings getClickhouseClusterSettings() { return clickhouseClusterSettings; } public int getTimeout() { return timeout; } public int getMaxRetries() { return maxRetries; } public String getFailedRecordsPath() { return failedRecordsPath; } @Override public String toString() { return "ClickhouseSinkCommonParams{" + "clickhouseClusterSettings=" + clickhouseClusterSettings + ", failedRecordsPath='" + failedRecordsPath + '\'' + ", numWriters=" + numWriters + ", queueMaxCapacity=" + queueMaxCapacity + ", timeout=" + timeout + ", maxRetries=" + maxRetries + '}'; } } ================================================ FILE: flink-learning-connectors/flink-learning-connectors-clickhouse/src/main/java/com/zhisheng/connectors/clickhouse/model/ClickhouseSinkConsts.java ================================================ package com.zhisheng.connectors.clickhouse.model; /** * Desc: * Created by zhisheng on 2019/9/28 上午10:03 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public final class ClickhouseSinkConsts { private ClickhouseSinkConsts() { } public static final String TARGET_TABLE_NAME = "clickhouse.sink.target-table"; public static final String MAX_BUFFER_SIZE = "clickhouse.sink.max-buffer-size"; public static final String NUM_WRITERS = "clickhouse.sink.num-writers"; public static final String QUEUE_MAX_CAPACITY = "clickhouse.sink.queue-max-capacity"; public static final String TIMEOUT_SEC = "clickhouse.sink.timeout-sec"; public static final String NUM_RETRIES = "clickhouse.sink.retries"; public static final String FAILED_RECORDS_PATH = "clickhouse.sink.failed-records-path"; } ================================================ FILE: flink-learning-connectors/flink-learning-connectors-clickhouse/src/main/java/com/zhisheng/connectors/clickhouse/util/ConfigUtil.java ================================================ package com.zhisheng.connectors.clickhouse.util; import com.typesafe.config.Config; import com.typesafe.config.ConfigValue; import java.util.*; /** * Desc: * Created by zhisheng on 2019/9/28 上午12:38 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class ConfigUtil { public static final String HOST_DELIMITER = ", "; private ConfigUtil(){ } public static Properties toProperties(Config config) { Properties properties = new Properties(); config.entrySet().forEach(e -> properties.put(e.getKey(), unwrapped(config.getValue(e.getKey())))); return properties; } public static Map toMap(Config config) { Map map = new HashMap<>(); config.entrySet().forEach(e -> map.put(e.getKey(), unwrapped(e.getValue()))); return map; } private static String unwrapped(ConfigValue configValue) { Object object = configValue.unwrapped(); return object.toString(); } static public String buildStringFromList(List list) { return String.join(HOST_DELIMITER, list); } static public List buildListFromString(String string) { return Arrays.asList(string.split(" ")); } } ================================================ FILE: flink-learning-connectors/flink-learning-connectors-clickhouse/src/main/java/com/zhisheng/connectors/clickhouse/util/ThreadUtil.java ================================================ package com.zhisheng.connectors.clickhouse.util; import com.google.common.util.concurrent.ThreadFactoryBuilder; import java.util.concurrent.ExecutorService; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; /** * Desc: * Created by zhisheng on 2019/9/28 上午12:39 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class ThreadUtil { private ThreadUtil() { } public static ThreadFactory threadFactory(String threadName, boolean isDaemon) { return new ThreadFactoryBuilder() .setNameFormat(threadName + "-%d") .setDaemon(isDaemon) .build(); } public static ThreadFactory threadFactory(String threadName) { return threadFactory(threadName, true); } public static void shutdownExecutorService(ExecutorService executorService) throws InterruptedException { shutdownExecutorService(executorService, 5); } public static void shutdownExecutorService(ExecutorService executorService, int timeoutS) throws InterruptedException { if (executorService != null && !executorService.isShutdown()) { executorService.shutdown(); if (!executorService.awaitTermination(timeoutS, TimeUnit.SECONDS)) { executorService.shutdownNow(); executorService.awaitTermination(timeoutS, TimeUnit.SECONDS); } } } } ================================================ FILE: flink-learning-connectors/flink-learning-connectors-clickhouse/src/main/resources/application.properties ================================================ kafka.brokers=localhost:9092 kafka.group.id=zhisheng kafka.zookeeper.connect=localhost:2181 metrics.topic=zhisheng stream.parallelism=4 stream.sink.parallelism=4 stream.default.parallelism=4 stream.checkpoint.interval=1000 stream.checkpoint.enable=false ================================================ FILE: flink-learning-connectors/flink-learning-connectors-clickhouse/src/main/resources/logback.xml ================================================ %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n ================================================ FILE: flink-learning-connectors/flink-learning-connectors-clickhouse/src/main/resources/reference.conf ================================================ clickhouse { sink { num-writers = 3 timeout-sec = 1 retries = 10 queue-max-capacity = 1000 failed-records-path = "/tmp/failed_records" } } ================================================ FILE: flink-learning-connectors/flink-learning-connectors-es/flink-learning-connectors-es-common/pom.xml ================================================ flink-learning-connectors-es com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 flink-learning-connectors-es-common ================================================ FILE: flink-learning-connectors/flink-learning-connectors-es/flink-learning-connectors-es-universal/README.md ================================================ ### 功能 一个项目支持通用的版本,可以写入 ES 5/6/7 多个版本 ================================================ FILE: flink-learning-connectors/flink-learning-connectors-es/flink-learning-connectors-es-universal/pom.xml ================================================ flink-learning-connectors-es com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 flink-learning-connectors-es-universal ================================================ FILE: flink-learning-connectors/flink-learning-connectors-es/flink-learning-connectors-es5/README.md ================================================ ## Flink connector ElasticSearch 5.x ================================================ FILE: flink-learning-connectors/flink-learning-connectors-es/flink-learning-connectors-es5/pom.xml ================================================ flink-learning-connectors-es com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 flink-learning-connectors-es5 org.apache.flink flink-connector-elasticsearch6 ${flink-connector-elasticsearch6.version} org.apache.maven.plugins maven-shade-plugin 3.1.0 false package shade com.zhisheng.connectors.es5.Sink2ES5Main reference.conf *:*:*:* META-INF/*.SF META-INF/*.DSA META-INF/*.RSA ================================================ FILE: flink-learning-connectors/flink-learning-connectors-es/flink-learning-connectors-es5/src/main/java/com/zhisheng/connectors/es5/Sink2ES5Main.java ================================================ package com.zhisheng.connectors.es5; /** * Desc: sink data to es5 * Created by zhisheng on 2019/10/22 下午5:10 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class Sink2ES5Main { } ================================================ FILE: flink-learning-connectors/flink-learning-connectors-es/flink-learning-connectors-es5/src/main/resources/logback.xml ================================================ %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n ================================================ FILE: flink-learning-connectors/flink-learning-connectors-es/flink-learning-connectors-es6/README.md ================================================ ## Flink connector ElasticSearch 6.x [http://www.54tianzhisheng.cn/2018/12/30/Flink-ElasticSearch-Sink/](http://www.54tianzhisheng.cn/2018/12/30/Flink-ElasticSearch-Sink/) ================================================ FILE: flink-learning-connectors/flink-learning-connectors-es/flink-learning-connectors-es6/pom.xml ================================================ flink-learning-connectors-es com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 flink-learning-connectors-es6 org.apache.flink flink-connector-elasticsearch6 ${flink-connector-elasticsearch6.version} org.apache.maven.plugins maven-shade-plugin 3.1.0 false package shade com.zhisheng.connectors.es6.Sink2ES6Main reference.conf *:*:*:* META-INF/*.SF META-INF/*.DSA META-INF/*.RSA ================================================ FILE: flink-learning-connectors/flink-learning-connectors-es/flink-learning-connectors-es6/src/main/java/com/zhisheng/connectors/es6/Sink2ES6Main.java ================================================ package com.zhisheng.connectors.es6; import com.zhisheng.common.model.MetricEvent; import com.zhisheng.common.utils.ExecutionEnvUtil; import com.zhisheng.common.utils.GsonUtil; import com.zhisheng.common.utils.KafkaConfigUtil; import com.zhisheng.connectors.es6.utils.ESSinkUtil; import lombok.extern.slf4j.Slf4j; import org.apache.flink.api.common.functions.RuntimeContext; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.streaming.api.datastream.DataStreamSource; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.connectors.elasticsearch.RequestIndexer; import org.apache.http.HttpHost; import org.elasticsearch.client.Requests; import org.elasticsearch.common.xcontent.XContentType; import java.util.List; import static com.zhisheng.common.constant.PropertiesConstants.*; /** * sink data to es6 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ @Slf4j public class Sink2ES6Main { public static void main(String[] args) throws Exception { final ParameterTool parameterTool = ExecutionEnvUtil.createParameterTool(args); StreamExecutionEnvironment env = ExecutionEnvUtil.prepare(parameterTool); DataStreamSource data = KafkaConfigUtil.buildSource(env); List esAddresses = ESSinkUtil.getEsAddresses(parameterTool.get(ELASTICSEARCH_HOSTS)); int bulkSize = parameterTool.getInt(ELASTICSEARCH_BULK_FLUSH_MAX_ACTIONS, 40); int sinkParallelism = parameterTool.getInt(STREAM_SINK_PARALLELISM, 5); log.info("-----esAddresses = {}, parameterTool = {}, ", esAddresses, parameterTool); ESSinkUtil.addSink(esAddresses, bulkSize, sinkParallelism, data, (MetricEvent metric, RuntimeContext runtimeContext, RequestIndexer requestIndexer) -> { requestIndexer.add(Requests.indexRequest() .index(ZHISHENG + "_" + metric.getName()) .type(ZHISHENG) .source(GsonUtil.toJSONBytes(metric), XContentType.JSON)); }, parameterTool); env.execute("flink learning connectors es6"); } } ================================================ FILE: flink-learning-connectors/flink-learning-connectors-es/flink-learning-connectors-es6/src/main/java/com/zhisheng/connectors/es6/utils/ESSinkUtil.java ================================================ package com.zhisheng.connectors.es6.utils; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator; import org.apache.flink.streaming.connectors.elasticsearch.ElasticsearchSinkFunction; import org.apache.flink.streaming.connectors.elasticsearch6.ElasticsearchSink; import org.apache.http.HttpHost; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.List; /** * Desc: ES Sink utils(get ES host、addSink)//todo: index template & x-pack * Created by zhisheng on 2019/10/21 下午3:05 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class ESSinkUtil { //es security constant public static final String ES_SECURITY_ENABLE = "es.security.enable"; public static final String ES_SECURITY_USERNAME = "es.security.username"; public static final String ES_SECURITY_PASSWORD = "es.security.password"; /** * es sink * * @param hosts es hosts * @param bulkFlushMaxActions bulk flush size * @param parallelism 并行数 * @param data 数据 * @param func * @param */ public static void addSink(List hosts, int bulkFlushMaxActions, int parallelism, SingleOutputStreamOperator data, ElasticsearchSinkFunction func, ParameterTool parameterTool) { ElasticsearchSink.Builder esSinkBuilder = new ElasticsearchSink.Builder<>(hosts, func); esSinkBuilder.setBulkFlushMaxActions(bulkFlushMaxActions); esSinkBuilder.setFailureHandler(new RetryRequestFailureHandler()); //todo:xpack security data.addSink(esSinkBuilder.build()).setParallelism(parallelism); } /** * 解析配置文件的 es hosts * * @param hosts * @return * @throws MalformedURLException */ public static List getEsAddresses(String hosts) throws MalformedURLException { String[] hostList = hosts.split(","); List addresses = new ArrayList<>(); for (String host : hostList) { if (host.startsWith("http")) { URL url = new URL(host); addresses.add(new HttpHost(url.getHost(), url.getPort())); } else { String[] parts = host.split(":", 2); if (parts.length > 1) { addresses.add(new HttpHost(parts[0], Integer.parseInt(parts[1]))); } else { throw new MalformedURLException("invalid elasticsearch hosts format"); } } } return addresses; } } ================================================ FILE: flink-learning-connectors/flink-learning-connectors-es/flink-learning-connectors-es6/src/main/java/com/zhisheng/connectors/es6/utils/RetryRequestFailureHandler.java ================================================ package com.zhisheng.connectors.es6.utils; import lombok.extern.slf4j.Slf4j; import org.apache.flink.streaming.connectors.elasticsearch.ActionRequestFailureHandler; import org.apache.flink.streaming.connectors.elasticsearch.RequestIndexer; import org.apache.flink.util.ExceptionUtils; import org.elasticsearch.action.ActionRequest; import org.elasticsearch.common.util.concurrent.EsRejectedExecutionException; import java.io.IOException; import java.net.SocketTimeoutException; import java.util.Optional; /** * Desc: es sink Request Failure Handler * Created by zhisheng on 2019/10/21 下午3:07 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ @Slf4j public class RetryRequestFailureHandler implements ActionRequestFailureHandler { public RetryRequestFailureHandler() { } @Override public void onFailure(ActionRequest actionRequest, Throwable throwable, int i, RequestIndexer requestIndexer) throws Throwable { if (ExceptionUtils.findThrowable(throwable, EsRejectedExecutionException.class).isPresent()) { requestIndexer.add(new ActionRequest[]{actionRequest}); } else { if (ExceptionUtils.findThrowable(throwable, SocketTimeoutException.class).isPresent()) { return; } else { Optional exp = ExceptionUtils.findThrowable(throwable, IOException.class); if (exp.isPresent()) { IOException ioExp = exp.get(); if (ioExp != null && ioExp.getMessage() != null && ioExp.getMessage().contains("max retry timeout")) { log.error(ioExp.getMessage()); return; } } } throw throwable; } } } ================================================ FILE: flink-learning-connectors/flink-learning-connectors-es/flink-learning-connectors-es6/src/main/resources/application.properties ================================================ kafka.brokers=localhost:9092 kafka.group.id=metrics-group-test kafka.zookeeper.connect=localhost:2181 metrics.topic=alert-metrics stream.parallelism=5 stream.checkpoint.interval=1000 stream.checkpoint.enable=false elasticsearch.hosts=localhost:9200,localhost:9202,localhost:9203 elasticsearch.bulk.flush.max.actions=1000 stream.sink.parallelism=5 # \u6743\u9650 es.security.enable=false es.security.username=zhisheng es.security.password=zhisheng ================================================ FILE: flink-learning-connectors/flink-learning-connectors-es/flink-learning-connectors-es6/src/main/resources/es_index_template.json ================================================ { "index_patterns": ["zhisheng-*"], "settings": { "refresh_interval": "30s", "index.translog.durability": "async", "index.translog.sync_interval": "20s", "index.translog.flush_threshold_size": "1024mb", "number_of_shards": 5, "number_of_replicas": 1 }, "mappings": { "zhisheng": { "date_detection": false, "dynamic_templates": [ { "timestamp": { "match": "timestamp", "match_mapping_type": "long", "mapping": { "type": "long" } } }, { "tags": { "match": "*", "match_mapping_type": "string", "mapping": { "type": "keyword", "ignore_above": 10240 } } }, { "fields_float": { "match": "*", "match_mapping_type": "double", "mapping": { "type": "double" } } }, { "fields_long": { "match": "*", "match_mapping_type": "long", "mapping": { "type": "double" } } } ] } } } ================================================ FILE: flink-learning-connectors/flink-learning-connectors-es/flink-learning-connectors-es6/src/main/resources/logback.xml ================================================ %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n ================================================ FILE: flink-learning-connectors/flink-learning-connectors-es/flink-learning-connectors-es7/README.md ================================================ ## Flink connector ElasticSearch 7.x 遇到过的问题有: 1、ElasticSearch 的分片和副本的调优 2、ElasticSearch 的线程队列的调优 3、ElasticSearch 的刷新时间的调整 4、ElasticSearch 磁盘到 85% 后出现不写入 5、ElasticSearch 某个节点挂了导致写入的请求丢失不会重试 6、ElasticSearch bulk 写 7、权限认证的问题 8、Index template 初始化 9、使用 RestHighLevelClient 总之目的就是为了在能够高效的将数据写入进 ElasticSearch,还要保证 ElasticSearch 不挂 ## TODO 1、X-Pack 权限认证 2、使用 RestHighLevelClient ================================================ FILE: flink-learning-connectors/flink-learning-connectors-es/flink-learning-connectors-es7/pom.xml ================================================ flink-learning-connectors-es com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 flink-learning-connectors-es7 org.apache.flink flink-connector-elasticsearch7 ${flink-connector-elasticsearch7.version} org.apache.maven.plugins maven-shade-plugin 3.1.0 false package shade com.zhisheng.connectors.es7.Sink2ES7Main reference.conf *:*:*:* META-INF/*.SF META-INF/*.DSA META-INF/*.RSA ================================================ FILE: flink-learning-connectors/flink-learning-connectors-es/flink-learning-connectors-es7/src/main/java/com/zhisheng/connectors/es7/Sink2ES7Main.java ================================================ package com.zhisheng.connectors.es7; import com.zhisheng.common.model.MetricEvent; import com.zhisheng.common.utils.ExecutionEnvUtil; import com.zhisheng.common.utils.GsonUtil; import com.zhisheng.connectors.es7.util.ESSinkUtil; import lombok.extern.slf4j.Slf4j; import org.apache.flink.api.common.functions.RuntimeContext; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.streaming.api.datastream.DataStreamSource; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.api.functions.source.ParallelSourceFunction; import org.apache.flink.streaming.connectors.elasticsearch.RequestIndexer; import org.apache.http.HttpHost; import org.elasticsearch.client.Requests; import org.elasticsearch.common.xcontent.XContentType; import java.util.HashMap; import java.util.List; import java.util.Map; import static com.zhisheng.common.constant.PropertiesConstants.*; /** * Desc: sink data to es7 * Created by zhisheng on 2019/10/22 下午5:10 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ @Slf4j public class Sink2ES7Main { public static void main(String[] args) throws Exception { final ParameterTool parameterTool = ExecutionEnvUtil.createParameterTool(args); StreamExecutionEnvironment env = ExecutionEnvUtil.prepare(parameterTool); List esAddresses = ESSinkUtil.getEsAddresses(parameterTool.get(ELASTICSEARCH_HOSTS)); int bulkSize = parameterTool.getInt(ELASTICSEARCH_BULK_FLUSH_MAX_ACTIONS, 40); int sinkParallelism = parameterTool.getInt(STREAM_SINK_PARALLELISM, 1); log.info("-----esAddresses = {}, parameterTool = {}, ", esAddresses, parameterTool); DataStreamSource data = env.addSource(new ParallelSourceFunction() { @Override public void run(SourceContext context) throws Exception { while (true) { //just for test Map fields = new HashMap<>(); fields.put("system", 10); fields.put("user", 20); fields.put("idle", 70); Map tags = new HashMap<>(); tags.put("cluster_name", "zhisheng"); tags.put("host_ip", "11.0.11.0"); MetricEvent metricEvent = MetricEvent.builder() .name("cpu") .timestamp(System.currentTimeMillis()) .fields(fields) .tags(tags) .build(); context.collect(metricEvent); Thread.sleep(200); } } @Override public void cancel() { } }); ESSinkUtil.addSink(esAddresses, bulkSize, sinkParallelism, data, (MetricEvent metric, RuntimeContext runtimeContext, RequestIndexer requestIndexer) -> { requestIndexer.add(Requests.indexRequest() .index(ZHISHENG + "_" + metric.getName()) .source(GsonUtil.toJSONBytes(metric), XContentType.JSON)); }, parameterTool); env.execute("flink learning connectors es7"); } } ================================================ FILE: flink-learning-connectors/flink-learning-connectors-es/flink-learning-connectors-es7/src/main/java/com/zhisheng/connectors/es7/util/ESSinkUtil.java ================================================ package com.zhisheng.connectors.es7.util; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator; import org.apache.flink.streaming.connectors.elasticsearch.ElasticsearchSinkFunction; import org.apache.flink.streaming.connectors.elasticsearch7.ElasticsearchSink; import org.apache.http.HttpHost; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.List; /** * Desc: ES Sink utils(get ES host、addSink)//todo: index template & x-pack * Created by zhisheng on 2019/10/21 下午3:05 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class ESSinkUtil { //es security constant public static final String ES_SECURITY_ENABLE = "es.security.enable"; public static final String ES_SECURITY_USERNAME = "es.security.username"; public static final String ES_SECURITY_PASSWORD = "es.security.password"; /** * es sink * * @param hosts es hosts * @param bulkFlushMaxActions bulk flush size * @param parallelism 并行数 * @param data 数据 * @param func * @param */ public static void addSink(List hosts, int bulkFlushMaxActions, int parallelism, SingleOutputStreamOperator data, ElasticsearchSinkFunction func, ParameterTool parameterTool) { ElasticsearchSink.Builder esSinkBuilder = new ElasticsearchSink.Builder<>(hosts, func); esSinkBuilder.setBulkFlushMaxActions(bulkFlushMaxActions); esSinkBuilder.setFailureHandler(new RetryRequestFailureHandler()); //todo:xpack security data.addSink(esSinkBuilder.build()).setParallelism(parallelism); } /** * 解析配置文件的 es hosts * * @param hosts * @return * @throws MalformedURLException */ public static List getEsAddresses(String hosts) throws MalformedURLException { String[] hostList = hosts.split(","); List addresses = new ArrayList<>(); for (String host : hostList) { if (host.startsWith("http")) { URL url = new URL(host); addresses.add(new HttpHost(url.getHost(), url.getPort())); } else { String[] parts = host.split(":", 2); if (parts.length > 1) { addresses.add(new HttpHost(parts[0], Integer.parseInt(parts[1]))); } else { throw new MalformedURLException("invalid elasticsearch hosts format"); } } } return addresses; } } ================================================ FILE: flink-learning-connectors/flink-learning-connectors-es/flink-learning-connectors-es7/src/main/java/com/zhisheng/connectors/es7/util/RetryRequestFailureHandler.java ================================================ package com.zhisheng.connectors.es7.util; import lombok.extern.slf4j.Slf4j; import org.apache.flink.streaming.connectors.elasticsearch.ActionRequestFailureHandler; import org.apache.flink.streaming.connectors.elasticsearch.RequestIndexer; import org.apache.flink.util.ExceptionUtils; import org.elasticsearch.action.ActionRequest; import org.elasticsearch.common.util.concurrent.EsRejectedExecutionException; import java.io.IOException; import java.net.SocketTimeoutException; import java.util.Optional; /** * Desc: es sink Request Failure Handler * Created by zhisheng on 2019/10/21 下午3:07 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ @Slf4j public class RetryRequestFailureHandler implements ActionRequestFailureHandler { public RetryRequestFailureHandler() { } @Override public void onFailure(ActionRequest actionRequest, Throwable throwable, int i, RequestIndexer requestIndexer) throws Throwable { if (ExceptionUtils.findThrowable(throwable, EsRejectedExecutionException.class).isPresent()) { requestIndexer.add(new ActionRequest[]{actionRequest}); } else { if (ExceptionUtils.findThrowable(throwable, SocketTimeoutException.class).isPresent()) { return; } else { Optional exp = ExceptionUtils.findThrowable(throwable, IOException.class); if (exp.isPresent()) { IOException ioExp = exp.get(); if (ioExp != null && ioExp.getMessage() != null && ioExp.getMessage().contains("max retry timeout")) { log.error(ioExp.getMessage()); return; } } } throw throwable; } } } ================================================ FILE: flink-learning-connectors/flink-learning-connectors-es/flink-learning-connectors-es7/src/main/resources/application.properties ================================================ kafka.brokers=localhost:9092 kafka.group.id=metrics-group-test kafka.zookeeper.connect=localhost:2181 metrics.topic=alert-metrics stream.parallelism=5 stream.checkpoint.interval=1000 stream.checkpoint.enable=false elasticsearch.hosts=localhost:9200 elasticsearch.bulk.flush.max.actions=1000 stream.sink.parallelism=1 # \u6743\u9650 es.security.enable=false es.security.username=zhisheng es.security.password=zhisheng ================================================ FILE: flink-learning-connectors/flink-learning-connectors-es/flink-learning-connectors-es7/src/main/resources/es_index_template.json ================================================ { "index_patterns": ["zhisheng-*"], "settings": { "refresh_interval": "30s", "index.translog.durability": "async", "index.translog.sync_interval": "20s", "index.translog.flush_threshold_size": "1024mb", "number_of_shards": 1, "number_of_replicas": 1 }, "mappings": { "zhisheng": { "date_detection": false, "dynamic_templates": [ { "timestamp": { "match": "timestamp", "match_mapping_type": "long", "mapping": { "type": "long" } } }, { "tags": { "match": "*", "match_mapping_type": "string", "mapping": { "type": "keyword", "ignore_above": 10240 } } }, { "fields_float": { "match": "*", "match_mapping_type": "double", "mapping": { "type": "double" } } }, { "fields_long": { "match": "*", "match_mapping_type": "long", "mapping": { "type": "double" } } } ] } } } ================================================ FILE: flink-learning-connectors/flink-learning-connectors-es/flink-learning-connectors-es7/src/main/resources/logback.xml ================================================ %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n ================================================ FILE: flink-learning-connectors/flink-learning-connectors-es/pom.xml ================================================ flink-learning-connectors com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 flink-learning-connectors-es pom flink-learning-connectors-es-common flink-learning-connectors-es5 flink-learning-connectors-es6 flink-learning-connectors-es7 flink-learning-connectors-es-universal ================================================ FILE: flink-learning-connectors/flink-learning-connectors-flume/README.md ================================================ ### Flink connector Flume ================================================ FILE: flink-learning-connectors/flink-learning-connectors-flume/pom.xml ================================================ flink-learning-connectors com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 flink-learning-connectors-flume 1.9.0 org.apache.flume flume-ng-core ${flume-ng.version} org.apache.maven.plugins maven-shade-plugin 3.1.0 false package shade com.zhisheng.connectors.flume.Main reference.conf *:*:*:* META-INF/*.SF META-INF/*.DSA META-INF/*.RSA ================================================ FILE: flink-learning-connectors/flink-learning-connectors-flume/src/main/java/com/zhisheng/connectors/flume/FlumeEventBuilder.java ================================================ package com.zhisheng.connectors.flume; import org.apache.flink.api.common.functions.Function; import org.apache.flink.api.common.functions.RuntimeContext; import org.apache.flume.Event; import java.io.Serializable; /** * Desc: A function that can create a Event from an incoming instance of the given type. * Created by zhisheng on 2019-05-04 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public interface FlumeEventBuilder extends Function, Serializable { Event createFlumeEvent(IN value, RuntimeContext ctx); } ================================================ FILE: flink-learning-connectors/flink-learning-connectors-flume/src/main/java/com/zhisheng/connectors/flume/FlumeSink.java ================================================ package com.zhisheng.connectors.flume; import com.zhisheng.connectors.flume.utils.FlumeUtil; import lombok.extern.slf4j.Slf4j; import org.apache.flink.configuration.Configuration; import org.apache.flink.streaming.api.functions.sink.RichSinkFunction; import org.apache.flume.Event; import org.apache.flume.EventDeliveryException; import org.apache.flume.api.RpcClient; import org.apache.flume.api.RpcClientConfigurationConstants; import java.util.ArrayList; import java.util.List; /** * Desc: Flume sink * Created by zhisheng on 2019-05-04 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ @Slf4j public class FlumeSink extends RichSinkFunction { /** * 最大尝试次数 */ private static final int DEFAULT_MAX_RETRY_ATTEMPTS = 3; /** * 等待时间 */ private static final long DEFAULT_WAIT_TIMEMS = 1000L; private String clientType; private String hostname; private int port; private int batchSize; private int maxRetryAttempts; private long waitTimeMs; private List incomingList; private FlumeEventBuilder eventBuilder; private RpcClient client; public FlumeSink(String clientType, String hostname, int port, FlumeEventBuilder eventBuilder) { this(clientType, hostname, port, eventBuilder, RpcClientConfigurationConstants.DEFAULT_BATCH_SIZE, DEFAULT_MAX_RETRY_ATTEMPTS, DEFAULT_WAIT_TIMEMS); } public FlumeSink(String clientType, String hostname, int port, FlumeEventBuilder eventBuilder, int batchSize) { this(clientType, hostname, port, eventBuilder, batchSize, DEFAULT_MAX_RETRY_ATTEMPTS, DEFAULT_WAIT_TIMEMS); } public FlumeSink(String clientType, String hostname, int port, FlumeEventBuilder eventBuilder, int batchSize, int maxRetryAttempts, long waitTimeMs) { this.clientType = clientType; this.hostname = hostname; this.port = port; this.eventBuilder = eventBuilder; this.batchSize = batchSize; this.maxRetryAttempts = maxRetryAttempts; this.waitTimeMs = waitTimeMs; } @Override public void open(Configuration parameters) throws Exception { super.open(parameters); incomingList = new ArrayList(); client = FlumeUtil.getRpcClient(clientType, hostname, port, batchSize); } @Override public void invoke(IN value) throws Exception { int number; synchronized (this) { if (null != value) { incomingList.add(value); } number = incomingList.size(); } if (number == batchSize) { flush(); } } @Override public void close() throws Exception { super.close(); FlumeUtil.destroy(client); } private void flush() throws IllegalStateException { List events = new ArrayList<>(); List toFlushList; synchronized (this) { if (incomingList.isEmpty()) { return; } toFlushList = incomingList; incomingList = new ArrayList(); } for (IN value : toFlushList) { Event event = this.eventBuilder.createFlumeEvent(value, getRuntimeContext()); events.add(event); } int retries = 0; boolean flag = true; while (flag) { if (null != client || retries > maxRetryAttempts) { flag = false; } if (retries <= maxRetryAttempts && null == client) { log.info("Wait for {} ms before retry", waitTimeMs); try { Thread.sleep(waitTimeMs); } catch (InterruptedException ignored) { log.error("Interrupted while trying to connect {} on {}", hostname, port); } reconnect(); log.info("Retry attempt number {}", retries); retries++; } } try { client.appendBatch(events); } catch (EventDeliveryException e) { log.info("Encountered exception while sending data to flume : {}", e.getMessage(), e); } } private void reconnect() { FlumeUtil.destroy(client); client = null; client = FlumeUtil.getRpcClient(clientType, hostname, port, batchSize); } } ================================================ FILE: flink-learning-connectors/flink-learning-connectors-flume/src/main/java/com/zhisheng/connectors/flume/Main.java ================================================ package com.zhisheng.connectors.flume; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; public class Main { public static void main(String[] args) throws Exception { final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.execute("flink learning project template"); } } ================================================ FILE: flink-learning-connectors/flink-learning-connectors-flume/src/main/java/com/zhisheng/connectors/flume/utils/FlumeUtil.java ================================================ package com.zhisheng.connectors.flume.utils; import org.apache.flume.api.RpcClient; import org.apache.flume.api.RpcClientConfigurationConstants; import org.apache.flume.api.RpcClientFactory; import java.util.Properties; /** * Desc: * Created by zhisheng on 2019-05-04 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class FlumeUtil { private static final String CLIENT_TYPE_KEY = "client.type"; private static final String CLIENT_TYPE_DEFAULT_FAILOVER = "default_failover"; private static final String CLIENT_TYPE_DEFAULT_LOADBALANCING = "default_loadbalance"; public static RpcClient getRpcClient(String clientType, String hostname, Integer port, Integer batchSize) { Properties props; RpcClient client; switch(clientType.toUpperCase()) { case "THRIFT": client = RpcClientFactory.getThriftInstance(hostname, port, batchSize); break; case "DEFAULT": client = RpcClientFactory.getDefaultInstance(hostname, port, batchSize); break; case "DEFAULT_FAILOVER": props = getDefaultProperties(hostname, port, batchSize); props.put(CLIENT_TYPE_KEY, CLIENT_TYPE_DEFAULT_FAILOVER); client = RpcClientFactory.getInstance(props); break; case "DEFAULT_LOADBALANCE": props = getDefaultProperties(hostname, port, batchSize); props.put(CLIENT_TYPE_KEY, CLIENT_TYPE_DEFAULT_LOADBALANCING); client = RpcClientFactory.getInstance(props); break; default: throw new IllegalStateException("Unsupported client type - cannot happen"); } return client; } public static void destroy(RpcClient client) { if (null != client) { client.close(); } } private static Properties getDefaultProperties(String hostname, Integer port, Integer batchSize) { Properties props = new Properties(); props.setProperty(RpcClientConfigurationConstants.CONFIG_HOSTS, "h1"); props.setProperty(RpcClientConfigurationConstants.CONFIG_HOSTS_PREFIX + "h1", hostname + ":" + port.intValue()); props.setProperty(RpcClientConfigurationConstants.CONFIG_BATCH_SIZE, batchSize.toString()); return props; } } ================================================ FILE: flink-learning-connectors/flink-learning-connectors-flume/src/main/resources/application.properties ================================================ kafka.brokers=localhost:9092 kafka.group.id=zhisheng kafka.zookeeper.connect=localhost:2181 metrics.topic=zhisheng stream.parallelism=4 stream.sink.parallelism=4 stream.default.parallelism=4 stream.checkpoint.interval=1000 stream.checkpoint.enable=false ================================================ FILE: flink-learning-connectors/flink-learning-connectors-flume/src/main/resources/logback.xml ================================================ %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n ================================================ FILE: flink-learning-connectors/flink-learning-connectors-gcp-pubsub/README.md ================================================ ### Flink connector gcp pubsub 关于 gcp pubsub 的介绍 https://cloud.google.com/pubsub/?hl=zh-cn ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-11-23-130544.jpg) 添加依赖: ```xml org.apache.flink flink-connector-gcp-pubsub_${scala.binary.version} ${flink.version} ``` ================================================ FILE: flink-learning-connectors/flink-learning-connectors-gcp-pubsub/pom.xml ================================================ flink-learning-connectors com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 flink-learning-connectors-gcp-pubsub org.apache.flink flink-connector-gcp-pubsub ${flink-connector-gcp-pubsub.version} org.apache.maven.plugins maven-shade-plugin 3.1.0 false package shade com.zhisheng.connectors.gcp.pubsub.Main reference.conf *:*:*:* META-INF/*.SF META-INF/*.DSA META-INF/*.RSA ================================================ FILE: flink-learning-connectors/flink-learning-connectors-gcp-pubsub/src/main/java/com/zhisheng/connectors/gcp/pubsub/IntegerSerializer.java ================================================ package com.zhisheng.connectors.gcp.pubsub; import com.google.pubsub.v1.PubsubMessage; import org.apache.flink.api.common.serialization.SerializationSchema; import org.apache.flink.api.common.typeinfo.TypeInformation; import org.apache.flink.streaming.connectors.gcp.pubsub.common.PubSubDeserializationSchema; import java.io.IOException; import java.math.BigInteger; /** * Desc: Deserialization schema to deserialize messages produced by PubSubPublisher * Created by zhisheng on 2019/11/23 下午9:22 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class IntegerSerializer implements PubSubDeserializationSchema, SerializationSchema { @Override public Integer deserialize(PubsubMessage message) throws IOException { return new BigInteger(message.getData().toByteArray()).intValue(); } @Override public boolean isEndOfStream(Integer integer) { return false; } @Override public TypeInformation getProducedType() { return TypeInformation.of(Integer.class); } @Override public byte[] serialize(Integer integer) { return BigInteger.valueOf(integer).toByteArray(); } } ================================================ FILE: flink-learning-connectors/flink-learning-connectors-gcp-pubsub/src/main/java/com/zhisheng/connectors/gcp/pubsub/Main.java ================================================ package com.zhisheng.connectors.gcp.pubsub; import com.zhisheng.common.constant.PropertiesConstants; import com.zhisheng.common.utils.ExecutionEnvUtil; import lombok.extern.slf4j.Slf4j; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.connectors.gcp.pubsub.PubSubSink; import org.apache.flink.streaming.connectors.gcp.pubsub.PubSubSource; /** * Desc: Flink connector gcp pubsub test * Created by zhisheng on 2019/11/23 下午9:16 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ @Slf4j public class Main { public static void main(String[] args) throws Exception { final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); ParameterTool parameterTool = ExecutionEnvUtil.createParameterTool(args); if (parameterTool.getNumberOfParameters() < 4) { System.out.println("Missing parameters!\n" + "Usage: flink run PubSub.jar --input-subscription --input-topicName --output-topicName " + "--google-project "); return; } String projectName = parameterTool.getRequired("stream.project.name"); String inputTopicName = parameterTool.getRequired("stream.input.topicName"); String subscriptionName = parameterTool.getRequired("stream.input.subscription"); String outputTopicName = parameterTool.getRequired("stream.output.topicName"); PubSubPublisherUtil pubSubPublisher = new PubSubPublisherUtil(projectName, inputTopicName); pubSubPublisher.publish(10); env.addSource(PubSubSource.newBuilder() .withDeserializationSchema(new IntegerSerializer()) .withProjectName(projectName) .withSubscriptionName(subscriptionName) .withMessageRateLimit(1) .build()) .map(Main::printAndReturn).disableChaining() .addSink(PubSubSink.newBuilder() .withSerializationSchema(new IntegerSerializer()) .withProjectName(projectName) .withTopicName(outputTopicName).build()); env.enableCheckpointing(parameterTool.getLong(PropertiesConstants.STREAM_CHECKPOINT_INTERVAL, 1000L)); env.execute("Flink connector gcp pubsub test"); } private static Integer printAndReturn(Integer i) { log.info("Processed message with payload: " + i); return i; } } ================================================ FILE: flink-learning-connectors/flink-learning-connectors-gcp-pubsub/src/main/java/com/zhisheng/connectors/gcp/pubsub/PubSubPublisherUtil.java ================================================ package com.zhisheng.connectors.gcp.pubsub; import com.google.cloud.pubsub.v1.Publisher; import com.google.protobuf.ByteString; import com.google.pubsub.v1.ProjectTopicName; import com.google.pubsub.v1.PubsubMessage; import lombok.Data; import java.math.BigInteger; /** * Desc: send PubSubMessages to a PubSub topic * Created by zhisheng on 2019/11/23 下午9:25 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ @Data public class PubSubPublisherUtil { private final String projectName; private final String topicName; /** * Publish messages with as payload a single integer. * The integers inside the messages start from 0 and increase by one for each message send. * * @param messagesCount message count */ void publish(int messagesCount) { Publisher publisher = null; try { publisher = Publisher.newBuilder(ProjectTopicName.of(projectName, topicName)).build(); for (int i = 0; i < messagesCount; i++) { ByteString messageData = ByteString.copyFrom(BigInteger.valueOf(i).toByteArray()); PubsubMessage message = PubsubMessage.newBuilder().setData(messageData).build(); publisher.publish(message).get(); System.out.println("Published message: " + i); Thread.sleep(100L); } } catch (Exception e) { throw new RuntimeException(e); } finally { try { if (publisher != null) { publisher.shutdown(); } } catch (Exception e) { } } } } ================================================ FILE: flink-learning-connectors/flink-learning-connectors-gcp-pubsub/src/main/resources/application.properties ================================================ stream.parallelism=4 stream.sink.parallelism=4 stream.default.parallelism=4 stream.checkpoint.interval=1000 stream.checkpoint.enable=false stream.project.name=zhisheng stream.input.topicName=zhisheng stream.input.subscription=zhisheng stream.output.topicName=zhisheng ================================================ FILE: flink-learning-connectors/flink-learning-connectors-gcp-pubsub/src/main/resources/logback.xml ================================================ %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n ================================================ FILE: flink-learning-connectors/flink-learning-connectors-hbase/README.md ================================================ ### Flink connector HBase https://blog.csdn.net/aA518189/article/details/86544844 https://blog.csdn.net/aA518189/article/details/85298889 ================================================ FILE: flink-learning-connectors/flink-learning-connectors-hbase/flink-learning-connectors-hbase-1.4/pom.xml ================================================ flink-learning-connectors-hbase com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 flink-learning-connectors-hbase1.4 org.apache.flink flink-connector-hbase-2.2 ${flink-connector-hbase.version} org.apache.maven.plugins maven-shade-plugin 3.1.0 false package shade com.zhisheng.connectors.hbase.HBaseWriteMain reference.conf *:*:*:* META-INF/*.SF META-INF/*.DSA META-INF/*.RSA ================================================ FILE: flink-learning-connectors/flink-learning-connectors-hbase/flink-learning-connectors-hbase-2.2/pom.xml ================================================ flink-learning-connectors-hbase com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 flink-learning-connectors-hbase2.2 org.apache.flink flink-connector-hbase-2.2 ${flink-connector-hbase.version} org.apache.maven.plugins maven-shade-plugin 3.1.0 false package shade com.zhisheng.connectors.hbase.HBaseWriteMain reference.conf *:*:*:* META-INF/*.SF META-INF/*.DSA META-INF/*.RSA ================================================ FILE: flink-learning-connectors/flink-learning-connectors-hbase/flink-learning-connectors-hbase-2.2/src/main/java/hbase/HBaseStreamWriteMain.java ================================================ package com.zhisheng.connectors.hbase; import com.zhisheng.common.utils.ExecutionEnvUtil; import com.zhisheng.common.utils.KafkaConfigUtil; import lombok.extern.slf4j.Slf4j; import org.apache.flink.api.common.io.OutputFormat; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.configuration.Configuration; import org.apache.flink.streaming.api.datastream.DataStream; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.api.functions.source.SourceFunction; import org.apache.hadoop.hbase.HBaseConfiguration; import org.apache.hadoop.hbase.HColumnDescriptor; import org.apache.hadoop.hbase.HTableDescriptor; import org.apache.hadoop.hbase.TableName; import org.apache.hadoop.hbase.client.*; import org.apache.hadoop.hbase.util.Bytes; import java.io.IOException; import java.util.Properties; import static com.zhisheng.connectors.hbase.constant.HBaseConstant.*; /** * Desc: 读取流数据,然后写入到 HBase * Created by zhisheng on 2019-05-04 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ @Slf4j public class HBaseStreamWriteMain { public static void main(String[] args) throws Exception { final ParameterTool parameterTool = ExecutionEnvUtil.createParameterTool(args); StreamExecutionEnvironment env = ExecutionEnvUtil.prepare(parameterTool); Properties props = KafkaConfigUtil.buildKafkaProps(parameterTool); /*env.addSource(new FlinkKafkaConsumer<>( parameterTool.get(METRICS_TOPIC), //这个 kafka topic 需要和上面的工具类的 topic 一致 new SimpleStringSchema(), props)) .writeUsingOutputFormat(new HBaseOutputFormat());*/ DataStream dataStream = env.addSource(new SourceFunction() { private static final long serialVersionUID = 1L; private volatile boolean isRunning = true; @Override public void run(SourceContext out) throws Exception { while (isRunning) { out.collect(String.valueOf(Math.floor(Math.random() * 100))); } } @Override public void cancel() { isRunning = false; } }); dataStream.writeUsingOutputFormat(new HBaseOutputFormat()); env.execute("Flink HBase connector sink"); } private static class HBaseOutputFormat implements OutputFormat { private org.apache.hadoop.conf.Configuration configuration; private Connection connection = null; private String taskNumber = null; private Table table = null; private int rowNumber = 0; @Override public void configure(Configuration parameters) { configuration = HBaseConfiguration.create(); configuration.set(HBASE_ZOOKEEPER_QUORUM, ExecutionEnvUtil.PARAMETER_TOOL.get(HBASE_ZOOKEEPER_QUORUM)); configuration.set(HBASE_ZOOKEEPER_PROPERTY_CLIENTPORT, ExecutionEnvUtil.PARAMETER_TOOL.get(HBASE_ZOOKEEPER_PROPERTY_CLIENTPORT)); configuration.set(HBASE_RPC_TIMEOUT, ExecutionEnvUtil.PARAMETER_TOOL.get(HBASE_RPC_TIMEOUT)); configuration.set(HBASE_CLIENT_OPERATION_TIMEOUT, ExecutionEnvUtil.PARAMETER_TOOL.get(HBASE_CLIENT_OPERATION_TIMEOUT)); configuration.set(HBASE_CLIENT_SCANNER_TIMEOUT_PERIOD, ExecutionEnvUtil.PARAMETER_TOOL.get(HBASE_CLIENT_SCANNER_TIMEOUT_PERIOD)); } @Override public void open(int taskNumber, int numTasks) throws IOException { connection = ConnectionFactory.createConnection(configuration); TableName tableName = TableName.valueOf(ExecutionEnvUtil.PARAMETER_TOOL.get(HBASE_TABLE_NAME)); Admin admin = connection.getAdmin(); if (!admin.tableExists(tableName)) { //检查是否有该表,如果没有,创建 log.info("==============不存在表 = {}", tableName); admin.createTable(new HTableDescriptor(TableName.valueOf(ExecutionEnvUtil.PARAMETER_TOOL.get(HBASE_TABLE_NAME))) .addFamily(new HColumnDescriptor(ExecutionEnvUtil.PARAMETER_TOOL.get(HBASE_COLUMN_NAME)))); } table = connection.getTable(tableName); this.taskNumber = String.valueOf(taskNumber); } @Override public void writeRecord(String record) throws IOException { Put put = new Put(Bytes.toBytes(taskNumber + rowNumber)); put.addColumn(Bytes.toBytes(ExecutionEnvUtil.PARAMETER_TOOL.get(HBASE_COLUMN_NAME)), Bytes.toBytes("zhisheng"), Bytes.toBytes(String.valueOf(rowNumber))); rowNumber++; table.put(put); } @Override public void close() throws IOException { table.close(); connection.close(); } } } ================================================ FILE: flink-learning-connectors/flink-learning-connectors-hbase/flink-learning-connectors-hbase-2.2/src/main/java/hbase/Main.java ================================================ package com.zhisheng.connectors.hbase; import com.zhisheng.common.utils.ExecutionEnvUtil; import com.zhisheng.common.utils.KafkaConfigUtil; import lombok.extern.slf4j.Slf4j; import org.apache.commons.net.ntp.TimeStamp; import org.apache.flink.api.common.functions.MapFunction; import org.apache.flink.api.common.serialization.SimpleStringSchema; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.streaming.api.datastream.DataStreamSource; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumer; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.HBaseConfiguration; import org.apache.hadoop.hbase.HColumnDescriptor; import org.apache.hadoop.hbase.HTableDescriptor; import org.apache.hadoop.hbase.TableName; import org.apache.hadoop.hbase.client.*; import org.apache.hadoop.hbase.util.Bytes; import java.io.IOException; import java.util.Date; import java.util.Properties; import static com.zhisheng.common.constant.PropertiesConstants.METRICS_TOPIC; import static com.zhisheng.connectors.hbase.constant.HBaseConstant.*; /** * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ @Slf4j public class Main { //表名 private static TableName HBASE_TABLE_NAME = TableName.valueOf("zhisheng_stream"); //列族 private static final String INFO_STREAM = "info_stream"; //列名 private static final String BAR_STREAM = "bar_stream"; public static void main(String[] args) throws Exception { final ParameterTool parameterTool = ExecutionEnvUtil.createParameterTool(args); StreamExecutionEnvironment env = ExecutionEnvUtil.prepare(parameterTool); Properties props = KafkaConfigUtil.buildKafkaProps(parameterTool); DataStreamSource data = env.addSource(new FlinkKafkaConsumer<>( parameterTool.get(METRICS_TOPIC), //这个 kafka topic 需要和上面的工具类的 topic 一致 new SimpleStringSchema(), props)); data.map(new MapFunction() { @Override public Object map(String string) throws Exception { writeEventToHbase(string, parameterTool); return string; } }).print(); env.execute("flink learning connectors hbase"); } private static void writeEventToHbase(String string, ParameterTool parameterTool) throws IOException { Configuration configuration = HBaseConfiguration.create(); configuration.set(HBASE_ZOOKEEPER_QUORUM, parameterTool.get(HBASE_ZOOKEEPER_QUORUM)); configuration.set(HBASE_ZOOKEEPER_PROPERTY_CLIENTPORT, parameterTool.get(HBASE_ZOOKEEPER_PROPERTY_CLIENTPORT)); configuration.set(HBASE_RPC_TIMEOUT, parameterTool.get(HBASE_RPC_TIMEOUT)); configuration.set(HBASE_CLIENT_OPERATION_TIMEOUT, parameterTool.get(HBASE_CLIENT_OPERATION_TIMEOUT)); configuration.set(HBASE_CLIENT_SCANNER_TIMEOUT_PERIOD, parameterTool.get(HBASE_CLIENT_SCANNER_TIMEOUT_PERIOD)); Connection connect = ConnectionFactory.createConnection(configuration); Admin admin = connect.getAdmin(); if (!admin.tableExists(HBASE_TABLE_NAME)) { //检查是否有该表,如果没有,创建 admin.createTable(new HTableDescriptor(HBASE_TABLE_NAME).addFamily(new HColumnDescriptor(INFO_STREAM))); } Table table = connect.getTable(HBASE_TABLE_NAME); TimeStamp ts = new TimeStamp(new Date()); Date date = ts.getDate(); Put put = new Put(Bytes.toBytes(date.getTime())); put.addColumn(Bytes.toBytes(INFO_STREAM), Bytes.toBytes("test"), Bytes.toBytes(string)); table.put(put); table.close(); connect.close(); } } ================================================ FILE: flink-learning-connectors/flink-learning-connectors-hbase/flink-learning-connectors-hbase-2.2/src/main/java/hbase/constant/HBaseConstant.java ================================================ package com.zhisheng.connectors.hbase.constant; public class HBaseConstant { public static final String HBASE_ZOOKEEPER_QUORUM = "hbase.zookeeper.quorum"; public static final String HBASE_CLIENT_RETRIES_NUMBER = "hbase.client.retries.number"; public static final String HBASE_MASTER_INFO_PORT = "hbase.master.info.port"; public static final String HBASE_ZOOKEEPER_PROPERTY_CLIENTPORT = "hbase.zookeeper.property.clientPort"; public static final String HBASE_RPC_TIMEOUT = "hbase.rpc.timeout"; public static final String HBASE_CLIENT_OPERATION_TIMEOUT = "hbase.client.operation.timeout"; public static final String HBASE_CLIENT_SCANNER_TIMEOUT_PERIOD = "hbase.client.scanner.timeout.period"; public static final String HBASE_TABLE_NAME = "hbase.table.name"; public static final String HBASE_COLUMN_NAME = "hbase.column.name"; } ================================================ FILE: flink-learning-connectors/flink-learning-connectors-hbase/flink-learning-connectors-hbase-2.2/src/main/resources/application.properties ================================================ kafka.brokers=localhost:9092 kafka.group.id=zhisheng kafka.zookeeper.connect=localhost:2181 metrics.topic=zhisheng stream.parallelism=4 stream.sink.parallelism=4 stream.default.parallelism=4 stream.checkpoint.interval=1000 stream.checkpoint.enable=false # HBase hbase.zookeeper.quorum=localhost:2181 hbase.client.retries.number=1 hbase.master.info.port=-1 hbase.zookeeper.property.clientPort=2081 hbase.rpc.timeout=30000 hbase.client.operation.timeout=30000 hbase.client.scanner.timeout.period=30000 # HBase table name hbase.table.name=zhisheng_stream hbase.column.name=info_stream ================================================ FILE: flink-learning-connectors/flink-learning-connectors-hbase/flink-learning-connectors-hbase-2.2/src/main/resources/logback.xml ================================================ %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n ================================================ FILE: flink-learning-connectors/flink-learning-connectors-hbase/pom.xml ================================================ flink-learning-connectors com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 flink-learning-connectors-hbase pom flink-learning-connectors-hbase-1.4 flink-learning-connectors-hbase-2.2 org.apache.hadoop hadoop-common 3.4.0 org.apache.hadoop hadoop-mapreduce-client-core 2.6.0 org.apache.flink flink-hadoop-compatibility_${scala.binary.version} ${flink.version} ================================================ FILE: flink-learning-connectors/flink-learning-connectors-hdfs/README.md ================================================ 模版项目,不做任何代码编写,方便创建新的 module 时复制 ================================================ FILE: flink-learning-connectors/flink-learning-connectors-hdfs/pom.xml ================================================ flink-learning-connectors com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 flink-learning-connectors-hdfs org.apache.maven.plugins maven-shade-plugin 3.1.0 false package shade com.zhisheng.connectors.hdfs.Main reference.conf *:*:*:* META-INF/*.SF META-INF/*.DSA META-INF/*.RSA ================================================ FILE: flink-learning-connectors/flink-learning-connectors-hdfs/src/main/java/com/zhisheng/connectors/hdfs/Main.java ================================================ package com.zhisheng.connectors.hdfs; import com.zhisheng.common.model.MetricEvent; import com.zhisheng.common.utils.ExecutionEnvUtil; import com.zhisheng.common.utils.KafkaConfigUtil; import lombok.extern.slf4j.Slf4j; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.streaming.api.datastream.DataStreamSource; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; /** * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ @Slf4j public class Main { public static void main(String[] args) throws Exception { final ParameterTool parameterTool = ExecutionEnvUtil.createParameterTool(args); StreamExecutionEnvironment env = ExecutionEnvUtil.prepare(parameterTool); DataStreamSource data = KafkaConfigUtil.buildSource(env); env.execute("flink learning connectors hdfs"); } } ================================================ FILE: flink-learning-connectors/flink-learning-connectors-hdfs/src/main/resources/application.properties ================================================ kafka.brokers=localhost:9092 kafka.group.id=zhisheng kafka.zookeeper.connect=localhost:2181 metrics.topic=zhisheng stream.parallelism=4 stream.sink.parallelism=4 stream.default.parallelism=4 stream.checkpoint.interval=1000 stream.checkpoint.enable=false ================================================ FILE: flink-learning-connectors/flink-learning-connectors-hdfs/src/main/resources/logback.xml ================================================ %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n ================================================ FILE: flink-learning-connectors/flink-learning-connectors-hive/README.md ================================================ ## Flink connector Hive Flink 1.9 版本开始支持 Hive Connector https://ci.apache.org/projects/flink/flink-docs-release-1.9/dev/table/hive/ ================================================ FILE: flink-learning-connectors/flink-learning-connectors-hive/flink-learning-connectors-hive-1.2.2/pom.xml ================================================ flink-learning-connectors-hive com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 flink-learning-connectors-hive-1.2.2 11 11 org.apache.flink flink-sql-connector-hive-3.1.3_${scala.binary.version} ${flink.version} org.apache.maven.plugins maven-shade-plugin 3.1.0 false package shade com.zhisheng.connectors.hive.Sink2HiveMain reference.conf *:*:*:* META-INF/*.SF META-INF/*.DSA META-INF/*.RSA ================================================ FILE: flink-learning-connectors/flink-learning-connectors-hive/flink-learning-connectors-hive-2.2.0/pom.xml ================================================ flink-learning-connectors-hive com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 flink-learning-connectors-hive-2.2.0 org.apache.flink flink-sql-connector-hive-3.1.3_${scala.binary.version} ${flink.version} org.apache.maven.plugins maven-shade-plugin 3.1.0 false package shade com.zhisheng.connectors.hive.Sink2HiveMain reference.conf *:*:*:* META-INF/*.SF META-INF/*.DSA META-INF/*.RSA ================================================ FILE: flink-learning-connectors/flink-learning-connectors-hive/flink-learning-connectors-hive-2.3.6/pom.xml ================================================ flink-learning-connectors-hive com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 flink-learning-connectors-hive-2.3.6 org.apache.flink flink-sql-connector-hive-3.1.3_${scala.binary.version} ${flink.version} org.apache.maven.plugins maven-shade-plugin 3.1.0 false package shade com.zhisheng.connectors.hive.Sink2HiveMain reference.conf *:*:*:* META-INF/*.SF META-INF/*.DSA META-INF/*.RSA ================================================ FILE: flink-learning-connectors/flink-learning-connectors-hive/flink-learning-connectors-hive-3.1.2/pom.xml ================================================ flink-learning-connectors-hive com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 flink-learning-connectors-hive-3.1.2 org.apache.flink flink-sql-connector-hive-3.1.3_${scala.binary.version} ${flink.version} org.apache.maven.plugins maven-shade-plugin 3.1.0 false package shade com.zhisheng.connectors.hive.Sink2HiveMain reference.conf *:*:*:* META-INF/*.SF META-INF/*.DSA META-INF/*.RSA ================================================ FILE: flink-learning-connectors/flink-learning-connectors-hive/pom.xml ================================================ flink-learning-connectors com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 flink-learning-connectors-hive pom flink-learning-connectors-hive-1.2.2 flink-learning-connectors-hive-2.2.0 flink-learning-connectors-hive-2.3.6 flink-learning-connectors-hive-3.1.2 ================================================ FILE: flink-learning-connectors/flink-learning-connectors-influxdb/README.md ================================================ ### Flink connector influxDB ================================================ FILE: flink-learning-connectors/flink-learning-connectors-influxdb/pom.xml ================================================ flink-learning-connectors com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 flink-learning-connectors-influxdb 2.7 org.influxdb influxdb-java ${influxdb-client.version} org.apache.maven.plugins maven-shade-plugin 3.1.0 false package shade com.zhisheng.connectors.influxdb.Main reference.conf *:*:*:* META-INF/*.SF META-INF/*.DSA META-INF/*.RSA ================================================ FILE: flink-learning-connectors/flink-learning-connectors-influxdb/src/main/java/com/zhisheng/connectors/influxdb/InfluxDBConfig.java ================================================ package com.zhisheng.connectors.influxdb; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import java.io.Serializable; import java.util.concurrent.TimeUnit; /** * Desc: InfluxDB 配置 * Created by zhisheng on 2019-05-01 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ @Data @Builder @NoArgsConstructor @AllArgsConstructor public class InfluxDBConfig implements Serializable { private static final long serialVersionUID = 1L; /** * 默认每次批处理的数据条数为 2000 条 */ private static final int DEFAULT_BATCH_ACTIONS = 2000; /** * 默认每隔 100 */ private static final int DEFAULT_FLUSH_DURATION = 100; /** * 数据库地址 */ private String url; /** * 数据库用户名 */ private String username; /** * 数据库密码 */ private String password; /** * 数据库名 */ private String database; /** * batch */ private int batchActions = DEFAULT_BATCH_ACTIONS; /** * flush duration */ private int flushDuration = DEFAULT_FLUSH_DURATION; /** * 单位 */ private TimeUnit flushDurationTimeUnit = TimeUnit.MILLISECONDS; /** * 是否开启 GZIP 压缩 */ private boolean enableGzip = false; /** * 是否创建数据库 */ private boolean createDatabase = false; } ================================================ FILE: flink-learning-connectors/flink-learning-connectors-influxdb/src/main/java/com/zhisheng/connectors/influxdb/InfluxDBSink.java ================================================ package com.zhisheng.connectors.influxdb; import com.zhisheng.common.model.MetricEvent; import org.apache.flink.configuration.Configuration; import org.apache.flink.streaming.api.functions.sink.RichSinkFunction; import org.apache.flink.util.CollectionUtil; import org.apache.flink.util.Preconditions; import org.apache.flink.util.StringUtils; import org.influxdb.InfluxDB; import org.influxdb.InfluxDBFactory; import org.influxdb.dto.Point; import java.util.concurrent.TimeUnit; /** * Desc: InfluxDB sink * Created by zhisheng on 2019-05-01 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class InfluxDBSink extends RichSinkFunction { private transient InfluxDB influxDBClient; private final InfluxDBConfig influxDBConfig; public InfluxDBSink(InfluxDBConfig influxDBConfig) { this.influxDBConfig = Preconditions.checkNotNull(influxDBConfig, "InfluxDB client config should not be null"); } @Override public void open(Configuration parameters) throws Exception { super.open(parameters); influxDBClient = InfluxDBFactory.connect(influxDBConfig.getUrl(), influxDBConfig.getUsername(), influxDBConfig.getPassword()); if (!influxDBClient.databaseExists(influxDBConfig.getDatabase())) { if(influxDBConfig.isCreateDatabase()) { influxDBClient.createDatabase(influxDBConfig.getDatabase()); } else { throw new RuntimeException("This " + influxDBConfig.getDatabase() + " database does not exist!"); } } influxDBClient.setDatabase(influxDBConfig.getDatabase()); if (influxDBConfig.getBatchActions() > 0) { influxDBClient.enableBatch(influxDBConfig.getBatchActions(), influxDBConfig.getFlushDuration(), influxDBConfig.getFlushDurationTimeUnit()); } if (influxDBConfig.isEnableGzip()) { influxDBClient.enableGzip(); } } @Override public void invoke(MetricEvent metricEvent, Context context) throws Exception { if (StringUtils.isNullOrWhitespaceOnly(metricEvent.getName())) { throw new RuntimeException("No measurement defined"); } Point.Builder builder = Point.measurement(metricEvent.getName()) .time(metricEvent.getTimestamp(), TimeUnit.MILLISECONDS); if (!CollectionUtil.isNullOrEmpty(metricEvent.getFields())) { builder.fields(metricEvent.getFields()); } if (!CollectionUtil.isNullOrEmpty(metricEvent.getTags())) { builder.tag(metricEvent.getTags()); } Point point = builder.build(); influxDBClient.write(point); } @Override public void close() { if (influxDBClient.isBatchEnabled()) { influxDBClient.disableBatch(); } influxDBClient.close(); } } ================================================ FILE: flink-learning-connectors/flink-learning-connectors-influxdb/src/main/java/com/zhisheng/connectors/influxdb/Main.java ================================================ package com.zhisheng.connectors.influxdb; import com.zhisheng.common.model.MetricEvent; import com.zhisheng.common.utils.ExecutionEnvUtil; import com.zhisheng.common.utils.KafkaConfigUtil; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.streaming.api.datastream.DataStreamSource; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; public class Main { public static void main(String[] args) throws Exception { ParameterTool parameterTool = ExecutionEnvUtil.PARAMETER_TOOL; StreamExecutionEnvironment env = ExecutionEnvUtil.prepare(parameterTool); DataStreamSource data = KafkaConfigUtil.buildSource(env); //请将下面的这些字段弄成常量 InfluxDBConfig config = InfluxDBConfig.builder() .url(parameterTool.get("influxdb.url")) .username(parameterTool.get("influxdb.username")) .password(parameterTool.get("influxdb.password")) .database(parameterTool.get("influxdb.database")) .batchActions(parameterTool.getInt("influxdb.batchActions")) .flushDuration(parameterTool.getInt("influxdb.flushDuration")) .enableGzip(parameterTool.getBoolean("influxdb.enableGzip")) .createDatabase(parameterTool.getBoolean("influxdb.createDatabase")) .build(); data.addSink(new InfluxDBSink(config)); env.execute("flink InfluxDB connector"); } } ================================================ FILE: flink-learning-connectors/flink-learning-connectors-influxdb/src/main/resources/application.properties ================================================ kafka.brokers=localhost:9092 kafka.group.id=zhisheng kafka.zookeeper.connect=localhost:2181 metrics.topic=zhisheng stream.parallelism=4 stream.sink.parallelism=4 stream.default.parallelism=4 stream.checkpoint.interval=1000 stream.checkpoint.enable=false # influxDB influxdb.url=http://localhost:8086 influxdb.username=root influxdb.password=root influxdb.database=metric-db influxdb.batchActions=2000 influxdb.flushDuration=100 influxdb.flushDurationTimeUnit=MILLISECONDS influxdb.enableGzip=false influxdb.createDatabase=false ================================================ FILE: flink-learning-connectors/flink-learning-connectors-influxdb/src/main/resources/logback.xml ================================================ %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n ================================================ FILE: flink-learning-connectors/flink-learning-connectors-jdbc/pom.xml ================================================ flink-learning-connectors com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 flink-learning-connectors-jdbc org.apache.flink flink-connector-jdbc ${flink-connector-jdbc.version} org.apache.maven.plugins maven-shade-plugin 3.1.0 false package shade com.zhisheng.connectors.jdbc.Main reference.conf *:*:*:* META-INF/*.SF META-INF/*.DSA META-INF/*.RSA ================================================ FILE: flink-learning-connectors/flink-learning-connectors-jdbc/src/main/java/com/zhisheng/connectors/jdbc/Main.java ================================================ package com.zhisheng.connectors.jdbc; /** * Desc: sink to mysql * Created by zhisheng on 2019-10-28 18:49 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class Main { public static void main(String[] args) { } } ================================================ FILE: flink-learning-connectors/flink-learning-connectors-jdbc/src/main/resources/application.properties ================================================ kafka.brokers=localhost:9092 kafka.group.id=zhisheng kafka.zookeeper.connect=localhost:2181 stream.parallelism=1 stream.checkpoint.interval=1000 stream.checkpoint.enable=false # JDBC ================================================ FILE: flink-learning-connectors/flink-learning-connectors-jdbc/src/main/resources/logback.xml ================================================ %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n ================================================ FILE: flink-learning-connectors/flink-learning-connectors-kafka/README.md ================================================ ## Flink connector Kafka [http://www.54tianzhisheng.cn/2019/01/06/Flink-Kafka-sink/](http://www.54tianzhisheng.cn/2019/01/06/Flink-Kafka-sink/) ================================================ FILE: flink-learning-connectors/flink-learning-connectors-kafka/pom.xml ================================================ flink-learning-connectors com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 flink-learning-connectors-kafka org.apache.maven.plugins maven-shade-plugin 3.1.0 false package shade com.zhisheng.connectors.kafka.Main reference.conf *:*:*:* META-INF/*.SF META-INF/*.DSA META-INF/*.RSA ================================================ FILE: flink-learning-connectors/flink-learning-connectors-kafka/src/main/java/com/zhisheng/connectors/kafka/FlinkKafkaConsumerTest1.java ================================================ package com.zhisheng.connectors.kafka; import com.zhisheng.common.model.MetricEvent; import com.zhisheng.common.schemas.MetricSchema; import com.zhisheng.common.utils.ExecutionEnvUtil; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.streaming.api.datastream.DataStreamSource; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumer; import java.util.Arrays; import java.util.List; import java.util.Properties; import static com.zhisheng.common.utils.KafkaConfigUtil.buildKafkaProps; /** * Desc: Flink 消费 kafka 多个 topic、Pattern 类型的 topic * Created by zhisheng on 2019-09-22 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class FlinkKafkaConsumerTest1 { public static void main(String[] args) throws Exception { final ParameterTool parameterTool = ExecutionEnvUtil.createParameterTool(args); StreamExecutionEnvironment env = ExecutionEnvUtil.prepare(parameterTool); Properties props = buildKafkaProps(parameterTool); //kafka topic list List topics = Arrays.asList(parameterTool.get("metrics.topic"), parameterTool.get("logs.topic")); FlinkKafkaConsumer consumer = new FlinkKafkaConsumer<>(topics, new MetricSchema(), props); //kafka topic Pattern //FlinkKafkaConsumer consumer = new FlinkKafkaConsumer<>(java.utils.regex.Pattern.compile("test-topic-[0-9]"), new MetricSchema(), props); // consumer.setStartFromLatest(); // consumer.setStartFromEarliest() DataStreamSource data = env.addSource(consumer); data.print(); env.execute("flink kafka connector test"); } } ================================================ FILE: flink-learning-connectors/flink-learning-connectors-kafka/src/main/java/com/zhisheng/connectors/kafka/FlinkKafkaConsumerTest2.java ================================================ package com.zhisheng.connectors.kafka; import com.zhisheng.common.utils.ExecutionEnvUtil; import org.apache.flink.api.common.serialization.SimpleStringSchema; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumer; import java.util.Properties; import static com.zhisheng.common.utils.KafkaConfigUtil.buildKafkaProps; /** * Desc: * Created by zhisheng on 2020-03-13 15:20 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class FlinkKafkaConsumerTest2 { public static void main(String[] args) throws Exception { final ParameterTool parameterTool = ExecutionEnvUtil.createParameterTool(args); StreamExecutionEnvironment env = ExecutionEnvUtil.prepare(parameterTool); env.setParallelism(1); Properties props = buildKafkaProps(parameterTool); FlinkKafkaConsumer consumer = new FlinkKafkaConsumer<>("user_behavior_sink", new SimpleStringSchema(), props); env.addSource(consumer).print(); env.execute("flink kafka connector test"); } } ================================================ FILE: flink-learning-connectors/flink-learning-connectors-kafka/src/main/java/com/zhisheng/connectors/kafka/FlinkKafkaProducerTest1.java ================================================ package com.zhisheng.connectors.kafka; import com.zhisheng.common.utils.ExecutionEnvUtil; import org.apache.flink.api.common.serialization.SimpleStringSchema; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.api.functions.source.SourceFunction; import org.apache.flink.streaming.connectors.kafka.FlinkKafkaProducer; import java.time.Instant; import java.util.Random; import java.util.TimeZone; /** * Desc: Flink 发送数据到 topic * Created by zhisheng on 2019-09-22 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class FlinkKafkaProducerTest1 { public static final Random random = new Random(); public static void main(String[] args) throws Exception { final ParameterTool parameterTool = ExecutionEnvUtil.createParameterTool(args); StreamExecutionEnvironment env = ExecutionEnvUtil.prepare(parameterTool); env.setParallelism(1); env.addSource(new SourceFunction() { @Override public void run(SourceContext context) throws Exception { while (true) { TimeZone tz = TimeZone.getTimeZone("Asia/Shanghai"); Instant instant = Instant.ofEpochMilli(System.currentTimeMillis() + tz.getOffset(System.currentTimeMillis())); String outline = String.format( "{\"user_id\": \"%s\", \"item_id\":\"%s\", \"category_id\": \"%s\", \"behavior\": \"%s\", \"ts\": \"%s\"}", random.nextInt(10), random.nextInt(100), random.nextInt(1000), "pv", instant.toString()); context.collect(outline); Thread.sleep(200); } } @Override public void cancel() { } }) .addSink(new FlinkKafkaProducer<>( "localhost:9092", "user_behavior", new SimpleStringSchema() )).name("flink-connectors-kafka"); env.execute("flink kafka connector test"); } } ================================================ FILE: flink-learning-connectors/flink-learning-connectors-kafka/src/main/java/com/zhisheng/connectors/kafka/FlinkKafkaSchemaTest1.java ================================================ package com.zhisheng.connectors.kafka; import com.zhisheng.common.model.MetricEvent; import com.zhisheng.common.schemas.MetricSchema; import com.zhisheng.common.utils.ExecutionEnvUtil; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.streaming.api.datastream.DataStreamSource; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumer; import org.apache.flink.streaming.connectors.kafka.internals.KafkaDeserializationSchemaWrapper; import java.util.Collections; import java.util.List; import java.util.Properties; import static com.zhisheng.common.utils.KafkaConfigUtil.buildKafkaProps; /** * Desc: Flink 消费 kafka topic,watermark 的修改 * Created by zhisheng on 2019-09-22 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class FlinkKafkaSchemaTest1 { public static void main(String[] args) throws Exception { final ParameterTool parameterTool = ExecutionEnvUtil.createParameterTool(args); StreamExecutionEnvironment env = ExecutionEnvUtil.prepare(parameterTool); Properties props = buildKafkaProps(parameterTool); //kafka topic list List topics = Collections.singletonList(parameterTool.get("metrics.topic")); FlinkKafkaConsumer consumer = new FlinkKafkaConsumer<>(topics, new KafkaDeserializationSchemaWrapper<>(new MetricSchema()), props); DataStreamSource data = env.addSource(consumer); data.print(); env.execute("flink kafka connector test"); } } ================================================ FILE: flink-learning-connectors/flink-learning-connectors-kafka/src/main/java/com/zhisheng/connectors/kafka/JSONKeyValueDeserializationSchemaTest.java ================================================ package com.zhisheng.connectors.kafka; import com.zhisheng.common.utils.ExecutionEnvUtil; import org.apache.flink.api.java.utils.ParameterTool; import com.fasterxml.jackson.databind.node.ObjectNode; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumer; import org.apache.flink.streaming.util.serialization.JSONKeyValueDeserializationSchema; import java.util.Properties; import static com.zhisheng.common.utils.KafkaConfigUtil.buildKafkaProps; /** * Desc: 该 Schema 可以反序列化 JSON 成对象,并包含数据的元数据信息 * Created by zhisheng on 2019-12-16 20:37 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class JSONKeyValueDeserializationSchemaTest { public static void main(String[] args) throws Exception { final ParameterTool parameterTool = ExecutionEnvUtil.createParameterTool(args); StreamExecutionEnvironment env = ExecutionEnvUtil.prepare(parameterTool); Properties props = buildKafkaProps(parameterTool); FlinkKafkaConsumer kafkaConsumer = new FlinkKafkaConsumer<>("zhisheng", new JSONKeyValueDeserializationSchema(true), //可以控制是否需要元数据字段 props); env.addSource(kafkaConsumer) .print(); //读取到的数据在 value 字段中,对应的元数据在 metadata 字段中 env.execute(); } } ================================================ FILE: flink-learning-connectors/flink-learning-connectors-kafka/src/main/java/com/zhisheng/connectors/kafka/KafkaDeserializationSchemaTest.java ================================================ package com.zhisheng.connectors.kafka; import com.zhisheng.common.model.MetricEvent; import com.zhisheng.common.schemas.KafkaMetricSchema; import com.zhisheng.common.utils.ExecutionEnvUtil; import com.zhisheng.common.utils.GsonUtil; import lombok.extern.slf4j.Slf4j; import org.apache.flink.api.common.functions.FlatMapFunction; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.shaded.jackson2.com.fasterxml.jackson.databind.node.ObjectNode; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumer; import org.apache.flink.util.Collector; import java.util.Properties; import static com.zhisheng.common.utils.KafkaConfigUtil.buildKafkaProps; /** * Desc: KafkaDeserializationSchema * Created by zhisheng on 2019-12-16 21:03 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ @Slf4j public class KafkaDeserializationSchemaTest { public static void main(String[] args) throws Exception { final ParameterTool parameterTool = ExecutionEnvUtil.createParameterTool(args); StreamExecutionEnvironment env = ExecutionEnvUtil.prepare(parameterTool); Properties props = buildKafkaProps(parameterTool); FlinkKafkaConsumer kafkaConsumer = new FlinkKafkaConsumer<>("zhisheng", new KafkaMetricSchema(true), props); env.addSource(kafkaConsumer) .flatMap(new FlatMapFunction() { @Override public void flatMap(ObjectNode jsonNodes, Collector collector) throws Exception { try { // System.out.println(jsonNodes); MetricEvent metricEvent = GsonUtil.fromJson(jsonNodes.get("value").asText(), MetricEvent.class); collector.collect(metricEvent); } catch (Exception e) { log.error("jsonNodes = {} convert to MetricEvent has an error", jsonNodes, e); } } }) .print(); env.execute(); } } ================================================ FILE: flink-learning-connectors/flink-learning-connectors-kafka/src/main/java/com/zhisheng/connectors/kafka/Main.java ================================================ package com.zhisheng.connectors.kafka; import com.zhisheng.common.model.MetricEvent; import com.zhisheng.common.schemas.MetricSchema; import com.zhisheng.common.utils.ExecutionEnvUtil; import com.zhisheng.common.utils.KafkaConfigUtil; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.streaming.api.datastream.DataStreamSource; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.connectors.kafka.FlinkKafkaProducer; /** * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class Main { public static void main(String[] args) throws Exception { final ParameterTool parameterTool = ExecutionEnvUtil.createParameterTool(args); StreamExecutionEnvironment env = ExecutionEnvUtil.prepare(parameterTool); DataStreamSource data = KafkaConfigUtil.buildSource(env); data.addSink(new FlinkKafkaProducer<>( parameterTool.get("kafka.sink.brokers"), parameterTool.get("kafka.sink.topic"), new MetricSchema() )).name("flink-connectors-kafka") .setParallelism(parameterTool.getInt("stream.sink.parallelism")); env.execute("flink learning connectors kafka"); } } ================================================ FILE: flink-learning-connectors/flink-learning-connectors-kafka/src/main/resources/application.properties ================================================ kafka.brokers=localhost:9092 kafka.group.id=metrics-group-test kafka.zookeeper.connect=xxx:2181 metrics.topic=zhisheng logs.topic=xxx kafka.sink.brokers=localhost:9092 kafka.sink.topic=metric-test stream.parallelism=5 stream.checkpoint.interval=1000 stream.checkpoint.enable=false stream.sink.parallelism=5 ================================================ FILE: flink-learning-connectors/flink-learning-connectors-kafka/src/main/resources/logback.xml ================================================ %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n ================================================ FILE: flink-learning-connectors/flink-learning-connectors-kudu/README.md ================================================ ### Flink connectors Kudu https://github.com/apache/bahir-flink/blob/master/flink-connector-kudu/README.md ================================================ FILE: flink-learning-connectors/flink-learning-connectors-kudu/pom.xml ================================================ flink-learning-connectors com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 flink-learning-connectors-kudu 1.9.0 org.apache.kudu kudu-client ${kudu.version} ================================================ FILE: flink-learning-connectors/flink-learning-connectors-mysql/README.md ================================================ ## Flink connector MySQL [http://www.54tianzhisheng.cn/2019/01/15/Flink-MySQL-sink/](http://www.54tianzhisheng.cn/2019/01/15/Flink-MySQL-sink/) ================================================ FILE: flink-learning-connectors/flink-learning-connectors-mysql/pom.xml ================================================ flink-learning-connectors com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 flink-learning-connectors-mysql mysql mysql-connector-java 5.1.34 org.apache.flink flink-connector-jdbc ${flink-connector-jdbc.version} org.apache.commons commons-dbcp2 2.1.1 org.apache.maven.plugins maven-shade-plugin 3.1.0 false package shade com.zhisheng.connectors.mysql.Main reference.conf *:*:*:* META-INF/*.SF META-INF/*.DSA META-INF/*.RSA ================================================ FILE: flink-learning-connectors/flink-learning-connectors-mysql/src/main/java/com/zhisheng/connectors/mysql/Main.java ================================================ package com.zhisheng.connectors.mysql; import com.google.common.collect.Lists; import com.zhisheng.common.utils.ExecutionEnvUtil; import com.zhisheng.common.utils.GsonUtil; import com.zhisheng.common.utils.KafkaConfigUtil; import com.zhisheng.connectors.mysql.model.Student; import com.zhisheng.connectors.mysql.sinks.SinkToMySQL; import lombok.extern.slf4j.Slf4j; import org.apache.flink.api.common.serialization.SimpleStringSchema; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.api.functions.windowing.AllWindowFunction; import org.apache.flink.streaming.api.windowing.time.Time; import org.apache.flink.streaming.api.windowing.windows.TimeWindow; import org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumer; import org.apache.flink.util.Collector; import java.util.ArrayList; import java.util.List; import java.util.Properties; import static com.zhisheng.common.constant.PropertiesConstants.*; /** * Created by zhisheng on 2019-02-17 * Blog: http://www.54tianzhisheng.cn/2019/01/09/Flink-MySQL-sink/ */ @Slf4j public class Main { public static void main(String[] args) throws Exception { final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); ParameterTool parameterTool = ExecutionEnvUtil.PARAMETER_TOOL; Properties props = KafkaConfigUtil.buildKafkaProps(parameterTool); SingleOutputStreamOperator student = env.addSource(new FlinkKafkaConsumer<>( parameterTool.get(METRICS_TOPIC), //这个 kafka topic 需要和上面的工具类的 topic 一致 new SimpleStringSchema(), props)).setParallelism(parameterTool.getInt(STREAM_PARALLELISM, 1)) .map(string -> GsonUtil.fromJson(string, Student.class)).setParallelism(4); //解析字符串成 student 对象 //timeWindowAll 并行度只能为 1 student.timeWindowAll(Time.minutes(1)).apply(new AllWindowFunction, TimeWindow>() { @Override public void apply(TimeWindow window, Iterable values, Collector> out) throws Exception { ArrayList students = Lists.newArrayList(values); if (students.size() > 0) { log.info("1 分钟内收集到 student 的数据条数是:" + students.size()); out.collect(students); } } }).addSink(new SinkToMySQL()).setParallelism(parameterTool.getInt(STREAM_SINK_PARALLELISM, 1)); env.execute("flink learning connectors mysql"); } } ================================================ FILE: flink-learning-connectors/flink-learning-connectors-mysql/src/main/java/com/zhisheng/connectors/mysql/model/Student.java ================================================ package com.zhisheng.connectors.mysql.model; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; /** * Desc: * Created by zhisheng on 2019-02-17 * Blog: http://www.54tianzhisheng.cn/tags/Flink/ */ @Data @AllArgsConstructor @NoArgsConstructor public class Student { public int id; public String name; public String password; public int age; } ================================================ FILE: flink-learning-connectors/flink-learning-connectors-mysql/src/main/java/com/zhisheng/connectors/mysql/sinks/SinkToMySQL.java ================================================ package com.zhisheng.connectors.mysql.sinks; import com.zhisheng.connectors.mysql.model.Student; import lombok.extern.slf4j.Slf4j; import org.apache.commons.dbcp2.BasicDataSource; import org.apache.flink.configuration.Configuration; import org.apache.flink.streaming.api.functions.sink.RichSinkFunction; import java.sql.Connection; import java.sql.PreparedStatement; import java.util.List; /** * Desc: 数据批量 sink 数据到 mysql * Created by yuanblog_tzs on 2019-02-17 * Blog: http://www.54tianzhisheng.cn/2019/01/09/Flink-MySQL-sink/ */ @Slf4j public class SinkToMySQL extends RichSinkFunction> { PreparedStatement ps; BasicDataSource dataSource; private Connection connection; /** * open() 方法中建立连接,这样不用每次 invoke 的时候都要建立连接和释放连接 * * @param parameters * @throws Exception */ @Override public void open(Configuration parameters) throws Exception { super.open(parameters); dataSource = new BasicDataSource(); connection = getConnection(dataSource); String sql = "insert into Student(id, name, password, age) values(?, ?, ?, ?);"; if (connection != null) { ps = this.connection.prepareStatement(sql); } } @Override public void close() throws Exception { super.close(); //关闭连接和释放资源 if (connection != null) { connection.close(); } if (ps != null) { ps.close(); } } /** * 每条数据的插入都要调用一次 invoke() 方法 * * @param value * @param context * @throws Exception */ @Override public void invoke(List value, Context context) throws Exception { if (ps == null) { return; } //遍历数据集合 for (Student student : value) { ps.setInt(1, student.getId()); ps.setString(2, student.getName()); ps.setString(3, student.getPassword()); ps.setInt(4, student.getAge()); ps.addBatch(); } int[] count = ps.executeBatch();//批量后执行 log.info("成功了插入了 {} 行数据", count.length); } private static Connection getConnection(BasicDataSource dataSource) { dataSource.setDriverClassName("com.mysql.jdbc.Driver"); //注意,替换成自己本地的 mysql 数据库地址和用户名、密码 dataSource.setUrl("jdbc:mysql://localhost:3306/test"); dataSource.setUsername("root"); dataSource.setPassword("root123456"); //设置连接池的一些参数 dataSource.setInitialSize(10); dataSource.setMaxTotal(50); dataSource.setMinIdle(2); Connection con = null; try { con = dataSource.getConnection(); log.info("创建连接池:{}", con); } catch (Exception e) { log.error("-----------mysql get connection has exception , msg = {}", e.getMessage()); } return con; } } ================================================ FILE: flink-learning-connectors/flink-learning-connectors-mysql/src/main/java/com/zhisheng/connectors/mysql/utils/KafkaUtil.java ================================================ package com.zhisheng.connectors.mysql.utils; import com.zhisheng.common.utils.GsonUtil; import com.zhisheng.connectors.mysql.model.Student; import org.apache.kafka.clients.producer.KafkaProducer; import org.apache.kafka.clients.producer.ProducerRecord; import java.util.Properties; /** * Desc: 往kafka中写数据,可以使用这个main函数进行测试 * Created by zhisheng on 2019-02-17 * Blog: http://www.54tianzhisheng.cn/2019/01/09/Flink-MySQL-sink/ */ public class KafkaUtil { public static final String broker_list = "localhost:9092"; public static final String topic = "student"; //kafka topic 需要和 flink 程序用同一个 topic public static void writeToKafka() throws InterruptedException { Properties props = new Properties(); props.put("bootstrap.servers", broker_list); props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer"); props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer"); KafkaProducer producer = new KafkaProducer(props); for (int i = 1; i <= 100; i++) { Student student = new Student(i, "zhisheng" + i, "password" + i, 18 + i); ProducerRecord record = new ProducerRecord(topic, null, null, GsonUtil.toJson(student)); producer.send(record); System.out.println("发送数据: " + GsonUtil.toJson(student)); Thread.sleep(10 * 1000); //发送一条数据 sleep 10s,相当于 1 分钟 6 条 } producer.flush(); } public static void main(String[] args) throws InterruptedException { writeToKafka(); } } ================================================ FILE: flink-learning-connectors/flink-learning-connectors-mysql/src/main/resources/application.properties ================================================ kafka.brokers=localhost:9092 kafka.group.id=metric-group kafka.zookeeper.connect=localhost:2181 metrics.topic=student stream.parallelism=4 stream.sink.parallelism=4 stream.default.parallelism=4 stream.checkpoint.interval=1000 stream.checkpoint.enable=false ================================================ FILE: flink-learning-connectors/flink-learning-connectors-mysql/src/main/resources/logback.xml ================================================ %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n ================================================ FILE: flink-learning-connectors/flink-learning-connectors-netty/pom.xml ================================================ flink-learning-connectors com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 flink-learning-connectors-netty io.netty netty-all 4.1.42.Final org.apache.maven.plugins maven-shade-plugin 3.1.0 false package shade com.zhisheng.connectors.netty.Main reference.conf *:*:*:* META-INF/*.SF META-INF/*.DSA META-INF/*.RSA ================================================ FILE: flink-learning-connectors/flink-learning-connectors-netty/src/main/java/com/zhisheng/connectors/netty/Main.java ================================================ package com.zhisheng.connectors.netty; import com.zhisheng.common.utils.ExecutionEnvUtil; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; /** * Desc: Netty connector * Created by zhisheng on 2019-05-04 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class Main { public static void main(String[] args) throws Exception { ParameterTool parameterTool = ExecutionEnvUtil.PARAMETER_TOOL; StreamExecutionEnvironment env = ExecutionEnvUtil.prepare(parameterTool); env.execute("flink netty connector"); } } ================================================ FILE: flink-learning-connectors/flink-learning-connectors-nifi/README.md ================================================ ### Flink-learning-connectors-nifi **NiFi 介绍**:An easy to use, powerful, and reliable system to process and distribute data. ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-11-24-030837.jpg) [Apache NiFi的一些学习资源](https://zhuanlan.zhihu.com/p/58168227) ================================================ FILE: flink-learning-connectors/flink-learning-connectors-nifi/pom.xml ================================================ flink-learning-connectors com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 flink-learning-connectors-nifi org.apache.flink flink-connector-nifi 1.15.4 org.apache.maven.plugins maven-shade-plugin 3.1.0 false package shade com.zhisheng.connectors.nifi reference.conf *:*:*:* META-INF/*.SF META-INF/*.DSA META-INF/*.RSA ================================================ FILE: flink-learning-connectors/flink-learning-connectors-nifi/src/main/java/com/zhisheng/connectors/nifi/NiFiSinkMain.java ================================================ package com.zhisheng.connectors.nifi; import org.apache.flink.api.common.functions.RuntimeContext; import org.apache.flink.configuration.ConfigConstants; import org.apache.flink.streaming.api.datastream.DataStreamSink; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.connectors.nifi.NiFiDataPacket; import org.apache.flink.streaming.connectors.nifi.NiFiDataPacketBuilder; import org.apache.flink.streaming.connectors.nifi.NiFiSink; import org.apache.flink.streaming.connectors.nifi.StandardNiFiDataPacket; import org.apache.nifi.remote.client.SiteToSiteClient; import org.apache.nifi.remote.client.SiteToSiteClientConfig; import java.util.HashMap; /** * Desc: nifi sink * Created by zhisheng on 2019/11/24 上午11:06 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class NiFiSinkMain { public static void main(String[] args) throws Exception { StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); SiteToSiteClientConfig clientConfig = new SiteToSiteClient.Builder() .url("http://localhost:8080/nifi") .portName("Data from Flink") .buildConfig(); DataStreamSink dataStream = env.fromElements("one", "two", "three", "four", "five", "q") .addSink(new NiFiSink<>(clientConfig, new NiFiDataPacketBuilder() { @Override public NiFiDataPacket createNiFiDataPacket(String s, RuntimeContext ctx) { return new StandardNiFiDataPacket(s.getBytes(ConfigConstants.DEFAULT_CHARSET), new HashMap()); } })); env.execute(); } } ================================================ FILE: flink-learning-connectors/flink-learning-connectors-nifi/src/main/java/com/zhisheng/connectors/nifi/NiFiSourceMain.java ================================================ package com.zhisheng.connectors.nifi; import org.apache.flink.api.common.functions.MapFunction; import org.apache.flink.streaming.api.datastream.DataStream; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.api.functions.source.SourceFunction; import org.apache.flink.streaming.connectors.nifi.NiFiDataPacket; import org.apache.flink.streaming.connectors.nifi.NiFiSource; import org.apache.nifi.remote.client.SiteToSiteClient; import org.apache.nifi.remote.client.SiteToSiteClientConfig; import java.nio.charset.Charset; /** * Desc: nifi source * Created by zhisheng on 2019/11/24 上午11:06 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class NiFiSourceMain { public static void main(String[] args) throws Exception { StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); SiteToSiteClientConfig clientConfig = new SiteToSiteClient.Builder() .url("http://localhost:8080/nifi") .portName("Data for Flink") .requestBatchCount(5) .buildConfig(); SourceFunction nifiSource = new NiFiSource(clientConfig); DataStream streamSource = env.addSource(nifiSource).setParallelism(2); DataStream dataStream = streamSource.map(new MapFunction() { @Override public String map(NiFiDataPacket value) throws Exception { return new String(value.getContent(), Charset.defaultCharset()); } }); dataStream.print(); env.execute(); } } ================================================ FILE: flink-learning-connectors/flink-learning-connectors-nifi/src/main/resources/application.properties ================================================ stream.parallelism=4 stream.sink.parallelism=4 stream.default.parallelism=4 stream.checkpoint.interval=1000 stream.checkpoint.enable=false ================================================ FILE: flink-learning-connectors/flink-learning-connectors-nifi/src/main/resources/logback.xml ================================================ %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n ================================================ FILE: flink-learning-connectors/flink-learning-connectors-pulsar/README.md ================================================ ### Flink-learning-connectors-pulsar **Pulsar 介绍**: [Pulsar 官网](https://pulsar.apache.org/) [Introduction to the Apache Pulsar pub-sub messaging platform](https://streaml.io/blog/intro-to-pulsar) **Pulsar & Flink**: [pulsar-flink](https://github.com/streamnative/pulsar-flink) [When Flink & Pulsar Come Together](https://flink.apache.org/2019/05/03/pulsar-flink.html) [Flink Pulsar Connector](https://cwiki.apache.org/confluence/display/FLINK/FLIP-72%3A+Introduce+Pulsar+Connector) **Pulsar VS Kafka**: [雅虎日本如何用 Pulsar 构建日均千亿的消息平台?](https://www.infoq.cn/article/pcfrbUj7THZH_qs9E6ZV) ================================================ FILE: flink-learning-connectors/flink-learning-connectors-pulsar/pom.xml ================================================ flink-learning-connectors com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 flink-learning-connectors-pulsar ================================================ FILE: flink-learning-connectors/flink-learning-connectors-pulsar/src/main/java/com/zhisheng/connectors/pulsar/PulsarSinkMain.java ================================================ package com.zhisheng.connectors.pulsar; /** * Desc: Pulsar sink * Created by zhisheng on 2019-11-30 10:16 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class PulsarSinkMain { public static void main(String[] args) { } } ================================================ FILE: flink-learning-connectors/flink-learning-connectors-pulsar/src/main/java/com/zhisheng/connectors/pulsar/PulsarSourceMain.java ================================================ package com.zhisheng.connectors.pulsar; /** * Desc: Pulsar Source * Created by zhisheng on 2019-11-30 10:15 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class PulsarSourceMain { public static void main(String[] args) { } } ================================================ FILE: flink-learning-connectors/flink-learning-connectors-pulsar/src/main/resources/application.properties ================================================ stream.parallelism=4 stream.sink.parallelism=4 stream.default.parallelism=4 stream.checkpoint.interval=1000 stream.checkpoint.enable=false ================================================ FILE: flink-learning-connectors/flink-learning-connectors-pulsar/src/main/resources/logback.xml ================================================ %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n ================================================ FILE: flink-learning-connectors/flink-learning-connectors-rabbitmq/README.md ================================================ ## Flink connector RabbitMQ [http://www.54tianzhisheng.cn/2019/01/20/Flink-RabbitMQ-sink/](http://www.54tianzhisheng.cn/2019/01/20/Flink-RabbitMQ-sink/) ================================================ FILE: flink-learning-connectors/flink-learning-connectors-rabbitmq/pom.xml ================================================ flink-learning-connectors com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 flink-learning-connectors-rabbitmq org.apache.flink flink-connector-rabbitmq ${flink-connector-rabbitmq.version} org.apache.maven.plugins maven-shade-plugin 3.1.0 false package shade com.zhisheng.connectors.rabbitmq.Main reference.conf *:*:*:* META-INF/*.SF META-INF/*.DSA META-INF/*.RSA ================================================ FILE: flink-learning-connectors/flink-learning-connectors-rabbitmq/src/main/java/com/zhisheng/connectors/rabbitmq/Main.java ================================================ package com.zhisheng.connectors.rabbitmq; import com.zhisheng.common.utils.ExecutionEnvUtil; import org.apache.flink.api.common.serialization.SimpleStringSchema; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.streaming.api.datastream.DataStreamSource; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.connectors.rabbitmq.RMQSource; import org.apache.flink.streaming.connectors.rabbitmq.common.RMQConnectionConfig; /** * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng * 从 rabbitmq 读取数据 */ public class Main { public static void main(String[] args) throws Exception { final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); ParameterTool parameterTool = ExecutionEnvUtil.PARAMETER_TOOL; //下面这些写死的参数可以放在配置文件中,然后通过 parameterTool 获取 final RMQConnectionConfig connectionConfig = new RMQConnectionConfig .Builder().setHost("localhost").setVirtualHost("/") .setPort(5672).setUserName("admin").setPassword("admin") .build(); DataStreamSource zhisheng = env.addSource(new RMQSource<>(connectionConfig, "zhisheng", true, new SimpleStringSchema())) .setParallelism(1); zhisheng.print(); //如果想保证 exactly-once 或 at-least-once 需要把 checkpoint 开启 // env.enableCheckpointing(10000); env.execute("flink learning connectors rabbitmq"); } } ================================================ FILE: flink-learning-connectors/flink-learning-connectors-rabbitmq/src/main/java/com/zhisheng/connectors/rabbitmq/Main1.java ================================================ package com.zhisheng.connectors.rabbitmq; import com.zhisheng.common.model.MetricEvent; import com.zhisheng.common.schemas.MetricSchema; import com.zhisheng.common.utils.ExecutionEnvUtil; import com.zhisheng.common.utils.KafkaConfigUtil; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.streaming.api.datastream.DataStreamSource; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.connectors.rabbitmq.RMQSink; import org.apache.flink.streaming.connectors.rabbitmq.common.RMQConnectionConfig; /** * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng * 从 kafka 读取数据 sink 到 rabbitmq */ public class Main1 { public static void main(String[] args) throws Exception { final ParameterTool parameterTool = ExecutionEnvUtil.createParameterTool(args); StreamExecutionEnvironment env = ExecutionEnvUtil.prepare(parameterTool); DataStreamSource data = KafkaConfigUtil.buildSource(env); final RMQConnectionConfig connectionConfig = new RMQConnectionConfig .Builder().setHost("localhost").setVirtualHost("/") .setPort(5672).setUserName("admin").setPassword("admin") .build(); //注意,换一个新的 queue,否则也会报错 data.addSink(new RMQSink<>(connectionConfig, "zhisheng001", new MetricSchema())); env.execute("flink learning connectors rabbitmq"); } } ================================================ FILE: flink-learning-connectors/flink-learning-connectors-rabbitmq/src/main/java/com/zhisheng/connectors/rabbitmq/model/EndPoint.java ================================================ package com.zhisheng.connectors.rabbitmq.model; import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; import com.rabbitmq.client.ConnectionFactory; import java.io.IOException; /** * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class EndPoint { protected Channel channel; protected Connection connection; protected String endPointName; public EndPoint(String endpointName) throws Exception { this.endPointName = endpointName; ConnectionFactory factory = new ConnectionFactory(); factory.setHost("127.0.0.1"); factory.setUsername("admin"); factory.setPassword("admin"); factory.setPort(5672); connection = factory.newConnection(); channel = connection.createChannel(); channel.queueDeclare(endpointName, false, false, false, null); } /** * 关闭channel和connection。并非必须,因为隐含是自动调用的 * * @throws IOException */ public void close() throws Exception { this.channel.close(); this.connection.close(); } } ================================================ FILE: flink-learning-connectors/flink-learning-connectors-rabbitmq/src/main/java/com/zhisheng/connectors/rabbitmq/utils/RabbitMQProducerUtil.java ================================================ package com.zhisheng.connectors.rabbitmq.utils; import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; import com.rabbitmq.client.ConnectionFactory; /** * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class RabbitMQProducerUtil { public final static String QUEUE_NAME = "zhisheng"; public static void main(String[] args) throws Exception { //创建连接工厂 ConnectionFactory factory = new ConnectionFactory(); //设置RabbitMQ相关信息 factory.setHost("localhost"); factory.setUsername("admin"); factory.setPassword("admin"); factory.setPort(5672); //创建一个新的连接 Connection connection = factory.newConnection(); //创建一个通道 Channel channel = connection.createChannel(); // 声明一个队列 // channel.queueDeclare(QUEUE_NAME, false, false, false, null); //发送消息到队列中 String message = "Hello zhisheng"; for (int i = 0; i < 1000; i++) { channel.basicPublish("", QUEUE_NAME, null, (message + i).getBytes("UTF-8")); System.out.println("Producer Send +'" + message + i); } //关闭通道和连接 channel.close(); connection.close(); } } ================================================ FILE: flink-learning-connectors/flink-learning-connectors-rabbitmq/src/main/resources/application.properties ================================================ kafka.brokers=localhost:9092 kafka.group.id=zhisheng kafka.zookeeper.connect=localhost:2181 metrics.topic=student stream.parallelism=4 stream.sink.parallelism=4 stream.default.parallelism=4 stream.checkpoint.interval=1000 stream.checkpoint.enable=false rmq.host=localhost rmq.port=5672 rmq.user=admin rmq.password=admin ================================================ FILE: flink-learning-connectors/flink-learning-connectors-rabbitmq/src/main/resources/logback.xml ================================================ %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n ================================================ FILE: flink-learning-connectors/flink-learning-connectors-redis/README.md ================================================ ### Flink connector Redis 利用自带的 Redis Connector 从 Kafka 中读取数据,然后写入到 Redis。 Redis 分三种情况: + 单机 Redis + Redis 集群 + Redis Sentinels ================================================ FILE: flink-learning-connectors/flink-learning-connectors-redis/pom.xml ================================================ flink-learning-connectors com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 flink-learning-connectors-redis org.apache.flink flink-connector-redis_2.10 1.1.5 redis.clients jedis 2.9.0 org.apache.maven.plugins maven-shade-plugin 3.1.0 false package shade com.zhisheng.connectors.redis.Main reference.conf *:*:*:* META-INF/*.SF META-INF/*.DSA META-INF/*.RSA ================================================ FILE: flink-learning-connectors/flink-learning-connectors-redis/src/main/java/com/zhisheng/connectors/redis/Main.java ================================================ package com.zhisheng.connectors.redis; import com.zhisheng.common.model.ProductEvent; import com.zhisheng.common.utils.ExecutionEnvUtil; import com.zhisheng.common.utils.GsonUtil; import com.zhisheng.common.utils.KafkaConfigUtil; import org.apache.flink.api.common.functions.FlatMapFunction; import org.apache.flink.api.common.serialization.SimpleStringSchema; import org.apache.flink.api.java.tuple.Tuple2; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumer; import org.apache.flink.streaming.connectors.redis.RedisSink; import org.apache.flink.streaming.connectors.redis.common.config.FlinkJedisPoolConfig; import org.apache.flink.streaming.connectors.redis.common.mapper.RedisCommand; import org.apache.flink.streaming.connectors.redis.common.mapper.RedisCommandDescription; import org.apache.flink.streaming.connectors.redis.common.mapper.RedisMapper; import org.apache.flink.util.Collector; import java.util.Properties; import static com.zhisheng.common.constant.PropertiesConstants.METRICS_TOPIC; /** * Desc: 从 Kafka 中读取数据然后写入到 Redis * Created by zhisheng on 2019-04-29 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class Main { public static void main(String[] args) throws Exception { final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); ParameterTool parameterTool = ExecutionEnvUtil.PARAMETER_TOOL; Properties props = KafkaConfigUtil.buildKafkaProps(parameterTool); SingleOutputStreamOperator> product = env.addSource(new FlinkKafkaConsumer<>( parameterTool.get(METRICS_TOPIC), //这个 kafka topic 需要和上面的工具类的 topic 一致 new SimpleStringSchema(), props)) .map(string -> GsonUtil.fromJson(string, ProductEvent.class)) //反序列化 JSON .flatMap(new FlatMapFunction>() { @Override public void flatMap(ProductEvent value, Collector> out) throws Exception { //收集商品 id 和 price 两个属性 out.collect(new Tuple2<>(value.getId().toString(), value.getPrice().toString())); } }); // product.print(); //单个 Redis FlinkJedisPoolConfig conf = new FlinkJedisPoolConfig.Builder().setHost(parameterTool.get("redis.host")).build(); product.addSink(new RedisSink>(conf, new RedisSinkMapper())); //Redis 的 ip 信息一般都从配置文件取出来 //Redis 集群 /* FlinkJedisClusterConfig clusterConfig = new FlinkJedisClusterConfig.Builder() .setNodes(new HashSet( Arrays.asList(new InetSocketAddress("redis1", 6379)))).build();*/ //Redis Sentinels /* FlinkJedisSentinelConfig sentinelConfig = new FlinkJedisSentinelConfig.Builder() .setMasterName("master") .setSentinels(new HashSet<>(Arrays.asList("sentinel1", "sentinel2"))) .setPassword("") .setDatabase(1).build();*/ env.execute("flink redis connector"); } public static class RedisSinkMapper implements RedisMapper> { @Override public RedisCommandDescription getCommandDescription() { return new RedisCommandDescription(RedisCommand.HSET, "zhisheng"); } @Override public String getKeyFromData(Tuple2 data) { return data.f0; } @Override public String getValueFromData(Tuple2 data) { return data.f1; } } } ================================================ FILE: flink-learning-connectors/flink-learning-connectors-redis/src/main/java/com/zhisheng/connectors/redis/utils/ProductUtil.java ================================================ package com.zhisheng.connectors.redis.utils; import com.zhisheng.common.model.ProductEvent; import com.zhisheng.common.utils.GsonUtil; import org.apache.kafka.clients.producer.KafkaProducer; import org.apache.kafka.clients.producer.ProducerRecord; import java.util.Properties; import java.util.Random; /** * Desc: * Created by zhisheng on 2019-04-29 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class ProductUtil { public static final String broker_list = "localhost:9092"; public static final String topic = "zhisheng"; //kafka topic 需要和 flink 程序用同一个 topic public static final Random random = new Random(); public static void main(String[] args) { Properties props = new Properties(); props.put("bootstrap.servers", broker_list); props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer"); props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer"); KafkaProducer producer = new KafkaProducer(props); for (int i = 1; i <= 10000; i++) { ProductEvent product = ProductEvent.builder().id((long) i) //商品的 id .name("product" + i) //商品 name .price(random.nextLong() / 10000000000000L) //商品价格(以分为单位) .code("code" + i).build(); //商品编码 ProducerRecord record = new ProducerRecord(topic, null, null, GsonUtil.toJson(product)); producer.send(record); System.out.println("发送数据: " + GsonUtil.toJson(product)); } producer.flush(); } } ================================================ FILE: flink-learning-connectors/flink-learning-connectors-redis/src/main/resources/application.properties ================================================ kafka.brokers=localhost:9092 kafka.group.id=zhisheng kafka.zookeeper.connect=localhost:2181 metrics.topic=zhisheng stream.parallelism=4 stream.sink.parallelism=4 stream.default.parallelism=4 stream.checkpoint.interval=1000 stream.checkpoint.enable=false redis.host=127.0.0.1 ================================================ FILE: flink-learning-connectors/flink-learning-connectors-redis/src/main/resources/logback.xml ================================================ %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n ================================================ FILE: flink-learning-connectors/flink-learning-connectors-redis/src/test/java/RedisTest.java ================================================ import redis.clients.jedis.Jedis; /** * Desc: 验证数据已经写入到 Redis * Created by zhisheng on 2019-04-29 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class RedisTest { public static void main(String[] args) { Jedis jedis = new Jedis("127.0.0.1"); System.out.println("Server is running: " + jedis.ping()); System.out.println("result:" + jedis.hgetAll("zhisheng")); } } ================================================ FILE: flink-learning-connectors/flink-learning-connectors-rocketmq/README.md ================================================ ### Flink-learning-connectors-rocketmq Flink 消费 RocketMQ 数据,转换后再将转换后到数据发送到 RocketMQ,demo 类可以参考 RocketMQFlinkExample 类。 ================================================ FILE: flink-learning-connectors/flink-learning-connectors-rocketmq/pom.xml ================================================ flink-learning-connectors com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 flink-learning-connectors-rocketmq 4.7.1 org.apache.rocketmq rocketmq-client ${rocketmq.version} org.apache.rocketmq rocketmq-acl ${rocketmq.version} org.apache.rocketmq rocketmq-common ${rocketmq.version} io.netty netty-tcnative org.apache.maven.plugins maven-shade-plugin 3.1.0 false package shade com.zhisheng.connectors.rocketmq.example.RocketMQFlinkExample reference.conf *:*:*:* META-INF/*.SF META-INF/*.DSA META-INF/*.RSA ================================================ FILE: flink-learning-connectors/flink-learning-connectors-rocketmq/src/main/java/com/zhisheng/connectors/rocketmq/RocketMQConfig.java ================================================ package com.zhisheng.connectors.rocketmq; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; import org.apache.rocketmq.client.ClientConfig; import org.apache.rocketmq.client.consumer.DefaultMQPullConsumer; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.common.protocol.heartbeat.MessageModel; import java.util.Properties; import java.util.UUID; import static com.zhisheng.connectors.rocketmq.RocketMQUtils.getInteger; /** * Desc: RocketMQConfig for Consumer/Producer * Created by zhisheng on 2019-06-05 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class RocketMQConfig { // Server Config public static final String NAME_SERVER_ADDR = "nameserver.address"; // 必须 public static final String NAME_SERVER_POLL_INTERVAL = "nameserver.poll.interval"; public static final int DEFAULT_NAME_SERVER_POLL_INTERVAL = 30000; // 30 seconds public static final String BROKER_HEART_BEAT_INTERVAL = "brokerserver.heartbeat.interval"; public static final int DEFAULT_BROKER_HEART_BEAT_INTERVAL = 30000; // 30 seconds // Producer related config public static final String PRODUCER_GROUP = "producer.group"; public static final String PRODUCER_RETRY_TIMES = "producer.retry.times"; public static final int DEFAULT_PRODUCER_RETRY_TIMES = 3; public static final String PRODUCER_TIMEOUT = "producer.timeout"; public static final int DEFAULT_PRODUCER_TIMEOUT = 3000; // 3 seconds // Consumer related config public static final String CONSUMER_GROUP = "consumer.group"; // 必须 public static final String CONSUMER_TOPIC = "consumer.topic"; // 必须 public static final String CONSUMER_TAG = "consumer.tag"; public static final String DEFAULT_CONSUMER_TAG = "*"; public static final String CONSUMER_OFFSET_RESET_TO = "consumer.offset.reset.to"; // offset 重制到 public static final String CONSUMER_OFFSET_LATEST = "latest"; public static final String CONSUMER_OFFSET_EARLIEST = "earliest"; public static final String CONSUMER_OFFSET_TIMESTAMP = "timestamp"; public static final String CONSUMER_OFFSET_FROM_TIMESTAMP = "consumer.offset.from.timestamp"; // offset 重制到某个时间点 public static final String CONSUMER_OFFSET_PERSIST_INTERVAL = "consumer.offset.persist.interval"; public static final int DEFAULT_CONSUMER_OFFSET_PERSIST_INTERVAL = 5000; // 5 seconds public static final String CONSUMER_PULL_POOL_SIZE = "consumer.pull.thread.pool.size"; public static final int DEFAULT_CONSUMER_PULL_POOL_SIZE = 20; public static final String CONSUMER_BATCH_SIZE = "consumer.batch.size"; public static final int DEFAULT_CONSUMER_BATCH_SIZE = 32; public static final String CONSUMER_DELAY_WHEN_MESSAGE_NOT_FOUND = "consumer.delay.when.message.not.found"; public static final int DEFAULT_CONSUMER_DELAY_WHEN_MESSAGE_NOT_FOUND = 10; public static final String MSG_DELAY_LEVEL = "msg.delay.level"; public static final int MSG_DELAY_LEVEL00 = 0; // no delay public static final int MSG_DELAY_LEVEL01 = 1; // 1s public static final int MSG_DELAY_LEVEL02 = 2; // 5s public static final int MSG_DELAY_LEVEL03 = 3; // 10s public static final int MSG_DELAY_LEVEL04 = 4; // 30s public static final int MSG_DELAY_LEVEL05 = 5; // 1min public static final int MSG_DELAY_LEVEL06 = 6; // 2min public static final int MSG_DELAY_LEVEL07 = 7; // 3min public static final int MSG_DELAY_LEVEL08 = 8; // 4min public static final int MSG_DELAY_LEVEL09 = 9; // 5min public static final int MSG_DELAY_LEVEL10 = 10; // 6min public static final int MSG_DELAY_LEVEL11 = 11; // 7min public static final int MSG_DELAY_LEVEL12 = 12; // 8min public static final int MSG_DELAY_LEVEL13 = 13; // 9min public static final int MSG_DELAY_LEVEL14 = 14; // 10min public static final int MSG_DELAY_LEVEL15 = 15; // 20min public static final int MSG_DELAY_LEVEL16 = 16; // 30min public static final int MSG_DELAY_LEVEL17 = 17; // 1h public static final int MSG_DELAY_LEVEL18 = 18; // 2h /** * 构建 producer 配置 * * @param props Properties * @param producer DefaultMQProducer */ public static void buildProducerConfigs(Properties props, DefaultMQProducer producer) { buildCommonConfigs(props, producer); String group = props.getProperty(PRODUCER_GROUP); if (StringUtils.isEmpty(group)) { group = UUID.randomUUID().toString(); } producer.setProducerGroup(props.getProperty(PRODUCER_GROUP, group)); producer.setRetryTimesWhenSendFailed(getInteger(props, PRODUCER_RETRY_TIMES, DEFAULT_PRODUCER_RETRY_TIMES)); producer.setRetryTimesWhenSendAsyncFailed(getInteger(props, PRODUCER_RETRY_TIMES, DEFAULT_PRODUCER_RETRY_TIMES)); producer.setSendMsgTimeout(getInteger(props, PRODUCER_TIMEOUT, DEFAULT_PRODUCER_TIMEOUT)); } /** * 构建 Consumer 配置 * * @param props Properties * @param consumer DefaultMQPushConsumer */ public static void buildConsumerConfigs(Properties props, DefaultMQPullConsumer consumer) { buildCommonConfigs(props, consumer); consumer.setMessageModel(MessageModel.CLUSTERING); consumer.setPersistConsumerOffsetInterval(getInteger(props, CONSUMER_OFFSET_PERSIST_INTERVAL, DEFAULT_CONSUMER_OFFSET_PERSIST_INTERVAL)); } /** * 构建通用的配置 * * @param props Properties * @param client ClientConfig */ private static void buildCommonConfigs(Properties props, ClientConfig client) { String nameServers = props.getProperty(NAME_SERVER_ADDR); Validate.notEmpty(nameServers); client.setNamesrvAddr(nameServers); client.setPollNameServerInterval(getInteger(props, NAME_SERVER_POLL_INTERVAL, DEFAULT_NAME_SERVER_POLL_INTERVAL)); client.setHeartbeatBrokerInterval(getInteger(props, BROKER_HEART_BEAT_INTERVAL, DEFAULT_BROKER_HEART_BEAT_INTERVAL)); } } ================================================ FILE: flink-learning-connectors/flink-learning-connectors-rocketmq/src/main/java/com/zhisheng/connectors/rocketmq/RocketMQSink.java ================================================ package com.zhisheng.connectors.rocketmq; import com.zhisheng.connectors.rocketmq.common.selector.TopicSelector; import com.zhisheng.connectors.rocketmq.common.serialization.KeyValueSerializationSchema; import org.apache.commons.lang3.Validate; import org.apache.flink.configuration.Configuration; import org.apache.flink.runtime.state.FunctionInitializationContext; import org.apache.flink.runtime.state.FunctionSnapshotContext; import org.apache.flink.streaming.api.checkpoint.CheckpointedFunction; import org.apache.flink.streaming.api.functions.sink.RichSinkFunction; import org.apache.flink.streaming.api.operators.StreamingRuntimeContext; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.client.producer.SendCallback; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.common.message.Message; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.nio.charset.StandardCharsets; import java.util.LinkedList; import java.util.List; import java.util.Properties; /** * Desc: * Created by zhisheng on 2019-06-07 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class RocketMQSink extends RichSinkFunction implements CheckpointedFunction { private static final long serialVersionUID = 1L; private static final Logger LOG = LoggerFactory.getLogger(RocketMQSink.class); private transient DefaultMQProducer producer; private boolean async; // false by default private Properties props; private TopicSelector topicSelector; private KeyValueSerializationSchema serializationSchema; private boolean batchFlushOnCheckpoint; // false by default private int batchSize = 1000; private List batchList; private int messageDeliveryDelayLevel = RocketMQConfig.MSG_DELAY_LEVEL00; public RocketMQSink(KeyValueSerializationSchema schema, TopicSelector topicSelector, Properties props) { this.serializationSchema = schema; this.topicSelector = topicSelector; this.props = props; if (this.props != null) { this.messageDeliveryDelayLevel = RocketMQUtils.getInteger(this.props, RocketMQConfig.MSG_DELAY_LEVEL, RocketMQConfig.MSG_DELAY_LEVEL00); if (this.messageDeliveryDelayLevel < RocketMQConfig.MSG_DELAY_LEVEL00) { this.messageDeliveryDelayLevel = RocketMQConfig.MSG_DELAY_LEVEL00; } else if (this.messageDeliveryDelayLevel > RocketMQConfig.MSG_DELAY_LEVEL18) { this.messageDeliveryDelayLevel = RocketMQConfig.MSG_DELAY_LEVEL18; } } } @Override public void open(Configuration parameters) throws Exception { Validate.notEmpty(props, "Producer properties can not be empty"); Validate.notNull(topicSelector, "TopicSelector can not be null"); Validate.notNull(serializationSchema, "KeyValueSerializationSchema can not be null"); producer = new DefaultMQProducer(); producer.setInstanceName(String.valueOf(getRuntimeContext().getIndexOfThisSubtask())); RocketMQConfig.buildProducerConfigs(props, producer); batchList = new LinkedList<>(); if (batchFlushOnCheckpoint && !((StreamingRuntimeContext) getRuntimeContext()).isCheckpointingEnabled()) { LOG.warn("Flushing on checkpoint is enabled, but checkpointing is not enabled. Disabling flushing."); batchFlushOnCheckpoint = false; } try { producer.start(); } catch (MQClientException e) { throw new RuntimeException(e); } } @Override public void invoke(IN input, Context context) throws Exception { Message msg = prepareMessage(input); if (batchFlushOnCheckpoint) { batchList.add(msg); if (batchList.size() >= batchSize) { flushSync(); } return; } if (async) { try { producer.send(msg, new SendCallback() { @Override public void onSuccess(SendResult sendResult) { LOG.debug("Async send message success! result: {}", sendResult); } @Override public void onException(Throwable throwable) { if (throwable != null) { LOG.error("Async send message failure!", throwable); } } }); } catch (Exception e) { LOG.error("Async send message failure!", e); } } else { try { SendResult result = producer.send(msg); LOG.debug("Sync send message result: {}", result); } catch (Exception e) { LOG.error("Sync send message failure!", e); } } } /** * 解析消息 * * @param input * @return */ private Message prepareMessage(IN input) { String topic = topicSelector.getTopic(input); String tag = topicSelector.getTag(input) != null ? topicSelector.getTag(input) : ""; byte[] k = serializationSchema.serializeKey(input); String key = k != null ? new String(k, StandardCharsets.UTF_8) : ""; byte[] value = serializationSchema.serializeValue(input); Validate.notNull(topic, "the message topic is null"); Validate.notNull(value, "the message body is null"); Message msg = new Message(topic, tag, key, value); if (this.messageDeliveryDelayLevel > RocketMQConfig.MSG_DELAY_LEVEL00) { msg.setDelayTimeLevel(this.messageDeliveryDelayLevel); } return msg; } public RocketMQSink withAsync(boolean async) { this.async = async; return this; } public RocketMQSink withBatchFlushOnCheckpoint(boolean batchFlushOnCheckpoint) { this.batchFlushOnCheckpoint = batchFlushOnCheckpoint; return this; } public RocketMQSink withBatchSize(int batchSize) { this.batchSize = batchSize; return this; } @Override public void close() throws Exception { if (producer != null) { flushSync(); producer.shutdown(); } } private void flushSync() throws Exception { if (batchFlushOnCheckpoint) { synchronized (batchList) { if (batchList.size() > 0) { producer.send(batchList); batchList.clear(); } } } } @Override public void snapshotState(FunctionSnapshotContext context) throws Exception { flushSync(); } @Override public void initializeState(FunctionInitializationContext context) throws Exception { // Nothing to do } } ================================================ FILE: flink-learning-connectors/flink-learning-connectors-rocketmq/src/main/java/com/zhisheng/connectors/rocketmq/RocketMQSource.java ================================================ package com.zhisheng.connectors.rocketmq; import com.zhisheng.connectors.rocketmq.common.serialization.KeyValueDeserializationSchema; import org.apache.commons.lang3.Validate; import org.apache.flink.api.common.state.ListState; import org.apache.flink.api.common.state.ListStateDescriptor; import org.apache.flink.api.common.typeinfo.TypeHint; import org.apache.flink.api.common.typeinfo.TypeInformation; import org.apache.flink.api.java.tuple.Tuple2; import org.apache.flink.api.java.typeutils.ResultTypeQueryable; import org.apache.flink.configuration.Configuration; import org.apache.flink.runtime.state.FunctionInitializationContext; import org.apache.flink.runtime.state.FunctionSnapshotContext; import org.apache.flink.streaming.api.checkpoint.CheckpointedFunction; import org.apache.flink.streaming.api.functions.source.RichParallelSourceFunction; import org.apache.rocketmq.client.consumer.*; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import static com.zhisheng.connectors.rocketmq.RocketMQConfig.*; import static com.zhisheng.connectors.rocketmq.RocketMQUtils.getInteger; import static com.zhisheng.connectors.rocketmq.RocketMQUtils.getLong; /** * Desc: * Created by zhisheng on 2019-06-05 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class RocketMQSource extends RichParallelSourceFunction implements CheckpointedFunction, ResultTypeQueryable { private static final long serialVersionUID = 1L; private static final Logger LOG = LoggerFactory.getLogger(RocketMQSource.class); private transient MQPullConsumerScheduleService pullConsumerScheduleService; private DefaultMQPullConsumer consumer; private KeyValueDeserializationSchema schema; private RunningChecker runningChecker; private transient ListState> unionOffsetStates; private Map offsetTable; private Map restoredOffsets; private Properties props; private String topic; private String group; private static final String OFFSETS_STATE_NAME = "topic-partition-offset-states"; private transient volatile boolean restored; public RocketMQSource(KeyValueDeserializationSchema schema, Properties props) { this.schema = schema; this.props = props; } @Override public void open(Configuration parameters) throws Exception { LOG.debug("source open...."); Validate.notEmpty(props, "Consumer properties can not be empty"); Validate.notNull(schema, "KeyValueDeserializationSchema can not be null"); this.topic = props.getProperty(RocketMQConfig.CONSUMER_TOPIC); this.group = props.getProperty(RocketMQConfig.CONSUMER_GROUP); Validate.notEmpty(topic, "Consumer topic can not be empty"); Validate.notEmpty(group, "Consumer group can not be empty"); if (offsetTable == null) { offsetTable = new ConcurrentHashMap<>(); } if (restoredOffsets == null) { restoredOffsets = new ConcurrentHashMap<>(); } runningChecker = new RunningChecker(); pullConsumerScheduleService = new MQPullConsumerScheduleService(group); consumer = pullConsumerScheduleService.getDefaultMQPullConsumer(); consumer.setInstanceName(String.valueOf(getRuntimeContext().getIndexOfThisSubtask())); RocketMQConfig.buildConsumerConfigs(props, consumer); } @Override public void run(SourceContext context) throws Exception { LOG.debug("source run...."); // The lock that guarantees that record emission and state updates are atomic, // from the view of taking a checkpoint. final Object lock = context.getCheckpointLock(); int delayWhenMessageNotFound = getInteger(props, RocketMQConfig.CONSUMER_DELAY_WHEN_MESSAGE_NOT_FOUND, RocketMQConfig.DEFAULT_CONSUMER_DELAY_WHEN_MESSAGE_NOT_FOUND); String tag = props.getProperty(RocketMQConfig.CONSUMER_TAG, RocketMQConfig.DEFAULT_CONSUMER_TAG); int pullPoolSize = getInteger(props, RocketMQConfig.CONSUMER_PULL_POOL_SIZE, RocketMQConfig.DEFAULT_CONSUMER_PULL_POOL_SIZE); int pullBatchSize = getInteger(props, RocketMQConfig.CONSUMER_BATCH_SIZE, RocketMQConfig.DEFAULT_CONSUMER_BATCH_SIZE); pullConsumerScheduleService.setPullThreadNums(pullPoolSize); pullConsumerScheduleService.registerPullTaskCallback(topic, new PullTaskCallback() { @Override public void doPullTask(MessageQueue mq, PullTaskContext pullTaskContext) { try { long offset = getMessageQueueOffset(mq); if (offset < 0) { return; } PullResult pullResult = consumer.pull(mq, tag, offset, pullBatchSize); boolean found = false; switch (pullResult.getPullStatus()) { case FOUND: List messages = pullResult.getMsgFoundList(); for (MessageExt msg : messages) { byte[] key = msg.getKeys() != null ? msg.getKeys().getBytes(StandardCharsets.UTF_8) : null; byte[] value = msg.getBody(); OUT data = schema.deserializeKeyAndValue(key, value); // output and state update are atomic synchronized (lock) { context.collectWithTimestamp(data, msg.getBornTimestamp()); } } found = true; break; case NO_MATCHED_MSG: LOG.debug("No matched message after offset {} for queue {}", offset, mq); break; case NO_NEW_MSG: break; case OFFSET_ILLEGAL: LOG.warn("Offset {} is illegal for queue {}", offset, mq); break; default: break; } synchronized (lock) { putMessageQueueOffset(mq, pullResult.getNextBeginOffset()); } if (found) { pullTaskContext.setPullNextDelayTimeMillis(0); // no delay when messages were found } else { pullTaskContext.setPullNextDelayTimeMillis(delayWhenMessageNotFound); } } catch (Exception e) { throw new RuntimeException(e); } } }); try { pullConsumerScheduleService.start(); } catch (MQClientException e) { throw new RuntimeException(e); } runningChecker.setRunning(true); awaitTermination(); } private void awaitTermination() throws InterruptedException { while (runningChecker.isRunning()) { Thread.sleep(50); } } private long getMessageQueueOffset(MessageQueue mq) throws MQClientException { Long offset = offsetTable.get(mq); // restoredOffsets(unionOffsetStates) is the restored global union state; // should only snapshot mqs that actually belong to us if (restored && offset == null) { offset = restoredOffsets.get(mq); } if (offset == null) { offset = consumer.fetchConsumeOffset(mq, false); if (offset < 0) { String initialOffset = props.getProperty(RocketMQConfig.CONSUMER_OFFSET_RESET_TO, CONSUMER_OFFSET_LATEST); switch (initialOffset) { case CONSUMER_OFFSET_EARLIEST: offset = consumer.minOffset(mq); break; case CONSUMER_OFFSET_LATEST: offset = consumer.maxOffset(mq); break; case CONSUMER_OFFSET_TIMESTAMP: offset = consumer.searchOffset(mq, getLong(props, RocketMQConfig.CONSUMER_OFFSET_FROM_TIMESTAMP, System.currentTimeMillis())); break; default: throw new IllegalArgumentException("Unknown value for CONSUMER_OFFSET_RESET_TO."); } } } offsetTable.put(mq, offset); return offsetTable.get(mq); } private void putMessageQueueOffset(MessageQueue mq, long offset) throws MQClientException { offsetTable.put(mq, offset); consumer.updateConsumeOffset(mq, offset); } @Override public void cancel() { LOG.debug("cancel ..."); runningChecker.setRunning(false); if (pullConsumerScheduleService != null) { pullConsumerScheduleService.shutdown(); } offsetTable.clear(); restoredOffsets.clear(); } @Override public void close() throws Exception { LOG.debug("close ..."); try { cancel(); } finally { super.close(); } } @Override public void snapshotState(FunctionSnapshotContext context) throws Exception { // called when a snapshot for a checkpoint is requested if (!runningChecker.isRunning()) { LOG.debug("snapshotState() called on closed source; returning null."); return; } if (LOG.isDebugEnabled()) { LOG.debug("Snapshotting state {} ...", context.getCheckpointId()); } unionOffsetStates.clear(); if (LOG.isDebugEnabled()) { LOG.debug("Snapshotted state, last processed offsets: {}, checkpoint id: {}, timestamp: {}", offsetTable, context.getCheckpointId(), context.getCheckpointTimestamp()); } // remove the unassigned queues in order to avoid read the wrong offset when the source restart Set assignedQueues = consumer.fetchMessageQueuesInBalance(topic); offsetTable.entrySet().removeIf(item -> !assignedQueues.contains(item.getKey())); for (Map.Entry entry : offsetTable.entrySet()) { unionOffsetStates.add(Tuple2.of(entry.getKey(), entry.getValue())); } } @Override public void initializeState(FunctionInitializationContext context) throws Exception { // called every time the user-defined function is initialized, // be that when the function is first initialized or be that // when the function is actually recovering from an earlier checkpoint. // Given this, initializeState() is not only the place where different types of state are initialized, // but also where state recovery logic is included. LOG.debug("initialize State ..."); this.unionOffsetStates = context.getOperatorStateStore().getUnionListState(new ListStateDescriptor<>( OFFSETS_STATE_NAME, TypeInformation.of(new TypeHint>() { }))); this.restored = context.isRestored(); if (restored) { if (restoredOffsets == null) { restoredOffsets = new ConcurrentHashMap<>(); } for (Tuple2 mqOffsets : unionOffsetStates.get()) { if (!restoredOffsets.containsKey(mqOffsets.f0) || restoredOffsets.get(mqOffsets.f0) < mqOffsets.f1) { restoredOffsets.put(mqOffsets.f0, mqOffsets.f1); } } LOG.info("Setting restore state in the consumer. Using the following offsets: {}", restoredOffsets); } else { LOG.info("No restore state for the consumer."); } } @Override public TypeInformation getProducedType() { return schema.getProducedType(); } } ================================================ FILE: flink-learning-connectors/flink-learning-connectors-rocketmq/src/main/java/com/zhisheng/connectors/rocketmq/RocketMQUtils.java ================================================ package com.zhisheng.connectors.rocketmq; import java.util.Properties; /** * Desc: RocketMQ 工具类 * Created by zhisheng on 2019-06-05 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public final class RocketMQUtils { public static int getInteger(Properties props, String key, int defaultValue) { return Integer.parseInt(props.getProperty(key, String.valueOf(defaultValue))); } public static long getLong(Properties props, String key, long defaultValue) { return Long.parseLong(props.getProperty(key, String.valueOf(defaultValue))); } public static boolean getBoolean(Properties props, String key, boolean defaultValue) { return Boolean.parseBoolean(props.getProperty(key, String.valueOf(defaultValue))); } } ================================================ FILE: flink-learning-connectors/flink-learning-connectors-rocketmq/src/main/java/com/zhisheng/connectors/rocketmq/RunningChecker.java ================================================ package com.zhisheng.connectors.rocketmq; import java.io.Serializable; /** * Desc: * Created by zhisheng on 2019-06-05 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class RunningChecker implements Serializable { private volatile boolean isRunning = false; public boolean isRunning() { return isRunning; } public void setRunning(boolean running) { isRunning = running; } } ================================================ FILE: flink-learning-connectors/flink-learning-connectors-rocketmq/src/main/java/com/zhisheng/connectors/rocketmq/common/selector/DefaultTopicSelector.java ================================================ package com.zhisheng.connectors.rocketmq.common.selector; /** * Desc: * Created by zhisheng on 2019-06-05 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class DefaultTopicSelector implements TopicSelector { private final String topicName; private final String tagName; public DefaultTopicSelector(final String topicName) { this(topicName, ""); } public DefaultTopicSelector(String topicName, String tagName) { this.topicName = topicName; this.tagName = tagName; } @Override public String getTopic(T tuple) { return topicName; } @Override public String getTag(T tuple) { return tagName; } } ================================================ FILE: flink-learning-connectors/flink-learning-connectors-rocketmq/src/main/java/com/zhisheng/connectors/rocketmq/common/selector/SimpleTopicSelector.java ================================================ package com.zhisheng.connectors.rocketmq.common.selector; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Map; /** * Desc: * Created by zhisheng on 2019-06-05 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class SimpleTopicSelector implements TopicSelector { private static final Logger LOG = LoggerFactory.getLogger(SimpleTopicSelector.class); private final String topicFieldName; private final String defaultTopicName; private final String tagFieldName; private final String defaultTagName; public SimpleTopicSelector(String topicFieldName, String defaultTopicName, String tagFieldName, String defaultTagName) { this.topicFieldName = topicFieldName; this.defaultTopicName = defaultTopicName; this.tagFieldName = tagFieldName; this.defaultTagName = defaultTagName; } @Override public String getTopic(Map tuple) { if (tuple.containsKey(topicFieldName)) { Object topic = tuple.get(topicFieldName); return topic != null ? topic.toString() : defaultTopicName; } else { LOG.warn("Field {} Not Found. Returning default topic {}", topicFieldName, defaultTopicName); return defaultTopicName; } } @Override public String getTag(Map tuple) { if (tuple.containsKey(tagFieldName)) { Object tag = tuple.get(tagFieldName); return tag != null ? tag.toString() : defaultTagName; } else { LOG.warn("Field {} Not Found. Returning default tag {}", tagFieldName, defaultTagName); return defaultTagName; } } } ================================================ FILE: flink-learning-connectors/flink-learning-connectors-rocketmq/src/main/java/com/zhisheng/connectors/rocketmq/common/selector/TopicSelector.java ================================================ package com.zhisheng.connectors.rocketmq.common.selector; import java.io.Serializable; /** * Desc: * Created by zhisheng on 2019-06-05 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public interface TopicSelector extends Serializable { String getTopic(T tuple); String getTag(T tuple); } ================================================ FILE: flink-learning-connectors/flink-learning-connectors-rocketmq/src/main/java/com/zhisheng/connectors/rocketmq/common/serialization/KeyValueDeserializationSchema.java ================================================ package com.zhisheng.connectors.rocketmq.common.serialization; import org.apache.flink.api.java.typeutils.ResultTypeQueryable; import java.io.Serializable; /** * Desc: * Created by zhisheng on 2019-06-05 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public interface KeyValueDeserializationSchema extends ResultTypeQueryable, Serializable { T deserializeKeyAndValue(byte[] key, byte[] value); } ================================================ FILE: flink-learning-connectors/flink-learning-connectors-rocketmq/src/main/java/com/zhisheng/connectors/rocketmq/common/serialization/KeyValueSerializationSchema.java ================================================ package com.zhisheng.connectors.rocketmq.common.serialization; import java.io.Serializable; /** * Desc: * Created by zhisheng on 2019-06-05 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public interface KeyValueSerializationSchema extends Serializable { byte[] serializeKey(T tuple); byte[] serializeValue(T tuple); } ================================================ FILE: flink-learning-connectors/flink-learning-connectors-rocketmq/src/main/java/com/zhisheng/connectors/rocketmq/common/serialization/SimpleKeyValueDeserializationSchema.java ================================================ package com.zhisheng.connectors.rocketmq.common.serialization; import org.apache.flink.api.common.typeinfo.TypeInformation; import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Map; /** * Desc: * Created by zhisheng on 2019-06-05 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class SimpleKeyValueDeserializationSchema implements KeyValueDeserializationSchema { public static final String DEFAULT_KEY_FIELD = "key"; public static final String DEFAULT_VALUE_FIELD = "value"; public String keyField; public String valueField; public SimpleKeyValueDeserializationSchema() { this(DEFAULT_KEY_FIELD, DEFAULT_VALUE_FIELD); } public SimpleKeyValueDeserializationSchema(String keyField, String valueField) { this.keyField = keyField; this.valueField = valueField; } @Override public Map deserializeKeyAndValue(byte[] key, byte[] value) { HashMap map = new HashMap(2); if (keyField != null) { String k = key != null ? new String(key, StandardCharsets.UTF_8) : null; map.put(keyField, k); } if (valueField != null) { String v = value != null ? new String(value, StandardCharsets.UTF_8) : null; map.put(valueField, v); } return map; } @Override public TypeInformation getProducedType() { return null; } } ================================================ FILE: flink-learning-connectors/flink-learning-connectors-rocketmq/src/main/java/com/zhisheng/connectors/rocketmq/common/serialization/SimpleKeyValueSerializationSchema.java ================================================ package com.zhisheng.connectors.rocketmq.common.serialization; import java.nio.charset.StandardCharsets; import java.util.Map; /** * Desc: * Created by zhisheng on 2019-06-05 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class SimpleKeyValueSerializationSchema implements KeyValueSerializationSchema { public static final String DEFAULT_KEY_FIELD = "key"; public static final String DEFAULT_VALUE_FIELD = "value"; public String keyField; public String valueField; public SimpleKeyValueSerializationSchema() { this(DEFAULT_KEY_FIELD, DEFAULT_VALUE_FIELD); } public SimpleKeyValueSerializationSchema(String keyField, String valueField) { this.keyField = keyField; this.valueField = valueField; } @Override public byte[] serializeKey(Map tuple) { return getBytes(tuple, keyField); } @Override public byte[] serializeValue(Map tuple) { return getBytes(tuple, valueField); } private byte[] getBytes(Map tuple, String key) { if (tuple == null || key == null) { return null; } Object value = tuple.get(key); return value != null ? value.toString().getBytes(StandardCharsets.UTF_8) : null; } } ================================================ FILE: flink-learning-connectors/flink-learning-connectors-rocketmq/src/main/java/com/zhisheng/connectors/rocketmq/example/RocketMQFlinkExample.java ================================================ package com.zhisheng.connectors.rocketmq.example; import com.zhisheng.connectors.rocketmq.RocketMQConfig; import com.zhisheng.connectors.rocketmq.RocketMQSink; import com.zhisheng.connectors.rocketmq.RocketMQSource; import com.zhisheng.connectors.rocketmq.common.selector.DefaultTopicSelector; import com.zhisheng.connectors.rocketmq.common.serialization.SimpleKeyValueDeserializationSchema; import com.zhisheng.connectors.rocketmq.common.serialization.SimpleKeyValueSerializationSchema; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.api.functions.ProcessFunction; import org.apache.flink.util.Collector; import java.util.HashMap; import java.util.Map; import java.util.Properties; /** * Desc: 从 RocketMQ 中获取数据后写入到 RocketMQ * Created by zhisheng on 2019-06-07 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class RocketMQFlinkExample { public static void main(String[] args) throws Exception { StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.enableCheckpointing(3000); Properties consumerProps = new Properties(); consumerProps.setProperty(RocketMQConfig.NAME_SERVER_ADDR, "localhost:9876"); consumerProps.setProperty(RocketMQConfig.CONSUMER_GROUP, "c002"); consumerProps.setProperty(RocketMQConfig.CONSUMER_TOPIC, "zhisheng"); Properties producerProps = new Properties(); producerProps.setProperty(RocketMQConfig.NAME_SERVER_ADDR, "localhost:9876"); int msgDelayLevel = RocketMQConfig.MSG_DELAY_LEVEL05; producerProps.setProperty(RocketMQConfig.MSG_DELAY_LEVEL, String.valueOf(msgDelayLevel)); // TimeDelayLevel is not supported for batching boolean batchFlag = msgDelayLevel <= 0; env.addSource(new RocketMQSource(new SimpleKeyValueDeserializationSchema("id", "address"), consumerProps)) .name("rocketmq-source") .setParallelism(2) .process(new ProcessFunction() { @Override public void processElement(Map in, Context ctx, Collector out) throws Exception { HashMap result = new HashMap(); result.put("id", in.get("id")); String[] arr = in.get("address").toString().split("\\s+"); result.put("province", arr[arr.length - 1]); out.collect(result); } }) .name("upper-processor") .setParallelism(2) .addSink(new RocketMQSink(new SimpleKeyValueSerializationSchema("id", "province"), new DefaultTopicSelector("zhisheng"), producerProps).withBatchFlushOnCheckpoint(batchFlag)) .name("rocketmq-sink") .setParallelism(2); env.execute("rocketmq-flink-example"); } } ================================================ FILE: flink-learning-connectors/flink-learning-connectors-rocketmq/src/main/java/com/zhisheng/connectors/rocketmq/example/SimpleConsumer.java ================================================ package com.zhisheng.connectors.rocketmq.example; import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus; import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.common.message.MessageExt; import java.util.List; /** * Desc: * Created by zhisheng on 2019-06-07 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class SimpleConsumer { public static void main(String[] args) { DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("g00003"); consumer.setNamesrvAddr("localhost:9876"); try { consumer.subscribe("zhisheng", "*"); } catch (MQClientException e) { e.printStackTrace(); } consumer.registerMessageListener(new MessageListenerConcurrently() { @Override public ConsumeConcurrentlyStatus consumeMessage(List msgs, ConsumeConcurrentlyContext context) { for (MessageExt msg : msgs) { System.out.println(msg.getKeys() + ":" + new String(msg.getBody())); } return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; } }); try { consumer.start(); } catch (MQClientException e) { e.printStackTrace(); } } } ================================================ FILE: flink-learning-connectors/flink-learning-connectors-rocketmq/src/main/java/com/zhisheng/connectors/rocketmq/example/SimpleProducer.java ================================================ package com.zhisheng.connectors.rocketmq.example; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.common.message.Message; /** * Desc: * Created by zhisheng on 2019-06-07 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class SimpleProducer { public static void main(String[] args) { DefaultMQProducer producer = new DefaultMQProducer("p001"); producer.setNamesrvAddr("localhost:9876"); try { producer.start(); } catch (MQClientException e) { e.printStackTrace(); } for (int i = 0; i < 10000; i++) { Message msg = new Message("zhisheng", "", "id_" + i, ("country_X province_" + i).getBytes()); try { producer.send(msg); } catch (Exception e) { e.printStackTrace(); } System.out.println("send " + i); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } } } ================================================ FILE: flink-learning-connectors/flink-learning-connectors-rocketmq/src/main/resources/application.properties ================================================ kafka.brokers=localhost:9092 kafka.group.id=zhisheng kafka.zookeeper.connect=localhost:2181 metrics.topic=zhisheng stream.parallelism=4 stream.sink.parallelism=4 stream.default.parallelism=4 stream.checkpoint.interval=1000 stream.checkpoint.enable=false ================================================ FILE: flink-learning-connectors/flink-learning-connectors-rocketmq/src/main/resources/logback.xml ================================================ %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n ================================================ FILE: flink-learning-connectors/pom.xml ================================================ flink-learning com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 flink-learning-connectors pom flink-learning-connectors-kafka flink-learning-connectors-cassandra flink-learning-connectors-mysql flink-learning-connectors-rabbitmq flink-learning-connectors-hdfs flink-learning-connectors-hbase flink-learning-connectors-rocketmq flink-learning-connectors-redis flink-learning-connectors-flume flink-learning-connectors-influxdb flink-learning-connectors-activemq flink-learning-connectors-netty flink-learning-connectors-kudu flink-learning-connectors-clickhouse flink-learning-connectors-hive flink-learning-connectors-gcp-pubsub flink-learning-connectors-nifi flink-learning-connectors-pulsar flink-learning-connectors-jdbc flink-learning-connectors-es com.zhisheng.flink flink-learning-common ${project.version} ================================================ FILE: flink-learning-core/pom.xml ================================================ flink-learning com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 flink-learning-core org.slf4j slf4j-api 1.7.36 ================================================ FILE: flink-learning-core/src/main/java/com/zhisheng/core/exception/FlinkRuntimeException.java ================================================ package com.zhisheng.core.exception; /** * Desc: Base class of all Flink-specific unchecked exceptions. * Created by zhisheng on 2019-09-25 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class FlinkRuntimeException extends RuntimeException { private static final long serialVersionUID = 193141189399279147L; /** * Creates a new Exception with the given message and null as the cause. * * @param message The exception message */ public FlinkRuntimeException(String message) { super(message); } /** * Creates a new exception with a null message and the given cause. * * @param cause The exception that caused this exception */ public FlinkRuntimeException(Throwable cause) { super(cause); } /** * Creates a new exception with the given message and cause. * * @param message The exception message * @param cause The exception that caused this exception */ public FlinkRuntimeException(String message, Throwable cause) { super(message, cause); } } ================================================ FILE: flink-learning-core/src/main/java/com/zhisheng/core/factory/DeserializerFactory.java ================================================ package com.zhisheng.core.factory; /** * Desc: * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class DeserializerFactory { } ================================================ FILE: flink-learning-core/src/main/java/com/zhisheng/core/factory/SerializerFactory.java ================================================ package com.zhisheng.core.factory; /** * Desc: * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class SerializerFactory { } ================================================ FILE: flink-learning-core/src/main/java/com/zhisheng/core/factory/SinkFactory.java ================================================ package com.zhisheng.core.factory; /** * Desc: * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class SinkFactory { } ================================================ FILE: flink-learning-core/src/main/java/com/zhisheng/core/factory/SourceFactory.java ================================================ package com.zhisheng.core.factory; /** * Desc: * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class SourceFactory { } ================================================ FILE: flink-learning-core/src/main/java/com/zhisheng/core/utils/ArrayUtils.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zhisheng.core.utils; /** * Utility class for Java arrays. */ public final class ArrayUtils { public static String[] concat(String[] array1, String[] array2) { if (array1.length == 0) { return array2; } if (array2.length == 0) { return array1; } String[] resultArray = new String[array1.length + array2.length]; System.arraycopy(array1, 0, resultArray, 0, array1.length); System.arraycopy(array2, 0, resultArray, array1.length, array2.length); return resultArray; } } ================================================ FILE: flink-learning-core/src/main/java/com/zhisheng/core/utils/CollectionUtil.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zhisheng.core.utils; import java.util.*; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.BiFunction; import java.util.stream.Stream; /** * Simple utility to work with Java collections. */ public final class CollectionUtil { /** * A safe maximum size for arrays in the JVM. */ public static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; private CollectionUtil() { throw new AssertionError(); } public static boolean isNullOrEmpty(Collection collection) { return collection == null || collection.isEmpty(); } public static boolean isNullOrEmpty(Map map) { return map == null || map.isEmpty(); } public static Stream mapWithIndex(Collection input, final BiFunction mapper) { final AtomicInteger count = new AtomicInteger(0); return input.stream().map(element -> mapper.apply(element, count.getAndIncrement())); } /** * Partition a collection into approximately n buckets. */ public static Collection> partition(Collection elements, int numBuckets) { Map> buckets = new HashMap<>(numBuckets); int initialCapacity = elements.size() / numBuckets; int index = 0; for (T element : elements) { int bucket = index % numBuckets; buckets.computeIfAbsent(bucket, key -> new ArrayList<>(initialCapacity)).add(element); } return buckets.values(); } } ================================================ FILE: flink-learning-core/src/main/java/com/zhisheng/core/utils/ExecutorUtils.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zhisheng.core.utils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; /** * Utilities for {@link java.util.concurrent.Executor Executors}. */ public class ExecutorUtils { private static final Logger LOG = LoggerFactory.getLogger(org.apache.flink.util.ExecutorUtils.class); /** * Gracefully shutdown the given {@link ExecutorService}. The call waits the given timeout that * all ExecutorServices terminate. If the ExecutorServices do not terminate in this time, * they will be shut down hard. * * @param timeout to wait for the termination of all ExecutorServices * @param unit of the timeout * @param executorServices to shut down */ public static void gracefulShutdown(long timeout, TimeUnit unit, ExecutorService... executorServices) { for (ExecutorService executorService: executorServices) { executorService.shutdown(); } boolean wasInterrupted = false; final long endTime = unit.toMillis(timeout) + System.currentTimeMillis(); long timeLeft = unit.toMillis(timeout); boolean hasTimeLeft = timeLeft > 0L; for (ExecutorService executorService: executorServices) { if (wasInterrupted || !hasTimeLeft) { executorService.shutdownNow(); } else { try { if (!executorService.awaitTermination(timeLeft, TimeUnit.MILLISECONDS)) { LOG.warn("ExecutorService did not terminate in time. Shutting it down now."); executorService.shutdownNow(); } } catch (InterruptedException e) { LOG.warn("Interrupted while shutting down executor services. Shutting all " + "remaining ExecutorServices down now.", e); executorService.shutdownNow(); wasInterrupted = true; Thread.currentThread().interrupt(); } timeLeft = endTime - System.currentTimeMillis(); hasTimeLeft = timeLeft > 0L; } } } /** * Shuts the given {@link ExecutorService} down in a non-blocking fashion. The shut down will * be executed by a thread from the common fork-join pool. * *

The executor services will be shut down gracefully for the given timeout period. Afterwards * {@link ExecutorService#shutdownNow()} will be called. * * @param timeout before {@link ExecutorService#shutdownNow()} is called * @param unit time unit of the timeout * @param executorServices to shut down * @return Future which is completed once the {@link ExecutorService} are shut down */ public static CompletableFuture nonBlockingShutdown(long timeout, TimeUnit unit, ExecutorService... executorServices) { return CompletableFuture.supplyAsync( () -> { gracefulShutdown(timeout, unit, executorServices); return null; }); } } ================================================ FILE: flink-learning-core/src/main/java/com/zhisheng/core/utils/StringUtils.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zhisheng.core.utils; import org.apache.flink.annotation.PublicEvolving; import org.apache.flink.core.memory.DataInputView; import org.apache.flink.core.memory.DataOutputView; import org.apache.flink.types.StringValue; import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.io.IOException; import java.util.Arrays; import java.util.Objects; import java.util.Random; import java.util.stream.Collectors; import static org.apache.flink.util.Preconditions.checkArgument; import static org.apache.flink.util.Preconditions.checkNotNull; /** * Utility class to convert objects into strings in vice-versa. */ @PublicEvolving public final class StringUtils { private static final char[] HEX_CHARS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; /** * Given an array of bytes it will convert the bytes to a hex string * representation of the bytes. * * @param bytes * the bytes to convert in a hex string * @param start * start index, inclusively * @param end * end index, exclusively * @return hex string representation of the byte array */ public static String byteToHexString(final byte[] bytes, final int start, final int end) { if (bytes == null) { throw new IllegalArgumentException("bytes == null"); } int length = end - start; char[] out = new char[length * 2]; for (int i = start, j = 0; i < end; i++) { out[j++] = HEX_CHARS[(0xF0 & bytes[i]) >>> 4]; out[j++] = HEX_CHARS[0x0F & bytes[i]]; } return new String(out); } /** * Given an array of bytes it will convert the bytes to a hex string * representation of the bytes. * * @param bytes * the bytes to convert in a hex string * @return hex string representation of the byte array */ public static String byteToHexString(final byte[] bytes) { return byteToHexString(bytes, 0, bytes.length); } /** * Given a hex string this will return the byte array corresponding to the * string . * * @param hex * the hex String array * @return a byte array that is a hex string representation of the given * string. The size of the byte array is therefore hex.length/2 */ public static byte[] hexStringToByte(final String hex) { final byte[] bts = new byte[hex.length() / 2]; for (int i = 0; i < bts.length; i++) { bts[i] = (byte) Integer.parseInt(hex.substring(2 * i, 2 * i + 2), 16); } return bts; } /** * This method calls {@link Object#toString()} on the given object, unless the * object is an array. In that case, it will use the {@link #arrayToString(Object)} * method to create a string representation of the array that includes all contained * elements. * * @param o The object for which to create the string representation. * @return The string representation of the object. */ public static String arrayAwareToString(Object o) { if (o == null) { return "null"; } if (o.getClass().isArray()) { return arrayToString(o); } return o.toString(); } /** * Returns a string representation of the given array. This method takes an Object * to allow also all types of primitive type arrays. * * @param array The array to create a string representation for. * @return The string representation of the array. * @throws IllegalArgumentException If the given object is no array. */ public static String arrayToString(Object array) { if (array == null) { throw new NullPointerException(); } if (array instanceof int[]) { return Arrays.toString((int[]) array); } if (array instanceof long[]) { return Arrays.toString((long[]) array); } if (array instanceof Object[]) { return Arrays.toString((Object[]) array); } if (array instanceof byte[]) { return Arrays.toString((byte[]) array); } if (array instanceof double[]) { return Arrays.toString((double[]) array); } if (array instanceof float[]) { return Arrays.toString((float[]) array); } if (array instanceof boolean[]) { return Arrays.toString((boolean[]) array); } if (array instanceof char[]) { return Arrays.toString((char[]) array); } if (array instanceof short[]) { return Arrays.toString((short[]) array); } if (array.getClass().isArray()) { return ""; } else { throw new IllegalArgumentException("The given argument is no array."); } } /** * Replaces control characters by their escape-coded version. For example, * if the string contains a line break character ('\n'), this character will * be replaced by the two characters backslash '\' and 'n'. As a consequence, the * resulting string will not contain any more control characters. * * @param str The string in which to replace the control characters. * @return The string with the replaced characters. */ public static String showControlCharacters(String str) { int len = str.length(); StringBuilder sb = new StringBuilder(); for (int i = 0; i < len; i += 1) { char c = str.charAt(i); switch (c) { case '\b': sb.append("\\b"); break; case '\t': sb.append("\\t"); break; case '\n': sb.append("\\n"); break; case '\f': sb.append("\\f"); break; case '\r': sb.append("\\r"); break; default: sb.append(c); } } return sb.toString(); } /** * Creates a random string with a length within the given interval. The string contains only characters that * can be represented as a single code point. * * @param rnd The random used to create the strings. * @param minLength The minimum string length. * @param maxLength The maximum string length (inclusive). * @return A random String. */ public static String getRandomString(Random rnd, int minLength, int maxLength) { int len = rnd.nextInt(maxLength - minLength + 1) + minLength; char[] data = new char[len]; for (int i = 0; i < data.length; i++) { data[i] = (char) (rnd.nextInt(0x7fff) + 1); } return new String(data); } /** * Creates a random string with a length within the given interval. The string contains only characters that * can be represented as a single code point. * * @param rnd The random used to create the strings. * @param minLength The minimum string length. * @param maxLength The maximum string length (inclusive). * @param minValue The minimum character value to occur. * @param maxValue The maximum character value to occur. * @return A random String. */ public static String getRandomString(Random rnd, int minLength, int maxLength, char minValue, char maxValue) { int len = rnd.nextInt(maxLength - minLength + 1) + minLength; char[] data = new char[len]; int diff = maxValue - minValue + 1; for (int i = 0; i < data.length; i++) { data[i] = (char) (rnd.nextInt(diff) + minValue); } return new String(data); } /** * Creates a random alphanumeric string of given length. * * @param rnd The random number generator to use. * @param length The number of alphanumeric characters to append. */ public static String generateRandomAlphanumericString(Random rnd, int length) { checkNotNull(rnd); checkArgument(length >= 0); StringBuilder buffer = new StringBuilder(length); for (int i = 0; i < length; i++) { buffer.append(nextAlphanumericChar(rnd)); } return buffer.toString(); } private static char nextAlphanumericChar(Random rnd) { int which = rnd.nextInt(62); char c; if (which < 10) { c = (char) ('0' + which); } else if (which < 36) { c = (char) ('A' - 10 + which); } else { c = (char) ('a' - 36 + which); } return c; } /** * Writes a String to the given output. * The written string can be read with {@link #readString(DataInputView)}. * * @param str The string to write * @param out The output to write to * * @throws IOException Thrown, if the writing or the serialization fails. */ public static void writeString(@Nonnull String str, DataOutputView out) throws IOException { checkNotNull(str); StringValue.writeString(str, out); } /** * Reads a non-null String from the given input. * * @param in The input to read from * @return The deserialized String * * @throws IOException Thrown, if the reading or the deserialization fails. */ public static String readString(DataInputView in) throws IOException { return StringValue.readString(in); } /** * Writes a String to the given output. The string may be null. * The written string can be read with {@link #readNullableString(DataInputView)}- * * @param str The string to write, or null. * @param out The output to write to. * * @throws IOException Thrown, if the writing or the serialization fails. */ public static void writeNullableString(@Nullable String str, DataOutputView out) throws IOException { if (str != null) { out.writeBoolean(true); writeString(str, out); } else { out.writeBoolean(false); } } /** * Reads a String from the given input. The string may be null and must have been written with * {@link #writeNullableString(String, DataOutputView)}. * * @param in The input to read from. * @return The deserialized string, or null. * * @throws IOException Thrown, if the reading or the deserialization fails. */ public static @Nullable String readNullableString(DataInputView in) throws IOException { if (in.readBoolean()) { return readString(in); } else { return null; } } /** * Checks if the string is null, empty, or contains only whitespace characters. * A whitespace character is defined via {@link Character#isWhitespace(char)}. * * @param str The string to check * @return True, if the string is null or blank, false otherwise. */ public static boolean isNullOrWhitespaceOnly(String str) { if (str == null || str.length() == 0) { return true; } final int len = str.length(); for (int i = 0; i < len; i++) { if (!Character.isWhitespace(str.charAt(i))) { return false; } } return true; } /** * If both string arguments are non-null, this method concatenates them with ' and '. * If only one of the arguments is non-null, this method returns the non-null argument. * If both arguments are null, this method returns null. * * @param s1 The first string argument * @param s2 The second string argument * * @return The concatenated string, or non-null argument, or null */ @Nullable public static String concatenateWithAnd(@Nullable String s1, @Nullable String s2) { if (s1 != null) { return s2 == null ? s1 : s1 + " and " + s2; } else { return s2; } } /** * Generates a string containing a comma-separated list of values in double-quotes. * Uses lower-cased values returned from {@link Object#toString()} method for each element in the given array. * Null values are skipped. * * @param values array of elements for the list * * @return The string with quoted list of elements */ public static String toQuotedListString(Object[] values) { return Arrays.stream(values).filter(Objects::nonNull) .map(v -> v.toString().toLowerCase()) .collect(Collectors.joining(", ", "\"", "\"")); } // ------------------------------------------------------------------------ /** Prevent instantiation of this utility class. */ private StringUtils() {} } ================================================ FILE: flink-learning-core/src/main/java/com/zhisheng/core/utils/TimeUtils.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zhisheng.core.utils; import java.time.Duration; import java.util.Locale; import static org.apache.flink.util.Preconditions.checkArgument; import static org.apache.flink.util.Preconditions.checkNotNull; /** * Collection of utilities about time intervals. */ public class TimeUtils { /** * Parse the given string to a java {@link Duration}. * The string is like "123ms", "321s", "12min" and such. * * @param text string to parse. */ public static Duration parseDuration(String text) { checkNotNull(text, "text"); final String trimmed = text.trim(); checkArgument(!trimmed.isEmpty(), "argument is an empty- or whitespace-only string"); final int len = trimmed.length(); int pos = 0; char current; while (pos < len && (current = trimmed.charAt(pos)) >= '0' && current <= '9') { pos++; } final String number = trimmed.substring(0, pos); final String unit = trimmed.substring(pos).trim().toLowerCase(Locale.US); if (number.isEmpty()) { throw new NumberFormatException("text does not start with a number"); } final long value; try { value = Long.parseLong(number); // this throws a NumberFormatException on overflow } catch (NumberFormatException e) { throw new IllegalArgumentException("The value '" + number + "' cannot be re represented as 64bit number (numeric overflow)."); } final long multiplier; if (unit.isEmpty()) { multiplier = 1L; } else { if (matchTimeUnit(unit, TimeUnit.MILLISECONDS)) { multiplier = 1L; } else if (matchTimeUnit(unit, TimeUnit.SECONDS)) { multiplier = 1000L; } else if (matchTimeUnit(unit, TimeUnit.MINUTES)) { multiplier = 1000L * 60L; } else if (matchTimeUnit(unit, TimeUnit.HOURS)) { multiplier = 1000L * 60L * 60L; } else { throw new IllegalArgumentException("Time interval unit '" + unit + "' does not match any of the recognized units: " + TimeUnit.getAllUnits()); } } final long result = value * multiplier; // check for overflow if (result / multiplier != value) { throw new IllegalArgumentException("The value '" + text + "' cannot be re represented as 64bit number of bytes (numeric overflow)."); } return Duration.ofMillis(result); } private static boolean matchTimeUnit(String text, TimeUnit unit) { return text.equals(unit.getUnit()); } /** * Enum which defines time unit, mostly used to parse value from configuration file. */ private enum TimeUnit { MILLISECONDS("ms"), SECONDS("s"), MINUTES("min"), HOURS("h"); private String unit; TimeUnit(String unit) { this.unit = unit; } public String getUnit() { return unit; } public static String getAllUnits() { return String.join(" | ", new String[]{ MILLISECONDS.getUnit(), SECONDS.getUnit(), MINUTES.getUnit(), HOURS.getUnit() }); } } } ================================================ FILE: flink-learning-datalake/README.md ================================================ ### Flink-learning-datalake Flink Data Lake 项目里面包含了数据湖四大组件的基础、原理、实战、应用、源码相关内容 ### 数据湖资料 + [delta lake 书籍](https://books.japila.pl/delta-lake-internals/overview/) ### 数据湖论文 + [Lakehouse Architecture](http://cidrdb.org/cidr2021/papers/cidr2021_paper17.pdf) ================================================ FILE: flink-learning-datalake/flink-learning-datalake-deltalake/pom.xml ================================================ flink-learning-datalake com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 flink-learning-datalake-deltalake jar io.delta delta-flink ${delta.version} io.delta delta-standalone_${scala.binary.version} ${delta.version} org.apache.flink flink-table-planner_${scala.binary.version} ${flink.version} provided org.apache.flink flink-parquet ${flink.version} org.apache.hadoop hadoop-client 3.4.1 ================================================ FILE: flink-learning-datalake/flink-learning-datalake-deltalake/src/main/java/com/zhisheng/datalake/delta/DeltaLakeExample.java ================================================ package com.zhisheng.datalake.delta; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.table.api.EnvironmentSettings; import org.apache.flink.table.api.bridge.java.StreamTableEnvironment; /** * Desc: Flink Delta Lake Example * 通过 Flink SQL 实现 Delta Lake 表的创建、写入和查询 * *

本示例演示: * 1. 使用 Flink SQL 创建 Delta Lake 表 * 2. 向 Delta Lake 表插入批量数据 * 3. 查询 Delta Lake 表中的数据 * *

使用前需要: * 1. 确保 Delta Flink Connector 依赖已正确引入 * 2. 确保本地 /tmp/delta_table 路径可写 * 3. 确保 Hadoop 依赖已正确配置 * *

注意:Delta Lake Flink Connector 的版本兼容性, * 请参考 Delta Lake 官方文档确认与当前 Flink 版本的兼容关系 * * Created by zhisheng */ public class DeltaLakeExample { public static void main(String[] args) throws Exception { StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.setParallelism(1); env.enableCheckpointing(5000); EnvironmentSettings settings = EnvironmentSettings.newInstance() .inStreamingMode() .build(); StreamTableEnvironment tEnv = StreamTableEnvironment.create(env, settings); // 创建 Delta Lake Catalog tEnv.executeSql("CREATE CATALOG delta_catalog WITH (\n" + " 'type' = 'delta-catalog',\n" + " 'catalog-type' = 'in-memory'\n" + ")"); tEnv.executeSql("USE CATALOG delta_catalog"); tEnv.executeSql("CREATE DATABASE IF NOT EXISTS delta_db"); tEnv.executeSql("USE delta_db"); // 创建 Delta Lake 表 tEnv.executeSql("CREATE TABLE IF NOT EXISTS delta_users (\n" + " id INT,\n" + " name STRING,\n" + " age INT,\n" + " dt STRING\n" + ") PARTITIONED BY (dt)\n" + "WITH (\n" + " 'connector' = 'delta',\n" + " 'table-path' = '/tmp/delta_table'\n" + ")"); // 向 Delta Lake 表插入数据 tEnv.executeSql("INSERT INTO delta_users VALUES\n" + " (1, 'Alice', 30, '2024-01-01'),\n" + " (2, 'Bob', 25, '2024-01-01'),\n" + " (3, 'Charlie', 35, '2024-01-02')"); // 查询 Delta Lake 表中的数据 tEnv.executeSql("SELECT * FROM delta_users").print(); } } ================================================ FILE: flink-learning-datalake/flink-learning-datalake-hudi/pom.xml ================================================ flink-learning-datalake com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 flink-learning-datalake-hudi jar org.apache.hudi hudi-flink1.20-bundle ${hudi.version} org.apache.flink flink-table-planner_${scala.binary.version} ${flink.version} provided ================================================ FILE: flink-learning-datalake/flink-learning-datalake-hudi/src/main/java/com/zhisheng/datalake/hudi/HudiCDCSyncExample.java ================================================ package com.zhisheng.datalake.hudi; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.table.api.EnvironmentSettings; import org.apache.flink.table.api.bridge.java.StreamTableEnvironment; /** * Desc: Flink CDC to Hudi Example * 通过 Flink CDC 实时同步 MySQL 数据到 Hudi 数据湖 * *

本示例演示湖仓一体的核心场景: * 1. 使用 Flink CDC 捕获 MySQL 数据变更 * 2. 创建 Hudi 数据湖表作为目标 * 3. 实现 MySQL -> Hudi 的实时数据同步 * *

使用前需要: * 1. 启动 MySQL 并开启 binlog * 2. 创建源表并插入测试数据 * 3. 引入 flink-sql-connector-mysql-cdc 依赖 * * Created by zhisheng */ public class HudiCDCSyncExample { public static void main(String[] args) throws Exception { StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.setParallelism(1); env.enableCheckpointing(5000); EnvironmentSettings settings = EnvironmentSettings.newInstance() .inStreamingMode() .build(); StreamTableEnvironment tEnv = StreamTableEnvironment.create(env, settings); // 创建 MySQL CDC 源表 String mysqlSourceDDL = "CREATE TABLE mysql_orders (\n" + " order_id INT NOT NULL,\n" + " order_date TIMESTAMP(3),\n" + " customer_name STRING,\n" + " product_name STRING,\n" + " price DECIMAL(10, 2),\n" + " order_status STRING,\n" + " PRIMARY KEY (order_id) NOT ENFORCED\n" + ") WITH (\n" + " 'connector' = 'mysql-cdc',\n" + " 'hostname' = 'localhost',\n" + " 'port' = '3306',\n" + " 'username' = 'root',\n" + " 'password' = '123456',\n" + " 'database-name' = 'mydb',\n" + " 'table-name' = 'orders'\n" + ")"; tEnv.executeSql(mysqlSourceDDL); // 创建 Hudi 目标表 String hudiSinkDDL = "CREATE TABLE hudi_orders (\n" + " order_id INT PRIMARY KEY NOT ENFORCED,\n" + " order_date TIMESTAMP(3),\n" + " customer_name STRING,\n" + " product_name STRING,\n" + " price DECIMAL(10, 2),\n" + " order_status STRING,\n" + " dt STRING\n" + ") PARTITIONED BY (dt)\n" + "WITH (\n" + " 'connector' = 'hudi',\n" + " 'path' = '/tmp/hudi_orders',\n" + " 'table.type' = 'COPY_ON_WRITE',\n" + " 'hoodie.datasource.write.recordkey.field' = 'order_id',\n" + " 'hoodie.datasource.write.precombine.field' = 'order_date',\n" + " 'write.tasks' = '1',\n" + " 'compaction.tasks' = '1'\n" + ")"; tEnv.executeSql(hudiSinkDDL); // 将 MySQL CDC 数据实时同步到 Hudi 数据湖 tEnv.executeSql("INSERT INTO hudi_orders " + "SELECT order_id, order_date, customer_name, product_name, price, order_status, " + "DATE_FORMAT(order_date, 'yyyy-MM-dd') " + "FROM mysql_orders"); } } ================================================ FILE: flink-learning-datalake/flink-learning-datalake-hudi/src/main/java/com/zhisheng/datalake/hudi/HudiDataLakeExample.java ================================================ package com.zhisheng.datalake.hudi; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.table.api.EnvironmentSettings; import org.apache.flink.table.api.bridge.java.StreamTableEnvironment; /** * Desc: Flink Hudi Data Lake Example * 通过 Flink SQL 实现 Hudi 表的创建、写入和查询 * *

本示例演示: * 1. 使用 Flink SQL 创建 Hudi MOR(Merge On Read)表 * 2. 向 Hudi 表插入批量数据 * 3. 查询 Hudi 表中的数据 * *

使用前需要: * 1. 确保 Hudi Flink Bundle 依赖已正确引入 * 2. 确保本地 /tmp/hudi_table 路径可写 * * Created by zhisheng */ public class HudiDataLakeExample { public static void main(String[] args) throws Exception { StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.setParallelism(1); env.enableCheckpointing(5000); EnvironmentSettings settings = EnvironmentSettings.newInstance() .inStreamingMode() .build(); StreamTableEnvironment tEnv = StreamTableEnvironment.create(env, settings); // 创建 Hudi 表(Merge On Read 类型) String createTableSQL = "CREATE TABLE hudi_users (\n" + " id INT PRIMARY KEY NOT ENFORCED,\n" + " name STRING,\n" + " age INT,\n" + " ts TIMESTAMP(3),\n" + " `partition` STRING\n" + ") PARTITIONED BY (`partition`)\n" + "WITH (\n" + " 'connector' = 'hudi',\n" + " 'path' = '/tmp/hudi_table',\n" + " 'table.type' = 'MERGE_ON_READ',\n" + " 'hoodie.datasource.write.recordkey.field' = 'id',\n" + " 'hoodie.datasource.write.precombine.field' = 'ts'\n" + ")"; tEnv.executeSql(createTableSQL); // 创建 print 结果表 String sinkDDL = "CREATE TABLE print_sink (\n" + " id INT,\n" + " name STRING,\n" + " age INT,\n" + " ts TIMESTAMP(3),\n" + " `partition` STRING\n" + ") WITH (\n" + " 'connector' = 'print'\n" + ")"; tEnv.executeSql(sinkDDL); // 向 Hudi 表插入数据 String insertSQL = "INSERT INTO hudi_users VALUES\n" + " (1, 'Alice', 30, TIMESTAMP '2024-01-01 00:00:00', '2024-01-01'),\n" + " (2, 'Bob', 25, TIMESTAMP '2024-01-01 00:00:00', '2024-01-01'),\n" + " (3, 'Charlie', 35, TIMESTAMP '2024-01-02 00:00:00', '2024-01-02')"; tEnv.executeSql(insertSQL); // 查询 Hudi 表中的数据并输出到 print sink tEnv.executeSql("INSERT INTO print_sink SELECT * FROM hudi_users"); } } ================================================ FILE: flink-learning-datalake/flink-learning-datalake-hudi/src/main/java/com/zhisheng/datalake/hudi/HudiStreamingWriteExample.java ================================================ package com.zhisheng.datalake.hudi; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.table.api.EnvironmentSettings; import org.apache.flink.table.api.bridge.java.StreamTableEnvironment; /** * Desc: Flink Hudi Streaming Write Example * 通过 Flink SQL 实现从 Kafka 实时写入 Hudi 表 * *

本示例演示: * 1. 创建 Kafka 源表(模拟实时数据流) * 2. 创建 Hudi 目标表 * 3. 使用 INSERT INTO 将 Kafka 数据实时写入 Hudi 表 * *

使用前需要: * 1. 启动 Kafka 并创建 user_behavior 主题 * 2. 向 Kafka 主题发送 JSON 格式的数据 * * Created by zhisheng */ public class HudiStreamingWriteExample { public static void main(String[] args) throws Exception { StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.setParallelism(1); env.enableCheckpointing(5000); EnvironmentSettings settings = EnvironmentSettings.newInstance() .inStreamingMode() .build(); StreamTableEnvironment tEnv = StreamTableEnvironment.create(env, settings); // 创建 Kafka 源表 String kafkaSourceDDL = "CREATE TABLE kafka_source (\n" + " user_id INT,\n" + " item_id INT,\n" + " behavior STRING,\n" + " ts TIMESTAMP(3),\n" + " WATERMARK FOR ts AS ts - INTERVAL '5' SECOND\n" + ") WITH (\n" + " 'connector' = 'kafka',\n" + " 'topic' = 'user_behavior',\n" + " 'properties.bootstrap.servers' = 'localhost:9092',\n" + " 'properties.group.id' = 'hudi-group',\n" + " 'scan.startup.mode' = 'earliest-offset',\n" + " 'format' = 'json'\n" + ")"; tEnv.executeSql(kafkaSourceDDL); // 创建 Hudi 目标表 String hudiSinkDDL = "CREATE TABLE hudi_user_behavior (\n" + " user_id INT PRIMARY KEY NOT ENFORCED,\n" + " item_id INT,\n" + " behavior STRING,\n" + " ts TIMESTAMP(3),\n" + " dt STRING\n" + ") PARTITIONED BY (dt)\n" + "WITH (\n" + " 'connector' = 'hudi',\n" + " 'path' = '/tmp/hudi_user_behavior',\n" + " 'table.type' = 'MERGE_ON_READ',\n" + " 'hoodie.datasource.write.recordkey.field' = 'user_id',\n" + " 'hoodie.datasource.write.precombine.field' = 'ts',\n" + " 'write.tasks' = '1',\n" + " 'compaction.tasks' = '1',\n" + " 'compaction.async.enabled' = 'true',\n" + " 'compaction.trigger.strategy' = 'num_commits',\n" + " 'compaction.delta_commits' = '5'\n" + ")"; tEnv.executeSql(hudiSinkDDL); // 将 Kafka 数据实时写入 Hudi 表 tEnv.executeSql("INSERT INTO hudi_user_behavior " + "SELECT user_id, item_id, behavior, ts, DATE_FORMAT(ts, 'yyyy-MM-dd') " + "FROM kafka_source"); } } ================================================ FILE: flink-learning-datalake/flink-learning-datalake-iceberg/pom.xml ================================================ flink-learning-datalake com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 flink-learning-datalake-iceberg jar org.apache.iceberg iceberg-flink-runtime-1.20 ${iceberg.version} org.apache.flink flink-table-planner_${scala.binary.version} ${flink.version} provided ================================================ FILE: flink-learning-datalake/flink-learning-datalake-iceberg/src/main/java/com/zhisheng/datalake/iceberg/IcebergCDCSyncExample.java ================================================ package com.zhisheng.datalake.iceberg; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.table.api.EnvironmentSettings; import org.apache.flink.table.api.bridge.java.StreamTableEnvironment; /** * Desc: Flink CDC to Iceberg Example * 通过 Flink CDC 实时同步 MySQL 数据到 Iceberg 数据湖 * *

本示例演示湖仓一体的核心场景: * 1. 使用 Flink CDC 捕获 MySQL 数据变更 * 2. 创建 Iceberg Catalog 和数据湖表 * 3. 实现 MySQL -> Iceberg 的实时数据同步 * *

使用前需要: * 1. 启动 MySQL 并开启 binlog * 2. 创建源表并插入测试数据 * 3. 引入 flink-sql-connector-mysql-cdc 依赖 * * Created by zhisheng */ public class IcebergCDCSyncExample { public static void main(String[] args) throws Exception { StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.setParallelism(1); env.enableCheckpointing(5000); EnvironmentSettings settings = EnvironmentSettings.newInstance() .inStreamingMode() .build(); StreamTableEnvironment tEnv = StreamTableEnvironment.create(env, settings); // 创建 MySQL CDC 源表(在默认 Catalog 中) String mysqlSourceDDL = "CREATE TABLE mysql_orders (\n" + " order_id INT NOT NULL,\n" + " order_date TIMESTAMP(3),\n" + " customer_name STRING,\n" + " product_name STRING,\n" + " price DECIMAL(10, 2),\n" + " order_status STRING,\n" + " PRIMARY KEY (order_id) NOT ENFORCED\n" + ") WITH (\n" + " 'connector' = 'mysql-cdc',\n" + " 'hostname' = 'localhost',\n" + " 'port' = '3306',\n" + " 'username' = 'root',\n" + " 'password' = '123456',\n" + " 'database-name' = 'mydb',\n" + " 'table-name' = 'orders'\n" + ")"; tEnv.executeSql(mysqlSourceDDL); // 创建 Iceberg Catalog tEnv.executeSql("CREATE CATALOG iceberg_catalog WITH (\n" + " 'type' = 'iceberg',\n" + " 'catalog-type' = 'hadoop',\n" + " 'warehouse' = '/tmp/iceberg_warehouse'\n" + ")"); tEnv.executeSql("USE CATALOG iceberg_catalog"); tEnv.executeSql("CREATE DATABASE IF NOT EXISTS iceberg_db"); tEnv.executeSql("USE iceberg_db"); // 创建 Iceberg 目标表 tEnv.executeSql("CREATE TABLE IF NOT EXISTS iceberg_orders (\n" + " order_id INT,\n" + " order_date TIMESTAMP(3),\n" + " customer_name STRING,\n" + " product_name STRING,\n" + " price DECIMAL(10, 2),\n" + " order_status STRING,\n" + " dt STRING\n" + ") PARTITIONED BY (dt)"); // 将 MySQL CDC 数据实时同步到 Iceberg 数据湖 tEnv.executeSql("INSERT INTO iceberg_orders " + "SELECT order_id, order_date, customer_name, product_name, price, order_status, " + "DATE_FORMAT(order_date, 'yyyy-MM-dd') " + "FROM default_catalog.default_database.mysql_orders"); } } ================================================ FILE: flink-learning-datalake/flink-learning-datalake-iceberg/src/main/java/com/zhisheng/datalake/iceberg/IcebergDataLakeExample.java ================================================ package com.zhisheng.datalake.iceberg; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.table.api.EnvironmentSettings; import org.apache.flink.table.api.bridge.java.StreamTableEnvironment; /** * Desc: Flink Iceberg Data Lake Example * 通过 Flink SQL 实现 Iceberg 表的创建、写入和查询 * *

本示例演示: * 1. 创建 Iceberg Hadoop Catalog * 2. 在 Catalog 中创建数据库和表 * 3. 向 Iceberg 表插入数据 * 4. 查询 Iceberg 表中的数据 * *

使用前需要: * 1. 确保 Iceberg Flink Runtime 依赖已正确引入 * 2. 确保本地 /tmp/iceberg_warehouse 路径可写 * * Created by zhisheng */ public class IcebergDataLakeExample { public static void main(String[] args) throws Exception { StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.setParallelism(1); env.enableCheckpointing(5000); EnvironmentSettings settings = EnvironmentSettings.newInstance() .inStreamingMode() .build(); StreamTableEnvironment tEnv = StreamTableEnvironment.create(env, settings); // 创建 Iceberg Hadoop Catalog tEnv.executeSql("CREATE CATALOG iceberg_catalog WITH (\n" + " 'type' = 'iceberg',\n" + " 'catalog-type' = 'hadoop',\n" + " 'warehouse' = '/tmp/iceberg_warehouse'\n" + ")"); tEnv.executeSql("USE CATALOG iceberg_catalog"); tEnv.executeSql("CREATE DATABASE IF NOT EXISTS iceberg_db"); tEnv.executeSql("USE iceberg_db"); // 创建 Iceberg 分区表 tEnv.executeSql("CREATE TABLE IF NOT EXISTS iceberg_users (\n" + " id INT,\n" + " name STRING,\n" + " age INT,\n" + " dt STRING\n" + ") PARTITIONED BY (dt)"); // 向 Iceberg 表插入数据 tEnv.executeSql("INSERT INTO iceberg_users VALUES\n" + " (1, 'Alice', 30, '2024-01-01'),\n" + " (2, 'Bob', 25, '2024-01-01'),\n" + " (3, 'Charlie', 35, '2024-01-02')"); // 查询 Iceberg 表中的数据 tEnv.executeSql("SELECT * FROM iceberg_users").print(); } } ================================================ FILE: flink-learning-datalake/flink-learning-datalake-iceberg/src/main/java/com/zhisheng/datalake/iceberg/IcebergStreamingWriteExample.java ================================================ package com.zhisheng.datalake.iceberg; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.table.api.EnvironmentSettings; import org.apache.flink.table.api.bridge.java.StreamTableEnvironment; /** * Desc: Flink Iceberg Streaming Write Example * 通过 Flink SQL 实现从 Kafka 实时写入 Iceberg 表 * *

本示例演示: * 1. 创建 Iceberg Catalog 和数据库 * 2. 在默认 Catalog 中创建 Kafka 源表 * 3. 创建 Iceberg 目标表 * 4. 使用 INSERT INTO 将 Kafka 数据实时写入 Iceberg 表 * *

使用前需要: * 1. 启动 Kafka 并创建 user_behavior 主题 * 2. 向 Kafka 主题发送 JSON 格式的数据 * * Created by zhisheng */ public class IcebergStreamingWriteExample { public static void main(String[] args) throws Exception { StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.setParallelism(1); env.enableCheckpointing(5000); EnvironmentSettings settings = EnvironmentSettings.newInstance() .inStreamingMode() .build(); StreamTableEnvironment tEnv = StreamTableEnvironment.create(env, settings); // 创建 Kafka 源表(在默认 Catalog 中) String kafkaSourceDDL = "CREATE TABLE kafka_source (\n" + " user_id INT,\n" + " item_id INT,\n" + " behavior STRING,\n" + " ts TIMESTAMP(3),\n" + " WATERMARK FOR ts AS ts - INTERVAL '5' SECOND\n" + ") WITH (\n" + " 'connector' = 'kafka',\n" + " 'topic' = 'user_behavior',\n" + " 'properties.bootstrap.servers' = 'localhost:9092',\n" + " 'properties.group.id' = 'iceberg-group',\n" + " 'scan.startup.mode' = 'earliest-offset',\n" + " 'format' = 'json'\n" + ")"; tEnv.executeSql(kafkaSourceDDL); // 创建 Iceberg Catalog tEnv.executeSql("CREATE CATALOG iceberg_catalog WITH (\n" + " 'type' = 'iceberg',\n" + " 'catalog-type' = 'hadoop',\n" + " 'warehouse' = '/tmp/iceberg_warehouse'\n" + ")"); tEnv.executeSql("USE CATALOG iceberg_catalog"); tEnv.executeSql("CREATE DATABASE IF NOT EXISTS iceberg_db"); tEnv.executeSql("USE iceberg_db"); // 创建 Iceberg 目标表 tEnv.executeSql("CREATE TABLE IF NOT EXISTS iceberg_user_behavior (\n" + " user_id INT,\n" + " item_id INT,\n" + " behavior STRING,\n" + " ts TIMESTAMP(3),\n" + " dt STRING\n" + ") PARTITIONED BY (dt)"); // 将 Kafka 数据实时写入 Iceberg 表 tEnv.executeSql("INSERT INTO iceberg_user_behavior " + "SELECT user_id, item_id, behavior, ts, DATE_FORMAT(ts, 'yyyy-MM-dd') " + "FROM default_catalog.default_database.kafka_source"); } } ================================================ FILE: flink-learning-datalake/flink-learning-paimon/README.md ================================================ ### Flink-learning-paimon + [Flink Table Store 是什么?](https://www.yuque.com/lijinsongzhixin/qxwonh/iktr4c) + [Flink Table Store ——从计算到存储提升流批统一端到端用户体验](https://mp.weixin.qq.com/s/siMnKbWzVFU4fic5-XFoRw) ### 架构 ![](https://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/2022-12-16-075225.jpg) ================================================ FILE: flink-learning-datalake/flink-learning-paimon/pom.xml ================================================ flink-learning-datalake com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 flink-learning-paimon ================================================ FILE: flink-learning-datalake/flink-learning-paimon/src/main/java/com/zhisheng/datalake/paimon/PaimonCDCSyncExample.java ================================================ package com.zhisheng.datalake.paimon; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.table.api.EnvironmentSettings; import org.apache.flink.table.api.bridge.java.StreamTableEnvironment; /** * Desc: Flink CDC to Paimon Example * 通过 Flink CDC 实时同步 MySQL 数据到 Paimon 数据湖 * *

本示例演示湖仓一体的核心场景: * 1. 使用 Flink CDC 捕获 MySQL 数据变更 * 2. 创建 Paimon Catalog 和数据湖主键表 * 3. 实现 MySQL -> Paimon 的实时数据同步 * *

Paimon 作为流式数据湖存储,天然支持 CDC 数据的写入 * 主键表会自动处理 INSERT/UPDATE/DELETE 操作 * *

使用前需要: * 1. 启动 MySQL 并开启 binlog * 2. 创建源表并插入测试数据 * 3. 引入 flink-sql-connector-mysql-cdc 依赖 * * Created by zhisheng */ public class PaimonCDCSyncExample { public static void main(String[] args) throws Exception { StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.setParallelism(1); env.enableCheckpointing(5000); EnvironmentSettings settings = EnvironmentSettings.newInstance() .inStreamingMode() .build(); StreamTableEnvironment tEnv = StreamTableEnvironment.create(env, settings); // 创建 MySQL CDC 源表(在默认 Catalog 中) String mysqlSourceDDL = "CREATE TABLE mysql_orders (\n" + " order_id INT NOT NULL,\n" + " order_date TIMESTAMP(3),\n" + " customer_name STRING,\n" + " product_name STRING,\n" + " price DECIMAL(10, 2),\n" + " order_status STRING,\n" + " PRIMARY KEY (order_id) NOT ENFORCED\n" + ") WITH (\n" + " 'connector' = 'mysql-cdc',\n" + " 'hostname' = 'localhost',\n" + " 'port' = '3306',\n" + " 'username' = 'root',\n" + " 'password' = '123456',\n" + " 'database-name' = 'mydb',\n" + " 'table-name' = 'orders'\n" + ")"; tEnv.executeSql(mysqlSourceDDL); // 创建 Paimon Catalog tEnv.executeSql("CREATE CATALOG paimon_catalog WITH (\n" + " 'type' = 'paimon',\n" + " 'warehouse' = '/tmp/paimon_warehouse'\n" + ")"); tEnv.executeSql("USE CATALOG paimon_catalog"); tEnv.executeSql("CREATE DATABASE IF NOT EXISTS paimon_db"); tEnv.executeSql("USE paimon_db"); // 创建 Paimon 主键目标表 tEnv.executeSql("CREATE TABLE IF NOT EXISTS paimon_orders (\n" + " order_id INT,\n" + " order_date TIMESTAMP(3),\n" + " customer_name STRING,\n" + " product_name STRING,\n" + " price DECIMAL(10, 2),\n" + " order_status STRING,\n" + " dt STRING,\n" + " PRIMARY KEY (order_id, dt) NOT ENFORCED\n" + ") PARTITIONED BY (dt)"); // 将 MySQL CDC 数据实时同步到 Paimon 数据湖 tEnv.executeSql("INSERT INTO paimon_orders " + "SELECT order_id, order_date, customer_name, product_name, price, order_status, " + "DATE_FORMAT(order_date, 'yyyy-MM-dd') " + "FROM default_catalog.default_database.mysql_orders"); } } ================================================ FILE: flink-learning-datalake/flink-learning-paimon/src/main/java/com/zhisheng/datalake/paimon/PaimonDataLakeExample.java ================================================ package com.zhisheng.datalake.paimon; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.table.api.EnvironmentSettings; import org.apache.flink.table.api.bridge.java.StreamTableEnvironment; /** * Desc: Flink Paimon (原 Flink Table Store) Data Lake Example * 通过 Flink SQL 实现 Paimon 表的创建、写入和查询 * *

本示例演示: * 1. 创建 Paimon Catalog(基于文件系统) * 2. 在 Catalog 中创建数据库和主键表 * 3. 向 Paimon 表插入数据 * 4. 查询 Paimon 表中的数据 * *

使用前需要: * 1. 确保 Paimon Flink 依赖已正确引入 * 2. 确保本地 /tmp/paimon_warehouse 路径可写 * *

Paimon 支持主键表(Primary Key Table)和仅追加表(Append Only Table) * 本示例使用主键表,支持数据的更新和删除操作 * * Created by zhisheng */ public class PaimonDataLakeExample { public static void main(String[] args) throws Exception { StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.setParallelism(1); env.enableCheckpointing(5000); EnvironmentSettings settings = EnvironmentSettings.newInstance() .inStreamingMode() .build(); StreamTableEnvironment tEnv = StreamTableEnvironment.create(env, settings); // 创建 Paimon Catalog(基于文件系统) tEnv.executeSql("CREATE CATALOG paimon_catalog WITH (\n" + " 'type' = 'paimon',\n" + " 'warehouse' = '/tmp/paimon_warehouse'\n" + ")"); tEnv.executeSql("USE CATALOG paimon_catalog"); tEnv.executeSql("CREATE DATABASE IF NOT EXISTS paimon_db"); tEnv.executeSql("USE paimon_db"); // 创建 Paimon 主键表(Primary Key Table) tEnv.executeSql("CREATE TABLE IF NOT EXISTS paimon_users (\n" + " id INT,\n" + " name STRING,\n" + " age INT,\n" + " dt STRING,\n" + " PRIMARY KEY (id, dt) NOT ENFORCED\n" + ") PARTITIONED BY (dt)"); // 向 Paimon 表插入数据 tEnv.executeSql("INSERT INTO paimon_users VALUES\n" + " (1, 'Alice', 30, '2024-01-01'),\n" + " (2, 'Bob', 25, '2024-01-01'),\n" + " (3, 'Charlie', 35, '2024-01-02')"); // 查询 Paimon 表中的数据 tEnv.executeSql("SELECT * FROM paimon_users").print(); } } ================================================ FILE: flink-learning-datalake/flink-learning-paimon/src/main/java/com/zhisheng/datalake/paimon/PaimonStreamingWriteExample.java ================================================ package com.zhisheng.datalake.paimon; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.table.api.EnvironmentSettings; import org.apache.flink.table.api.bridge.java.StreamTableEnvironment; /** * Desc: Flink Paimon Streaming Write Example * 通过 Flink SQL 实现从 Kafka 实时写入 Paimon 表 * *

本示例演示: * 1. 创建 Paimon Catalog 和数据库 * 2. 在默认 Catalog 中创建 Kafka 源表 * 3. 创建 Paimon 主键表 * 4. 使用 INSERT INTO 将 Kafka 数据实时写入 Paimon 表 * *

使用前需要: * 1. 启动 Kafka 并创建 user_behavior 主题 * 2. 向 Kafka 主题发送 JSON 格式的数据 * *

Paimon 的流式写入会自动合并数据,支持 partial-update 等高级合并模式 * * Created by zhisheng */ public class PaimonStreamingWriteExample { public static void main(String[] args) throws Exception { StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.setParallelism(1); env.enableCheckpointing(5000); EnvironmentSettings settings = EnvironmentSettings.newInstance() .inStreamingMode() .build(); StreamTableEnvironment tEnv = StreamTableEnvironment.create(env, settings); // 创建 Kafka 源表(在默认 Catalog 中) String kafkaSourceDDL = "CREATE TABLE kafka_source (\n" + " user_id INT,\n" + " item_id INT,\n" + " behavior STRING,\n" + " ts TIMESTAMP(3),\n" + " WATERMARK FOR ts AS ts - INTERVAL '5' SECOND\n" + ") WITH (\n" + " 'connector' = 'kafka',\n" + " 'topic' = 'user_behavior',\n" + " 'properties.bootstrap.servers' = 'localhost:9092',\n" + " 'properties.group.id' = 'paimon-group',\n" + " 'scan.startup.mode' = 'earliest-offset',\n" + " 'format' = 'json'\n" + ")"; tEnv.executeSql(kafkaSourceDDL); // 创建 Paimon Catalog tEnv.executeSql("CREATE CATALOG paimon_catalog WITH (\n" + " 'type' = 'paimon',\n" + " 'warehouse' = '/tmp/paimon_warehouse'\n" + ")"); tEnv.executeSql("USE CATALOG paimon_catalog"); tEnv.executeSql("CREATE DATABASE IF NOT EXISTS paimon_db"); tEnv.executeSql("USE paimon_db"); // 创建 Paimon 主键表 tEnv.executeSql("CREATE TABLE IF NOT EXISTS paimon_user_behavior (\n" + " user_id INT,\n" + " item_id INT,\n" + " behavior STRING,\n" + " ts TIMESTAMP(3),\n" + " dt STRING,\n" + " PRIMARY KEY (user_id, dt) NOT ENFORCED\n" + ") PARTITIONED BY (dt)"); // 将 Kafka 数据实时写入 Paimon 表 tEnv.executeSql("INSERT INTO paimon_user_behavior " + "SELECT user_id, item_id, behavior, ts, DATE_FORMAT(ts, 'yyyy-MM-dd') " + "FROM default_catalog.default_database.kafka_source"); } } ================================================ FILE: flink-learning-datalake/pom.xml ================================================ flink-learning com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 flink-learning-datalake pom flink-learning-datalake-hudi flink-learning-datalake-iceberg flink-learning-datalake-deltalake flink-learning-paimon ${target.java.version} ${target.java.version} 1.0.1 1.7.1 3.2.1 1.3.1 ================================================ FILE: flink-learning-examples/README.md ================================================ ### Flink-learning-example 该 module 存放一些简单的测试用例。 #### batch + accumulator + wordcount #### Streaming + async + broadcast + checkpoint + exception + file + iteration + join + machine-learning + remote + sideoutput + socket + watermark + wordcount ================================================ FILE: flink-learning-examples/pom.xml ================================================ flink-learning com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 flink-learning-examples com.zhisheng.flink flink-learning-common ${project.version} mysql mysql-connector-java 5.1.34 org.apache.maven.plugins maven-shade-plugin 3.1.0 false package shade com.zhisheng.examples.streaming.socket.Main reference.conf *:*:*:* META-INF/*.SF META-INF/*.DSA META-INF/*.RSA ================================================ FILE: flink-learning-examples/src/main/java/com/zhisheng/examples/batch/accumulator/Main.java ================================================ package com.zhisheng.examples.batch.accumulator; import org.apache.flink.api.common.functions.FlatMapFunction; import org.apache.flink.api.java.ExecutionEnvironment; import org.apache.flink.api.java.operators.DataSource; import org.apache.flink.api.java.tuple.Tuple2; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.util.Collector; /** * Desc: 累加器 * Created by zhisheng on 2019-06-07 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class Main { public static void main(String[] args) throws Exception { final ParameterTool params = ParameterTool.fromArgs(args); final ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment(); env.getConfig().setGlobalJobParameters(params); DataSource dataSource = env.fromElements(WORDS); dataSource.flatMap(new FlatMapFunction>() { @Override public void flatMap(String line, Collector> out) throws Exception { String[] words = line.split("\\W+"); for (String word : words) { out.collect(new Tuple2<>(word, 1)); } } }) .groupBy(0) .sum(1) .print(); long count = dataSource.count(); System.out.println(count); } private static final String[] WORDS = new String[]{ "To be, or not to be,--that is the question:--", "Whether 'tis nobler in the mind to suffer", "The slings and arrows of outrageous fortune", "Or to take arms against a sea of troubles,", "And by opposing end them?--To die,--to sleep,--", "No more; and by a sleep to say we end", "The heartache, and the thousand natural shocks", "That flesh is heir to,--'tis a consummation", "Devoutly to be wish'd. To die,--to sleep;--", "To sleep! perchance to dream:--ay, there's the rub;", "For in that sleep of death what dreams may come,", "When we have shuffled off this mortal coil,", "Must give us pause: there's the respect", "That makes calamity of so long life;", "For who would bear the whips and scorns of time,", "The oppressor's wrong, the proud man's contumely,", "The pangs of despis'd love, the law's delay,", "The insolence of office, and the spurns", "That patient merit of the unworthy takes,", "When he himself might his quietus make", "With a bare bodkin? who would these fardels bear,", "To grunt and sweat under a weary life,", "But that the dread of something after death,--", "The undiscover'd country, from whose bourn", "No traveller returns,--puzzles the will,", "And makes us rather bear those ills we have", "Than fly to others that we know not of?", "Thus conscience does make cowards of us all;", "And thus the native hue of resolution", "Is sicklied o'er with the pale cast of thought;", "And enterprises of great pith and moment,", "With this regard, their currents turn awry,", "And lose the name of action.--Soft you now!", "The fair Ophelia!--Nymph, in thy orisons", "Be all my sins remember'd." }; } ================================================ FILE: flink-learning-examples/src/main/java/com/zhisheng/examples/batch/accumulator/Main2.java ================================================ package com.zhisheng.examples.batch.accumulator; import org.apache.flink.api.common.accumulators.IntCounter; import org.apache.flink.api.common.functions.RichFlatMapFunction; import org.apache.flink.api.java.ExecutionEnvironment; import org.apache.flink.api.java.operators.DataSource; import org.apache.flink.api.java.tuple.Tuple2; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.configuration.Configuration; import org.apache.flink.util.Collector; /** * Desc: 累加器 * Created by zhisheng on 2019-06-07 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class Main2 { public static void main(String[] args) throws Exception { final ParameterTool params = ParameterTool.fromArgs(args); final ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment(); env.getConfig().setGlobalJobParameters(params); DataSource dataSource = env.fromElements(WORDS); dataSource.flatMap(new RichFlatMapFunction>() { //创建一个累加器 private IntCounter linesNum = new IntCounter(); @Override public void open(Configuration parameters) throws Exception { //注册一个累加器 getRuntimeContext().addAccumulator("linesNum", linesNum); } @Override public void flatMap(String line, Collector> out) throws Exception { String[] words = line.split("\\W+"); for (String word : words) { out.collect(new Tuple2<>(word, 1)); } // 处理每一行数据后 linesNum 递增 linesNum.add(1); } }) .groupBy(0) .sum(1) .print(); int linesNum = env.getLastJobExecutionResult().getAccumulatorResult("linesNum"); System.out.println(linesNum); } private static final String[] WORDS = new String[]{ "To be, or not to be,--that is the question:--", "Whether 'tis nobler in the mind to suffer", "The slings and arrows of outrageous fortune", "Or to take arms against a sea of troubles,", "And by opposing end them?--To die,--to sleep,--", "No more; and by a sleep to say we end", "The heartache, and the thousand natural shocks", "That flesh is heir to,--'tis a consummation", "Devoutly to be wish'd. To die,--to sleep;--", "To sleep! perchance to dream:--ay, there's the rub;", "For in that sleep of death what dreams may come,", "When we have shuffled off this mortal coil,", "Must give us pause: there's the respect", "That makes calamity of so long life;", "For who would bear the whips and scorns of time,", "The oppressor's wrong, the proud man's contumely,", "The pangs of despis'd love, the law's delay,", "The insolence of office, and the spurns", "That patient merit of the unworthy takes,", "When he himself might his quietus make", "With a bare bodkin? who would these fardels bear,", "To grunt and sweat under a weary life,", "But that the dread of something after death,--", "The undiscover'd country, from whose bourn", "No traveller returns,--puzzles the will,", "And makes us rather bear those ills we have", "Than fly to others that we know not of?", "Thus conscience does make cowards of us all;", "And thus the native hue of resolution", "Is sicklied o'er with the pale cast of thought;", "And enterprises of great pith and moment,", "With this regard, their currents turn awry,", "And lose the name of action.--Soft you now!", "The fair Ophelia!--Nymph, in thy orisons", "Be all my sins remember'd." }; } ================================================ FILE: flink-learning-examples/src/main/java/com/zhisheng/examples/batch/wordcount/Main.java ================================================ package com.zhisheng.examples.batch.wordcount; import org.apache.flink.api.common.functions.FlatMapFunction; import org.apache.flink.api.common.functions.ReduceFunction; import org.apache.flink.api.java.ExecutionEnvironment; import org.apache.flink.api.java.tuple.Tuple2; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.util.Collector; /** * batch */ public class Main { public static void main(String[] args) throws Exception { final ParameterTool params = ParameterTool.fromArgs(args); final ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment(); env.getConfig().setGlobalJobParameters(params); env.fromElements(WORDS) .flatMap(new FlatMapFunction>() { @Override public void flatMap(String value, Collector> out) throws Exception { String[] splits = value.toLowerCase().split("\\W+"); for (String split : splits) { if (split.length() > 0) { out.collect(new Tuple2<>(split, 1)); } } } }) .groupBy(0) .reduce(new ReduceFunction>() { @Override public Tuple2 reduce(Tuple2 value1, Tuple2 value2) throws Exception { return new Tuple2<>(value1.f0, value1.f1 + value2.f1); } }) .print(); } private static final String[] WORDS = new String[]{ "To be, or not to be,--that is the question:--", "Whether 'tis nobler in the mind to suffer", "The slings and arrows of outrageous fortune", "Or to take arms against a sea of troubles,", "And by opposing end them?--To die,--to sleep,--", "No more; and by a sleep to say we end", "The heartache, and the thousand natural shocks", "That flesh is heir to,--'tis a consummation", "Devoutly to be wish'd. To die,--to sleep;--", "To sleep! perchance to dream:--ay, there's the rub;", "For in that sleep of death what dreams may come,", "When we have shuffled off this mortal coil,", "Must give us pause: there's the respect", "That makes calamity of so long life;", "For who would bear the whips and scorns of time,", "The oppressor's wrong, the proud man's contumely,", "The pangs of despis'd love, the law's delay,", "The insolence of office, and the spurns", "That patient merit of the unworthy takes,", "When he himself might his quietus make", "With a bare bodkin? who would these fardels bear,", "To grunt and sweat under a weary life,", "But that the dread of something after death,--", "The undiscover'd country, from whose bourn", "No traveller returns,--puzzles the will,", "And makes us rather bear those ills we have", "Than fly to others that we know not of?", "Thus conscience does make cowards of us all;", "And thus the native hue of resolution", "Is sicklied o'er with the pale cast of thought;", "And enterprises of great pith and moment,", "With this regard, their currents turn awry,", "And lose the name of action.--Soft you now!", "The fair Ophelia!--Nymph, in thy orisons", "Be all my sins remember'd." }; } ================================================ FILE: flink-learning-examples/src/main/java/com/zhisheng/examples/streaming/async/AsyncIOExample.java ================================================ package com.zhisheng.examples.streaming.async; import lombok.extern.slf4j.Slf4j; import org.apache.flink.api.common.functions.FlatMapFunction; import org.apache.flink.api.java.tuple.Tuple2; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.configuration.Configuration; import org.apache.flink.runtime.state.filesystem.FsStateBackend; import org.apache.flink.streaming.api.CheckpointingMode; import org.apache.flink.streaming.api.TimeCharacteristic; import org.apache.flink.streaming.api.checkpoint.ListCheckpointed; import org.apache.flink.streaming.api.datastream.AsyncDataStream; import org.apache.flink.streaming.api.datastream.DataStream; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.api.functions.async.AsyncFunction; import org.apache.flink.streaming.api.functions.async.ResultFuture; import org.apache.flink.streaming.api.functions.async.RichAsyncFunction; import org.apache.flink.streaming.api.functions.source.SourceFunction; import org.apache.flink.util.Collector; import org.apache.flink.util.ExecutorUtils; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; /** * Example to show how to use AsyncFunction. * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ @Slf4j public class AsyncIOExample { private static final String EXACTLY_ONCE_MODE = "exactly_once"; private static final String EVENT_TIME = "EventTime"; private static final String INGESTION_TIME = "IngestionTime"; private static final String ORDERED = "ordered"; public static void main(String[] args) throws Exception { StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); final ParameterTool params = ParameterTool.fromArgs(args); final String statePath; final String cpMode; final int maxCount; final long sleepFactor; final float failRatio; final String mode; final int taskNum; final String timeType; final long shutdownWaitTS; final long timeout; try { // check the configuration for the job statePath = params.get("fsStatePath", null); cpMode = params.get("checkpointMode", "exactly_once"); maxCount = params.getInt("maxCount", 100000); sleepFactor = params.getLong("sleepFactor", 100); failRatio = params.getFloat("failRatio", 0.001f); mode = params.get("waitMode", "ordered"); taskNum = params.getInt("waitOperatorParallelism", 1); timeType = params.get("eventType", "EventTime"); shutdownWaitTS = params.getLong("shutdownWaitTS", 20000); timeout = params.getLong("timeout", 10000L); } catch (Exception e) { printUsage(); throw e; } if (statePath != null) { env.setStateBackend(new FsStateBackend(statePath)); } if (EXACTLY_ONCE_MODE.equals(cpMode)) { env.enableCheckpointing(1000L, CheckpointingMode.EXACTLY_ONCE); } else { env.enableCheckpointing(1000L, CheckpointingMode.AT_LEAST_ONCE); } if (EVENT_TIME.equals(timeType)) { env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime); } else if (INGESTION_TIME.equals(timeType)) { env.setStreamTimeCharacteristic(TimeCharacteristic.IngestionTime); } DataStream inputStream = env.addSource(new SimpleSource(maxCount)); // create async function, which will *wait* for a while to simulate the process of async i/o AsyncFunction function = new SampleAsyncFunction(sleepFactor, failRatio, shutdownWaitTS); // add async operator to streaming job DataStream result; if (ORDERED.equals(mode)) { result = AsyncDataStream.orderedWait( inputStream, function, timeout, TimeUnit.MILLISECONDS, 20).setParallelism(taskNum); } else { result = AsyncDataStream.unorderedWait( inputStream, function, timeout, TimeUnit.MILLISECONDS, 20).setParallelism(taskNum); } result.flatMap(new FlatMapFunction>() { private static final long serialVersionUID = -938116068682344455L; @Override public void flatMap(String value, Collector> out) throws Exception { out.collect(new Tuple2<>(value, 1)); } }) .keyBy(0) .sum(1) .print(); env.execute("Async IO Example"); } /** * A checkpointed source. */ private static class SimpleSource implements SourceFunction, ListCheckpointed { private static final long serialVersionUID = 1L; private volatile boolean isRunning = true; private int counter = 0; private int start = 0; @Override public List snapshotState(long checkpointId, long timestamp) throws Exception { return Collections.singletonList(start); } @Override public void restoreState(List state) throws Exception { for (Integer i : state) { this.start = i; } } public SimpleSource(int maxNum) { this.counter = maxNum; } @Override public void run(SourceContext ctx) throws Exception { while ((start < counter || counter == -1) && isRunning) { synchronized (ctx.getCheckpointLock()) { ctx.collect(start); ++start; // loop back to 0 if (start == Integer.MAX_VALUE) { start = 0; } } Thread.sleep(10L); } } @Override public void cancel() { isRunning = false; } } /** * An sample of {@link AsyncFunction} using a thread pool and executing working threads * to simulate multiple async operations. * *

For the real use case in production environment, the thread pool may stay in the * async client. */ private static class SampleAsyncFunction extends RichAsyncFunction { private static final long serialVersionUID = 2098635244857937717L; private transient ExecutorService executorService; /** * The result of multiplying sleepFactor with a random float is used to pause * the working thread in the thread pool, simulating a time consuming async operation. */ private final long sleepFactor; /** * The ratio to generate an exception to simulate an async error. For example, the error * may be a TimeoutException while visiting HBase. */ private final float failRatio; private final long shutdownWaitTS; SampleAsyncFunction(long sleepFactor, float failRatio, long shutdownWaitTS) { this.sleepFactor = sleepFactor; this.failRatio = failRatio; this.shutdownWaitTS = shutdownWaitTS; } @Override public void open(Configuration parameters) throws Exception { super.open(parameters); executorService = Executors.newFixedThreadPool(30); } @Override public void close() throws Exception { super.close(); ExecutorUtils.gracefulShutdown(shutdownWaitTS, TimeUnit.MILLISECONDS, executorService); } @Override public void asyncInvoke(final Integer input, final ResultFuture resultFuture) { executorService.submit(() -> { // wait for while to simulate async operation here long sleep = (long) (ThreadLocalRandom.current().nextFloat() * sleepFactor); try { Thread.sleep(sleep); if (ThreadLocalRandom.current().nextFloat() < failRatio) { resultFuture.completeExceptionally(new Exception("wahahahaha...")); } else { resultFuture.complete( Collections.singletonList("key-" + (input % 10))); } } catch (InterruptedException e) { resultFuture.complete(new ArrayList<>(0)); } }); } } private static void printUsage() { System.out.println("To customize example, use: AsyncIOExample [--fsStatePath ] " + "[--checkpointMode ] " + "[--maxCount ] " + "[--sleepFactor ] [--failRatio ] " + "[--waitMode ] [--waitOperatorParallelism ] " + "[--eventType ] [--shutdownWaitTS ]" + "[--timeout ]"); } } ================================================ FILE: flink-learning-examples/src/main/java/com/zhisheng/examples/streaming/broadcast/BroadcastAlertRule.java ================================================ package com.zhisheng.examples.streaming.broadcast; import com.zhisheng.common.utils.ExecutionEnvUtil; import lombok.extern.slf4j.Slf4j; import org.apache.flink.api.common.state.BroadcastState; import org.apache.flink.api.common.state.MapStateDescriptor; import org.apache.flink.api.common.state.ReadOnlyBroadcastState; import org.apache.flink.api.common.typeinfo.BasicTypeInfo; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.api.functions.co.BroadcastProcessFunction; import org.apache.flink.util.Collector; import java.util.Arrays; import java.util.List; /** * Desc: 集合变量管广播的情况下 读取该集合的数据后就会 task 就会 finished * Created by zhisheng on 2019/10/17 下午4:28 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ @Slf4j public class BroadcastAlertRule { final static MapStateDescriptor ALERT_RULE = new MapStateDescriptor<>( "alert_rule", BasicTypeInfo.STRING_TYPE_INFO, BasicTypeInfo.STRING_TYPE_INFO); public static void main(String[] args) throws Exception { final ParameterTool parameterTool = ExecutionEnvUtil.createParameterTool(args); StreamExecutionEnvironment env = ExecutionEnvUtil.prepare(parameterTool); List strings = Arrays.asList("A", "B", "C"); env.socketTextStream("127.0.0.1", 9200) .connect(env.fromCollection(strings).broadcast(ALERT_RULE)) .process(new BroadcastProcessFunction() { @Override public void processElement(String value, ReadOnlyContext ctx, Collector out) throws Exception { ReadOnlyBroadcastState broadcastState = ctx.getBroadcastState(ALERT_RULE); if (broadcastState.contains(value)) { out.collect(value); } } @Override public void processBroadcastElement(String value, Context ctx, Collector out) throws Exception { BroadcastState broadcastState = ctx.getBroadcastState(ALERT_RULE); broadcastState.put(value, value); } }) .print(); env.execute(); } } ================================================ FILE: flink-learning-examples/src/main/java/com/zhisheng/examples/streaming/broadcast/DataSetBrocastMain.java ================================================ package com.zhisheng.examples.streaming.broadcast; import org.apache.flink.api.common.functions.RichMapFunction; import org.apache.flink.api.java.DataSet; import org.apache.flink.api.java.ExecutionEnvironment; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.configuration.Configuration; import java.util.List; /** * Desc: * Created by zhisheng on 2019/10/17 上午9:21 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class DataSetBrocastMain { public static void main(String[] args) throws Exception { final ParameterTool params = ParameterTool.fromArgs(args); final ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment(); //1. 待广播的数据 DataSet toBroadcast = env.fromElements(1, 2, 3); env.fromElements("a", "b") .map(new RichMapFunction() { List broadcastData; @Override public void open(Configuration parameters) throws Exception { // 3. 获取广播的DataSet数据 作为一个Collection broadcastData = getRuntimeContext().getBroadcastVariable("zhisheng"); } @Override public String map(String value) throws Exception { return broadcastData.get(1) + value; } }).withBroadcastSet(toBroadcast, "zhisheng")// 2. 广播DataSet .print(); } } ================================================ FILE: flink-learning-examples/src/main/java/com/zhisheng/examples/streaming/broadcast/GetAlarmNotifyData.java ================================================ package com.zhisheng.examples.streaming.broadcast; import com.zhisheng.examples.util.MySQLUtil; import lombok.extern.slf4j.Slf4j; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.configuration.Configuration; import org.apache.flink.streaming.api.functions.source.RichSourceFunction; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.util.HashMap; import java.util.Map; import static com.zhisheng.common.constant.PropertiesConstants.*; @Slf4j public class GetAlarmNotifyData extends RichSourceFunction> { private Connection connection = null; private PreparedStatement ps = null; private volatile boolean isRunning = true; private ParameterTool parameterTool; @Override public void open(Configuration parameters) throws Exception { super.open(parameters); parameterTool = (ParameterTool) (getRuntimeContext().getExecutionConfig().getGlobalJobParameters()); String database = parameterTool.get(MYSQL_DATABASE); String host = parameterTool.get(MYSQL_HOST); String password = parameterTool.get(MYSQL_PASSWORD); String port = parameterTool.get(MYSQL_PORT); String username = parameterTool.get(MYSQL_USERNAME); String driver = "com.mysql.jdbc.Driver"; String url = "jdbc:mysql://" + host + ":" + port + "/" + database + "?useUnicode=true&characterEncoding=UTF-8"; connection = MySQLUtil.getConnection(driver, url, username, password); if (connection != null) { String sql = "select * from alarm_notify"; ps = connection.prepareStatement(sql); } } @Override public void run(SourceContext> ctx) throws Exception { Map map = new HashMap<>(); while (isRunning) { ResultSet resultSet = ps.executeQuery(); while (resultSet.next()) { if (0 == resultSet.getInt("status")) { map.put(resultSet.getString("type") + resultSet.getString("type_id"), resultSet.getString("target_id")); } } log.info("=======select alarm notify from mysql, size = {}, map = {}", map.size(), map); ctx.collect(map); map.clear(); Thread.sleep(2000 * 60); } } @Override public void cancel() { try { super.close(); if (connection != null) { connection.close(); } if (ps != null) { ps.close(); } } catch (Exception e) { log.error("runException:{}", e); } isRunning = false; } } ================================================ FILE: flink-learning-examples/src/main/java/com/zhisheng/examples/streaming/broadcast/Main.java ================================================ package com.zhisheng.examples.streaming.broadcast; import com.zhisheng.common.model.MetricEvent; import com.zhisheng.common.utils.ExecutionEnvUtil; import com.zhisheng.common.utils.KafkaConfigUtil; import org.apache.flink.api.common.state.MapStateDescriptor; import org.apache.flink.api.common.typeinfo.BasicTypeInfo; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.streaming.api.datastream.DataStreamSource; import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import java.util.Map; /** * Desc: 广播变量,定时从数据库读取告警规则数据 * Created by zhisheng on 2019-05-30 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class Main { final static MapStateDescriptor ALARM_RULES = new MapStateDescriptor<>( "alarm_rules", BasicTypeInfo.STRING_TYPE_INFO, BasicTypeInfo.STRING_TYPE_INFO); public static void main(String[] args) throws Exception { final ParameterTool parameterTool = ExecutionEnvUtil.createParameterTool(args); StreamExecutionEnvironment env = ExecutionEnvUtil.prepare(parameterTool); DataStreamSource> alarmDataStream = env.addSource(new GetAlarmNotifyData()).setParallelism(1);//数据流定时从数据库中查出来数据 //test for get data from MySQL // alarmDataStream.print(); DataStreamSource metricEventDataStream = KafkaConfigUtil.buildSource(env); SingleOutputStreamOperator alert = metricEventDataStream.connect(alarmDataStream.broadcast(ALARM_RULES)) .process(new MyBroadcastProcessFunction(ALARM_RULES)); //其他的业务逻辑 //alert. //然后在下游的算子中有使用到 alarmNotifyMap 中的配置信息 env.execute("zhisheng broadcast demo"); } } ================================================ FILE: flink-learning-examples/src/main/java/com/zhisheng/examples/streaming/broadcast/Main2.java ================================================ package com.zhisheng.examples.streaming.broadcast; import com.zhisheng.common.utils.ExecutionEnvUtil; import lombok.extern.slf4j.Slf4j; import org.apache.flink.api.common.functions.MapFunction; import org.apache.flink.api.common.state.MapStateDescriptor; import org.apache.flink.api.common.typeinfo.BasicTypeInfo; import org.apache.flink.api.java.tuple.Tuple2; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.streaming.api.TimeCharacteristic; import org.apache.flink.streaming.api.datastream.DataStreamSource; import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.api.functions.AssignerWithPeriodicWatermarks; import org.apache.flink.streaming.api.functions.co.CoFlatMapFunction; import org.apache.flink.streaming.api.functions.source.SourceFunction; import org.apache.flink.streaming.api.watermark.Watermark; import org.apache.flink.streaming.api.windowing.time.Time; import org.apache.flink.util.Collector; import javax.annotation.Nullable; /** * Desc: * Created by zhisheng on 2020-02-26 18:38 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ @Slf4j public class Main2 { final static MapStateDescriptor ALARM_RULES = new MapStateDescriptor<>( "alarm_rules", BasicTypeInfo.STRING_TYPE_INFO, BasicTypeInfo.STRING_TYPE_INFO); public static void main(String[] args) throws Exception { final ParameterTool parameterTool = ExecutionEnvUtil.createParameterTool(args); StreamExecutionEnvironment env = ExecutionEnvUtil.prepare(parameterTool); env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime); env.setParallelism(1); DataStreamSource rule = env.addSource(new SourceFunction() { @Override public void run(SourceContext sourceContext) throws Exception { while (true) { sourceContext.collect("AAA"); Thread.sleep(60000); } } @Override public void cancel() { } }); SingleOutputStreamOperator> flatMap = env.socketTextStream("127.0.0.1", 9001) .map(new MapFunction>() { @Override public Tuple2 map(String s) throws Exception { String[] split = s.split(","); return new Tuple2<>(split[0], Long.valueOf(split[1])); } }).assignTimestampsAndWatermarks(new AssignerWithPeriodicWatermarks>() { private long currentTimestamp = Long.MIN_VALUE; @Nullable @Override public Watermark getCurrentWatermark() { Watermark watermark = new Watermark(currentTimestamp == Long.MIN_VALUE ? Long.MIN_VALUE : currentTimestamp - 5000); log.info("watermark is {}", watermark.getTimestamp()); return watermark; } @Override public long extractTimestamp(Tuple2 tuple2, long l) { long timestamp = tuple2.f1; currentTimestamp = Math.max(timestamp, currentTimestamp); return timestamp; } }).connect(rule.broadcast()) .flatMap(new CoFlatMapFunction, String, Tuple2>() { @Override public void flatMap1(Tuple2 tuple2, Collector> collector) throws Exception { System.out.println("flatMap1 " + tuple2.f0 + " " + tuple2.f1); collector.collect(tuple2); } @Override public void flatMap2(String s, Collector> collector) throws Exception { System.out.println("flatMap2 " + s); collector.collect(new Tuple2<>(s, System.currentTimeMillis())); } })/*.assignTimestampsAndWatermarks(new AssignerWithPeriodicWatermarks>() { private long currentTimestamp = Long.MIN_VALUE; @Nullable @Override public Watermark getCurrentWatermark() { Watermark watermark = new Watermark(currentTimestamp == Long.MIN_VALUE ? Long.MIN_VALUE : currentTimestamp - 5000); log.info("watermark is {}", watermark.getTimestamp()); return watermark; } @Override public long extractTimestamp(Tuple2 tuple2, long l) { long timestamp = tuple2.f1; currentTimestamp = Math.max(timestamp, currentTimestamp); return timestamp; } })*/; flatMap.keyBy(0).timeWindow(Time.minutes(2)).sum(1).print(); env.execute(); } } ================================================ FILE: flink-learning-examples/src/main/java/com/zhisheng/examples/streaming/broadcast/MyBroadcastProcessFunction.java ================================================ package com.zhisheng.examples.streaming.broadcast; import com.zhisheng.common.model.MetricEvent; import org.apache.flink.api.common.state.BroadcastState; import org.apache.flink.api.common.state.MapStateDescriptor; import org.apache.flink.api.common.state.ReadOnlyBroadcastState; import org.apache.flink.streaming.api.functions.co.BroadcastProcessFunction; import org.apache.flink.util.Collector; import java.util.Map; /** * Desc: * Created by zhisheng on 2020-04-28 15:46 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class MyBroadcastProcessFunction extends BroadcastProcessFunction, MetricEvent> { private MapStateDescriptor alarmRulesMapStateDescriptor; public MyBroadcastProcessFunction(MapStateDescriptor alarmRulesMapStateDescriptor) { this.alarmRulesMapStateDescriptor = alarmRulesMapStateDescriptor; } @Override public void processElement(MetricEvent metricEvent, ReadOnlyContext readOnlyContext, Collector collector) throws Exception { ReadOnlyBroadcastState broadcastState = readOnlyContext.getBroadcastState(alarmRulesMapStateDescriptor); Map tags = metricEvent.getTags(); if (!tags.containsKey("type") && !tags.containsKey("type_id")) { return; } String targetId = broadcastState.get(tags.get("type") + tags.containsKey("type_id")); if (targetId != null) { metricEvent.getTags().put("target_id", targetId); //将通知方式的 hook 放在 tag 里面,在下游要告警的时候通过该字段获取到对应的 hook 地址 collector.collect(metricEvent); } } @Override public void processBroadcastElement(Map value, Context context, Collector collector) throws Exception { if (value != null) { BroadcastState broadcastState = context.getBroadcastState(alarmRulesMapStateDescriptor); for (Map.Entry entry : value.entrySet()) { broadcastState.put(entry.getKey(), entry.getValue()); } } } } ================================================ FILE: flink-learning-examples/src/main/java/com/zhisheng/examples/streaming/chain/DefaultChainMain.java ================================================ package com.zhisheng.examples.streaming.chain; import org.apache.flink.api.common.functions.FlatMapFunction; import org.apache.flink.api.common.functions.ReduceFunction; import org.apache.flink.api.java.tuple.Tuple2; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.util.Collector; /** * Desc: operator chain * Created by zhisheng on 2019/10/6 下午7:42 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class DefaultChainMain { public static void main(String[] args) throws Exception { StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.getConfig().setGlobalJobParameters(ParameterTool.fromArgs(args)); env.fromElements(WORDS) .flatMap(new FlatMapFunction>() { @Override public void flatMap(String value, Collector> out) throws Exception { String[] splits = value.toLowerCase().split("\\W+"); for (String split : splits) { if (split.length() > 0) { out.collect(new Tuple2<>(split, 1)); } } } }) .keyBy(0) .reduce(new ReduceFunction>() { @Override public Tuple2 reduce(Tuple2 value1, Tuple2 value2) throws Exception { return new Tuple2<>(value1.f0, value1.f1 + value2.f1); } }) .print(); env.execute("zhisheng —— word count chain demo"); } private static final String[] WORDS = new String[]{ "To be, or not to be,--that is the question:--", "Whether 'tis nobler in the mind to suffer", "The slings and arrows of outrageous fortune", "Or to take arms against a sea of troubles,", "And by opposing end them?--To die,--to sleep,--", "No more; and by a sleep to say we end", "The heartache, and the thousand natural shocks", "That flesh is heir to,--'tis a consummation", "Devoutly to be wish'd. To die,--to sleep;--", "To sleep! perchance to dream:--ay, there's the rub;", "For in that sleep of death what dreams may come,", "When we have shuffled off this mortal coil,", "Must give us pause: there's the respect", "That makes calamity of so long life;", "For who would bear the whips and scorns of time,", "The oppressor's wrong, the proud man's contumely,", "The pangs of despis'd love, the law's delay,", "The insolence of office, and the spurns", "That patient merit of the unworthy takes,", "When he himself might his quietus make", "With a bare bodkin? who would these fardels bear,", "To grunt and sweat under a weary life,", "But that the dread of something after death,--", "The undiscover'd country, from whose bourn", "No traveller returns,--puzzles the will,", "And makes us rather bear those ills we have", "Than fly to others that we know not of?", "Thus conscience does make cowards of us all;", "And thus the native hue of resolution", "Is sicklied o'er with the pale cast of thought;", "And enterprises of great pith and moment,", "With this regard, their currents turn awry,", "And lose the name of action.--Soft you now!", "The fair Ophelia!--Nymph, in thy orisons", "Be all my sins remember'd." }; } ================================================ FILE: flink-learning-examples/src/main/java/com/zhisheng/examples/streaming/chain/DisableChainMain.java ================================================ package com.zhisheng.examples.streaming.chain; import org.apache.flink.api.common.functions.FlatMapFunction; import org.apache.flink.api.common.functions.ReduceFunction; import org.apache.flink.api.java.tuple.Tuple2; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.util.Collector; /** * Desc: operator chain —— disable chain * Created by zhisheng on 2019/10/6 下午7:42 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class DisableChainMain { public static void main(String[] args) throws Exception { StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.getConfig().setGlobalJobParameters(ParameterTool.fromArgs(args)); //disable operator chain env.disableOperatorChaining(); env.fromElements(WORDS) .flatMap(new FlatMapFunction>() { @Override public void flatMap(String value, Collector> out) throws Exception { String[] splits = value.toLowerCase().split("\\W+"); for (String split : splits) { if (split.length() > 0) { out.collect(new Tuple2<>(split, 1)); } } } }) .keyBy(0) .reduce(new ReduceFunction>() { @Override public Tuple2 reduce(Tuple2 value1, Tuple2 value2) throws Exception { return new Tuple2<>(value1.f0, value1.f1 + value2.f1); } }) .print(); env.execute("zhisheng —— word count disable chain demo"); } private static final String[] WORDS = new String[]{ "To be, or not to be,--that is the question:--", "Whether 'tis nobler in the mind to suffer", "The slings and arrows of outrageous fortune", "Or to take arms against a sea of troubles,", "And by opposing end them?--To die,--to sleep,--", "No more; and by a sleep to say we end", "The heartache, and the thousand natural shocks", "That flesh is heir to,--'tis a consummation", "Devoutly to be wish'd. To die,--to sleep;--", "To sleep! perchance to dream:--ay, there's the rub;", "For in that sleep of death what dreams may come,", "When we have shuffled off this mortal coil,", "Must give us pause: there's the respect", "That makes calamity of so long life;", "For who would bear the whips and scorns of time,", "The oppressor's wrong, the proud man's contumely,", "The pangs of despis'd love, the law's delay,", "The insolence of office, and the spurns", "That patient merit of the unworthy takes,", "When he himself might his quietus make", "With a bare bodkin? who would these fardels bear,", "To grunt and sweat under a weary life,", "But that the dread of something after death,--", "The undiscover'd country, from whose bourn", "No traveller returns,--puzzles the will,", "And makes us rather bear those ills we have", "Than fly to others that we know not of?", "Thus conscience does make cowards of us all;", "And thus the native hue of resolution", "Is sicklied o'er with the pale cast of thought;", "And enterprises of great pith and moment,", "With this regard, their currents turn awry,", "And lose the name of action.--Soft you now!", "The fair Ophelia!--Nymph, in thy orisons", "Be all my sins remember'd." }; } ================================================ FILE: flink-learning-examples/src/main/java/com/zhisheng/examples/streaming/chain/DisableChainMain1.java ================================================ package com.zhisheng.examples.streaming.chain; import org.apache.flink.api.common.functions.FlatMapFunction; import org.apache.flink.api.common.functions.ReduceFunction; import org.apache.flink.api.java.tuple.Tuple2; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.util.Collector; /** * Desc: operator chain —— disable chain in operator * Created by zhisheng on 2019/10/6 下午7:42 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class DisableChainMain1 { public static void main(String[] args) throws Exception { StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.getConfig().setGlobalJobParameters(ParameterTool.fromArgs(args)); env.fromElements(WORDS) .flatMap(new FlatMapFunction>() { @Override public void flatMap(String value, Collector> out) throws Exception { String[] splits = value.toLowerCase().split("\\W+"); for (String split : splits) { if (split.length() > 0) { out.collect(new Tuple2<>(split, 1)); } } } }).disableChaining() .keyBy(0) .reduce(new ReduceFunction>() { @Override public Tuple2 reduce(Tuple2 value1, Tuple2 value2) throws Exception { return new Tuple2<>(value1.f0, value1.f1 + value2.f1); } }) .print(); env.execute("zhisheng —— word count disable chain in operator demo"); } private static final String[] WORDS = new String[]{ "To be, or not to be,--that is the question:--", "Whether 'tis nobler in the mind to suffer", "The slings and arrows of outrageous fortune", "Or to take arms against a sea of troubles,", "And by opposing end them?--To die,--to sleep,--", "No more; and by a sleep to say we end", "The heartache, and the thousand natural shocks", "That flesh is heir to,--'tis a consummation", "Devoutly to be wish'd. To die,--to sleep;--", "To sleep! perchance to dream:--ay, there's the rub;", "For in that sleep of death what dreams may come,", "When we have shuffled off this mortal coil,", "Must give us pause: there's the respect", "That makes calamity of so long life;", "For who would bear the whips and scorns of time,", "The oppressor's wrong, the proud man's contumely,", "The pangs of despis'd love, the law's delay,", "The insolence of office, and the spurns", "That patient merit of the unworthy takes,", "When he himself might his quietus make", "With a bare bodkin? who would these fardels bear,", "To grunt and sweat under a weary life,", "But that the dread of something after death,--", "The undiscover'd country, from whose bourn", "No traveller returns,--puzzles the will,", "And makes us rather bear those ills we have", "Than fly to others that we know not of?", "Thus conscience does make cowards of us all;", "And thus the native hue of resolution", "Is sicklied o'er with the pale cast of thought;", "And enterprises of great pith and moment,", "With this regard, their currents turn awry,", "And lose the name of action.--Soft you now!", "The fair Ophelia!--Nymph, in thy orisons", "Be all my sins remember'd." }; } ================================================ FILE: flink-learning-examples/src/main/java/com/zhisheng/examples/streaming/chain/DisableChainMain3.java ================================================ package com.zhisheng.examples.streaming.chain; import org.apache.flink.api.common.functions.FlatMapFunction; import org.apache.flink.api.common.functions.ReduceFunction; import org.apache.flink.api.java.tuple.Tuple2; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.api.functions.source.SourceFunction; import org.apache.flink.util.Collector; /** * Desc: operator chain —— disable chain * Created by zhisheng on 2019/10/6 下午7:42 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class DisableChainMain3 { public static void main(String[] args) throws Exception { StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.getConfig().setGlobalJobParameters(ParameterTool.fromArgs(args)); //disable operator chain env.disableOperatorChaining(); env.addSource(new SourceFunction() { @Override public void cancel() { } @Override public void run(SourceContext ctx) throws Exception { int i = 0; while (true) { ctx.collect("zhisheng" + i ++); } } }) .flatMap(new FlatMapFunction>() { @Override public void flatMap(String value, Collector> out) throws Exception { String[] splits = value.toLowerCase().split("\\W+"); for (String split : splits) { if (split.length() > 0) { out.collect(new Tuple2<>(split, 1)); } } } }) .keyBy(0) .reduce(new ReduceFunction>() { @Override public Tuple2 reduce(Tuple2 value1, Tuple2 value2) throws Exception { return new Tuple2<>(value1.f0, value1.f1 + value2.f1); } }) .print(); env.execute("zhisheng —— word count disable chain demo"); } } ================================================ FILE: flink-learning-examples/src/main/java/com/zhisheng/examples/streaming/chain/ExecutionPlanMain.java ================================================ package com.zhisheng.examples.streaming.chain; import org.apache.flink.api.common.functions.FlatMapFunction; import org.apache.flink.api.common.functions.ReduceFunction; import org.apache.flink.api.java.tuple.Tuple2; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.util.Collector; /** * Desc: get the job ExecutionPlan * Created by zhisheng on 2019/10/6 下午6:18 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class ExecutionPlanMain { public static void main(String[] args) throws Exception { StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.getConfig().setGlobalJobParameters(ParameterTool.fromArgs(args)); env.fromElements(WORDS) .flatMap(new FlatMapFunction>() { @Override public void flatMap(String value, Collector> out) throws Exception { String[] splits = value.toLowerCase().split("\\W+"); for (String split : splits) { if (split.length() > 0) { out.collect(new Tuple2<>(split, 1)); } } } }) .keyBy(0) .reduce(new ReduceFunction>() { @Override public Tuple2 reduce(Tuple2 value1, Tuple2 value2) throws Exception { return new Tuple2<>(value1.f0, value1.f1 + value2.f1); } }) .print(); //get the job ExecutionPlan json System.out.println("=====" + env.getExecutionPlan()); env.execute("zhisheng —— word count executionPlan demo"); } private static final String[] WORDS = new String[]{ "To be, or not to be,--that is the question:--", "Whether 'tis nobler in the mind to suffer", "The slings and arrows of outrageous fortune", "Or to take arms against a sea of troubles,", "And by opposing end them?--To die,--to sleep,--", "No more; and by a sleep to say we end", "The heartache, and the thousand natural shocks", "That flesh is heir to,--'tis a consummation", "Devoutly to be wish'd. To die,--to sleep;--", "To sleep! perchance to dream:--ay, there's the rub;", "For in that sleep of death what dreams may come,", "When we have shuffled off this mortal coil,", "Must give us pause: there's the respect", "That makes calamity of so long life;", "For who would bear the whips and scorns of time,", "The oppressor's wrong, the proud man's contumely,", "The pangs of despis'd love, the law's delay,", "The insolence of office, and the spurns", "That patient merit of the unworthy takes,", "When he himself might his quietus make", "With a bare bodkin? who would these fardels bear,", "To grunt and sweat under a weary life,", "But that the dread of something after death,--", "The undiscover'd country, from whose bourn", "No traveller returns,--puzzles the will,", "And makes us rather bear those ills we have", "Than fly to others that we know not of?", "Thus conscience does make cowards of us all;", "And thus the native hue of resolution", "Is sicklied o'er with the pale cast of thought;", "And enterprises of great pith and moment,", "With this regard, their currents turn awry,", "And lose the name of action.--Soft you now!", "The fair Ophelia!--Nymph, in thy orisons", "Be all my sins remember'd." }; } ================================================ FILE: flink-learning-examples/src/main/java/com/zhisheng/examples/streaming/chain/SharingGroupMain.java ================================================ package com.zhisheng.examples.streaming.chain; import org.apache.flink.api.common.functions.FlatMapFunction; import org.apache.flink.api.common.functions.MapFunction; import org.apache.flink.api.common.functions.ReduceFunction; import org.apache.flink.api.java.tuple.Tuple2; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.util.Collector; import java.util.Objects; /** * Desc: Set slot sharing group * Created by zhisheng on 2019/10/6 下午8:16 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class SharingGroupMain { public static void main(String[] args) throws Exception { StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.getConfig().setGlobalJobParameters(ParameterTool.fromArgs(args)); env.fromElements(WORDS) .filter(Objects::nonNull).slotSharingGroup("zhisheng") .flatMap(new FlatMapFunction>() { @Override public void flatMap(String value, Collector> out) throws Exception { String[] splits = value.toLowerCase().split("\\W+"); for (String split : splits) { if (split.length() > 0) { out.collect(new Tuple2<>(split, 1)); } } } }).slotSharingGroup("zhisheng") .map(new MapFunction, Tuple2>() { @Override public Tuple2 map(Tuple2 in) throws Exception { return new Tuple2<>(in.f0, in.f1 + 1); } }).slotSharingGroup("zhisheng01") .keyBy(0) .reduce(new ReduceFunction>() { @Override public Tuple2 reduce(Tuple2 value1, Tuple2 value2) throws Exception { return new Tuple2<>(value1.f0, value1.f1 + value2.f1); } }).slotSharingGroup("zhisheng02") .print().slotSharingGroup("zhisheng"); env.execute("zhisheng —— word count Set slot sharing group demo"); } private static final String[] WORDS = new String[]{ "To be, or not to be,--that is the question:--", "Whether 'tis nobler in the mind to suffer", "The slings and arrows of outrageous fortune", "Or to take arms against a sea of troubles,", "And by opposing end them?--To die,--to sleep,--", "No more; and by a sleep to say we end", "The heartache, and the thousand natural shocks", "That flesh is heir to,--'tis a consummation", "Devoutly to be wish'd. To die,--to sleep;--", "To sleep! perchance to dream:--ay, there's the rub;", "For in that sleep of death what dreams may come,", "When we have shuffled off this mortal coil,", "Must give us pause: there's the respect", "That makes calamity of so long life;", "For who would bear the whips and scorns of time,", "The oppressor's wrong, the proud man's contumely,", "The pangs of despis'd love, the law's delay,", "The insolence of office, and the spurns", "That patient merit of the unworthy takes,", "When he himself might his quietus make", "With a bare bodkin? who would these fardels bear,", "To grunt and sweat under a weary life,", "But that the dread of something after death,--", "The undiscover'd country, from whose bourn", "No traveller returns,--puzzles the will,", "And makes us rather bear those ills we have", "Than fly to others that we know not of?", "Thus conscience does make cowards of us all;", "And thus the native hue of resolution", "Is sicklied o'er with the pale cast of thought;", "And enterprises of great pith and moment,", "With this regard, their currents turn awry,", "And lose the name of action.--Soft you now!", "The fair Ophelia!--Nymph, in thy orisons", "Be all my sins remember'd." }; } ================================================ FILE: flink-learning-examples/src/main/java/com/zhisheng/examples/streaming/chain/StartNewChainMain.java ================================================ package com.zhisheng.examples.streaming.chain; import org.apache.flink.api.common.functions.FlatMapFunction; import org.apache.flink.api.common.functions.MapFunction; import org.apache.flink.api.common.functions.ReduceFunction; import org.apache.flink.api.java.tuple.Tuple2; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.util.Collector; import java.util.Objects; /** * Desc: operator chain —— start new chain * Created by zhisheng on 2019/10/6 下午7:42 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class StartNewChainMain { public static void main(String[] args) throws Exception { StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.getConfig().setGlobalJobParameters(ParameterTool.fromArgs(args)); env.fromElements(WORDS) .filter(Objects::nonNull) .flatMap(new FlatMapFunction>() { @Override public void flatMap(String value, Collector> out) throws Exception { String[] splits = value.toLowerCase().split("\\W+"); for (String split : splits) { if (split.length() > 0) { out.collect(new Tuple2<>(split, 1)); } } } }) .startNewChain() //start the new chain,flatMap & map operator will chain, but not filter .map(new MapFunction, Tuple2>() { @Override public Tuple2 map(Tuple2 in) throws Exception { return new Tuple2<>(in.f0, in.f1 + 1); } }) .keyBy(0) .reduce(new ReduceFunction>() { @Override public Tuple2 reduce(Tuple2 value1, Tuple2 value2) throws Exception { return new Tuple2<>(value1.f0, value1.f1 + value2.f1); } }) .print(); env.execute("zhisheng —— word count start new chain demo"); } private static final String[] WORDS = new String[]{ "To be, or not to be,--that is the question:--", "Whether 'tis nobler in the mind to suffer", "The slings and arrows of outrageous fortune", "Or to take arms against a sea of troubles,", "And by opposing end them?--To die,--to sleep,--", "No more; and by a sleep to say we end", "The heartache, and the thousand natural shocks", "That flesh is heir to,--'tis a consummation", "Devoutly to be wish'd. To die,--to sleep;--", "To sleep! perchance to dream:--ay, there's the rub;", "For in that sleep of death what dreams may come,", "When we have shuffled off this mortal coil,", "Must give us pause: there's the respect", "That makes calamity of so long life;", "For who would bear the whips and scorns of time,", "The oppressor's wrong, the proud man's contumely,", "The pangs of despis'd love, the law's delay,", "The insolence of office, and the spurns", "That patient merit of the unworthy takes,", "When he himself might his quietus make", "With a bare bodkin? who would these fardels bear,", "To grunt and sweat under a weary life,", "But that the dread of something after death,--", "The undiscover'd country, from whose bourn", "No traveller returns,--puzzles the will,", "And makes us rather bear those ills we have", "Than fly to others that we know not of?", "Thus conscience does make cowards of us all;", "And thus the native hue of resolution", "Is sicklied o'er with the pale cast of thought;", "And enterprises of great pith and moment,", "With this regard, their currents turn awry,", "And lose the name of action.--Soft you now!", "The fair Ophelia!--Nymph, in thy orisons", "Be all my sins remember'd." }; } ================================================ FILE: flink-learning-examples/src/main/java/com/zhisheng/examples/streaming/checkpoint/Main.java ================================================ package com.zhisheng.examples.streaming.checkpoint; import com.zhisheng.common.model.MetricEvent; import com.zhisheng.common.utils.CheckPointUtil; import com.zhisheng.common.utils.ExecutionEnvUtil; import com.zhisheng.common.utils.GsonUtil; import com.zhisheng.common.utils.KafkaConfigUtil; import org.apache.flink.api.common.serialization.SimpleStringSchema; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumer; import java.util.Properties; import static com.zhisheng.common.constant.PropertiesConstants.METRICS_TOPIC; /** * Desc: checkpoint 的配置 * Created by zhisheng on 2019-04-17 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class Main { public static void main(String[] args) throws Exception { final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); ParameterTool parameterTool = ExecutionEnvUtil.PARAMETER_TOOL; Properties props = KafkaConfigUtil.buildKafkaProps(parameterTool); SingleOutputStreamOperator metricData = env.addSource(new FlinkKafkaConsumer<>( parameterTool.get(METRICS_TOPIC), new SimpleStringSchema(), props)).setParallelism(1) .map(string -> GsonUtil.fromJson(string, MetricEvent.class)); metricData.print(); CheckPointUtil.setCheckpointConfig(env, parameterTool) .execute("zhisheng --- checkpoint config example"); } } ================================================ FILE: flink-learning-examples/src/main/java/com/zhisheng/examples/streaming/checkpoint/PvStatExactlyOnce.java ================================================ package com.zhisheng.examples.streaming.checkpoint; import com.zhisheng.examples.streaming.checkpoint.util.PvStatExactlyOnceKafkaUtil; import lombok.extern.slf4j.Slf4j; import org.apache.flink.api.common.functions.RichMapFunction; import org.apache.flink.api.common.serialization.SimpleStringSchema; import org.apache.flink.api.common.state.ValueState; import org.apache.flink.api.common.state.ValueStateDescriptor; import org.apache.flink.api.common.typeinfo.TypeHint; import org.apache.flink.api.common.typeinfo.TypeInformation; import org.apache.flink.api.java.functions.KeySelector; import org.apache.flink.configuration.Configuration; import org.apache.flink.streaming.api.CheckpointingMode; import org.apache.flink.streaming.api.datastream.DataStreamSource; import org.apache.flink.streaming.api.environment.CheckpointConfig; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumer; import org.apache.kafka.clients.consumer.ConsumerConfig; import org.apache.flink.api.java.tuple.Tuple2; import java.util.Properties; import java.util.concurrent.TimeUnit; /** * @author fanrui * @date 2019-10-03 21:05:33 * @desc 代码中设置 1 分钟一次CheckPoint,CheckPoint 语义 EXACTLY ONCE,从 Kafka 中读取数据, * 这里为了简化代码,所以 Kafka 中读取的直接就是 String 类型的 appId,按照 appId KeyBy 后,执行 RichMapFunction, * RichMapFunction 的 open 方法中会初始化 ValueState 类型的 pvState,pvState 就是上文一直强调的状态信息, * 每次 CheckPoint 的时候,会把 pvState 的状态信息快照一份到 hdfs 来提供恢复。 * 这里按照 appId 进行 keyBy,所以每一个 appId 都会对应一个 pvState,pvState 里存储着该 appId 对应的 pv 值。 * 每来一条数据都会执行一次 map 方法, * 当这条数据对应的 appId 是新 app 时,pvState 里就没有存储这个 appId 当前的 pv 值,将 pv 值赋值为 1, * 当 pvState 里存储的 value 不为 null 时,拿出 pv 值 +1后 update 到 pvState 里。 * map 方法再将 appId 和 pv 值发送到下游算子,下游直接调用了 print 进行输出,这里完全可以替换成相应的 RedisSink 或 HBaseSink。 * 本案例中计算 pv 的工作交给了 Flink 内部的 ValueState,不依赖外部存储介质进行累加,外部介质承担的角色仅仅是提供数据给业务方查询, * 所以无论下游使用什么形式的 Sink,只要 Sink 端能够按照主键去重,该统计方案就可以保障 Exactly Once。 */ @Slf4j public class PvStatExactlyOnce { // 生产数据: kafka-console-producer --topic app-topic --broker-list 192.168.30.215:9092,192.168.30.216:9092,192.168.30.220:9092 public static void main(String[] args) throws Exception { final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); // 1 分钟一次CheckPoint env.enableCheckpointing(TimeUnit.MINUTES.toMillis(1)); env.setParallelism(2); CheckpointConfig checkpointConf = env.getCheckpointConfig(); // CheckPoint 语义 EXACTLY ONCE checkpointConf.setCheckpointingMode(CheckpointingMode.EXACTLY_ONCE); checkpointConf.enableExternalizedCheckpoints(CheckpointConfig.ExternalizedCheckpointCleanup.RETAIN_ON_CANCELLATION); Properties props = new Properties(); props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092"); props.put(ConsumerConfig.GROUP_ID_CONFIG, "app-pv-stat"); DataStreamSource appInfoSource = env.addSource(new FlinkKafkaConsumer<>( // kafka topic, String 序列化 PvStatExactlyOnceKafkaUtil.topic, new SimpleStringSchema(), props)); // 按照 appId 进行 keyBy appInfoSource.keyBy((KeySelector) appId -> appId) .map(new RichMapFunction>() { private ValueState pvState; private long pv = 0; @Override public void open(Configuration parameters) throws Exception { super.open(parameters); // 初始化状态 pvState = getRuntimeContext().getState( new ValueStateDescriptor<>("pvStat", TypeInformation.of(new TypeHint() { }))); } @Override public Tuple2 map(String appId) throws Exception { // 从状态中获取该 app 的pv值,+1后,update 到状态中 if (null == pvState.value()) { log.info("{} is new, pv is 1", appId); pv = 1; } else { pv = pvState.value(); pv += 1; log.info("{} is old , pv is {}", appId, pv); } pvState.update(pv); return new Tuple2<>(appId, pv); } }) .print(); env.execute("Flink pv stat"); } } ================================================ FILE: flink-learning-examples/src/main/java/com/zhisheng/examples/streaming/checkpoint/PvStatLocalKeyByExactlyOnce.java ================================================ package com.zhisheng.examples.streaming.checkpoint; import com.zhisheng.examples.streaming.checkpoint.util.PvStatExactlyOnceKafkaUtil; import lombok.extern.slf4j.Slf4j; import org.apache.flink.api.common.functions.RichFlatMapFunction; import org.apache.flink.api.common.functions.RichMapFunction; import org.apache.flink.api.common.serialization.SimpleStringSchema; import org.apache.flink.api.common.state.ListState; import org.apache.flink.api.common.state.ListStateDescriptor; import org.apache.flink.api.common.state.ValueState; import org.apache.flink.api.common.state.ValueStateDescriptor; import org.apache.flink.api.common.typeinfo.TypeHint; import org.apache.flink.api.common.typeinfo.TypeInformation; import org.apache.flink.api.java.functions.KeySelector; import org.apache.flink.api.java.tuple.Tuple2; import org.apache.flink.configuration.Configuration; import org.apache.flink.runtime.state.FunctionInitializationContext; import org.apache.flink.runtime.state.FunctionSnapshotContext; import org.apache.flink.streaming.api.CheckpointingMode; import org.apache.flink.streaming.api.checkpoint.CheckpointedFunction; import org.apache.flink.streaming.api.environment.CheckpointConfig; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumer; import org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumerBase; import org.apache.flink.util.Collector; import org.apache.kafka.clients.consumer.ConsumerConfig; import java.util.HashMap; import java.util.Map; import java.util.Properties; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import static org.apache.flink.util.Preconditions.checkArgument; /** * @author fanrui * @date 2019-10-20 13:45:46 * @desc 本案例在 PvStatExactlyOnce 的基础上增加了 LocalKeyBy 的优化, * 优化场景如下:统计各 app 的 pv,需要有个前提,将相同 app 的数据必须发送到相同的 subtask, * 否则,多个 subtask 都会统计同一个 app 的数据,就会造成 pv 值计算错误。 * 问题就在于当某个 app 比较热门时,这个app 对应的数据量较大,可能单个 subtask 根本处理不了这么多的数据 * 或者热门 app 占整个数据量的 99%,就算计算pv 的 task 设置的并行度为 10, * 但是这个 app 的数据只能被分到同一个 subtask 上去处理, * 问题就出现了,1个 subtask 要处理 99%的数据,其余 9 个 subtask 处理 1%的数据, * 发生了严重的数据倾斜,怎么处理呢? 本案例使用 LocalKeyBy 的思想来处理数据倾斜 */ @Slf4j public class PvStatLocalKeyByExactlyOnce { public static void main(String[] args) throws Exception { final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); // 1 分钟一次 Checkpoint env.enableCheckpointing(TimeUnit.MINUTES.toMillis(1)); env.setParallelism(2); CheckpointConfig checkpointConf = env.getCheckpointConfig(); // Checkpoint 语义 EXACTLY ONCE checkpointConf.setCheckpointingMode(CheckpointingMode.EXACTLY_ONCE); checkpointConf.enableExternalizedCheckpoints(CheckpointConfig.ExternalizedCheckpointCleanup.RETAIN_ON_CANCELLATION); Properties props = new Properties(); props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, PvStatExactlyOnceKafkaUtil.broker_list); props.put(ConsumerConfig.GROUP_ID_CONFIG, "app-pv-stat"); FlinkKafkaConsumerBase appKafkaConsumer = new FlinkKafkaConsumer<>( // kafka topic, String 序列化 PvStatExactlyOnceKafkaUtil.topic, new SimpleStringSchema(), props).setStartFromLatest(); env.addSource(appKafkaConsumer) .flatMap(new LocalKeyByFlatMap(10)) // 按照 appId 进行 keyBy .keyBy((KeySelector, String>) appIdPv -> appIdPv.f0) .map(new RichMapFunction, Tuple2>() { private ValueState pvState; private long pv = 0; @Override public void open(Configuration parameters) throws Exception { super.open(parameters); // 初始化状态 pvState = getRuntimeContext().getState( new ValueStateDescriptor<>("pvStat", TypeInformation.of(new TypeHint() { }))); } @Override public Tuple2 map(Tuple2 tuple2) throws Exception { // 从状态中获取该 app 的pv值,加上新收到的 pv 值以后后,update 到状态中 if (null == pvState.value()) { log.info("{} is new, PV is {}", tuple2.f0, tuple2.f1); pv = tuple2.f1; } else { pv = pvState.value(); pv += tuple2.f1; log.info("{} is old, PV is {}", tuple2.f0, pv); } pvState.update(pv); tuple2.setField(pv, 1); return tuple2; } }) .print(); env.execute("Flink pv stat LocalKeyBy"); } } /** * LocalKeyByFlatMap 中实现了在 shuffle 的上游端对数据进行预聚合, * 从而减少发送到下游的数据量,使得热点数据量大大降低。 * 注:本案例中积攒批次使用数据量来积攒,当长时间数据量较少时,由于数据量积攒不够, * 可能导致上游buffer中数据不往下游发送,可以加定时策略, * 例如:如果数据量少但是时间超过了 200ms,也会强制将数据发送到下游 */ @Slf4j class LocalKeyByFlatMap extends RichFlatMapFunction> implements CheckpointedFunction { /** * 由于加了 buffer,所以 Checkpoint 的时候, * 可能还有 Checkpoint 之前的数据缓存在 buffer 中没有发送到下游被处理 * 把这部分数据放到 localPvStatListState 中,当 Checkpoint 恢复时, * 把这部分数据从状态中恢复到 buffer 中 */ private ListState> localPvStatListState; /** * 本地 buffer,存放 local 端缓存的 app 的 pv 信息 */ private HashMap localPvStat; /** * 缓存的数据量大小,即:缓存多少数据再向下游发送 */ private int batchSize; /** * 计数器,获取当前批次接收的数据量 */ private AtomicInteger currentSize; private int subtaskIndex; LocalKeyByFlatMap(int batchSize) { checkArgument(batchSize >= 0, "Cannot define a negative value for the batchSize."); this.batchSize = batchSize; } @Override public void flatMap(String appId, Collector> collector) throws Exception { // 将新来的数据添加到 buffer 中 Long pv = localPvStat.getOrDefault(appId, 0L); localPvStat.put(appId, pv + 1); log.info("invoke subtask: {} appId: {} pv: {}", subtaskIndex, appId, localPvStat.get(appId)); // 如果到达设定的批次,则将 buffer 中的数据发送到下游 if (currentSize.incrementAndGet() >= batchSize) { for (Map.Entry appIdPv : localPvStat.entrySet()) { collector.collect(Tuple2.of(appIdPv.getKey(), appIdPv.getValue())); log.info("batchSend subtask: {} appId: {} PV: {}", subtaskIndex, appIdPv.getKey(), appIdPv.getValue()); } localPvStat.clear(); currentSize.set(0); } } @Override public void snapshotState(FunctionSnapshotContext functionSnapshotContext) throws Exception { // 将 buffer 中的数据保存到状态中,来保证 Exactly Once localPvStatListState.clear(); for (Map.Entry appIdPv : localPvStat.entrySet()) { localPvStatListState.add(Tuple2.of(appIdPv.getKey(), appIdPv.getValue())); log.info("snapshot subtask: {} appId: {} pv: {}", subtaskIndex, appIdPv.getKey(), appIdPv.getValue()); } } @Override public void initializeState(FunctionInitializationContext context) throws Exception { subtaskIndex = getRuntimeContext().getIndexOfThisSubtask(); // 从状态中恢复 buffer 中的数据 localPvStatListState = context.getOperatorStateStore().getListState( new ListStateDescriptor<>("localPvStat", TypeInformation.of(new TypeHint>() { }))); localPvStat = new HashMap<>(); if (context.isRestored()) { // 从状态中恢复 buffer 中的数据 for (Tuple2 appIdPv : localPvStatListState.get()) { long pv = localPvStat.getOrDefault(appIdPv.f0, 0L); // 如果出现 pv != 0,说明改变了并行度, // ListState 中的数据会被均匀分发到新的 subtask中 // 所以单个 subtask 恢复的状态中可能包含两个相同的 app 的数据 localPvStat.put(appIdPv.f0, pv + appIdPv.f1); log.info("init subtask: {} appId: {} pv:{}", subtaskIndex, appIdPv.f0, appIdPv.f1); } // 从状态恢复时,默认认为 buffer 中数据量达到了 batchSize,需要向下游发送数据了 currentSize = new AtomicInteger(batchSize); } else { currentSize = new AtomicInteger(0); } } } ================================================ FILE: flink-learning-examples/src/main/java/com/zhisheng/examples/streaming/checkpoint/util/PvStatExactlyOnceKafkaUtil.java ================================================ package com.zhisheng.examples.streaming.checkpoint.util; import org.apache.kafka.clients.producer.KafkaProducer; import org.apache.kafka.clients.producer.ProducerRecord; import java.util.HashMap; import java.util.Map; import java.util.Properties; import java.util.Random; /** * @author fanrui * @date 2019-10-03 22:35:40 * @desc 用于给 PvStatExactlyOnce 生成数据, 并统计数据集中不同数据的个数 */ public class PvStatExactlyOnceKafkaUtil { public static final String broker_list = "192.168.30.215:9092,192.168.30.216:9092,192.168.30.220:9092"; private static final HashMap producerMap = new HashMap<>(); /** * kafka topic,Flink 程序中需要和这个统一 */ public static final String topic = "app-topic"; private static void writeToKafka() { Properties props = new Properties(); props.put("bootstrap.servers", broker_list); props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer"); props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer"); KafkaProducer producer = new KafkaProducer(props); // 生成 0~9 的随机数做为 appId String value = "" + new Random().nextInt(2); ProducerRecord record = new ProducerRecord(topic, null, null, value); producer.send(record); System.out.println("发送数据: " + value); Long pv = producerMap.get(value); if (null == pv) { producerMap.put(value, 1L); } else { producerMap.put(value, pv + 1); } System.out.println("生产数据:"); for (Map.Entry appIdPv : producerMap.entrySet()) { System.out.println("appId:" + appIdPv.getKey() + " pv:" + appIdPv.getValue()); } producer.flush(); } public static void main(String[] args) throws InterruptedException { while (true) { Thread.sleep(1000); writeToKafka(); } } } ================================================ FILE: flink-learning-examples/src/main/java/com/zhisheng/examples/streaming/config/ConfigurationMain.java ================================================ package com.zhisheng.examples.streaming.config; import org.apache.flink.api.common.functions.RichFlatMapFunction; import org.apache.flink.api.java.ExecutionEnvironment; import org.apache.flink.api.java.tuple.Tuple2; import org.apache.flink.configuration.Configuration; import org.apache.flink.util.Collector; /** * Desc: Configuration store the config * Created by zhisheng on 2019/10/9 下午8:13 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class ConfigurationMain { public static void main(String[] args) throws Exception { ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment(); // Configuration store the config Configuration configuration = new Configuration(); configuration.setString("name", "zhisheng"); env.fromElements(WORDS) .flatMap(new RichFlatMapFunction>() { String name; @Override public void open(Configuration parameters) throws Exception { name = parameters.getString("name", ""); super.open(parameters); } @Override public void flatMap(String value, Collector> out) throws Exception { String[] splits = value.toLowerCase().split("\\W+"); for (String split : splits) { if (split.length() > 0) { out.collect(new Tuple2<>(split + name, 1)); } } } }).withParameters(configuration) .print(); } private static final String[] WORDS = new String[]{ "To be, or not to be,--that is the question:--", "Whether 'tis nobler in the mind to suffer", "The slings and arrows of outrageous fortune", "Or to take arms against a sea of troubles,", "And by opposing end them?--To die,--to sleep,--", "No more; and by a sleep to say we end", "The heartache, and the thousand natural shocks", "That flesh is heir to,--'tis a consummation", "Devoutly to be wish'd. To die,--to sleep;--", "To sleep! perchance to dream:--ay, there's the rub;", "For in that sleep of death what dreams may come,", "When we have shuffled off this mortal coil,", "Must give us pause: there's the respect", "That makes calamity of so long life;", "For who would bear the whips and scorns of time,", "The oppressor's wrong, the proud man's contumely,", "The pangs of despis'd love, the law's delay,", "The insolence of office, and the spurns", "That patient merit of the unworthy takes,", "When he himself might his quietus make", "With a bare bodkin? who would these fardels bear,", "To grunt and sweat under a weary life,", "But that the dread of something after death,--", "The undiscover'd country, from whose bourn", "No traveller returns,--puzzles the will,", "And makes us rather bear those ills we have", "Than fly to others that we know not of?", "Thus conscience does make cowards of us all;", "And thus the native hue of resolution", "Is sicklied o'er with the pale cast of thought;", "And enterprises of great pith and moment,", "With this regard, their currents turn awry,", "And lose the name of action.--Soft you now!", "The fair Ophelia!--Nymph, in thy orisons", "Be all my sins remember'd." }; } ================================================ FILE: flink-learning-examples/src/main/java/com/zhisheng/examples/streaming/config/ConfigurationMain1.java ================================================ package com.zhisheng.examples.streaming.config; import org.apache.flink.api.common.functions.RichFilterFunction; import org.apache.flink.api.common.functions.RichFlatMapFunction; import org.apache.flink.api.java.ExecutionEnvironment; import org.apache.flink.api.java.tuple.Tuple2; import org.apache.flink.configuration.Configuration; import org.apache.flink.util.Collector; /** * Desc: Configuration store the config * Created by zhisheng on 2019/10/9 下午8:13 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class ConfigurationMain1 { public static void main(String[] args) throws Exception { ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment(); // Configuration store the config Configuration configuration = new Configuration(); configuration.setString("name", "zhisheng"); env.fromElements(WORDS) .flatMap(new RichFlatMapFunction>() { String name; @Override public void open(Configuration parameters) throws Exception { name = parameters.getString("name", ""); super.open(parameters); } @Override public void flatMap(String value, Collector> out) throws Exception { String[] splits = value.toLowerCase().split("\\W+"); for (String split : splits) { if (split.length() > 0) { out.collect(new Tuple2<>(split + name, 1)); } } } }).withParameters(configuration) .filter(new RichFilterFunction>() { private String name; @Override public void open(Configuration parameters) throws Exception { name = parameters.getString("name", ""); System.out.println("=====================" + name); } @Override public boolean filter(Tuple2 tuple2) throws Exception { return !tuple2.f0.contains(name); } }).withParameters(configuration) .print(); } private static final String[] WORDS = new String[]{ "To be, or not to be,--that is the question:--", "Whether 'tis nobler in the mind to suffer", "The slings and arrows of outrageous fortune", "Or to take arms against a sea of troubles,", "And by opposing end them?--To die,--to sleep,--", "No more; and by a sleep to say we end", "The heartache, and the thousand natural shocks", "That flesh is heir to,--'tis a consummation", "Devoutly to be wish'd. To die,--to sleep;--", "To sleep! perchance to dream:--ay, there's the rub;", "For in that sleep of death what dreams may come,", "When we have shuffled off this mortal coil,", "Must give us pause: there's the respect", "That makes calamity of so long life;", "For who would bear the whips and scorns of time,", "The oppressor's wrong, the proud man's contumely,", "The pangs of despis'd love, the law's delay,", "The insolence of office, and the spurns", "That patient merit of the unworthy takes,", "When he himself might his quietus make", "With a bare bodkin? who would these fardels bear,", "To grunt and sweat under a weary life,", "But that the dread of something after death,--", "The undiscover'd country, from whose bourn", "No traveller returns,--puzzles the will,", "And makes us rather bear those ills we have", "Than fly to others that we know not of?", "Thus conscience does make cowards of us all;", "And thus the native hue of resolution", "Is sicklied o'er with the pale cast of thought;", "And enterprises of great pith and moment,", "With this regard, their currents turn awry,", "And lose the name of action.--Soft you now!", "The fair Ophelia!--Nymph, in thy orisons", "Be all my sins remember'd." }; } ================================================ FILE: flink-learning-examples/src/main/java/com/zhisheng/examples/streaming/config/ParameterToolGetArgsMain.java ================================================ package com.zhisheng.examples.streaming.config; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.api.functions.source.RichSourceFunction; /** * Desc: ParameterTool get args config * you can run the demo with the arguments like `--name zhisheng` or `-name zhisheng` * Created by zhisheng on 2019/10/9 下午8:50 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class ParameterToolGetArgsMain { public static void main(String[] args) throws Exception { StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.getConfig().setGlobalJobParameters(ParameterTool.fromArgs(args)); env.addSource(new RichSourceFunction() { @Override public void run(SourceContext sourceContext) throws Exception { while (true) { ParameterTool parameterTool = (ParameterTool) getRuntimeContext().getExecutionConfig().getGlobalJobParameters(); sourceContext.collect(System.currentTimeMillis() + parameterTool.get("name") + parameterTool.get("username")); } } @Override public void cancel() { } }).print(); env.execute("ParameterTool Get config from Args"); } } ================================================ FILE: flink-learning-examples/src/main/java/com/zhisheng/examples/streaming/config/ParameterToolGetPropertiesMain.java ================================================ package com.zhisheng.examples.streaming.config; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.api.functions.source.RichSourceFunction; /** * Desc: ParameterTool get SystemProperties config * Created by zhisheng on 2019/10/9 下午8:50 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class ParameterToolGetPropertiesMain { public static void main(String[] args) throws Exception { StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.getConfig().setGlobalJobParameters(ParameterTool.fromPropertiesFile(ParameterToolGetPropertiesMain.class.getResourceAsStream("/application.properties"))); env.addSource(new RichSourceFunction() { @Override public void run(SourceContext sourceContext) throws Exception { while (true) { ParameterTool parameterTool = (ParameterTool) getRuntimeContext().getExecutionConfig().getGlobalJobParameters(); sourceContext.collect(System.currentTimeMillis() + parameterTool.get("metrics.topic")); } } @Override public void cancel() { } }).print(); env.execute("ParameterTool Get config from SystemProperties"); } } ================================================ FILE: flink-learning-examples/src/main/java/com/zhisheng/examples/streaming/config/ParameterToolGetSystemMain.java ================================================ package com.zhisheng.examples.streaming.config; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.api.functions.source.RichSourceFunction; /** * Desc: ParameterTool get SystemProperties config * Created by zhisheng on 2019/10/9 下午8:50 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class ParameterToolGetSystemMain { public static void main(String[] args) throws Exception { StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.getConfig().setGlobalJobParameters(ParameterTool.fromSystemProperties()); env.addSource(new RichSourceFunction() { @Override public void run(SourceContext sourceContext) throws Exception { while (true) { ParameterTool parameterTool = (ParameterTool) getRuntimeContext().getExecutionConfig().getGlobalJobParameters(); sourceContext.collect(System.currentTimeMillis() + parameterTool.get("os.name") + parameterTool.get("user.home")); } } @Override public void cancel() { } }).print(); env.execute("ParameterTool Get config from SystemProperties"); } } ================================================ FILE: flink-learning-examples/src/main/java/com/zhisheng/examples/streaming/file/Main.java ================================================ package com.zhisheng.examples.streaming.file; import org.apache.flink.streaming.api.datastream.DataStreamSource; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; /** * 从文件读取数据 & 数据写入到文件 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class Main { public static void main(String[] args) throws Exception { final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.setParallelism(1); DataStreamSource data = env.readTextFile("file:///usr/local/blink-1.5.1/README.txt"); data.print(); //两种格式都行,另外还支持写入到 hdfs // data.writeAsText("file:///usr/local/blink-1.5.1/README1.txt"); data.writeAsText("/usr/local/blink-1.5.1/README1.txt"); env.execute(); } } ================================================ FILE: flink-learning-examples/src/main/java/com/zhisheng/examples/streaming/join/WindowJoin.java ================================================ package com.zhisheng.examples.streaming.join; import org.apache.flink.api.common.functions.JoinFunction; import org.apache.flink.api.java.functions.KeySelector; import org.apache.flink.api.java.tuple.Tuple2; import org.apache.flink.api.java.tuple.Tuple3; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.streaming.api.TimeCharacteristic; import org.apache.flink.streaming.api.datastream.DataStream; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.api.windowing.assigners.TumblingEventTimeWindows; import org.apache.flink.streaming.api.windowing.time.Time; /** * Example illustrating a windowed stream join between two data streams. * *

The example works on two input streams with pairs (name, grade) and (name, salary) * respectively. It joins the steams based on "name" within a configurable window. * *

The example uses a built-in sample data generator that generates * the steams of pairs at a configurable rate. * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class WindowJoin { // ************************************************************************* // PROGRAM // ************************************************************************* public static void main(String[] args) throws Exception { final ParameterTool params = ParameterTool.fromArgs(args); final long windowSize = params.getLong("windowSize", 2000); final long rate = params.getLong("rate", 3L); System.out.println("Using windowSize=" + windowSize + ", data rate=" + rate); StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.setStreamTimeCharacteristic(TimeCharacteristic.IngestionTime); env.getConfig().setGlobalJobParameters(params); DataStream> grades = WindowJoinSampleData.GradeSource.getSource(env, rate); DataStream> salaries = WindowJoinSampleData.SalarySource.getSource(env, rate); // grades.print(); // salaries.print(); runWindowJoin(grades, salaries, windowSize).print().setParallelism(1); env.execute("Windowed Join Example"); } public static DataStream> runWindowJoin( DataStream> grades, DataStream> salaries, long windowSize) { return grades.join(salaries) .where(new NameKeySelector()) .equalTo(new NameKeySelector()) .window(TumblingEventTimeWindows.of(Time.milliseconds(windowSize))) .apply(new JoinFunction, Tuple2, Tuple3>() { @Override public Tuple3 join(Tuple2 first, Tuple2 second) { return new Tuple3(first.f0, first.f1, second.f1); } }); } private static class NameKeySelector implements KeySelector, String> { @Override public String getKey(Tuple2 value) { return value.f0; } } } ================================================ FILE: flink-learning-examples/src/main/java/com/zhisheng/examples/streaming/join/WindowJoinSampleData.java ================================================ package com.zhisheng.examples.streaming.join; import com.zhisheng.examples.util.ThrottledIterator; import org.apache.flink.api.common.typeinfo.TypeHint; import org.apache.flink.api.common.typeinfo.TypeInformation; import org.apache.flink.api.java.tuple.Tuple2; import org.apache.flink.streaming.api.datastream.DataStream; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import java.io.Serializable; import java.util.Iterator; import java.util.Random; /** * Sample data for the {@link WindowJoin} example. * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class WindowJoinSampleData { static final String[] NAMES = {"tom", "jerry", "alice", "bob", "john", "grace"}; static final int GRADE_COUNT = 5; static final int SALARY_MAX = 10000; /** * Continuously generates (name, grade). */ public static class GradeSource implements Iterator>, Serializable { private final Random rnd = new Random(hashCode()); @Override public boolean hasNext() { return true; } @Override public Tuple2 next() { return new Tuple2<>(NAMES[rnd.nextInt(NAMES.length)], rnd.nextInt(GRADE_COUNT) + 1); } @Override public void remove() { throw new UnsupportedOperationException(); } public static DataStream> getSource(StreamExecutionEnvironment env, long rate) { return env.fromCollection(new ThrottledIterator<>(new GradeSource(), rate), TypeInformation.of(new TypeHint>() { })); } } /** * Continuously generates (name, salary). */ public static class SalarySource implements Iterator>, Serializable { private final Random rnd = new Random(hashCode()); @Override public boolean hasNext() { return true; } @Override public Tuple2 next() { return new Tuple2<>(NAMES[rnd.nextInt(NAMES.length)], rnd.nextInt(SALARY_MAX) + 1); } @Override public void remove() { throw new UnsupportedOperationException(); } public static DataStream> getSource(StreamExecutionEnvironment env, long rate) { return env.fromCollection(new ThrottledIterator<>(new SalarySource(), rate), TypeInformation.of(new TypeHint>() { })); } } } ================================================ FILE: flink-learning-examples/src/main/java/com/zhisheng/examples/streaming/ml/IncrementalLearningSkeleton.java ================================================ package com.zhisheng.examples.streaming.ml; import org.apache.flink.streaming.api.TimeCharacteristic; import org.apache.flink.streaming.api.datastream.DataStream; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.api.functions.AssignerWithPunctuatedWatermarks; import org.apache.flink.streaming.api.functions.co.CoMapFunction; import org.apache.flink.streaming.api.functions.source.SourceFunction; import org.apache.flink.streaming.api.functions.windowing.AllWindowFunction; import org.apache.flink.streaming.api.watermark.Watermark; import org.apache.flink.streaming.api.windowing.time.Time; import org.apache.flink.streaming.api.windowing.windows.TimeWindow; import org.apache.flink.util.Collector; import java.util.concurrent.TimeUnit; /** * Skeleton for incremental machine learning algorithm consisting of a * pre-computed model, which gets updated for the new inputs and new input data * for which the job provides predictions. * *

This may serve as a base of a number of algorithms, e.g. updating an * incremental Alternating Least Squares model while also providing the * predictions. * *

This example shows how to use: *

    *
  • Connected streams *
  • CoFunctions *
  • Tuple data types *
* blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class IncrementalLearningSkeleton { public static void main(String[] args) throws Exception { StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime); DataStream trainingData = env.addSource(new FiniteTrainingDataSource()); DataStream newData = env.addSource(new FiniteNewDataSource()); DataStream model = trainingData .assignTimestampsAndWatermarks(new LinearTimestamp()) .timeWindowAll(Time.of(5000, TimeUnit.MILLISECONDS)) .apply(new PartialModelBuilder()); newData.connect(model).map(new Predictor()).print(); env.execute("Streaming Incremental Learning"); } /** * Feeds new data for newData. By default it is implemented as constantly * emitting the Integer 1 in a loop. */ public static class FiniteNewDataSource implements SourceFunction { private static final long serialVersionUID = 1L; private int counter; @Override public void run(SourceContext ctx) throws Exception { Thread.sleep(15); while (counter < 50) { ctx.collect(getNewData()); } } @Override public void cancel() { // No cleanup needed } private Integer getNewData() throws InterruptedException { Thread.sleep(5); counter++; return 1; } } /** * Feeds new training data for the partial model builder. By default it is * implemented as constantly emitting the Integer 1 in a loop. */ public static class FiniteTrainingDataSource implements SourceFunction { private static final long serialVersionUID = 1L; private int counter = 0; @Override public void run(SourceContext collector) throws Exception { while (counter < 8200) { collector.collect(getTrainingData()); } } @Override public void cancel() { // No cleanup needed } private Integer getTrainingData() throws InterruptedException { counter++; return 1; } } private static class LinearTimestamp implements AssignerWithPunctuatedWatermarks { private static final long serialVersionUID = 1L; private long counter = 0L; @Override public long extractTimestamp(Integer element, long previousElementTimestamp) { return counter += 10L; } @Override public Watermark checkAndGetNextWatermark(Integer lastElement, long extractedTimestamp) { return new Watermark(counter - 1); } } /** * Builds up-to-date partial models on new training data. */ public static class PartialModelBuilder implements AllWindowFunction { private static final long serialVersionUID = 1L; protected Double[] buildPartialModel(Iterable values) { return new Double[]{1.}; } @Override public void apply(TimeWindow window, Iterable values, Collector out) throws Exception { out.collect(buildPartialModel(values)); } } /** * Creates newData using the model produced in batch-processing and the * up-to-date partial model. *

* By default emits the Integer 0 for every newData and the Integer 1 * for every model update. *

*/ public static class Predictor implements CoMapFunction { private static final long serialVersionUID = 1L; Double[] batchModel = null; Double[] partialModel = null; @Override public Integer map1(Integer value) { // Return newData return predict(value); } @Override public Integer map2(Double[] value) { // Update model partialModel = value; batchModel = getBatchModel(); return 1; } protected Double[] getBatchModel() { return new Double[]{0.}; } protected Integer predict(Integer inTuple) { return 0; } } } ================================================ FILE: flink-learning-examples/src/main/java/com/zhisheng/examples/streaming/ml/IncrementalLearningSkeletonData.java ================================================ package com.zhisheng.examples.streaming.ml; /** * Data for IncrementalLearningSkeletonITCase. * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class IncrementalLearningSkeletonData { public static final String RESULTS = "1\n" + "1\n" + "1\n" + "1\n" + "1\n" + "1\n" + "1\n" + "1\n" + "1\n" + "1\n" + "1\n" + "1\n" + "1\n" + "1\n" + "1\n" + "1\n" + "1\n" + "0\n" + "0\n" + "0\n" + "0\n" + "0\n" + "0\n" + "0\n" + "0\n" + "0\n" + "0\n" + "0\n" + "0\n" + "0\n" + "0\n" + "0\n" + "0\n" + "0\n" + "0\n" + "0\n" + "0\n" + "0\n" + "0\n" + "0\n" + "0\n" + "0\n" + "0\n" + "0\n" + "0\n" + "0\n" + "0\n" + "0\n" + "0\n" + "0\n" + "0\n" + "0\n" + "0\n" + "0\n" + "0\n" + "0\n" + "0\n" + "0\n" + "0\n" + "0\n" + "0\n" + "0\n" + "0\n" + "0\n" + "0\n" + "0\n" + "0\n"; private IncrementalLearningSkeletonData() { } } ================================================ FILE: flink-learning-examples/src/main/java/com/zhisheng/examples/streaming/parallelism/Main.java ================================================ package com.zhisheng.examples.streaming.parallelism; import org.apache.flink.api.common.functions.MapFunction; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.api.functions.source.SourceFunction; /** * Desc: parallelism & slot * Created by zhisheng on 2019/10/6 下午1:43 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class Main { public static void main(String[] args) throws Exception { //创建流运行环境 StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.getConfig().setGlobalJobParameters(ParameterTool.fromArgs(args)); env.setParallelism(1); env.addSource(new SourceFunction() { @Override public void run(SourceContext sourceContext) throws Exception { while (true) { sourceContext.collect(System.currentTimeMillis()); } } @Override public void cancel() { } }) .map((MapFunction) aLong -> aLong / 1000).setParallelism(3) .print(); env.execute("zhisheng RestartStrategy example"); } } ================================================ FILE: flink-learning-examples/src/main/java/com/zhisheng/examples/streaming/processFunction/KeyedProcessFunctionMain.java ================================================ package com.zhisheng.examples.streaming.processFunction; import org.apache.flink.api.common.functions.FlatMapFunction; import org.apache.flink.api.java.tuple.Tuple; import org.apache.flink.api.java.tuple.Tuple2; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.api.functions.KeyedProcessFunction; import org.apache.flink.util.Collector; /** * Desc: KeyedProcessFunction * Created by zhisheng on 2019/10/14 下午3:50 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class KeyedProcessFunctionMain { public static void main(String[] args) throws Exception { StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.getConfig().setGlobalJobParameters(ParameterTool.fromArgs(args)); env.fromElements(WORDS) .flatMap(new FlatMapFunction>() { @Override public void flatMap(String value, Collector> out) throws Exception { String[] splits = value.toLowerCase().split("\\W+"); for (String split : splits) { if (split.length() > 0) { out.collect(new Tuple2<>(split, 1)); } } } }) .keyBy(0) .process(new KeyedProcessFunction, Tuple2>() { @Override public void processElement(Tuple2 value, Context ctx, Collector> out) throws Exception { System.out.println(ctx.getCurrentKey()); out.collect(new Tuple2<>(ctx.getCurrentKey() + value.f0, value.f1 + 1)); } @Override public void onTimer(long timestamp, OnTimerContext ctx, Collector> out) throws Exception { System.out.println(ctx.getCurrentKey()); super.onTimer(timestamp, ctx, out); } }) .print(); env.execute(); } private static final String[] WORDS = new String[]{ "To be, or not to be,--that is the question:--", "Whether 'tis nobler in the mind to suffer", "The slings and arrows of outrageous fortune", "Or to take arms against a sea of troubles,", "And by opposing end them?--To die,--to sleep,--", "No more; and by a sleep to say we end", "The heartache, and the thousand natural shocks", "That flesh is heir to,--'tis a consummation", "Devoutly to be wish'd. To die,--to sleep;--", "To sleep! perchance to dream:--ay, there's the rub;", "For in that sleep of death what dreams may come,", "When we have shuffled off this mortal coil,", "Must give us pause: there's the respect", "That makes calamity of so long life;", "For who would bear the whips and scorns of time,", "The oppressor's wrong, the zhisheng_blog proud man's contumely,", "The pangs of despis'd love, the law's delay,", "The insolence of office, and the spurns", "That patient merit of the unworthy takes,", "When he himself might his quietus make", "With a bare bodkin? who would these fardels bear,", "To grunt and sweat under a weary life,", "But that the dread of something after death,--", "The undiscover'd country, from whose bourn", "No traveller returns,--puzzles the will,", "And makes us rather bear those ills we have", "Than fly to others that we know not of?", "Thus conscience does make cowards of us all;", "And thus the native hue of resolution", "Is sicklied o'er with the pale cast of thought;", "And enterprises of great pith and moment,", "With this regard, their currents turn awry,", "And lose the name of action.--Soft you now!", "The fair Ophelia!--Nymph, in thy orisons", "Be all my sins remember'd." }; } ================================================ FILE: flink-learning-examples/src/main/java/com/zhisheng/examples/streaming/processFunction/ProcessFunctionMain.java ================================================ package com.zhisheng.examples.streaming.processFunction; import org.apache.flink.api.common.functions.FlatMapFunction; import org.apache.flink.api.java.tuple.Tuple2; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.api.functions.ProcessFunction; import org.apache.flink.util.Collector; /** * Desc: ProcessFunction * Created by zhisheng on 2019/10/14 下午3:49 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class ProcessFunctionMain { public static void main(String[] args) throws Exception { StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.getConfig().setGlobalJobParameters(ParameterTool.fromArgs(args)); env.fromElements(WORDS) .flatMap(new FlatMapFunction>() { @Override public void flatMap(String value, Collector> out) throws Exception { String[] splits = value.toLowerCase().split("\\W+"); for (String split : splits) { if (split.length() > 0) { out.collect(new Tuple2<>(split, 1)); } } } }) .process(new ProcessFunction, Tuple2>() { @Override public void processElement(Tuple2 value, Context ctx, Collector> out) throws Exception { out.collect(new Tuple2<>(value.f0, value.f1 + 1)); } }) .print(); env.execute(); } private static final String[] WORDS = new String[]{ "To be, or not to be,--that is the question:--", "Whether 'tis nobler in the mind to suffer", "The slings and arrows of outrageous fortune", "Or to take arms against a sea of troubles,", "And by opposing end them?--To die,--to sleep,--", "No more; and by a sleep to say we end", "The heartache, and the thousand natural shocks", "That flesh is heir to,--'tis a consummation", "Devoutly to be wish'd. To die,--to sleep;--", "To sleep! perchance to dream:--ay, there's the rub;", "For in that sleep of death what dreams may come,", "When we have shuffled off this mortal coil,", "Must give us pause: there's the respect", "That makes calamity of so long life;", "For who would bear the whips and scorns of time,", "The oppressor's wrong, the zhisheng_blog proud man's contumely,", "The pangs of despis'd love, the law's delay,", "The insolence of office, and the spurns", "That patient merit of the unworthy takes,", "When he himself might his quietus make", "With a bare bodkin? who would these fardels bear,", "To grunt and sweat under a weary life,", "But that the dread of something after death,--", "The undiscover'd country, from whose bourn", "No traveller returns,--puzzles the will,", "And makes us rather bear those ills we have", "Than fly to others that we know not of?", "Thus conscience does make cowards of us all;", "And thus the native hue of resolution", "Is sicklied o'er with the pale cast of thought;", "And enterprises of great pith and moment,", "With this regard, their currents turn awry,", "And lose the name of action.--Soft you now!", "The fair Ophelia!--Nymph, in thy orisons", "Be all my sins remember'd." }; } ================================================ FILE: flink-learning-examples/src/main/java/com/zhisheng/examples/streaming/remote/Main.java ================================================ package com.zhisheng.examples.streaming.remote; import org.apache.flink.api.java.ExecutionEnvironment; /** * 向远程集群提交 job * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class Main { public static void main(String[] args) throws Exception { ExecutionEnvironment env = ExecutionEnvironment.createRemoteEnvironment( "localhost", 6123, 1, "/usr/local/blink-1.5.1/examples/streaming/SessionWindowing.jar" ); } } ================================================ FILE: flink-learning-examples/src/main/java/com/zhisheng/examples/streaming/restartStrategy/AEMain.java ================================================ package com.zhisheng.examples.streaming.restartStrategy; import org.apache.flink.api.common.functions.MapFunction; import org.apache.flink.api.common.restartstrategy.RestartStrategies; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.api.functions.source.SourceFunction; /** * Desc: 测试重启代码出现异常,导致触发重启策略 * Created by zhisheng on 2019-04-19 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class AEMain { public static void main(String[] args) throws Exception { //创建流运行环境 StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.getConfig().setGlobalJobParameters(ParameterTool.fromArgs(args)); //每隔 5s 重启一次,尝试三次如果 Job 还没有起来则停止 env.setRestartStrategy(RestartStrategies.fixedDelayRestart(3, 5000)); env.addSource(new SourceFunction() { @Override public void run(SourceContext sourceContext) throws Exception { while (true) { sourceContext.collect(System.currentTimeMillis()); } } @Override public void cancel() { } }) .map((MapFunction) aLong -> aLong / 0) .print(); env.execute("zhisheng RestartStrategy example"); } } ================================================ FILE: flink-learning-examples/src/main/java/com/zhisheng/examples/streaming/restartStrategy/DefaultRestartStrategyMain.java ================================================ package com.zhisheng.examples.streaming.restartStrategy; import org.apache.flink.api.common.functions.MapFunction; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.api.functions.source.SourceFunction; /** * Desc: NullPointerException application,default RestartStrategy Test * Created by zhisheng on 2019/10/5 下午11:22 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class DefaultRestartStrategyMain { public static void main(String[] args) throws Exception { StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.getConfig().setGlobalJobParameters(ParameterTool.fromArgs(args)); env.setParallelism(1); env.addSource(new SourceFunction() { @Override public void run(SourceContext sourceContext) throws Exception { while (true) { sourceContext.collect(null); } } @Override public void cancel() { } }) .map((MapFunction) aLong -> aLong / 1) .print(); env.execute("zhisheng default RestartStrategy example"); } } ================================================ FILE: flink-learning-examples/src/main/java/com/zhisheng/examples/streaming/restartStrategy/EnableCheckpointMain.java ================================================ package com.zhisheng.examples.streaming.restartStrategy; import org.apache.flink.api.common.functions.MapFunction; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.runtime.state.StateBackend; import org.apache.flink.runtime.state.memory.MemoryStateBackend; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.api.functions.source.SourceFunction; /** * Desc: NullPointerException application,default RestartStrategy enable checkpoint Test * Created by zhisheng on 2019/10/5 下午11:22 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class EnableCheckpointMain { public static void main(String[] args) throws Exception { //创建流运行环境 StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.getConfig().setGlobalJobParameters(ParameterTool.fromArgs(args)); env.setParallelism(1); env.addSource(new SourceFunction() { @Override public void run(SourceContext sourceContext) throws Exception { while (true) { sourceContext.collect(null); } } @Override public void cancel() { } }) .map((MapFunction) aLong -> aLong / 1) .print(); //开启 checkpoint StateBackend stateBackend = new MemoryStateBackend(5 * 1024 * 1024 * 100); env.enableCheckpointing(10000); env.setStateBackend(stateBackend); env.execute("zhisheng default RestartStrategy enable checkpoint example"); } } ================================================ FILE: flink-learning-examples/src/main/java/com/zhisheng/examples/streaming/restartStrategy/FailureRateRestartStrategyMain.java ================================================ package com.zhisheng.examples.streaming.restartStrategy; import org.apache.flink.api.common.functions.MapFunction; import org.apache.flink.api.common.restartstrategy.RestartStrategies; import org.apache.flink.api.common.time.Time; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.api.functions.source.SourceFunction; /** * Desc: NullPointerException application,failureRate Restart Strategy Test * Created by zhisheng on 2019/10/5 下午11:22 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class FailureRateRestartStrategyMain { public static void main(String[] args) throws Exception { StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.getConfig().setGlobalJobParameters(ParameterTool.fromArgs(args)); //每隔 10s 重启一次,如果两分钟内重启过三次则停止 Job env.setRestartStrategy(RestartStrategies.failureRateRestart(3, Time.minutes(2), Time.seconds(10))); env.addSource(new SourceFunction() { @Override public void run(SourceContext sourceContext) throws Exception { while (true) { sourceContext.collect(null); } } @Override public void cancel() { } }) .map((MapFunction) aLong -> aLong / 1) .print(); env.execute("zhisheng failureRate Restart Strategy example"); } } ================================================ FILE: flink-learning-examples/src/main/java/com/zhisheng/examples/streaming/restartStrategy/FixedDelayRestartStrategyMain.java ================================================ package com.zhisheng.examples.streaming.restartStrategy; import org.apache.flink.api.common.functions.MapFunction; import org.apache.flink.api.common.restartstrategy.RestartStrategies; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.api.functions.source.SourceFunction; /** * Desc: NullPointerException application,fixedDelay Restart Strategy Test * Created by zhisheng on 2019/10/5 下午11:22 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class FixedDelayRestartStrategyMain { public static void main(String[] args) throws Exception { //创建流运行环境 StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.getConfig().setGlobalJobParameters(ParameterTool.fromArgs(args)); //每隔 5s 重启一次,尝试三次如果 Job 还没有起来则停止 env.setRestartStrategy(RestartStrategies.fixedDelayRestart(3, 5000)); env.addSource(new SourceFunction() { @Override public void run(SourceContext sourceContext) throws Exception { while (true) { sourceContext.collect(null); } } @Override public void cancel() { } }) .map((MapFunction) aLong -> aLong / 1) .print(); env.execute("zhisheng fixedDelay Restart Strategy example"); } } ================================================ FILE: flink-learning-examples/src/main/java/com/zhisheng/examples/streaming/restartStrategy/NoRestartStrategyMain.java ================================================ package com.zhisheng.examples.streaming.restartStrategy; import org.apache.flink.api.common.functions.MapFunction; import org.apache.flink.api.common.restartstrategy.RestartStrategies; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.api.functions.source.SourceFunction; /** * Desc: NullPointerException application,no Restart Strategy Test * Created by zhisheng on 2019/10/5 下午11:22 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class NoRestartStrategyMain { public static void main(String[] args) throws Exception { StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.getConfig().setGlobalJobParameters(ParameterTool.fromArgs(args)); env.setRestartStrategy(RestartStrategies.noRestart()); env.addSource(new SourceFunction() { @Override public void run(SourceContext sourceContext) throws Exception { while (true) { sourceContext.collect(null); } } @Override public void cancel() { } }) .map((MapFunction) aLong -> aLong / 1) .print(); env.execute("zhisheng no Restart Strategy example"); } } ================================================ FILE: flink-learning-examples/src/main/java/com/zhisheng/examples/streaming/sideoutput/FilterEvent.java ================================================ package com.zhisheng.examples.streaming.sideoutput; import com.zhisheng.common.model.MetricEvent; import com.zhisheng.common.utils.KafkaConfigUtil; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.streaming.api.datastream.DataStreamSource; import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; /** * Desc: 使用 filter 过滤数据 * Created by zhisheng on 2019/10/1 下午4:54 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class FilterEvent { public static void main(String[] args) throws Exception { final ParameterTool params = ParameterTool.fromArgs(args); final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.getConfig().setGlobalJobParameters(params); DataStreamSource data = KafkaConfigUtil.buildSource(env); //从 Kafka 获取到所有的数据流 SingleOutputStreamOperator machineData = data.filter(m -> "machine".equals(m.getTags().get("type"))); //过滤出机器的数据 SingleOutputStreamOperator dockerData = data.filter(m -> "docker".equals(m.getTags().get("type"))); //过滤出容器的数据 SingleOutputStreamOperator applicationData = data.filter(m -> "application".equals(m.getTags().get("type"))); //过滤出应用的数据 SingleOutputStreamOperator middlewareData = data.filter(m -> "middleware".equals(m.getTags().get("type"))); //过滤出中间件的数据 } } ================================================ FILE: flink-learning-examples/src/main/java/com/zhisheng/examples/streaming/sideoutput/Main.java ================================================ package com.zhisheng.examples.streaming.sideoutput; import org.apache.flink.api.java.functions.KeySelector; import org.apache.flink.api.java.tuple.Tuple2; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.streaming.api.TimeCharacteristic; import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.api.functions.ProcessFunction; import org.apache.flink.util.Collector; import org.apache.flink.util.OutputTag; /** * Desc: SideOutput * Created by zhisheng on 2019-06-02 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class Main { private static final OutputTag overFiveTag = new OutputTag("overFive") { }; private static final OutputTag equalFiveTag = new OutputTag("equalFive") { }; public static void main(String[] args) throws Exception { final ParameterTool params = ParameterTool.fromArgs(args); final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.setStreamTimeCharacteristic(TimeCharacteristic.IngestionTime); env.getConfig().setGlobalJobParameters(params); SingleOutputStreamOperator> tokenizer = env.fromElements(WORDS) .keyBy(new KeySelector() { private static final long serialVersionUID = 1L; @Override public Integer getKey(String value) throws Exception { return 0; } }) .process(new Tokenizer()); // tokenizer.getSideOutput(overFiveTag).print(); //将字符串长度大于 5 的打印出来 // tokenizer.getSideOutput(equalFiveTag).print(); //将字符串长度等于 5 的打印出来 // tokenizer.print(); //这个打印出来的是字符串长度小于 5 的 tokenizer.keyBy(0) .sum(1) .print(); //做 word count 后打印出来 env.execute("Streaming WordCount SideOutput"); } public static final class Tokenizer extends ProcessFunction> { private static final long serialVersionUID = 1L; @Override public void processElement(String value, Context ctx, Collector> out) throws Exception { String[] tokens = value.toLowerCase().split("\\W+"); for (String token : tokens) { if (token.length() > 5) { ctx.output(overFiveTag, token); //将字符串长度大于 5 的 word 放到 overFiveTag 中去 } else if (token.length() == 5) { ctx.output(equalFiveTag, token); //将字符串长度等于 5 的 word 放到 equalFiveTag 中去 } else if (token.length() < 5) { out.collect(new Tuple2<>(token, 1)); } } } } public static final String[] WORDS = new String[]{ "To be, or not to be,--that is the question:--", "Whether 'tis nobler in the mind to suffer", "The slings and arrows of outrageous fortune", "Or to take arms against a sea of troubles,", "And by opposing end them?--To die,--to sleep,--", "No more; and by a sleep to say we end", "The heartache, and the thousand natural shocks", "That flesh is heir to,--'tis a consummation", "Devoutly to be wish'd. To die,--to sleep;--", "To sleep! perchance to dream:--ay, there's the rub;", "For in that sleep of death what dreams may come,", "When we have shuffled off this mortal coil,", "Must give us pause: there's the respect", "That makes calamity of so long life;", "For who would bear the whips and scorns of time,", "The oppressor's wrong, the proud man's contumely,", "The pangs of despis'd love, the law's delay,", "The insolence of office, and the spurns", "That patient merit of the unworthy takes,", "When he himself might his quietus make", "With a bare bodkin? who would these fardels bear,", "To grunt and sweat under a weary life,", "But that the dread of something after death,--", "The undiscover'd country, from whose bourn", "No traveller returns,--puzzles the will,", "And makes us rather bear those ills we have", "Than fly to others that we know not of?", "Thus conscience does make cowards of us all;", "And thus the native hue of resolution", "Is sicklied o'er with the pale cast of thought;", "And enterprises of great pith and moment,", "With this regard, their currents turn awry,", "And lose the name of action.--Soft you now!", "The fair Ophelia!--Nymph, in thy orisons", "Be all my sins remember'd." }; } ================================================ FILE: flink-learning-examples/src/main/java/com/zhisheng/examples/streaming/sideoutput/SideOutputEvent.java ================================================ package com.zhisheng.examples.streaming.sideoutput; import com.zhisheng.common.model.MetricEvent; import com.zhisheng.common.utils.KafkaConfigUtil; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.streaming.api.datastream.DataStream; import org.apache.flink.streaming.api.datastream.DataStreamSource; import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.api.functions.ProcessFunction; import org.apache.flink.util.Collector; import org.apache.flink.util.OutputTag; /** * Desc: 使用 side output 过滤数据 * Created by zhisheng on 2019/10/1 下午5:15 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class SideOutputEvent { private static final OutputTag machineTag = new OutputTag("machine") { }; private static final OutputTag dockerTag = new OutputTag("docker") { }; private static final OutputTag applicationTag = new OutputTag("application") { }; private static final OutputTag middlewareTag = new OutputTag("middleware") { }; public static void main(String[] args) throws Exception { final ParameterTool params = ParameterTool.fromArgs(args); final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.getConfig().setGlobalJobParameters(params); DataStreamSource data = KafkaConfigUtil.buildSource(env); //从 Kafka 获取到所有的数据流 SingleOutputStreamOperator sideOutputData = data.process(new ProcessFunction() { @Override public void processElement(MetricEvent metricEvent, Context context, Collector collector) throws Exception { String type = metricEvent.getTags().get("type"); switch (type) { case "machine": context.output(machineTag, metricEvent); case "docker": context.output(dockerTag, metricEvent); case "application": context.output(applicationTag, metricEvent); case "middleware": context.output(middlewareTag, metricEvent); default: collector.collect(metricEvent); } } }); DataStream machine = sideOutputData.getSideOutput(machineTag); DataStream docker = sideOutputData.getSideOutput(dockerTag); DataStream application = sideOutputData.getSideOutput(applicationTag); DataStream middleware = sideOutputData.getSideOutput(middlewareTag); } } ================================================ FILE: flink-learning-examples/src/main/java/com/zhisheng/examples/streaming/socket/LambdaMain.java ================================================ package com.zhisheng.examples.streaming.socket; import org.apache.flink.api.java.tuple.Tuple2; import org.apache.flink.streaming.api.datastream.DataStreamSource; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; /** * Desc: socket * Created by zhisheng on 2019-04-26 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class LambdaMain { public static void main(String[] args) throws Exception { //参数检查 if (args.length != 2) { System.err.println("USAGE:\nSocketTextStreamWordCount "); return; } String hostname = args[0]; Integer port = Integer.parseInt(args[1]); // set up the streaming execution environment final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); //获取数据 DataStreamSource stream = env.socketTextStream(hostname, port); //计数 stream.flatMap((s, collector) -> { for (String token : s.toLowerCase().split("\\W+")) { if (token.length() > 0) { collector.collect(new Tuple2(token, 1)); } } }) // .returns((TypeInformation) TupleTypeInfo.getBasicTupleTypeInfo(String.class, Integer.class)) .keyBy(0) .sum(1) .print(); env.execute("Java WordCount from SocketTextStream Example"); } } ================================================ FILE: flink-learning-examples/src/main/java/com/zhisheng/examples/streaming/socket/Main.java ================================================ package com.zhisheng.examples.streaming.socket; import org.apache.flink.api.common.functions.FlatMapFunction; import org.apache.flink.api.java.tuple.Tuple2; import org.apache.flink.streaming.api.datastream.DataStreamSource; import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.util.Collector; /** * Desc: socket * Created by zhisheng on 2019-04-26 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class Main { public static void main(String[] args) throws Exception { //参数检查 if (args.length != 2) { System.err.println("USAGE:\nSocketTextStreamWordCount "); return; } String hostname = args[0]; Integer port = Integer.parseInt(args[1]); // set up the streaming execution environment final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); //获取数据 DataStreamSource stream = env.socketTextStream(hostname, port); //计数 SingleOutputStreamOperator> sum = stream.flatMap(new LineSplitter()) .keyBy(0) .sum(1); sum.print(); env.execute("Java WordCount from SocketTextStream Example"); } public static final class LineSplitter implements FlatMapFunction> { @Override public void flatMap(String s, Collector> collector) { String[] tokens = s.toLowerCase().split("\\W+"); for (String token: tokens) { if (token.length() > 0) { collector.collect(new Tuple2(token, 1)); } } } } } ================================================ FILE: flink-learning-examples/src/main/java/com/zhisheng/examples/streaming/state/StateMain.java ================================================ package com.zhisheng.examples.streaming.state; import org.apache.flink.api.common.functions.RichMapFunction; import org.apache.flink.api.common.state.ValueState; import org.apache.flink.api.common.state.ValueStateDescriptor; import org.apache.flink.api.common.typeinfo.TypeHint; import org.apache.flink.api.common.typeinfo.TypeInformation; import org.apache.flink.api.java.tuple.Tuple2; import org.apache.flink.configuration.Configuration; import org.apache.flink.runtime.state.memory.MemoryStateBackend; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.api.functions.source.RichParallelSourceFunction; /** * Desc: * Created by zhisheng on 2020-03-07 20:26 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class StateMain { public static void main(String[] args) throws Exception { final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.enableCheckpointing(10000); env.setStateBackend(new MemoryStateBackend()); env.addSource(new RichParallelSourceFunction>() { @Override public void run(SourceContext> sourceContext) throws Exception { while (true) { sourceContext.collect(new Tuple2<>(String.valueOf(System.currentTimeMillis()), System.currentTimeMillis())); Thread.sleep(10); } } @Override public void cancel() { } }).keyBy(0) .map(new RichMapFunction, Tuple2>() { private ValueState state; @Override public void open(Configuration parameters) throws Exception { super.open(parameters); state = getRuntimeContext().getState( new ValueStateDescriptor<>("uvState", TypeInformation.of(new TypeHint() { }))); } @Override public Tuple2 map(Tuple2 tuple2) throws Exception { state.update(tuple2.f1); return tuple2; } }).print(); env.execute(); } } ================================================ FILE: flink-learning-examples/src/main/java/com/zhisheng/examples/streaming/watermark/Main.java ================================================ package com.zhisheng.examples.streaming.watermark; import org.apache.flink.api.common.functions.MapFunction; import org.apache.flink.streaming.api.TimeCharacteristic; import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; /** * Desc: Punctuated Watermark * Created by zhisheng on 2019-07-07 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class Main { public static void main(String[] args) throws Exception { final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime); //并行度设置为 1 env.setParallelism(1); // env.setParallelism(4); SingleOutputStreamOperator data = env.socketTextStream("localhost", 9001) .map(new MapFunction() { @Override public Word map(String value) throws Exception { String[] split = value.split(","); return new Word(split[0], Integer.valueOf(split[1]), Long.valueOf(split[2])); } }); //Punctuated Watermark data.assignTimestampsAndWatermarks(new WordPunctuatedWatermark()); data.print(); env.execute("watermark demo"); } } ================================================ FILE: flink-learning-examples/src/main/java/com/zhisheng/examples/streaming/watermark/Main1.java ================================================ package com.zhisheng.examples.streaming.watermark; import org.apache.flink.api.common.functions.MapFunction; import org.apache.flink.streaming.api.TimeCharacteristic; import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; /** * Desc: Periodic Watermark * Created by zhisheng on 2019-07-07 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class Main1 { public static void main(String[] args) throws Exception { final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime); env.getConfig().setAutoWatermarkInterval(5000); //并行度设置为 1 env.setParallelism(1); // env.setParallelism(4); SingleOutputStreamOperator data = env.socketTextStream("localhost", 9001) .map(new MapFunction() { @Override public Word map(String value) throws Exception { String[] split = value.split(","); return new Word(split[0], Integer.valueOf(split[1]), Long.valueOf(split[2])); } }); //Periodic Watermark data.assignTimestampsAndWatermarks(new WordPeriodicWatermark()); data.print(); env.execute("watermark demo"); } } ================================================ FILE: flink-learning-examples/src/main/java/com/zhisheng/examples/streaming/watermark/Main2.java ================================================ package com.zhisheng.examples.streaming.watermark; import org.apache.flink.api.common.functions.MapFunction; import org.apache.flink.streaming.api.TimeCharacteristic; import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.api.functions.timestamps.BoundedOutOfOrdernessTimestampExtractor; import org.apache.flink.streaming.api.windowing.time.Time; /** * Desc: BoundedOutOfOrdernessTimestampExtractor * Created by zhisheng on 2019-07-07 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class Main2 { public static void main(String[] args) throws Exception { final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime); //并行度设置为 1 env.setParallelism(1); // env.setParallelism(4); SingleOutputStreamOperator data = env.socketTextStream("localhost", 9001) .map(new MapFunction() { @Override public Word map(String value) throws Exception { String[] split = value.split(","); return new Word(split[0], Integer.valueOf(split[1]), Long.valueOf(split[2])); } }); //BoundedOutOfOrdernessTimestampExtractor data.assignTimestampsAndWatermarks(new BoundedOutOfOrdernessTimestampExtractor(Time.seconds(10)) { @Override public long extractTimestamp(Word element) { return element.getTimestamp(); } }); data.print(); env.execute("watermark demo"); } } ================================================ FILE: flink-learning-examples/src/main/java/com/zhisheng/examples/streaming/watermark/Main3.java ================================================ package com.zhisheng.examples.streaming.watermark; import org.apache.flink.api.common.functions.MapFunction; import org.apache.flink.streaming.api.TimeCharacteristic; import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.api.windowing.time.Time; /** * Desc: allowedLateness * Created by zhisheng on 2019-07-07 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class Main3 { public static void main(String[] args) throws Exception { final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime); //并行度设置为 1 env.setParallelism(1); // env.setParallelism(4); SingleOutputStreamOperator data = env.socketTextStream("localhost", 9001) .map(new MapFunction() { @Override public Word map(String value) throws Exception { String[] split = value.split(","); return new Word(split[0], Integer.valueOf(split[1]), Long.valueOf(split[2])); } }).assignTimestampsAndWatermarks(new WordPeriodicWatermark()); data.keyBy(0) .timeWindow(Time.seconds(10)) .allowedLateness(Time.milliseconds(2)) .sum(1) .print(); env.execute("watermark demo"); } } ================================================ FILE: flink-learning-examples/src/main/java/com/zhisheng/examples/streaming/watermark/Main4.java ================================================ package com.zhisheng.examples.streaming.watermark; import org.apache.flink.api.common.functions.MapFunction; import org.apache.flink.streaming.api.TimeCharacteristic; import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.api.windowing.time.Time; import org.apache.flink.util.OutputTag; /** * Desc: sideOutputLateData * Created by zhisheng on 2019-07-07 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class Main4 { public static void main(String[] args) throws Exception { final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime); //并行度设置为 1 env.setParallelism(1); // env.setParallelism(4); OutputTag lateDataTag = new OutputTag("late") { }; SingleOutputStreamOperator data = env.socketTextStream("localhost", 9001) .map(new MapFunction() { @Override public Word map(String value) throws Exception { String[] split = value.split(","); return new Word(split[0], Integer.valueOf(split[1]), Long.valueOf(split[2])); } }).assignTimestampsAndWatermarks(new WordPeriodicWatermark()); SingleOutputStreamOperator sum = data.keyBy(0) .timeWindow(Time.seconds(10)) // .allowedLateness(Time.milliseconds(2)) .sideOutputLateData(lateDataTag) .sum(1); sum.print(); sum.getSideOutput(lateDataTag) .print(); env.execute("watermark demo"); } } ================================================ FILE: flink-learning-examples/src/main/java/com/zhisheng/examples/streaming/watermark/Word.java ================================================ package com.zhisheng.examples.streaming.watermark; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; /** * Desc: * Created by zhisheng on 2019-07-07 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ @Data @AllArgsConstructor @NoArgsConstructor public class Word { private String word; private int count; private long timestamp; } ================================================ FILE: flink-learning-examples/src/main/java/com/zhisheng/examples/streaming/watermark/WordPeriodicWatermark.java ================================================ package com.zhisheng.examples.streaming.watermark; import com.zhisheng.common.utils.DateUtil; import lombok.extern.slf4j.Slf4j; import org.apache.flink.streaming.api.functions.AssignerWithPeriodicWatermarks; import org.apache.flink.streaming.api.watermark.Watermark; import javax.annotation.Nullable; import static com.zhisheng.common.utils.DateUtil.YYYY_MM_DD_HH_MM_SS; /** * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ @Slf4j public class WordPeriodicWatermark implements AssignerWithPeriodicWatermarks { private long currentTimestamp = Long.MIN_VALUE; @Override public long extractTimestamp(Word word, long previousElementTimestamp) { long timestamp = word.getTimestamp(); currentTimestamp = Math.max(timestamp, currentTimestamp); log.info("event timestamp = {}, {}, CurrentWatermark = {}, {}", word.getTimestamp(), DateUtil.format(word.getTimestamp(), YYYY_MM_DD_HH_MM_SS), getCurrentWatermark().getTimestamp(), DateUtil.format(getCurrentWatermark().getTimestamp(), YYYY_MM_DD_HH_MM_SS)); return word.getTimestamp(); } @Nullable @Override public Watermark getCurrentWatermark() { long maxTimeLag = 5000; return new Watermark(currentTimestamp == Long.MIN_VALUE ? Long.MIN_VALUE : currentTimestamp - maxTimeLag); } } ================================================ FILE: flink-learning-examples/src/main/java/com/zhisheng/examples/streaming/watermark/WordPunctuatedWatermark.java ================================================ package com.zhisheng.examples.streaming.watermark; import org.apache.flink.streaming.api.functions.AssignerWithPunctuatedWatermarks; import org.apache.flink.streaming.api.watermark.Watermark; import javax.annotation.Nullable; /** * Desc: Punctuated Watermark * Created by zhisheng on 2019-07-09 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class WordPunctuatedWatermark implements AssignerWithPunctuatedWatermarks { @Nullable @Override public Watermark checkAndGetNextWatermark(Word lastElement, long extractedTimestamp) { return extractedTimestamp % 3 == 0 ? new Watermark(extractedTimestamp) : null; // return new Watermark(extractedTimestamp); } @Override public long extractTimestamp(Word element, long previousElementTimestamp) { return element.getTimestamp(); } } ================================================ FILE: flink-learning-examples/src/main/java/com/zhisheng/examples/streaming/wordcount/Main.java ================================================ package com.zhisheng.examples.streaming.wordcount; import org.apache.flink.api.common.functions.FlatMapFunction; import org.apache.flink.api.common.functions.ReduceFunction; import org.apache.flink.api.java.tuple.Tuple2; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.util.Collector; /** * Streaming */ public class Main { public static void main(String[] args) throws Exception { //创建流运行环境 StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.getConfig().setGlobalJobParameters(ParameterTool.fromArgs(args)); env.fromElements(WORDS) .flatMap(new FlatMapFunction>() { @Override public void flatMap(String value, Collector> out) throws Exception { String[] splits = value.toLowerCase().split("\\W+"); for (String split : splits) { if (split.length() > 0) { out.collect(new Tuple2<>(split, 1)); } } } }) .keyBy(0) .reduce(new ReduceFunction>() { @Override public Tuple2 reduce(Tuple2 value1, Tuple2 value2) throws Exception { return new Tuple2<>(value1.f0, value1.f1 + value2.f1); } }) .print(); //Streaming 程序必须加这个才能启动程序,否则不会有结果 env.execute("zhisheng —— word count streaming demo"); } private static final String[] WORDS = new String[]{ "To be, or not to be,--that is the question:--", "Whether 'tis nobler in the mind to suffer", "The slings and arrows of outrageous fortune", "Or to take arms against a sea of troubles,", "And by opposing end them?--To die,--to sleep,--", "No more; and by a sleep to say we end", "The heartache, and the thousand natural shocks", "That flesh is heir to,--'tis a consummation", "Devoutly to be wish'd. To die,--to sleep;--", "To sleep! perchance to dream:--ay, there's the rub;", "For in that sleep of death what dreams may come,", "When we have shuffled off this mortal coil,", "Must give us pause: there's the respect", "That makes calamity of so long life;", "For who would bear the whips and scorns of time,", "The oppressor's wrong, the proud man's contumely,", "The pangs of despis'd love, the law's delay,", "The insolence of office, and the spurns", "That patient merit of the unworthy takes,", "When he himself might his quietus make", "With a bare bodkin? who would these fardels bear,", "To grunt and sweat under a weary life,", "But that the dread of something after death,--", "The undiscover'd country, from whose bourn", "No traveller returns,--puzzles the will,", "And makes us rather bear those ills we have", "Than fly to others that we know not of?", "Thus conscience does make cowards of us all;", "And thus the native hue of resolution", "Is sicklied o'er with the pale cast of thought;", "And enterprises of great pith and moment,", "With this regard, their currents turn awry,", "And lose the name of action.--Soft you now!", "The fair Ophelia!--Nymph, in thy orisons", "Be all my sins remember'd." }; } ================================================ FILE: flink-learning-examples/src/main/java/com/zhisheng/examples/util/MySQLUtil.java ================================================ package com.zhisheng.examples.util; import com.google.common.base.Throwables; import lombok.extern.slf4j.Slf4j; import java.sql.Connection; import java.sql.DriverManager; @Slf4j public class MySQLUtil { public static Connection getConnection(String driver, String url, String user, String password) { Connection con = null; try { Class.forName(driver); con = DriverManager.getConnection(url, user, password); } catch (Exception e) { log.error("-----------mysql get connection has exception , msg = " + Throwables.getStackTraceAsString(e)); } return con; } } ================================================ FILE: flink-learning-examples/src/main/java/com/zhisheng/examples/util/ThrottledIterator.java ================================================ package com.zhisheng.examples.util; import java.io.Serializable; import java.util.Iterator; import static java.util.Objects.requireNonNull; /** * A variant of the collection source (emits a sequence of elements as a stream) * that supports throttling the emission rate. */ public class ThrottledIterator implements Iterator, Serializable { private static final long serialVersionUID = 1L; @SuppressWarnings("NonSerializableFieldInSerializableClass") private final Iterator source; private final long sleepBatchSize; private final long sleepBatchTime; private long lastBatchCheckTime; private long num; public ThrottledIterator(Iterator source, long elementsPerSecond) { this.source = requireNonNull(source); if (!(source instanceof Serializable)) { throw new IllegalArgumentException("source must be java.io.Serializable"); } if (elementsPerSecond >= 100) { // how many elements would we emit per 50ms this.sleepBatchSize = elementsPerSecond / 20; this.sleepBatchTime = 50; } else if (elementsPerSecond >= 1) { // how long does element take this.sleepBatchSize = 1; this.sleepBatchTime = 1000 / elementsPerSecond; } else { throw new IllegalArgumentException("'elements per second' must be positive and not zero"); } } @Override public boolean hasNext() { return source.hasNext(); } @Override public T next() { // delay if necessary if (lastBatchCheckTime > 0) { if (++num >= sleepBatchSize) { num = 0; final long now = System.currentTimeMillis(); final long elapsed = now - lastBatchCheckTime; if (elapsed < sleepBatchTime) { try { Thread.sleep(sleepBatchTime - elapsed); } catch (InterruptedException e) { // restore interrupt flag and proceed Thread.currentThread().interrupt(); } } lastBatchCheckTime = now; } } else { lastBatchCheckTime = System.currentTimeMillis(); } return source.next(); } @Override public void remove() { throw new UnsupportedOperationException(); } } ================================================ FILE: flink-learning-examples/src/main/resources/alarm-notify.sql ================================================ # ************************************************************ # Sequel Pro SQL dump # Version 4541 # # http://www.sequelpro.com/ # https://github.com/sequelpro/sequelpro # # Host: 127.0.0.1 (MySQL 5.7.22) # Database: test # Generation Time: 2019-09-06 06:13:02 +0000 # ************************************************************ /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; /*!40101 SET NAMES utf8 */; /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; # Dump of table alarm_notify # ------------------------------------------------------------ DROP TABLE IF EXISTS `alarm_notify`; CREATE TABLE `alarm_notify` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `type` varchar(12) COLLATE utf8_bin DEFAULT NULL, `type_id` varchar(25) COLLATE utf8_bin DEFAULT NULL, `target_id` varchar(255) COLLATE utf8_bin DEFAULT NULL, `status` int(11) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin; LOCK TABLES `alarm_notify` WRITE; /*!40000 ALTER TABLE `alarm_notify` DISABLE KEYS */; INSERT INTO `alarm_notify` (`id`, `type`, `type_id`, `target_id`, `status`) VALUES (1,X'6164646F6E',X'7A68697368656E672D646576',X'68747470733A2F2F6F6170692E64696E6774616C6B2E636F6D2F726F626F742F73656E643F6163636573735F746F6B656E3D353661393261313634613063376636323535666661646565343233613061303232343138653137336637336666383133323032323164616366303663653839',0), (2,X'636F6D706F6E656E74',X'7A68697368656E672D646576',X'68747470733A2F2F6F6170692E64696E6774616C6B2E636F6D2F726F626F742F73656E643F6163636573735F746F6B656E3D353661393261313634613063376636323535666661646565343233613061303232343138653133663637336666383133323032323164616366303663653839',0), (3,X'6D616368696E65',X'7A68697368656E672D646576',X'68747470733A2F2F6F6170692E64696E6774616C6B2E636F6D2F726F626F742F73656E643F6163636573735F746F6B656E3D353661393261313634613063376636323535666661646565343233613061303232343138653137363637336666383133323032323164616366303663653839',0); /*!40000 ALTER TABLE `alarm_notify` ENABLE KEYS */; UNLOCK TABLES; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; ================================================ FILE: flink-learning-examples/src/main/resources/application.properties ================================================ kafka.brokers=localhost:9092 kafka.group.id=zhisheng kafka.zookeeper.connect=localhost:2181 metrics.topic=zhisheng_metrics stream.parallelism=5 stream.checkpoint.interval=1000 stream.checkpoint.enable=false #stream.checkpoint.type=memory #stream.checkpoint.type=fs stream.checkpoint.type=rocksdb #stream.checkpoint.dir=file:///usr/local/state/ stream.checkpoint.dir=/Users/zhisheng/Desktop #mysql mysql.host=localhost mysql.port=3306 mysql.database=test mysql.username=root mysql.password=root123456 ================================================ FILE: flink-learning-examples/src/main/resources/logback.xml ================================================ %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n ================================================ FILE: flink-learning-examples/src/test/java/Test1.java ================================================ public class Test1 { public static void main(String[] args) { Path("file:///usr/local/blink-1.5.1/README.txt.bak"); } public static void Path(String pathString) { // We can't use 'new URI(String)' directly, since it assumes things are // escaped, which we don't require of Paths. // parse uri components String scheme = null; String authority = null; int start = 0; // parse uri scheme, if any final int colon = pathString.indexOf(':'); final int slash = pathString.indexOf('/'); if ((colon != -1) && ((slash == -1) || (colon < slash))) { // has a // scheme scheme = pathString.substring(0, colon); start = colon + 1; } // parse uri authority, if any if (pathString.startsWith("//", start) && (pathString.length() - start > 2)) { // has authority final int nextSlash = pathString.indexOf('/', start + 2); final int authEnd = nextSlash > 0 ? nextSlash : pathString.length(); authority = pathString.substring(start + 2, authEnd); start = authEnd; } // uri path is the rest of the string -- query & fragment not supported final String path = pathString.substring(start, pathString.length()); System.out.println(path); } } ================================================ FILE: flink-learning-extends/FlinkLogKafkaAppender/KafkaAppenderCommon/pom.xml ================================================ FlinkLogKafkaAppender com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 KafkaAppenderCommon KafkaAppenderCommon ================================================ FILE: flink-learning-extends/FlinkLogKafkaAppender/KafkaAppenderCommon/src/main/java/com/zhisheng/flink/model/LogEvent.java ================================================ package com.zhisheng.flink.model; import lombok.Data; import java.util.HashMap; import java.util.Map; @Data public class LogEvent { private String source; // default is flink, maybe others will use this kafka appender in future private String id; // log id, default it is UUID private Long timestamp; private String content; // log message private Map tags = new HashMap<>(); // tags of the log, eg: host_name, application_id, job_name etc } ================================================ FILE: flink-learning-extends/FlinkLogKafkaAppender/KafkaAppenderCommon/src/main/java/com/zhisheng/flink/util/ExceptionUtil.java ================================================ package com.zhisheng.flink.util; import java.io.ByteArrayOutputStream; import java.io.OutputStream; import java.io.PrintStream; import java.util.HashMap; import java.util.Map; public class ExceptionUtil { private static final char TAB = ' '; private static final char CR = '\r'; private static final char LF = '\n'; private static final String SPACE = " "; private static final String EMPTY = ""; /** * 堆栈转为单行完整字符串 * * @param throwable 异常对象 * @param limit 限制最大长度 * @return 堆栈转为的字符串 */ public static String stacktraceToOneLineString(Throwable throwable, int limit) { Map replaceCharToStrMap = new HashMap<>(); replaceCharToStrMap.put(CR, SPACE); replaceCharToStrMap.put(LF, SPACE); replaceCharToStrMap.put(TAB, SPACE); return stacktraceToString(throwable, limit, replaceCharToStrMap); } public static String stacktraceToString(Throwable throwable) { final OutputStream baos = new ByteArrayOutputStream(); throwable.printStackTrace(new PrintStream(baos)); return baos.toString(); } /** * 堆栈转为完整字符串 * * @param throwable 异常对象 * @param limit 限制最大长度 * @param replaceCharToStrMap 替换字符为指定字符串 * @return 堆栈转为的字符串 */ private static String stacktraceToString(Throwable throwable, int limit, Map replaceCharToStrMap) { final OutputStream baos = new ByteArrayOutputStream(); throwable.printStackTrace(new PrintStream(baos)); String exceptionStr = baos.toString(); int length = exceptionStr.length(); if (limit > 0 && limit < length) { length = limit; } if (!replaceCharToStrMap.isEmpty()) { final StringBuilder sb = new StringBuilder(); char c; String value; for (int i = 0; i < length; i++) { c = exceptionStr.charAt(i); value = replaceCharToStrMap.get(c); if (null != value) { sb.append(value); } else { sb.append(c); } } return sb.toString(); } else { return sub(exceptionStr, 0, limit); } } /** * 改进JDK subString
* index从0开始计算,最后一个字符为-1
* 如果from和to位置一样,返回 ""
* 如果from或to为负数,则按照length从后向前数位置,如果绝对值大于字符串长度,则from归到0,to归到length
* 如果经过修正的index中from大于to,则互换from和to example:
* abcdefgh 2 3 =》 c
* abcdefgh 2 -3 =》 cde
* * @param str String * @param fromIndex 开始的index(包括) * @param toIndex 结束的index(不包括) * @return 字串 */ private static String sub(CharSequence str, int fromIndex, int toIndex) { if (isEmpty(str)) { return str(str); } int len = str.length(); if (fromIndex < 0) { fromIndex = len + fromIndex; if (fromIndex < 0) { fromIndex = 0; } } else if (fromIndex > len) { fromIndex = len; } if (toIndex < 0) { toIndex = len + toIndex; if (toIndex < 0) { toIndex = len; } } else if (toIndex > len) { toIndex = len; } if (toIndex < fromIndex) { int tmp = fromIndex; fromIndex = toIndex; toIndex = tmp; } if (fromIndex == toIndex) { return EMPTY; } return str.toString().substring(fromIndex, toIndex); } /** * {@link CharSequence} 转为字符串,null安全 * * @param cs {@link CharSequence} * @return 字符串 */ private static String str(CharSequence cs) { return null == cs ? null : cs.toString(); } /** * 字符串是否为空,空的定义如下:
* 1、为null
* 2、为""
* * @param str 被检测的字符串 * @return 是否为空 */ private static boolean isEmpty(CharSequence str) { return str == null || str.length() == 0; } } ================================================ FILE: flink-learning-extends/FlinkLogKafkaAppender/KafkaAppenderCommon/src/main/java/com/zhisheng/flink/util/JacksonUtil.java ================================================ package com.zhisheng.flink.util; import org.apache.flink.shaded.jackson2.com.fasterxml.jackson.core.JsonProcessingException; import org.apache.flink.shaded.jackson2.com.fasterxml.jackson.databind.ObjectMapper; public class JacksonUtil { private final static ObjectMapper mapper = new ObjectMapper(); /** * 将对象转换成普通的 JSON 数据 * * @param value * @return * @throws JsonProcessingException */ public static String toJson(Object value) throws JsonProcessingException { return mapper.writeValueAsString(value); } /** * 将对象转换成结构化的 JSON 数据 * * @param value * @return * @throws JsonProcessingException */ public static String toFormatJson(Object value) throws JsonProcessingException { return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(value); } } ================================================ FILE: flink-learning-extends/FlinkLogKafkaAppender/Log4j2KafkaAppender/pom.xml ================================================ FlinkLogKafkaAppender com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 Log4j2KafkaAppender 2.25.3 19.0 2.15.3 com.zhisheng.flink KafkaAppenderCommon 1.0-SNAPSHOT org.apache.logging.log4j log4j-slf4j-impl ${log4j.version} ${scope} org.apache.logging.log4j log4j-api ${log4j.version} ${scope} org.apache.logging.log4j log4j-core ${log4j.version} ${scope} org.apache.flink flink-shaded-jackson ${jackson.version}-${flink.shaded.version} ${scope} org.apache.maven.plugins maven-shade-plugin 3.1.1 shade-flink package shade org.apache.kafka:* ================================================ FILE: flink-learning-extends/FlinkLogKafkaAppender/Log4j2KafkaAppender/src/main/java/com/zhisheng/log/appender/KafkaLog4j2Appender.java ================================================ package com.zhisheng.log.appender; import com.zhisheng.flink.model.LogEvent; import com.zhisheng.flink.util.ExceptionUtil; import com.zhisheng.flink.util.JacksonUtil; import lombok.extern.slf4j.Slf4j; import org.apache.flink.shaded.jackson2.com.fasterxml.jackson.core.JsonProcessingException; import org.apache.kafka.clients.producer.KafkaProducer; import org.apache.kafka.clients.producer.Producer; import org.apache.kafka.clients.producer.ProducerRecord; import org.apache.kafka.common.config.ConfigException; import org.apache.logging.log4j.core.Filter; import org.apache.logging.log4j.core.Layout; import org.apache.logging.log4j.core.appender.AbstractAppender; import org.apache.logging.log4j.core.config.Property; import org.apache.logging.log4j.core.config.plugins.Plugin; import org.apache.logging.log4j.core.config.plugins.PluginAttribute; import org.apache.logging.log4j.core.config.plugins.PluginElement; import org.apache.logging.log4j.core.config.plugins.PluginFactory; import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required; import java.io.File; import java.io.Serializable; import java.net.InetAddress; import java.util.HashMap; import java.util.Map; import java.util.Properties; import java.util.UUID; @Slf4j @Plugin(name = "KafkaLog4j2Appender", category = "Core", elementType = "appender", printObject = true) public class KafkaLog4j2Appender extends AbstractAppender { private final String source; private final String topic; private final String level; private final Producer producer; private String appId; private String containerId; private String containerType; private final String taskName; private final String taskId; private String nodeIp; protected KafkaLog4j2Appender(String name, Filter filter, Layout layout, boolean ignoreExceptions, Property[] properties, String source, String bootstrapServers, String topic, String level) { super(name, filter, layout, ignoreExceptions, properties); this.source = source; this.topic = topic; this.level = level; Properties envProperties = System.getProperties(); Map envs = System.getenv(); String clusterId = envs.get("CLUSTER_ID"); if (clusterId != null) { //k8s cluster appId = clusterId; containerId = envs.get("HOSTNAME"); if (envs.get("HOSTNAME").contains("taskmanager")) { containerType = "taskmanager"; } else { containerType = "jobmanager"; } //k8s 物理机器 ip if (envs.get("_HOST_IP_ADDRESS") != null) { nodeIp = envs.get("_HOST_IP_ADDRESS"); } } else { //yarn cluster String logFile = envProperties.getProperty("log.file"); String[] values = logFile.split(File.separator); if (values.length >= 3) { appId = values[values.length - 3]; containerId = values[values.length - 2]; String log = values[values.length - 1]; if (log.contains("jobmanager")) { containerType = "jobmanager"; } else if (log.contains("taskmanager")) { containerType = "taskmanager"; } else { containerType = "others"; } } else { log.error("log.file Property ({}) doesn't contains yarn application id or container id", logFile); } } taskName = envProperties.getProperty("taskName", null); taskId = envProperties.getProperty("taskId", null); Properties props = new Properties(); for (Property property : properties) { props.put(property.getName(), property.getValue()); } if (bootstrapServers != null) { props.setProperty("bootstrap.servers", bootstrapServers); } else { throw new ConfigException("The bootstrap servers property must be specified"); } if (this.topic == null) { throw new ConfigException("Topic must be specified by the Kafka log4j appender"); } String clientIdPrefix = taskId != null ? taskId : appId; if (clientIdPrefix != null) { props.setProperty("client.id", clientIdPrefix + "_log"); } if (props.getProperty("acks") == null) { props.setProperty("acks", "0"); } if (props.getProperty("retries") == null) { props.setProperty("retries", "0"); } if (props.getProperty("batch.size") == null) { props.setProperty("batch.size", "16384"); } if (props.getProperty("linger.ms") == null) { props.setProperty("linger.ms", "5"); } if (props.getProperty("compression.type") == null) { props.setProperty("compression.type", "lz4"); } props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer"); props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer"); producer = new KafkaProducer<>(props); } @Override public void append(org.apache.logging.log4j.core.LogEvent event) { try { if (level.contains(event.getLevel().toString().toUpperCase()) && !event.getLoggerName().contains("xxx")) { //控制哪些类的日志不收集 producer.send(new ProducerRecord<>(topic, appId, subAppend(event))); } } catch (Exception e) { log.warn("Parsing the log event or send log event to kafka has exception", e); } } private String subAppend(org.apache.logging.log4j.core.LogEvent event) throws JsonProcessingException { LogEvent logEvent = new LogEvent(); Map tags = new HashMap<>(); String logMessage = null; try { InetAddress inetAddress = InetAddress.getLocalHost(); tags.put("host_name", inetAddress.getHostName()); tags.put("host_ip", inetAddress.getHostAddress()); } catch (Exception e) { log.error("Error getting the ip and host name of the node where the job({}) is running", appId, e); } finally { try { logMessage = ExceptionUtil.stacktraceToString(event.getThrown()); logEvent.setContent(logMessage); } catch (Exception e) { if (logMessage != null) { logMessage = logMessage + "\n\t" + e.getMessage(); } logEvent.setContent(logMessage); } finally { logEvent.setId(UUID.randomUUID().toString()); logEvent.setTimestamp(event.getTimeMillis()); logEvent.setSource(source); if (logMessage != null) { logMessage = event.getMessage().getFormattedMessage() + "\n" + logMessage; } else { logMessage = event.getMessage().getFormattedMessage(); } logEvent.setContent(logMessage); StackTraceElement eventSource = event.getSource(); tags.put("class_name", eventSource.getClassName()); tags.put("method_name", eventSource.getMethodName()); tags.put("file_name", eventSource.getFileName()); tags.put("line_number", String.valueOf(eventSource.getLineNumber())); tags.put("logger_name", event.getLoggerName()); tags.put("level", event.getLevel().toString()); tags.put("thread_name", event.getThreadName()); tags.put("app_id", appId); tags.put("container_id", containerId); tags.put("container_type", containerType); if (taskId != null) { tags.put("task_id", taskId); } if (taskName != null) { tags.put("task_name", taskName); } if (nodeIp != null) { tags.put("node_ip", nodeIp); } logEvent.setTags(tags); } } return JacksonUtil.toJson(logEvent); } @PluginFactory public static KafkaLog4j2Appender createAppender(@PluginElement("Layout") final Layout layout, @PluginElement("Filter") final Filter filter, @Required(message = "No name provided for KafkaLog4j2Appender") @PluginAttribute("name") final String name, @PluginAttribute(value = "ignoreExceptions", defaultBoolean = true) final boolean ignoreExceptions, @Required(message = "No bootstrapServers provided for KafkaLog4j2Appender") @PluginAttribute("bootstrapServers") final String bootstrapServers, @Required(message = "No source provided for KafkaLog4j2Appender") @PluginAttribute("source") final String source, @Required(message = "No topic provided for KafkaLog4j2Appender") @PluginAttribute("topic") final String topic, @Required(message = "No level provided for KafkaLog4j2Appender") @PluginAttribute("level") final String level, @PluginElement("Properties") final Property[] properties) { return new KafkaLog4j2Appender(name, filter, layout, ignoreExceptions, properties, source, bootstrapServers, topic, level); } @Override public void stop() { super.stop(); if (producer != null) { producer.close(); } } } ================================================ FILE: flink-learning-extends/FlinkLogKafkaAppender/Log4j2KafkaAppender/src/main/resources/log4j2-example.properties ================================================ ################################################################################ # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. ################################################################################ # monitorInterval=30 # This affects logging for both user code and Flink rootLogger.level = INFO rootLogger.appenderRef.file.ref = MainAppender rootLogger.appenderRef.kafka.ref = KafkaLog4j2Appender # Uncomment this if you want to _only_ change Flink's logging #logger.flink.name = org.apache.flink #logger.flink.level = INFO # The following lines keep the log level of common libraries/connectors on # log level INFO. The root logger does not override this. You have to manually # change the log levels here. logger.akka.name = akka logger.akka.level = INFO logger.kafka.name= org.apache.kafka logger.kafka.level = INFO logger.hadoop.name = org.apache.hadoop logger.hadoop.level = INFO logger.zookeeper.name = org.apache.zookeeper logger.zookeeper.level = INFO # Log all infos in the given file appender.main.name = MainAppender appender.main.type = RollingFile appender.main.append = true appender.main.fileName = ${sys:log.file} appender.main.filePattern = ${sys:log.file}.%i appender.main.layout.type = PatternLayout appender.main.layout.pattern = %d{yyyy-MM-dd HH:mm:ss,SSS} %-5p %-60c %x - %m%n appender.main.policies.type = Policies appender.main.policies.size.type = SizeBasedTriggeringPolicy appender.main.policies.size.size = 100MB appender.main.policies.startup.type = OnStartupTriggeringPolicy appender.main.strategy.type = DefaultRolloverStrategy appender.main.strategy.max = ${env:MAX_LOG_FILE_NUMBER:-10} appender.kafka.name = KafkaLog4j2Appender appender.kafka.type = KafkaLog4j2Appender appender.kafka.source = flink-1.12.0 appender.kafka.bootstrapServers=http://localhost:9092 appender.kafka.topic = yarn_flink_log appender.kafka.level = ERROR,WARN logger.netty.name = org.apache.flink.shaded.akka.org.jboss.netty.channel.DefaultChannelPipeline logger.netty.level = OFF ================================================ FILE: flink-learning-extends/FlinkLogKafkaAppender/Log4j2KafkaAppender/src/test/java/ExceptionUtilTest.java ================================================ import com.zhisheng.log.util.ExceptionUtil; import java.util.HashMap; import java.util.Map; public class ExceptionUtilTest { public static void main(String[] args) { Throwable throwable = new Throwable("producer the metrics to kafka has exception\n" + "java.util.ConcurrentModificationException: null\n" + "\tat java.util.HashMap$HashIterator.nextNode(HashMap.java:1445)\n" + "\tat java.util.HashMap$EntryIterator.next(HashMap.java:1479)\n" + "\tat java.util.HashMap$EntryIterator.next(HashMap.java:1477)\n" + "\tat org.apache.flink.metrics.kafka.KafkaReporter.report(KafkaReporter.java:220)\n" + "\tat org.apache.flink.runtime.metrics.MetricRegistryImpl$ReporterTask.run(MetricRegistryImpl.java:451)\n" + "\tat java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)\n" + "\tat java.util.concurrent.FutureTask.runAndReset(FutureTask.java:308)\n" + "\tat java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$301(ScheduledThreadPoolExecutor.java:180)\n" + "\tat java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:294)\n" + "\tat java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)\n" + "\tat java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)"); String stacktrace = ExceptionUtil.stacktraceToString(throwable); System.out.println(stacktrace); Map map = new HashMap<>(); for (Map.Entry entry : map.entrySet()) { System.out.println(entry.getKey() + " " + entry.getValue()); } } } ================================================ FILE: flink-learning-extends/FlinkLogKafkaAppender/Log4jKafkaAppender/pom.xml ================================================ FlinkLogKafkaAppender com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 Log4jKafkaAppender 1.7.15 19.0 2.15.3 com.zhisheng.flink KafkaAppenderCommon 1.0-SNAPSHOT org.slf4j slf4j-log4j12 ${slf4j.version} ${scope} org.apache.flink flink-shaded-jackson ${jackson.version}-${flink.shaded.version} ${scope} org.apache.maven.plugins maven-shade-plugin 3.1.1 shade-flink package shade org.apache.kafka:* ================================================ FILE: flink-learning-extends/FlinkLogKafkaAppender/Log4jKafkaAppender/src/main/java/com/zhisheng/log/appender/KafkaLog4jAppender.java ================================================ package com.zhisheng.log.appender; import com.zhisheng.flink.model.LogEvent; import com.zhisheng.flink.util.ExceptionUtil; import com.zhisheng.flink.util.JacksonUtil; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.apache.flink.shaded.jackson2.com.fasterxml.jackson.core.JsonProcessingException; import org.apache.kafka.clients.producer.KafkaProducer; import org.apache.kafka.clients.producer.Producer; import org.apache.kafka.clients.producer.ProducerRecord; import org.apache.kafka.common.config.ConfigException; import org.apache.log4j.AppenderSkeleton; import org.apache.log4j.spi.LocationInfo; import org.apache.log4j.spi.LoggingEvent; import java.io.File; import java.net.InetAddress; import java.util.HashMap; import java.util.Map; import java.util.Properties; import java.util.UUID; @Slf4j @Data public class KafkaLog4jAppender extends AppenderSkeleton { private String bootstrapServers; private String source; private String topic; private String level; private String acks; private String compressionType; private String retries; private String batchSize; private String lingerMs; private String maxRequestSize; private String requestTimeoutMs; private Producer producer; private String appId; private String containerId; private String containerType; private String taskId; private String taskName; @Override public void activateOptions() { super.activateOptions(); Properties envProperties = System.getProperties(); String logFile = envProperties.getProperty("log.file"); String[] values = logFile.split(File.separator); if (values.length >= 3) { appId = values[values.length - 3]; containerId = values[values.length - 2]; String log = values[values.length - 1]; if (log.contains("jobmanager")) { containerType = "jobmanager"; } else if (log.contains("taskmanager")) { containerType = "taskmanager"; } else { containerType = "others"; } } else { log.error("log.file Property ({}) doesn't contains yarn application id or container id", logFile); } taskId = envProperties.getProperty("taskId", null); taskName = envProperties.getProperty("taskName", null); Properties props = new Properties(); if (this.bootstrapServers != null) { props.setProperty("bootstrap.servers", this.bootstrapServers); } else { throw new ConfigException("The bootstrap servers property must be specified"); } if (this.topic == null) { throw new ConfigException("Topic must be specified by the Kafka log4j appender"); } if (this.source == null) { throw new ConfigException("Source must be specified by the Kafka log4j appender"); } String clientIdPrefix = taskId != null ? taskId : appId; if (clientIdPrefix != null) { props.setProperty("client.id", clientIdPrefix + "_log"); } if (this.acks != null) { props.setProperty("acks", this.acks); } else { props.setProperty("acks", "0"); } if (this.retries != null) { props.setProperty("retries", this.retries); } else { props.setProperty("retries", "0"); } if (this.batchSize != null) { props.setProperty("batch.size", this.batchSize); } else { props.setProperty("batch.size", "16384"); } if (this.lingerMs != null) { props.setProperty("linger.ms", this.lingerMs); } else { props.setProperty("linger.ms", "5"); } if (this.compressionType != null) { props.setProperty("compression.type", this.compressionType); } else { props.setProperty("compression.type", "lz4"); } props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer"); props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer"); producer = new KafkaProducer<>(props); } @Override protected void append(LoggingEvent loggingEvent) { try { if (level.contains(loggingEvent.getLevel().toString().toUpperCase()) && !loggingEvent.getLoggerName().contains("xxx")) { //控制哪些类的日志不收集 producer.send(new ProducerRecord<>(topic, appId, subAppend(loggingEvent))); } } catch (Exception e) { log.warn("Parsing the log event or send log event to kafka has exception", e); } } private String subAppend(LoggingEvent event) throws JsonProcessingException { LogEvent logEvent = new LogEvent(); Map tags = new HashMap<>(); String logMessage = null; try { InetAddress inetAddress = InetAddress.getLocalHost(); tags.put("host_name", inetAddress.getHostName()); tags.put("host_ip", inetAddress.getHostAddress()); } catch (Exception e) { log.error("Error getting the ip and host name of the node where the job({}) is running", appId, e); } finally { try { logMessage = ExceptionUtil.stacktraceToString(event.getThrowableInformation().getThrowable()); logEvent.setContent(logMessage); } catch (Exception e) { if (logMessage != null) { logMessage = logMessage + "\n\t" + e.getMessage(); } logEvent.setContent(logMessage); } finally { logEvent.setId(UUID.randomUUID().toString()); logEvent.setTimestamp(event.getTimeStamp()); logEvent.setSource(source); if (logMessage != null) { logMessage = event.getMessage().toString() + "\n" + logMessage; } else { logMessage = event.getMessage().toString(); } logEvent.setContent(logMessage); LocationInfo locationInformation = event.getLocationInformation(); tags.put("class_name", locationInformation.getClassName()); tags.put("method_name", locationInformation.getMethodName()); tags.put("file_name", locationInformation.getFileName()); tags.put("line_number", locationInformation.getLineNumber()); tags.put("logger_name", event.getLoggerName()); tags.put("level", event.getLevel().toString()); tags.put("thread_name", event.getThreadName()); tags.put("app_id", appId); tags.put("container_id", containerId); tags.put("container_type", containerType); if (taskName != null) { tags.put("task_name", taskName); } if (taskId != null) { tags.put("task_id", taskId); } logEvent.setTags(tags); } } return JacksonUtil.toJson(logEvent); } @Override public void close() { if (!this.closed) { this.closed = true; this.producer.close(); } } @Override public boolean requiresLayout() { return false; } } ================================================ FILE: flink-learning-extends/FlinkLogKafkaAppender/Log4jKafkaAppender/src/main/resources/log4j-example.properties ================================================ # This affects logging for both user code and Flink log4j.rootLogger=INFO, RFA, kafka # Uncomment this if you want to _only_ change Flink's logging #log4j.logger.org.apache.flink=INFO # The following lines keep the log level of common libraries/connectors on # log level INFO. The root logger does not override this. You have to manually # change the log levels here. log4j.logger.akka=INFO log4j.logger.org.apache.kafka=INFO log4j.logger.org.apache.hadoop=INFO log4j.logger.org.apache.zookeeper=INFO log4j.appender.RFA=org.apache.log4j.RollingFileAppender log4j.appender.RFA.File=${log.file} log4j.appender.RFA.MaxFileSize=256MB log4j.appender.RFA.Append=true log4j.appender.RFA.MaxBackupIndex=10 log4j.appender.RFA.layout=org.apache.log4j.PatternLayout log4j.appender.RFA.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS} %t %-5p %-60c %x - %m%n log4j.logger.org.apache.kafka.clients.Metadata=WARN,kafka log4j.appender.kafka=com.zhisheng.log.appender.KafkaLog4jAppender log4j.appender.kafka.source=flink-1.10.0 log4j.appender.kafka.bootstrapServers=http://localhost:9092 log4j.appender.kafka.topic=flink_log log4j.appender.kafka.level=ERROR,WARN # Suppress the irrelevant (wrong) warnings from the Netty channel handler log4j.logger.org.apache.flink.shaded.akka.org.jboss.netty.channel.DefaultChannelPipeline=ERROR, RFA ================================================ FILE: flink-learning-extends/FlinkLogKafkaAppender/README.md ================================================ ## FlinkLogKafkaAppender + Log4jKafkaAppender: 适用 Flink 1.10 版本(使用的是 log4j) + Log4j2KafkaAppender:适用 Flink 1.10 之后版本(使用的是 log4j2) ### 使用方式 1、将项目打出来打 kafka appender jar 包放到 flink lib 目录 2、按照项目提示的 flink log4j 配置去配置 flink conf 下面的 log4j.properties 文件,其中 k8s 的要配置 log4j-console.properties 文件 ================================================ FILE: flink-learning-extends/FlinkLogKafkaAppender/pom.xml ================================================ flink-learning-extends com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 FlinkLogKafkaAppender pom FlinkLogKafkaAppender Log4jKafkaAppender Log4j2KafkaAppender KafkaAppenderCommon 11 11 3.9.1 provided 2.11.0 org.apache.kafka kafka-clients ${kafka.version} org.projectlombok lombok 1.18.36 provided ================================================ FILE: flink-learning-extends/README.md ================================================ ### flink-learning-extends Flink 项目的扩展项目,比如自定义 Flink SQL Connector / Metrics Reporter / 日志收集 等扩展项目 ================================================ FILE: flink-learning-extends/flink-metrics/flink-metrics-kafka/README.md ================================================ ### flink-metrics-kafka compile the module and move the target `flink-metrics-kafka.jar` to flink lib folder, and add metrics reporter configuration in the `flink-config.xml`. eg: ```xml #============================================================================== #### Kafka Metrics Reporter ###============================================================================== metrics.reporter.kafka.class: org.apache.flink.metrics.kafka.KafkaReporter metrics.reporter.kafka.bootstrapServers: http://localhost:9092 metrics.reporter.kafka.topic: metrics-flink-jobs metrics.reporter.kafka.acks: 0 metrics.reporter.kafka.compressionType: lz4 metrics.reporter.kafka.bufferMemory: 33554432 metrics.reporter.kafka.retries: 0 metrics.reporter.kafka.batchSize: 16384 metrics.reporter.kafka.lingerMs: 5 metrics.reporter.kafka.maxRequestSize: 1048576 metrics.reporter.kafka.requestTimeoutMs: 30000 ``` ================================================ FILE: flink-learning-extends/flink-metrics/flink-metrics-kafka/pom.xml ================================================ 4.0.0 com.zhisheng.flink flink-metrics 1.0-SNAPSHOT flink-metrics-kafka org.apache.flink flink-annotations ${flink.version} provided org.slf4j slf4j-api 1.7.36 provided org.apache.flink flink-core ${flink.version} provided org.apache.flink flink-runtime ${flink.version} provided org.apache.flink flink-metrics-core ${flink.version} provided org.apache.kafka kafka-clients 3.9.1 org.apache.maven.plugins maven-shade-plugin shade-flink package shade org.apache.kafka:* ================================================ FILE: flink-learning-extends/flink-metrics/flink-metrics-kafka/src/main/java/org/apache/flink/metrics/kafka/KafkaReporter.java ================================================ package org.apache.flink.metrics.kafka; import org.apache.flink.annotation.PublicEvolving; import org.apache.flink.annotation.VisibleForTesting; import org.apache.flink.metrics.*; import org.apache.flink.metrics.kafka.util.JacksonUtil; import org.apache.flink.metrics.reporter.InstantiateViaFactory; import org.apache.flink.metrics.reporter.MetricReporter; import org.apache.flink.metrics.reporter.Scheduled; import org.apache.flink.runtime.metrics.groups.AbstractMetricGroup; import org.apache.flink.runtime.metrics.groups.FrontMetricGroup; import org.apache.kafka.clients.producer.KafkaProducer; import org.apache.kafka.clients.producer.Producer; import org.apache.kafka.clients.producer.ProducerRecord; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; import java.util.HashMap; import java.util.Map; import java.util.Properties; import java.util.regex.Pattern; import static org.apache.flink.metrics.kafka.KafkaReporterOptions.*; /** * {@link MetricReporter} that exports {@link Metric Metrics} via Kafka. */ @PublicEvolving @InstantiateViaFactory(factoryClassName = "org.apache.flink.metrics.kafka.KafkaReporterFactory") public class KafkaReporter implements MetricReporter, Scheduled { private static final Logger LOG = LoggerFactory.getLogger(KafkaReporter.class); private final Map, MetricEvent> gauges = new HashMap<>(); private final Map counters = new HashMap<>(); private final Map histograms = new HashMap<>(); private final Map meters = new HashMap<>(); private static final Map kafkaLagTimes = new HashMap<>(); @VisibleForTesting static final char SCOPE_SEPARATOR = '_'; private static final CharacterFilter CHARACTER_FILTER = new CharacterFilter() { private final Pattern notAllowedCharacters = Pattern.compile("[^a-zA-Z0-9:_]"); @Override public String filterCharacters(String input) { return notAllowedCharacters.matcher(input).replaceAll("_"); } }; private Producer producer; private String topic; private String appId; private String containerId; private String taskName; private String taskId; @Override public void open(MetricConfig config) { Map envs = System.getenv(); String clusterId = envs.get("CLUSTER_ID"); if (clusterId != null) { //k8s cluster appId = clusterId; containerId = envs.get("HOSTNAME"); } else { //yarn cluster String pwd = envs.get("PWD"); String[] values = pwd.split(File.separator); if (values.length >= 2) { appId = values[values.length - 2]; containerId = values[values.length - 1]; } else { LOG.error("PWD env ({}) doesn't contains yarn application id or container id", pwd); throw new RuntimeException( "PWD env doesn't contains yarn application id or container id"); } } Properties properties = System.getProperties(); taskName = properties.getProperty("taskName", null); taskId = properties.getProperty("taskId", null); Properties props = new Properties(); String clientIdPrefix = taskId != null ? taskId : appId; props.put("client.id", "flink_" + clientIdPrefix + "_metrics"); props.put("bootstrap.servers", getString(config, BOOTSTRAP_SERVERS)); props.put("acks", getString(config, ACKS)); props.put("retries", getInteger(config, RETRIES)); props.put("batch.size", getInteger(config, BATCH_SIZE)); props.put("linger.ms", getInteger(config, LINGER_MS)); props.put("buffer.memory", getInteger(config, BUFFER_MEMORY)); props.put("max.request.size", getInteger(config, MAX_REQUEST_SIZE)); props.put("request.timeout.ms", getInteger(config, REQUEST_TIMEOUT_MS)); props.put("compression.type", getString(config, COMPRESSION_TYPE)); props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer"); props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer"); producer = new KafkaProducer<>(props); topic = getString(config, TOPIC); } @Override public void close() { producer.close(); } @Override public void notifyOfAddedMetric(Metric metric, String metricName, MetricGroup group) { MetricEvent metricEvent = new MetricEvent(getScopedName(metricName, group), getTags(group)); synchronized (this) { if (metric instanceof Counter) { counters.put((Counter) metric, metricEvent); } else if (metric instanceof Gauge) { gauges.put((Gauge) metric, metricEvent); } else if (metric instanceof Histogram) { histograms.put((Histogram) metric, metricEvent); } else if (metric instanceof Meter) { meters.put((Meter) metric, metricEvent); } else { LOG.warn("Cannot add unknown metric type {}. This indicates that the reporter " + "does not support this metric type.", metric.getClass().getName()); } } } @Override public void notifyOfRemovedMetric(Metric metric, String metricName, MetricGroup group) { synchronized (this) { if (metric instanceof Counter) { counters.remove(metric); } else if (metric instanceof Gauge) { gauges.remove(metric); } else if (metric instanceof Histogram) { histograms.remove(metric); } else if (metric instanceof Meter) { meters.remove(metric); } else { LOG.warn("Cannot remove unknown metric type {}. This indicates that the reporter " + "does not support this metric type.", metric.getClass().getName()); } } } @Override public void report() { try { long currentTimeMillis = System.currentTimeMillis(); Map tags = new HashMap<>(); tags.put("app_id", appId); tags.put("container_id", containerId); tags.put("flink_version", "1.12.0"); if (taskName != null) { tags.put("dataman_task_name", taskName); } if (taskId != null) { tags.put("dataman_task_id", taskId); } Map envs = System.getenv(); //k8s 集群,该值为物理机器 ip,和 pod ip 有区别 if (envs.containsKey("_HOST_IP_ADDRESS")) { tags.put("node_ip", envs.get("_HOST_IP_ADDRESS")); } //后面的条件是为了 k8s cluster 区分 if (envs.containsKey("_APP_ID") || !envs.get("HOSTNAME").contains("taskmanager")) { //JobManager tags.put("container_type", "jobmanager"); MetricEvent jvmClassLoader = new MetricEvent("jobmanager_Status_JVM_ClassLoader", tags, currentTimeMillis); MetricEvent jvmGarbageCollector = new MetricEvent("jobmanager_Status_JVM_GarbageCollector", tags, currentTimeMillis); MetricEvent jvmMemory = new MetricEvent("jobmanager_Status_JVM_Memory", tags, currentTimeMillis); MetricEvent jvmCPU = new MetricEvent("jobmanager_Status_JVM_CPU", tags, currentTimeMillis); MetricEvent jvmThreadsCount = new MetricEvent("jobmanager_Status_JVM_Threads_Count", tags, currentTimeMillis); MetricEvent jobCheckpointing = new MetricEvent("jobmanager_Job_Checkpointing", tags, currentTimeMillis); MetricEvent cluster = new MetricEvent("jobmanager_Cluster", tags, currentTimeMillis); for (Map.Entry, MetricEvent> entry : gauges.entrySet()) { MetricEvent event = entry.getValue(); String name = event.getName(); if (name.contains("ClassLoader")) { jvmClassLoader.addTags(event.getTags()); String[] groups = name.split("_"); addFields(jvmClassLoader, groups[groups.length - 1], entry.getKey()); } else if (name.contains("GarbageCollector")) { jvmGarbageCollector.addTags(event.getTags()); String[] groups = name.split("GarbageCollector_"); addFields(jvmGarbageCollector, groups[groups.length - 1], entry.getKey()); } else if (name.contains("JVM_Memory")) { jvmMemory.addTags(event.getTags()); String[] groups = name.split("JVM_Memory_"); addFields(jvmMemory, groups[groups.length - 1], entry.getKey()); } else if (name.contains("JVM_CPU")) { jvmCPU.addTags(event.getTags()); String[] groups = name.split("JVM_CPU_"); addFields(jvmCPU, groups[groups.length - 1], entry.getKey()); } else if (name.contains("JVM_Threads_Count")) { jvmThreadsCount.addTags(event.getTags()); String[] groups = name.split("Status_JVM_"); addFields(jvmThreadsCount, groups[groups.length - 1], entry.getKey()); } else if (name.contains("Checkpoint")) { jobCheckpointing.addTags(event.getTags()); String[] groups = name.split("job_"); addFields(jobCheckpointing, groups[groups.length - 1], entry.getKey()); } else { cluster.addTags(event.getTags()); String[] groups = name.split("jobmanager_"); addFields(cluster, groups[groups.length - 1], entry.getKey()); } } producer.send(new ProducerRecord<>(topic, appId, JacksonUtil.toJson(jvmClassLoader))); producer.send(new ProducerRecord<>(topic, appId, JacksonUtil.toJson(jvmGarbageCollector))); producer.send(new ProducerRecord<>(topic, appId, JacksonUtil.toJson(jvmMemory))); producer.send(new ProducerRecord<>(topic, appId, JacksonUtil.toJson(jvmCPU))); producer.send(new ProducerRecord<>(topic, appId, JacksonUtil.toJson(jvmThreadsCount))); producer.send(new ProducerRecord<>(topic, appId, JacksonUtil.toJson(jobCheckpointing))); producer.send(new ProducerRecord<>(topic, appId, JacksonUtil.toJson(cluster))); } else { //TaskManager tags.put("container_type", "taskmanager"); MetricEvent jvmClassLoader = new MetricEvent("taskmanager_Status_JVM_ClassLoader", tags, currentTimeMillis); MetricEvent jvmGarbageCollector = new MetricEvent("taskmanager_Status_JVM_GarbageCollector", tags, currentTimeMillis); MetricEvent jvmMemory = new MetricEvent("taskmanager_Status_JVM_Memory", tags, currentTimeMillis); MetricEvent jvmCPU = new MetricEvent("taskmanager_Status_JVM_CPU", tags, currentTimeMillis); MetricEvent jvmThreadsCount = new MetricEvent("taskmanager_Status_JVM_Threads_Count", tags, currentTimeMillis); MetricEvent statusShuffleNetty = new MetricEvent("taskmanager_Status_Shuffle_Netty", tags, currentTimeMillis); for (Map.Entry, MetricEvent> entry : gauges.entrySet()) { MetricEvent event = entry.getValue(); String name = event.getName(); if (name.contains("ClassLoader")) { jvmClassLoader.addTags(event.getTags()); String[] groups = name.split("_"); addFields(jvmClassLoader, groups[groups.length - 1], entry.getKey()); } else if (name.contains("GarbageCollector")) { jvmGarbageCollector.addTags(event.getTags()); String[] groups = name.split("GarbageCollector_"); addFields(jvmGarbageCollector, groups[groups.length - 1], entry.getKey()); } else if (name.contains("_Memory_")) { jvmMemory.addTags(event.getTags()); String[] groups = name.split("Memory_"); addFields(jvmMemory, groups[groups.length - 1], entry.getKey()); } else if (name.contains("JVM_CPU")) { jvmCPU.addTags(event.getTags()); String[] groups = name.split("JVM_CPU_"); addFields(jvmCPU, groups[groups.length - 1], entry.getKey()); } else if (name.contains("JVM_Threads_Count")) { jvmThreadsCount.addTags(event.getTags()); String[] groups = name.split("Status_JVM_"); addFields(jvmThreadsCount, groups[groups.length - 1], entry.getKey()); } else if (name.contains("Status_Shuffle_Netty")) { statusShuffleNetty.addTags(event.getTags()); String[] groups = name.split("Shuffle_Netty_"); addFields(statusShuffleNetty, groups[groups.length - 1], entry.getKey()); } else if (name.contains("taskmanager_job_task_buffers") || name.contains("taskmanager_Status_Network")) { continue; } else if (name.startsWith("taskmanager_job_task_operator_KafkaConsumer")) { if (name.contains("currentDataTimestampOffsetsAndCommittedOffsets")) { MetricEvent metricEvent = addKafkaLagMetricFields(entry.getValue(), currentTimeMillis, entry.getKey()); if (metricEvent == null) { continue; } //todo:可能 Kafka Lag Time 埋点可能需要额外发送到一个 topic 供 Kafka Lag 告警使用 producer.send(new ProducerRecord<>(topic, appId, JacksonUtil.toJson(metricEvent.addTags(tags)))); } else { continue; } } else { MetricEvent metricEvent = addFields(entry.getValue(), currentTimeMillis, entry.getKey()); producer.send(new ProducerRecord<>(topic, appId, JacksonUtil.toJson(metricEvent.addTags(tags)))); } } producer.send(new ProducerRecord<>(topic, appId, JacksonUtil.toJson(jvmClassLoader))); producer.send(new ProducerRecord<>(topic, appId, JacksonUtil.toJson(jvmGarbageCollector))); producer.send(new ProducerRecord<>(topic, appId, JacksonUtil.toJson(jvmMemory))); producer.send(new ProducerRecord<>(topic, appId, JacksonUtil.toJson(jvmCPU))); producer.send(new ProducerRecord<>(topic, appId, JacksonUtil.toJson(jvmThreadsCount))); producer.send(new ProducerRecord<>(topic, appId, JacksonUtil.toJson(statusShuffleNetty))); } for (Map.Entry entry : counters.entrySet()) { String name = entry.getValue().getName(); if (name.contains("taskmanager_job_task_numBytesIn") && name.length() > 31) { continue; } MetricEvent metricEvent = addFields(entry.getValue(), currentTimeMillis, entry.getKey()); metricEvent.addTags(tags); producer.send(new ProducerRecord<>(topic, appId, JacksonUtil.toJson(metricEvent))); } for (Map.Entry entry : histograms.entrySet()) { MetricEvent metricEvent = addFields(entry.getValue(), currentTimeMillis, entry.getKey()); metricEvent.addTags(tags); producer.send(new ProducerRecord<>(topic, appId, JacksonUtil.toJson(metricEvent))); } for (Map.Entry entry : meters.entrySet()) { String name = entry.getValue().getName(); if (name.contains("taskmanager_job_task_numBytesIn") && name.length() > 31) { continue; } MetricEvent metricEvent = addFields(entry.getValue(), currentTimeMillis, entry.getKey()); metricEvent.addTags(tags); producer.send(new ProducerRecord<>(topic, appId, JacksonUtil.toJson(metricEvent))); } } catch (Exception e) { // LOG.warn("producer the metrics to kafka has exception", e); //todo: 计数,当出现多少发送失败的时候给自己一个告警 } } private static Map getTags(MetricGroup group) { // Keys are surrounded by brackets: remove them, transforming "" to "name". Map tags = new HashMap<>(); for (Map.Entry variable : group.getAllVariables().entrySet()) { String name = variable.getKey(); //remove TaskManager tm_id tag, because we will add container_id tag when report, the two tag value is same if (name.contains("tm_id")) { continue; } tags.put(name.substring(1, name.length() - 1), variable.getValue()); } return tags; } private static String getScopedName(String metricName, MetricGroup group) { return getLogicalScope(group) + SCOPE_SEPARATOR + metricName; } private static String getLogicalScope(MetricGroup group) { return ((FrontMetricGroup>) group).getLogicalScope( CHARACTER_FILTER, SCOPE_SEPARATOR); } static MetricEvent addFields(MetricEvent metricEvent, String field, Gauge gauge) { Object value = gauge.getValue(); Map fields = metricEvent.getFields(); if (fields != null) { if (value instanceof Number) { metricEvent.addField(field, (Number) value); } else { metricEvent.addField(field, String.valueOf(value)); } } else { Map eventFields = new HashMap<>(); if (value instanceof Number) { eventFields.put(field, (Number) value); } else { eventFields.put(field, String.valueOf(value)); } metricEvent.setFields(eventFields); } return metricEvent; } static MetricEvent addKafkaLagMetricFields(MetricEvent metricEvent, Long timestamp, Gauge gauge) { String gaugeValue = (String) gauge.getValue(); String[] split = gaugeValue.split("_"); Map tags = metricEvent.getTags(); if (split.length == 3) { Map fields = new HashMap<>(3); fields.put("currentOffsets", Long.valueOf(split[0])); fields.put("currentDataTimestamp", Long.valueOf(split[1])); fields.put("committedOffsets", Long.valueOf(split[2])); String key = tags.get("kafka") + tags.get("topic") + tags.get("group") + tags.get("partition"); String value = split[0] + "_" +split[1]; if (kafkaLagTimes.get(key) != null && kafkaLagTimes.get(key).equals(value)) { return null; } else { kafkaLagTimes.put(key, value); } metricEvent.setFields(fields); } metricEvent.setTimestamp(timestamp); return metricEvent; } static MetricEvent addFields(MetricEvent metricEvent, Long timestamp, Gauge gauge) { Object value = gauge.getValue(); Map fields = new HashMap<>(1); if (value instanceof Number) { fields.put("value", (Number) value); } else { fields.put("value", String.valueOf(value)); } metricEvent.setFields(fields); metricEvent.setTimestamp(timestamp); return metricEvent; } static MetricEvent addFields(MetricEvent metricEvent, Long timestamp, Counter counter) { Map fields = new HashMap<>(1); fields.put("count", counter.getCount()); metricEvent.setFields(fields); metricEvent.setTimestamp(timestamp); return metricEvent; } static MetricEvent addFields(MetricEvent metricEvent, Long timestamp, Histogram histogram) { HistogramStatistics statistics = histogram.getStatistics(); Map fields = new HashMap<>(11); fields.put("count", statistics.size()); fields.put("min", statistics.getMin()); fields.put("max", statistics.getMax()); fields.put("stddev", statistics.getStdDev()); fields.put("mean", statistics.getMean()); fields.put("p50", statistics.getQuantile(.50)); fields.put("p75", statistics.getQuantile(.75)); fields.put("p95", statistics.getQuantile(.95)); fields.put("p98", statistics.getQuantile(.98)); fields.put("p99", statistics.getQuantile(.99)); fields.put("p999", statistics.getQuantile(.999)); metricEvent.setFields(fields); metricEvent.setTimestamp(timestamp); return metricEvent; } static MetricEvent addFields(MetricEvent metricEvent, Long timestamp, Meter meter) { Map fields = new HashMap<>(2); fields.put("count", meter.getCount()); fields.put("rate", meter.getRate()); metricEvent.setFields(fields); metricEvent.setTimestamp(timestamp); return metricEvent; } } ================================================ FILE: flink-learning-extends/flink-metrics/flink-metrics-kafka/src/main/java/org/apache/flink/metrics/kafka/KafkaReporterFactory.java ================================================ package org.apache.flink.metrics.kafka; import org.apache.flink.metrics.reporter.InterceptInstantiationViaReflection; import org.apache.flink.metrics.reporter.MetricReporterFactory; import java.util.Properties; /** * {@link MetricReporterFactory} for {@link KafkaReporter}. */ @InterceptInstantiationViaReflection(reporterClassName = "org.apache.flink.metrics.kafka.KafkaReporter") public class KafkaReporterFactory implements MetricReporterFactory { @Override public KafkaReporter createMetricReporter(Properties properties) { return new KafkaReporter(); } } ================================================ FILE: flink-learning-extends/flink-metrics/flink-metrics-kafka/src/main/java/org/apache/flink/metrics/kafka/KafkaReporterOptions.java ================================================ package org.apache.flink.metrics.kafka; import org.apache.flink.annotation.docs.Documentation; import org.apache.flink.configuration.ConfigOption; import org.apache.flink.configuration.ConfigOptions; import org.apache.flink.metrics.MetricConfig; /** * Config options for the {@link KafkaReporter}. */ @Documentation.SuffixOption(value = "metrics.reporter..") public class KafkaReporterOptions { //https://gjtmaster.com/2018/09/03/kafka%E7%94%9F%E4%BA%A7%E8%80%85Producer%E5%8F%82%E6%95%B0%E8%AE%BE%E7%BD%AE%E5%8F%8A%E8%B0%83%E4%BC%98/ //https://blog.csdn.net/qq_28410283/article/details/88570141 public static final ConfigOption BOOTSTRAP_SERVERS = ConfigOptions .key("bootstrapServers") .defaultValue("http://localhost:9092") .withDescription("the Kafka broker server host"); public static final ConfigOption TOPIC = ConfigOptions .key("topic") .defaultValue("metrics-flink-jobs") .withDescription("the Kafka topic to store metrics"); public static final ConfigOption ACKS = ConfigOptions .key("acks") .defaultValue("0") .withDescription("the Kafka producer acks(0/-1/1)"); public static final ConfigOption COMPRESSION_TYPE = ConfigOptions .key("compressionType") .defaultValue("none") .withDescription("Set whether the producer side compresses the message, the default value is none," + " that is, the message is not compressed,you can choose none/gzip/snappy/lz4"); public static final ConfigOption BUFFER_MEMORY = ConfigOptions .key("bufferMemory") .defaultValue(33554432) .withDescription("This parameter is used to specify the size of the buffer used by the Producer to cache messages, " + "in bytes, the default value is 33554432 and the total is 32M."); public static final ConfigOption RETRIES = ConfigOptions .key("retries") .defaultValue(0) .withDescription("This parameter indicates the number of retries. The default value is 0, which means no retries."); public static final ConfigOption BATCH_SIZE = ConfigOptions .key("batchSize") .defaultValue(16384) .withDescription("The default value of the batch.size parameter is 16384, which is 16KB"); public static final ConfigOption LINGER_MS = ConfigOptions .key("lingerMs") .defaultValue(5) .withDescription("In order to reduce network IO, improve the overall TPS. Assuming that linger.ms=5 is set," + " it means that the producer request may be sent with a delay of 5ms."); public static final ConfigOption MAX_REQUEST_SIZE = ConfigOptions .key("maxRequestSize") .defaultValue(1048576) .withDescription("This parameter controls the maximum message size that the producer can send, the default is 1048576 bytes (1MB)"); public static final ConfigOption REQUEST_TIMEOUT_MS = ConfigOptions .key("requestTimeoutMs") .defaultValue(30) .withDescription("After the producer sends a request to the broker, the broker needs to" + " return the processing result to the producer within the specified time frame. The default is 30 seconds."); public static final ConfigOption DING_DING_ALERT_REBOOT = ConfigOptions .key("dingDingAlertReboot") .noDefaultValue() .withDescription("alert to the flink administrator"); static String getString(MetricConfig config, ConfigOption key) { return config.getString(key.key(), key.defaultValue()); } static int getInteger(MetricConfig config, ConfigOption key) { return config.getInteger(key.key(), key.defaultValue()); } } ================================================ FILE: flink-learning-extends/flink-metrics/flink-metrics-kafka/src/main/java/org/apache/flink/metrics/kafka/MetricEvent.java ================================================ package org.apache.flink.metrics.kafka; import java.util.HashMap; import java.util.Map; public class MetricEvent { // measurement name private String name; // timestamp for metric private long timestamp; // values for metric private Map fields = new HashMap<>(); // tags for metric private Map tags = new HashMap<>(); public void setTimestamp(long timestamp) { this.timestamp = timestamp; } public MetricEvent(String name, Map tags) { this.name = name; this.tags = tags; } public MetricEvent(String name, Map tags, long metricsTimestamp) { this.name = name; this.tags = tags; this.timestamp = metricsTimestamp; } public String getName() { return name; } public Map getTags() { return tags; } public long getTimestamp() { return timestamp; } public void setFields(Map fields) { this.fields = fields; } public Map getFields() { return fields; } // add field whit ingore empty key public MetricEvent addField(String key, Object val) { if (key == null || "".equals(key)) return this; fields.put(key, val); return this; } // add tag whit ingore empty key and val public MetricEvent addTag(String key, String val) { if (key == null || "".equals(key)) return this; if (val == null || "".equals(val)) return this; tags.put(key, val); return this; } public MetricEvent addTags(Map newTags) { if (newTags == null || newTags.size() == 0) return this; tags.putAll(newTags); return this; } @Override public String toString() { return "MetricEvent{" + "name='" + name + '\'' + ", timestamp=" + timestamp + ", fields=" + fields + ", tags=" + tags + '}'; } } ================================================ FILE: flink-learning-extends/flink-metrics/flink-metrics-kafka/src/main/java/org/apache/flink/metrics/kafka/util/JacksonUtil.java ================================================ package org.apache.flink.metrics.kafka.util; import org.apache.flink.shaded.jackson2.com.fasterxml.jackson.core.JsonProcessingException; import org.apache.flink.shaded.jackson2.com.fasterxml.jackson.databind.ObjectMapper; public class JacksonUtil { private final static ObjectMapper mapper = new ObjectMapper(); /** * 将对象转换成普通的 JSON 数据 * * @param value * @return * @throws JsonProcessingException */ public static String toJson(Object value) throws JsonProcessingException { return mapper.writeValueAsString(value); } /** * 将对象转换成结构化的 JSON 数据 * * @param value * @return * @throws JsonProcessingException */ public static String toFormatJson(Object value) throws JsonProcessingException { return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(value); } } ================================================ FILE: flink-learning-extends/flink-metrics/flink-metrics-kafka/src/main/resources/META-INF/services/org.apache.flink.metrics.reporter.MetricReporterFactory ================================================ # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. org.apache.flink.metrics.kafka.KafkaReporterFactory ================================================ FILE: flink-learning-extends/flink-metrics/flink-metrics-prometheus/README.md ================================================ ### flink-metrics-prometheus compile the module and move the target `flink-metrics-prometheus.jar` to flink lib folder, and add metrics reporter configuration in the `flink-config.xml`. eg: ```xml #============================================================================== # Metrics Reporter #============================================================================== metrics.reporter.promgateway.class: org.apache.flink.metrics.prometheus.PrometheusPushGatewayReporter metrics.reporter.promgateway.host: k8s # metrics.reporter.promgateway.host: localhost metrics.reporter.promgateway.port: 9091 metrics.reporter.promgateway.clusterMode: k8s metrics.reporter.promgateway.jobName: flink-job metrics.reporter.promgateway.randomJobNameSuffix: false metrics.reporter.promgateway.deleteOnShutdown: true metrics.reporter.promgateway.interval: 5 SECONDS ``` ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/2022-05-08-074128.png) ================================================ FILE: flink-learning-extends/flink-metrics/flink-metrics-prometheus/pom.xml ================================================ 4.0.0 com.zhisheng.flink flink-metrics 1.0-SNAPSHOT flink-metrics-prometheus 0.8.1 org.apache.flink flink-annotations ${flink.version} provided org.apache.flink flink-core ${flink.version} provided org.apache.flink flink-metrics-core ${flink.version} provided io.prometheus simpleclient ${prometheus.version} io.prometheus simpleclient_httpserver ${prometheus.version} io.prometheus simpleclient_pushgateway ${prometheus.version} org.apache.flink flink-metrics-core ${flink.version} test test-jar org.apache.flink flink-test-utils-junit ${flink.version} com.mashape.unirest unirest-java 1.4.9 test org.apache.maven.plugins maven-shade-plugin shade-flink package shade io.prometheus:* ================================================ FILE: flink-learning-extends/flink-metrics/flink-metrics-prometheus/src/main/java/org/apache/flink/metrics/prometheus/AbstractPrometheusReporter.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.flink.metrics.prometheus; import io.prometheus.client.Collector; import io.prometheus.client.CollectorRegistry; import org.apache.flink.annotation.PublicEvolving; import org.apache.flink.annotation.VisibleForTesting; import org.apache.flink.metrics.*; import org.apache.flink.metrics.reporter.MetricReporter; import org.apache.flink.runtime.metrics.groups.AbstractMetricGroup; import org.apache.flink.runtime.metrics.groups.FrontMetricGroup; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.*; import java.util.regex.Pattern; import static org.apache.flink.metrics.prometheus.PrometheusPushGatewayReporterOptions.FILTER_LABEL_VALUE_CHARACTER; /** * base prometheus reporter for prometheus metrics. */ @PublicEvolving public abstract class AbstractPrometheusReporter implements MetricReporter { protected final Logger log = LoggerFactory.getLogger(getClass()); private static final Pattern UNALLOWED_CHAR_PATTERN = Pattern.compile("[^a-zA-Z0-9:_-]"); public static final CharacterFilter CHARACTER_FILTER = new CharacterFilter() { @Override public String filterCharacters(String input) { return replaceInvalidChars(input); } }; private static final char SCOPE_SEPARATOR = '_'; private static final String SCOPE_PREFIX = "flink" + SCOPE_SEPARATOR; public final Map> collectorsWithCountByMetricName = new HashMap<>(); @VisibleForTesting static String replaceInvalidChars(final String input) { // https://prometheus.io/docs/instrumenting/writing_exporters/ // Only [a-zA-Z0-9:_] are valid in metric names, any other characters should be sanitized to an underscore. return UNALLOWED_CHAR_PATTERN.matcher(input).replaceAll("_"); } public CharacterFilter labelValueCharactersFilter = CHARACTER_FILTER; @Override public void open(MetricConfig config) { boolean filterLabelValueCharacters = config.getBoolean( FILTER_LABEL_VALUE_CHARACTER.key(), FILTER_LABEL_VALUE_CHARACTER.defaultValue()); if (!filterLabelValueCharacters) { labelValueCharactersFilter = input -> input; } } @Override public void close() { CollectorRegistry.defaultRegistry.clear(); } @Override public void notifyOfAddedMetric(final Metric metric, final String metricName, final MetricGroup group) { List dimensionKeys = new LinkedList<>(); List dimensionValues = new LinkedList<>(); //----------------------------------------- for (final Map.Entry dimension : group.getAllVariables().entrySet()) { final String key = dimension.getKey(); dimensionKeys.add(CHARACTER_FILTER.filterCharacters(key.substring(1, key.length() - 1))); dimensionValues.add(labelValueCharactersFilter.filterCharacters(dimension.getValue())); } final String scopedMetricName = getScopedName(metricName, group); final String helpString = metricName + " (scope: " + getLogicalScope(group) + ")"; final Collector collector; Integer count = 0; synchronized (this) { if (collectorsWithCountByMetricName.containsKey(scopedMetricName)) { final AbstractMap.SimpleImmutableEntry collectorWithCount = collectorsWithCountByMetricName.get(scopedMetricName); collector = collectorWithCount.getKey(); count = collectorWithCount.getValue(); } else { collector = createCollector(metric, dimensionKeys, dimensionValues, scopedMetricName, helpString); try { collector.register(); } catch (Exception e) { log.warn("There was a problem registering metric {}.", metricName, e); } } addMetric(metric, dimensionValues, collector); collectorsWithCountByMetricName.put(scopedMetricName, new AbstractMap.SimpleImmutableEntry<>(collector, count + 1)); } } public static String getScopedName(String metricName, MetricGroup group) { return SCOPE_PREFIX + getLogicalScope(group) + SCOPE_SEPARATOR + CHARACTER_FILTER.filterCharacters(metricName); } public Collector createCollector(Metric metric, List dimensionKeys, List dimensionValues, String scopedMetricName, String helpString) { Collector collector; if (metric instanceof Gauge || metric instanceof Counter || metric instanceof Meter) { collector = io.prometheus.client.Gauge .build() .name(scopedMetricName) .help(helpString) .labelNames(toArray(dimensionKeys)) .create(); } else if (metric instanceof Histogram) { collector = new HistogramSummaryProxy((Histogram) metric, scopedMetricName, helpString, dimensionKeys, dimensionValues); } else { log.warn("Cannot create collector for unknown metric type: {}. This indicates that the metric type is not supported by this reporter.", metric.getClass().getName()); collector = null; } return collector; } public void addMetric(Metric metric, List dimensionValues, Collector collector) { if (metric instanceof Gauge) { ((io.prometheus.client.Gauge) collector).setChild(gaugeFrom((Gauge) metric), toArray(dimensionValues)); } else if (metric instanceof Counter) { ((io.prometheus.client.Gauge) collector).setChild(gaugeFrom((Counter) metric), toArray(dimensionValues)); } else if (metric instanceof Meter) { ((io.prometheus.client.Gauge) collector).setChild(gaugeFrom((Meter) metric), toArray(dimensionValues)); } else if (metric instanceof Histogram) { ((HistogramSummaryProxy) collector).addChild((Histogram) metric, dimensionValues); } else { log.warn("Cannot add unknown metric type: {}. This indicates that the metric type is not supported by this reporter.", metric.getClass().getName()); } } private void removeMetric(Metric metric, List dimensionValues, Collector collector) { if (metric instanceof Gauge) { ((io.prometheus.client.Gauge) collector).remove(toArray(dimensionValues)); } else if (metric instanceof Counter) { ((io.prometheus.client.Gauge) collector).remove(toArray(dimensionValues)); } else if (metric instanceof Meter) { ((io.prometheus.client.Gauge) collector).remove(toArray(dimensionValues)); } else if (metric instanceof Histogram) { ((HistogramSummaryProxy) collector).remove(dimensionValues); } else { log.warn("Cannot remove unknown metric type: {}. This indicates that the metric type is not supported by this reporter.", metric.getClass().getName()); } } @Override public void notifyOfRemovedMetric(final Metric metric, final String metricName, final MetricGroup group) { List dimensionValues = new LinkedList<>(); for (final Map.Entry dimension : group.getAllVariables().entrySet()) { dimensionValues.add(labelValueCharactersFilter.filterCharacters(dimension.getValue())); } final String scopedMetricName = getScopedName(metricName, group); synchronized (this) { final AbstractMap.SimpleImmutableEntry collectorWithCount = collectorsWithCountByMetricName.get(scopedMetricName); final Integer count = collectorWithCount.getValue(); final Collector collector = collectorWithCount.getKey(); removeMetric(metric, dimensionValues, collector); if (count == 1) { try { CollectorRegistry.defaultRegistry.unregister(collector); } catch (Exception e) { log.warn("There was a problem unregistering metric {}.", scopedMetricName, e); } collectorsWithCountByMetricName.remove(scopedMetricName); } else { collectorsWithCountByMetricName.put(scopedMetricName, new AbstractMap.SimpleImmutableEntry<>(collector, count - 1)); } } } @SuppressWarnings("unchecked") public static String getLogicalScope(MetricGroup group) { return ((FrontMetricGroup>) group).getLogicalScope(CHARACTER_FILTER, SCOPE_SEPARATOR); } @VisibleForTesting io.prometheus.client.Gauge.Child gaugeFrom(Gauge gauge) { return new io.prometheus.client.Gauge.Child() { @Override public double get() { final Object value = gauge.getValue(); if (value == null) { log.debug("Gauge {} is null-valued, defaulting to 0.", gauge); return 0; } if (value instanceof Double) { return (double) value; } if (value instanceof Number) { return ((Number) value).doubleValue(); } if (value instanceof Boolean) { return ((Boolean) value) ? 1 : 0; } log.debug("Invalid type for Gauge {}: {}, only number types and booleans are supported by this reporter.", gauge, value.getClass().getName()); return 0; } }; } private static io.prometheus.client.Gauge.Child gaugeFrom(Counter counter) { return new io.prometheus.client.Gauge.Child() { @Override public double get() { return (double) counter.getCount(); } }; } private static io.prometheus.client.Gauge.Child gaugeFrom(Meter meter) { return new io.prometheus.client.Gauge.Child() { @Override public double get() { return meter.getRate(); } }; } @VisibleForTesting static class HistogramSummaryProxy extends Collector { static final List QUANTILES = Arrays.asList(.5, .75, .95, .98, .99, .999); private final String metricName; private final String helpString; private final List labelNamesWithQuantile; private final Map, Histogram> histogramsByLabelValues = new HashMap<>(); HistogramSummaryProxy(final Histogram histogram, final String metricName, final String helpString, final List labelNames, final List labelValues) { this.metricName = metricName; this.helpString = helpString; this.labelNamesWithQuantile = addToList(labelNames, "quantile"); histogramsByLabelValues.put(labelValues, histogram); } @Override public List collect() { // We cannot use SummaryMetricFamily because it is impossible to get a sum of all values (at least for Dropwizard histograms, // whose snapshot's values array only holds a sample of recent values). List samples = new LinkedList<>(); for (Map.Entry, Histogram> labelValuesToHistogram : histogramsByLabelValues.entrySet()) { addSamples(labelValuesToHistogram.getKey(), labelValuesToHistogram.getValue(), samples); } return Collections.singletonList(new MetricFamilySamples(metricName, Type.SUMMARY, helpString, samples)); } void addChild(final Histogram histogram, final List labelValues) { histogramsByLabelValues.put(labelValues, histogram); } void remove(final List labelValues) { histogramsByLabelValues.remove(labelValues); } private void addSamples(final List labelValues, final Histogram histogram, final List samples) { samples.add(new MetricFamilySamples.Sample(metricName + "_count", labelNamesWithQuantile.subList(0, labelNamesWithQuantile.size() - 1), labelValues, histogram.getCount())); final HistogramStatistics statistics = histogram.getStatistics(); for (final Double quantile : QUANTILES) { samples.add(new MetricFamilySamples.Sample(metricName, labelNamesWithQuantile, addToList(labelValues, quantile.toString()), statistics.getQuantile(quantile))); } } } private static List addToList(List list, String element) { final List result = new ArrayList<>(list); result.add(element); return result; } private static String[] toArray(List list) { return list.toArray(new String[list.size()]); } } ================================================ FILE: flink-learning-extends/flink-metrics/flink-metrics-prometheus/src/main/java/org/apache/flink/metrics/prometheus/ClusterMode.java ================================================ package org.apache.flink.metrics.prometheus; /** * Desc: * Created by zhisheng on 2020-12-08 11:08 */ public enum ClusterMode { /** * yarn mode. */ YARN, /** * K8s mode. */ K8S } ================================================ FILE: flink-learning-extends/flink-metrics/flink-metrics-prometheus/src/main/java/org/apache/flink/metrics/prometheus/PrometheusPushGatewayReporter.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.flink.metrics.prometheus; import io.prometheus.client.Collector; import io.prometheus.client.CollectorRegistry; import io.prometheus.client.exporter.PushGateway; import org.apache.flink.annotation.PublicEvolving; import org.apache.flink.metrics.Metric; import org.apache.flink.metrics.MetricConfig; import org.apache.flink.metrics.MetricGroup; import org.apache.flink.metrics.reporter.InstantiateViaFactory; import org.apache.flink.metrics.reporter.MetricReporter; import org.apache.flink.metrics.reporter.Scheduled; import org.apache.flink.util.AbstractID; import org.apache.flink.util.StringUtils; import java.io.File; import java.io.IOException; import java.util.*; import static org.apache.flink.metrics.prometheus.PrometheusPushGatewayReporterOptions.*; /** * {@link MetricReporter} that exports {@link Metric Metrics} via Prometheus {@link PushGateway}. */ @PublicEvolving @InstantiateViaFactory(factoryClassName = "org.apache.flink.metrics.prometheus.PrometheusPushGatewayReporterFactory") public class PrometheusPushGatewayReporter extends AbstractPrometheusReporter implements Scheduled { private PushGateway pushGateway; private String jobName; private boolean deleteOnShutdown; private Map groupingKey; private String appId; private String taskName; private String taskId; @Override public void open(MetricConfig config) { super.open(config); String host = config.getString(HOST.key(), HOST.defaultValue()); int port = config.getInteger(PORT.key(), PORT.defaultValue()); String clusterMode = config.getString(CLUSTER_MODE.key(), CLUSTER_MODE.defaultValue()); String configuredJobName = config.getString(JOB_NAME.key(), JOB_NAME.defaultValue()); boolean randomSuffix = config.getBoolean(RANDOM_JOB_NAME_SUFFIX.key(), RANDOM_JOB_NAME_SUFFIX.defaultValue()); deleteOnShutdown = config.getBoolean(DELETE_ON_SHUTDOWN.key(), DELETE_ON_SHUTDOWN.defaultValue()); groupingKey = parseGroupingKey(config.getString(GROUPING_KEY.key(), GROUPING_KEY.defaultValue())); if (host == null || host.isEmpty() || port < 1) { throw new IllegalArgumentException( "Invalid host/port configuration. Host: " + host + " Port: " + port); } Properties properties = System.getProperties(); taskName = properties.getProperty("taskName", null); taskId = properties.getProperty("taskId", null); String jobNamePrefix = ""; if (!StringUtils.isNullOrWhitespaceOnly(clusterMode) && clusterMode .toUpperCase() .equals(ClusterMode.YARN.name())) { //yarn cluster appId = System.getenv("_APP_ID"); if (!StringUtils.isNullOrWhitespaceOnly(appId)) { jobNamePrefix = appId + "_jobmanager"; } else { String pwd = System.getenv("PWD"); String[] values = pwd.split(File.separator); String containerId = ""; if (values.length >= 2) { appId = values[values.length - 2]; containerId = values[values.length - 1]; } jobNamePrefix = appId + "_taskmanager_" + containerId; } } else if (!StringUtils.isNullOrWhitespaceOnly(clusterMode) && clusterMode.toUpperCase().equals(ClusterMode.K8S.name())) { //K8s cluster Map envs = System.getenv(); appId = envs.get("CLUSTER_ID"); if ("k8s".equalsIgnoreCase(host)) { // 每台 node 上的 pod 监控指标只发往该 node 上部署的 pushgateway host = envs.get("_HOST_IP_ADDRESS"); log.info("the pod is on K8s cluster, the host ip is {}", host); } if (appId != null) { String hostname = envs.get("HOSTNAME"); if (hostname.contains("taskmanager")) { //taskmanager jobNamePrefix = appId + "_taskmanager_" + hostname; } else { //jobmanager jobNamePrefix = appId + "_jobmanager"; } } } else { jobNamePrefix = configuredJobName; } if (randomSuffix) { this.jobName = jobNamePrefix + "_" + new AbstractID(); } else { this.jobName = jobNamePrefix; } pushGateway = new PushGateway(host + ':' + port); log.info("Configured PrometheusPushGatewayReporter with {host:{}, port:{}, jobName:{}, randomJobNameSuffix:{}, deleteOnShutdown:{}, groupingKey:{}}", host, port, jobName, randomSuffix, deleteOnShutdown, groupingKey); } Map parseGroupingKey(final String groupingKeyConfig) { if (!groupingKeyConfig.isEmpty()) { Map groupingKey = new HashMap<>(); String[] kvs = groupingKeyConfig.split(";"); for (String kv : kvs) { int idx = kv.indexOf("="); if (idx < 0) { log.warn("Invalid prometheusPushGateway groupingKey:{}, will be ignored", kv); continue; } String labelKey = kv.substring(0, idx); String labelValue = kv.substring(idx + 1); if (StringUtils.isNullOrWhitespaceOnly(labelKey) || StringUtils.isNullOrWhitespaceOnly(labelValue)) { log.warn( "Invalid groupingKey {labelKey:{}, labelValue:{}} must not be empty", labelKey, labelValue); continue; } groupingKey.put(labelKey, labelValue); } return groupingKey; } return Collections.emptyMap(); } @Override public void notifyOfAddedMetric(Metric metric, String metricName, MetricGroup group) { List dimensionKeys = new LinkedList<>(); List dimensionValues = new LinkedList<>(); Map allVariables = group.getAllVariables(); //给每条监控数据增加标签,yarn 为作业的 application id,k8s 则为 cluster id,另外增加实时平台的 task id 和 task name if (appId != null) { allVariables.put("", appId); } if (taskName != null) { allVariables.put("", taskName); } if (taskId != null) { allVariables.put("", taskId); } for (final Map.Entry dimension : allVariables.entrySet()) { final String key = dimension.getKey(); dimensionKeys.add(CHARACTER_FILTER.filterCharacters(key.substring(1, key.length() - 1))); dimensionValues.add(labelValueCharactersFilter.filterCharacters(dimension.getValue())); } final String scopedMetricName = getScopedName(metricName, group); final String helpString = metricName + " (scope: " + getLogicalScope(group) + ")"; final Collector collector; Integer count = 0; synchronized (this) { if (collectorsWithCountByMetricName.containsKey(scopedMetricName)) { final AbstractMap.SimpleImmutableEntry collectorWithCount = collectorsWithCountByMetricName.get(scopedMetricName); collector = collectorWithCount.getKey(); count = collectorWithCount.getValue(); } else { collector = createCollector(metric, dimensionKeys, dimensionValues, scopedMetricName, helpString); try { collector.register(); } catch (Exception e) { log.warn("There was a problem registering metric {}.", metricName, e); } } addMetric(metric, dimensionValues, collector); collectorsWithCountByMetricName.put(scopedMetricName, new AbstractMap.SimpleImmutableEntry<>(collector, count + 1)); } } @Override public void report() { try { pushGateway.push(CollectorRegistry.defaultRegistry, jobName, groupingKey); } catch (Exception e) { // log.warn("Failed to push metrics to PushGateway with jobName {}, groupingKey {}.", jobName, groupingKey, e); } } @Override public void close() { if (deleteOnShutdown && pushGateway != null) { try { pushGateway.delete(jobName, groupingKey); } catch (IOException e) { log.warn("Failed to delete metrics from PushGateway with jobName {}, groupingKey {}.", jobName, groupingKey, e); } } super.close(); } } ================================================ FILE: flink-learning-extends/flink-metrics/flink-metrics-prometheus/src/main/java/org/apache/flink/metrics/prometheus/PrometheusPushGatewayReporterFactory.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.flink.metrics.prometheus; import org.apache.flink.metrics.reporter.InterceptInstantiationViaReflection; import org.apache.flink.metrics.reporter.MetricReporterFactory; import java.util.Properties; /** * {@link MetricReporterFactory} for {@link PrometheusPushGatewayReporter}. */ @InterceptInstantiationViaReflection(reporterClassName = "org.apache.flink.metrics.prometheus.PrometheusPushGatewayReporter") public class PrometheusPushGatewayReporterFactory implements MetricReporterFactory { @Override public PrometheusPushGatewayReporter createMetricReporter(Properties properties) { return new PrometheusPushGatewayReporter(); } } ================================================ FILE: flink-learning-extends/flink-metrics/flink-metrics-prometheus/src/main/java/org/apache/flink/metrics/prometheus/PrometheusPushGatewayReporterOptions.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.flink.metrics.prometheus; import org.apache.flink.annotation.docs.Documentation; import org.apache.flink.configuration.ConfigOption; import org.apache.flink.configuration.ConfigOptions; import org.apache.flink.configuration.description.Description; import org.apache.flink.configuration.description.LinkElement; import org.apache.flink.configuration.description.TextElement; /** * Config options for the {@link PrometheusPushGatewayReporter}. */ @Documentation.SuffixOption(value = "metrics.reporter..") public class PrometheusPushGatewayReporterOptions { public static final ConfigOption HOST = ConfigOptions .key("host") .noDefaultValue() .withDescription("The PushGateway server host."); public static final ConfigOption PORT = ConfigOptions .key("port") .defaultValue(-1) .withDescription("The PushGateway server port."); public static final ConfigOption JOB_NAME = ConfigOptions .key("jobName") .defaultValue("") .withDescription("The job name under which metrics will be pushed"); public static final ConfigOption CLUSTER_MODE = ConfigOptions .key("clusterMode") .defaultValue("") .withDescription("job deploy mode used to identify job metric name"); public static final ConfigOption RANDOM_JOB_NAME_SUFFIX = ConfigOptions .key("randomJobNameSuffix") .defaultValue(true) .withDescription("Specifies whether a random suffix should be appended to the job name."); public static final ConfigOption DELETE_ON_SHUTDOWN = ConfigOptions .key("deleteOnShutdown") .defaultValue(true) .withDescription("Specifies whether to delete metrics from the PushGateway on shutdown."); public static final ConfigOption FILTER_LABEL_VALUE_CHARACTER = ConfigOptions .key("filterLabelValueCharacters") .defaultValue(true) .withDescription(Description.builder() .text("Specifies whether to filter label value characters." + " If enabled, all characters not matching [a-zA-Z0-9:_] will be removed," + " otherwise no characters will be removed." + " Before disabling this option please ensure that your" + " label values meet the %s.", LinkElement.link("https://prometheus.io/docs/concepts/data_model/#metric-names-and-labels", "Prometheus requirements")) .build()); public static final ConfigOption GROUPING_KEY = ConfigOptions .key("groupingKey") .defaultValue("") .withDescription(Description.builder() .text("Specifies the grouping key which is the group and global labels of all metrics." + " The label name and value are separated by '=', and labels are separated by ';', e.g., %s." + " Please ensure that your grouping key meets the %s.", TextElement.code("k1=v1;k2=v2"), LinkElement.link("https://prometheus.io/docs/concepts/data_model/#metric-names-and-labels", "Prometheus requirements")) .build()); } ================================================ FILE: flink-learning-extends/flink-metrics/flink-metrics-prometheus/src/main/java/org/apache/flink/metrics/prometheus/PrometheusReporter.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.flink.metrics.prometheus; import io.prometheus.client.exporter.HTTPServer; import org.apache.flink.annotation.PublicEvolving; import org.apache.flink.annotation.VisibleForTesting; import org.apache.flink.metrics.Metric; import org.apache.flink.metrics.MetricConfig; import org.apache.flink.metrics.reporter.InstantiateViaFactory; import org.apache.flink.metrics.reporter.MetricReporter; import org.apache.flink.util.NetUtils; import org.apache.flink.util.Preconditions; import java.io.IOException; import java.util.Iterator; /** * {@link MetricReporter} that exports {@link Metric Metrics} via Prometheus. */ @PublicEvolving @InstantiateViaFactory(factoryClassName = "org.apache.flink.metrics.prometheus.PrometheusReporterFactory") public class PrometheusReporter extends AbstractPrometheusReporter { static final String ARG_PORT = "port"; private static final String DEFAULT_PORT = "9249"; private HTTPServer httpServer; private int port; @VisibleForTesting int getPort() { Preconditions.checkState(httpServer != null, "Server has not been initialized."); return port; } @Override public void open(MetricConfig config) { super.open(config); String portsConfig = config.getString(ARG_PORT, DEFAULT_PORT); Iterator ports = NetUtils.getPortRangeFromString(portsConfig); while (ports.hasNext()) { int port = ports.next(); try { // internally accesses CollectorRegistry.defaultRegistry httpServer = new HTTPServer(port); this.port = port; log.info("Started PrometheusReporter HTTP server on port {}.", port); break; } catch (IOException ioe) { //assume port conflict log.debug("Could not start PrometheusReporter HTTP server on port {}.", port, ioe); } } if (httpServer == null) { throw new RuntimeException("Could not start PrometheusReporter HTTP server on any configured port. Ports: " + portsConfig); } } @Override public void close() { if (httpServer != null) { httpServer.stop(); } super.close(); } } ================================================ FILE: flink-learning-extends/flink-metrics/flink-metrics-prometheus/src/main/java/org/apache/flink/metrics/prometheus/PrometheusReporterFactory.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.flink.metrics.prometheus; import org.apache.flink.metrics.reporter.InterceptInstantiationViaReflection; import org.apache.flink.metrics.reporter.MetricReporterFactory; import java.util.Properties; /** * {@link MetricReporterFactory} for {@link PrometheusReporter}. */ @InterceptInstantiationViaReflection(reporterClassName = "org.apache.flink.metrics.prometheus.PrometheusReporter") public class PrometheusReporterFactory implements MetricReporterFactory { @Override public PrometheusReporter createMetricReporter(Properties properties) { return new PrometheusReporter(); } } ================================================ FILE: flink-learning-extends/flink-metrics/flink-metrics-prometheus/src/main/resources/META-INF/NOTICE ================================================ flink-metrics-prometheus Copyright 2014-2020 The Apache Software Foundation This product includes software developed at The Apache Software Foundation (http://www.apache.org/). This project bundles the following dependencies under the Apache Software License 2.0. (http://www.apache.org/licenses/LICENSE-2.0.txt) - io.prometheus:simpleclient:0.8.1 - io.prometheus:simpleclient_common:0.8.1 - io.prometheus:simpleclient_httpserver:0.8.1 - io.prometheus:simpleclient_pushgateway:0.8.1 ================================================ FILE: flink-learning-extends/flink-metrics/flink-metrics-prometheus/src/main/resources/META-INF/services/org.apache.flink.metrics.reporter.MetricReporterFactory ================================================ # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. org.apache.flink.metrics.prometheus.PrometheusReporterFactory org.apache.flink.metrics.prometheus.PrometheusPushGatewayReporterFactory ================================================ FILE: flink-learning-extends/flink-metrics/flink-metrics-prometheus/src/test/resources/log4j2-test.properties ================================================ ################################################################################ # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. ################################################################################ # Set root logger level to OFF to not flood build logs # set manually to INFO for debugging purposes rootLogger.level = OFF rootLogger.appenderRef.test.ref = TestLogger appender.testlogger.name = TestLogger appender.testlogger.type = CONSOLE appender.testlogger.target = SYSTEM_ERR appender.testlogger.layout.type = PatternLayout appender.testlogger.layout.pattern = %-4r [%t] %-5p %c %x - %m%n ================================================ FILE: flink-learning-extends/flink-metrics/pom.xml ================================================ flink-learning-extends com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 pom flink-metrics flink-metrics-prometheus flink-metrics-kafka ================================================ FILE: flink-learning-extends/pom.xml ================================================ flink-learning com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 flink-learning-extends pom flink-metrics FlinkLogKafkaAppender ================================================ FILE: flink-learning-k8s/README.md ================================================ ### flink-learning-k8s 1、自定义构建 Flink 镜像 ```shell ./build_flink_docker_images.sh flink $imageTag eg: ./build_flink_docker_images.sh flink 1.12.0-jar-pro-20220727 ``` 2、Flink 任务提交到 K8s 集群,不同的运行模式: + Session mode + Native Application mode + Flink K8s Operator + Standalone mode 3、Ingress ```shell ./build_ingress.sh $cluster.id $namespace eg: ./build_ingress.sh statemachine-test1 namespace-flink ``` ================================================ FILE: flink-learning-k8s/blogs/Flink HA 配置.md ================================================ ## 作业 HA ### ZK ```shell ./bin/flink run-application -p 1 -t kubernetes-application \ -Dkubernetes.cluster-id=state-machine-cluster \ -Dtaskmanager.memory.process.size=1024m \ -Dkubernetes.taskmanager.cpu=0.5 \ -Dtaskmanager.numberOfTaskSlots=1 \ -Dkubernetes.container.image=harbor.xxx.cn/flink/statemachine:v0.0.6 \ -Dkubernetes.namespace=hke-flink \ -Dkubernetes.jobmanager.service-account=flink \ -Dkubernetes.container.image.pull-secrets=docker-registry-test \ -Dkubernetes.jobmanager.node-selector=kubernetes.io/role:flink-node \ -Dkubernetes.taskmanager.node-selector=kubernetes.io/role:flink-node \ -Dkubernetes.rest-service.exposed.type=NodePort \ -Dhigh-availability=zookeeper \ -Dhigh-availability.storageDir=hdfs:///flink/ha/k8s \ local:///opt/flink/usrlib/StateMachineExample.jar ``` 使用高可用后作业的 job id 变成了 00000000000000000000000000000000 ![](https://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/2022-12-12-114349.jpg) 在 ZK 生成的文件目录为下: ![](https://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/2022-12-12-114408.jpg) 在 HDFS 生成的文件目录为下: ![](https://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/2022-12-12-114426.jpg) 因为 flink checkpoint 的状态生成路径规则是: ```shell hdfs:/flink/checkpoints/{jobid}/chk-xxx ``` 那么 application mode 下所有作业的 id 都是 00000000000000000000000000000000,那么这些相同的作业 id 会将 checkpoint 数据都放在同一个路径下,这样会让作业状态文件看起来很混乱。 ```shell hdfs:/flink/checkpoints/00000000000000000000000000000000/chk-84225 ``` ![](https://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/2022-12-12-114516.jpg) **解决办法**: 平台提交作业的时候为每个作业单独设置一个 checkpoint 路径当作启动参数,规则如下: ```shell -Dstate.checkpoints.dir=hdfs:///flink/checkpoints/{kubernetes.cluster-id} ``` 这样每个作业的 checkpoint 路径可以保持独立的 ![](https://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/2022-12-12-114606.jpg) ## Kubernetes https://flink.apache.org/2021/02/10/native-k8s-with-ha.html https://cwiki.apache.org/confluence/display/FLINK/FLIP-144%3A+Native+Kubernetes+HA+for+Flink ![](https://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/2022-12-12-114723.jpg) ```shell ./bin/flink run-application -p 1 -t kubernetes-application \ -Dkubernetes.cluster-id=state-machine-cluster-test14 \ -Dtaskmanager.memory.process.size=1024m \ -Dkubernetes.taskmanager.cpu=0.5 \ -Dtaskmanager.numberOfTaskSlots=1 \ -Dkubernetes.container.image=harbor.xxx.cn/flink/statemachine:v0.0.6 \ -Dkubernetes.namespace=hke-flink \ -Dkubernetes.jobmanager.service-account=flink \ -Dkubernetes.container.image.pull-secrets=docker-registry-test \ -Dkubernetes.jobmanager.node-selector=kubernetes.io/role:flink-node \ -Dkubernetes.taskmanager.node-selector=kubernetes.io/role:flink-node \ -Dkubernetes.rest-service.exposed.type=NodePort \ -Dhigh-availability=org.apache.flink.kubernetes.highavailability.KubernetesHaServicesFactory \ -Dhigh-availability.storageDir=hdfs:///flink/ha/k8s \ -Dstate.checkpoints.dir=hdfs:///flink/checkpoints/state-machine-cluster-test14 \ local:///opt/flink/usrlib/StateMachineExample.jar ``` ================================================ FILE: flink-learning-k8s/blogs/Flink K8s Pod 增加环境变量.md ================================================ ### 问题 为啥要增加环境变量?---> 默认已有的环境变量不能满足需求,我们会从作业的 env 中获取到作业的 POD IP/物理机器 IP/Cluster.id/实时平台注入的 taskid/taskname,把这些 label 打进收集到的 metrics 和 log 里面,方便后期查询和告警。 ![](https://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/2022-12-12-115936.jpg) ![](https://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/2022-12-12-115951.jpg) ### 解决办法 注入需要的 env 到 pod 里面去,代码如下: JM ```java .addNewEnv() .withName(ENV_FLINK_HOST_IP_ADDRESS) .withValueFrom(new EnvVarSourceBuilder() .withNewFieldRef(API_VERSION, HOST_IP_FIELD_PATH) .build()) .endEnv() .addNewEnv() .withName("CLUSTER_ID") .withValue(kubernetesJobManagerParameters.getClusterId()) .endEnv() ``` ![](https://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/2022-12-12-120030.jpg) TM ```java .addNewEnv() .withName(ENV_FLINK_HOST_IP_ADDRESS) .withValueFrom(new EnvVarSourceBuilder() .withNewFieldRef(API_VERSION, HOST_IP_FIELD_PATH) .build()) .endEnv() .addNewEnv() .withName(ENV_FLINK_POD_IP_ADDRESS) .withValueFrom(new EnvVarSourceBuilder() .withNewFieldRef(API_VERSION, POD_IP_FIELD_PATH) .build()) .endEnv() .addNewEnv() .withName("CLUSTER_ID") .withValue(kubernetesTaskManagerParameters.getClusterId()) .endEnv() ``` ![](https://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/2022-12-12-120058.jpg) ### 最终结果 可以看到加到 env 里面的环境变量已经 OK 了 ![](https://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/2022-12-12-120127.jpg) ================================================ FILE: flink-learning-k8s/blogs/Kubernetes 入门之知识点梳理.md ================================================ 本篇文章是第一篇,从介绍 k8s 知识点和常见名词开始。 ## Master k8s 中作为大脑存在的是 Master 节点,是集群的控制节点,负责整个集群的管理和控制。所有的其他 Node 都会向 Master 注册自己,并定期上报自身的所有信息。在 Master 节点上运行着下面 4 种进程: - Api Server :提供 HTTP Rest 接口的服务进程;所有资源的增删改查操作的唯一入口;集群控制的入口, kubectl 就是直接对 Api Server 负责; - Controller Manager :所有资源对象的自动化控制中心; - Scheduler :负责资源的调度,主要将 Pod 调度到指定的 Node 上; - etcd Server :所有资源对象的数据保存在 etcd 中。 ## Node 除了 Master 节点外,其他的节点都称为 Node,即工作节点,且接受 Master 的控制。Node 超过指定时间不上报信息时,会被 Master 判定为失联,则该 Node 的状态被标记为不可用,随后 Master 会触发"工作负载大转移"的自动流程。而 Node 上运行着下面 3 种主要的进程: - kubelet :负责 Pod 对应的容器的创建、启停等任务;与 Master 节点密切合作,实现集群管理的基本面功能,Node 向 Master 上报信息,就是通过 kubelet 实现的; - kube-proxy :实现 Service 的通信与负载均衡机制; - Docker Engine :负责本机容器的创建与管理。 ## Namespace 用于实现多租户的资源隔离。通过将集群内部的资源对象分配到不同的 Namespace 中。创建资源对象时可以指定属于哪个 Namespace。 ## Pod Pod 是 k8s 的最小调度单元。 ![](https://tva1.sinaimg.cn/large/008vxvgGly1h919917hccj30dp0bgmxa.jpg) 如上图所示,而 Pod 是由一个个容器组成的,称为容器组。而组成 Pod 的容器分为 Pause 容器和一个个业务容器。其中以 Pause 容器的状态代表整个 Pod 的状态,由于 Pause 容器不易死亡,这样就能保证对 Pod 这个整体的状态的判断;而所有的业务容器共享 Pause 的 IP 和 Volume,这样就解决了联系紧密的业务容器之间的通信和共享资源的问题。Pod 在哪个 Node 上工作是由 kubelet 调度的。 Pod 拥有唯一 IP ,k8s 以 Endpoint (pod_ip + containerPort) 作为 Pod 中一个服务进程的对外通信地址。而任意两个 Pod 之间直接 TCP/IP 通信,采用虚拟二层网络技术实现,如 Flannel、Openvswitch。而 Pod 的 Endpoint 与 Pod 同生命周期,当 Pod 被销毁,对应的 Endpoint 也随之被销毁。 Pod 有两种类型: - 普通 Pod ,存放在 etcd 中,被调度到 Node 中进行绑定,调度后被 Node 中的 kubelet 实例化成一组容器并启动; - 静态 Pod ,存放在某个具体的 Node 上的一个具体文件中,只在此 Node 中启动运行。 ## Volume 定义在 Pod 上,被一个 Pod 里的多个容器挂载到具体文件目录下。需要注意的是 Volume 与 Pod 的生命周期相同。 作用:Pod 中多个容器共享文件;让容器的数据写到宿主机的磁盘上;写文件到网络存储中;容器配置文件集中化定义与管理。 ### 类型 - emptyDir:Pod 分配到 Node 上时创建,无需指定宿主机上的目录文件。Pod 被移除时,emptyDir 上的数据被永久删除。 - hostPath:在 Pod 上挂载宿主机上的文件或目录。在不同 Node 上具有相同配置的 Pod 可能会因为宿主机上的目录和文件不同而导致对 Volume 上目录和文件的访问结果不一致;若使用了资源配额管理, k8s 无法将 hostPath 在宿主机上使用的资源纳入管理。 - 用途:容器生成的日志文件需要永久保存;需要访问宿主机上 Docker 引擎,将 hostPath 定义为宿主机 /var/lib/docker 目录。 - 其他:如 gcePersistentDisk 、 awsElasticBlockStore 等,都是由特定的云服务提供的永久磁盘。Pod 结束时不会被删除,只会被卸载。使用时需要按照要求安装特定虚拟机和永久磁盘。 ## Deployment 用于更好地解决 Pod 的编排问题,其内部使用 ReplicaSet 来实现目的。 使用场景: - 生成 RS 并完成 Pod 副本的创建过程; - 检查部署动作是否完成; - 更新 Deployment 以创建新的 Pod; - 回滚; - 挂起或恢复。 Pod 数量的描述 - DESIRED:Pod 副本数量的期望值 - CURRENT:当前的副本数 - UP_TO_DATE:最新版本的 Pod 副本数 - AVAILABLE:当前集群中可用 Pod 副本数 ## Label 定义形式:key=value。我们主要使用 Label Selector 来查询和筛选某些 Label 的资源对象。 使用场景: - kube-controller 筛选要监控的 Pod - kube-proxy 进程建立 Service 对 Pod 的请求转发路由表 - kube-scheduler 进程实现 Pod 定向调度 ## Annotation 定义形式:key=value 与 Label 的区别: - Label 有严格命名规则 - Label 定义的是 metadata ,且用于 Label Selector;Annotation 是用户任意定义的附加信息 使用场景: - build 信息、 release 信息、 Docker 镜像信息等 - 日志库、监控库、分析库等资源库的地址信息 - 程序调试工具信息 - 团队的联系信息 ## Replica Set Replica Set(RS) 是 Replication Controller(RC) 的升级版本。两者的唯一区别是对选择器的支持。ReplicaSet 支持 labels user guide 中描述的 set-based 选择器要求, 而 Replication Controller 仅支持 equality-based 的选择器要求。 我们一般用 Deployment 来定义 RS,很少直接创建 RS,从而形成一套完整的 Pod 的创建、删除、更新的编排机制。 RS 中可以定义的是:Pod 期待的副本数(Replicas);用于筛选目标 Pod 的 Label Selector;当 Pod 副本数小于预期数量时,用于创建新 Pod 的模板。 Master 的 Controller Manager 定期巡检系统中当前存活的目标 Pod,确保目标 Pod 实例数等于期望值。删除 RS 不会影响 Pod ,支持基于集合的 Label Selector;通过改变 RS 中 Pod 副本数量,实现 Pod 扩容和缩容;通过改变 RS 中 Pod 模板中的镜像版本,实现 Pod 的滚动升级。 ## Service 定义:微服务架构中的微服务。 ![](https://tva1.sinaimg.cn/large/008vxvgGly1h9199olemuj30ij0l9wf3.jpg) Service 定义了一个服务的访问入口地址,客户端通过该入口地址访问背后的集群实例。Service 通过 Label Selector 与后端 Pod 副本集群之间实现对接。Service 之间通过 TCP/IP 通信。 ### 负载均衡 kube-proxy 进程是一个智能的负载均衡器,负责将对 Service 的请求转发到后端的 Pod。Pod 的所有副本为一组,提供一个对外的服务端口,将这些 Pod 的 Endpoint 列表加入该端口的转发列表。客户端通过负载均衡的对外 IP + 服务端口来访问此服务。 ### ClusterIP Service 拥有全局唯一虚拟 IP,称为 ClusterIP,每个 Service 变成了具备全局唯一 IP 的通信节点。与 Pod 不同的是,Pod 的 Endpoint 会随 Pod 的销毁而发生改变,但 ClusterIP 在 Service 的生命周期中不会发生改变。并且只要用 Service 的 Name 与 Service 的 ClusterIP 做一个 DNS 域名映射,即可实现服务发现。 Service 中一般会定义一个 targetPort,即提供该服务的容器暴露的端口,具体业务进程在容器内的 targetPort 上提供 TCP/IP 接入;而 Service 的 port 属性定义了 Service 的虚接口。 ### 服务发现 before:每个 Service 生成一些对应的 Linux 环境变量,Pod 容器启动时自动注入。 now:通过 Add-On 增值包的方式引入 DNS 系统,将服务名作为 DNS 域名即可实现。 ### 外部系统访问 Service k8s 中有三种类型的 IP: - Node IP集群中每个节点的物理网卡的 IP 地址;所有属于这个网络的服务器之间都通过这个网络直接通信;集群之外的节点访问该集群时,必须通过 Node IP 通信。 - Pod IPDocker Engine 根据 docker0 网桥的 IP 地址段进行分配的;虚拟的二层网络;不同 Pod 的容器之间互相访问时,通过 Pod IP 所在的虚拟二层网络进行通信。 - Cluster IP - 仅仅作用于 Service :由 kuber 管理和分配 IP 地址; - 无法被 ping:因为没有一个实体网络对象来响应; - Cluster IP 只能结合 Service Port 组成一个具体的通信端口:单独的 Cluster IP 不具备 TCP/IP 通信基础;属于 kuber 集群的封闭空间;集群外的节点若需要访问,需要一些额外的操作; - Node IP 网、Pod IP 网和 Cluster IP 网之间的通信是 kuber 自制的一种编程方式的路由规则; k8s 中实现外部系统访问 Service 的方法,主要是通过 NodePort,其实现方式是在每个 Node 上为需要提供外部访问的 Service 开启一个对应的 TCP 监听端口。此时,外部系统只要用任意一个 Node 的 IP + NodePort 即可访问此服务。 ================================================ FILE: flink-learning-k8s/blogs/Pod 异常问题排查.md ================================================ ![](https://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/2022-12-12-115151.jpg) ![](https://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/2022-12-12-115208.jpg) ### OOM 在 K8s 集群下很常见的异常就是因内存使用超过配置的 limit 值而触发 OOMKilled 异常 ```shell 2022-05-19 17:46:40,102 WARN akka.remote.ReliableDeliverySupervisor [] - Association with remote system [akka.tcp://flink@10.73.131.241:6122] has failed, address is now gated for [50] ms. Reason: [Association failed with [akka.tcp://flink@10.73.131.241:6122]] Caused by: [java.net.ConnectException: Connection refused: /10.73.131.241:6122] 2022-05-19 17:46:40,261 WARN org.apache.flink.runtime.resourcemanager.active.ActiveResourceManager [] - Worker flink-4760-1652949469041-taskmanager-1-3 is terminated. Diagnostics: Pod terminated, container termination statuses: [flink-task-manager(exitCode=137, reason=OOMKilled, message=null)], pod status: Failed(reason=null, message=null) 2022-05-19 17:46:40,262 WARN org.apache.flink.runtime.resourcemanager.active.ActiveResourceManager [] - Closing TaskExecutor connection flink-4760-1652949469041-taskmanager-1-3 because: Pod terminated, container termination statuses: [flink-task-manager(exitCode=137, reason=OOMKilled, message=null)], pod status: Failed(reason=null, message=null) ``` ![](https://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/2022-12-12-115252.jpg) ================================================ FILE: flink-learning-k8s/blogs/合理设置 Request 与 Limit.md ================================================ 如何为容器配置 Request 与 Limit? 这是一个即常见又棘手的问题,这个根据服务类型,需求与场景的不同而不同,没有固定的答案,这里结合生产经验总结了一些最佳实践,可以作为参考。 ## 所有容器都应该设置 request request 的值并不是指给容器实际分配的资源大小,它仅仅是给调度器看的,调度器会 "观察" 每个节点可以用于分配的资源有多少,也知道每个节点已经被分配了多少资源。被分配资源的大小就是节点上所有 Pod 中定义的容器 request 之和,它可以计算出节点剩余多少资源可以被分配(可分配资源减去已分配的 request 之和)。如果发现节点剩余可分配资源大小比当前要被调度的 Pod 的 reuqest 还小,那么就不会考虑调度到这个节点,反之,才可能调度。所以,如果不配置 request,那么调度器就不能知道节点大概被分配了多少资源出去,调度器得不到准确信息,也就无法做出合理的调度决策,很容易造成调度不合理,有些节点可能很闲,而有些节点可能很忙,甚至 NotReady。 所以,建议是给所有容器都设置 request,让调度器感知节点有多少资源被分配了,以便做出合理的调度决策,让集群节点的资源能够被合理的分配使用,避免陷入资源分配不均导致一些意外发生。 ## CPU request 与 limit 的一般性建议 + 如果不确定应用最佳的 CPU 限制,可以不设置 CPU limit,参考: Understanding resource limits in kubernetes: cpu time。 + 如果要设置 CPU request,大多可以设置到不大于 1 核,除非是 CPU 密集型应用。 ## 重要的线上应用改如何设置 节点资源不足时,会触发自动驱逐,将一些低优先级的 Pod 删除掉以释放资源让节点自愈。没有设置 request,limit 的 Pod 优先级最低,容易被驱逐;request 不等于 limit 的其次; request 等于 limit 的 Pod 优先级较高,不容易被驱逐。所以如果是重要的线上应用,不希望在节点故障时被驱逐导致线上业务受影响,就建议将 request 和 limit 设成一致。 ## 怎样设置才能提高资源利用率? 如果给给你的应用设置较高的 request 值,而实际占用资源长期远小于它的 request 值,导致节点整体的资源利用率较低。当然这对时延非常敏感的业务除外,因为敏感的业务本身不期望节点利用率过高,影响网络包收发速度。所以对一些非核心,并且资源不长期占用的应用,可以适当减少 request 以提高资源利用率。 如果你的服务支持水平扩容,单副本的 request 值一般可以设置到不大于 1 核,CPU 密集型应用除外。比如 coredns,设置到 0.1 核就可以,即 100m。 ## 尽量避免使用过大的 request 与 limit 如果你的服务使用单副本或者少量副本,给很大的 request 与 limit,让它分配到足够多的资源来支撑业务,那么某个副本故障对业务带来的影响可能就比较大,并且由于 request 较大,当集群内资源分配比较碎片化,如果这个 Pod 所在节点挂了,其它节点又没有一个有足够的剩余可分配资源能够满足这个 Pod 的 request 时,这个 Pod 就无法实现漂移,也就不能自愈,加重对业务的影响。 相反,建议尽量减小 request 与 limit,通过增加副本的方式来对你的服务支撑能力进行水平扩容,让你的系统更加灵活可靠。 ## 避免测试 namespace 消耗过多资源影响生产业务 若生产集群有用于测试的 namespace,如果不加以限制,可能导致集群负载过高,从而影响生产业务。可以使用 ResourceQuota 来限制测试 namespace 的 request 与 limit 的总大小。 示例: ================================================ FILE: flink-learning-k8s/dockerfile/Dockerfile-Hadoop-Hive ================================================ # use openjdk for more java tools FROM openjdk:8u181 # Install dependencies RUN set -ex; \ apt-get update; \ apt-get -y install vim net-tools iputils-ping telnet procps libsnappy1v5 gettext-base libjemalloc-dev; \ rm -rf /var/lib/apt/lists/*; \ mkdir -p /opt/flink; \ mkdir -p /opt/hadoop; \ mkdir -p /app/hive; # Grab gosu for easy step-down from root ENV GOSU_VERSION 1.11 RUN set -ex; \ wget -nv -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$(dpkg --print-architecture)"; \ wget -nv -O /usr/local/bin/gosu.asc "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$(dpkg --print-architecture).asc"; \ export GNUPGHOME="$(mktemp -d)"; \ for server in ha.pool.sks-keyservers.net $(shuf -e \ hkp://p80.pool.sks-keyservers.net:80 \ keyserver.ubuntu.com \ hkp://keyserver.ubuntu.com:80 \ pgp.mit.edu) ; do \ gpg --batch --keyserver "$server" --recv-keys B42F6819007F00F88E364FD4036A9C25BF357DD4 && break || : ; \ done && \ gpg --batch --verify /usr/local/bin/gosu.asc /usr/local/bin/gosu; \ gpgconf --kill all; \ rm -rf "$GNUPGHOME" /usr/local/bin/gosu.asc; \ chmod +x /usr/local/bin/gosu; \ gosu nobody true # Install Flink、Hadoop、Hive ENV FLINK_HOME=/opt/flink ENV FLINK_CONF_DIR=$FLINK_HOME/conf ENV HADOOP_HOME=/opt/hadoop ENV HADOOP_CONF_DIR=$HADOOP_HOME/etc/hadoop ENV HIVE_HOME=/app/hive ENV HIVE_CONF_DIR=$HIVE_HOME/conf ENV PATH=$FLINK_HOME/bin:$FLINK_CONF_DIR:$HADOOP_HOME/bin:$HADOOP_CONF_DIR:$HIVE_HOME/bin:$HIVE_CONF_DIR:$PATH WORKDIR $FLINK_HOME COPY hadoop-2.9.2-pro.tar.gz $HADOOP_HOME COPY hive-2.1.1-pro.tar.gz $HIVE_HOME COPY docker-entrypoint.sh / RUN set -ex; \ groupadd --system --gid=9999 flink; \ useradd --system --home-dir $FLINK_HOME --uid=9999 --gid=flink flink; \ tar -xf $HADOOP_HOME/hadoop-2.9.2-pro.tar.gz --strip-components=1 -C $HADOOP_HOME; \ rm $HADOOP_HOME/hadoop-2.9.2-pro.tar.gz;\ tar -xf $HIVE_HOME/hive-2.1.1-pro.tar.gz --strip-components=1 -C $HIVE_HOME; \ rm $HIVE_HOME/hive-2.1.1-pro.tar.gz;\ chown -R flink:flink /opt; \ chmod +x /docker-entrypoint.sh; # Configure container ENTRYPOINT ["/docker-entrypoint.sh"] EXPOSE 6123 8081 CMD ["help"] ================================================ FILE: flink-learning-k8s/dockerfile/Dockerfile-example-statemachine ================================================ FROM harbor.xxx.cn/flink/flink:1.12.0-20210625 RUN mkdir -p $FLINK_HOME/usrlib COPY ./examples/streaming/StateMachineExample.jar $FLINK_HOME/usrlib/ ================================================ FILE: flink-learning-k8s/dockerfile/Dockerfile-flink-1.12.0-jar ================================================ # 先构建好 Hadoop Hive 基础镜像 FROM harbor.xxx.cn/flink/hadoop:2.9.2 # 复制 Flink 1.12 jar 客户端 WORKDIR $FLINK_HOME COPY flink-1.12.0-jar.tar.gz $FLINK_HOME RUN set -ex; \ tar -xf $FLINK_HOME/flink-1.12.0-jar.tar.gz --strip-components=1 -C $FLINK_HOME; \ rm $FLINK_HOME/flink-1.12.0-jar.tar.gz; \ chmod 777 $FLINK_HOME/log; # Configure container ENTRYPOINT ["/docker-entrypoint.sh"] EXPOSE 6123 8081 CMD ["help"] ================================================ FILE: flink-learning-k8s/dockerfile/Dockerfile-flink-1.12.0-sql ================================================ FROM harbor.xxx.cn/flink/hadoop:2.9.2 # 复制 Flink 1.12 SQL 客户端(含 SQL Connector 和 UDF) WORKDIR $FLINK_HOME COPY flink-1.12.0-sql.tar.gz $FLINK_HOME RUN set -ex; \ tar -xf $FLINK_HOME/flink-1.12.0-sql.tar.gz --strip-components=1 -C $FLINK_HOME; \ rm $FLINK_HOME/flink-1.12.0-sql.tar.gz; \ chmod 777 $FLINK_HOME/log; # Configure container ENTRYPOINT ["/docker-entrypoint.sh"] EXPOSE 6123 8081 CMD ["help"] ================================================ FILE: flink-learning-k8s/dockerfile/build_flink_docker_images.sh ================================================ #!/bin/bash [ ! $1 ] && echo "未配置 镜像名" && exit [ ! $2 ] && echo "未配置 镜像版本" && exit docker login -u flink -p xxx harbor.xxx.cn/flink docker build -t harbor.xxx.cn/flink/$1:$2 . docker push harbor.xxx.cn/flink/$1:$2% ================================================ FILE: flink-learning-k8s/dockerfile/build_ingress.sh ================================================ #!/bin/bash [ ! $1 ] && echo "未配置 kubernetes.cluster-id" && exit echo "配置的 kubernetes.cluster-id 为 $1" [ ! $2 ] && echo "未配置 namespace" && exit echo "配置的 namespace 为 $2" cat ./ingress_template.yaml | sed 's/$K8S_CLUSTER_ID/'"$1"'/g' | sed 's/$K8S_NAMESPACE/'"$2"'/g' > $1-ingress.yaml kubectl apply -f $1-ingress.yaml ================================================ FILE: flink-learning-k8s/dockerfile/docker-entrypoint.sh ================================================ #!/usr/bin/env bash ############################################################################### # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. ############################################################################### COMMAND_STANDALONE="standalone-job" # Deprecated, should be remove in Flink release 1.13 COMMAND_NATIVE_KUBERNETES="native-k8s" COMMAND_HISTORY_SERVER="history-server" # If unspecified, the hostname of the container is taken as the JobManager address JOB_MANAGER_RPC_ADDRESS=${JOB_MANAGER_RPC_ADDRESS:-$(hostname -f)} CONF_FILE="${FLINK_HOME}/conf/flink-conf.yaml" drop_privs_cmd() { if [ $(id -u) != 0 ]; then # Don't need to drop privs if EUID != 0 return elif [ -x /sbin/su-exec ]; then # Alpine echo su-exec flink else # Others echo gosu flink fi } copy_plugins_if_required() { if [ -z "$ENABLE_BUILT_IN_PLUGINS" ]; then return 0 fi echo "Enabling required built-in plugins" for target_plugin in $(echo "$ENABLE_BUILT_IN_PLUGINS" | tr ';' ' '); do echo "Linking ${target_plugin} to plugin directory" plugin_name=${target_plugin%.jar} mkdir -p "${FLINK_HOME}/plugins/${plugin_name}" if [ ! -e "${FLINK_HOME}/opt/${target_plugin}" ]; then echo "Plugin ${target_plugin} does not exist. Exiting." exit 1 else ln -fs "${FLINK_HOME}/opt/${target_plugin}" "${FLINK_HOME}/plugins/${plugin_name}" echo "Successfully enabled ${target_plugin}" fi done } set_config_option() { local option=$1 local value=$2 # escape periods for usage in regular expressions local escaped_option=$(echo ${option} | sed -e "s/\./\\\./g") # either override an existing entry, or append a new one if grep -E "^${escaped_option}:.*" "${CONF_FILE}" > /dev/null; then sed -i -e "s/${escaped_option}:.*/$option: $value/g" "${CONF_FILE}" else echo "${option}: ${value}" >> "${CONF_FILE}" fi } prepare_configuration() { set_config_option jobmanager.rpc.address ${JOB_MANAGER_RPC_ADDRESS} set_config_option blob.server.port 6124 set_config_option query.server.port 6125 TASK_MANAGER_NUMBER_OF_TASK_SLOTS=${TASK_MANAGER_NUMBER_OF_TASK_SLOTS:-1} set_config_option taskmanager.numberOfTaskSlots ${TASK_MANAGER_NUMBER_OF_TASK_SLOTS} if [ -n "${FLINK_PROPERTIES}" ]; then echo "${FLINK_PROPERTIES}" >> "${CONF_FILE}" fi envsubst < "${CONF_FILE}" > "${CONF_FILE}.tmp" && mv "${CONF_FILE}.tmp" "${CONF_FILE}" } maybe_enable_jemalloc() { if [ "${DISABLE_JEMALLOC:-false}" == "false" ]; then export LD_PRELOAD=$LD_PRELOAD:/usr/lib/x86_64-linux-gnu/libjemalloc.so fi } maybe_enable_jemalloc copy_plugins_if_required prepare_configuration args=("$@") if [ "$1" = "help" ]; then printf "Usage: $(basename "$0") (jobmanager|${COMMAND_STANDALONE}|taskmanager|${COMMAND_HISTORY_SERVER})\n" printf " Or $(basename "$0") help\n\n" printf "By default, Flink image adopts jemalloc as default memory allocator. This behavior can be disabled by setting the 'DISABLE_JEMALLOC' environment variable to 'true'.\n" exit 0 elif [ "$1" = "jobmanager" ]; then args=("${args[@]:1}") echo "Starting Job Manager" exec $(drop_privs_cmd) "$FLINK_HOME/bin/jobmanager.sh" start-foreground "${args[@]}" elif [ "$1" = ${COMMAND_STANDALONE} ]; then args=("${args[@]:1}") echo "Starting Job Manager" exec $(drop_privs_cmd) "$FLINK_HOME/bin/standalone-job.sh" start-foreground "${args[@]}" elif [ "$1" = ${COMMAND_HISTORY_SERVER} ]; then args=("${args[@]:1}") echo "Starting History Server" exec $(drop_privs_cmd) "$FLINK_HOME/bin/historyserver.sh" start-foreground "${args[@]}" elif [ "$1" = "taskmanager" ]; then args=("${args[@]:1}") echo "Starting Task Manager" exec $(drop_privs_cmd) "$FLINK_HOME/bin/taskmanager.sh" start-foreground "${args[@]}" elif [ "$1" = "$COMMAND_NATIVE_KUBERNETES" ]; then args=("${args[@]:1}") export _FLINK_HOME_DETERMINED=true . $FLINK_HOME/bin/config.sh export FLINK_CLASSPATH="`constructFlinkClassPath`:$INTERNAL_HADOOP_CLASSPATHS" # Start commands for jobmanager and taskmanager are generated by Flink internally. echo "Start command: ${args[@]}" exec $(drop_privs_cmd) bash -c "${args[@]}" fi args=("${args[@]}") # Set the Flink related environments export _FLINK_HOME_DETERMINED=true . $FLINK_HOME/bin/config.sh export FLINK_CLASSPATH="`constructFlinkClassPath`:$INTERNAL_HADOOP_CLASSPATHS" # Running command in pass-through mode exec $(drop_privs_cmd) "${args[@]}" ================================================ FILE: flink-learning-k8s/dockerfile/ingress_template.yaml ================================================ apiVersion: extensions/v1beta1 kind: Ingress metadata: annotations: kubernetes.io/ingress.class: hke-nginx meta.helm.sh/release-name: $K8S_CLUSTER_ID meta.helm.sh/release-namespace: $K8S_NAMESPACE nginx.ingress.kubernetes.io/rewrite-target: /$2 nginx.ingress.kubernetes.io/connection-proxy-header: close nginx.ingress.kubernetes.io/proxy-http-version: "1.0" labels: app.kubernetes.io/managed-by: Helm xxx.com/deployResource: HKE-FLINK-A-PROD-SH01 xxx.com/env: PRO xxx.com/provider: xxx hke-app: $K8S_CLUSTER_ID name: $K8S_CLUSTER_ID namespace: $K8S_NAMESPACE spec: rules: - host: zhisheng-proxy-k8s.xxx.cn http: paths: - backend: serviceName: $K8S_CLUSTER_ID-rest servicePort: 8081 path: /proxy/$K8S_CLUSTER_ID(/|$)(.*) pathType: Prefix% ================================================ FILE: flink-learning-k8s/flink-k8s/README.md ================================================ ## Flink-K8s 基于开源 1.12.0 版本添加一些自己改造的 feature ================================================ FILE: flink-learning-k8s/flink-k8s/pom.xml ================================================ flink-learning-k8s com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 flink-k8s jar flink-k8s UTF-8 4.9.2 com.squareup.okhttp3 okhttp 4.9.2 org.apache.flink flink-clients ${flink.version} provided org.apache.flink flink-runtime ${flink.version} provided io.fabric8 kubernetes-client ${kubernetes.client.version} org.apache.flink flink-shaded-jackson 2.15.3-19.0 provided org.apache.maven.plugins maven-compiler-plugin true true org.apache.maven.plugins maven-shade-plugin shade-flink package shade io.fabric8:kubernetes-client io.fabric8:kubernetes-model io.fabric8:kubernetes-model-common io.fabric8:zjsonpatch com.fasterxml.jackson.core:jackson-core com.fasterxml.jackson.core:jackson-annotations com.fasterxml.jackson.core:jackson-databind com.fasterxml.jackson.dataformat:jackson-dataformat-yaml com.fasterxml.jackson.datatype:jackson-datatype-jsr310 com.squareup.okhttp3:* com.squareup.okio:okio org.yaml:* dk.brics.automaton:* META-INF/services/org.apache.flink.* *:* *.aut META-INF/maven/** META-INF/services/*com.fasterxml* META-INF/proguard/** OSGI-INF/** schema/** *.vm *.properties *.xml META-INF/jandex.idx license.header com.fasterxml.jackson org.apache.flink.kubernetes.shaded.com.fasterxml.jackson okhttp3 org.apache.flink.kubernetes.shaded.okhttp3 okio org.apache.flink.kubernetes.shaded.okio org.yaml org.apache.flink.kubernetes.shaded.org.yaml dk.brics.automaton org.apache.flink.kubernetes.shaded.dk.brics.automaton ================================================ FILE: flink-learning-k8s/flink-k8s/src/main/java/org/apache/flink/kubernetes/KubernetesClusterClientFactory.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.flink.kubernetes; import org.apache.flink.annotation.Internal; import org.apache.flink.client.deployment.AbstractContainerizedClusterClientFactory; import org.apache.flink.client.deployment.ClusterClientFactory; import org.apache.flink.configuration.Configuration; import org.apache.flink.configuration.DeploymentOptions; import org.apache.flink.kubernetes.configuration.KubernetesConfigOptions; import org.apache.flink.kubernetes.configuration.KubernetesDeploymentTarget; import org.apache.flink.kubernetes.kubeclient.FlinkKubeClientFactory; import org.apache.flink.kubernetes.utils.Constants; import org.apache.flink.util.AbstractID; import javax.annotation.Nullable; import java.util.Optional; import static org.apache.flink.util.Preconditions.checkNotNull; /** * A {@link ClusterClientFactory} for a Kubernetes cluster. */ @Internal public class KubernetesClusterClientFactory extends AbstractContainerizedClusterClientFactory { private static final String CLUSTER_ID_PREFIX = "flink-cluster-"; @Override public boolean isCompatibleWith(Configuration configuration) { checkNotNull(configuration); final String deploymentTarget = configuration.getString(DeploymentOptions.TARGET); return KubernetesDeploymentTarget.isValidKubernetesTarget(deploymentTarget); } @Override public KubernetesClusterDescriptor createClusterDescriptor(Configuration configuration) { checkNotNull(configuration); if (!configuration.contains(KubernetesConfigOptions.CLUSTER_ID)) { final String clusterId = generateClusterId(); configuration.setString(KubernetesConfigOptions.CLUSTER_ID, clusterId); } return new KubernetesClusterDescriptor( configuration, FlinkKubeClientFactory.getInstance().fromConfiguration(configuration, "client")); } @Nullable @Override public String getClusterId(Configuration configuration) { checkNotNull(configuration); return configuration.getString(KubernetesConfigOptions.CLUSTER_ID); } @Override public Optional getApplicationTargetName() { return Optional.of(KubernetesDeploymentTarget.APPLICATION.getName()); } private String generateClusterId() { final String randomID = new AbstractID().toString(); return (CLUSTER_ID_PREFIX + randomID).substring(0, Constants.MAXIMUM_CHARACTERS_OF_CLUSTER_ID); } } ================================================ FILE: flink-learning-k8s/flink-k8s/src/main/java/org/apache/flink/kubernetes/KubernetesClusterDescriptor.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.flink.kubernetes; import org.apache.flink.client.deployment.ClusterDeploymentException; import org.apache.flink.client.deployment.ClusterDescriptor; import org.apache.flink.client.deployment.ClusterRetrieveException; import org.apache.flink.client.deployment.ClusterSpecification; import org.apache.flink.client.deployment.application.ApplicationConfiguration; import org.apache.flink.client.program.ClusterClient; import org.apache.flink.client.program.ClusterClientProvider; import org.apache.flink.client.program.PackagedProgramUtils; import org.apache.flink.client.program.rest.RestClusterClient; import org.apache.flink.configuration.*; import org.apache.flink.kubernetes.configuration.KubernetesConfigOptions; import org.apache.flink.kubernetes.configuration.KubernetesConfigOptionsInternal; import org.apache.flink.kubernetes.configuration.KubernetesDeploymentTarget; import org.apache.flink.kubernetes.entrypoint.KubernetesApplicationClusterEntrypoint; import org.apache.flink.kubernetes.entrypoint.KubernetesSessionClusterEntrypoint; import org.apache.flink.kubernetes.kubeclient.Endpoint; import org.apache.flink.kubernetes.kubeclient.FlinkKubeClient; import org.apache.flink.kubernetes.kubeclient.KubernetesJobManagerSpecification; import org.apache.flink.kubernetes.kubeclient.factory.KubernetesJobManagerFactory; import org.apache.flink.kubernetes.kubeclient.parameters.KubernetesJobManagerParameters; import org.apache.flink.kubernetes.kubeclient.resources.KubernetesService; import org.apache.flink.kubernetes.utils.Constants; import org.apache.flink.kubernetes.utils.KubernetesUtils; import org.apache.flink.runtime.entrypoint.ClusterEntrypoint; import org.apache.flink.runtime.highavailability.HighAvailabilityServicesUtils; import org.apache.flink.runtime.highavailability.nonha.standalone.StandaloneClientHAServices; import org.apache.flink.runtime.jobgraph.JobGraph; import org.apache.flink.runtime.jobmanager.HighAvailabilityMode; import org.apache.flink.util.FlinkException; import org.apache.flink.util.Preconditions; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; import java.util.List; import java.util.Optional; import static org.apache.flink.util.Preconditions.checkNotNull; /** * Kubernetes specific {@link ClusterDescriptor} implementation. */ public class KubernetesClusterDescriptor implements ClusterDescriptor { private static final Logger LOG = LoggerFactory.getLogger(KubernetesClusterDescriptor.class); private static final String CLUSTER_DESCRIPTION = "Kubernetes cluster"; private final Configuration flinkConfig; private final FlinkKubeClient client; private final String clusterId; public KubernetesClusterDescriptor(Configuration flinkConfig, FlinkKubeClient client) { this.flinkConfig = flinkConfig; this.client = client; this.clusterId = checkNotNull( flinkConfig.getString(KubernetesConfigOptions.CLUSTER_ID), "ClusterId must be specified!"); } @Override public String getClusterDescription() { return CLUSTER_DESCRIPTION; } private ClusterClientProvider createClusterClientProvider(String clusterId) { return () -> { final Configuration configuration = new Configuration(flinkConfig); final Optional restEndpoint = client.getRestEndpoint(clusterId); if (restEndpoint.isPresent()) { configuration.setString(RestOptions.ADDRESS, restEndpoint.get().getAddress()); configuration.setInteger(RestOptions.PORT, restEndpoint.get().getPort()); } else { throw new RuntimeException( new ClusterRetrieveException( "Could not get the rest endpoint of " + clusterId)); } try { // Flink client will always use Kubernetes service to contact with jobmanager. So we have a pre-configured web // monitor address. Using StandaloneClientHAServices to create RestClusterClient is reasonable. return new RestClusterClient<>( configuration, clusterId, new StandaloneClientHAServices(getWebMonitorAddress(configuration))); } catch (Exception e) { client.handleException(e); throw new RuntimeException(new ClusterRetrieveException("Could not create the RestClusterClient.", e)); } }; } private String getWebMonitorAddress(Configuration configuration) throws Exception { HighAvailabilityServicesUtils.AddressResolution resolution = HighAvailabilityServicesUtils.AddressResolution.TRY_ADDRESS_RESOLUTION; if (configuration.get(KubernetesConfigOptions.REST_SERVICE_EXPOSED_TYPE) == KubernetesConfigOptions.ServiceExposedType.ClusterIP) { resolution = HighAvailabilityServicesUtils.AddressResolution.NO_ADDRESS_RESOLUTION; LOG.warn( "Please note that Flink client operations(e.g. cancel, list, stop," + " savepoint, etc.) won't work from outside the Kubernetes cluster" + " since '{}' has been set to {}.", KubernetesConfigOptions.REST_SERVICE_EXPOSED_TYPE.key(), KubernetesConfigOptions.ServiceExposedType.ClusterIP); } return HighAvailabilityServicesUtils.getWebMonitorAddress(configuration, resolution); } @Override public ClusterClientProvider retrieve(String clusterId) { final ClusterClientProvider clusterClientProvider = createClusterClientProvider(clusterId); try (ClusterClient clusterClient = clusterClientProvider.getClusterClient()) { LOG.info( "Retrieve flink cluster {} successfully, JobManager Web Interface: {}", clusterId, clusterClient.getWebInterfaceURL()); } return clusterClientProvider; } @Override public ClusterClientProvider deploySessionCluster(ClusterSpecification clusterSpecification) throws ClusterDeploymentException { final ClusterClientProvider clusterClientProvider = deployClusterInternal( KubernetesSessionClusterEntrypoint.class.getName(), clusterSpecification, false); try (ClusterClient clusterClient = clusterClientProvider.getClusterClient()) { LOG.info( "Create flink session cluster {} successfully, JobManager Web Interface: {}", clusterId, clusterClient.getWebInterfaceURL()); } return clusterClientProvider; } @Override public ClusterClientProvider deployApplicationCluster( final ClusterSpecification clusterSpecification, final ApplicationConfiguration applicationConfiguration) throws ClusterDeploymentException { //todo:k8s application mode if (client.getService(KubernetesService.ServiceType.REST_SERVICE, clusterId).isPresent()) { throw new ClusterDeploymentException("The Flink cluster " + clusterId + " already exists."); } checkNotNull(clusterSpecification); checkNotNull(applicationConfiguration); final KubernetesDeploymentTarget deploymentTarget = KubernetesDeploymentTarget.fromConfig(flinkConfig); if (KubernetesDeploymentTarget.APPLICATION != deploymentTarget) { throw new ClusterDeploymentException( "Couldn't deploy Kubernetes Application Cluster." + " Expected deployment.target=" + KubernetesDeploymentTarget.APPLICATION.getName() + " but actual one was \"" + deploymentTarget + "\""); } applicationConfiguration.applyToConfiguration(flinkConfig); // No need to do pipelineJars validation if it is a PyFlink job. if (!(PackagedProgramUtils.isPython(applicationConfiguration.getApplicationClassName()) || PackagedProgramUtils.isPython(applicationConfiguration.getProgramArguments()))) { final List pipelineJars = KubernetesUtils.checkJarFileForApplicationMode(flinkConfig); Preconditions.checkArgument(pipelineJars.size() == 1, "Should only have one jar"); } //todo:deploy jm final ClusterClientProvider clusterClientProvider = deployClusterInternal( KubernetesApplicationClusterEntrypoint.class.getName(), clusterSpecification, false); try (ClusterClient clusterClient = clusterClientProvider.getClusterClient()) { LOG.info( "Create flink application cluster {} successfully, JobManager Web Interface: {}", clusterId, clusterClient.getWebInterfaceURL()); } return clusterClientProvider; } @Override public ClusterClientProvider deployJobCluster( ClusterSpecification clusterSpecification, JobGraph jobGraph, boolean detached) throws ClusterDeploymentException { throw new ClusterDeploymentException("Per-Job Mode not supported by Active Kubernetes deployments."); } private ClusterClientProvider deployClusterInternal( String entryPoint, ClusterSpecification clusterSpecification, boolean detached) throws ClusterDeploymentException { final ClusterEntrypoint.ExecutionMode executionMode = detached ? ClusterEntrypoint.ExecutionMode.DETACHED : ClusterEntrypoint.ExecutionMode.NORMAL; flinkConfig.setString(ClusterEntrypoint.EXECUTION_MODE, executionMode.toString()); flinkConfig.setString(KubernetesConfigOptionsInternal.ENTRY_POINT_CLASS, entryPoint); // Rpc, blob, rest, taskManagerRpc ports need to be exposed, so update them to fixed values. KubernetesUtils.checkAndUpdatePortConfigOption(flinkConfig, BlobServerOptions.PORT, Constants.BLOB_SERVER_PORT); KubernetesUtils.checkAndUpdatePortConfigOption(flinkConfig, TaskManagerOptions.RPC_PORT, Constants.TASK_MANAGER_RPC_PORT); KubernetesUtils.checkAndUpdatePortConfigOption(flinkConfig, RestOptions.BIND_PORT, Constants.REST_PORT); if (HighAvailabilityMode.isHighAvailabilityModeActivated(flinkConfig)) { flinkConfig.setString(HighAvailabilityOptions.HA_CLUSTER_ID, clusterId); KubernetesUtils.checkAndUpdatePortConfigOption( flinkConfig, HighAvailabilityOptions.HA_JOB_MANAGER_PORT_RANGE, flinkConfig.get(JobManagerOptions.PORT)); } try { //todo:构建 jm param final KubernetesJobManagerParameters kubernetesJobManagerParameters = new KubernetesJobManagerParameters(flinkConfig, clusterSpecification); //todo:构建 jm spec final KubernetesJobManagerSpecification kubernetesJobManagerSpec = KubernetesJobManagerFactory.buildKubernetesJobManagerSpecification(kubernetesJobManagerParameters); //todo:create jm deployment client.createJobManagerComponent(kubernetesJobManagerSpec); return createClusterClientProvider(clusterId); } catch (Exception e) { try { LOG.warn("Failed to create the Kubernetes cluster \"{}\", try to clean up the residual resources.", clusterId); client.stopAndCleanupCluster(clusterId); } catch (Exception e1) { LOG.info("Failed to stop and clean up the Kubernetes cluster \"{}\".", clusterId, e1); } throw new ClusterDeploymentException("Could not create Kubernetes cluster \"" + clusterId + "\".", e); } } @Override public void killCluster(String clusterId) throws FlinkException { try { client.stopAndCleanupCluster(clusterId); } catch (Exception e) { client.handleException(e); throw new FlinkException("Could not kill Kubernetes cluster " + clusterId); } } @Override public void close() { try { client.close(); } catch (Exception e) { client.handleException(e); LOG.error("failed to close client, exception {}", e.toString()); } } } ================================================ FILE: flink-learning-k8s/flink-k8s/src/main/java/org/apache/flink/kubernetes/KubernetesResourceManagerDriver.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.flink.kubernetes; import org.apache.flink.api.common.time.Time; import org.apache.flink.configuration.*; import org.apache.flink.kubernetes.configuration.KubernetesConfigOptions; import org.apache.flink.kubernetes.configuration.KubernetesResourceManagerDriverConfiguration; import org.apache.flink.kubernetes.kubeclient.FlinkKubeClient; import org.apache.flink.kubernetes.kubeclient.factory.KubernetesTaskManagerFactory; import org.apache.flink.kubernetes.kubeclient.parameters.KubernetesTaskManagerParameters; import org.apache.flink.kubernetes.kubeclient.resources.KubernetesPod; import org.apache.flink.kubernetes.kubeclient.resources.KubernetesService; import org.apache.flink.kubernetes.kubeclient.resources.KubernetesTooOldResourceVersionException; import org.apache.flink.kubernetes.kubeclient.resources.KubernetesWatch; import org.apache.flink.kubernetes.utils.Constants; import org.apache.flink.kubernetes.utils.KubernetesUtils; import org.apache.flink.runtime.clusterframework.ApplicationStatus; import org.apache.flink.runtime.clusterframework.BootstrapTools; import org.apache.flink.runtime.clusterframework.ContaineredTaskManagerParameters; import org.apache.flink.runtime.clusterframework.TaskExecutorProcessSpec; import org.apache.flink.runtime.clusterframework.types.ResourceID; import org.apache.flink.runtime.concurrent.FutureUtils; import org.apache.flink.runtime.externalresource.ExternalResourceUtils; import org.apache.flink.runtime.jobmanager.HighAvailabilityMode; import org.apache.flink.runtime.resourcemanager.active.AbstractResourceManagerDriver; import org.apache.flink.runtime.resourcemanager.active.ResourceManagerDriver; import org.apache.flink.runtime.resourcemanager.exceptions.ResourceManagerException; import org.apache.flink.util.ExceptionUtils; import org.apache.flink.util.FlinkException; import org.apache.flink.util.Preconditions; import javax.annotation.Nullable; import java.util.*; import java.util.concurrent.CompletableFuture; /** * Implementation of {@link ResourceManagerDriver} for Kubernetes deployment. */ public class KubernetesResourceManagerDriver extends AbstractResourceManagerDriver { /** The taskmanager pod name pattern is {clusterId}-{taskmanager}-{attemptId}-{podIndex}. */ private static final String TASK_MANAGER_POD_FORMAT = "%s-taskmanager-%d-%d"; private final String clusterId; private final String webInterfaceUrl; private final Time podCreationRetryInterval; private final FlinkKubeClient flinkKubeClient; /** Request resource futures, keyed by pod names. */ private final Map> requestResourceFutures; /** When ResourceManager failover, the max attempt should recover. */ private long currentMaxAttemptId = 0; /** Current max pod index. When creating a new pod, it should increase one. */ private long currentMaxPodId = 0; private Optional podsWatchOpt; private volatile boolean running; /** * Incompletion of this future indicates that there was a pod creation failure recently and the driver should not * retry creating pods until the future become completed again. It's guaranteed to be modified in main thread. */ private CompletableFuture podCreationCoolDown; public KubernetesResourceManagerDriver( Configuration flinkConfig, FlinkKubeClient flinkKubeClient, KubernetesResourceManagerDriverConfiguration configuration) { super(flinkConfig, GlobalConfiguration.loadConfiguration()); this.clusterId = Preconditions.checkNotNull(configuration.getClusterId()); this.webInterfaceUrl = configuration.getWebInterfaceUrl(); this.podCreationRetryInterval = Preconditions.checkNotNull(configuration.getPodCreationRetryInterval()); this.flinkKubeClient = Preconditions.checkNotNull(flinkKubeClient); this.requestResourceFutures = new HashMap<>(); this.podCreationCoolDown = FutureUtils.completedVoidFuture(); this.running = false; } // ------------------------------------------------------------------------ // ResourceManagerDriver // ------------------------------------------------------------------------ @Override protected void initializeInternal() throws Exception { podsWatchOpt = watchTaskManagerPods(); updateKubernetesServiceTargetPortIfNecessary(); recoverWorkerNodesFromPreviousAttempts(); this.running = true; } @Override public CompletableFuture terminate() { if (!running) { return FutureUtils.completedVoidFuture(); } running = false; // shut down all components Exception exception = null; try { podsWatchOpt.ifPresent(KubernetesWatch::close); } catch (Exception e) { exception = e; } try { flinkKubeClient.close(); } catch (Exception e) { exception = ExceptionUtils.firstOrSuppressed(e, exception); } return exception == null ? FutureUtils.completedVoidFuture() : FutureUtils.completedExceptionally(exception); } @Override public void deregisterApplication(ApplicationStatus finalStatus, @Nullable String optionalDiagnostics) { log.info("Deregistering Flink Kubernetes cluster, clusterId: {}, diagnostics: {}", clusterId, optionalDiagnostics == null ? "" : optionalDiagnostics); flinkKubeClient.stopAndCleanupCluster(clusterId); } @Override public CompletableFuture requestResource(TaskExecutorProcessSpec taskExecutorProcessSpec) { final KubernetesTaskManagerParameters parameters = createKubernetesTaskManagerParameters(taskExecutorProcessSpec); final KubernetesPod taskManagerPod = KubernetesTaskManagerFactory.buildTaskManagerKubernetesPod(parameters); final String podName = taskManagerPod.getName(); final CompletableFuture requestResourceFuture = new CompletableFuture<>(); requestResourceFutures.put(podName, requestResourceFuture); log.info("Creating new TaskManager pod with name {} and resource <{},{}>.", podName, parameters.getTaskManagerMemoryMB(), parameters.getTaskManagerCPU()); // When K8s API Server is temporary unavailable, `kubeClient.createTaskManagerPod` might fail immediately. // In case of pod creation failures, we should wait for an interval before trying to create new pods. // Otherwise, ActiveResourceManager will always re-requesting the worker, which keeps the main thread busy. // todo:创建 tm pod final CompletableFuture createPodFuture = podCreationCoolDown.thenCompose((ignore) -> flinkKubeClient.createTaskManagerPod(taskManagerPod)); FutureUtils.assertNoException( createPodFuture.handleAsync((ignore, exception) -> { if (exception != null) { log.warn("Could not create pod {}, exception: {}", podName, exception); tryResetPodCreationCoolDown(); CompletableFuture future = requestResourceFutures.remove(taskManagerPod.getName()); if (future != null) { future.completeExceptionally(exception); } } else { log.info("Pod {} is created.", podName); } return null; }, getMainThreadExecutor())); return requestResourceFuture; } @Override public void releaseResource(KubernetesWorkerNode worker) { final String podName = worker.getResourceID().toString(); log.info("Stopping TaskManager pod {}.", podName); stopPod(podName); } // ------------------------------------------------------------------------ // Internal // ------------------------------------------------------------------------ private void recoverWorkerNodesFromPreviousAttempts() throws ResourceManagerException { List podList = flinkKubeClient.getPodsWithLabels(KubernetesUtils.getTaskManagerLabels(clusterId)); final List recoveredWorkers = new ArrayList<>(); for (KubernetesPod pod : podList) { final KubernetesWorkerNode worker = new KubernetesWorkerNode(new ResourceID(pod.getName())); final long attempt = worker.getAttempt(); if (attempt > currentMaxAttemptId) { currentMaxAttemptId = attempt; } if (pod.isTerminated() || !pod.isScheduled()) { stopPod(pod.getName()); } else { recoveredWorkers.add(worker); } } log.info("Recovered {} pods from previous attempts, current attempt id is {}.", recoveredWorkers.size(), ++currentMaxAttemptId); // Should not invoke resource event handler on the main thread executor. // We are in the initializing thread. The main thread executor is not yet ready. getResourceEventHandler().onPreviousAttemptWorkersRecovered(recoveredWorkers); } private void updateKubernetesServiceTargetPortIfNecessary() throws Exception { if (!KubernetesUtils.isHostNetwork(flinkConfig)) { return; } final int restPort = KubernetesUtils.parseRestBindPortFromWebInterfaceUrl(webInterfaceUrl); Preconditions.checkArgument( restPort > 0, "Failed to parse rest port from " + webInterfaceUrl); flinkKubeClient .updateServiceTargetPort( KubernetesService.ServiceType.REST_SERVICE, clusterId, Constants.REST_PORT_NAME, restPort) .get(); if (!HighAvailabilityMode.isHighAvailabilityModeActivated(flinkConfig)) { flinkKubeClient .updateServiceTargetPort( KubernetesService.ServiceType.INTERNAL_SERVICE, clusterId, Constants.BLOB_SERVER_PORT_NAME, Integer.parseInt(flinkConfig.getString(BlobServerOptions.PORT))) .get(); flinkKubeClient .updateServiceTargetPort( KubernetesService.ServiceType.INTERNAL_SERVICE, clusterId, Constants.JOB_MANAGER_RPC_PORT_NAME, flinkConfig.getInteger(JobManagerOptions.PORT)) .get(); } } private KubernetesTaskManagerParameters createKubernetesTaskManagerParameters(TaskExecutorProcessSpec taskExecutorProcessSpec) { final String podName = String.format( TASK_MANAGER_POD_FORMAT, clusterId, currentMaxAttemptId, ++currentMaxPodId); final ContaineredTaskManagerParameters taskManagerParameters = ContaineredTaskManagerParameters.create(flinkConfig, taskExecutorProcessSpec); final Configuration taskManagerConfig = new Configuration(flinkConfig); taskManagerConfig.set(TaskManagerOptions.TASK_MANAGER_RESOURCE_ID, podName); final String dynamicProperties = BootstrapTools.getDynamicPropertiesAsString(flinkClientConfig, taskManagerConfig); return new KubernetesTaskManagerParameters( flinkConfig, podName, dynamicProperties, taskManagerParameters, ExternalResourceUtils.getExternalResources(flinkConfig, KubernetesConfigOptions.EXTERNAL_RESOURCE_KUBERNETES_CONFIG_KEY_SUFFIX)); } private void tryResetPodCreationCoolDown() { if (podCreationCoolDown.isDone()) { log.info("Pod creation failed. Will not retry creating pods in {}.", podCreationRetryInterval); podCreationCoolDown = new CompletableFuture<>(); getMainThreadExecutor().schedule( () -> podCreationCoolDown.complete(null), podCreationRetryInterval.getSize(), podCreationRetryInterval.getUnit()); } } private void onPodTerminated(KubernetesPod pod) { final String podName = pod.getName(); log.debug("TaskManager pod {} is terminated.", podName); // this is a safe net, in case onModified/onDeleted/onError is // received before onAdded final CompletableFuture requestResourceFuture = requestResourceFutures.remove(podName); if (requestResourceFuture != null) { log.warn("Pod {} is terminated before being scheduled.", podName); requestResourceFuture.completeExceptionally(new FlinkException("Pod is terminated.")); } getResourceEventHandler() .onWorkerTerminated(new ResourceID(podName), pod.getTerminatedDiagnostics()); stopPod(podName); } private void onPodScheduled(KubernetesPod pod) { final String podName = pod.getName(); final CompletableFuture requestResourceFuture = requestResourceFutures.remove(podName); if (requestResourceFuture == null) { log.debug("Ignore TaskManager pod that is already added: {}", podName); return; } log.info("Received new TaskManager pod: {}", podName); requestResourceFuture.complete(new KubernetesWorkerNode(new ResourceID(podName))); } private void handlePodEventsInMainThread(List pods) { getMainThreadExecutor() .execute( () -> { for (KubernetesPod pod : pods) { if (pod.isTerminated()) { onPodTerminated(pod); } else if (pod.isScheduled()) { onPodScheduled(pod); } } }); } private void stopPod(String podName) { flinkKubeClient.stopPod(podName) .whenComplete((ignore, throwable) -> { if (throwable != null) { log.warn("Could not remove TaskManager pod {}, exception: {}", podName, throwable); } }); } private Optional watchTaskManagerPods() { return Optional.of( flinkKubeClient .watchPodsAndDoCallback( KubernetesUtils.getTaskManagerLabels(clusterId), new PodCallbackHandlerImpl())); } // ------------------------------------------------------------------------ // FlinkKubeClient.WatchCallbackHandler // ------------------------------------------------------------------------ private class PodCallbackHandlerImpl implements FlinkKubeClient.WatchCallbackHandler { @Override public void onAdded(List pods) { handlePodEventsInMainThread(pods); } @Override public void onModified(List pods) { handlePodEventsInMainThread(pods); } @Override public void onDeleted(List pods) { handlePodEventsInMainThread(pods); } @Override public void onError(List pods) { handlePodEventsInMainThread(pods); } @Override public void handleFatalError(Throwable throwable) { if (throwable instanceof KubernetesTooOldResourceVersionException) { getMainThreadExecutor() .execute( () -> { if (running) { podsWatchOpt.ifPresent(KubernetesWatch::close); log.info("Creating a new watch on TaskManager pods."); podsWatchOpt = watchTaskManagerPods(); } }); } else { getResourceEventHandler().onError(throwable); } } } } ================================================ FILE: flink-learning-k8s/flink-k8s/src/main/java/org/apache/flink/kubernetes/KubernetesWorkerNode.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.flink.kubernetes; import org.apache.flink.runtime.clusterframework.types.ResourceID; import org.apache.flink.runtime.clusterframework.types.ResourceIDRetrievable; import org.apache.flink.runtime.resourcemanager.exceptions.ResourceManagerException; import java.util.regex.Matcher; import java.util.regex.Pattern; import static org.apache.flink.util.Preconditions.checkNotNull; /** * A stored Kubernetes worker, which contains the kubernetes pod. */ public class KubernetesWorkerNode implements ResourceIDRetrievable { private final ResourceID resourceID; /** * This pattern should be updated when {@link KubernetesResourceManagerDriver#TASK_MANAGER_POD_FORMAT} changed. */ private static final Pattern TASK_MANAGER_POD_PATTERN = Pattern.compile("\\S+-taskmanager-([\\d]+)-([\\d]+)"); KubernetesWorkerNode(ResourceID resourceID) { this.resourceID = checkNotNull(resourceID); } @Override public ResourceID getResourceID() { return resourceID; } public long getAttempt() throws ResourceManagerException { Matcher matcher = TASK_MANAGER_POD_PATTERN.matcher(resourceID.toString()); if (matcher.find()) { return Long.parseLong(matcher.group(1)); } else { throw new ResourceManagerException("Error to parse KubernetesWorkerNode from " + resourceID + "."); } } } ================================================ FILE: flink-learning-k8s/flink-k8s/src/main/java/org/apache/flink/kubernetes/cli/KubernetesSessionCli.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.flink.kubernetes.cli; import org.apache.commons.cli.CommandLine; import org.apache.flink.annotation.Internal; import org.apache.flink.api.java.tuple.Tuple2; import org.apache.flink.client.cli.AbstractCustomCommandLine; import org.apache.flink.client.cli.CliArgsException; import org.apache.flink.client.cli.CliFrontend; import org.apache.flink.client.cli.GenericCLI; import org.apache.flink.client.deployment.ClusterClientFactory; import org.apache.flink.client.deployment.ClusterClientServiceLoader; import org.apache.flink.client.deployment.ClusterDescriptor; import org.apache.flink.client.deployment.DefaultClusterClientServiceLoader; import org.apache.flink.client.program.ClusterClient; import org.apache.flink.configuration.Configuration; import org.apache.flink.configuration.DeploymentOptions; import org.apache.flink.configuration.GlobalConfiguration; import org.apache.flink.configuration.UnmodifiableConfiguration; import org.apache.flink.kubernetes.executors.KubernetesSessionClusterExecutor; import org.apache.flink.kubernetes.kubeclient.FlinkKubeClient; import org.apache.flink.kubernetes.kubeclient.FlinkKubeClientFactory; import org.apache.flink.kubernetes.kubeclient.resources.KubernetesService; import org.apache.flink.runtime.security.SecurityUtils; import org.apache.flink.util.FlinkException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import static org.apache.flink.util.Preconditions.checkNotNull; /** * Kubernetes customized commandline. */ @Internal public class KubernetesSessionCli { private static final Logger LOG = LoggerFactory.getLogger(KubernetesSessionCli.class); private static final long CLIENT_POLLING_INTERVAL_MS = 3000L; private static final String KUBERNETES_CLUSTER_HELP = "Available commands:\n" + "help - show these commands\n" + "stop - stop the kubernetes cluster\n" + "quit - quit attach mode"; private final Configuration baseConfiguration; private final GenericCLI cli; private final ClusterClientServiceLoader clusterClientServiceLoader; public KubernetesSessionCli(Configuration configuration, String configDir) { this(configuration, new DefaultClusterClientServiceLoader(), configDir); } public KubernetesSessionCli(Configuration configuration, ClusterClientServiceLoader clusterClientServiceLoader, String configDir) { this.baseConfiguration = new UnmodifiableConfiguration(checkNotNull(configuration)); this.clusterClientServiceLoader = checkNotNull(clusterClientServiceLoader); this.cli = new GenericCLI(baseConfiguration, configDir); } Configuration getEffectiveConfiguration(String[] args) throws CliArgsException { final CommandLine commandLine = cli.parseCommandLineOptions(args, true); final Configuration effectiveConfiguration = new Configuration(baseConfiguration); effectiveConfiguration.addAll(cli.toConfiguration(commandLine)); effectiveConfiguration.set(DeploymentOptions.TARGET, KubernetesSessionClusterExecutor.NAME); return effectiveConfiguration; } private int run(String[] args) throws FlinkException, CliArgsException { final Configuration configuration = getEffectiveConfiguration(args); final ClusterClientFactory kubernetesClusterClientFactory = clusterClientServiceLoader.getClusterClientFactory(configuration); final ClusterDescriptor kubernetesClusterDescriptor = kubernetesClusterClientFactory.createClusterDescriptor(configuration); try { final ClusterClient clusterClient; String clusterId = kubernetesClusterClientFactory.getClusterId(configuration); final boolean detached = !configuration.get(DeploymentOptions.ATTACHED); final FlinkKubeClient kubeClient = FlinkKubeClientFactory .getInstance().fromConfiguration(configuration, "client"); // Retrieve or create a session cluster. if (clusterId != null && kubeClient.getService(KubernetesService.ServiceType.REST_SERVICE, clusterId).isPresent()) { clusterClient = kubernetesClusterDescriptor.retrieve(clusterId).getClusterClient(); } else { clusterClient = kubernetesClusterDescriptor .deploySessionCluster( kubernetesClusterClientFactory.getClusterSpecification(configuration)) .getClusterClient(); clusterId = clusterClient.getClusterId(); } try { if (!detached) { Tuple2 continueRepl = new Tuple2<>(true, false); try (BufferedReader in = new BufferedReader(new InputStreamReader(System.in))) { while (continueRepl.f0) { continueRepl = repStep(in); } } catch (Exception e) { LOG.warn("Exception while running the interactive command line interface.", e); } if (continueRepl.f1) { kubernetesClusterDescriptor.killCluster(clusterId); } } clusterClient.close(); kubeClient.close(); } catch (Exception e) { LOG.info("Could not properly shutdown cluster client.", e); } } finally { try { kubernetesClusterDescriptor.close(); } catch (Exception e) { LOG.info("Could not properly close the kubernetes cluster descriptor.", e); } } return 0; } /** * Check whether need to continue or kill the cluster. * @param in input buffer reader * @return f0, whether need to continue read from input. f1, whether need to kill the cluster. */ private Tuple2 repStep(BufferedReader in) throws IOException, InterruptedException { final long startTime = System.currentTimeMillis(); while ((System.currentTimeMillis() - startTime) < CLIENT_POLLING_INTERVAL_MS && (!in.ready())) { Thread.sleep(200L); } //------------- handle interactive command by user. ---------------------- if (in.ready()) { final String command = in.readLine(); switch (command) { case "quit": return new Tuple2<>(false, false); case "stop": return new Tuple2<>(false, true); case "help": System.err.println(KUBERNETES_CLUSTER_HELP); break; default: System.err.println("Unknown command '" + command + "'. Showing help:"); System.err.println(KUBERNETES_CLUSTER_HELP); break; } } return new Tuple2<>(true, false); } public static void main(String[] args) { final Configuration configuration = GlobalConfiguration.loadConfiguration(); final String configDir = CliFrontend.getConfigurationDirectoryFromEnv(); int retCode; try { final KubernetesSessionCli cli = new KubernetesSessionCli(configuration, configDir); retCode = SecurityUtils.getInstalledContext().runSecured(() -> cli.run(args)); } catch (CliArgsException e) { retCode = AbstractCustomCommandLine.handleCliArgsException(e, LOG); } catch (Exception e) { retCode = AbstractCustomCommandLine.handleError(e, LOG); } System.exit(retCode); } } ================================================ FILE: flink-learning-k8s/flink-k8s/src/main/java/org/apache/flink/kubernetes/configuration/KubernetesConfigOptions.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.flink.kubernetes.configuration; import org.apache.flink.annotation.PublicEvolving; import org.apache.flink.annotation.docs.Documentation; import org.apache.flink.configuration.ConfigOption; import org.apache.flink.configuration.ConfigOptions; import org.apache.flink.configuration.ExternalResourceOptions; import org.apache.flink.configuration.description.Description; import org.apache.flink.runtime.util.EnvironmentInformation; import java.util.List; import java.util.Locale; import java.util.Map; import static org.apache.flink.configuration.ConfigOptions.key; import static org.apache.flink.configuration.description.TextElement.code; /** * This class holds configuration constants used by Flink's kubernetes runners. */ @PublicEvolving public class KubernetesConfigOptions { private static final String KUBERNETES_SERVICE_ACCOUNT_KEY = "kubernetes.service-account"; public static final ConfigOption CONTEXT = key("kubernetes.context") .stringType() .noDefaultValue() .withDescription("The desired context from your Kubernetes config file used to configure the Kubernetes client " + "for interacting with the cluster. This could be helpful if one has multiple contexts configured and " + "wants to administrate different Flink clusters on different Kubernetes clusters/contexts."); public static final ConfigOption REST_SERVICE_EXPOSED_TYPE = key("kubernetes.rest-service.exposed.type") .enumType(ServiceExposedType.class) .defaultValue(ServiceExposedType.LoadBalancer) .withDescription("The type of the rest service (ClusterIP or NodePort or LoadBalancer). " + "When set to ClusterIP, the rest service will not be created."); public static final ConfigOption JOB_MANAGER_SERVICE_ACCOUNT = key("kubernetes.jobmanager.service-account") .stringType() .noDefaultValue() .withDescription("Service account that is used by jobmanager within kubernetes cluster. " + "The job manager uses this service account when requesting taskmanager pods from the API server. " + "If not explicitly configured, config option '" + KUBERNETES_SERVICE_ACCOUNT_KEY + "' will be used."); public static final ConfigOption TASK_MANAGER_SERVICE_ACCOUNT = key("kubernetes.taskmanager.service-account") .stringType() .noDefaultValue() .withDescription("Service account that is used by taskmanager within kubernetes cluster. " + "The task manager uses this service account when watching config maps on the API server to retrieve " + "leader address of jobmanager and resourcemanager. If not explicitly configured, config option '" + KUBERNETES_SERVICE_ACCOUNT_KEY + "' will be used."); public static final ConfigOption KUBERNETES_SERVICE_ACCOUNT = key(KUBERNETES_SERVICE_ACCOUNT_KEY) .stringType() .defaultValue("default") .withDescription("Service account that is used by jobmanager and taskmanager within kubernetes cluster. " + "Notice that this can be overwritten by config options '" + JOB_MANAGER_SERVICE_ACCOUNT.key() + "' and '" + TASK_MANAGER_SERVICE_ACCOUNT.key() + "' for jobmanager and taskmanager respectively."); public static final ConfigOption JOB_MANAGER_CPU = key("kubernetes.jobmanager.cpu") .doubleType() .defaultValue(1.0) .withDescription("The number of cpu used by job manager"); public static final ConfigOption JOB_MANAGER_CPU_REQUEST_FACTOR = key("kubernetes.jobmanager.cpu.request-factor") .doubleType() .defaultValue(0.5) .withDescription( "The request factor of cpu used by job manager. " + "The resources request cpu will be set to cpu * request-factor."); public static final ConfigOption JOB_MANAGER_MEMORY_REQUEST_FACTOR = key("kubernetes.jobmanager.memory.request-factor") .doubleType() .defaultValue(0.5) .withDescription( "The request factor of memory used by job manager. " + "The resources request memory will be set to memory * request-factor."); public static final ConfigOption TASK_MANAGER_CPU = key("kubernetes.taskmanager.cpu") .doubleType() .defaultValue(-1.0) .withDescription("The number of cpu used by task manager. By default, the cpu is set " + "to the number of slots per TaskManager"); public static final ConfigOption TASK_MANAGER_CPU_REQUEST_FACTOR = key("kubernetes.taskmanager.cpu.request-factor") .doubleType() .defaultValue(1.0) .withDescription( "The request factor of cpu used by task manager. " + "The resources request cpu will be set to cpu * request-factor."); public static final ConfigOption TASK_MANAGER_MEMORY_REQUEST_FACTOR = key("kubernetes.taskmanager.memory.request-factor") .doubleType() .defaultValue(1.0) .withDescription( "The request factor of memory used by task manager. " + "The resources request memory will be set to memory * request-factor."); public static final ConfigOption CONTAINER_IMAGE_PULL_POLICY = key("kubernetes.container.image.pull-policy") .enumType(ImagePullPolicy.class) .defaultValue(ImagePullPolicy.IfNotPresent) .withDescription("The Kubernetes container image pull policy (IfNotPresent or Always or Never). " + "The default policy is IfNotPresent to avoid putting pressure to image repository."); public static final ConfigOption> CONTAINER_IMAGE_PULL_SECRETS = key("kubernetes.container.image.pull-secrets") .stringType() .asList() .noDefaultValue() .withDescription("A semicolon-separated list of the Kubernetes secrets used to access " + "private image registries."); public static final ConfigOption KUBE_CONFIG_FILE = key("kubernetes.config.file") .stringType() .noDefaultValue() .withDescription("The kubernetes config file will be used to create the client. The default " + "is located at ~/.kube/config"); public static final ConfigOption NAMESPACE = key("kubernetes.namespace") .stringType() .defaultValue("default") .withDescription("The namespace that will be used for running the jobmanager and taskmanager pods."); public static final ConfigOption CONTAINER_START_COMMAND_TEMPLATE = key("kubernetes.container-start-command-template") .stringType() .defaultValue("%java% %classpath% %jvmmem% %jvmopts% %logging% %class% %args%") .withDescription("Template for the kubernetes jobmanager and taskmanager container start invocation."); public static final ConfigOption> JOB_MANAGER_LABELS = key("kubernetes.jobmanager.labels") .mapType() .noDefaultValue() .withDescription("The labels to be set for JobManager pod. Specified as key:value pairs separated by commas. " + "For example, version:alphav1,deploy:test."); public static final ConfigOption> TASK_MANAGER_LABELS = key("kubernetes.taskmanager.labels") .mapType() .noDefaultValue() .withDescription("The labels to be set for TaskManager pods. Specified as key:value pairs separated by commas. " + "For example, version:alphav1,deploy:test."); public static final ConfigOption> JOB_MANAGER_NODE_SELECTOR = key("kubernetes.jobmanager.node-selector") .mapType() .noDefaultValue() .withDescription("The node selector to be set for JobManager pod. Specified as key:value pairs separated by " + "commas. For example, environment:production,disk:ssd."); public static final ConfigOption> TASK_MANAGER_NODE_SELECTOR = key("kubernetes.taskmanager.node-selector") .mapType() .noDefaultValue() .withDescription("The node selector to be set for TaskManager pods. Specified as key:value pairs separated by " + "commas. For example, environment:production,disk:ssd."); public static final ConfigOption CLUSTER_ID = key("kubernetes.cluster-id") .stringType() .noDefaultValue() .withDescription(Description.builder() .text("The cluster-id, which should be no more than 45 characters, is used for identifying a unique Flink cluster. " + "The id must only contain lowercase alphanumeric characters and \"-\". " + "The required format is %s. " + "If not set, the client will automatically generate it with a random ID.", code("[a-z]([-a-z0-9]*[a-z0-9])")) .build()); @Documentation.OverrideDefault("The default value depends on the actually running version. In general it looks like \"flink:-scala_\"") public static final ConfigOption CONTAINER_IMAGE = key("kubernetes.container.image") .stringType() .defaultValue(getDefaultFlinkImage()) .withDescription("Image to use for Flink containers. " + "The specified image must be based upon the same Apache Flink and Scala versions as used by the application. " + "Visit https://hub.docker.com/_/flink?tab=tags for the images provided by the Flink project."); /** * The following config options need to be set according to the image. */ public static final ConfigOption KUBERNETES_ENTRY_PATH = key("kubernetes.entry.path") .stringType() .defaultValue("/docker-entrypoint.sh") .withDescription("The entrypoint script of kubernetes in the image. It will be used as command for jobmanager " + "and taskmanager container."); public static final ConfigOption FLINK_CONF_DIR = key("kubernetes.flink.conf.dir") .stringType() .defaultValue("/opt/flink/conf") .withDescription("The flink conf directory that will be mounted in pod. The flink-conf.yaml, log4j.properties, " + "logback.xml in this path will be overwritten from config map."); public static final ConfigOption FLINK_LOG_DIR = key("kubernetes.flink.log.dir") .stringType() .defaultValue("/opt/flink/log") .withDescription("The directory that logs of jobmanager and taskmanager be saved in the pod."); public static final ConfigOption HADOOP_CONF_CONFIG_MAP = key("kubernetes.hadoop.conf.config-map.name") .stringType() .noDefaultValue() .withDescription("Specify the name of an existing ConfigMap that contains custom Hadoop configuration " + "to be mounted on the JobManager(s) and TaskManagers."); public static final ConfigOption> JOB_MANAGER_ANNOTATIONS = key("kubernetes.jobmanager.annotations") .mapType() .noDefaultValue() .withDescription("The user-specified annotations that are set to the JobManager pod. The value could be " + "in the form of a1:v1,a2:v2"); public static final ConfigOption> TASK_MANAGER_ANNOTATIONS = key("kubernetes.taskmanager.annotations") .mapType() .noDefaultValue() .withDescription("The user-specified annotations that are set to the TaskManager pod. The value could be " + "in the form of a1:v1,a2:v2"); public static final ConfigOption>> JOB_MANAGER_TOLERATIONS = key("kubernetes.jobmanager.tolerations") .mapType() .asList() .noDefaultValue() .withDescription("The user-specified tolerations to be set to the JobManager pod. The value should be " + "in the form of key:key1,operator:Equal,value:value1,effect:NoSchedule;" + "key:key2,operator:Exists,effect:NoExecute,tolerationSeconds:6000"); public static final ConfigOption>> TASK_MANAGER_TOLERATIONS = key("kubernetes.taskmanager.tolerations") .mapType() .asList() .noDefaultValue() .withDescription("The user-specified tolerations to be set to the TaskManager pod. The value should be " + "in the form of key:key1,operator:Equal,value:value1,effect:NoSchedule;" + "key:key2,operator:Exists,effect:NoExecute,tolerationSeconds:6000"); public static final ConfigOption> REST_SERVICE_ANNOTATIONS = key("kubernetes.rest-service.annotations") .mapType() .noDefaultValue() .withDescription("The user-specified annotations that are set to the rest Service. The value should be " + "in the form of a1:v1,a2:v2"); /** Defines the configuration key of that external resource in Kubernetes. This is used as a suffix in an actual config. */ public static final String EXTERNAL_RESOURCE_KUBERNETES_CONFIG_KEY_SUFFIX = "kubernetes.config-key"; public static final ConfigOption> KUBERNETES_SECRETS = key("kubernetes.secrets") .mapType() .noDefaultValue() .withDescription( Description.builder() .text("The user-specified secrets that will be mounted into Flink container. The value should be in " + "the form of %s.", code("foo:/opt/secrets-foo,bar:/opt/secrets-bar")) .build()); public static final ConfigOption>> KUBERNETES_ENV_SECRET_KEY_REF = key("kubernetes.env.secretKeyRef") .mapType() .asList() .noDefaultValue() .withDescription( Description.builder() .text("The user-specified secrets to set env variables in Flink container. The value should be in " + "the form of %s.", code("env:FOO_ENV,secret:foo_secret,key:foo_key;env:BAR_ENV,secret:bar_secret,key:bar_key")) .build()); /** * If configured, Flink will add "resources.limits.>config-key<" and "resources.requests.>config-key<" to the main * container of TaskExecutor and set the value to {@link ExternalResourceOptions#EXTERNAL_RESOURCE_AMOUNT}. * *

It is intentionally included into user docs while unused. */ @SuppressWarnings("unused") public static final ConfigOption EXTERNAL_RESOURCE_KUBERNETES_CONFIG_KEY = key(ExternalResourceOptions.genericKeyWithSuffix(EXTERNAL_RESOURCE_KUBERNETES_CONFIG_KEY_SUFFIX)) .stringType() .noDefaultValue() .withDescription("If configured, Flink will add \"resources.limits.\" and \"resources.requests.\" " + "to the main container of TaskExecutor and set the value to the value of " + ExternalResourceOptions.EXTERNAL_RESOURCE_AMOUNT.key() + "."); public static final ConfigOption KUBERNETES_TRANSACTIONAL_OPERATION_MAX_RETRIES = key("kubernetes.transactional-operation.max-retries") .intType() .defaultValue(5) .withDescription( Description.builder() .text("Defines the number of Kubernetes transactional operation retries before the " + "client gives up. For example, %s.", code("FlinkKubeClient#checkAndUpdateConfigMap")) .build()); public static final ConfigOption KUBERNETES_CLIENT_IO_EXECUTOR_POOL_SIZE = ConfigOptions.key("kubernetes.client.io-pool.size") .intType() .defaultValue(4) .withDescription( "The size of the IO executor pool used by the Kubernetes client to execute blocking IO operations " + "(e.g. start/stop TaskManager pods, update leader related ConfigMaps, etc.). " + "Increasing the pool size allows to run more IO operations concurrently."); public static final ConfigOption KUBERNETES_JOBMANAGER_REPLICAS = key("kubernetes.jobmanager.replicas") .intType() .defaultValue(1) .withDescription( "Specify how many JobManager pods will be started simultaneously. " + "Configure the value to greater than 1 to start standby JobManagers. " + "It will help to achieve faster recovery. " + "Notice that high availability should be enabled when starting standby JobManagers."); public static final ConfigOption KUBERNETES_HOSTNETWORK_ENABLED = key("kubernetes.hostnetwork.enabled") .booleanType() .defaultValue(false) .withDescription( "Whether to enable HostNetwork mode. " + "The HostNetwork allows the pod could use the node network namespace instead " + "of the individual pod network namespace. Please note that the JobManager " + "service account should have the permission to update Kubernetes service."); private static String getDefaultFlinkImage() { // The default container image that ties to the exact needed versions of both Flink and Scala. boolean snapshot = EnvironmentInformation.getVersion().toLowerCase(Locale.ENGLISH).contains("snapshot"); String tag = snapshot ? "latest" : EnvironmentInformation.getVersion() + "-scala_" + EnvironmentInformation.getScalaVersion(); return "flink:" + tag; } /** * The flink rest service exposed type. */ public enum ServiceExposedType { ClusterIP, NodePort, LoadBalancer } /** * The container image pull policy. */ public enum ImagePullPolicy { IfNotPresent, Always, Never } /** This class is not meant to be instantiated. */ private KubernetesConfigOptions() {} } ================================================ FILE: flink-learning-k8s/flink-k8s/src/main/java/org/apache/flink/kubernetes/configuration/KubernetesConfigOptionsInternal.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.flink.kubernetes.configuration; import org.apache.flink.annotation.Internal; import org.apache.flink.configuration.ConfigOption; import org.apache.flink.configuration.ConfigOptions; /** * Kubernetes configuration options that are not meant to be set by the user. */ @Internal public class KubernetesConfigOptionsInternal { public static final ConfigOption ENTRY_POINT_CLASS = ConfigOptions .key("kubernetes.internal.jobmanager.entrypoint.class") .stringType() .noDefaultValue() .withDescription("The entrypoint class for jobmanager. It will be set in kubernetesClusterDescriptor."); /** This class is not meant to be instantiated. */ private KubernetesConfigOptionsInternal() {} } ================================================ FILE: flink-learning-k8s/flink-k8s/src/main/java/org/apache/flink/kubernetes/configuration/KubernetesDeploymentTarget.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.flink.kubernetes.configuration; import org.apache.flink.annotation.Internal; import org.apache.flink.configuration.Configuration; import org.apache.flink.configuration.DeploymentOptions; import java.util.Arrays; import java.util.stream.Collectors; import static org.apache.flink.util.Preconditions.checkNotNull; /** * A class containing all the supported deployment target names for Kubernetes. */ @Internal public enum KubernetesDeploymentTarget { SESSION("kubernetes-session"), APPLICATION("kubernetes-application"); private final String name; KubernetesDeploymentTarget(final String name) { this.name = checkNotNull(name); } public static KubernetesDeploymentTarget fromConfig(final Configuration configuration) { checkNotNull(configuration); final String deploymentTargetStr = configuration.get(DeploymentOptions.TARGET); final KubernetesDeploymentTarget deploymentTarget = getFromName(deploymentTargetStr); if (deploymentTarget == null) { throw new IllegalArgumentException( "Unknown Kubernetes deployment target \"" + deploymentTargetStr + "\"." + " The available options are: " + options()); } return deploymentTarget; } public String getName() { return name; } public static boolean isValidKubernetesTarget(final String configValue) { return configValue != null && Arrays.stream(KubernetesDeploymentTarget.values()) .anyMatch(kubernetesDeploymentTarget -> kubernetesDeploymentTarget.name.equalsIgnoreCase(configValue)); } private static KubernetesDeploymentTarget getFromName(final String deploymentTarget) { if (deploymentTarget == null) { return null; } if (SESSION.name.equalsIgnoreCase(deploymentTarget)) { return SESSION; } else if (APPLICATION.name.equalsIgnoreCase(deploymentTarget)) { return APPLICATION; } return null; } private static String options() { return Arrays.stream(KubernetesDeploymentTarget.values()) .map(KubernetesDeploymentTarget::getName) .collect(Collectors.joining(",")); } } ================================================ FILE: flink-learning-k8s/flink-k8s/src/main/java/org/apache/flink/kubernetes/configuration/KubernetesHighAvailabilityOptions.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.flink.kubernetes.configuration; import org.apache.flink.annotation.PublicEvolving; import org.apache.flink.annotation.docs.Documentation; import org.apache.flink.configuration.ConfigOption; import java.time.Duration; import static org.apache.flink.configuration.ConfigOptions.key; /** * The set of configuration options relating to Kubernetes high-availability settings. */ @PublicEvolving public class KubernetesHighAvailabilityOptions { @Documentation.Section(Documentation.Sections.EXPERT_KUBERNETES_HIGH_AVAILABILITY) public static final ConfigOption KUBERNETES_LEASE_DURATION = key("high-availability.kubernetes.leader-election.lease-duration") .durationType() .defaultValue(Duration.ofSeconds(15)) .withDescription("Define the lease duration for the Kubernetes leader election. The leader will " + "continuously renew its lease time to indicate its existence. And the followers will do a lease " + "checking against the current time. \"renewTime + leaseDuration > now\" means the leader is alive."); @Documentation.Section(Documentation.Sections.EXPERT_KUBERNETES_HIGH_AVAILABILITY) public static final ConfigOption KUBERNETES_RENEW_DEADLINE = key("high-availability.kubernetes.leader-election.renew-deadline") .durationType() .defaultValue(Duration.ofSeconds(15)) .withDescription("Defines the deadline duration when the leader tries to renew the lease. The leader will " + "give up its leadership if it cannot successfully renew the lease in the given time."); @Documentation.Section(Documentation.Sections.EXPERT_KUBERNETES_HIGH_AVAILABILITY) public static final ConfigOption KUBERNETES_RETRY_PERIOD = key("high-availability.kubernetes.leader-election.retry-period") .durationType() .defaultValue(Duration.ofSeconds(5)) .withDescription("Defines the pause duration between consecutive retries. All the contenders, including " + "the current leader and all other followers, periodically try to acquire/renew the leadership if " + "possible at this interval."); /** Not intended to be instantiated. */ private KubernetesHighAvailabilityOptions() {} } ================================================ FILE: flink-learning-k8s/flink-k8s/src/main/java/org/apache/flink/kubernetes/configuration/KubernetesLeaderElectionConfiguration.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.flink.kubernetes.configuration; import org.apache.flink.configuration.Configuration; import java.time.Duration; /** * Configuration specific to {@link org.apache.flink.kubernetes.kubeclient.resources.KubernetesLeaderElector}. */ public class KubernetesLeaderElectionConfiguration { private final String clusterId; private final String configMapName; private final String lockIdentity; private final Duration leaseDuration; private final Duration renewDeadline; private final Duration retryPeriod; public KubernetesLeaderElectionConfiguration(String configMapName, String lockIdentity, Configuration config) { this.clusterId = config.getString(KubernetesConfigOptions.CLUSTER_ID); this.configMapName = configMapName; this.lockIdentity = lockIdentity; this.leaseDuration = config.get(KubernetesHighAvailabilityOptions.KUBERNETES_LEASE_DURATION); this.renewDeadline = config.get(KubernetesHighAvailabilityOptions.KUBERNETES_RENEW_DEADLINE); this.retryPeriod = config.get(KubernetesHighAvailabilityOptions.KUBERNETES_RETRY_PERIOD); } public String getClusterId() { return clusterId; } public String getConfigMapName() { return configMapName; } public String getLockIdentity() { return lockIdentity; } public Duration getLeaseDuration() { return leaseDuration; } public Duration getRenewDeadline() { return renewDeadline; } public Duration getRetryPeriod() { return retryPeriod; } } ================================================ FILE: flink-learning-k8s/flink-k8s/src/main/java/org/apache/flink/kubernetes/configuration/KubernetesResourceManagerDriverConfiguration.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.flink.kubernetes.configuration; import org.apache.flink.api.common.time.Time; import javax.annotation.Nullable; /** * Configuration specific to {@link org.apache.flink.kubernetes.KubernetesResourceManagerDriver}. */ public class KubernetesResourceManagerDriverConfiguration { private final String clusterId; private final Time podCreationRetryInterval; @Nullable private final String webInterfaceUrl; public KubernetesResourceManagerDriverConfiguration(String clusterId, Time podCreationRetryInterval, @Nullable String webInterfaceUrl) { this.clusterId = clusterId; this.podCreationRetryInterval = podCreationRetryInterval; this.webInterfaceUrl = webInterfaceUrl; } public String getClusterId() { return clusterId; } public Time getPodCreationRetryInterval() { return podCreationRetryInterval; } @Nullable public String getWebInterfaceUrl() { return webInterfaceUrl; } } ================================================ FILE: flink-learning-k8s/flink-k8s/src/main/java/org/apache/flink/kubernetes/entrypoint/KubernetesApplicationClusterEntrypoint.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.flink.kubernetes.entrypoint; import org.apache.flink.annotation.Internal; import org.apache.flink.client.deployment.application.ApplicationClusterEntryPoint; import org.apache.flink.client.deployment.application.ApplicationConfiguration; import org.apache.flink.client.deployment.application.ClassPathPackagedProgramRetriever; import org.apache.flink.client.program.PackagedProgram; import org.apache.flink.client.program.PackagedProgramRetriever; import org.apache.flink.client.program.PackagedProgramUtils; import org.apache.flink.configuration.Configuration; import org.apache.flink.kubernetes.utils.KubernetesUtils; import org.apache.flink.runtime.entrypoint.ClusterEntrypoint; import org.apache.flink.runtime.entrypoint.ClusterEntrypointUtils; import org.apache.flink.runtime.entrypoint.DynamicParametersConfigurationParserFactory; import org.apache.flink.runtime.util.EnvironmentInformation; import org.apache.flink.runtime.util.JvmShutdownSafeguard; import org.apache.flink.runtime.util.SignalHandler; import org.apache.flink.util.FlinkException; import org.apache.flink.util.Preconditions; import javax.annotation.Nullable; import java.io.File; import java.io.IOException; import java.util.List; /** * An {@link ApplicationClusterEntryPoint} for Kubernetes. */ @Internal public final class KubernetesApplicationClusterEntrypoint extends ApplicationClusterEntryPoint { private KubernetesApplicationClusterEntrypoint( final Configuration configuration, final PackagedProgram program) { super(configuration, program, KubernetesResourceManagerFactory.getInstance()); } public static void main(final String[] args) { // startup checks and logging EnvironmentInformation.logEnvironmentInfo(LOG, KubernetesApplicationClusterEntrypoint.class.getSimpleName(), args); SignalHandler.register(LOG); JvmShutdownSafeguard.installAsShutdownHook(LOG); //-Dkey=value 的参数 final Configuration dynamicParameters = ClusterEntrypointUtils.parseParametersOrExit( args, new DynamicParametersConfigurationParserFactory(), KubernetesApplicationClusterEntrypoint.class); final Configuration configuration = KubernetesEntrypointUtils.loadConfiguration(dynamicParameters); PackagedProgram program = null; try { //获取到作业信息(jar、参数、启动 main 类) program = getPackagedProgram(configuration); } catch (Exception e) { LOG.error("Could not create application program.", e); System.exit(1); } try { //配置读取到的配置 configureExecution(configuration, program); } catch (Exception e) { LOG.error("Could not apply application configuration.", e); System.exit(1); } //todo: final KubernetesApplicationClusterEntrypoint kubernetesApplicationClusterEntrypoint = new KubernetesApplicationClusterEntrypoint(configuration, program); ClusterEntrypoint.runClusterEntrypoint(kubernetesApplicationClusterEntrypoint); } private static PackagedProgram getPackagedProgram(final Configuration configuration) throws IOException, FlinkException { //获取作业的参数和启动类 final ApplicationConfiguration applicationConfiguration = ApplicationConfiguration.fromConfiguration(configuration); final PackagedProgramRetriever programRetriever = getPackagedProgramRetriever( configuration, applicationConfiguration.getProgramArguments(), applicationConfiguration.getApplicationClassName()); return programRetriever.getPackagedProgram(); } private static PackagedProgramRetriever getPackagedProgramRetriever( final Configuration configuration, final String[] programArguments, @Nullable final String jobClassName) throws IOException { final File userLibDir = ClusterEntrypointUtils.tryFindUserLibDirectory().orElse(null); final ClassPathPackagedProgramRetriever.Builder retrieverBuilder = ClassPathPackagedProgramRetriever .newBuilder(programArguments) .setUserLibDirectory(userLibDir) .setJobClassName(jobClassName); // No need to do pipelineJars validation if it is a PyFlink job. if (!(PackagedProgramUtils.isPython(jobClassName) || PackagedProgramUtils.isPython(programArguments))) { final List pipelineJars = KubernetesUtils.checkJarFileForApplicationMode(configuration); Preconditions.checkArgument(pipelineJars.size() == 1, "Should only have one jar"); retrieverBuilder.setJarFile(pipelineJars.get(0)); } return retrieverBuilder.build(); } } ================================================ FILE: flink-learning-k8s/flink-k8s/src/main/java/org/apache/flink/kubernetes/entrypoint/KubernetesEntrypointUtils.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.flink.kubernetes.entrypoint; import org.apache.flink.configuration.*; import org.apache.flink.kubernetes.KubernetesClusterDescriptor; import org.apache.flink.kubernetes.utils.Constants; import org.apache.flink.kubernetes.utils.KubernetesUtils; import org.apache.flink.runtime.jobmanager.HighAvailabilityMode; import org.apache.flink.util.Preconditions; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * This class contains utility methods for the {@link KubernetesSessionClusterEntrypoint}. */ class KubernetesEntrypointUtils { private static final Logger LOG = LoggerFactory.getLogger(KubernetesEntrypointUtils.class); /** * For non-HA cluster, {@link JobManagerOptions#ADDRESS} has be set to Kubernetes service name on client side. See * {@link KubernetesClusterDescriptor#deployClusterInternal}. So the TaskManager will use service address to contact * with JobManager. * For HA cluster, {@link JobManagerOptions#ADDRESS} will be set to the pod ip address. The TaskManager use Zookeeper * or other high-availability service to find the address of JobManager. * * @return Updated configuration */ static Configuration loadConfiguration(Configuration dynamicParameters) { final String configDir = System.getenv(ConfigConstants.ENV_FLINK_CONF_DIR); Preconditions.checkNotNull( configDir, "Flink configuration directory (%s) in environment should not be null!", ConfigConstants.ENV_FLINK_CONF_DIR); //读取 flink yaml 文件到配置中 final Configuration configuration = GlobalConfiguration.loadConfiguration( configDir, dynamicParameters); if (KubernetesUtils.isHostNetwork(configuration)) { configuration.setString(RestOptions.BIND_PORT, "0"); configuration.setInteger(JobManagerOptions.PORT, 0); configuration.setString(BlobServerOptions.PORT, "0"); configuration.setString(HighAvailabilityOptions.HA_JOB_MANAGER_PORT_RANGE, "0"); configuration.setString(TaskManagerOptions.RPC_PORT, "0"); } if (HighAvailabilityMode.isHighAvailabilityModeActivated(configuration)) { final String ipAddress = System.getenv().get(Constants.ENV_FLINK_POD_IP_ADDRESS); Preconditions.checkState( ipAddress != null, "JobManager ip address environment variable %s not set", Constants.ENV_FLINK_POD_IP_ADDRESS); //将从环境变量里面拿到的 POD IP 地址设置 jm rpc/rest 地址 configuration.setString(JobManagerOptions.ADDRESS, ipAddress); configuration.setString(RestOptions.ADDRESS, ipAddress); } return configuration; } private KubernetesEntrypointUtils() {} } ================================================ FILE: flink-learning-k8s/flink-k8s/src/main/java/org/apache/flink/kubernetes/entrypoint/KubernetesResourceManagerFactory.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.flink.kubernetes.entrypoint; import org.apache.flink.api.common.time.Time; import org.apache.flink.configuration.Configuration; import org.apache.flink.kubernetes.KubernetesResourceManagerDriver; import org.apache.flink.kubernetes.KubernetesWorkerNode; import org.apache.flink.kubernetes.configuration.KubernetesConfigOptions; import org.apache.flink.kubernetes.configuration.KubernetesResourceManagerDriverConfiguration; import org.apache.flink.kubernetes.kubeclient.FlinkKubeClientFactory; import org.apache.flink.runtime.resourcemanager.ResourceManagerRuntimeServicesConfiguration; import org.apache.flink.runtime.resourcemanager.active.ActiveResourceManager; import org.apache.flink.runtime.resourcemanager.active.ActiveResourceManagerFactory; import org.apache.flink.runtime.resourcemanager.active.ResourceManagerDriver; import org.apache.flink.util.ConfigurationException; import javax.annotation.Nullable; /** * {@link ActiveResourceManagerFactory} implementation which creates {@link ActiveResourceManager} with {@link KubernetesResourceManagerDriver}. */ public class KubernetesResourceManagerFactory extends ActiveResourceManagerFactory { private static final KubernetesResourceManagerFactory INSTANCE = new KubernetesResourceManagerFactory(); private static final Time POD_CREATION_RETRY_INTERVAL = Time.seconds(3L); private KubernetesResourceManagerFactory() {} public static KubernetesResourceManagerFactory getInstance() { return INSTANCE; } @Override protected ResourceManagerDriver createResourceManagerDriver( Configuration configuration, @Nullable String webInterfaceUrl, String rpcAddress) { final KubernetesResourceManagerDriverConfiguration kubernetesResourceManagerDriverConfiguration = new KubernetesResourceManagerDriverConfiguration( configuration.getString(KubernetesConfigOptions.CLUSTER_ID), POD_CREATION_RETRY_INTERVAL, webInterfaceUrl); return new KubernetesResourceManagerDriver( configuration, FlinkKubeClientFactory.getInstance() .fromConfiguration(configuration, "resource-manager"), kubernetesResourceManagerDriverConfiguration); } @Override protected ResourceManagerRuntimeServicesConfiguration createResourceManagerRuntimeServicesConfiguration( Configuration configuration) throws ConfigurationException { return ResourceManagerRuntimeServicesConfiguration.fromConfiguration(configuration, KubernetesWorkerResourceSpecFactory.INSTANCE); } } ================================================ FILE: flink-learning-k8s/flink-k8s/src/main/java/org/apache/flink/kubernetes/entrypoint/KubernetesSessionClusterEntrypoint.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.flink.kubernetes.entrypoint; import org.apache.flink.configuration.Configuration; import org.apache.flink.runtime.entrypoint.ClusterEntrypoint; import org.apache.flink.runtime.entrypoint.ClusterEntrypointUtils; import org.apache.flink.runtime.entrypoint.DynamicParametersConfigurationParserFactory; import org.apache.flink.runtime.entrypoint.SessionClusterEntrypoint; import org.apache.flink.runtime.entrypoint.component.DefaultDispatcherResourceManagerComponentFactory; import org.apache.flink.runtime.entrypoint.component.DispatcherResourceManagerComponentFactory; import org.apache.flink.runtime.util.EnvironmentInformation; import org.apache.flink.runtime.util.JvmShutdownSafeguard; import org.apache.flink.runtime.util.SignalHandler; /** * Entry point for a Kubernetes session cluster. */ public class KubernetesSessionClusterEntrypoint extends SessionClusterEntrypoint { public KubernetesSessionClusterEntrypoint(Configuration configuration) { super(configuration); } @Override protected DispatcherResourceManagerComponentFactory createDispatcherResourceManagerComponentFactory(Configuration configuration) { return DefaultDispatcherResourceManagerComponentFactory.createSessionComponentFactory( KubernetesResourceManagerFactory.getInstance()); } public static void main(String[] args) { // startup checks and logging EnvironmentInformation.logEnvironmentInfo(LOG, KubernetesSessionClusterEntrypoint.class.getSimpleName(), args); SignalHandler.register(LOG); JvmShutdownSafeguard.installAsShutdownHook(LOG); final Configuration dynamicParameters = ClusterEntrypointUtils.parseParametersOrExit( args, new DynamicParametersConfigurationParserFactory(), KubernetesSessionClusterEntrypoint.class); final ClusterEntrypoint entrypoint = new KubernetesSessionClusterEntrypoint( KubernetesEntrypointUtils.loadConfiguration(dynamicParameters)); ClusterEntrypoint.runClusterEntrypoint(entrypoint); } } ================================================ FILE: flink-learning-k8s/flink-k8s/src/main/java/org/apache/flink/kubernetes/entrypoint/KubernetesWorkerResourceSpecFactory.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.flink.kubernetes.entrypoint; import org.apache.flink.annotation.VisibleForTesting; import org.apache.flink.api.common.resources.CPUResource; import org.apache.flink.configuration.Configuration; import org.apache.flink.kubernetes.configuration.KubernetesConfigOptions; import org.apache.flink.runtime.clusterframework.TaskExecutorProcessUtils; import org.apache.flink.runtime.resourcemanager.WorkerResourceSpec; import org.apache.flink.runtime.resourcemanager.WorkerResourceSpecFactory; /** * Implementation of {@link WorkerResourceSpecFactory} for Kubernetes deployments. */ public class KubernetesWorkerResourceSpecFactory extends WorkerResourceSpecFactory { public static final KubernetesWorkerResourceSpecFactory INSTANCE = new KubernetesWorkerResourceSpecFactory(); private KubernetesWorkerResourceSpecFactory() {} @Override public WorkerResourceSpec createDefaultWorkerResourceSpec(Configuration configuration) { return workerResourceSpecFromConfigAndCpu(configuration, getDefaultCpus(configuration)); } @VisibleForTesting static CPUResource getDefaultCpus(Configuration configuration) { double fallback = configuration.getDouble(KubernetesConfigOptions.TASK_MANAGER_CPU); return TaskExecutorProcessUtils.getCpuCoresWithFallback(configuration, fallback); } } ================================================ FILE: flink-learning-k8s/flink-k8s/src/main/java/org/apache/flink/kubernetes/executors/KubernetesSessionClusterExecutor.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.flink.kubernetes.executors; import org.apache.flink.annotation.Internal; import org.apache.flink.client.deployment.executors.AbstractSessionClusterExecutor; import org.apache.flink.core.execution.PipelineExecutor; import org.apache.flink.kubernetes.KubernetesClusterClientFactory; import org.apache.flink.kubernetes.configuration.KubernetesDeploymentTarget; /** * The {@link PipelineExecutor} to be used when executing a job on an already running cluster. */ @Internal public class KubernetesSessionClusterExecutor extends AbstractSessionClusterExecutor { public static final String NAME = KubernetesDeploymentTarget.SESSION.getName(); public KubernetesSessionClusterExecutor() { super(new KubernetesClusterClientFactory()); } } ================================================ FILE: flink-learning-k8s/flink-k8s/src/main/java/org/apache/flink/kubernetes/executors/KubernetesSessionClusterExecutorFactory.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.flink.kubernetes.executors; import org.apache.flink.annotation.Internal; import org.apache.flink.configuration.Configuration; import org.apache.flink.configuration.DeploymentOptions; import org.apache.flink.core.execution.PipelineExecutor; import org.apache.flink.core.execution.PipelineExecutorFactory; import javax.annotation.Nonnull; /** * An {@link PipelineExecutorFactory} for executing jobs on an existing (session) cluster. */ @Internal public class KubernetesSessionClusterExecutorFactory implements PipelineExecutorFactory { @Override public String getName() { return KubernetesSessionClusterExecutor.NAME; } @Override public boolean isCompatibleWith(@Nonnull final Configuration configuration) { return configuration.get(DeploymentOptions.TARGET) .equalsIgnoreCase(KubernetesSessionClusterExecutor.NAME); } @Override public PipelineExecutor getExecutor(@Nonnull final Configuration configuration) { return new KubernetesSessionClusterExecutor(); } } ================================================ FILE: flink-learning-k8s/flink-k8s/src/main/java/org/apache/flink/kubernetes/highavailability/KubernetesCheckpointIDCounter.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.flink.kubernetes.highavailability; import org.apache.flink.api.common.JobStatus; import org.apache.flink.kubernetes.kubeclient.FlinkKubeClient; import org.apache.flink.kubernetes.kubeclient.resources.KubernetesConfigMap; import org.apache.flink.kubernetes.kubeclient.resources.KubernetesException; import org.apache.flink.kubernetes.kubeclient.resources.KubernetesLeaderElector; import org.apache.flink.runtime.checkpoint.CheckpointIDCounter; import org.apache.flink.util.FlinkRuntimeException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Optional; import java.util.concurrent.atomic.AtomicLong; import static org.apache.flink.kubernetes.utils.Constants.CHECKPOINT_COUNTER_KEY; import static org.apache.flink.util.Preconditions.checkNotNull; /** * {@link CheckpointIDCounter} implementation for Kubernetes. The counter will be stored in * JobManager-{@link org.apache.flink.api.common.JobID}-leader ConfigMap. The key is * {@link org.apache.flink.kubernetes.utils.Constants#CHECKPOINT_COUNTER_KEY}, * and value is counter value. */ public class KubernetesCheckpointIDCounter implements CheckpointIDCounter { private static final Logger LOG = LoggerFactory.getLogger(KubernetesCheckpointIDCounter.class); private final FlinkKubeClient kubeClient; private final String configMapName; private final String lockIdentity; private boolean running; public KubernetesCheckpointIDCounter(FlinkKubeClient kubeClient, String configMapName, String lockIdentity) { this.kubeClient = checkNotNull(kubeClient); this.configMapName = checkNotNull(configMapName); this.lockIdentity = checkNotNull(lockIdentity); this.running = false; } @Override public void start() { if (!running) { running = true; } } @Override public void shutdown(JobStatus jobStatus) { if (!running) { return; } running = false; LOG.info("Shutting down."); if (jobStatus.isGloballyTerminalState()) { LOG.info("Removing counter from ConfigMap {}", configMapName); kubeClient.checkAndUpdateConfigMap( configMapName, configMap -> { if (KubernetesLeaderElector.hasLeadership(configMap, lockIdentity)) { configMap.getData().remove(CHECKPOINT_COUNTER_KEY); return Optional.of(configMap); } return Optional.empty(); }); } } @Override public long getAndIncrement() throws Exception { final AtomicLong current = new AtomicLong(); final boolean updated = kubeClient.checkAndUpdateConfigMap( configMapName, configMap -> { if (KubernetesLeaderElector.hasLeadership(configMap, lockIdentity)) { final long currentValue = getCurrentCounter(configMap); current.set(currentValue); configMap.getData().put(CHECKPOINT_COUNTER_KEY, String.valueOf(currentValue + 1)); return Optional.of(configMap); } return Optional.empty(); } ).get(); if (updated) { return current.get(); } else { throw new KubernetesException("Failed to update ConfigMap " + configMapName + " since current KubernetesCheckpointIDCounter does not have the leadership."); } } @Override public long get() { return kubeClient.getConfigMap(configMapName) .map(this::getCurrentCounter) .orElseThrow(() -> new FlinkRuntimeException( new KubernetesException("ConfigMap " + configMapName + " does not exist."))); } @Override public void setCount(long newCount) throws Exception { kubeClient.checkAndUpdateConfigMap( configMapName, configMap -> { if (KubernetesLeaderElector.hasLeadership(configMap, lockIdentity)) { final String existing = configMap.getData().get(CHECKPOINT_COUNTER_KEY); final String newValue = String.valueOf(newCount); if (existing == null || !existing.equals(newValue)) { configMap.getData().put(CHECKPOINT_COUNTER_KEY, String.valueOf(newCount)); return Optional.of(configMap); } } return Optional.empty(); } ).get(); } private long getCurrentCounter(KubernetesConfigMap configMap) { if (configMap.getData().containsKey(CHECKPOINT_COUNTER_KEY)) { return Long.valueOf(configMap.getData().get(CHECKPOINT_COUNTER_KEY)); } else { // Initial checkpoint id return 1; } } } ================================================ FILE: flink-learning-k8s/flink-k8s/src/main/java/org/apache/flink/kubernetes/highavailability/KubernetesCheckpointRecoveryFactory.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.flink.kubernetes.highavailability; import org.apache.flink.api.common.JobID; import org.apache.flink.configuration.Configuration; import org.apache.flink.kubernetes.kubeclient.FlinkKubeClient; import org.apache.flink.kubernetes.utils.KubernetesUtils; import org.apache.flink.runtime.checkpoint.CheckpointIDCounter; import org.apache.flink.runtime.checkpoint.CheckpointRecoveryFactory; import org.apache.flink.runtime.checkpoint.CompletedCheckpointStore; import java.util.concurrent.Executor; import java.util.function.Function; import static org.apache.flink.util.Preconditions.checkNotNull; /** * Factory to create {@link CompletedCheckpointStore} and {@link CheckpointIDCounter}. */ public class KubernetesCheckpointRecoveryFactory implements CheckpointRecoveryFactory { private final FlinkKubeClient kubeClient; private final Executor executor; // Function to get the ConfigMap name for checkpoint. Input is job id, and output is ConfigMap name. private final Function getConfigMapNameFunction; private final Configuration configuration; private final String lockIdentity; /** * Create a KubernetesCheckpointRecoveryFactory. * * @param kubeClient Kubernetes client * @param configuration Flink configuration * @param executor IO executor to run blocking calls * @param function Function to get the ConfigMap name for checkpoint. * @param lockIdentity Lock identity of current HA service */ public KubernetesCheckpointRecoveryFactory( FlinkKubeClient kubeClient, Configuration configuration, Executor executor, Function function, String lockIdentity) { this.kubeClient = checkNotNull(kubeClient); this.configuration = checkNotNull(configuration); this.executor = checkNotNull(executor); this.getConfigMapNameFunction = checkNotNull(function); this.lockIdentity = checkNotNull(lockIdentity); } @Override public CompletedCheckpointStore createCheckpointStore( JobID jobID, int maxNumberOfCheckpointsToRetain, ClassLoader userClassLoader) throws Exception { final String configMapName = getConfigMapNameFunction.apply(jobID); return KubernetesUtils.createCompletedCheckpointStore( configuration, kubeClient, executor, configMapName, lockIdentity, maxNumberOfCheckpointsToRetain); } @Override public CheckpointIDCounter createCheckpointIDCounter(JobID jobID) { return new KubernetesCheckpointIDCounter(kubeClient, getConfigMapNameFunction.apply(jobID), lockIdentity); } } ================================================ FILE: flink-learning-k8s/flink-k8s/src/main/java/org/apache/flink/kubernetes/highavailability/KubernetesCheckpointStoreUtil.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.flink.kubernetes.highavailability; import org.apache.flink.runtime.checkpoint.CheckpointStoreUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import static org.apache.flink.kubernetes.utils.Constants.CHECKPOINT_ID_KEY_PREFIX; /** * Singleton {@link CheckpointStoreUtil} implementation for Kubernetes. * */ public enum KubernetesCheckpointStoreUtil implements CheckpointStoreUtil { INSTANCE; private static final Logger LOG = LoggerFactory.getLogger(KubernetesCheckpointStoreUtil.class); /** * Convert a checkpoint id into a ConfigMap key. * * @param checkpointId to convert to the key * * @return key created from the given checkpoint id */ @Override public String checkpointIDToName(long checkpointId) { return CHECKPOINT_ID_KEY_PREFIX + String.format("%019d", checkpointId); } /** * Converts a key in ConfigMap to the checkpoint id. * * @param key in ConfigMap * * @return Checkpoint id parsed from the key */ @Override public long nameToCheckpointID(String key) { try { return Long.parseLong(key.substring(CHECKPOINT_ID_KEY_PREFIX.length())); } catch (NumberFormatException e) { LOG.warn("Could not parse checkpoint id from {}. This indicates that the " + "checkpoint id to path conversion has changed.", key); return INVALID_CHECKPOINT_ID; } } } ================================================ FILE: flink-learning-k8s/flink-k8s/src/main/java/org/apache/flink/kubernetes/highavailability/KubernetesHaServices.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.flink.kubernetes.highavailability; import org.apache.flink.api.common.JobID; import org.apache.flink.configuration.Configuration; import org.apache.flink.kubernetes.configuration.KubernetesConfigOptions; import org.apache.flink.kubernetes.configuration.KubernetesLeaderElectionConfiguration; import org.apache.flink.kubernetes.kubeclient.FlinkKubeClient; import org.apache.flink.kubernetes.utils.KubernetesUtils; import org.apache.flink.runtime.blob.BlobStoreService; import org.apache.flink.runtime.checkpoint.CheckpointRecoveryFactory; import org.apache.flink.runtime.highavailability.AbstractHaServices; import org.apache.flink.runtime.highavailability.RunningJobsRegistry; import org.apache.flink.runtime.jobmanager.JobGraphStore; import org.apache.flink.runtime.leaderelection.DefaultLeaderElectionService; import org.apache.flink.runtime.leaderelection.LeaderElectionService; import org.apache.flink.runtime.leaderretrieval.DefaultLeaderRetrievalService; import org.apache.flink.runtime.leaderretrieval.LeaderRetrievalService; import java.util.UUID; import java.util.concurrent.Executor; import static org.apache.flink.kubernetes.utils.Constants.LABEL_CONFIGMAP_TYPE_HIGH_AVAILABILITY; import static org.apache.flink.kubernetes.utils.Constants.NAME_SEPARATOR; import static org.apache.flink.util.Preconditions.checkNotNull; /** * An implementation of the {@link AbstractHaServices} using Apache ZooKeeper. * *

All the HA information relevant for a specific component will be stored in a single ConfigMap. * For example, the Dispatcher's ConfigMap would then contain the current leader, the running jobs * and the pointers to the persisted JobGraphs. * The JobManager's ConfigMap would then contain the current leader, the pointers to the checkpoints * and the checkpoint ID counter. * *

The ConfigMap name will be created with the pattern "{clusterId}-{componentName}-leader". Given that the cluster * id is configured to "k8s-ha-app1", then we could get the following ConfigMap names. * e.g. k8s-ha-app1-restserver-leader, k8s-ha-app1-00000000000000000000000000000000-jobmanager-leader * *

Note that underline("_") is not allowed in Kubernetes ConfigMap name. */ public class KubernetesHaServices extends AbstractHaServices { private final String clusterId; /** Kubernetes client. */ private final FlinkKubeClient kubeClient; private static final String RESOURCE_MANAGER_NAME = "resourcemanager"; private static final String DISPATCHER_NAME = "dispatcher"; private static final String JOB_MANAGER_NAME = "jobmanager"; private static final String REST_SERVER_NAME = "restserver"; private static final String LEADER_SUFFIX = "leader"; /** * Each {@link KubernetesHaServices} will have a dedicated lock identity for all the components above. Different * instances will have different identities. */ private final String lockIdentity; KubernetesHaServices( FlinkKubeClient kubeClient, Executor executor, Configuration config, BlobStoreService blobStoreService) { super(config, executor, blobStoreService); this.kubeClient = checkNotNull(kubeClient); this.clusterId = checkNotNull(config.get(KubernetesConfigOptions.CLUSTER_ID)); lockIdentity = UUID.randomUUID().toString(); } @Override public LeaderElectionService createLeaderElectionService(String leaderName) { final KubernetesLeaderElectionConfiguration leaderConfig = new KubernetesLeaderElectionConfiguration( leaderName, lockIdentity, configuration); return new DefaultLeaderElectionService( new KubernetesLeaderElectionDriverFactory(kubeClient, leaderConfig)); } @Override public LeaderRetrievalService createLeaderRetrievalService(String leaderName) { return new DefaultLeaderRetrievalService(new KubernetesLeaderRetrievalDriverFactory(kubeClient, leaderName)); } @Override public CheckpointRecoveryFactory createCheckpointRecoveryFactory() { return new KubernetesCheckpointRecoveryFactory( kubeClient, configuration, ioExecutor, this::getLeaderNameForJobManager, lockIdentity); } @Override public JobGraphStore createJobGraphStore() throws Exception { return KubernetesUtils.createJobGraphStore( configuration, kubeClient, getLeaderNameForDispatcher(), lockIdentity); } @Override public RunningJobsRegistry createRunningJobsRegistry() { return new KubernetesRunningJobsRegistry(kubeClient, getLeaderNameForDispatcher(), lockIdentity); } @Override public void internalClose() { kubeClient.close(); } @Override public void internalCleanup() throws Exception { kubeClient.deleteConfigMapsByLabels( KubernetesUtils.getConfigMapLabels(clusterId, LABEL_CONFIGMAP_TYPE_HIGH_AVAILABILITY)).get(); } @Override protected String getLeaderNameForResourceManager() { return getLeaderName(RESOURCE_MANAGER_NAME); } @Override protected String getLeaderNameForDispatcher() { return getLeaderName(DISPATCHER_NAME); } public String getLeaderNameForJobManager(final JobID jobID) { return getLeaderName(jobID.toString() + NAME_SEPARATOR + JOB_MANAGER_NAME); } @Override protected String getLeaderNameForRestServer() { return getLeaderName(REST_SERVER_NAME); } private String getLeaderName(String component) { return clusterId + NAME_SEPARATOR + component + NAME_SEPARATOR + LEADER_SUFFIX; } } ================================================ FILE: flink-learning-k8s/flink-k8s/src/main/java/org/apache/flink/kubernetes/highavailability/KubernetesHaServicesFactory.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.flink.kubernetes.highavailability; import org.apache.flink.configuration.Configuration; import org.apache.flink.kubernetes.kubeclient.FlinkKubeClientFactory; import org.apache.flink.runtime.blob.BlobUtils; import org.apache.flink.runtime.highavailability.HighAvailabilityServices; import org.apache.flink.runtime.highavailability.HighAvailabilityServicesFactory; import java.util.concurrent.Executor; /** * Factory for creating Kubernetes high availability services. */ public class KubernetesHaServicesFactory implements HighAvailabilityServicesFactory { @Override public HighAvailabilityServices createHAServices(Configuration configuration, Executor executor) throws Exception { return new KubernetesHaServices( FlinkKubeClientFactory.getInstance() .fromConfiguration(configuration, "kubernetes-ha-services"), executor, configuration, BlobUtils.createBlobStoreFromConfig(configuration)); } } ================================================ FILE: flink-learning-k8s/flink-k8s/src/main/java/org/apache/flink/kubernetes/highavailability/KubernetesJobGraphStoreUtil.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.flink.kubernetes.highavailability; import org.apache.flink.api.common.JobID; import org.apache.flink.kubernetes.utils.Constants; import org.apache.flink.runtime.jobmanager.JobGraphStoreUtil; import static org.apache.flink.kubernetes.utils.Constants.JOB_GRAPH_STORE_KEY_PREFIX; /** * Singleton {@link JobGraphStoreUtil} implementation for Kubernetes. * */ public enum KubernetesJobGraphStoreUtil implements JobGraphStoreUtil { INSTANCE; /** * Convert a key in ConfigMap to {@link JobID}. The key is stored with prefix * {@link Constants#JOB_GRAPH_STORE_KEY_PREFIX}. * * @param key job graph key in ConfigMap. * * @return the parsed {@link JobID}. */ public JobID nameToJobID(String key) { return JobID.fromHexString(key.substring(JOB_GRAPH_STORE_KEY_PREFIX.length())); } /** * Convert a {@link JobID} to config map key. We will add prefix {@link Constants#JOB_GRAPH_STORE_KEY_PREFIX}. * * @param jobID job id * * @return a key to store job graph in the ConfigMap */ public String jobIDToName(JobID jobID) { return JOB_GRAPH_STORE_KEY_PREFIX + jobID; } } ================================================ FILE: flink-learning-k8s/flink-k8s/src/main/java/org/apache/flink/kubernetes/highavailability/KubernetesLeaderElectionDriver.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.flink.kubernetes.highavailability; import org.apache.flink.kubernetes.configuration.KubernetesLeaderElectionConfiguration; import org.apache.flink.kubernetes.kubeclient.FlinkKubeClient; import org.apache.flink.kubernetes.kubeclient.resources.*; import org.apache.flink.kubernetes.utils.KubernetesUtils; import org.apache.flink.runtime.leaderelection.LeaderElectionDriver; import org.apache.flink.runtime.leaderelection.LeaderElectionEventHandler; import org.apache.flink.runtime.leaderelection.LeaderElectionException; import org.apache.flink.runtime.leaderelection.LeaderInformation; import org.apache.flink.runtime.rpc.FatalErrorHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.annotation.concurrent.GuardedBy; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.UUID; import static org.apache.flink.kubernetes.utils.Constants.*; import static org.apache.flink.kubernetes.utils.KubernetesUtils.checkConfigMaps; import static org.apache.flink.kubernetes.utils.KubernetesUtils.getLeaderInformationFromConfigMap; import static org.apache.flink.util.Preconditions.checkNotNull; /** * {@link LeaderElectionDriver} implementation for Kubernetes. The active leader is elected using Kubernetes. * The current leader's address as well as its leader session ID is published via Kubernetes ConfigMap. * Note that the contending lock and leader storage are using the same ConfigMap. And every component(e.g. * ResourceManager, Dispatcher, RestEndpoint, JobManager for each job) will have a separate ConfigMap. */ public class KubernetesLeaderElectionDriver implements LeaderElectionDriver { private static final Logger LOG = LoggerFactory.getLogger(KubernetesLeaderElectionDriver.class); private final Object watchLock = new Object(); private final FlinkKubeClient kubeClient; private final String configMapName; private final String lockIdentity; private final KubernetesLeaderElector leaderElector; // Labels will be used to clean up the ha related ConfigMaps. private final Map configMapLabels; private final LeaderElectionEventHandler leaderElectionEventHandler; @GuardedBy("watchLock") private KubernetesWatch kubernetesWatch; private final FatalErrorHandler fatalErrorHandler; private volatile boolean running; public KubernetesLeaderElectionDriver( FlinkKubeClient kubeClient, KubernetesLeaderElectionConfiguration leaderConfig, LeaderElectionEventHandler leaderElectionEventHandler, FatalErrorHandler fatalErrorHandler) { this.kubeClient = checkNotNull(kubeClient, "Kubernetes client"); checkNotNull(leaderConfig, "Leader election configuration"); this.leaderElectionEventHandler = checkNotNull(leaderElectionEventHandler, "LeaderElectionEventHandler"); this.fatalErrorHandler = checkNotNull(fatalErrorHandler); this.configMapName = leaderConfig.getConfigMapName(); this.lockIdentity = leaderConfig.getLockIdentity(); this.leaderElector = kubeClient.createLeaderElector(leaderConfig, new LeaderCallbackHandlerImpl()); this.configMapLabels = KubernetesUtils.getConfigMapLabels( leaderConfig.getClusterId(), LABEL_CONFIGMAP_TYPE_HIGH_AVAILABILITY); running = true; leaderElector.run(); kubernetesWatch = kubeClient.watchConfigMaps(configMapName, new ConfigMapCallbackHandlerImpl()); } @Override public void close() { if (!running) { return; } running = false; LOG.info("Closing {}.", this); leaderElector.stop(); synchronized (watchLock) { if (kubernetesWatch != null) { kubernetesWatch.close(); } } } @Override public void writeLeaderInformation(LeaderInformation leaderInformation) { assert(running); final UUID confirmedLeaderSessionID = leaderInformation.getLeaderSessionID(); final String confirmedLeaderAddress = leaderInformation.getLeaderAddress(); try { kubeClient.checkAndUpdateConfigMap( configMapName, configMap -> { if (KubernetesLeaderElector.hasLeadership(configMap, lockIdentity)) { // Get the updated ConfigMap with new leader information if (confirmedLeaderAddress == null) { configMap.getData().remove(LEADER_ADDRESS_KEY); } else { configMap.getData().put(LEADER_ADDRESS_KEY, confirmedLeaderAddress); } if (confirmedLeaderSessionID == null) { configMap.getData().remove(LEADER_SESSION_ID_KEY); } else { configMap.getData().put(LEADER_SESSION_ID_KEY, confirmedLeaderSessionID.toString()); } configMap.getLabels().putAll(configMapLabels); return Optional.of(configMap); } return Optional.empty(); }).get(); if (LOG.isDebugEnabled()) { LOG.debug( "Successfully wrote leader information: Leader={}, session ID={}.", confirmedLeaderAddress, confirmedLeaderSessionID); } } catch (Exception e) { fatalErrorHandler.onFatalError( new KubernetesException("Could not write leader information since ConfigMap " + configMapName + " does not exist.", e)); } } @Override public boolean hasLeadership() { assert(running); final Optional configMapOpt = kubeClient.getConfigMap(configMapName); if (configMapOpt.isPresent()) { return KubernetesLeaderElector.hasLeadership(configMapOpt.get(), lockIdentity); } else { fatalErrorHandler.onFatalError( new KubernetesException("ConfigMap " + configMapName + " does not exist.", null)); return false; } } private class LeaderCallbackHandlerImpl extends KubernetesLeaderElector.LeaderCallbackHandler { @Override public void isLeader() { leaderElectionEventHandler.onGrantLeadership(); } @Override public void notLeader() { leaderElectionEventHandler.onRevokeLeadership(); // Continue to contend the leader leaderElector.run(); } } private class ConfigMapCallbackHandlerImpl implements FlinkKubeClient.WatchCallbackHandler { @Override public void onAdded(List configMaps) { // noop } @Override public void onModified(List configMaps) { // We should only receive events for the watched ConfigMap final KubernetesConfigMap configMap = checkConfigMaps(configMaps, configMapName); if (KubernetesLeaderElector.hasLeadership(configMap, lockIdentity)) { leaderElectionEventHandler.onLeaderInformationChange(getLeaderInformationFromConfigMap(configMap)); } } @Override public void onDeleted(List configMaps) { final KubernetesConfigMap configMap = checkConfigMaps(configMaps, configMapName); // The ConfigMap is deleted externally. if (KubernetesLeaderElector.hasLeadership(configMap, lockIdentity)) { fatalErrorHandler.onFatalError( new LeaderElectionException("ConfigMap " + configMapName + " is deleted externally")); } } @Override public void onError(List configMaps) { final KubernetesConfigMap configMap = checkConfigMaps(configMaps, configMapName); if (KubernetesLeaderElector.hasLeadership(configMap, lockIdentity)) { fatalErrorHandler.onFatalError( new LeaderElectionException("Error while watching the ConfigMap " + configMapName)); } } @Override public void handleFatalError(Throwable throwable) { if (throwable instanceof KubernetesTooOldResourceVersionException) { synchronized (watchLock) { if (running) { if (kubernetesWatch != null) { kubernetesWatch.close(); } LOG.info("Creating a new watch on ConfigMap {}.", configMapName); kubernetesWatch = kubeClient.watchConfigMaps( configMapName, new ConfigMapCallbackHandlerImpl()); } } } else { fatalErrorHandler.onFatalError( new LeaderElectionException( "Error while watching the ConfigMap " + configMapName, throwable)); } } } @Override public String toString() { return "KubernetesLeaderElectionDriver{configMapName='" + configMapName + "'}"; } } ================================================ FILE: flink-learning-k8s/flink-k8s/src/main/java/org/apache/flink/kubernetes/highavailability/KubernetesLeaderElectionDriverFactory.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.flink.kubernetes.highavailability; import org.apache.flink.kubernetes.configuration.KubernetesLeaderElectionConfiguration; import org.apache.flink.kubernetes.kubeclient.FlinkKubeClient; import org.apache.flink.runtime.leaderelection.LeaderElectionDriverFactory; import org.apache.flink.runtime.leaderelection.LeaderElectionEventHandler; import org.apache.flink.runtime.rpc.FatalErrorHandler; /** * {@link LeaderElectionDriverFactory} implementation for Kubernetes. */ public class KubernetesLeaderElectionDriverFactory implements LeaderElectionDriverFactory { private final FlinkKubeClient kubeClient; private final KubernetesLeaderElectionConfiguration leaderConfig; public KubernetesLeaderElectionDriverFactory( FlinkKubeClient kubeClient, KubernetesLeaderElectionConfiguration leaderConfig) { this.kubeClient = kubeClient; this.leaderConfig = leaderConfig; } @Override public KubernetesLeaderElectionDriver createLeaderElectionDriver( LeaderElectionEventHandler leaderEventHandler, FatalErrorHandler fatalErrorHandler, String leaderContenderDescription) { return new KubernetesLeaderElectionDriver( kubeClient, leaderConfig, leaderEventHandler, fatalErrorHandler); } } ================================================ FILE: flink-learning-k8s/flink-k8s/src/main/java/org/apache/flink/kubernetes/highavailability/KubernetesLeaderRetrievalDriver.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.flink.kubernetes.highavailability; import org.apache.flink.kubernetes.kubeclient.FlinkKubeClient; import org.apache.flink.kubernetes.kubeclient.resources.KubernetesConfigMap; import org.apache.flink.kubernetes.kubeclient.resources.KubernetesTooOldResourceVersionException; import org.apache.flink.kubernetes.kubeclient.resources.KubernetesWatch; import org.apache.flink.runtime.leaderretrieval.LeaderRetrievalDriver; import org.apache.flink.runtime.leaderretrieval.LeaderRetrievalEventHandler; import org.apache.flink.runtime.leaderretrieval.LeaderRetrievalException; import org.apache.flink.runtime.rpc.FatalErrorHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.annotation.concurrent.GuardedBy; import java.util.List; import static org.apache.flink.kubernetes.utils.KubernetesUtils.checkConfigMaps; import static org.apache.flink.kubernetes.utils.KubernetesUtils.getLeaderInformationFromConfigMap; import static org.apache.flink.util.Preconditions.checkNotNull; /** * The counterpart to the {@link KubernetesLeaderElectionDriver}. * {@link LeaderRetrievalDriver} implementation for Kubernetes. It retrieves the current leader which has * been elected by the {@link KubernetesLeaderElectionDriver}. * The leader address as well as the current leader session ID is retrieved from Kubernetes ConfigMap. */ public class KubernetesLeaderRetrievalDriver implements LeaderRetrievalDriver { private static final Logger LOG = LoggerFactory.getLogger(KubernetesLeaderRetrievalDriver.class); private final Object watchLock = new Object(); private final FlinkKubeClient kubeClient; private final String configMapName; private final LeaderRetrievalEventHandler leaderRetrievalEventHandler; @GuardedBy("watchLock") private KubernetesWatch kubernetesWatch; private final FatalErrorHandler fatalErrorHandler; private volatile boolean running; public KubernetesLeaderRetrievalDriver( FlinkKubeClient kubeClient, String configMapName, LeaderRetrievalEventHandler leaderRetrievalEventHandler, FatalErrorHandler fatalErrorHandler) { this.kubeClient = checkNotNull(kubeClient, "Kubernetes client"); this.configMapName = checkNotNull(configMapName, "ConfigMap name"); this.leaderRetrievalEventHandler = checkNotNull(leaderRetrievalEventHandler, "LeaderRetrievalEventHandler"); this.fatalErrorHandler = checkNotNull(fatalErrorHandler); kubernetesWatch = kubeClient.watchConfigMaps(configMapName, new ConfigMapCallbackHandlerImpl()); running = true; } @Override public void close() { if (!running) { return; } running = false; LOG.info("Stopping {}.", this); synchronized (watchLock) { if (kubernetesWatch != null) { kubernetesWatch.close(); } } } private class ConfigMapCallbackHandlerImpl implements FlinkKubeClient.WatchCallbackHandler { @Override public void onAdded(List configMaps) { // The ConfigMap is created by KubernetesLeaderElectionDriver with empty data. We do not process this // useless event. } @Override public void onModified(List configMaps) { final KubernetesConfigMap configMap = checkConfigMaps(configMaps, configMapName); leaderRetrievalEventHandler.notifyLeaderAddress(getLeaderInformationFromConfigMap(configMap)); } @Override public void onDeleted(List configMaps) { // Nothing to do since the delete event will be handled in the leader election part. } @Override public void onError(List configMaps) { fatalErrorHandler.onFatalError( new LeaderRetrievalException("Error while watching the ConfigMap " + configMapName)); } @Override public void handleFatalError(Throwable throwable) { if (throwable instanceof KubernetesTooOldResourceVersionException) { synchronized (watchLock) { if (running) { if (kubernetesWatch != null) { kubernetesWatch.close(); } LOG.info("Creating a new watch on ConfigMap {}.", configMapName); kubernetesWatch = kubeClient.watchConfigMaps( configMapName, new ConfigMapCallbackHandlerImpl()); } } } else { fatalErrorHandler.onFatalError( new LeaderRetrievalException( "Error while watching the ConfigMap " + configMapName, throwable)); } } } @Override public String toString() { return "KubernetesLeaderRetrievalDriver{configMapName='" + configMapName + "'}"; } } ================================================ FILE: flink-learning-k8s/flink-k8s/src/main/java/org/apache/flink/kubernetes/highavailability/KubernetesLeaderRetrievalDriverFactory.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.flink.kubernetes.highavailability; import org.apache.flink.kubernetes.kubeclient.FlinkKubeClient; import org.apache.flink.runtime.leaderretrieval.LeaderRetrievalDriverFactory; import org.apache.flink.runtime.leaderretrieval.LeaderRetrievalEventHandler; import org.apache.flink.runtime.rpc.FatalErrorHandler; /** * {@link LeaderRetrievalDriverFactory} implementation for Kubernetes. */ public class KubernetesLeaderRetrievalDriverFactory implements LeaderRetrievalDriverFactory { private final FlinkKubeClient kubeClient; private final String configMapName; public KubernetesLeaderRetrievalDriverFactory(FlinkKubeClient kubeClient, String configMapName) { this.kubeClient = kubeClient; this.configMapName = configMapName; } @Override public KubernetesLeaderRetrievalDriver createLeaderRetrievalDriver( LeaderRetrievalEventHandler leaderEventHandler, FatalErrorHandler fatalErrorHandler) { return new KubernetesLeaderRetrievalDriver(kubeClient, configMapName, leaderEventHandler, fatalErrorHandler); } } ================================================ FILE: flink-learning-k8s/flink-k8s/src/main/java/org/apache/flink/kubernetes/highavailability/KubernetesRunningJobsRegistry.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.flink.kubernetes.highavailability; import org.apache.flink.api.common.JobID; import org.apache.flink.kubernetes.kubeclient.FlinkKubeClient; import org.apache.flink.kubernetes.kubeclient.resources.KubernetesConfigMap; import org.apache.flink.kubernetes.kubeclient.resources.KubernetesLeaderElector; import org.apache.flink.runtime.highavailability.RunningJobsRegistry; import org.apache.flink.util.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.util.Optional; import static org.apache.flink.kubernetes.utils.Constants.RUNNING_JOBS_REGISTRY_KEY_PREFIX; import static org.apache.flink.util.Preconditions.checkNotNull; /** * {@link RunningJobsRegistry} implementation for Kubernetes. All the running jobs will be stored in * Dispatcher-leader ConfigMap. The key is the job id with prefix * {@link org.apache.flink.kubernetes.utils.Constants#RUNNING_JOBS_REGISTRY_KEY_PREFIX}, * and value is job status. */ public class KubernetesRunningJobsRegistry implements RunningJobsRegistry { private static final Logger LOG = LoggerFactory.getLogger(KubernetesRunningJobsRegistry.class); private final FlinkKubeClient kubeClient; private final String configMapName; private final String lockIdentity; public KubernetesRunningJobsRegistry(FlinkKubeClient kubeClient, String configMapName, String lockIdentity) { this.kubeClient = checkNotNull(kubeClient); this.configMapName = checkNotNull(configMapName); this.lockIdentity = checkNotNull(lockIdentity); } @Override public void setJobRunning(JobID jobID) throws IOException { checkNotNull(jobID); writeJobStatusToConfigMap(jobID, JobSchedulingStatus.RUNNING); } @Override public void setJobFinished(JobID jobID) throws IOException { checkNotNull(jobID); writeJobStatusToConfigMap(jobID, JobSchedulingStatus.DONE); } @Override public JobSchedulingStatus getJobSchedulingStatus(JobID jobID) throws IOException { checkNotNull(jobID); return kubeClient.getConfigMap(configMapName) .map(configMap -> getJobStatus(configMap, jobID).orElse(JobSchedulingStatus.PENDING)) .orElseThrow(() -> new IOException("ConfigMap " + configMapName + " does not exist.")); } @Override public void clearJob(JobID jobID) throws IOException { checkNotNull(jobID); try { kubeClient.checkAndUpdateConfigMap( configMapName, configMap -> { if (KubernetesLeaderElector.hasLeadership(configMap, lockIdentity)) { if (configMap.getData().remove(getKeyForJobId(jobID)) != null) { return Optional.of(configMap); } } return Optional.empty(); } ).get(); } catch (Exception e) { throw new IOException("Failed to clear job state in ConfigMap " + configMapName + " for job " + jobID, e); } } private void writeJobStatusToConfigMap(JobID jobID, JobSchedulingStatus status) throws IOException { LOG.debug("Setting scheduling state for job {} to {}.", jobID, status); final String key = getKeyForJobId(jobID); try { kubeClient.checkAndUpdateConfigMap( configMapName, configMap -> { if (KubernetesLeaderElector.hasLeadership(configMap, lockIdentity)) { final Optional optional = getJobStatus(configMap, jobID); if (!optional.isPresent() || optional.get() != status) { configMap.getData().put(key, status.name()); return Optional.of(configMap); } } return Optional.empty(); } ).get(); } catch (Exception e) { throw new IOException("Failed to set " + status.name() + " state in ConfigMap " + configMapName + " for job " + jobID, e); } } private Optional getJobStatus(KubernetesConfigMap configMap, JobID jobId) { final String key = getKeyForJobId(jobId); final String status = configMap.getData().get(key); if (!StringUtils.isNullOrWhitespaceOnly(status)) { return Optional.of(JobSchedulingStatus.valueOf(status)); } return Optional.empty(); } private String getKeyForJobId(JobID jobId) { return RUNNING_JOBS_REGISTRY_KEY_PREFIX + jobId.toString(); } } ================================================ FILE: flink-learning-k8s/flink-k8s/src/main/java/org/apache/flink/kubernetes/highavailability/KubernetesStateHandleStore.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.flink.kubernetes.highavailability; import org.apache.flink.api.java.tuple.Tuple2; import org.apache.flink.kubernetes.kubeclient.FlinkKubeClient; import org.apache.flink.kubernetes.kubeclient.resources.KubernetesConfigMap; import org.apache.flink.kubernetes.kubeclient.resources.KubernetesException; import org.apache.flink.kubernetes.kubeclient.resources.KubernetesLeaderElector; import org.apache.flink.runtime.persistence.RetrievableStateStorageHelper; import org.apache.flink.runtime.persistence.StateHandleStore; import org.apache.flink.runtime.persistence.StringResourceVersion; import org.apache.flink.runtime.state.RetrievableStateHandle; import org.apache.flink.util.ExceptionUtils; import org.apache.flink.util.InstantiationUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.io.Serializable; import java.util.*; import java.util.concurrent.CompletionException; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Predicate; import java.util.stream.Collectors; import static org.apache.flink.util.Preconditions.checkNotNull; /** * Class which stores state via the provided {@link RetrievableStateStorageHelper} and writes the * returned state handle to ConfigMap. * *

Added state is persisted via {@link RetrievableStateHandle RetrievableStateHandles}, * which in turn are written to ConfigMap. This level of indirection is necessary to keep the * amount of data in ConfigMap small. ConfigMap is build for data less than 1MB whereas * state can grow to multiple MBs and GBs. * *

This is a very different implementation with {@link org.apache.flink.runtime.zookeeper.ZooKeeperStateHandleStore}. * Benefit from the {@link FlinkKubeClient#checkAndUpdateConfigMap} transactional operation, we could guarantee that * only the leader could update the store. Then we will completely get rid of the lock-and-release in Zookeeper * implementation. * * @param Type of state */ public class KubernetesStateHandleStore implements StateHandleStore { private static final Logger LOG = LoggerFactory.getLogger(KubernetesStateHandleStore.class); private final FlinkKubeClient kubeClient; private final String configMapName; private final RetrievableStateStorageHelper storage; private final Predicate configMapKeyFilter; private final String lockIdentity; /** * Creates a {@link KubernetesStateHandleStore}. * * @param kubeClient The Kubernetes client. * @param storage To persist the actual state and whose returned state handle is then written to ConfigMap * @param configMapName ConfigMap to store the state handle store pointer * @param configMapKeyFilter filter to get the expected keys for state handle * @param lockIdentity lock identity of current HA service */ public KubernetesStateHandleStore( FlinkKubeClient kubeClient, String configMapName, RetrievableStateStorageHelper storage, Predicate configMapKeyFilter, String lockIdentity) { this.kubeClient = checkNotNull(kubeClient, "Kubernetes client"); this.storage = checkNotNull(storage, "State storage"); this.configMapName = checkNotNull(configMapName, "ConfigMap name"); this.configMapKeyFilter = checkNotNull(configMapKeyFilter); this.lockIdentity = checkNotNull(lockIdentity, "Lock identity of current HA service"); } /** * Creates a state handle, stores it in ConfigMap. We could guarantee that only the leader could update the * ConfigMap. Since “Get(check the leader)-and-Update(write back to the ConfigMap)” is a * transactional operation. * * @param key Key in ConfigMap * @param state State to be added * * @throws AlreadyExistException if the name already exists * @throws Exception if persisting state or writing state handle failed */ @Override public RetrievableStateHandle addAndLock(String key, T state) throws Exception { checkNotNull(key, "Key in ConfigMap."); checkNotNull(state, "State."); final RetrievableStateHandle storeHandle = storage.store(state); boolean success = false; try { final byte[] serializedStoreHandle = InstantiationUtil.serializeObject(storeHandle); success = kubeClient.checkAndUpdateConfigMap( configMapName, c -> { if (KubernetesLeaderElector.hasLeadership(c, lockIdentity)) { if (!c.getData().containsKey(key)) { c.getData().put(key, encodeStateHandle(serializedStoreHandle)); return Optional.of(c); } else { throw new CompletionException(getKeyAlreadyExistException(key)); } } return Optional.empty(); }).get(); return storeHandle; } catch (Exception ex) { throw ExceptionUtils.findThrowable(ex, AlreadyExistException.class).orElseThrow(() -> ex); } finally { if (!success) { // Cleanup the state handle if it was not written to ConfigMap. if (storeHandle != null) { storeHandle.discardState(); } } } } /** * Replaces a state handle in ConfigMap and discards the old state handle. Wo do not lock resource * version and then replace in Kubernetes. Since the ConfigMap is periodically updated by leader, the * resource version changes very fast. We use a "check-existence and update" transactional operation instead. * * @param key Key in ConfigMap * @param resourceVersion resource version when checking existence via {@link #exists}. * @param state State to be added * * @throws NotExistException if the name does not exist * @throws Exception if persisting state or writing state handle failed */ @Override public void replace(String key, StringResourceVersion resourceVersion, T state) throws Exception { checkNotNull(key, "Key in ConfigMap."); checkNotNull(state, "State."); final RetrievableStateHandle oldStateHandle = getAndLock(key); final RetrievableStateHandle newStateHandle = storage.store(state); boolean success = false; try { final byte[] serializedStoreHandle = InstantiationUtil.serializeObject(newStateHandle); success = kubeClient.checkAndUpdateConfigMap( configMapName, c -> { if (KubernetesLeaderElector.hasLeadership(c, lockIdentity)) { // Check the existence if (c.getData().containsKey(key)) { c.getData().put(key, encodeStateHandle(serializedStoreHandle)); } else { throw new CompletionException(getKeyNotExistException(key)); } return Optional.of(c); } return Optional.empty(); }).get(); } catch (Exception ex) { throw ExceptionUtils.findThrowable(ex, NotExistException.class).orElseThrow(() -> ex); } finally { if (success) { oldStateHandle.discardState(); } else { newStateHandle.discardState(); } } } /** * Returns the resource version of the ConfigMap. * * @param key Key in ConfigMap * * @return resource version in {@link StringResourceVersion} format. * * @throws Exception if the check existence operation failed */ @Override public StringResourceVersion exists(String key) throws Exception { checkNotNull(key, "Key in ConfigMap."); return kubeClient.getConfigMap(configMapName) .map(configMap -> { if (configMap.getData().containsKey(key)) { return StringResourceVersion.valueOf(configMap.getResourceVersion()); } return StringResourceVersion.notExisting(); }) .orElseThrow(this::getConfigMapNotExistException); } /** * Gets the {@link RetrievableStateHandle} stored in the given ConfigMap. * * @param key Key in ConfigMap * * @return The retrieved state handle from the specified ConfigMap and key * * @throws IOException if the method failed to deserialize the stored state handle * @throws NotExistException when the name does not exist * @throws Exception if get state handle from ConfigMap failed */ @Override public RetrievableStateHandle getAndLock(String key) throws Exception { checkNotNull(key, "Key in ConfigMap."); final Optional optional = kubeClient.getConfigMap(configMapName); if (optional.isPresent()) { final KubernetesConfigMap configMap = optional.get(); if (configMap.getData().containsKey(key)) { return deserializeObject(configMap.getData().get(key)); } else { throw getKeyNotExistException(key); } } else { throw getConfigMapNotExistException(); } } /** * Gets all available state handles from Kubernetes. * * @return All state handles from ConfigMap. */ @Override public List, String>> getAllAndLock() { return kubeClient.getConfigMap(configMapName) .map( configMap -> { final List, String>> stateHandles = new ArrayList<>(); configMap.getData().entrySet().stream() .filter(entry -> configMapKeyFilter.test(entry.getKey())) .forEach(entry -> { try { stateHandles.add(new Tuple2<>(deserializeObject(entry.getValue()), entry.getKey())); } catch (IOException e) { LOG.warn("ConfigMap {} contained corrupted data. Ignoring the key {}.", configMapName, entry.getKey()); } }); return stateHandles; }) .orElse(Collections.emptyList()); } /** * Return a list of all valid keys for state handles. * * @return List of valid state handle keys in Kubernetes ConfigMap * * @throws Exception if get state handle names from ConfigMap failed. */ @Override public Collection getAllHandles() throws Exception { return kubeClient.getConfigMap(configMapName) .map(configMap -> configMap.getData().keySet().stream() .filter(configMapKeyFilter) .collect(Collectors.toList())) .orElseThrow(this::getConfigMapNotExistException); } /** * Remove the key in state config map. As well as the state on external storage will be removed. * It returns the {@link RetrievableStateHandle} stored under the given state node if any. * * @param key Key to be removed from ConfigMap * * @return True if the state handle is removed successfully * * @throws Exception if removing the key or discarding the state failed */ @Override public boolean releaseAndTryRemove(String key) throws Exception { checkNotNull(key, "Key in ConfigMap."); final AtomicReference> stateHandleRefer = new AtomicReference<>(); return kubeClient.checkAndUpdateConfigMap( configMapName, configMap -> { if (KubernetesLeaderElector.hasLeadership(configMap, lockIdentity)) { final String content = configMap.getData().remove(key); if (content != null) { try { stateHandleRefer.set(deserializeObject(content)); } catch (IOException e) { LOG.warn("Could not retrieve the state handle of {} from ConfigMap {}.", key, configMapName, e); } } return Optional.of(configMap); } return Optional.empty(); }) .whenComplete((succeed, ignore) -> { if (succeed) { if (stateHandleRefer.get() != null) { try { stateHandleRefer.get().discardState(); } catch (Exception e) { throw new CompletionException(e); } } } }).get(); } /** * Remove all the state handle keys in the ConfigMap and discard the states. * * @throws Exception when removing the keys or discarding the state failed */ @Override public void releaseAndTryRemoveAll() throws Exception { final List> validStateHandles = new ArrayList<>(); kubeClient.checkAndUpdateConfigMap( configMapName, c -> { if (KubernetesLeaderElector.hasLeadership(c, lockIdentity)) { final Map updateData = new HashMap<>(c.getData()); c.getData().entrySet().stream() .filter(entry -> configMapKeyFilter.test(entry.getKey())) .forEach(entry -> { try { validStateHandles.add(deserializeObject(entry.getValue())); updateData.remove(entry.getKey()); } catch (IOException e) { LOG.warn("ConfigMap {} contained corrupted data. Ignoring the key {}.", configMapName, entry.getKey()); } }); c.getData().clear(); c.getData().putAll(updateData); return Optional.of(c); } return Optional.empty(); }) .whenComplete((succeed, ignore) -> { if (succeed) { Exception exception = null; for (RetrievableStateHandle stateHandle : validStateHandles) { try { stateHandle.discardState(); } catch (Exception e) { exception = ExceptionUtils.firstOrSuppressed(e, exception); } } if (exception != null) { throw new CompletionException(new KubernetesException( "Could not properly remove all state handles.", exception)); } } }).get(); } /** * Remove all the filtered keys in the ConfigMap. * * @throws Exception when removing the keys failed */ @Override public void clearEntries() throws Exception { kubeClient.checkAndUpdateConfigMap( configMapName, c -> { if (KubernetesLeaderElector.hasLeadership(c, lockIdentity)) { c.getData().keySet().removeIf(configMapKeyFilter); return Optional.of(c); } return Optional.empty(); }).get(); } @Override public void release(String name) { // noop } @Override public void releaseAll() { // noop } @Override public String toString() { return this.getClass().getSimpleName() + "{configMapName='" + configMapName + "'}"; } private RetrievableStateHandle deserializeObject(String content) throws IOException { checkNotNull(content, "Content should not be null."); final byte[] data = Base64.getDecoder().decode(content); try { return InstantiationUtil.deserializeObject(data, Thread.currentThread().getContextClassLoader()); } catch (IOException | ClassNotFoundException e) { throw new IOException("Failed to deserialize state handle from ConfigMap data " + content + '.', e); } } private KubernetesException getConfigMapNotExistException() { return new KubernetesException("ConfigMap " + configMapName + " does not exists. " + "It may be deleted externally."); } private NotExistException getKeyNotExistException(String key) { return new NotExistException("Could not find " + key + " in ConfigMap " + configMapName); } private AlreadyExistException getKeyAlreadyExistException(String key) { return new AlreadyExistException(key + " already exists in ConfigMap " + configMapName); } private String encodeStateHandle(byte[] serializedStoreHandle) { return Base64.getEncoder().encodeToString(serializedStoreHandle); } } ================================================ FILE: flink-learning-k8s/flink-k8s/src/main/java/org/apache/flink/kubernetes/kubeclient/Endpoint.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.flink.kubernetes.kubeclient; import static org.apache.flink.util.Preconditions.checkNotNull; /** * Represent an endpoint. */ public class Endpoint { private final String address; private final int port; public Endpoint(String address, int port) { this.address = checkNotNull(address, "Address should not be null."); this.port = port; } public String getAddress() { return address; } public int getPort() { return port; } } ================================================ FILE: flink-learning-k8s/flink-k8s/src/main/java/org/apache/flink/kubernetes/kubeclient/Fabric8FlinkKubeClient.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.flink.kubernetes.kubeclient; import io.fabric8.kubernetes.api.model.*; import io.fabric8.kubernetes.api.model.apps.Deployment; import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.client.KubernetesClientException; import io.fabric8.kubernetes.client.NamespacedKubernetesClient; import org.apache.flink.configuration.Configuration; import org.apache.flink.kubernetes.configuration.KubernetesConfigOptions; import org.apache.flink.kubernetes.configuration.KubernetesLeaderElectionConfiguration; import org.apache.flink.kubernetes.kubeclient.decorators.ExternalServiceDecorator; import org.apache.flink.kubernetes.kubeclient.decorators.InternalServiceDecorator; import org.apache.flink.kubernetes.kubeclient.resources.*; import org.apache.flink.kubernetes.utils.Constants; import org.apache.flink.kubernetes.utils.KubernetesUtils; import org.apache.flink.runtime.concurrent.FutureUtils; import org.apache.flink.util.ExceptionUtils; import org.apache.flink.util.ExecutorUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; import java.util.stream.Collectors; import static org.apache.flink.util.Preconditions.checkNotNull; /** * The implementation of {@link FlinkKubeClient}. */ public class Fabric8FlinkKubeClient implements FlinkKubeClient { private static final Logger LOG = LoggerFactory.getLogger(Fabric8FlinkKubeClient.class); private final KubernetesClient internalClient; private final String clusterId; private final String namespace; private final int maxRetryAttempts; private final ExecutorService kubeClientExecutorService; // save the master deployment atomic reference for setting owner reference of task manager pods private final AtomicReference masterDeploymentRef; public Fabric8FlinkKubeClient( Configuration flinkConfig, KubernetesClient client, ExecutorService executorService) { this.internalClient = checkNotNull(client); this.clusterId = flinkConfig .getOptional(KubernetesConfigOptions.CLUSTER_ID) .orElseThrow( () -> new IllegalArgumentException( String.format( "Configuration option '%s' is not set.", KubernetesConfigOptions.CLUSTER_ID.key()))); this.namespace = flinkConfig.getString(KubernetesConfigOptions.NAMESPACE); this.maxRetryAttempts = flinkConfig.getInteger( KubernetesConfigOptions.KUBERNETES_TRANSACTIONAL_OPERATION_MAX_RETRIES); this.kubeClientExecutorService = checkNotNull(executorService); this.masterDeploymentRef = new AtomicReference<>(); } @Override public void createJobManagerComponent(KubernetesJobManagerSpecification kubernetesJMSpec) { final Deployment deployment = kubernetesJMSpec.getDeployment(); final List accompanyingResources = kubernetesJMSpec.getAccompanyingResources(); // create Deployment LOG.debug( "Start to create deployment with spec {}{}", System.lineSeparator(), KubernetesUtils.tryToGetPrettyPrintYaml(deployment)); final Deployment createdDeployment = this.internalClient .apps() .deployments() .inNamespace(this.namespace) .create(deployment); // Note that we should use the uid of the created Deployment for the OwnerReference. setOwnerReference(createdDeployment, accompanyingResources); this.internalClient .resourceList(accompanyingResources) .inNamespace(this.namespace) .createOrReplace(); } @Override public CompletableFuture createTaskManagerPod(KubernetesPod kubernetesPod) { return CompletableFuture.runAsync( () -> { if (masterDeploymentRef.get() == null) { final Deployment masterDeployment = this.internalClient .apps() .deployments() .withName(KubernetesUtils.getDeploymentName(clusterId)) .get(); if (masterDeployment == null) { throw new RuntimeException( "Failed to find Deployment named " + clusterId + " in namespace " + this.namespace); } masterDeploymentRef.compareAndSet(null, masterDeployment); } // Note that we should use the uid of the master Deployment for the OwnerReference. setOwnerReference(masterDeploymentRef.get(), Collections.singletonList(kubernetesPod.getInternalResource())); LOG.debug( "Start to create pod with spec {}{}", System.lineSeparator(), KubernetesUtils.tryToGetPrettyPrintYaml( kubernetesPod.getInternalResource())); this.internalClient .pods() .inNamespace(this.namespace) .create(kubernetesPod.getInternalResource()); }, kubeClientExecutorService); } @Override public CompletableFuture stopPod(String podName) { return CompletableFuture.runAsync( () -> this.internalClient.pods().withName(podName).delete(), kubeClientExecutorService); } @Override public Optional getRestEndpoint(String clusterId) { Optional restService = getService(KubernetesService.ServiceType.REST_SERVICE, clusterId); if (!restService.isPresent()) { return Optional.empty(); } final Service service = restService.get().getInternalResource(); final int restPort = getRestPortFromExternalService(service); final KubernetesConfigOptions.ServiceExposedType serviceExposedType = KubernetesConfigOptions.ServiceExposedType.valueOf(service.getSpec().getType()); // Return the external service.namespace directly when using ClusterIP. if (serviceExposedType == KubernetesConfigOptions.ServiceExposedType.ClusterIP) { return Optional.of( new Endpoint(ExternalServiceDecorator.getNamespacedExternalServiceName(clusterId, namespace), restPort)); } return getRestEndPointFromService(service, restPort); } @Override public List getPodsWithLabels(Map labels) { final List podList = this.internalClient.pods().withLabels(labels).list().getItems(); if (podList == null || podList.isEmpty()) { return new ArrayList<>(); } return podList .stream() .map(KubernetesPod::new) .collect(Collectors.toList()); } @Override public void stopAndCleanupCluster(String clusterId) { this.internalClient .apps() .deployments() .inNamespace(this.namespace) .withName(KubernetesUtils.getDeploymentName(clusterId)) .cascading(true) .delete(); } @Override public void handleException(Exception e) { LOG.error("A Kubernetes exception occurred.", e); } @Override public Optional getService( KubernetesService.ServiceType serviceType, String clusterId) { final String serviceName = getServiceName(serviceType, clusterId); final Service service = this.internalClient .services() .inNamespace(namespace) .withName(serviceName) .fromServer() .get(); if (service == null) { LOG.debug("Service {} does not exist", serviceName); return Optional.empty(); } return Optional.of(new KubernetesService(service)); } @Override public KubernetesWatch watchPodsAndDoCallback( Map labels, WatchCallbackHandler podCallbackHandler) { return new KubernetesWatch( this.internalClient.pods() .withLabels(labels) .watch(new KubernetesPodsWatcher(podCallbackHandler))); } @Override public KubernetesLeaderElector createLeaderElector( KubernetesLeaderElectionConfiguration leaderElectionConfiguration, KubernetesLeaderElector.LeaderCallbackHandler leaderCallbackHandler) { return new KubernetesLeaderElector( (NamespacedKubernetesClient) this.internalClient, namespace, leaderElectionConfiguration, leaderCallbackHandler); } @Override public CompletableFuture createConfigMap(KubernetesConfigMap configMap) { final String configMapName = configMap.getName(); return CompletableFuture.runAsync( () -> this.internalClient.configMaps().inNamespace(namespace).create(configMap.getInternalResource()), kubeClientExecutorService) .exceptionally( throwable -> { throw new CompletionException( new KubernetesException("Failed to create ConfigMap " + configMapName, throwable)); }); } @Override public Optional getConfigMap(String name) { final ConfigMap configMap = this.internalClient.configMaps().inNamespace(namespace).withName(name).get(); return configMap == null ? Optional.empty() : Optional.of(new KubernetesConfigMap(configMap)); } @Override public CompletableFuture checkAndUpdateConfigMap( String configMapName, Function> function) { return FutureUtils.retry( () -> CompletableFuture.supplyAsync( () -> getConfigMap(configMapName) .map( configMap -> function.apply(configMap).map( updatedConfigMap -> { try { this.internalClient.configMaps() .inNamespace(namespace) .withName(configMapName) .lockResourceVersion(updatedConfigMap.getResourceVersion()) .replace(updatedConfigMap.getInternalResource()); } catch (Throwable throwable) { LOG.debug("Failed to update ConfigMap {} with data {} because of concurrent " + "modifications. Trying again.", configMap.getName(), configMap.getData()); throw throwable; } return true; }).orElse(false)) .orElseThrow(() -> new CompletionException( new KubernetesException("Cannot retry checkAndUpdateConfigMap with configMap " + configMapName + " because it does not exist."))), kubeClientExecutorService), maxRetryAttempts, // Only KubernetesClientException is retryable throwable -> ExceptionUtils.findThrowable(throwable, KubernetesClientException.class).isPresent(), kubeClientExecutorService); } @Override public KubernetesWatch watchConfigMaps( String name, WatchCallbackHandler callbackHandler) { return new KubernetesWatch( this.internalClient.configMaps().withName(name).watch(new KubernetesConfigMapWatcher(callbackHandler))); } @Override public CompletableFuture deleteConfigMapsByLabels(Map labels) { return CompletableFuture.runAsync( () -> this.internalClient.configMaps().inNamespace(namespace).withLabels(labels).delete(), kubeClientExecutorService); } @Override public CompletableFuture deleteConfigMap(String configMapName) { return CompletableFuture.runAsync( () -> this.internalClient.configMaps().inNamespace(namespace).withName(configMapName).delete(), kubeClientExecutorService); } @Override public void close() { this.internalClient.close(); ExecutorUtils.gracefulShutdown(5, TimeUnit.SECONDS, this.kubeClientExecutorService); } @Override public CompletableFuture updateServiceTargetPort( KubernetesService.ServiceType serviceType, String clusterId, String portName, int targetPort) { LOG.debug("Update {} target port to {}", portName, targetPort); return CompletableFuture.runAsync( () -> getService(serviceType, clusterId) .ifPresent( service -> { final Service updatedService = new ServiceBuilder( service.getInternalResource()) .editSpec() .editMatchingPort( servicePortBuilder -> servicePortBuilder .build() .getName() .equals( portName)) .withTargetPort( new IntOrString(targetPort)) .endPort() .endSpec() .build(); this.internalClient .services() .withName( getServiceName(serviceType, clusterId)) .replace(updatedService); }), kubeClientExecutorService); } /** * Get the Kubernetes service name. * * @param serviceType The service type * @param clusterId The cluster id * @return Return the Kubernetes service name if the service type is known. */ private String getServiceName(KubernetesService.ServiceType serviceType, String clusterId) { switch (serviceType) { case REST_SERVICE: return ExternalServiceDecorator.getExternalServiceName(clusterId); case INTERNAL_SERVICE: return InternalServiceDecorator.getInternalServiceName(clusterId); default: throw new IllegalArgumentException( "Unrecognized service type: " + serviceType.name()); } } private void setOwnerReference(Deployment deployment, List resources) { final OwnerReference deploymentOwnerReference = new OwnerReferenceBuilder() .withName(deployment.getMetadata().getName()) .withApiVersion(deployment.getApiVersion()) .withUid(deployment.getMetadata().getUid()) .withKind(deployment.getKind()) .withController(true) .withBlockOwnerDeletion(true) .build(); resources.forEach(resource -> resource.getMetadata().setOwnerReferences(Collections.singletonList(deploymentOwnerReference))); } /** * Get rest port from the external Service. */ private int getRestPortFromExternalService(Service externalService) { final List servicePortCandidates = externalService.getSpec().getPorts() .stream() .filter(x -> x.getName().equals(Constants.REST_PORT_NAME)) .collect(Collectors.toList()); if (servicePortCandidates.isEmpty()) { throw new RuntimeException("Failed to find port \"" + Constants.REST_PORT_NAME + "\" in Service \"" + ExternalServiceDecorator.getExternalServiceName(this.clusterId) + "\""); } final ServicePort externalServicePort = servicePortCandidates.get(0); final KubernetesConfigOptions.ServiceExposedType externalServiceType = KubernetesConfigOptions.ServiceExposedType.valueOf(externalService.getSpec().getType()); switch (externalServiceType) { case ClusterIP: case LoadBalancer: return externalServicePort.getPort(); case NodePort: return externalServicePort.getNodePort(); default: throw new RuntimeException("Unrecognized Service type: " + externalServiceType); } } private Optional getRestEndPointFromService(Service service, int restPort) { if (service.getStatus() == null) { return Optional.empty(); } LoadBalancerStatus loadBalancer = service.getStatus().getLoadBalancer(); boolean hasExternalIP = service.getSpec() != null && service.getSpec().getExternalIPs() != null && !service.getSpec().getExternalIPs().isEmpty(); if (loadBalancer != null) { return getLoadBalancerRestEndpoint(loadBalancer, restPort); } else if (hasExternalIP) { final String address = service.getSpec().getExternalIPs().get(0); if (address != null && !address.isEmpty()) { return Optional.of(new Endpoint(address, restPort)); } } return Optional.empty(); } private Optional getLoadBalancerRestEndpoint(LoadBalancerStatus loadBalancer, int restPort) { boolean hasIngress = loadBalancer.getIngress() != null && !loadBalancer.getIngress().isEmpty(); String address; if (hasIngress) { address = loadBalancer.getIngress().get(0).getIp(); // Use hostname when the ip address is null if (address == null || address.isEmpty()) { address = loadBalancer.getIngress().get(0).getHostname(); } } else { // Use node port address = this.internalClient.getMasterUrl().getHost(); } boolean noAddress = address == null || address.isEmpty(); return noAddress ? Optional.empty() : Optional.of(new Endpoint(address, restPort)); } } ================================================ FILE: flink-learning-k8s/flink-k8s/src/main/java/org/apache/flink/kubernetes/kubeclient/FlinkKubeClient.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.flink.kubernetes.kubeclient; import org.apache.flink.kubernetes.configuration.KubernetesLeaderElectionConfiguration; import org.apache.flink.kubernetes.kubeclient.resources.*; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.function.Function; /** * The client to talk with kubernetes. The interfaces will be called both in Client and ResourceManager. To avoid * potentially blocking the execution of RpcEndpoint's main thread, these interfaces * {@link #createTaskManagerPod(KubernetesPod)}, {@link #stopPod(String)} should be implemented asynchronously. */ public interface FlinkKubeClient extends AutoCloseable { /** * Create the Master components, this can include the Deployment, the ConfigMap(s), and the Service(s). * * @param kubernetesJMSpec jobmanager specification */ void createJobManagerComponent(KubernetesJobManagerSpecification kubernetesJMSpec); /** * Create task manager pod. * * @param kubernetesPod taskmanager pod * @return Return the taskmanager pod creation future */ CompletableFuture createTaskManagerPod(KubernetesPod kubernetesPod); /** * Stop a specified pod by name. * * @param podName pod name * @return Return the pod stop future */ CompletableFuture stopPod(String podName); /** * Stop cluster and clean up all resources, include services, auxiliary services and all running pods. * * @param clusterId cluster id */ void stopAndCleanupCluster(String clusterId); /** * Get the kubernetes service of the given flink clusterId. * * @param serviceType Internal/Rest * @param clusterId cluster id * @return Return the optional rest service of the specified cluster id. */ Optional getService( KubernetesService.ServiceType serviceType, String clusterId); /** * Get the rest endpoint for access outside cluster. * * @param clusterId cluster id * @return Return empty if the service does not exist or could not extract the Endpoint from the service. */ Optional getRestEndpoint(String clusterId); /** * List the pods with specified labels. * * @param labels labels to filter the pods * @return pod list */ List getPodsWithLabels(Map labels); /** * Log exceptions. */ void handleException(Exception e); /** * Watch the pods selected by labels and do the {@link WatchCallbackHandler}. * * @param labels labels to filter the pods to watch * @param podCallbackHandler podCallbackHandler which reacts to pod events * @return Return a watch for pods. It needs to be closed after use. */ KubernetesWatch watchPodsAndDoCallback( Map labels, WatchCallbackHandler podCallbackHandler); /** * Create a leader elector service based on Kubernetes api. * @param leaderElectionConfiguration election configuration * @param leaderCallbackHandler Callback when the current instance is leader or not. * * @return Return the created leader elector. It should be started manually via {@code KubernetesLeaderElector#run}. */ KubernetesLeaderElector createLeaderElector( KubernetesLeaderElectionConfiguration leaderElectionConfiguration, KubernetesLeaderElector.LeaderCallbackHandler leaderCallbackHandler); /** * Create the ConfigMap with specified content. If the ConfigMap already exists, a * {@link org.apache.flink.kubernetes.kubeclient.resources.KubernetesException} will be thrown. * * @param configMap ConfigMap to be created. * * @return Return the ConfigMap create future. The returned future will be completed exceptionally if the ConfigMap * already exists. */ CompletableFuture createConfigMap(KubernetesConfigMap configMap); /** * Get the ConfigMap with specified name. * * @param name name of the ConfigMap to retrieve. * * @return Return the ConfigMap, or empty if the ConfigMap does not exist. */ Optional getConfigMap(String name); /** * Update an existing ConfigMap with the data. Benefit from * resource version and combined with {@link #getConfigMap(String)}, we could perform a get-check-and-update * transactional operation. Since concurrent modification could happen on a same ConfigMap, * the update operation may fail. We need to retry internally in the implementation. * * @param configMapName configMapName specifies the name of the ConfigMap which shall be updated. * @param updateFunction Function to be applied to the obtained ConfigMap and get a new updated one. If the returned * optional is empty, we will not do the update. * * @return Return the ConfigMap update future. The boolean result indicates whether the ConfigMap is updated. The * returned future will be completed exceptionally if the ConfigMap does not exist. */ CompletableFuture checkAndUpdateConfigMap( String configMapName, Function> updateFunction); /** * Watch the ConfigMaps with specified name and do the {@link WatchCallbackHandler}. * * @param name name to filter the ConfigMaps to watch * @param callbackHandler callbackHandler which reacts to ConfigMap events * @return Return a watch for ConfigMaps. It needs to be closed after use. */ KubernetesWatch watchConfigMaps( String name, WatchCallbackHandler callbackHandler); /** * Delete the Kubernetes ConfigMaps by labels. This will be used by * {@link org.apache.flink.kubernetes.highavailability.KubernetesHaServices} to clean up all data. * @param labels labels to filter the resources. e.g. type: high-availability * * @return Return the delete future. */ CompletableFuture deleteConfigMapsByLabels(Map labels); /** * Delete a Kubernetes ConfigMap by name. * * @param configMapName ConfigMap name * * @return Return the delete future. */ CompletableFuture deleteConfigMap(String configMapName); /** * Close the Kubernetes client with no exception. */ void close(); /** * Update the target ports of the given Kubernetes service. * * @param serviceType The service type which needs to be updated * @param portName The port name which needs to be updated * @param targetPort The updated target port * @return Return the update service target port future */ CompletableFuture updateServiceTargetPort( KubernetesService.ServiceType serviceType, String clusterId, String portName, int targetPort); /** * Callback handler for kubernetes resources. */ interface WatchCallbackHandler { void onAdded(List resources); void onModified(List resources); void onDeleted(List resources); void onError(List resources); void handleFatalError(Throwable throwable); } } ================================================ FILE: flink-learning-k8s/flink-k8s/src/main/java/org/apache/flink/kubernetes/kubeclient/FlinkKubeClientFactory.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.flink.kubernetes.kubeclient; import io.fabric8.kubernetes.client.Config; import io.fabric8.kubernetes.client.DefaultKubernetesClient; import io.fabric8.kubernetes.client.KubernetesClientException; import io.fabric8.kubernetes.client.NamespacedKubernetesClient; import org.apache.flink.configuration.Configuration; import org.apache.flink.kubernetes.configuration.KubernetesConfigOptions; import org.apache.flink.runtime.util.ExecutorThreadFactory; import org.apache.flink.util.FileUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; import java.io.IOException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** A {@link FlinkKubeClientFactory} for creating the {@link FlinkKubeClient}. */ public class FlinkKubeClientFactory { private static final Logger LOG = LoggerFactory.getLogger(FlinkKubeClientFactory.class); private static final FlinkKubeClientFactory INSTANCE = new FlinkKubeClientFactory(); public static FlinkKubeClientFactory getInstance() { return INSTANCE; } /** * Create a Flink Kubernetes client with the given configuration. * * @param flinkConfig Flink configuration * @param useCase Flink Kubernetes client use case (e.g. client, resourcemanager, * kubernetes-ha-services) * @return Return the Flink Kubernetes client with the specified configuration and dedicated IO * executor. */ public FlinkKubeClient fromConfiguration(Configuration flinkConfig, String useCase) { final Config config; final String kubeContext = flinkConfig.getString(KubernetesConfigOptions.CONTEXT); if (kubeContext != null) { LOG.info("Configuring kubernetes client to use context {}.", kubeContext); } final String kubeConfigFile = flinkConfig.getString(KubernetesConfigOptions.KUBE_CONFIG_FILE); if (kubeConfigFile != null) { LOG.debug("Trying to load kubernetes config from file: {}.", kubeConfigFile); try { // If kubeContext is null, the default context in the kubeConfigFile will be used. // Note: the third parameter kubeconfigPath is optional and is set to null. It is // only used to rewrite // relative tls asset paths inside kubeconfig when a file is passed, and in the case // that the kubeconfig // references some assets via relative paths. config = Config.fromKubeconfig( kubeContext, FileUtils.readFileUtf8(new File(kubeConfigFile)), null); } catch (IOException e) { throw new KubernetesClientException("Load kubernetes config failed.", e); } } else { LOG.debug("Trying to load default kubernetes config."); config = Config.autoConfigure(kubeContext); } final String namespace = flinkConfig.getString(KubernetesConfigOptions.NAMESPACE); LOG.debug("Setting namespace of Kubernetes client to {}", namespace); config.setNamespace(namespace); final NamespacedKubernetesClient client = new DefaultKubernetesClient(config); final int poolSize = flinkConfig.get(KubernetesConfigOptions.KUBERNETES_CLIENT_IO_EXECUTOR_POOL_SIZE); return new Fabric8FlinkKubeClient( flinkConfig, client, createThreadPoolForAsyncIO(poolSize, useCase)); } private static ExecutorService createThreadPoolForAsyncIO(int poolSize, String useCase) { return Executors.newFixedThreadPool( poolSize, new ExecutorThreadFactory("flink-kube-client-io-for-" + useCase)); } } ================================================ FILE: flink-learning-k8s/flink-k8s/src/main/java/org/apache/flink/kubernetes/kubeclient/FlinkPod.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.flink.kubernetes.kubeclient; import io.fabric8.kubernetes.api.model.Container; import io.fabric8.kubernetes.api.model.ContainerBuilder; import io.fabric8.kubernetes.api.model.Pod; import io.fabric8.kubernetes.api.model.PodBuilder; import static org.apache.flink.util.Preconditions.checkNotNull; /** * A collection of variables that composes a JobManager/TaskManager Pod. This can include * the Pod, the main Container, and the InitContainer, etc. */ public class FlinkPod { private final Pod pod; private final Container mainContainer; public FlinkPod(Pod pod, Container mainContainer) { this.pod = pod; this.mainContainer = mainContainer; } public Pod getPod() { return pod; } public Container getMainContainer() { return mainContainer; } /** * Builder for creating a {@link FlinkPod}. */ public static class Builder { private Pod pod; private Container mainContainer; public Builder() { this.pod = new PodBuilder() .withNewMetadata() .endMetadata() .withNewSpec() .endSpec() .build(); this.mainContainer = new ContainerBuilder().build(); } public Builder(FlinkPod flinkPod) { checkNotNull(flinkPod); this.pod = checkNotNull(flinkPod.getPod()); this.mainContainer = checkNotNull(flinkPod.getMainContainer()); } public Builder withPod(Pod pod) { this.pod = checkNotNull(pod); return this; } public Builder withMainContainer(Container mainContainer) { this.mainContainer = checkNotNull(mainContainer); return this; } public FlinkPod build() { return new FlinkPod(this.pod, this.mainContainer); } } } ================================================ FILE: flink-learning-k8s/flink-k8s/src/main/java/org/apache/flink/kubernetes/kubeclient/KubeClientFactory.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.flink.kubernetes.kubeclient; import org.apache.flink.configuration.Configuration; import java.util.concurrent.Executor; /** * Factory to create {@link FlinkKubeClient}. */ public interface KubeClientFactory { /** * Get the kubernetes client with the given configuration. * * @param configuration flink configuration * @return Return the kubernetes client with the specified configuration. */ FlinkKubeClient fromConfiguration(Configuration configuration); /** * Get the kubernetes client with the given configuration and io executor. * * @param configuration flink configuration * @param ioExecutor IO executor * @return Return the kubernetes client with the specified flink configuration and IO executor. */ FlinkKubeClient fromConfiguration(Configuration configuration, Executor ioExecutor); } ================================================ FILE: flink-learning-k8s/flink-k8s/src/main/java/org/apache/flink/kubernetes/kubeclient/KubernetesJobManagerSpecification.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.flink.kubernetes.kubeclient; import io.fabric8.kubernetes.api.model.HasMetadata; import io.fabric8.kubernetes.api.model.apps.Deployment; import java.util.List; /** * Composition of the created Kubernetes components that represents a Flink application. */ public class KubernetesJobManagerSpecification { private Deployment deployment; private List accompanyingResources; public KubernetesJobManagerSpecification(Deployment deployment, List accompanyingResources) { this.deployment = deployment; this.accompanyingResources = accompanyingResources; } public Deployment getDeployment() { return deployment; } public List getAccompanyingResources() { return accompanyingResources; } } ================================================ FILE: flink-learning-k8s/flink-k8s/src/main/java/org/apache/flink/kubernetes/kubeclient/decorators/AbstractKubernetesStepDecorator.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.flink.kubernetes.kubeclient.decorators; import io.fabric8.kubernetes.api.model.HasMetadata; import org.apache.flink.kubernetes.kubeclient.FlinkPod; import java.io.IOException; import java.util.Collections; import java.util.List; /** * An abstract {@link KubernetesStepDecorator} contains common implementations for different plug-in features. */ public abstract class AbstractKubernetesStepDecorator implements KubernetesStepDecorator { /** * Apply transformations on the given FlinkPod in accordance to this feature. * Note that we should return a FlinkPod that keeps all of the properties of the passed FlinkPod object. * *

So this is correct: * *

	 * {@code
	 *
	 * Pod decoratedPod = new PodBuilder(pod) // Keeps the original state
	 *     ...
	 *     .build()
	 *
	 * Container decoratedContainer = new ContainerBuilder(container) // Keeps the original state
	 *     ...
	 *     .build()
	 *
	 * FlinkPod decoratedFlinkPod = new FlinkPodBuilder(flinkPod) // Keeps the original state
	 *     ...
	 *     .build()
	 *
	 * }
	 * 
* *

And this is the incorrect: * *

	 * {@code
	 *
	 * Pod decoratedPod = new PodBuilder() // Loses the original state
	 *     ...
	 *     .build()
	 *
	 * Container decoratedContainer = new ContainerBuilder() // Loses the original state
	 *     ...
	 *     .build()
	 *
	 * FlinkPod decoratedFlinkPod = new FlinkPodBuilder() // Loses the original state
	 *     ...
	 *     .build()
	 *
	 * }
	 * 
*/ @Override public FlinkPod decorateFlinkPod(FlinkPod flinkPod) { return flinkPod; } /** * Note that the method could have a side effect of modifying the Flink Configuration object, such as * update the JobManager address. */ @Override public List buildAccompanyingKubernetesResources() throws IOException { return Collections.emptyList(); } } ================================================ FILE: flink-learning-k8s/flink-k8s/src/main/java/org/apache/flink/kubernetes/kubeclient/decorators/EnvSecretsDecorator.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.flink.kubernetes.kubeclient.decorators; import io.fabric8.kubernetes.api.model.Container; import io.fabric8.kubernetes.api.model.ContainerBuilder; import io.fabric8.kubernetes.api.model.EnvVar; import org.apache.flink.kubernetes.kubeclient.FlinkPod; import org.apache.flink.kubernetes.kubeclient.parameters.AbstractKubernetesParameters; import org.apache.flink.kubernetes.kubeclient.resources.KubernetesSecretEnvVar; import java.util.List; import java.util.stream.Collectors; import static org.apache.flink.util.Preconditions.checkNotNull; /** * Support setting environment variables via Secrets. */ public class EnvSecretsDecorator extends AbstractKubernetesStepDecorator { private final AbstractKubernetesParameters kubernetesComponentConf; public EnvSecretsDecorator(AbstractKubernetesParameters kubernetesComponentConf) { this.kubernetesComponentConf = checkNotNull(kubernetesComponentConf); } @Override public FlinkPod decorateFlinkPod(FlinkPod flinkPod) { final Container basicMainContainer = new ContainerBuilder(flinkPod.getMainContainer()) .addAllToEnv(getSecretEnvs()) .build(); return new FlinkPod.Builder(flinkPod) .withMainContainer(basicMainContainer) .build(); } private List getSecretEnvs() { return kubernetesComponentConf.getEnvironmentsFromSecrets().stream() .map(e -> KubernetesSecretEnvVar.fromMap(e).getInternalResource()) .collect(Collectors.toList()); } } ================================================ FILE: flink-learning-k8s/flink-k8s/src/main/java/org/apache/flink/kubernetes/kubeclient/decorators/ExternalServiceDecorator.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.flink.kubernetes.kubeclient.decorators; import io.fabric8.kubernetes.api.model.HasMetadata; import io.fabric8.kubernetes.api.model.Service; import io.fabric8.kubernetes.api.model.ServiceBuilder; import org.apache.flink.kubernetes.kubeclient.parameters.KubernetesJobManagerParameters; import org.apache.flink.kubernetes.utils.Constants; import java.io.IOException; import java.util.Collections; import java.util.List; import static org.apache.flink.util.Preconditions.checkNotNull; /** * Creates an external Service to expose the rest port of the Flink JobManager(s). */ public class ExternalServiceDecorator extends AbstractKubernetesStepDecorator { private final KubernetesJobManagerParameters kubernetesJobManagerParameters; public ExternalServiceDecorator(KubernetesJobManagerParameters kubernetesJobManagerParameters) { this.kubernetesJobManagerParameters = checkNotNull(kubernetesJobManagerParameters); } @Override public List buildAccompanyingKubernetesResources() throws IOException { final String serviceName = getExternalServiceName(kubernetesJobManagerParameters.getClusterId()); final Service externalService = new ServiceBuilder() .withApiVersion(Constants.API_VERSION) .withNewMetadata() .withName(serviceName) .withLabels(kubernetesJobManagerParameters.getCommonLabels()) .withAnnotations(kubernetesJobManagerParameters.getRestServiceAnnotations()) .endMetadata() .withNewSpec() .withType(kubernetesJobManagerParameters.getRestServiceExposedType().name()) .withSelector(kubernetesJobManagerParameters.getLabels()) .addNewPort() .withName(Constants.REST_PORT_NAME) .withPort(kubernetesJobManagerParameters.getRestPort()) .withNewTargetPort(kubernetesJobManagerParameters.getRestBindPort()) .endPort() .endSpec() .build(); return Collections.singletonList(externalService); } /** * Generate name of the external Service. */ public static String getExternalServiceName(String clusterId) { return clusterId + Constants.FLINK_REST_SERVICE_SUFFIX; } /** * Generate namespaced name of the external Service. */ public static String getNamespacedExternalServiceName(String clusterId, String namespace) { return getExternalServiceName(clusterId) + "." + namespace; } } ================================================ FILE: flink-learning-k8s/flink-k8s/src/main/java/org/apache/flink/kubernetes/kubeclient/decorators/FlinkConfMountDecorator.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.flink.kubernetes.kubeclient.decorators; import io.fabric8.kubernetes.api.model.*; import org.apache.flink.annotation.VisibleForTesting; import org.apache.flink.configuration.Configuration; import org.apache.flink.configuration.DeploymentOptionsInternal; import org.apache.flink.kubernetes.configuration.KubernetesConfigOptions; import org.apache.flink.kubernetes.kubeclient.FlinkPod; import org.apache.flink.kubernetes.kubeclient.parameters.AbstractKubernetesParameters; import org.apache.flink.kubernetes.utils.Constants; import org.apache.flink.shaded.guava18.com.google.common.io.Files; import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.nio.charset.StandardCharsets; import java.util.*; import java.util.stream.Collectors; import static org.apache.flink.configuration.GlobalConfiguration.FLINK_CONF_FILENAME; import static org.apache.flink.kubernetes.utils.Constants.*; import static org.apache.flink.util.Preconditions.checkNotNull; /** * Mounts the log4j.properties, logback.xml, and flink-conf.yaml configuration on the JobManager or TaskManager pod. */ public class FlinkConfMountDecorator extends AbstractKubernetesStepDecorator { private final AbstractKubernetesParameters kubernetesComponentConf; public FlinkConfMountDecorator(AbstractKubernetesParameters kubernetesComponentConf) { this.kubernetesComponentConf = checkNotNull(kubernetesComponentConf); } @Override public FlinkPod decorateFlinkPod(FlinkPod flinkPod) { final Pod mountedPod = decoratePod(flinkPod.getPod()); final Container mountedMainContainer = new ContainerBuilder(flinkPod.getMainContainer()) .addNewVolumeMount() .withName(FLINK_CONF_VOLUME) .withMountPath(kubernetesComponentConf.getFlinkConfDirInPod()) .endVolumeMount() .build(); return new FlinkPod.Builder(flinkPod) .withPod(mountedPod) .withMainContainer(mountedMainContainer) .build(); } private Pod decoratePod(Pod pod) { final List keyToPaths = getLocalLogConfFiles().stream() .map(file -> new KeyToPathBuilder() .withKey(file.getName()) .withPath(file.getName()) .build()) .collect(Collectors.toList()); keyToPaths.add(new KeyToPathBuilder() .withKey(FLINK_CONF_FILENAME) .withPath(FLINK_CONF_FILENAME) .build()); final Volume flinkConfVolume = new VolumeBuilder() .withName(FLINK_CONF_VOLUME) .withNewConfigMap() .withName(getFlinkConfConfigMapName(kubernetesComponentConf.getClusterId())) .withItems(keyToPaths) .endConfigMap() .build(); return new PodBuilder(pod) .editSpec() .addNewVolumeLike(flinkConfVolume) .endVolume() .endSpec() .build(); } @Override public List buildAccompanyingKubernetesResources() throws IOException { final String clusterId = kubernetesComponentConf.getClusterId(); final Map data = new HashMap<>(); final List localLogFiles = getLocalLogConfFiles(); for (File file : localLogFiles) { data.put(file.getName(), Files.toString(file, StandardCharsets.UTF_8)); } final Map propertiesMap = getClusterSidePropertiesMap(kubernetesComponentConf.getFlinkConfiguration()); data.put(FLINK_CONF_FILENAME, getFlinkConfData(propertiesMap)); final ConfigMap flinkConfConfigMap = new ConfigMapBuilder() .withApiVersion(Constants.API_VERSION) .withNewMetadata() .withName(getFlinkConfConfigMapName(clusterId)) .withLabels(kubernetesComponentConf.getCommonLabels()) .endMetadata() .addToData(data) .build(); return Collections.singletonList(flinkConfConfigMap); } /** * Get properties map for the cluster-side after removal of some keys. */ private Map getClusterSidePropertiesMap(Configuration flinkConfig) { final Configuration clusterSideConfig = flinkConfig.clone(); // Remove some configuration options that should not be taken to cluster side. clusterSideConfig.removeConfig(KubernetesConfigOptions.KUBE_CONFIG_FILE); clusterSideConfig.removeConfig(DeploymentOptionsInternal.CONF_DIR); return clusterSideConfig.toMap(); } @VisibleForTesting String getFlinkConfData(Map propertiesMap) throws IOException { try (StringWriter sw = new StringWriter(); PrintWriter out = new PrintWriter(sw)) { propertiesMap.forEach((k, v) -> { out.print(k); out.print(": "); out.println(v); }); return sw.toString(); } } private List getLocalLogConfFiles() { final String confDir = kubernetesComponentConf.getConfigDirectory(); final File logbackFile = new File(confDir, CONFIG_FILE_LOGBACK_NAME); final File log4jFile = new File(confDir, CONFIG_FILE_LOG4J_NAME); List localLogConfFiles = new ArrayList<>(); if (logbackFile.exists()) { localLogConfFiles.add(logbackFile); } if (log4jFile.exists()) { localLogConfFiles.add(log4jFile); } return localLogConfFiles; } @VisibleForTesting public static String getFlinkConfConfigMapName(String clusterId) { return CONFIG_MAP_PREFIX + clusterId; } } ================================================ FILE: flink-learning-k8s/flink-k8s/src/main/java/org/apache/flink/kubernetes/kubeclient/decorators/HadoopConfMountDecorator.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.flink.kubernetes.kubeclient.decorators; import io.fabric8.kubernetes.api.model.*; import org.apache.flink.kubernetes.kubeclient.FlinkPod; import org.apache.flink.kubernetes.kubeclient.parameters.AbstractKubernetesParameters; import org.apache.flink.kubernetes.utils.Constants; import org.apache.flink.util.FileUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; import java.io.IOException; import java.util.*; import java.util.stream.Collectors; import static org.apache.flink.util.Preconditions.checkNotNull; /** * Mount the custom Hadoop Configuration to the JobManager(s)/TaskManagers. We provide two options: * 1. Mount an existing ConfigMap containing custom Hadoop Configuration. * 2. Create and mount a dedicated ConfigMap containing the custom Hadoop configuration from a local directory * specified via the HADOOP_CONF_DIR or HADOOP_HOME environment variable. */ public class HadoopConfMountDecorator extends AbstractKubernetesStepDecorator { private static final Logger LOG = LoggerFactory.getLogger(HadoopConfMountDecorator.class); private final AbstractKubernetesParameters kubernetesParameters; public HadoopConfMountDecorator(AbstractKubernetesParameters kubernetesParameters) { this.kubernetesParameters = checkNotNull(kubernetesParameters); } @Override public FlinkPod decorateFlinkPod(FlinkPod flinkPod) { Volume hadoopConfVolume; final Optional existingConfigMap = kubernetesParameters.getExistingHadoopConfigurationConfigMap(); if (existingConfigMap.isPresent()) { hadoopConfVolume = new VolumeBuilder() .withName(Constants.HADOOP_CONF_VOLUME) .withNewConfigMap() .withName(existingConfigMap.get()) .endConfigMap() .build(); } else { final Optional localHadoopConfigurationDirectory = kubernetesParameters.getLocalHadoopConfigurationDirectory(); if (!localHadoopConfigurationDirectory.isPresent()) { return flinkPod; } final List hadoopConfigurationFileItems = getHadoopConfigurationFileItems(localHadoopConfigurationDirectory.get()); if (hadoopConfigurationFileItems.isEmpty()) { LOG.warn("Found 0 files in directory {}, skip to mount the Hadoop Configuration ConfigMap.", localHadoopConfigurationDirectory.get()); return flinkPod; } final List keyToPaths = hadoopConfigurationFileItems.stream() .map(file -> new KeyToPathBuilder() .withKey(file.getName()) .withPath(file.getName()) .build()) .collect(Collectors.toList()); hadoopConfVolume = new VolumeBuilder() .withName(Constants.HADOOP_CONF_VOLUME) .withNewConfigMap() .withName(getHadoopConfConfigMapName(kubernetesParameters.getClusterId())) .withItems(keyToPaths) .endConfigMap() .build(); } final Pod podWithHadoopConf = new PodBuilder(flinkPod.getPod()) .editOrNewSpec() .addNewVolumeLike(hadoopConfVolume) .endVolume() .endSpec() .build(); final Container containerWithHadoopConf = new ContainerBuilder(flinkPod.getMainContainer()) .addNewVolumeMount() .withName(Constants.HADOOP_CONF_VOLUME) .withMountPath(Constants.HADOOP_CONF_DIR_IN_POD) .endVolumeMount() .addNewEnv() .withName(Constants.ENV_HADOOP_CONF_DIR) .withValue(Constants.HADOOP_CONF_DIR_IN_POD) .endEnv() .build(); return new FlinkPod.Builder(flinkPod) .withPod(podWithHadoopConf) .withMainContainer(containerWithHadoopConf) .build(); } @Override public List buildAccompanyingKubernetesResources() throws IOException { if (kubernetesParameters.getExistingHadoopConfigurationConfigMap().isPresent()) { return Collections.emptyList(); } final Optional localHadoopConfigurationDirectory = kubernetesParameters.getLocalHadoopConfigurationDirectory(); if (!localHadoopConfigurationDirectory.isPresent()) { return Collections.emptyList(); } final List hadoopConfigurationFileItems = getHadoopConfigurationFileItems(localHadoopConfigurationDirectory.get()); if (hadoopConfigurationFileItems.isEmpty()) { LOG.warn("Found 0 files in directory {}, skip to create the Hadoop Configuration ConfigMap.", localHadoopConfigurationDirectory.get()); return Collections.emptyList(); } final Map data = new HashMap<>(); for (File file: hadoopConfigurationFileItems) { data.put(file.getName(), FileUtils.readFileUtf8(file)); } final ConfigMap hadoopConfigMap = new ConfigMapBuilder() .withApiVersion(Constants.API_VERSION) .withNewMetadata() .withName(getHadoopConfConfigMapName(kubernetesParameters.getClusterId())) .withLabels(kubernetesParameters.getCommonLabels()) .endMetadata() .addToData(data) .build(); return Collections.singletonList(hadoopConfigMap); } private List getHadoopConfigurationFileItems(String localHadoopConfigurationDirectory) { final List expectedFileNames = new ArrayList<>(); expectedFileNames.add("core-site.xml"); expectedFileNames.add("hdfs-site.xml"); final File directory = new File(localHadoopConfigurationDirectory); if (directory.exists() && directory.isDirectory()) { return Arrays.stream(directory.listFiles()) .filter(file -> file.isFile() && expectedFileNames.stream().anyMatch(name -> file.getName().equals(name))) .collect(Collectors.toList()); } else { return Collections.emptyList(); } } public static String getHadoopConfConfigMapName(String clusterId) { return Constants.HADOOP_CONF_CONFIG_MAP_PREFIX + clusterId; } } ================================================ FILE: flink-learning-k8s/flink-k8s/src/main/java/org/apache/flink/kubernetes/kubeclient/decorators/HiveConfMountDecorator.java ================================================ package org.apache.flink.kubernetes.kubeclient.decorators; import io.fabric8.kubernetes.api.model.HasMetadata; import org.apache.flink.kubernetes.kubeclient.FlinkPod; import org.apache.flink.kubernetes.kubeclient.parameters.AbstractKubernetesParameters; import org.apache.flink.kubernetes.utils.Constants; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; import java.io.IOException; import java.util.*; import java.util.stream.Collectors; import static org.apache.flink.util.Preconditions.checkNotNull; /** * Mount the custom Hive Configuration to the JobManager(s)/TaskManagers. We provide one options: * 1. Create and mount a dedicated ConfigMap containing the custom Hive configuration from a local directory * specified via the HIVE_CONF_DIR or HIVE_HOME environment variable. */ public class HiveConfMountDecorator extends AbstractKubernetesStepDecorator { private static final Logger LOG = LoggerFactory.getLogger(HadoopConfMountDecorator.class); private final AbstractKubernetesParameters kubernetesParameters; public HiveConfMountDecorator(AbstractKubernetesParameters kubernetesParameters) { this.kubernetesParameters = checkNotNull(kubernetesParameters); } @Override public FlinkPod decorateFlinkPod(FlinkPod flinkPod) { return super.decorateFlinkPod(flinkPod); } @Override public List buildAccompanyingKubernetesResources() throws IOException { return super.buildAccompanyingKubernetesResources(); } private List getHiveConfigurationFileItems(String localHiveConfigurationDirectory) { final List expectedFileNames = new ArrayList<>(); expectedFileNames.add("hive-site.xml"); final File directory = new File(localHiveConfigurationDirectory); if (directory.exists() && directory.isDirectory()) { return Arrays.stream(Objects.requireNonNull(directory.listFiles())) .filter(file -> file.isFile() && expectedFileNames.stream().anyMatch(name -> file.getName().equals(name))) .collect(Collectors.toList()); } else { return Collections.emptyList(); } } public static String getHiveConfigMapName(String clusterId) { return Constants.HIVE_CONF_CONFIG_MAP_PREFIX + clusterId; } } ================================================ FILE: flink-learning-k8s/flink-k8s/src/main/java/org/apache/flink/kubernetes/kubeclient/decorators/InitJobManagerDecorator.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.flink.kubernetes.kubeclient.decorators; import io.fabric8.kubernetes.api.model.*; import org.apache.flink.kubernetes.kubeclient.FlinkPod; import org.apache.flink.kubernetes.kubeclient.parameters.KubernetesJobManagerParameters; import org.apache.flink.kubernetes.kubeclient.resources.KubernetesToleration; import org.apache.flink.kubernetes.utils.Constants; import org.apache.flink.kubernetes.utils.KubernetesUtils; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.stream.Collectors; import static org.apache.flink.kubernetes.utils.Constants.*; import static org.apache.flink.util.Preconditions.checkNotNull; /** * An initializer for the JobManager {@link org.apache.flink.kubernetes.kubeclient.FlinkPod}. */ public class InitJobManagerDecorator extends AbstractKubernetesStepDecorator { private final KubernetesJobManagerParameters kubernetesJobManagerParameters; public InitJobManagerDecorator(KubernetesJobManagerParameters kubernetesJobManagerParameters) { this.kubernetesJobManagerParameters = checkNotNull(kubernetesJobManagerParameters); } @Override public FlinkPod decorateFlinkPod(FlinkPod flinkPod) { final Pod basicPod = new PodBuilder(flinkPod.getPod()) .withApiVersion(API_VERSION) .editOrNewMetadata() .withLabels(kubernetesJobManagerParameters.getLabels()) .withAnnotations(kubernetesJobManagerParameters.getAnnotations()) .endMetadata() .editOrNewSpec() .withServiceAccountName(kubernetesJobManagerParameters.getServiceAccount()) .withHostNetwork(kubernetesJobManagerParameters.isHostNetworkEnabled()) .withDnsPolicy( kubernetesJobManagerParameters.isHostNetworkEnabled() ? DNS_PLOICY_HOSTNETWORK : DNS_PLOICY_DEFAULT) .withImagePullSecrets(kubernetesJobManagerParameters.getImagePullSecrets()) .withNodeSelector(kubernetesJobManagerParameters.getNodeSelector()) .withTolerations(kubernetesJobManagerParameters.getTolerations().stream() .map(e -> KubernetesToleration.fromMap(e).getInternalResource()) .collect(Collectors.toList())) .endSpec() .build(); final Container basicMainContainer = decorateMainContainer(flinkPod.getMainContainer()); return new FlinkPod.Builder(flinkPod) .withPod(basicPod) .withMainContainer(basicMainContainer) .build(); } private Container decorateMainContainer(Container container) { //JM 资源描述 final ResourceRequirements requirements = KubernetesUtils.getResourceRequirements( kubernetesJobManagerParameters.getJobManagerMemoryMB(), kubernetesJobManagerParameters.getJobManagerMemoryRequestFactor(), kubernetesJobManagerParameters.getJobManagerCPU(), kubernetesJobManagerParameters.getJobManagerCPURequestFactor(), Collections.emptyMap()); return new ContainerBuilder(container) .withName(kubernetesJobManagerParameters.getJobManagerMainContainerName()) .withImage(kubernetesJobManagerParameters.getImage()) .withImagePullPolicy(kubernetesJobManagerParameters.getImagePullPolicy().name()) .withResources(requirements) .withPorts(getContainerPorts()) .withEnv(getCustomizedEnvs()) .addNewEnv() .withName(ENV_FLINK_HOST_IP_ADDRESS) .withValueFrom(new EnvVarSourceBuilder() .withNewFieldRef(API_VERSION, HOST_IP_FIELD_PATH) .build()) .endEnv() .addNewEnv() .withName(ENV_FLINK_POD_IP_ADDRESS) .withValueFrom(new EnvVarSourceBuilder() .withNewFieldRef(API_VERSION, POD_IP_FIELD_PATH) .build()) .endEnv() .addNewEnv() .withName("CLUSTER_ID") .withValue(kubernetesJobManagerParameters.getClusterId()) .endEnv() .build(); } private List getContainerPorts() { if (kubernetesJobManagerParameters.isHostNetworkEnabled()) { return Collections.emptyList(); } return Arrays.asList( new ContainerPortBuilder() .withName(Constants.REST_PORT_NAME) .withContainerPort(kubernetesJobManagerParameters.getRestPort()) .build(), new ContainerPortBuilder() .withName(Constants.JOB_MANAGER_RPC_PORT_NAME) .withContainerPort(kubernetesJobManagerParameters.getRPCPort()) .build(), new ContainerPortBuilder() .withName(Constants.BLOB_SERVER_PORT_NAME) .withContainerPort(kubernetesJobManagerParameters.getBlobServerPort()) .build()); } private List getCustomizedEnvs() { return kubernetesJobManagerParameters.getEnvironments() .entrySet() .stream() .map(kv -> new EnvVarBuilder() .withName(kv.getKey()) .withValue(kv.getValue()) .build()) .collect(Collectors.toList()); } } ================================================ FILE: flink-learning-k8s/flink-k8s/src/main/java/org/apache/flink/kubernetes/kubeclient/decorators/InitTaskManagerDecorator.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.flink.kubernetes.kubeclient.decorators; import io.fabric8.kubernetes.api.model.*; import org.apache.flink.kubernetes.kubeclient.FlinkPod; import org.apache.flink.kubernetes.kubeclient.parameters.KubernetesTaskManagerParameters; import org.apache.flink.kubernetes.kubeclient.resources.KubernetesToleration; import org.apache.flink.kubernetes.utils.Constants; import org.apache.flink.kubernetes.utils.KubernetesUtils; import java.util.Collections; import java.util.List; import java.util.stream.Collectors; import static org.apache.flink.kubernetes.utils.Constants.*; import static org.apache.flink.util.Preconditions.checkNotNull; /** * An initializer for the TaskManager {@link org.apache.flink.kubernetes.kubeclient.FlinkPod}. */ public class InitTaskManagerDecorator extends AbstractKubernetesStepDecorator { private final KubernetesTaskManagerParameters kubernetesTaskManagerParameters; public InitTaskManagerDecorator(KubernetesTaskManagerParameters kubernetesTaskManagerParameters) { this.kubernetesTaskManagerParameters = checkNotNull(kubernetesTaskManagerParameters); } @Override public FlinkPod decorateFlinkPod(FlinkPod flinkPod) { final Pod basicPod = new PodBuilder(flinkPod.getPod()) .withApiVersion(Constants.API_VERSION) .editOrNewMetadata() .withName(kubernetesTaskManagerParameters.getPodName()) .withLabels(kubernetesTaskManagerParameters.getLabels()) .withAnnotations(kubernetesTaskManagerParameters.getAnnotations()) .endMetadata() .editOrNewSpec() .withServiceAccountName(kubernetesTaskManagerParameters.getServiceAccount()) .withRestartPolicy(Constants.RESTART_POLICY_OF_NEVER) .withHostNetwork(kubernetesTaskManagerParameters.isHostNetworkEnabled()) .withDnsPolicy( kubernetesTaskManagerParameters.isHostNetworkEnabled() ? DNS_PLOICY_HOSTNETWORK : DNS_PLOICY_DEFAULT) .withImagePullSecrets(kubernetesTaskManagerParameters.getImagePullSecrets()) .withNodeSelector(kubernetesTaskManagerParameters.getNodeSelector()) .withTolerations(kubernetesTaskManagerParameters.getTolerations().stream() .map(e -> KubernetesToleration.fromMap(e).getInternalResource()) .collect(Collectors.toList())) .endSpec() .build(); final Container basicMainContainer = decorateMainContainer(flinkPod.getMainContainer()); return new FlinkPod.Builder(flinkPod) .withPod(basicPod) .withMainContainer(basicMainContainer) .build(); } private Container decorateMainContainer(Container container) { //TM 资源 final ResourceRequirements resourceRequirements = KubernetesUtils.getResourceRequirements( kubernetesTaskManagerParameters.getTaskManagerMemoryMB(), kubernetesTaskManagerParameters.getTaskManagerMemoryRequestFactor(), kubernetesTaskManagerParameters.getTaskManagerCPU(), kubernetesTaskManagerParameters.getTaskManagerCPURequestFactor(), kubernetesTaskManagerParameters.getTaskManagerExternalResources()); return new ContainerBuilder(container) .withName(kubernetesTaskManagerParameters.getTaskManagerMainContainerName()) .withImage(kubernetesTaskManagerParameters.getImage()) .withImagePullPolicy(kubernetesTaskManagerParameters.getImagePullPolicy().name()) .withResources(resourceRequirements) .withPorts(getContainerPorts()) .withEnv(getCustomizedEnvs()) .addNewEnv() .withName(ENV_FLINK_HOST_IP_ADDRESS) .withValueFrom(new EnvVarSourceBuilder() .withNewFieldRef(API_VERSION, HOST_IP_FIELD_PATH) .build()) .endEnv() .addNewEnv() .withName(ENV_FLINK_POD_IP_ADDRESS) .withValueFrom(new EnvVarSourceBuilder() .withNewFieldRef(API_VERSION, POD_IP_FIELD_PATH) .build()) .endEnv() .addNewEnv() .withName("CLUSTER_ID") .withValue(kubernetesTaskManagerParameters.getClusterId()) .endEnv() .build(); } private List getContainerPorts() { if (kubernetesTaskManagerParameters.isHostNetworkEnabled()) { return Collections.emptyList(); } return Collections.singletonList( new ContainerPortBuilder() .withName(Constants.TASK_MANAGER_RPC_PORT_NAME) .withContainerPort(kubernetesTaskManagerParameters.getRPCPort()) .build()); } private List getCustomizedEnvs() { return kubernetesTaskManagerParameters.getEnvironments() .entrySet() .stream() .map(kv -> new EnvVar(kv.getKey(), kv.getValue(), null)) .collect(Collectors.toList()); } } ================================================ FILE: flink-learning-k8s/flink-k8s/src/main/java/org/apache/flink/kubernetes/kubeclient/decorators/InternalServiceDecorator.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.flink.kubernetes.kubeclient.decorators; import io.fabric8.kubernetes.api.model.HasMetadata; import io.fabric8.kubernetes.api.model.Service; import io.fabric8.kubernetes.api.model.ServiceBuilder; import org.apache.flink.configuration.JobManagerOptions; import org.apache.flink.kubernetes.kubeclient.parameters.KubernetesJobManagerParameters; import org.apache.flink.kubernetes.utils.Constants; import java.io.IOException; import java.util.Collections; import java.util.List; import static org.apache.flink.util.Preconditions.checkNotNull; /** * Creates an internal Service which forwards the requests from the TaskManager(s) to the * active JobManager. * Note that only the non-HA scenario relies on this Service for internal communication, since * in the HA mode, the TaskManager(s) directly connects to the JobManager via IP address. */ public class InternalServiceDecorator extends AbstractKubernetesStepDecorator { private final KubernetesJobManagerParameters kubernetesJobManagerParameters; public InternalServiceDecorator(KubernetesJobManagerParameters kubernetesJobManagerParameters) { this.kubernetesJobManagerParameters = checkNotNull(kubernetesJobManagerParameters); } @Override public List buildAccompanyingKubernetesResources() throws IOException { if (!kubernetesJobManagerParameters.isInternalServiceEnabled()) { return Collections.emptyList(); } final String serviceName = getInternalServiceName(kubernetesJobManagerParameters.getClusterId()); final Service headlessService = new ServiceBuilder() .withApiVersion(Constants.API_VERSION) .withNewMetadata() .withName(serviceName) .withLabels(kubernetesJobManagerParameters.getCommonLabels()) .endMetadata() .withNewSpec() .withClusterIP(Constants.HEADLESS_SERVICE_CLUSTER_IP) .withSelector(kubernetesJobManagerParameters.getLabels()) .addNewPort() .withName(Constants.JOB_MANAGER_RPC_PORT_NAME) .withPort(kubernetesJobManagerParameters.getRPCPort()) .endPort() .addNewPort() .withName(Constants.BLOB_SERVER_PORT_NAME) .withPort(kubernetesJobManagerParameters.getBlobServerPort()) .endPort() .endSpec() .build(); // Set job manager address to namespaced service name final String namespace = kubernetesJobManagerParameters.getNamespace(); kubernetesJobManagerParameters.getFlinkConfiguration().setString( JobManagerOptions.ADDRESS, getNamespacedInternalServiceName(serviceName, namespace)); return Collections.singletonList(headlessService); } /** * Generate name of the internal Service. */ public static String getInternalServiceName(String clusterId) { return clusterId; } /** * Generate namespaced name of the internal Service. */ public static String getNamespacedInternalServiceName(String clusterId, String namespace) { return getInternalServiceName(clusterId) + "." + namespace; } } ================================================ FILE: flink-learning-k8s/flink-k8s/src/main/java/org/apache/flink/kubernetes/kubeclient/decorators/JavaCmdJobManagerDecorator.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.flink.kubernetes.kubeclient.decorators; import io.fabric8.kubernetes.api.model.Container; import io.fabric8.kubernetes.api.model.ContainerBuilder; import org.apache.flink.configuration.Configuration; import org.apache.flink.configuration.JobManagerOptions; import org.apache.flink.kubernetes.kubeclient.FlinkPod; import org.apache.flink.kubernetes.kubeclient.parameters.KubernetesJobManagerParameters; import org.apache.flink.kubernetes.utils.KubernetesUtils; import org.apache.flink.runtime.jobmanager.JobManagerProcessSpec; import org.apache.flink.runtime.jobmanager.JobManagerProcessUtils; import java.util.Arrays; import static org.apache.flink.kubernetes.utils.Constants.NATIVE_KUBERNETES_COMMAND; import static org.apache.flink.util.Preconditions.checkNotNull; /** * Attach the jvm command and args to the main container for running the JobManager code. */ public class JavaCmdJobManagerDecorator extends AbstractKubernetesStepDecorator { private final KubernetesJobManagerParameters kubernetesJobManagerParameters; public JavaCmdJobManagerDecorator(KubernetesJobManagerParameters kubernetesJobManagerParameters) { this.kubernetesJobManagerParameters = checkNotNull(kubernetesJobManagerParameters); } @Override public FlinkPod decorateFlinkPod(FlinkPod flinkPod) { final JobManagerProcessSpec processSpec = JobManagerProcessUtils.processSpecFromConfigWithNewOptionToInterpretLegacyHeap( kubernetesJobManagerParameters.getFlinkConfiguration(), JobManagerOptions.TOTAL_PROCESS_MEMORY); final String startCommand = getJobManagerStartCommand( kubernetesJobManagerParameters.getFlinkConfiguration(), processSpec, kubernetesJobManagerParameters.getFlinkConfDirInPod(), kubernetesJobManagerParameters.getFlinkLogDirInPod(), kubernetesJobManagerParameters.hasLogback(), kubernetesJobManagerParameters.hasLog4j(), kubernetesJobManagerParameters.getEntrypointClass()); final Container mainContainerWithStartCmd = new ContainerBuilder(flinkPod.getMainContainer()) .withCommand(kubernetesJobManagerParameters.getContainerEntrypoint()) .withArgs(Arrays.asList(NATIVE_KUBERNETES_COMMAND, startCommand)) .build(); return new FlinkPod.Builder(flinkPod) .withMainContainer(mainContainerWithStartCmd) .build(); } /** * Generates the shell command to start a jobmanager for kubernetes. * * @param flinkConfig The Flink configuration. * @param jobManagerProcessSpec JobManager process memory spec. * @param configDirectory The configuration directory for the flink-conf.yaml * @param logDirectory The log directory. * @param hasLogback Uses logback? * @param hasLog4j Uses log4j? * @param mainClass The main class to start with. * @return A String containing the job manager startup command. */ private static String getJobManagerStartCommand( Configuration flinkConfig, JobManagerProcessSpec jobManagerProcessSpec, String configDirectory, String logDirectory, boolean hasLogback, boolean hasLog4j, String mainClass) { final String jvmMemOpts = JobManagerProcessUtils.generateJvmParametersStr(jobManagerProcessSpec, flinkConfig); final String dynamicParameters = JobManagerProcessUtils.generateDynamicConfigsStr(jobManagerProcessSpec); return KubernetesUtils.getCommonStartCommand( flinkConfig, KubernetesUtils.ClusterComponent.JOB_MANAGER, jvmMemOpts, configDirectory, logDirectory, hasLogback, hasLog4j, mainClass, dynamicParameters); } } ================================================ FILE: flink-learning-k8s/flink-k8s/src/main/java/org/apache/flink/kubernetes/kubeclient/decorators/JavaCmdTaskManagerDecorator.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.flink.kubernetes.kubeclient.decorators; import io.fabric8.kubernetes.api.model.Container; import io.fabric8.kubernetes.api.model.ContainerBuilder; import org.apache.flink.configuration.Configuration; import org.apache.flink.kubernetes.kubeclient.FlinkPod; import org.apache.flink.kubernetes.kubeclient.parameters.KubernetesTaskManagerParameters; import org.apache.flink.kubernetes.taskmanager.KubernetesTaskExecutorRunner; import org.apache.flink.kubernetes.utils.KubernetesUtils; import org.apache.flink.runtime.clusterframework.ContaineredTaskManagerParameters; import org.apache.flink.runtime.clusterframework.TaskExecutorProcessSpec; import org.apache.flink.runtime.clusterframework.TaskExecutorProcessUtils; import org.apache.flink.runtime.entrypoint.parser.CommandLineOptions; import org.apache.flink.runtime.util.config.memory.ProcessMemoryUtils; import java.util.Arrays; import static org.apache.flink.kubernetes.utils.Constants.NATIVE_KUBERNETES_COMMAND; import static org.apache.flink.util.Preconditions.checkNotNull; /** * Attach the jvm command and args to the main container for running the TaskManager code. */ public class JavaCmdTaskManagerDecorator extends AbstractKubernetesStepDecorator { private final KubernetesTaskManagerParameters kubernetesTaskManagerParameters; public JavaCmdTaskManagerDecorator(KubernetesTaskManagerParameters kubernetesTaskManagerParameters) { this.kubernetesTaskManagerParameters = checkNotNull(kubernetesTaskManagerParameters); } @Override public FlinkPod decorateFlinkPod(FlinkPod flinkPod) { final Container mainContainerWithStartCmd = new ContainerBuilder(flinkPod.getMainContainer()) .withCommand(kubernetesTaskManagerParameters.getContainerEntrypoint()) .withArgs(Arrays.asList(NATIVE_KUBERNETES_COMMAND, getTaskManagerStartCommand())) .build(); return new FlinkPod.Builder(flinkPod) .withMainContainer(mainContainerWithStartCmd) .build(); } private String getTaskManagerStartCommand() { final String confDirInPod = kubernetesTaskManagerParameters.getFlinkConfDirInPod(); final String logDirInPod = kubernetesTaskManagerParameters.getFlinkLogDirInPod(); final String mainClassArgs = "--" + CommandLineOptions.CONFIG_DIR_OPTION.getLongOpt() + " " + confDirInPod + " " + kubernetesTaskManagerParameters.getDynamicProperties(); //组装好 task manager 启动命令 return getTaskManagerStartCommand( kubernetesTaskManagerParameters.getFlinkConfiguration(), kubernetesTaskManagerParameters.getContaineredTaskManagerParameters(), confDirInPod, logDirInPod, kubernetesTaskManagerParameters.hasLogback(), kubernetesTaskManagerParameters.hasLog4j(), KubernetesTaskExecutorRunner.class.getCanonicalName(), mainClassArgs); } private static String getTaskManagerStartCommand( Configuration flinkConfig, ContaineredTaskManagerParameters tmParams, String configDirectory, String logDirectory, boolean hasLogback, boolean hasLog4j, String mainClass, String mainArgs) { final TaskExecutorProcessSpec taskExecutorProcessSpec = tmParams.getTaskExecutorProcessSpec(); final String jvmMemOpts = ProcessMemoryUtils.generateJvmParametersStr(taskExecutorProcessSpec); String args = TaskExecutorProcessUtils.generateDynamicConfigsStr(taskExecutorProcessSpec); if (mainArgs != null) { args += " " + mainArgs; } return KubernetesUtils.getCommonStartCommand( flinkConfig, KubernetesUtils.ClusterComponent.TASK_MANAGER, jvmMemOpts, configDirectory, logDirectory, hasLogback, hasLog4j, mainClass, args); } } ================================================ FILE: flink-learning-k8s/flink-k8s/src/main/java/org/apache/flink/kubernetes/kubeclient/decorators/KerberosMountDecorator.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.flink.kubernetes.kubeclient.decorators; import io.fabric8.kubernetes.api.model.*; import org.apache.flink.configuration.SecurityOptions; import org.apache.flink.kubernetes.kubeclient.FlinkPod; import org.apache.flink.kubernetes.kubeclient.parameters.AbstractKubernetesParameters; import org.apache.flink.kubernetes.utils.Constants; import org.apache.flink.runtime.security.SecurityConfiguration; import org.apache.flink.shaded.guava18.com.google.common.io.Files; import org.apache.flink.util.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Base64; import java.util.List; import static org.apache.flink.util.Preconditions.checkNotNull; /** * Mount the custom Kerberos Configuration and Credential to the JobManager(s)/TaskManagers. */ public class KerberosMountDecorator extends AbstractKubernetesStepDecorator { private static final Logger LOG = LoggerFactory.getLogger(KerberosMountDecorator.class); private final AbstractKubernetesParameters kubernetesParameters; private final SecurityConfiguration securityConfig; public KerberosMountDecorator(AbstractKubernetesParameters kubernetesParameters) { this.kubernetesParameters = checkNotNull(kubernetesParameters); this.securityConfig = new SecurityConfiguration(kubernetesParameters.getFlinkConfiguration()); } @Override public FlinkPod decorateFlinkPod(FlinkPod flinkPod) { PodBuilder podBuilder = new PodBuilder(flinkPod.getPod()); ContainerBuilder containerBuilder = new ContainerBuilder(flinkPod.getMainContainer()); if (!StringUtils.isNullOrWhitespaceOnly(securityConfig.getKeytab()) && !StringUtils.isNullOrWhitespaceOnly(securityConfig.getPrincipal())) { podBuilder = podBuilder .editOrNewSpec() .addNewVolume() .withName(Constants.KERBEROS_KEYTAB_VOLUME) .withNewSecret() .withSecretName(getKerberosKeytabSecretName(kubernetesParameters.getClusterId())) .endSecret() .endVolume() .endSpec(); containerBuilder = containerBuilder .addNewVolumeMount() .withName(Constants.KERBEROS_KEYTAB_VOLUME) .withMountPath(Constants.KERBEROS_KEYTAB_MOUNT_POINT) .endVolumeMount(); } if (!StringUtils.isNullOrWhitespaceOnly(kubernetesParameters.getFlinkConfiguration().get(SecurityOptions.KERBEROS_KRB5_PATH))) { final File krb5Conf = new File(kubernetesParameters.getFlinkConfiguration().get(SecurityOptions.KERBEROS_KRB5_PATH)); podBuilder = podBuilder .editOrNewSpec() .addNewVolume() .withName(Constants.KERBEROS_KRB5CONF_VOLUME) .withNewConfigMap() .withName(getKerberosKrb5confConfigMapName(kubernetesParameters.getClusterId())) .withItems(new KeyToPathBuilder() .withKey(krb5Conf.getName()) .withPath(krb5Conf.getName()) .build()) .endConfigMap() .endVolume() .endSpec(); containerBuilder = containerBuilder .addNewVolumeMount() .withName(Constants.KERBEROS_KRB5CONF_VOLUME) .withMountPath(Constants.KERBEROS_KRB5CONF_MOUNT_DIR + "/krb5.conf") .withSubPath("krb5.conf") .endVolumeMount(); } return new FlinkPod(podBuilder.build(), containerBuilder.build()); } @Override public List buildAccompanyingKubernetesResources() throws IOException { final List resources = new ArrayList<>(); if (!StringUtils.isNullOrWhitespaceOnly(securityConfig.getKeytab()) && !StringUtils.isNullOrWhitespaceOnly(securityConfig.getPrincipal())) { final File keytab = new File(securityConfig.getKeytab()); if (!keytab.exists()) { LOG.warn("Could not found the kerberos keytab file in {}.", keytab.getAbsolutePath()); } else { resources.add(new SecretBuilder() .withNewMetadata() .withName(getKerberosKeytabSecretName(kubernetesParameters.getClusterId())) .endMetadata() .addToData(keytab.getName(), Base64.getEncoder().encodeToString(Files.toByteArray(keytab))) .build()); // Set keytab path in the container. One should make sure this decorator is triggered before FlinkConfMountDecorator. kubernetesParameters.getFlinkConfiguration().set(SecurityOptions.KERBEROS_LOGIN_KEYTAB, String.format("%s/%s", Constants.KERBEROS_KEYTAB_MOUNT_POINT, keytab.getName())); } } if (!StringUtils.isNullOrWhitespaceOnly(kubernetesParameters.getFlinkConfiguration().get(SecurityOptions.KERBEROS_KRB5_PATH))) { final File krb5Conf = new File(kubernetesParameters.getFlinkConfiguration().get(SecurityOptions.KERBEROS_KRB5_PATH)); if (!krb5Conf.exists()) { LOG.warn("Could not found the kerberos config file in {}.", krb5Conf.getAbsolutePath()); } else { resources.add( new ConfigMapBuilder() .withNewMetadata() .withName(getKerberosKrb5confConfigMapName(kubernetesParameters.getClusterId())) .endMetadata() .addToData(krb5Conf.getName(), Files.toString(krb5Conf, StandardCharsets.UTF_8)) .build()); } } return resources; } public static String getKerberosKeytabSecretName(String clusterId) { return Constants.KERBEROS_KEYTAB_SECRET_PREFIX + clusterId; } public static String getKerberosKrb5confConfigMapName(String clusterID) { return Constants.KERBEROS_KRB5CONF_CONFIG_MAP_PREFIX + clusterID; } } ================================================ FILE: flink-learning-k8s/flink-k8s/src/main/java/org/apache/flink/kubernetes/kubeclient/decorators/KubernetesStepDecorator.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.flink.kubernetes.kubeclient.decorators; import io.fabric8.kubernetes.api.model.HasMetadata; import org.apache.flink.kubernetes.kubeclient.FlinkPod; import java.io.IOException; import java.util.List; /** * A set of functions that together represent a feature in pods that are deployed for * the JobManager(s) or the TaskManager(s), which provides an extension to the way the * given Flink application works. */ public interface KubernetesStepDecorator { /** * Apply transformations to the given FlinkPod in accordance with this feature. This can include adding * labels/annotations, mounting volumes, and setting startup command or parameters, etc. */ FlinkPod decorateFlinkPod(FlinkPod flinkPod); /** * Build the accompanying Kubernetes resources that should be introduced to support this feature. This could * only be applicable on the client-side submission process. */ List buildAccompanyingKubernetesResources() throws IOException; } ================================================ FILE: flink-learning-k8s/flink-k8s/src/main/java/org/apache/flink/kubernetes/kubeclient/decorators/MountSecretsDecorator.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.flink.kubernetes.kubeclient.decorators; import io.fabric8.kubernetes.api.model.*; import org.apache.flink.kubernetes.kubeclient.FlinkPod; import org.apache.flink.kubernetes.kubeclient.parameters.AbstractKubernetesParameters; import static org.apache.flink.util.Preconditions.checkNotNull; /** * Support mounting Secrets on the JobManager or TaskManager pod.. */ public class MountSecretsDecorator extends AbstractKubernetesStepDecorator { private final AbstractKubernetesParameters kubernetesComponentConf; public MountSecretsDecorator(AbstractKubernetesParameters kubernetesComponentConf) { this.kubernetesComponentConf = checkNotNull(kubernetesComponentConf); } @Override public FlinkPod decorateFlinkPod(FlinkPod flinkPod) { final Pod podWithMount = decoratePod(flinkPod.getPod()); final Container containerWithMount = decorateMainContainer(flinkPod.getMainContainer()); return new FlinkPod.Builder(flinkPod) .withPod(podWithMount) .withMainContainer(containerWithMount) .build(); } private Container decorateMainContainer(Container container) { final VolumeMount[] volumeMounts = kubernetesComponentConf.getSecretNamesToMountPaths().entrySet().stream() .map(secretNameToPath -> new VolumeMountBuilder() .withName(secretVolumeName(secretNameToPath.getKey())) .withMountPath(secretNameToPath.getValue()) .build() ) .toArray(VolumeMount[]::new); return new ContainerBuilder(container) .addToVolumeMounts(volumeMounts) .build(); } private Pod decoratePod(Pod pod) { final Volume[] volumes = kubernetesComponentConf.getSecretNamesToMountPaths().keySet().stream() .map(secretName -> new VolumeBuilder() .withName(secretVolumeName(secretName)) .withNewSecret() .withSecretName(secretName) .endSecret() .build() ) .toArray(Volume[]::new); return new PodBuilder(pod) .editOrNewSpec() .addToVolumes(volumes) .endSpec() .build(); } private String secretVolumeName(String secretName) { return secretName + "-volume"; } } ================================================ FILE: flink-learning-k8s/flink-k8s/src/main/java/org/apache/flink/kubernetes/kubeclient/factory/KubernetesJobManagerFactory.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.flink.kubernetes.kubeclient.factory; import io.fabric8.kubernetes.api.model.Container; import io.fabric8.kubernetes.api.model.HasMetadata; import io.fabric8.kubernetes.api.model.Pod; import io.fabric8.kubernetes.api.model.PodBuilder; import io.fabric8.kubernetes.api.model.apps.Deployment; import io.fabric8.kubernetes.api.model.apps.DeploymentBuilder; import org.apache.flink.kubernetes.kubeclient.FlinkPod; import org.apache.flink.kubernetes.kubeclient.KubernetesJobManagerSpecification; import org.apache.flink.kubernetes.kubeclient.decorators.*; import org.apache.flink.kubernetes.kubeclient.parameters.KubernetesJobManagerParameters; import org.apache.flink.kubernetes.utils.Constants; import org.apache.flink.kubernetes.utils.KubernetesUtils; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Map; /** * Utility class for constructing all the Kubernetes components on the client-side. This can * include the Deployment, the ConfigMap(s), and the Service(s). */ public class KubernetesJobManagerFactory { public static KubernetesJobManagerSpecification buildKubernetesJobManagerSpecification( KubernetesJobManagerParameters kubernetesJobManagerParameters) throws IOException { FlinkPod flinkPod = new FlinkPod.Builder().build(); List accompanyingResources = new ArrayList<>(); final KubernetesStepDecorator[] stepDecorators = new KubernetesStepDecorator[] { new InitJobManagerDecorator(kubernetesJobManagerParameters), new EnvSecretsDecorator(kubernetesJobManagerParameters), new MountSecretsDecorator(kubernetesJobManagerParameters), new JavaCmdJobManagerDecorator(kubernetesJobManagerParameters), new InternalServiceDecorator(kubernetesJobManagerParameters), new ExternalServiceDecorator(kubernetesJobManagerParameters), new HadoopConfMountDecorator(kubernetesJobManagerParameters), new KerberosMountDecorator(kubernetesJobManagerParameters), new FlinkConfMountDecorator(kubernetesJobManagerParameters)}; for (KubernetesStepDecorator stepDecorator: stepDecorators) { flinkPod = stepDecorator.decorateFlinkPod(flinkPod); accompanyingResources.addAll(stepDecorator.buildAccompanyingKubernetesResources()); } final Deployment deployment = createJobManagerDeployment(flinkPod, kubernetesJobManagerParameters); return new KubernetesJobManagerSpecification(deployment, accompanyingResources); } private static Deployment createJobManagerDeployment( FlinkPod flinkPod, KubernetesJobManagerParameters kubernetesJobManagerParameters) { final Container resolvedMainContainer = flinkPod.getMainContainer(); final Pod resolvedPod = new PodBuilder(flinkPod.getPod()) .editOrNewSpec() .addToContainers(resolvedMainContainer) .endSpec() .build(); final Map labels = resolvedPod.getMetadata().getLabels(); return new DeploymentBuilder() .withApiVersion(Constants.APPS_API_VERSION) .editOrNewMetadata() .withName(KubernetesUtils.getDeploymentName(kubernetesJobManagerParameters.getClusterId())) .withAnnotations(kubernetesJobManagerParameters.getAnnotations()) .withLabels(kubernetesJobManagerParameters.getLabels()) .endMetadata() .editOrNewSpec() .withReplicas(kubernetesJobManagerParameters.getReplicas()) .editOrNewTemplate() .withMetadata(resolvedPod.getMetadata()) .withSpec(resolvedPod.getSpec()) .endTemplate() .editOrNewSelector() .addToMatchLabels(labels) .endSelector() .endSpec() .build(); } } ================================================ FILE: flink-learning-k8s/flink-k8s/src/main/java/org/apache/flink/kubernetes/kubeclient/factory/KubernetesTaskManagerFactory.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.flink.kubernetes.kubeclient.factory; import io.fabric8.kubernetes.api.model.Pod; import io.fabric8.kubernetes.api.model.PodBuilder; import org.apache.flink.kubernetes.kubeclient.FlinkPod; import org.apache.flink.kubernetes.kubeclient.decorators.*; import org.apache.flink.kubernetes.kubeclient.parameters.KubernetesTaskManagerParameters; import org.apache.flink.kubernetes.kubeclient.resources.KubernetesPod; /** * Utility class for constructing the TaskManager Pod on the JobManager. */ public class KubernetesTaskManagerFactory { public static KubernetesPod buildTaskManagerKubernetesPod(KubernetesTaskManagerParameters kubernetesTaskManagerParameters) { FlinkPod flinkPod = new FlinkPod.Builder().build(); final KubernetesStepDecorator[] stepDecorators = new KubernetesStepDecorator[] { new InitTaskManagerDecorator(kubernetesTaskManagerParameters), new EnvSecretsDecorator(kubernetesTaskManagerParameters), new MountSecretsDecorator(kubernetesTaskManagerParameters), new JavaCmdTaskManagerDecorator(kubernetesTaskManagerParameters), new HadoopConfMountDecorator(kubernetesTaskManagerParameters), new KerberosMountDecorator(kubernetesTaskManagerParameters), new FlinkConfMountDecorator(kubernetesTaskManagerParameters)}; for (KubernetesStepDecorator stepDecorator: stepDecorators) { flinkPod = stepDecorator.decorateFlinkPod(flinkPod); } final Pod resolvedPod = new PodBuilder(flinkPod.getPod()) .editOrNewSpec() .addToContainers(flinkPod.getMainContainer()) .endSpec() .build(); return new KubernetesPod(resolvedPod); } } ================================================ FILE: flink-learning-k8s/flink-k8s/src/main/java/org/apache/flink/kubernetes/kubeclient/parameters/AbstractKubernetesParameters.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.flink.kubernetes.kubeclient.parameters; import io.fabric8.kubernetes.api.model.LocalObjectReference; import org.apache.commons.lang3.StringUtils; import org.apache.flink.configuration.Configuration; import org.apache.flink.configuration.DeploymentOptionsInternal; import org.apache.flink.kubernetes.configuration.KubernetesConfigOptions; import org.apache.flink.kubernetes.utils.Constants; import org.apache.flink.kubernetes.utils.KubernetesUtils; import java.io.File; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Optional; import static org.apache.flink.kubernetes.configuration.KubernetesConfigOptions.CONTAINER_IMAGE_PULL_SECRETS; import static org.apache.flink.kubernetes.utils.Constants.CONFIG_FILE_LOG4J_NAME; import static org.apache.flink.kubernetes.utils.Constants.CONFIG_FILE_LOGBACK_NAME; import static org.apache.flink.util.Preconditions.checkArgument; import static org.apache.flink.util.Preconditions.checkNotNull; /** * Abstract class for the {@link KubernetesParameters}. */ public abstract class AbstractKubernetesParameters implements KubernetesParameters { protected final Configuration flinkConfig; public AbstractKubernetesParameters(Configuration flinkConfig) { this.flinkConfig = checkNotNull(flinkConfig); } public Configuration getFlinkConfiguration() { return flinkConfig; } @Override public String getConfigDirectory() { final String configDir = flinkConfig.getOptional(DeploymentOptionsInternal.CONF_DIR).orElse( flinkConfig.getString(KubernetesConfigOptions.FLINK_CONF_DIR)); checkNotNull(configDir); return configDir; } @Override public String getClusterId() { final String clusterId = flinkConfig.getString(KubernetesConfigOptions.CLUSTER_ID); if (StringUtils.isBlank(clusterId)) { throw new IllegalArgumentException(KubernetesConfigOptions.CLUSTER_ID.key() + " must not be blank."); } else if (clusterId.length() > Constants.MAXIMUM_CHARACTERS_OF_CLUSTER_ID) { throw new IllegalArgumentException(KubernetesConfigOptions.CLUSTER_ID.key() + " must be no more than " + Constants.MAXIMUM_CHARACTERS_OF_CLUSTER_ID + " characters."); } return clusterId; } @Override public String getNamespace() { final String namespace = flinkConfig.getString(KubernetesConfigOptions.NAMESPACE); checkArgument(!namespace.trim().isEmpty(), "Invalid " + KubernetesConfigOptions.NAMESPACE + "."); return namespace; } @Override public String getImage() { final String containerImage = flinkConfig.getString(KubernetesConfigOptions.CONTAINER_IMAGE); checkArgument(!containerImage.trim().isEmpty(), "Invalid " + KubernetesConfigOptions.CONTAINER_IMAGE + "."); return containerImage; } @Override public KubernetesConfigOptions.ImagePullPolicy getImagePullPolicy() { return flinkConfig.get(KubernetesConfigOptions.CONTAINER_IMAGE_PULL_POLICY); } @Override public LocalObjectReference[] getImagePullSecrets() { final List imagePullSecrets = flinkConfig.get(CONTAINER_IMAGE_PULL_SECRETS); if (imagePullSecrets == null) { return new LocalObjectReference[0]; } else { return imagePullSecrets.stream() .map(String::trim) .filter(secret -> !secret.isEmpty()) .map(LocalObjectReference::new) .toArray(LocalObjectReference[]::new); } } @Override public Map getCommonLabels() { return Collections.unmodifiableMap(KubernetesUtils.getCommonLabels(getClusterId())); } @Override public String getFlinkConfDirInPod() { return flinkConfig.getString(KubernetesConfigOptions.FLINK_CONF_DIR); } @Override public String getFlinkLogDirInPod() { return flinkConfig.getString(KubernetesConfigOptions.FLINK_LOG_DIR); } @Override public String getContainerEntrypoint() { return flinkConfig.getString(KubernetesConfigOptions.KUBERNETES_ENTRY_PATH); } @Override public boolean hasLogback() { final String confDir = getConfigDirectory(); final File logbackFile = new File(confDir, CONFIG_FILE_LOGBACK_NAME); return logbackFile.exists(); } @Override public boolean hasLog4j() { final String confDir = getConfigDirectory(); final File log4jFile = new File(confDir, CONFIG_FILE_LOG4J_NAME); return log4jFile.exists(); } @Override public Optional getExistingHadoopConfigurationConfigMap() { final String existingHadoopConfigMap = flinkConfig.getString(KubernetesConfigOptions.HADOOP_CONF_CONFIG_MAP); if (StringUtils.isBlank(existingHadoopConfigMap)) { return Optional.empty(); } else { return Optional.of(existingHadoopConfigMap.trim()); } } @Override public Optional getLocalHadoopConfigurationDirectory() { final String hadoopConfDirEnv = System.getenv(Constants.ENV_HADOOP_CONF_DIR); if (StringUtils.isNotBlank(hadoopConfDirEnv)) { return Optional.of(hadoopConfDirEnv); } final String hadoopHomeEnv = System.getenv(Constants.ENV_HADOOP_HOME); if (StringUtils.isNotBlank(hadoopHomeEnv)) { // Hadoop 2.2+ final File hadoop2ConfDir = new File(hadoopHomeEnv, "/etc/hadoop"); if (hadoop2ConfDir.exists()) { return Optional.of(hadoop2ConfDir.getAbsolutePath()); } // Hadoop 1.x final File hadoop1ConfDir = new File(hadoopHomeEnv, "/conf"); if (hadoop1ConfDir.exists()) { return Optional.of(hadoop1ConfDir.getAbsolutePath()); } } return Optional.empty(); } public Map getSecretNamesToMountPaths() { return flinkConfig.getOptional(KubernetesConfigOptions.KUBERNETES_SECRETS).orElse(Collections.emptyMap()); } @Override public List> getEnvironmentsFromSecrets() { return flinkConfig.getOptional(KubernetesConfigOptions.KUBERNETES_ENV_SECRET_KEY_REF).orElse(Collections.emptyList()); } public boolean isHostNetworkEnabled() { return flinkConfig.getBoolean(KubernetesConfigOptions.KUBERNETES_HOSTNETWORK_ENABLED); } } ================================================ FILE: flink-learning-k8s/flink-k8s/src/main/java/org/apache/flink/kubernetes/kubeclient/parameters/KubernetesJobManagerParameters.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.flink.kubernetes.kubeclient.parameters; import org.apache.flink.client.deployment.ClusterSpecification; import org.apache.flink.configuration.*; import org.apache.flink.kubernetes.configuration.KubernetesConfigOptions; import org.apache.flink.kubernetes.configuration.KubernetesConfigOptionsInternal; import org.apache.flink.kubernetes.utils.Constants; import org.apache.flink.kubernetes.utils.KubernetesUtils; import org.apache.flink.runtime.jobmanager.HighAvailabilityMode; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import static org.apache.flink.util.Preconditions.checkArgument; import static org.apache.flink.util.Preconditions.checkNotNull; /** * A utility class helps parse, verify, and manage the Kubernetes side parameters * that are used for constructing the JobManager Pod and all accompanying resources * connected to it. */ public class KubernetesJobManagerParameters extends AbstractKubernetesParameters { public static final String JOB_MANAGER_MAIN_CONTAINER_NAME = "flink-job-manager"; private final ClusterSpecification clusterSpecification; public KubernetesJobManagerParameters(Configuration flinkConfig, ClusterSpecification clusterSpecification) { super(flinkConfig); this.clusterSpecification = checkNotNull(clusterSpecification); } @Override public Map getLabels() { final Map labels = new HashMap<>(); labels.putAll(flinkConfig.getOptional(KubernetesConfigOptions.JOB_MANAGER_LABELS).orElse(Collections.emptyMap())); labels.putAll(getCommonLabels()); labels.put(Constants.LABEL_COMPONENT_KEY, Constants.LABEL_COMPONENT_JOB_MANAGER); return Collections.unmodifiableMap(labels); } @Override public Map getNodeSelector() { return Collections.unmodifiableMap( flinkConfig.getOptional(KubernetesConfigOptions.JOB_MANAGER_NODE_SELECTOR).orElse(Collections.emptyMap())); } @Override public Map getEnvironments() { return ConfigurationUtils.getPrefixedKeyValuePairs(ResourceManagerOptions.CONTAINERIZED_MASTER_ENV_PREFIX, flinkConfig); } @Override public Map getAnnotations() { return flinkConfig.getOptional(KubernetesConfigOptions.JOB_MANAGER_ANNOTATIONS).orElse(Collections.emptyMap()); } @Override public List> getTolerations() { return flinkConfig.getOptional(KubernetesConfigOptions.JOB_MANAGER_TOLERATIONS).orElse(Collections.emptyList()); } public Map getRestServiceAnnotations() { return flinkConfig.getOptional(KubernetesConfigOptions.REST_SERVICE_ANNOTATIONS).orElse(Collections.emptyMap()); } public String getJobManagerMainContainerName() { return JOB_MANAGER_MAIN_CONTAINER_NAME; } public int getJobManagerMemoryMB() { return clusterSpecification.getMasterMemoryMB(); } public double getJobManagerCPU() { return flinkConfig.getDouble(KubernetesConfigOptions.JOB_MANAGER_CPU); } public double getJobManagerCPURequestFactor() { final double requestFactor = flinkConfig.getDouble(KubernetesConfigOptions.JOB_MANAGER_CPU_REQUEST_FACTOR); checkArgument( requestFactor <= 1, "%s should be less than or equal to 1.", KubernetesConfigOptions.JOB_MANAGER_CPU_REQUEST_FACTOR.key()); return requestFactor; } public double getJobManagerMemoryRequestFactor() { final double requestFactor = flinkConfig.getDouble(KubernetesConfigOptions.JOB_MANAGER_MEMORY_REQUEST_FACTOR); checkArgument( requestFactor <= 1, "%s should be less than or equal to 1.", KubernetesConfigOptions.JOB_MANAGER_MEMORY_REQUEST_FACTOR.key()); return requestFactor; } public int getRestPort() { return flinkConfig.getInteger(RestOptions.PORT); } public int getRestBindPort() { return Integer.valueOf(flinkConfig.getString(RestOptions.BIND_PORT)); } public int getRPCPort() { return flinkConfig.getInteger(JobManagerOptions.PORT); } public int getBlobServerPort() { final int blobServerPort = KubernetesUtils.parsePort(flinkConfig, BlobServerOptions.PORT); checkArgument(blobServerPort > 0, "%s should not be 0.", BlobServerOptions.PORT.key()); return blobServerPort; } public String getServiceAccount() { return flinkConfig.getOptional(KubernetesConfigOptions.JOB_MANAGER_SERVICE_ACCOUNT) .orElse(flinkConfig.getString(KubernetesConfigOptions.KUBERNETES_SERVICE_ACCOUNT)); } public String getEntrypointClass() { final String entrypointClass = flinkConfig.getString(KubernetesConfigOptionsInternal.ENTRY_POINT_CLASS); checkNotNull(entrypointClass, KubernetesConfigOptionsInternal.ENTRY_POINT_CLASS + " must be specified!"); return entrypointClass; } public KubernetesConfigOptions.ServiceExposedType getRestServiceExposedType() { return flinkConfig.get(KubernetesConfigOptions.REST_SERVICE_EXPOSED_TYPE); } public boolean isInternalServiceEnabled() { return !HighAvailabilityMode.isHighAvailabilityModeActivated(flinkConfig); } public int getReplicas() { final int replicas = flinkConfig.get(KubernetesConfigOptions.KUBERNETES_JOBMANAGER_REPLICAS); if (replicas < 1) { throw new IllegalConfigurationException( String.format( "'%s' should not be configured less than one.", KubernetesConfigOptions.KUBERNETES_JOBMANAGER_REPLICAS.key())); } else if (replicas > 1 && !HighAvailabilityMode.isHighAvailabilityModeActivated(flinkConfig)) { throw new IllegalConfigurationException( "High availability should be enabled when starting standby JobManagers."); } return replicas; } } ================================================ FILE: flink-learning-k8s/flink-k8s/src/main/java/org/apache/flink/kubernetes/kubeclient/parameters/KubernetesParameters.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.flink.kubernetes.kubeclient.parameters; import io.fabric8.kubernetes.api.model.LocalObjectReference; import org.apache.flink.kubernetes.configuration.KubernetesConfigOptions; import java.util.List; import java.util.Map; import java.util.Optional; /** * A common collection of parameters that is used to construct the JobManager/TaskManager Pods, * including the accompanying Kubernetes resources that together represent a Flink application. */ public interface KubernetesParameters { String getConfigDirectory(); String getClusterId(); String getNamespace(); String getImage(); KubernetesConfigOptions.ImagePullPolicy getImagePullPolicy(); LocalObjectReference[] getImagePullSecrets(); /** * A common collection of labels that are attached to every created Kubernetes resources. * This can include the Deployment, the Pod(s), the ConfigMap(s), and the Service(s), etc. */ Map getCommonLabels(); /** * A collection of labels that are attached to the JobManager and TaskManager Pod(s). */ Map getLabels(); /** * A collection of node selector to constrain a pod to only be able to run on particular node(s). */ Map getNodeSelector(); /** * A collection of customized environments that are attached to the JobManager and TaskManager Container(s). */ Map getEnvironments(); /** * A map of user-specified annotations that are set to the JobManager and TaskManager pods. */ Map getAnnotations(); /** * A collection of tolerations that are set to the JobManager and TaskManager Pod(s). Kubernetes taints and * tolerations work together to ensure that pods are not scheduled onto inappropriate nodes. */ List> getTolerations(); /** * Directory in Pod that stores the flink-conf.yaml, log4j.properties, and the logback.xml. */ String getFlinkConfDirInPod(); /** * Directory in Pod that saves the log files. */ String getFlinkLogDirInPod(); /** * The docker entrypoint that starts processes in the container. */ String getContainerEntrypoint(); /** * Whether the logback.xml is located. */ boolean hasLogback(); /** * Whether the log4j.properties is located. */ boolean hasLog4j(); /** * The existing ConfigMap containing custom Hadoop configuration. */ Optional getExistingHadoopConfigurationConfigMap(); /** * The local directory to locate the custom Hadoop configuration. */ Optional getLocalHadoopConfigurationDirectory(); /** * A collection of secret and path pairs that are mounted to the JobManager and TaskManager container(s). */ Map getSecretNamesToMountPaths(); /** * A collection of customized environments that are attached to the JobManager and TaskManager container(s). */ List> getEnvironmentsFromSecrets(); } ================================================ FILE: flink-learning-k8s/flink-k8s/src/main/java/org/apache/flink/kubernetes/kubeclient/parameters/KubernetesTaskManagerParameters.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.flink.kubernetes.kubeclient.parameters; import org.apache.flink.configuration.Configuration; import org.apache.flink.configuration.TaskManagerOptions; import org.apache.flink.kubernetes.configuration.KubernetesConfigOptions; import org.apache.flink.kubernetes.utils.KubernetesUtils; import org.apache.flink.runtime.clusterframework.ContaineredTaskManagerParameters; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import static org.apache.flink.util.Preconditions.checkArgument; import static org.apache.flink.util.Preconditions.checkNotNull; /** * A utility class helps parse, verify, and manage the Kubernetes side parameters * that are used for constructing the TaskManager Pod. */ public class KubernetesTaskManagerParameters extends AbstractKubernetesParameters { public static final String TASK_MANAGER_MAIN_CONTAINER_NAME = "flink-task-manager"; private final String podName; private final String dynamicProperties; private final ContaineredTaskManagerParameters containeredTaskManagerParameters; private final Map taskManagerExternalResources; public KubernetesTaskManagerParameters( Configuration flinkConfig, String podName, String dynamicProperties, ContaineredTaskManagerParameters containeredTaskManagerParameters, Map taskManagerExternalResources) { super(flinkConfig); this.podName = checkNotNull(podName); this.dynamicProperties = checkNotNull(dynamicProperties); this.containeredTaskManagerParameters = checkNotNull(containeredTaskManagerParameters); this.taskManagerExternalResources = checkNotNull(taskManagerExternalResources); } @Override public Map getLabels() { final Map labels = new HashMap<>(); labels.putAll(flinkConfig.getOptional(KubernetesConfigOptions.TASK_MANAGER_LABELS).orElse(Collections.emptyMap())); labels.putAll(KubernetesUtils.getTaskManagerLabels(getClusterId())); return Collections.unmodifiableMap(labels); } @Override public Map getNodeSelector() { return Collections.unmodifiableMap( flinkConfig.getOptional(KubernetesConfigOptions.TASK_MANAGER_NODE_SELECTOR).orElse(Collections.emptyMap())); } @Override public Map getEnvironments() { return this.containeredTaskManagerParameters.taskManagerEnv(); } @Override public Map getAnnotations() { return flinkConfig.getOptional(KubernetesConfigOptions.TASK_MANAGER_ANNOTATIONS).orElse(Collections.emptyMap()); } @Override public List> getTolerations() { return flinkConfig.getOptional(KubernetesConfigOptions.TASK_MANAGER_TOLERATIONS).orElse(Collections.emptyList()); } public String getTaskManagerMainContainerName() { return TASK_MANAGER_MAIN_CONTAINER_NAME; } public String getPodName() { return podName; } public int getTaskManagerMemoryMB() { return containeredTaskManagerParameters.getTaskExecutorProcessSpec().getTotalProcessMemorySize().getMebiBytes(); } public double getTaskManagerCPU() { return containeredTaskManagerParameters.getTaskExecutorProcessSpec().getCpuCores().getValue().doubleValue(); } public double getTaskManagerCPURequestFactor() { final double requestFactor = flinkConfig.getDouble(KubernetesConfigOptions.TASK_MANAGER_CPU_REQUEST_FACTOR); checkArgument( requestFactor <= 1, "%s should be less than or equal to 1.", KubernetesConfigOptions.TASK_MANAGER_CPU_REQUEST_FACTOR.key()); return requestFactor; } public double getTaskManagerMemoryRequestFactor() { final double requestFactor = flinkConfig.getDouble(KubernetesConfigOptions.TASK_MANAGER_MEMORY_REQUEST_FACTOR); checkArgument( requestFactor <= 1, "%s should be less than or equal to 1.", KubernetesConfigOptions.TASK_MANAGER_MEMORY_REQUEST_FACTOR.key()); return requestFactor; } public String getServiceAccount() { return flinkConfig.getOptional(KubernetesConfigOptions.TASK_MANAGER_SERVICE_ACCOUNT) .orElse(flinkConfig.getString(KubernetesConfigOptions.KUBERNETES_SERVICE_ACCOUNT)); } public Map getTaskManagerExternalResources() { return taskManagerExternalResources; } public int getRPCPort() { final int taskManagerRpcPort = KubernetesUtils.parsePort(flinkConfig, TaskManagerOptions.RPC_PORT); checkArgument(taskManagerRpcPort > 0, "%s should not be 0.", TaskManagerOptions.RPC_PORT.key()); return taskManagerRpcPort; } public String getDynamicProperties() { return dynamicProperties; } public ContaineredTaskManagerParameters getContaineredTaskManagerParameters() { return containeredTaskManagerParameters; } } ================================================ FILE: flink-learning-k8s/flink-k8s/src/main/java/org/apache/flink/kubernetes/kubeclient/resources/AbstractKubernetesWatcher.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.flink.kubernetes.kubeclient.resources; import io.fabric8.kubernetes.api.model.HasMetadata; import io.fabric8.kubernetes.client.KubernetesClientException; import io.fabric8.kubernetes.client.Watcher; import org.apache.flink.kubernetes.kubeclient.FlinkKubeClient; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import static java.net.HttpURLConnection.HTTP_GONE; /** * Watcher for resources in Kubernetes. */ public abstract class AbstractKubernetesWatcher> implements Watcher { protected final Logger logger = LoggerFactory.getLogger(getClass()); protected final FlinkKubeClient.WatchCallbackHandler callbackHandler; AbstractKubernetesWatcher(FlinkKubeClient.WatchCallbackHandler callbackHandler) { this.callbackHandler = callbackHandler; } @Override public void onClose(KubernetesClientException cause) { // null means the watcher is closed normally. if (cause == null) { logger.info("The watcher is closing."); } else { // Fabric8 Kubernetes client will directly close the watcher when received a HTTP_GONE // status code, so this should be handled by the caller. Refer to // https://github.com/fabric8io/kubernetes-client/blob/v4.9.2/kubernetes-client/src/main/java/io/fabric8/kubernetes/client/dsl/internal/WatchConnectionManager.java#L255 // for more information about the implementation. if (cause.getCode() == HTTP_GONE) { logger.debug( "Got a http code 'HTTP_GONE' which means the Kubernetes client has the " + "too old resource version.", cause); callbackHandler.handleFatalError(new KubernetesTooOldResourceVersionException(cause)); } else { callbackHandler.handleFatalError(cause); } } } } ================================================ FILE: flink-learning-k8s/flink-k8s/src/main/java/org/apache/flink/kubernetes/kubeclient/resources/KubernetesConfigMap.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.flink.kubernetes.kubeclient.resources; import io.fabric8.kubernetes.api.model.ConfigMap; import java.util.HashMap; import java.util.Map; /** * Represent {@link ConfigMap} resource in kubernetes. */ public class KubernetesConfigMap extends KubernetesResource { public KubernetesConfigMap(ConfigMap configMap) { super(configMap); } public String getName() { return this.getInternalResource().getMetadata().getName(); } public String getResourceVersion() { return this.getInternalResource().getMetadata().getResourceVersion(); } public Map getAnnotations() { if (this.getInternalResource().getMetadata().getAnnotations() == null) { this.getInternalResource().getMetadata().setAnnotations(new HashMap<>()); } return this.getInternalResource().getMetadata().getAnnotations(); } public Map getData() { if (this.getInternalResource().getData() == null) { this.getInternalResource().setData(new HashMap<>()); } return this.getInternalResource().getData(); } public Map getLabels() { if (this.getInternalResource().getMetadata().getLabels() == null) { this.getInternalResource().getMetadata().setLabels(new HashMap<>()); } return this.getInternalResource().getMetadata().getLabels(); } } ================================================ FILE: flink-learning-k8s/flink-k8s/src/main/java/org/apache/flink/kubernetes/kubeclient/resources/KubernetesConfigMapWatcher.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.flink.kubernetes.kubeclient.resources; import io.fabric8.kubernetes.api.model.ConfigMap; import org.apache.flink.kubernetes.kubeclient.FlinkKubeClient; import org.apache.flink.kubernetes.utils.KubernetesUtils; import java.util.Collections; import java.util.List; /** * Watcher for {@link ConfigMap ConfigMaps} in Kubernetes. */ public class KubernetesConfigMapWatcher extends AbstractKubernetesWatcher { public KubernetesConfigMapWatcher(FlinkKubeClient.WatchCallbackHandler callbackHandler) { super(callbackHandler); } @Override public void eventReceived(Action action, ConfigMap configMap) { logger.debug( "Received {} event for configMap {}, details: {}{}", action, configMap.getMetadata().getName(), System.lineSeparator(), KubernetesUtils.tryToGetPrettyPrintYaml(configMap)); final List configMaps = Collections.singletonList(new KubernetesConfigMap(configMap)); switch (action) { case ADDED: callbackHandler.onAdded(configMaps); break; case MODIFIED: callbackHandler.onModified(configMaps); break; case ERROR: callbackHandler.onError(configMaps); break; case DELETED: callbackHandler.onDeleted(configMaps); break; default: logger.debug("Ignore handling {} event for configMap {}", action, configMap.getMetadata().getName()); break; } } } ================================================ FILE: flink-learning-k8s/flink-k8s/src/main/java/org/apache/flink/kubernetes/kubeclient/resources/KubernetesException.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.flink.kubernetes.kubeclient.resources; import org.apache.flink.util.FlinkException; /** * Kubernetes related checked exceptions. */ public class KubernetesException extends FlinkException { private static final long serialVersionUID = 1L; public KubernetesException(String message) { super(message); } public KubernetesException(String message, Throwable cause) { super(message, cause); } } ================================================ FILE: flink-learning-k8s/flink-k8s/src/main/java/org/apache/flink/kubernetes/kubeclient/resources/KubernetesLeaderElector.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.flink.kubernetes.kubeclient.resources; import io.fabric8.kubernetes.client.NamespacedKubernetesClient; import io.fabric8.kubernetes.client.extended.leaderelection.LeaderCallbacks; import io.fabric8.kubernetes.client.extended.leaderelection.LeaderElectionConfig; import io.fabric8.kubernetes.client.extended.leaderelection.LeaderElectionConfigBuilder; import io.fabric8.kubernetes.client.extended.leaderelection.LeaderElector; import io.fabric8.kubernetes.client.extended.leaderelection.resourcelock.ConfigMapLock; import org.apache.flink.annotation.VisibleForTesting; import org.apache.flink.kubernetes.configuration.KubernetesLeaderElectionConfiguration; import org.apache.flink.runtime.util.ExecutorThreadFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * Represent {@link KubernetesLeaderElector} in kubernetes. {@link LeaderElector#run()} is a blocking call. It should be * run in the IO executor, not the main thread. The lifecycle is bound to single leader election. Once the leadership * is revoked, as well as the {@link LeaderCallbackHandler#notLeader()} is called, the {@link LeaderElector#run()} will * finish. To start another round of election, we need to trigger again. * *

{@link LeaderElector#run()} is responsible for creating the leader ConfigMap and continuously update the * annotation. The annotation key is {@link #LEADER_ANNOTATION_KEY} and the value is in the following json format. * metadata: * annotations: * control-plane.alpha.kubernetes.io/leader: '{"holderIdentity":"623e39fb-70c3-44f1-811f-561ec4a28d75","leaseDuration":15.000000000,"acquireTime":"2020-10-20T04:06:31.431000Z","renewTime":"2020-10-22T08:51:36.843000Z","leaderTransitions":37981}' */ public class KubernetesLeaderElector { private static final Logger LOG = LoggerFactory.getLogger(KubernetesLeaderElector.class); @VisibleForTesting public static final String LEADER_ANNOTATION_KEY = "control-plane.alpha.kubernetes.io/leader"; private final ExecutorService executorService = Executors.newSingleThreadExecutor( new ExecutorThreadFactory("KubernetesLeaderElector-ExecutorService")); private final LeaderElector internalLeaderElector; public KubernetesLeaderElector( NamespacedKubernetesClient kubernetesClient, String namespace, KubernetesLeaderElectionConfiguration leaderConfig, LeaderCallbackHandler leaderCallbackHandler) { final LeaderElectionConfig leaderElectionConfig = new LeaderElectionConfigBuilder() .withName(leaderConfig.getConfigMapName()) .withLeaseDuration(leaderConfig.getLeaseDuration()) .withLock(new ConfigMapLock(namespace, leaderConfig.getConfigMapName(), leaderConfig.getLockIdentity())) .withRenewDeadline(leaderConfig.getRenewDeadline()) .withRetryPeriod(leaderConfig.getRetryPeriod()) .withLeaderCallbacks(new LeaderCallbacks( leaderCallbackHandler::isLeader, leaderCallbackHandler::notLeader, newLeader -> LOG.info("New leader elected {} for {}.", newLeader, leaderConfig.getConfigMapName()) )) .build(); internalLeaderElector = new LeaderElector<>(kubernetesClient, leaderElectionConfig); LOG.info("Create KubernetesLeaderElector {} with lock identity {}.", leaderConfig.getConfigMapName(), leaderConfig.getLockIdentity()); } public void run() { executorService.submit(internalLeaderElector::run); } public void stop() { executorService.shutdownNow(); } public static boolean hasLeadership(KubernetesConfigMap configMap, String lockIdentity) { final String leader = configMap.getAnnotations().get(LEADER_ANNOTATION_KEY); return leader != null && leader.contains(lockIdentity); } /** * Callback handler for leader election. */ public abstract static class LeaderCallbackHandler { public abstract void isLeader(); public abstract void notLeader(); } } ================================================ FILE: flink-learning-k8s/flink-k8s/src/main/java/org/apache/flink/kubernetes/kubeclient/resources/KubernetesPod.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.flink.kubernetes.kubeclient.resources; import io.fabric8.kubernetes.api.model.ContainerStateTerminated; import io.fabric8.kubernetes.api.model.Pod; import org.apache.flink.annotation.VisibleForTesting; import java.util.Objects; import java.util.stream.Collectors; /** * Represent KubernetesPod resource in kubernetes. */ public class KubernetesPod extends KubernetesResource { public KubernetesPod(Pod pod) { super(pod); } public String getName() { return this.getInternalResource().getMetadata().getName(); } public boolean isTerminated() { if (getInternalResource().getStatus() != null) { final boolean podFailed = PodPhase.Failed.name().equals(getInternalResource().getStatus().getPhase()); final boolean containersFailed = getInternalResource().getStatus().getContainerStatuses().stream() .anyMatch( e -> e.getState() != null && e.getState().getTerminated() != null); return containersFailed || podFailed; } return false; } public boolean isScheduled() { if (getInternalResource().getStatus() != null) { return getInternalResource().getStatus().getConditions().stream() .anyMatch( e -> Objects.equals(e.getType(), "PodScheduled") && Objects.equals(e.getStatus(), "True")); } return false; } public String getTerminatedDiagnostics() { final StringBuilder sb = new StringBuilder(); sb.append("Pod terminated, container termination statuses: ["); if (getInternalResource().getStatus() != null) { sb.append( getInternalResource().getStatus().getContainerStatuses() .stream() .filter(containerStatus -> containerStatus.getState() != null && containerStatus.getState().getTerminated() != null) .map((containerStatus) -> { final ContainerStateTerminated containerStateTerminated = containerStatus.getState().getTerminated(); return String.format("%s(exitCode=%d, reason=%s, message=%s)", containerStatus.getName(), containerStateTerminated.getExitCode(), containerStateTerminated.getReason(), containerStateTerminated.getMessage()); }) .collect(Collectors.joining(","))); } sb.append("]"); if (PodPhase.Failed.name().equals(getInternalResource().getStatus().getPhase())) { sb.append( String.format( ", pod status: %s(reason=%s, message=%s)", getInternalResource().getStatus().getPhase(), getInternalResource().getStatus().getReason(), getInternalResource().getStatus().getMessage())); } return sb.toString(); } /** The phase of a Pod, high-level summary of where the Pod is in its lifecycle. */ @VisibleForTesting enum PodPhase { Pending, Running, Succeeded, Failed, Unknown } } ================================================ FILE: flink-learning-k8s/flink-k8s/src/main/java/org/apache/flink/kubernetes/kubeclient/resources/KubernetesPodsWatcher.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.flink.kubernetes.kubeclient.resources; import io.fabric8.kubernetes.api.model.Pod; import org.apache.flink.kubernetes.kubeclient.FlinkKubeClient; import org.apache.flink.kubernetes.utils.KubernetesUtils; import java.util.Collections; import java.util.List; /** * Watcher for pods in Kubernetes. */ public class KubernetesPodsWatcher extends AbstractKubernetesWatcher { public KubernetesPodsWatcher(FlinkKubeClient.WatchCallbackHandler callbackHandler) { super(callbackHandler); } @Override public void eventReceived(Action action, Pod pod) { logger.debug( "Received {} event for pod {}, details: {}{}", action, pod.getMetadata().getName(), System.lineSeparator(), KubernetesUtils.tryToGetPrettyPrintYaml(pod.getStatus())); final List pods = Collections.singletonList(new KubernetesPod(pod)); switch (action) { case ADDED: callbackHandler.onAdded(pods); break; case MODIFIED: callbackHandler.onModified(pods); break; case ERROR: callbackHandler.onError(pods); break; case DELETED: callbackHandler.onDeleted(pods); break; default: logger.debug("Ignore handling {} event for pod {}", action, pod.getMetadata().getName()); break; } } } ================================================ FILE: flink-learning-k8s/flink-k8s/src/main/java/org/apache/flink/kubernetes/kubeclient/resources/KubernetesResource.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.flink.kubernetes.kubeclient.resources; /** * Represent a kubernetes resource. */ public abstract class KubernetesResource { private T internalResource; public KubernetesResource(T internalResource) { this.internalResource = internalResource; } public T getInternalResource() { return internalResource; } public void setInternalResource(T resource) { this.internalResource = resource; } } ================================================ FILE: flink-learning-k8s/flink-k8s/src/main/java/org/apache/flink/kubernetes/kubeclient/resources/KubernetesSecretEnvVar.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.flink.kubernetes.kubeclient.resources; import io.fabric8.kubernetes.api.model.EnvVar; import io.fabric8.kubernetes.api.model.EnvVarBuilder; import java.util.Map; /** * Represents EnvVar resource in Kubernetes. */ public class KubernetesSecretEnvVar extends KubernetesResource { private static final String ENV = "env"; private static final String SECRET = "secret"; private static final String KEY = "key"; private KubernetesSecretEnvVar(EnvVar envVar) { super(envVar); } public static KubernetesSecretEnvVar fromMap(Map stringMap) { final EnvVarBuilder envVarBuilder = new EnvVarBuilder() .withName(stringMap.get(ENV)) .withNewValueFrom() .withNewSecretKeyRef() .withName(stringMap.get(SECRET)) .withKey(stringMap.get(KEY)) .endSecretKeyRef() .endValueFrom(); return new KubernetesSecretEnvVar(envVarBuilder.build()); } } ================================================ FILE: flink-learning-k8s/flink-k8s/src/main/java/org/apache/flink/kubernetes/kubeclient/resources/KubernetesService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.flink.kubernetes.kubeclient.resources; import io.fabric8.kubernetes.api.model.Service; /** * Represent Service resource in kubernetes. */ public class KubernetesService extends KubernetesResource { public KubernetesService(Service internalResource) { super(internalResource); } /** The flink service type. */ public enum ServiceType { REST_SERVICE, INTERNAL_SERVICE, } } ================================================ FILE: flink-learning-k8s/flink-k8s/src/main/java/org/apache/flink/kubernetes/kubeclient/resources/KubernetesToleration.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.flink.kubernetes.kubeclient.resources; import io.fabric8.kubernetes.api.model.Toleration; import io.fabric8.kubernetes.api.model.TolerationBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Map; /** * Represent Toleration resource in kubernetes. */ public class KubernetesToleration extends KubernetesResource { private static final Logger LOG = LoggerFactory.getLogger(KubernetesToleration.class); private KubernetesToleration(Toleration toleration) { super(toleration); } public static KubernetesToleration fromMap(Map stringMap) { final TolerationBuilder tolerationBuilder = new TolerationBuilder(); stringMap.forEach((k, v) -> { switch (k.toLowerCase()) { case "effect": tolerationBuilder.withEffect(v); break; case "key": tolerationBuilder.withKey(v); break; case "operator": tolerationBuilder.withOperator(v); break; case "tolerationseconds": tolerationBuilder.withTolerationSeconds(Long.valueOf(v)); break; case "value": tolerationBuilder.withValue(v); break; default: LOG.warn("Unrecognized key({}) of toleration, will ignore.", k); break; } }); return new KubernetesToleration(tolerationBuilder.build()); } } ================================================ FILE: flink-learning-k8s/flink-k8s/src/main/java/org/apache/flink/kubernetes/kubeclient/resources/KubernetesTooOldResourceVersionException.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.flink.kubernetes.kubeclient.resources; import org.apache.flink.util.FlinkException; /** Kubernetes too old resource version exception. */ public class KubernetesTooOldResourceVersionException extends FlinkException { private static final long serialVersionUID = 1L; public KubernetesTooOldResourceVersionException(Throwable cause) { super(cause); } public KubernetesTooOldResourceVersionException(String message, Throwable cause) { super(message, cause); } } ================================================ FILE: flink-learning-k8s/flink-k8s/src/main/java/org/apache/flink/kubernetes/kubeclient/resources/KubernetesWatch.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.flink.kubernetes.kubeclient.resources; import io.fabric8.kubernetes.client.Watch; /** * Watch resource in Kubernetes. */ public class KubernetesWatch extends KubernetesResource { public KubernetesWatch(Watch watch) { super(watch); } public void close() { getInternalResource().close(); } } ================================================ FILE: flink-learning-k8s/flink-k8s/src/main/java/org/apache/flink/kubernetes/taskmanager/KubernetesTaskExecutorRunner.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.flink.kubernetes.taskmanager; import org.apache.flink.runtime.taskexecutor.TaskManagerRunner; import org.apache.flink.runtime.util.EnvironmentInformation; import org.apache.flink.runtime.util.JvmShutdownSafeguard; import org.apache.flink.runtime.util.SignalHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * This class is the executable entry point for running a TaskExecutor in a Kubernetes pod. */ public class KubernetesTaskExecutorRunner { protected static final Logger LOG = LoggerFactory.getLogger(KubernetesTaskExecutorRunner.class); public static void main(String[] args) { EnvironmentInformation.logEnvironmentInfo(LOG, "Kubernetes TaskExecutor runner", args); SignalHandler.register(LOG); JvmShutdownSafeguard.installAsShutdownHook(LOG); TaskManagerRunner.runTaskManagerSecurely(args); } } ================================================ FILE: flink-learning-k8s/flink-k8s/src/main/java/org/apache/flink/kubernetes/utils/Constants.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.flink.kubernetes.utils; /** * Constants for kubernetes. */ public class Constants { // Kubernetes api version public static final String API_VERSION = "v1"; public static final String APPS_API_VERSION = "apps/v1"; public static final String DNS_PLOICY_DEFAULT = "ClusterFirst"; public static final String DNS_PLOICY_HOSTNETWORK = "ClusterFirstWithHostNet"; public static final String CONFIG_FILE_LOGBACK_NAME = "logback-console.xml"; public static final String CONFIG_FILE_LOG4J_NAME = "log4j-console.properties"; public static final String FLINK_CONF_VOLUME = "flink-config-volume"; public static final String CONFIG_MAP_PREFIX = "flink-config-"; public static final String HIVE_CONF_VOLUME = "hive-config-volume"; public static final String HIVE_CONF_CONFIG_MAP_PREFIX = "hive-config-"; public static final String HIVE_CONF_DIR_IN_POD = "/app/hive/conf"; public static final String ENV_HIVE_CONF_DIR = "HIVE_CONF_DIR"; public static final String ENV_HIVE_HOME = "HIVE_HOME"; public static final String HADOOP_CONF_VOLUME = "hadoop-config-volume"; public static final String HADOOP_CONF_CONFIG_MAP_PREFIX = "hadoop-config-"; public static final String HADOOP_CONF_DIR_IN_POD = "/opt/hadoop/conf"; public static final String ENV_HADOOP_CONF_DIR = "HADOOP_CONF_DIR"; public static final String ENV_HADOOP_HOME = "HADOOP_HOME"; public static final String KERBEROS_KEYTAB_VOLUME = "kerberos-keytab-volume"; public static final String KERBEROS_KEYTAB_SECRET_PREFIX = "kerberos-keytab-"; public static final String KERBEROS_KEYTAB_MOUNT_POINT = "/opt/kerberos/kerberos-keytab"; public static final String KERBEROS_KRB5CONF_VOLUME = "kerberos-krb5conf-volume"; public static final String KERBEROS_KRB5CONF_CONFIG_MAP_PREFIX = "kerberos-krb5conf-"; public static final String KERBEROS_KRB5CONF_MOUNT_DIR = "/etc"; public static final String FLINK_REST_SERVICE_SUFFIX = "-rest"; public static final String NAME_SEPARATOR = "-"; // Constants for label builder public static final String LABEL_TYPE_KEY = "type"; public static final String LABEL_TYPE_NATIVE_TYPE = "flink-native-kubernetes"; public static final String LABEL_APP_KEY = "app"; public static final String LABEL_COMPONENT_KEY = "component"; public static final String LABEL_COMPONENT_JOB_MANAGER = "jobmanager"; public static final String LABEL_COMPONENT_TASK_MANAGER = "taskmanager"; public static final String LABEL_CONFIGMAP_TYPE_KEY = "configmap-type"; public static final String LABEL_CONFIGMAP_TYPE_HIGH_AVAILABILITY = "high-availability"; // Use fixed port in kubernetes, it needs to be exposed. public static final int REST_PORT = 8081; public static final int BLOB_SERVER_PORT = 6124; public static final int TASK_MANAGER_RPC_PORT = 6122; public static final String JOB_MANAGER_RPC_PORT_NAME = "jobmanager-rpc"; public static final String BLOB_SERVER_PORT_NAME = "blobserver"; public static final String REST_PORT_NAME = "rest"; public static final String TASK_MANAGER_RPC_PORT_NAME = "taskmanager-rpc"; public static final String RESOURCE_NAME_MEMORY = "memory"; public static final String RESOURCE_NAME_CPU = "cpu"; public static final String RESOURCE_UNIT_MB = "Mi"; public static final String ENV_FLINK_CLASSPATH = "FLINK_CLASSPATH"; public static final String ENV_FLINK_POD_IP_ADDRESS = "_POD_IP_ADDRESS"; public static final String ENV_FLINK_HOST_IP_ADDRESS = "_HOST_IP_ADDRESS"; public static final String POD_IP_FIELD_PATH = "status.podIP"; public static final String HOST_IP_FIELD_PATH = "status.hostIP"; public static final String HEADLESS_SERVICE_CLUSTER_IP = "None"; public static final int MAXIMUM_CHARACTERS_OF_CLUSTER_ID = 45; public static final String RESTART_POLICY_OF_NEVER = "Never"; public static final String NATIVE_KUBERNETES_COMMAND = "native-k8s"; // Constants for Kubernetes high availability public static final String LEADER_ADDRESS_KEY = "address"; public static final String LEADER_SESSION_ID_KEY = "sessionId"; public static final String JOB_GRAPH_STORE_KEY_PREFIX = "jobGraph-"; public static final String SUBMITTED_JOBGRAPH_FILE_PREFIX = "submittedJobGraph"; public static final String RUNNING_JOBS_REGISTRY_KEY_PREFIX = "runningJobsRegistry-"; public static final String CHECKPOINT_COUNTER_KEY = "counter"; public static final String CHECKPOINT_ID_KEY_PREFIX = "checkpointID-"; public static final String COMPLETED_CHECKPOINT_FILE_SUFFIX = "completedCheckpoint"; } ================================================ FILE: flink-learning-k8s/flink-k8s/src/main/java/org/apache/flink/kubernetes/utils/KubernetesUtils.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.flink.kubernetes.utils; import io.fabric8.kubernetes.api.model.KubernetesResource; import io.fabric8.kubernetes.api.model.Quantity; import io.fabric8.kubernetes.api.model.ResourceRequirements; import io.fabric8.kubernetes.api.model.ResourceRequirementsBuilder; import org.apache.flink.client.program.PackagedProgramUtils; import org.apache.flink.configuration.ConfigOption; import org.apache.flink.configuration.Configuration; import org.apache.flink.configuration.CoreOptions; import org.apache.flink.configuration.PipelineOptions; import org.apache.flink.kubernetes.configuration.KubernetesConfigOptions; import org.apache.flink.kubernetes.highavailability.KubernetesCheckpointStoreUtil; import org.apache.flink.kubernetes.highavailability.KubernetesJobGraphStoreUtil; import org.apache.flink.kubernetes.highavailability.KubernetesStateHandleStore; import org.apache.flink.kubernetes.kubeclient.FlinkKubeClient; import org.apache.flink.kubernetes.kubeclient.resources.KubernetesConfigMap; import org.apache.flink.runtime.checkpoint.CompletedCheckpoint; import org.apache.flink.runtime.checkpoint.CompletedCheckpointStore; import org.apache.flink.runtime.checkpoint.DefaultCompletedCheckpointStore; import org.apache.flink.runtime.clusterframework.BootstrapTools; import org.apache.flink.runtime.highavailability.HighAvailabilityServicesUtils; import org.apache.flink.runtime.jobgraph.JobGraph; import org.apache.flink.runtime.jobmanager.DefaultJobGraphStore; import org.apache.flink.runtime.jobmanager.JobGraphStore; import org.apache.flink.runtime.jobmanager.NoOpJobGraphStoreWatcher; import org.apache.flink.runtime.leaderelection.LeaderInformation; import org.apache.flink.runtime.persistence.RetrievableStateStorageHelper; import org.apache.flink.runtime.persistence.filesystem.FileSystemStateStorageHelper; import org.apache.flink.shaded.jackson2.com.fasterxml.jackson.dataformat.yaml.YAMLMapper; import org.apache.flink.util.FlinkRuntimeException; import org.apache.flink.util.function.FunctionUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.annotation.Nullable; import java.io.File; import java.net.URI; import java.util.*; import java.util.concurrent.Executor; import java.util.stream.Collectors; import static org.apache.flink.kubernetes.utils.Constants.*; import static org.apache.flink.util.Preconditions.checkNotNull; /** * Common utils for Kubernetes. */ public class KubernetesUtils { private static final Logger LOG = LoggerFactory.getLogger(KubernetesUtils.class); private static final YAMLMapper yamlMapper = new YAMLMapper(); /** * Check whether the port config option is a fixed port. If not, the fallback port will be set to configuration. * @param flinkConfig flink configuration * @param port config option need to be checked * @param fallbackPort the fallback port that will be set to the configuration */ public static void checkAndUpdatePortConfigOption( Configuration flinkConfig, ConfigOption port, int fallbackPort) { if (KubernetesUtils.parsePort(flinkConfig, port) == 0) { flinkConfig.setString(port, String.valueOf(fallbackPort)); LOG.info( "Kubernetes deployment requires a fixed port. Configuration {} will be set to {}", port.key(), fallbackPort); } } /** * Parse a valid port for the config option. A fixed port is expected, and do not support a range of ports. * * @param flinkConfig flink config * @param port port config option * @return valid port */ public static Integer parsePort(Configuration flinkConfig, ConfigOption port) { checkNotNull(flinkConfig.get(port), port.key() + " should not be null."); try { return Integer.parseInt(flinkConfig.get(port)); } catch (NumberFormatException ex) { throw new FlinkRuntimeException( port.key() + " should be specified to a fixed port. Do not support a range of ports.", ex); } } /** * Generate name of the Deployment. */ public static String getDeploymentName(String clusterId) { return clusterId; } /** * Get task manager labels for the current Flink cluster. They could be used to watch the pods status. * * @return Task manager labels. */ public static Map getTaskManagerLabels(String clusterId) { final Map labels = getCommonLabels(clusterId); labels.put(Constants.LABEL_COMPONENT_KEY, Constants.LABEL_COMPONENT_TASK_MANAGER); return Collections.unmodifiableMap(labels); } /** * Get the common labels for Flink native clusters. All the Kubernetes resources will be set with these labels. * * @param clusterId cluster id * * @return Return common labels map */ public static Map getCommonLabels(String clusterId) { final Map commonLabels = new HashMap<>(); commonLabels.put(Constants.LABEL_TYPE_KEY, Constants.LABEL_TYPE_NATIVE_TYPE); commonLabels.put(Constants.LABEL_APP_KEY, clusterId); return commonLabels; } /** * Get ConfigMap labels for the current Flink cluster. They could be used to filter and clean-up the resources. * * @param clusterId cluster id * @param type the config map use case. It could only be {@link Constants#LABEL_CONFIGMAP_TYPE_HIGH_AVAILABILITY} * now. * * @return Return ConfigMap labels. */ public static Map getConfigMapLabels(String clusterId, String type) { final Map labels = new HashMap<>(getCommonLabels(clusterId)); labels.put(Constants.LABEL_CONFIGMAP_TYPE_KEY, type); return Collections.unmodifiableMap(labels); } /** * Check the ConfigMap list should only contain the expected one. * * @param configMaps ConfigMap list to check * @param expectedConfigMapName expected ConfigMap Name * * @return Return the expected ConfigMap */ public static KubernetesConfigMap checkConfigMaps( List configMaps, String expectedConfigMapName) { assert(configMaps.size() == 1); assert(configMaps.get(0).getName().equals(expectedConfigMapName)); return configMaps.get(0); } /** * Get the {@link LeaderInformation} from ConfigMap. * @param configMap ConfigMap contains the leader information * @return Parsed leader information. It could be {@link LeaderInformation#empty()} if there is no corresponding * data in the ConfigMap. */ public static LeaderInformation getLeaderInformationFromConfigMap(KubernetesConfigMap configMap) { final String leaderAddress = configMap.getData().get(LEADER_ADDRESS_KEY); final String sessionIDStr = configMap.getData().get(LEADER_SESSION_ID_KEY); final UUID sessionID = sessionIDStr == null ? null : UUID.fromString(sessionIDStr); if (leaderAddress == null && sessionIDStr == null) { return LeaderInformation.empty(); } return LeaderInformation.known(sessionID, leaderAddress); } /** * Create a {@link DefaultJobGraphStore} with {@link NoOpJobGraphStoreWatcher}. * * @param configuration configuration to build a RetrievableStateStorageHelper * @param flinkKubeClient flink kubernetes client * @param configMapName ConfigMap name * @param lockIdentity lock identity to check the leadership * * @return a {@link DefaultJobGraphStore} with {@link NoOpJobGraphStoreWatcher} * @throws Exception when create the storage helper */ public static JobGraphStore createJobGraphStore( Configuration configuration, FlinkKubeClient flinkKubeClient, String configMapName, String lockIdentity) throws Exception { final KubernetesStateHandleStore stateHandleStore = createJobGraphStateHandleStore( configuration, flinkKubeClient, configMapName, lockIdentity); return new DefaultJobGraphStore<>( stateHandleStore, NoOpJobGraphStoreWatcher.INSTANCE, KubernetesJobGraphStoreUtil.INSTANCE); } /** * Create a {@link KubernetesStateHandleStore} which storing {@link JobGraph}. * * @param configuration configuration to build a RetrievableStateStorageHelper * @param flinkKubeClient flink kubernetes client * @param configMapName ConfigMap name * @param lockIdentity lock identity to check the leadership * * @return a {@link KubernetesStateHandleStore} which storing {@link JobGraph}. * * @throws Exception when create the storage helper */ public static KubernetesStateHandleStore createJobGraphStateHandleStore( Configuration configuration, FlinkKubeClient flinkKubeClient, String configMapName, String lockIdentity) throws Exception { final RetrievableStateStorageHelper stateStorage = new FileSystemStateStorageHelper<>(HighAvailabilityServicesUtils .getClusterHighAvailableStoragePath(configuration), SUBMITTED_JOBGRAPH_FILE_PREFIX); return new KubernetesStateHandleStore<>( flinkKubeClient, configMapName, stateStorage, k -> k.startsWith(JOB_GRAPH_STORE_KEY_PREFIX), lockIdentity); } /** * Create a {@link DefaultCompletedCheckpointStore} with {@link KubernetesStateHandleStore}. * * @param configuration configuration to build a RetrievableStateStorageHelper * @param kubeClient flink kubernetes client * @param configMapName ConfigMap name * @param executor executor to run blocking calls * @param lockIdentity lock identity to check the leadership * @param maxNumberOfCheckpointsToRetain max number of checkpoints to retain on state store handle * * @return a {@link DefaultCompletedCheckpointStore} with {@link KubernetesStateHandleStore}. * * @throws Exception when create the storage helper failed */ public static CompletedCheckpointStore createCompletedCheckpointStore( Configuration configuration, FlinkKubeClient kubeClient, Executor executor, String configMapName, String lockIdentity, int maxNumberOfCheckpointsToRetain) throws Exception { final RetrievableStateStorageHelper stateStorage = new FileSystemStateStorageHelper<>(HighAvailabilityServicesUtils .getClusterHighAvailableStoragePath(configuration), COMPLETED_CHECKPOINT_FILE_SUFFIX); final KubernetesStateHandleStore stateHandleStore = new KubernetesStateHandleStore<>( kubeClient, configMapName, stateStorage, k -> k.startsWith(CHECKPOINT_ID_KEY_PREFIX), lockIdentity); return new DefaultCompletedCheckpointStore<>( maxNumberOfCheckpointsToRetain, stateHandleStore, KubernetesCheckpointStoreUtil.INSTANCE, executor); } /** * Get resource requirements from memory and cpu. * * @param mem Memory in mb. * @param memoryRequestFactor Memory request factor. * @param cpu cpu. * @param cpuRequestFactor cpu request factor. * @param externalResources external resources * @return KubernetesResource requirements. */ public static ResourceRequirements getResourceRequirements( int mem, double memoryRequestFactor, double cpu, double cpuRequestFactor, Map externalResources) { final Quantity cpuQuantity = new Quantity(String.valueOf(cpu)); final Quantity cpuRequestQuantity = new Quantity(String.valueOf(cpu * cpuRequestFactor)); final Quantity memQuantity = new Quantity(mem + Constants.RESOURCE_UNIT_MB); final Quantity memRequestQuantity = new Quantity(((int) (mem * memoryRequestFactor)) + Constants.RESOURCE_UNIT_MB); ResourceRequirementsBuilder resourceRequirementsBuilder = new ResourceRequirementsBuilder() .addToRequests(Constants.RESOURCE_NAME_MEMORY, memRequestQuantity) .addToRequests(Constants.RESOURCE_NAME_CPU, cpuRequestQuantity) .addToLimits(Constants.RESOURCE_NAME_MEMORY, memQuantity) .addToLimits(Constants.RESOURCE_NAME_CPU, cpuQuantity); // Add the external resources to resource requirement. for (Map.Entry externalResource: externalResources.entrySet()) { final Quantity resourceQuantity = new Quantity(String.valueOf(externalResource.getValue())); resourceRequirementsBuilder .addToRequests(externalResource.getKey(), resourceQuantity) .addToLimits(externalResource.getKey(), resourceQuantity); LOG.info("Request external resource {} with config key {}.", resourceQuantity.getAmount(), externalResource.getKey()); } return resourceRequirementsBuilder.build(); } public static String getCommonStartCommand( Configuration flinkConfig, ClusterComponent mode, String jvmMemOpts, String configDirectory, String logDirectory, boolean hasLogback, boolean hasLog4j, String mainClass, @Nullable String mainArgs) { final Map startCommandValues = new HashMap<>(); startCommandValues.put("java", "$JAVA_HOME/bin/java"); startCommandValues.put("classpath", "-classpath " + "$" + Constants.ENV_FLINK_CLASSPATH); startCommandValues.put("jvmmem", jvmMemOpts); final String opts; final String fileName; if (mode == ClusterComponent.JOB_MANAGER) { opts = getJavaOpts(flinkConfig, CoreOptions.FLINK_JM_JVM_OPTIONS); fileName = "jobmanager"; } else { opts = getJavaOpts(flinkConfig, CoreOptions.FLINK_TM_JVM_OPTIONS); fileName = "taskmanager"; } startCommandValues.put("jvmopts", opts); startCommandValues.put("logging", getLogging(logDirectory + "/" + fileName, configDirectory, hasLogback, hasLog4j)); startCommandValues.put("class", mainClass); startCommandValues.put("args", mainArgs != null ? mainArgs : ""); final String commandTemplate = flinkConfig.getString(KubernetesConfigOptions.CONTAINER_START_COMMAND_TEMPLATE); return BootstrapTools.getStartCommand(commandTemplate, startCommandValues); } public static List checkJarFileForApplicationMode(Configuration configuration) { return configuration.get(PipelineOptions.JARS).stream().map( FunctionUtils.uncheckedFunction( uri -> { final URI jarURI = PackagedProgramUtils.resolveURI(uri); if (jarURI.getScheme().equals("local") && jarURI.isAbsolute()) { return new File(jarURI.getPath()); } throw new IllegalArgumentException("Only \"local\" is supported as schema for application mode." + " This assumes that the jar is located in the image, not the Flink client." + " An example of such path is: local:///opt/flink/examples/streaming/WindowJoin.jar"); }) ).collect(Collectors.toList()); } private static String getJavaOpts(Configuration flinkConfig, ConfigOption configOption) { String baseJavaOpts = flinkConfig.getString(CoreOptions.FLINK_JVM_OPTIONS); if (flinkConfig.getString(configOption).length() > 0) { return baseJavaOpts + " " + flinkConfig.getString(configOption); } else { return baseJavaOpts; } } private static String getLogging(String fileName, String confDir, boolean hasLogback, boolean hasLog4j) { StringBuilder logging = new StringBuilder(); if (hasLogback || hasLog4j) { logging.append("-Dout.file=").append(fileName).append(".out"); logging.append(" -Dlog.file=").append(fileName).append(".log"); logging.append(" -Derr.file=").append(fileName).append(".err"); if (hasLogback) { logging.append(" -Dlogback.configurationFile=file:").append(confDir).append("/").append(CONFIG_FILE_LOGBACK_NAME); } if (hasLog4j) { logging.append(" -Dlog4j.configuration=file:").append(confDir).append("/").append(CONFIG_FILE_LOG4J_NAME) .append(" -Dlog4j.configurationFile=file:").append(confDir).append("/").append(CONFIG_FILE_LOG4J_NAME); } } return logging.toString(); } /** * Try to get the pretty print yaml for Kubernetes resource. * * @param kubernetesResource kubernetes resource * @return the pretty print yaml, or the {@link KubernetesResource#toString()} if parse failed. */ public static String tryToGetPrettyPrintYaml(KubernetesResource kubernetesResource) { try { return yamlMapper .writerWithDefaultPrettyPrinter() .writeValueAsString(kubernetesResource); } catch (Exception ex) { LOG.debug( "Failed to get the pretty print yaml, fallback to {}", kubernetesResource, ex); return kubernetesResource.toString(); } } /** Checks if hostNetwork is enabled. */ public static boolean isHostNetwork(Configuration configuration) { return configuration.getBoolean(KubernetesConfigOptions.KUBERNETES_HOSTNETWORK_ENABLED); } /** * Parse the port from the webInterfaceUrl. * * @param webInterfaceUrl The web interface url to be parsed * @return the parsed rest port or -1 if failed */ public static Integer parseRestBindPortFromWebInterfaceUrl(String webInterfaceUrl) { if (webInterfaceUrl != null) { final int lastColon = webInterfaceUrl.lastIndexOf(':'); if (lastColon == -1) { return -1; } else { try { return Integer.parseInt(webInterfaceUrl.substring(lastColon + 1)); } catch (NumberFormatException e) { return -1; } } } else { return -1; } } /** * Cluster components. */ public enum ClusterComponent { JOB_MANAGER, TASK_MANAGER } private KubernetesUtils() {} } ================================================ FILE: flink-learning-k8s/flink-k8s/src/main/resources/META-INF/NOTICE ================================================ flink-kubernetes Copyright 2014-2020 The Apache Software Foundation This project includes software developed at The Apache Software Foundation (http://www.apache.org/). This project bundles the following dependencies under the Apache Software License 2.0 (http://www.apache.org/licenses/LICENSE-2.0.txt) - com.fasterxml.jackson.core:jackson-annotations:2.10.1 - com.fasterxml.jackson.core:jackson-core:2.10.1 - com.fasterxml.jackson.core:jackson-databind:2.10.1 - com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.10.1 - com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.10.1 - com.squareup.okhttp3:logging-interceptor:3.12.6 - com.squareup.okhttp3:okhttp:3.12.1 - com.squareup.okio:okio:1.15.0 - io.fabric8:kubernetes-client:4.9.2 - io.fabric8:kubernetes-model:4.9.2 - io.fabric8:kubernetes-model-common:4.9.2 - io.fabric8:zjsonpatch:0.3.0 - org.yaml:snakeyaml:1.27 This project bundles the following dependencies under the BSD License. See bundled license files for details. - dk.brics.automaton:automaton:1.11-8 ================================================ FILE: flink-learning-k8s/flink-k8s/src/main/resources/META-INF/services/org.apache.flink.client.deployment.ClusterClientFactory ================================================ # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. org.apache.flink.kubernetes.KubernetesClusterClientFactory ================================================ FILE: flink-learning-k8s/flink-k8s/src/main/resources/META-INF/services/org.apache.flink.core.execution.PipelineExecutorFactory ================================================ # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. org.apache.flink.kubernetes.executors.KubernetesSessionClusterExecutorFactory ================================================ FILE: flink-learning-k8s/pom.xml ================================================ flink-learning com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 flink-learning-k8s pom flink-k8s ================================================ FILE: flink-learning-monitor/README.md ================================================ ## Flink 监控、告警、存储、可视化展示 1、[监控数据采集](./flink-learning-monitor-collector) 2、[告警](./flink-learning-monitor-alert) 3、[日志处理](./flink-learning-monitor-log) 4、[监控数据存储](./flink-learning-monitor-storage) 5、[PV/UV](./flink-learning-monitor-pvuv) 6、[监控数据可视化展示](./flink-learning-monitor-dashboard) ================================================ FILE: flink-learning-monitor/flink-learning-monitor-alert/README.md ================================================ ## Flink 监控告警 ### Flink JobManager ### Flink TaskManager ### Flink Jobs ## 通知方式 ### 钉钉 + text 格式消息 + markdown 格式消息 + link 格式消息 + 同一个钉钉地址发送多条消息 + 同一条消息发送多个群 + 钉钉工作通知 工具类 [DingDingGroupMsgUtil.java](./src/main/java/com/zhisheng/alert/utils/DingDingGroupMsgUtil.java) 如何使用,请参考 [DingDingMsgTest.java](./src/test/java/DingDingMsgTest.java) ### 邮件 ### 短信 ### 电话 ================================================ FILE: flink-learning-monitor/flink-learning-monitor-alert/pom.xml ================================================ flink-learning-monitor com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 flink-learning-monitor-alert com.zhisheng.flink flink-learning-monitor-common ${project.version} mysql mysql-connector-java 5.1.34 org.apache.maven.plugins maven-shade-plugin 3.1.0 false package shade com.zhisheng.alert.alert.LogEventAlert reference.conf *:*:*:* META-INF/*.SF META-INF/*.DSA META-INF/*.RSA ================================================ FILE: flink-learning-monitor/flink-learning-monitor-alert/src/main/java/com/zhisheng/alert/alert/AsyncIOAlert.java ================================================ package com.zhisheng.alert.alert; import com.zhisheng.alert.function.AlertRuleAsyncIOFunction; import com.zhisheng.alert.model.AlertEvent; import com.zhisheng.common.model.MetricEvent; import com.zhisheng.common.schemas.MetricSchema; import com.zhisheng.common.utils.ExecutionEnvUtil; import com.zhisheng.common.utils.KafkaConfigUtil; import com.zhisheng.common.watermarks.MetricWatermark; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.streaming.api.datastream.AsyncDataStream; import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumer; import java.util.List; import java.util.Properties; import java.util.concurrent.TimeUnit; /** * Desc: 使用 AsynIO 读取告警规则并判断监控数据是否告警 * Created by zhisheng on 2019/10/16 下午5:10 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class AsyncIOAlert { @SuppressWarnings("unchecked") public static void main(String[] args) throws Exception { final ParameterTool parameterTool = ExecutionEnvUtil.createParameterTool(args); StreamExecutionEnvironment env = ExecutionEnvUtil.prepare(parameterTool); Properties properties = KafkaConfigUtil.buildKafkaProps(parameterTool); FlinkKafkaConsumer consumer = new FlinkKafkaConsumer<>( parameterTool.get("metrics.topic"), new MetricSchema(), properties); SingleOutputStreamOperator machineData = env.addSource(consumer) .assignTimestampsAndWatermarks(new MetricWatermark()); AsyncDataStream.unorderedWait(machineData, new AlertRuleAsyncIOFunction(), 10000, TimeUnit.MICROSECONDS, 100) .map(metricEvent -> { List ma = (List) metricEvent.getFields().get("xx"); AlertEvent alertEvent = new AlertEvent(); alertEvent.setType(metricEvent.getName()); alertEvent.setTrigerTime(metricEvent.getTimestamp()); alertEvent.setMetricEvent(metricEvent); if (metricEvent.getTags().get("recover") != null && Boolean.parseBoolean(metricEvent.getTags().get("recover"))) { alertEvent.setRecover(true); alertEvent.setRecoverTime(metricEvent.getTimestamp()); } else { alertEvent.setRecover(false); } return alertEvent; }) .print(); env.execute("Async IO get MySQL data"); } } ================================================ FILE: flink-learning-monitor/flink-learning-monitor-alert/src/main/java/com/zhisheng/alert/alert/BroadcastUpdateAlertRule.java ================================================ package com.zhisheng.alert.alert; import com.zhisheng.alert.function.GetAlertRuleSourceFunction; import com.zhisheng.alert.model.AlertRule; import com.zhisheng.common.model.MetricEvent; import com.zhisheng.common.schemas.MetricSchema; import com.zhisheng.common.utils.ExecutionEnvUtil; import com.zhisheng.common.utils.KafkaConfigUtil; import com.zhisheng.common.watermarks.MetricWatermark; import lombok.extern.slf4j.Slf4j; import org.apache.flink.api.common.state.BroadcastState; import org.apache.flink.api.common.state.MapStateDescriptor; import org.apache.flink.api.common.state.ReadOnlyBroadcastState; import org.apache.flink.api.common.typeinfo.BasicTypeInfo; import org.apache.flink.api.common.typeinfo.TypeInformation; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.streaming.api.datastream.DataStreamSource; import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.api.functions.co.BroadcastProcessFunction; import org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumer; import org.apache.flink.util.Collector; import java.util.List; import java.util.Properties; /** * Desc: 利用广播变量动态更新告警规则中的数据 * Created by zhisheng on 2019/10/17 下午4:28 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ @Slf4j public class BroadcastUpdateAlertRule { private final static MapStateDescriptor ALERT_RULE = new MapStateDescriptor<>( "alert_rule", BasicTypeInfo.STRING_TYPE_INFO, TypeInformation.of(AlertRule.class)); public static void main(String[] args) throws Exception { final ParameterTool parameterTool = ExecutionEnvUtil.createParameterTool(args); StreamExecutionEnvironment env = ExecutionEnvUtil.prepare(parameterTool); Properties properties = KafkaConfigUtil.buildKafkaProps(parameterTool); FlinkKafkaConsumer consumer = new FlinkKafkaConsumer<>( parameterTool.get("metrics.topic"), new MetricSchema(), properties); SingleOutputStreamOperator machineData = env.addSource(consumer) .assignTimestampsAndWatermarks(new MetricWatermark()); DataStreamSource> alarmDataStream = env.addSource(new GetAlertRuleSourceFunction()).setParallelism(1);//定时从数据库中查出告警规则数据 machineData.connect(alarmDataStream.broadcast(ALERT_RULE)) .process(new BroadcastProcessFunction, MetricEvent>() { @Override public void processElement(MetricEvent value, ReadOnlyContext ctx, Collector out) throws Exception { ReadOnlyBroadcastState broadcastState = ctx.getBroadcastState(ALERT_RULE); if (broadcastState.contains(value.getName())) { AlertRule alertRule = broadcastState.get(value.getName()); double used = (double) value.getFields().get(alertRule.getMeasurement()); if (used > Double.parseDouble(alertRule.getThresholds())) { log.info("AlertRule = {}, MetricEvent = {}", alertRule, value); out.collect(value); } } } @Override public void processBroadcastElement(List value, Context ctx, Collector out) throws Exception { if (value == null || value.size() == 0) { return; } BroadcastState alertRuleBroadcastState = ctx.getBroadcastState(ALERT_RULE); for (AlertRule aValue : value) { alertRuleBroadcastState.put(aValue.getName(), aValue); } } }).print(); env.execute(); } } ================================================ FILE: flink-learning-monitor/flink-learning-monitor-alert/src/main/java/com/zhisheng/alert/alert/LogEventAlert.java ================================================ package com.zhisheng.alert.alert; import com.zhisheng.common.model.LogEvent; import com.zhisheng.common.schemas.LogSchema; import com.zhisheng.common.utils.ExecutionEnvUtil; import com.zhisheng.common.utils.KafkaConfigUtil; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumer; import java.util.Properties; /** * Desc: 异常日志告警 * Created by zhisheng on 2019/10/13 下午12:27 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class LogEventAlert { public static void main(String[] args) throws Exception { final ParameterTool parameterTool = ExecutionEnvUtil.createParameterTool(args); StreamExecutionEnvironment env = ExecutionEnvUtil.prepare(parameterTool); Properties properties = KafkaConfigUtil.buildKafkaProps(parameterTool); FlinkKafkaConsumer consumer = new FlinkKafkaConsumer<>( parameterTool.get("log.topic"), new LogSchema(), properties); env.addSource(consumer) .filter(logEvent -> "error".equals(logEvent.getLevel())) .print(); env.execute("log event alert"); } } ================================================ FILE: flink-learning-monitor/flink-learning-monitor-alert/src/main/java/com/zhisheng/alert/alert/OutageAlert.java ================================================ package com.zhisheng.alert.alert; import com.google.common.collect.Maps; import com.zhisheng.alert.function.OutageProcessFunction; import com.zhisheng.alert.model.AlertEvent; import com.zhisheng.alert.model.OutageMetricEvent; import com.zhisheng.alert.watermark.OutageMetricWaterMark; import com.zhisheng.common.model.MetricEvent; import com.zhisheng.common.schemas.MetricSchema; import com.zhisheng.common.utils.ExecutionEnvUtil; import com.zhisheng.common.utils.KafkaConfigUtil; import com.zhisheng.common.watermarks.MetricWatermark; import org.apache.flink.api.common.functions.FlatMapFunction; import org.apache.flink.api.common.functions.MapFunction; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumer; import org.apache.flink.util.Collector; import java.util.HashMap; import java.util.Map; import java.util.Properties; import static com.zhisheng.common.constant.MachineConstant.*; /** * Desc: machine outage alert * Created by zhisheng on 2019/10/15 上午12:03 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class OutageAlert { public static void main(String[] args) throws Exception { final ParameterTool parameterTool = ExecutionEnvUtil.createParameterTool(args); StreamExecutionEnvironment env = ExecutionEnvUtil.prepare(parameterTool); Properties properties = KafkaConfigUtil.buildKafkaProps(parameterTool); FlinkKafkaConsumer consumer = new FlinkKafkaConsumer<>( parameterTool.get("metrics.topic"), new MetricSchema(), properties); env.addSource(consumer) .assignTimestampsAndWatermarks(new MetricWatermark()) .flatMap(new FlatMapFunction() { @Override public void flatMap(MetricEvent metricEvent, Collector collector) throws Exception { Map tags = metricEvent.getTags(); if (tags.containsKey(CLUSTER_NAME) && tags.containsKey(HOST_IP)) { OutageMetricEvent outageMetricEvent = OutageMetricEvent.buildFromEvent(metricEvent); if (outageMetricEvent != null) { collector.collect(outageMetricEvent); } } } }) .assignTimestampsAndWatermarks(new OutageMetricWaterMark()) .keyBy(outageMetricEvent -> outageMetricEvent.getKey()) .process(new OutageProcessFunction(1000 * 10, 60)) // .assignTimestampsAndWatermarks(new OutageMetricWaterMark()) .map(new MapFunction() { @Override public AlertEvent map(OutageMetricEvent value) throws Exception { AlertEvent alertEvent = new AlertEvent(); alertEvent.setType("outage"); alertEvent.setRecover(value.getRecover()); alertEvent.setTrigerTime(value.getTimestamp()); if (value.getRecover()) { alertEvent.setRecoverTime(value.getRecoverTime()); } MetricEvent metricEvent = new MetricEvent(); metricEvent.setTimestamp(value.getTimestamp()); metricEvent.setName("outage"); HashMap fields = Maps.newHashMap(); if (value.getMemUsedPercent() != null) { fields.put(MEM + "_" + USED_PERCENT, value.getMemUsedPercent()); } if (value.getLoad5() != null) { fields.put(LOAD5, value.getLoad5()); } if (value.getSwapUsedPercent() != null) { fields.put(SWAP + "_" + USED_PERCENT, value.getSwapUsedPercent()); } if (value.getCpuUsePercent() != null) { fields.put(CPU + "_" + USED_PERCENT, value.getCpuUsePercent()); } metricEvent.setFields(fields); HashMap tags = Maps.newHashMap(); tags.put(CLUSTER_NAME, value.getClusterName()); tags.put(HOST_IP, value.getHostIp()); metricEvent.setTags(tags); alertEvent.setMetricEvent(metricEvent); return alertEvent; } }) .print(); env.execute("machine outage alert"); } } ================================================ FILE: flink-learning-monitor/flink-learning-monitor-alert/src/main/java/com/zhisheng/alert/function/AlertRuleAsyncIOFunction.java ================================================ package com.zhisheng.alert.function; import com.zhisheng.common.model.MetricEvent; import lombok.extern.slf4j.Slf4j; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.configuration.Configuration; import org.apache.flink.streaming.api.functions.async.ResultFuture; import org.apache.flink.streaming.api.functions.async.RichAsyncFunction; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; /** * Desc: * Created by zhisheng on 2019/10/16 下午5:24 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ @Slf4j public class AlertRuleAsyncIOFunction extends RichAsyncFunction { PreparedStatement ps; private Connection connection; private ParameterTool parameterTool; @Override public void open(Configuration parameters) throws Exception { parameterTool = (ParameterTool) getRuntimeContext().getExecutionConfig().getGlobalJobParameters(); connection = getConnection(); String sql = "select * from alert_rule where name = ?;"; if (connection != null) { ps = this.connection.prepareStatement(sql); } } @Override public void timeout(MetricEvent metricEvent, ResultFuture resultFuture) throws Exception { log.info("=================timeout======{} ", metricEvent); } @Override public void asyncInvoke(MetricEvent metricEvent, ResultFuture resultFuture) throws Exception { ps.setString(1, metricEvent.getName()); ResultSet resultSet = ps.executeQuery(); Map fields = metricEvent.getFields(); if (resultSet.next()) { String thresholds = resultSet.getString("thresholds"); String measurement = resultSet.getString("measurement"); if (fields.get(measurement) != null && (double) fields.get(measurement) > Double.valueOf(thresholds)) { List list = new ArrayList<>(); list.add(metricEvent); resultFuture.complete(Collections.singletonList(metricEvent)); } } } private static Connection getConnection() { Connection con = null; try { Class.forName("com.mysql.jdbc.Driver"); //注意,替换成自己本地的 mysql 数据库地址和用户名、密码 con = DriverManager.getConnection("jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8", "root", "root123456"); } catch (Exception e) { log.error("-----------mysql get connection has exception , msg = {}", e.getMessage()); } return con; } } ================================================ FILE: flink-learning-monitor/flink-learning-monitor-alert/src/main/java/com/zhisheng/alert/function/GetAlertRuleSourceFunction.java ================================================ package com.zhisheng.alert.function; import com.zhisheng.alert.model.AlertRule; import lombok.extern.slf4j.Slf4j; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.configuration.Configuration; import org.apache.flink.streaming.api.functions.source.RichSourceFunction; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.util.ArrayList; import java.util.List; /** * Desc: * Created by zhisheng on 2019/10/17 下午4:47 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ @Slf4j public class GetAlertRuleSourceFunction extends RichSourceFunction> { private PreparedStatement ps; private Connection connection; private volatile boolean isRunning = true; private ParameterTool parameterTool; @Override public void open(Configuration parameters) throws Exception { parameterTool = (ParameterTool) getRuntimeContext().getExecutionConfig().getGlobalJobParameters(); connection = getConnection(); String sql = "select * from alert_rule;"; if (connection != null) { ps = this.connection.prepareStatement(sql); } } @Override public void run(SourceContext> ctx) throws Exception { List list = new ArrayList<>(); while (isRunning) { ResultSet resultSet = ps.executeQuery(); while (resultSet.next()) { AlertRule alertRule = new AlertRule().builder() .id(resultSet.getInt("id")) .name(resultSet.getString("name")) .measurement(resultSet.getString("measurement")) .thresholds(resultSet.getString("thresholds")) .build(); list.add(alertRule); } log.info("=======select alarm notify from mysql, size = {}, map = {}", list.size(), list); ctx.collect(list); list.clear(); Thread.sleep(1000 * 60); } } @Override public void cancel() { try { super.close(); if (connection != null) { connection.close(); } if (ps != null) { ps.close(); } } catch (Exception e) { log.error("runException:{}", e); } isRunning = false; } private static Connection getConnection() { Connection con = null; try { Class.forName("com.mysql.jdbc.Driver"); //注意,替换成自己本地的 mysql 数据库地址和用户名、密码 con = DriverManager.getConnection("jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8", "root", "root123456"); } catch (Exception e) { log.error("-----------mysql get connection has exception , msg = {}", e.getMessage()); } return con; } } ================================================ FILE: flink-learning-monitor/flink-learning-monitor-alert/src/main/java/com/zhisheng/alert/function/OutageProcessFunction.java ================================================ package com.zhisheng.alert.function; import com.zhisheng.alert.model.OutageMetricEvent; import lombok.extern.slf4j.Slf4j; import org.apache.flink.api.common.state.ValueState; import org.apache.flink.api.common.state.ValueStateDescriptor; import org.apache.flink.api.common.typeinfo.TypeHint; import org.apache.flink.api.common.typeinfo.TypeInformation; import org.apache.flink.configuration.Configuration; import org.apache.flink.streaming.api.functions.KeyedProcessFunction; import org.apache.flink.util.Collector; /** * Desc: * Created by zhisheng on 2019/10/15 上午12:07 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ @Slf4j public class OutageProcessFunction extends KeyedProcessFunction { private ValueState outageMetricState; private ValueState recover; private int delay; private int alertCountLimit; public OutageProcessFunction(int delay, int alertCountLimit) { this.delay = delay; this.alertCountLimit = alertCountLimit; } @Override public void open(Configuration parameters) throws Exception { TypeInformation outageInfo = TypeInformation.of(new TypeHint() { }); TypeInformation recoverInfo = TypeInformation.of(new TypeHint() { }); outageMetricState = getRuntimeContext().getState(new ValueStateDescriptor<>("outage_zhisheng", outageInfo)); recover = getRuntimeContext().getState(new ValueStateDescriptor<>("recover_zhisheng", recoverInfo)); } @Override public void processElement(OutageMetricEvent outageMetricEvent, Context ctx, Collector collector) throws Exception { OutageMetricEvent current = outageMetricState.value(); if (current == null) { current = new OutageMetricEvent(outageMetricEvent.getClusterName(), outageMetricEvent.getHostIp(), outageMetricEvent.getTimestamp(), outageMetricEvent.getRecover(), System.currentTimeMillis()); } else { if (outageMetricEvent.getLoad5() != null) { current.setLoad5(outageMetricEvent.getLoad5()); } if (outageMetricEvent.getCpuUsePercent() != null) { current.setCpuUsePercent(outageMetricEvent.getCpuUsePercent()); } if (outageMetricEvent.getMemUsedPercent() != null) { current.setMemUsedPercent(outageMetricEvent.getMemUsedPercent()); } if (outageMetricEvent.getSwapUsedPercent() != null) { current.setSwapUsedPercent(outageMetricEvent.getSwapUsedPercent()); } current.setSystemTimestamp(System.currentTimeMillis()); } if (recover.value() != null && !recover.value() && outageMetricEvent.getTimestamp() > current.getTimestamp()) { OutageMetricEvent recoverEvent = new OutageMetricEvent(outageMetricEvent.getClusterName(), outageMetricEvent.getHostIp(), current.getTimestamp(), true, System.currentTimeMillis()); recoverEvent.setRecoverTime(ctx.timestamp()); log.info("触发宕机恢复事件:{}", recoverEvent); collector.collect(recoverEvent); current.setCounter(0); outageMetricState.update(current); recover.update(true); } current.setTimestamp(outageMetricEvent.getTimestamp()); outageMetricState.update(current); ctx.timerService().registerEventTimeTimer(current.getSystemTimestamp() + delay); } @Override public void onTimer(long timestamp, OnTimerContext ctx, Collector out) throws Exception { OutageMetricEvent result = outageMetricState.value(); if (result != null && timestamp >= result.getSystemTimestamp() + delay && System.currentTimeMillis() - result.getTimestamp() >= delay) { if (result.getCounter() > alertCountLimit) { log.info("宕机告警次数大于:{} :{}", alertCountLimit, result); return; } log.info("触发宕机告警事件:timestamp = {}, result = {}", System.currentTimeMillis(), result); result.setRecover(false); out.collect(result); ctx.timerService().registerEventTimeTimer(timestamp + delay); result.setCounter(result.getCounter() + 1); result.setSystemTimestamp(timestamp); outageMetricState.update(result); recover.update(false); } } } ================================================ FILE: flink-learning-monitor/flink-learning-monitor-alert/src/main/java/com/zhisheng/alert/model/AlertEvent.java ================================================ package com.zhisheng.alert.model; import com.zhisheng.common.model.MetricEvent; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; /** * Desc: alert event * Created by zhisheng on 2019/10/13 上午10:14 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ @Data @NoArgsConstructor @AllArgsConstructor @Builder public class AlertEvent { private String type; private MetricEvent metricEvent; private boolean recover; private Long trigerTime; private Long recoverTime; } ================================================ FILE: flink-learning-monitor/flink-learning-monitor-alert/src/main/java/com/zhisheng/alert/model/AlertRule.java ================================================ package com.zhisheng.alert.model; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; /** * Desc: alert rule * Created by zhisheng on 2019/10/16 下午5:07 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ @Data @AllArgsConstructor @NoArgsConstructor @Builder public class AlertRule { private Integer id; private String name; private String measurement; private String thresholds; } ================================================ FILE: flink-learning-monitor/flink-learning-monitor-alert/src/main/java/com/zhisheng/alert/model/AtMobiles.java ================================================ package com.zhisheng.alert.model; import lombok.Data; import java.util.List; /** * @ * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ @Data public class AtMobiles { /** * 被@人的手机号 * * @return */ public List atMobiles; /** * @所有人时:true,否则为:false */ public Boolean isAtAll; } ================================================ FILE: flink-learning-monitor/flink-learning-monitor-alert/src/main/java/com/zhisheng/alert/model/BaseMessage.java ================================================ package com.zhisheng.alert.model; import java.io.Serializable; /** * 请求消息的抽象类 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public abstract class BaseMessage implements Serializable { public BaseMessage() { init(); } /** * 消息类型 */ protected MessageType msgtype; public MessageType getMsgtype() { return msgtype; } /** * 初始化 MessageType 方法 */ protected abstract void init(); } ================================================ FILE: flink-learning-monitor/flink-learning-monitor-alert/src/main/java/com/zhisheng/alert/model/Email.java ================================================ package com.zhisheng.alert.model; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.io.File; import java.util.Map; import java.util.Set; @Data @NoArgsConstructor @AllArgsConstructor public class Email { /** * 收件人 */ private Set to; /** * 邮件主题 */ private String subject; /** * 邮件正文 */ private String content; /** * 正文是否是 HTML */ private boolean isHtml; /** * 附件路径 */ private Map attachments; /** * 是否有附件 */ private boolean isAttachment; } ================================================ FILE: flink-learning-monitor/flink-learning-monitor-alert/src/main/java/com/zhisheng/alert/model/LinkMessage.java ================================================ package com.zhisheng.alert.model; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; /** * 链接类型钉钉消息 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ @Data @AllArgsConstructor @NoArgsConstructor public class LinkMessage extends BaseMessage { public Link link; @Override protected void init() { this.msgtype = MessageType.link; } @Data public static class Link { /** * 消息简介 */ private String text; /** * 消息标题 */ private String title; /** * 封面图片URL */ private String picUrl; /** * 消息跳转URL */ private String messageUrl; } } ================================================ FILE: flink-learning-monitor/flink-learning-monitor-alert/src/main/java/com/zhisheng/alert/model/MarkDownMessage.java ================================================ package com.zhisheng.alert.model; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; /** * markdown 类型钉钉消息 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ @Data @AllArgsConstructor @NoArgsConstructor public class MarkDownMessage extends BaseMessage { public MarkDownContent markdown; public AtMobiles at; @Override protected void init() { this.msgtype = MessageType.markdown; } @Data public static class MarkDownContent { /** * 首屏会话透出的展示内容 */ private String title; /** * markdown格式的消息 */ private String text; } } ================================================ FILE: flink-learning-monitor/flink-learning-monitor-alert/src/main/java/com/zhisheng/alert/model/MessageType.java ================================================ package com.zhisheng.alert.model; /** * 消息类型:文本、链接、MarkDown、跳转卡片、消息卡片五种枚举值 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public enum MessageType { /** * 文本类型 */ text, /** * 链接类型 */ link, /** * MarkDown类型 */ markdown, /** * 跳转卡片类型 */ actionCard, /** * 消息卡片类型 */ feedCard; } ================================================ FILE: flink-learning-monitor/flink-learning-monitor-alert/src/main/java/com/zhisheng/alert/model/OutageMetricEvent.java ================================================ package com.zhisheng.alert.model; import com.zhisheng.common.constant.MachineConstant; import com.zhisheng.common.model.MetricEvent; import lombok.Data; import lombok.NoArgsConstructor; import java.util.Map; import static com.zhisheng.common.constant.MachineConstant.*; /** * Desc: outage event * Created by zhisheng on 2019/10/15 上午12:11 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ @Data @NoArgsConstructor public class OutageMetricEvent { //machine cluster name private String clusterName; //machine host ip private String hostIp; //event time private Long timestamp; //if the machine alert is recover (true/false) private Boolean recover; //the time when machine alert recover private Long recoverTime; //system time private Long systemTimestamp; //machine cpu usage percent private Double cpuUsePercent; //machine mem usage percent private Double memUsedPercent; //machine swap usage percent private Double swapUsedPercent; //machine load in five minutes private Double load5; //the machine alert count private int counter = 0; public OutageMetricEvent(String clusterName, String hostIp, long timestamp, Boolean recover, Long systemTimestamp) { this.clusterName = clusterName; this.hostIp = hostIp; this.timestamp = timestamp; this.recover = recover; this.systemTimestamp = systemTimestamp; } public String getKey() { return clusterName + hostIp; } public static OutageMetricEvent buildFromEvent(MetricEvent event) { Map tags = event.getTags(); Map fields = event.getFields(); OutageMetricEvent outageMetricEvent = new OutageMetricEvent(tags.get(MachineConstant.CLUSTER_NAME), tags.get(MachineConstant.HOST_IP), event.getTimestamp(), null, null); switch (event.getName()) { case MEM: if (fields.containsKey(USED_PERCENT)) { outageMetricEvent.setMemUsedPercent((Double) fields.get(USED_PERCENT)); } break; case LOAD: if (fields.containsKey(LOAD5)) { outageMetricEvent.setLoad5((Double) fields.get(LOAD5)); } break; case SWAP: if (fields.containsKey(USED_PERCENT)) { outageMetricEvent.setMemUsedPercent((Double) fields.get(USED_PERCENT)); } break; case CPU: if (fields.containsKey(USED_PERCENT)) { outageMetricEvent.setCpuUsePercent((Double) fields.get(USED_PERCENT)); } break; default: return null; } return outageMetricEvent; } } ================================================ FILE: flink-learning-monitor/flink-learning-monitor-alert/src/main/java/com/zhisheng/alert/model/TextMessage.java ================================================ package com.zhisheng.alert.model; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; /** * 文本类型钉钉消息 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ @Data @AllArgsConstructor @NoArgsConstructor public class TextMessage extends BaseMessage { /** * 消息内容 */ public TextContent text; /** * @ */ public AtMobiles at; @Override protected void init() { this.msgtype = MessageType.text; } @Data public static class TextContent { /** * 消息内容 */ private String content; } } ================================================ FILE: flink-learning-monitor/flink-learning-monitor-alert/src/main/java/com/zhisheng/alert/model/WorkNotify.java ================================================ package com.zhisheng.alert.model; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; /** * 钉钉工作通知 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ @Data @NoArgsConstructor public class WorkNotify { /** * 微应用 id */ private Integer agent_id; /** * 接收人列表,逗号分隔 */ private String userid_list; /** * 部门 id 列表,可选 */ private String dept_id_list; /** * 是否发送给所有 */ private Boolean to_all_user; /** * 消息体 */ private Msg msg; @Data @AllArgsConstructor public static class Msg { /** * 消息类型 */ private String msgtype; private Text text; @Data @AllArgsConstructor public static class Text { /** * 消息内容 */ private String content; } } } ================================================ FILE: flink-learning-monitor/flink-learning-monitor-alert/src/main/java/com/zhisheng/alert/utils/DingDingAccessTokenUtil.java ================================================ package com.zhisheng.alert.utils; import com.google.common.base.Throwables; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import com.jayway.jsonpath.JsonPath; import com.zhisheng.common.utils.HttpUtil; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.TimeUnit; /** * 获取access_token * https://open-doc.dingtalk.com/microapp/serverapi2/eev437 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ @Slf4j public class DingDingAccessTokenUtil { public static final String DING_DING_ACCESS_TOKEN = "access_token"; public static final String DING_DING_GET_TOKEN_URL = "https://oapi.dingtalk.com/gettoken"; public static Cache expireCache = CacheBuilder.newBuilder() .expireAfterAccess(2, TimeUnit.HOURS) .build(); /** * 根据微应用 key 和 secret 获取 access_token * * @param appKey * @param appSecret * @return */ public static String getAccessToken(String appKey, String appSecret) { String accessToken = ""; String dingDingAccessTocken = expireCache.getIfPresent(DING_DING_ACCESS_TOKEN); if (dingDingAccessTocken == null || "".equals(dingDingAccessTocken)) { String result = ""; try { result = HttpUtil.doGet(DING_DING_GET_TOKEN_URL + "?appkey=" + appKey + "&appsecret=" + appSecret); accessToken = JsonPath.read(result, "$.access_token"); if (accessToken != null && !"".equals(accessToken)) { expireCache.put(DING_DING_ACCESS_TOKEN, String.valueOf(accessToken)); log.info("get ding ding access token = {}", String.valueOf(accessToken)); } } catch (Exception e) { log.error("--------------httpclient do get request exception , msg = {}, result = {}", Throwables.getStackTraceAsString(e), result); } } else { accessToken = dingDingAccessTocken; } return accessToken; } } ================================================ FILE: flink-learning-monitor/flink-learning-monitor-alert/src/main/java/com/zhisheng/alert/utils/DingDingGroupMsgUtil.java ================================================ package com.zhisheng.alert.utils; import com.zhisheng.alert.model.AtMobiles; import com.zhisheng.alert.model.LinkMessage; import com.zhisheng.alert.model.MarkDownMessage; import com.zhisheng.alert.model.TextMessage; import com.zhisheng.common.utils.GsonUtil; import com.zhisheng.common.utils.HttpUtil; import lombok.extern.slf4j.Slf4j; import java.util.List; /** * 钉钉群消息工具类 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ @Slf4j public class DingDingGroupMsgUtil { /** * 组装发送的文本信息 * * @param isAtAll 是否需要 @所有人 * @param msgContent 要发送信息的主体 * @param telList 要 @人的电话号码,如果@单独的几个人,就传一个空list,而不是 null * @return */ public static String setTextMessage(boolean isAtAll, String msgContent, List telList) { AtMobiles atMobiles = new AtMobiles(); atMobiles.setIsAtAll(isAtAll); atMobiles.setAtMobiles(telList); TextMessage.TextContent content = new TextMessage.TextContent(); content.setContent(msgContent); return GsonUtil.toJson(new TextMessage(content, atMobiles)); } /** * 组装发送的 markdown 信息 * * @param isAtAll 是否需要 @所有人 * @param title 要发送信息的标题 * @param text 要发送信息的主体 * @param telList 要 @人的电话号码,如果@单独的几个人,就传一个空list,而不是 null * @return */ public static String setMarkdownMessage(boolean isAtAll, String title, String text, List telList) { AtMobiles atMobiles = new AtMobiles(); atMobiles.setIsAtAll(isAtAll); atMobiles.setAtMobiles(telList); MarkDownMessage.MarkDownContent content = new MarkDownMessage.MarkDownContent(); content.setTitle(title); content.setText(text); return GsonUtil.toJson(new MarkDownMessage(content, atMobiles)); } /** * 组装发送的 link 信息 * * @param messageUrl 消息跳转 url * @param picUrl 图片 url * @param text 要发送信息的主体 * @param title 要发送信息的标题 * @return */ public static String setLinkMessage(String messageUrl, String picUrl, String text, String title) { LinkMessage.Link link = new LinkMessage.Link(); link.setTitle(title); link.setText(text); link.setPicUrl(picUrl); link.setMessageUrl(messageUrl); return GsonUtil.toJson(new LinkMessage(link)); } /** * 给具体的钉钉机器人发送消息 * * @param url 钉钉机器人地址 * @param msg 消息内容 * @return 发送消息返回的结果 */ public static String sendDingDingMsg(String url, String msg) { String result = null; try { result = HttpUtil.doPostString(url, msg); } catch (Exception e) { log.error("send ding ding msg has an error, detail : {}", e); } return result; } /** * 同时给多个钉钉机器人发送消息 * * @param urls 多个钉钉机器人地址 * @param msg 消息内容 */ public static void sendDingDingMsg(List urls, String msg) { if (urls == null || urls.size() <= 0) { log.warn("you may forget add notify dingding hook"); return; } for (String url : urls) { sendDingDingMsg(url, msg); } } /** * 给一个钉钉机器人同时发送多条消息(注意:钉钉本身的限制 1分钟内一个机器人最多发送 20 条消息) * * @param url 钉钉地址 * @param msgs 多条消息 */ public static void sendDingDingMsg(String url, List msgs) { if (msgs == null || msgs.size() <= 0) { log.warn("you may forget add notify msg"); return; } for (String msg: msgs) { sendDingDingMsg(url, msg); } } /** * 给多个钉钉群发送多条消息 * * @param urls 多个钉钉地址 * @param msgs 多条消息 */ public static void sendDingDingMsg(List urls, List msgs) { if (msgs == null || msgs.size() <= 0) { log.warn("you may forget add notify msg"); return; } if (urls == null || urls.size() <= 0) { log.warn("you may forget add notify dingding hook"); return; } for (String url : urls) { for (String msg: msgs) { sendDingDingMsg(url, msg); } } } } ================================================ FILE: flink-learning-monitor/flink-learning-monitor-alert/src/main/java/com/zhisheng/alert/utils/DingDingWorkspaceNoticeUtil.java ================================================ package com.zhisheng.alert.utils; /** * 钉钉工作通知工具类 * https://open-doc.dingtalk.com/microapp/serverapi2/pgoxpy */ public class DingDingWorkspaceNoticeUtil { public static final String workNotifyUrl = "https://oapi.dingtalk.com/topapi/message/corpconversation/asyncsend_v2?access_token="; } ================================================ FILE: flink-learning-monitor/flink-learning-monitor-alert/src/main/java/com/zhisheng/alert/utils/EmailNoticeUtil.java ================================================ package com.zhisheng.alert.utils; /** * 邮件通知工具类 */ public class EmailNoticeUtil { } ================================================ FILE: flink-learning-monitor/flink-learning-monitor-alert/src/main/java/com/zhisheng/alert/utils/PhoneNoticeUtil.java ================================================ package com.zhisheng.alert.utils; /** * 电话通知工具类 */ public class PhoneNoticeUtil { } ================================================ FILE: flink-learning-monitor/flink-learning-monitor-alert/src/main/java/com/zhisheng/alert/utils/SMSNoticeUtil.java ================================================ package com.zhisheng.alert.utils; /** * 短信通知工具类 */ public class SMSNoticeUtil { } ================================================ FILE: flink-learning-monitor/flink-learning-monitor-alert/src/main/java/com/zhisheng/alert/watermark/OutageMetricWaterMark.java ================================================ package com.zhisheng.alert.watermark; import com.zhisheng.alert.model.OutageMetricEvent; import org.apache.flink.streaming.api.functions.AssignerWithPeriodicWatermarks; import org.apache.flink.streaming.api.watermark.Watermark; /** * Desc: * Created by zhisheng on 2019/10/15 上午12:30 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class OutageMetricWaterMark implements AssignerWithPeriodicWatermarks { private long currentTimestamp = Long.MIN_VALUE; private final long maxTimeLag = 5000; @Override public Watermark getCurrentWatermark() { return new Watermark(currentTimestamp == Long.MIN_VALUE ? Long.MIN_VALUE : currentTimestamp - maxTimeLag); } @Override public long extractTimestamp(OutageMetricEvent outageMetricEvent, long l) { long timestamp = outageMetricEvent.getTimestamp(); currentTimestamp = Math.max(timestamp, currentTimestamp); return timestamp; } } ================================================ FILE: flink-learning-monitor/flink-learning-monitor-alert/src/main/resources/LogEventDataExample.json ================================================ { "type": "app", "id": "121", "timestamp": 1570941591229, "level": "error", "offset": 32313131, "content": "Exception in thread \"main\" java.lang.NoClassDefFoundError: org/apache/flink/api/common/ExecutionConfig$GlobalJobParameters", "tags": { "cluster_name": "zhisheng", "app_name": "zhisheng", "host_ip": "127.0.0.1", "app_id": "21" } } ================================================ FILE: flink-learning-monitor/flink-learning-monitor-alert/src/main/resources/application.properties ================================================ kafka.brokers=localhost:9092 kafka.group.id=zhisheng kafka.zookeeper.connect=localhost:2181 metrics.topic=zhisheng_metrics log.topic=zhisheng_log stream.parallelism=4 stream.sink.parallelism=4 stream.default.parallelism=4 stream.checkpoint.interval=1000 stream.checkpoint.enable=false ================================================ FILE: flink-learning-monitor/flink-learning-monitor-alert/src/main/resources/logback.xml ================================================ %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n ================================================ FILE: flink-learning-monitor/flink-learning-monitor-alert/src/test/java/BuildLogEventDataUtil.java ================================================ import com.zhisheng.common.model.LogEvent; import com.zhisheng.common.utils.GsonUtil; import lombok.extern.slf4j.Slf4j; import org.apache.kafka.clients.producer.KafkaProducer; import org.apache.kafka.clients.producer.ProducerRecord; import java.util.HashMap; import java.util.Map; import java.util.Properties; import java.util.Random; /** * Desc: build log event data * Created by zhisheng on 2019/10/13 下午12:29 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ @Slf4j public class BuildLogEventDataUtil { public static final String BROKER_LIST = "localhost:9092"; public static final String LOG_TOPIC = "zhisheng_log"; public static void writeDataToKafka() { Properties props = new Properties(); props.put("bootstrap.servers", BROKER_LIST); props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer"); props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer"); KafkaProducer producer = new KafkaProducer(props); for (int i = 0; i < 100; i++) { LogEvent logEvent = new LogEvent().builder() .type("app") .timestamp(System.currentTimeMillis()) .level(logLevel()) .message(message(i + 1)) .tags(mapData()) .build(); // System.out.println(logEvent); ProducerRecord record = new ProducerRecord(LOG_TOPIC, null, null, GsonUtil.toJson(logEvent)); producer.send(record); } producer.flush(); } public static void main(String[] args) { writeDataToKafka(); } public static String message(int i) { return "这是第 " + i + " 行日志!"; } public static String logLevel() { Random random = new Random(); int number = random.nextInt(4); switch (number) { case 0: return "debug"; case 1: return "info"; case 2: return "warn"; case 3: return "error"; default: return "info"; } } public static String hostIp() { Random random = new Random(); int number = random.nextInt(4); switch (number) { case 0: return "121.12.17.10"; case 1: return "121.12.17.11"; case 2: return "121.12.17.12"; case 3: return "121.12.17.13"; default: return "121.12.17.10"; } } public static Map mapData() { Map map = new HashMap<>(); map.put("app_id", "11"); map.put("app_name", "zhisheng"); map.put("cluster_name", "zhisheng"); map.put("host_ip", hostIp()); map.put("class", "BuildLogEventDataUtil"); map.put("method", "main"); map.put("line", String.valueOf(new Random().nextInt(100))); //add more tag return map; } } ================================================ FILE: flink-learning-monitor/flink-learning-monitor-alert/src/test/java/BuildMachineMetricDataUtil.java ================================================ import com.zhisheng.common.model.MetricEvent; import com.zhisheng.common.utils.GsonUtil; import org.apache.kafka.clients.producer.KafkaProducer; import org.apache.kafka.clients.producer.ProducerRecord; import java.util.*; /** * Desc: build machine metric data * Created by zhisheng on 2019/10/15 上午10:47 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class BuildMachineMetricDataUtil { public static final String BROKER_LIST = "localhost:9092"; public static final String METRICS_TOPIC = "zhisheng_metrics"; public static Random random = new Random(); public static List hostIps = Arrays.asList("121.12.17.10", "121.12.17.11", "121.12.17.12", "121.12.17.13"); public static void writeDataToKafka() throws InterruptedException { Properties props = new Properties(); props.put("bootstrap.servers", BROKER_LIST); props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer"); props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer"); KafkaProducer producer = new KafkaProducer(props); while (true) { long timestamp = System.currentTimeMillis(); for (int i = 0; i < hostIps.size(); i++) { MetricEvent cpuData = buildCpuData(hostIps.get(i), timestamp); MetricEvent loadData = buildLoadData(hostIps.get(i), timestamp); MetricEvent memData = buildMemData(hostIps.get(i), timestamp); MetricEvent swapData = buildSwapData(hostIps.get(i), timestamp); ProducerRecord cpuRecord = new ProducerRecord(METRICS_TOPIC, null, null, GsonUtil.toJson(cpuData)); ProducerRecord loadRecord = new ProducerRecord(METRICS_TOPIC, null, null, GsonUtil.toJson(loadData)); ProducerRecord memRecord = new ProducerRecord(METRICS_TOPIC, null, null, GsonUtil.toJson(memData)); ProducerRecord swapRecord = new ProducerRecord(METRICS_TOPIC, null, null, GsonUtil.toJson(swapData)); producer.send(cpuRecord); producer.send(loadRecord); // System.out.println(cpuData); // System.out.println(loadData); producer.send(memRecord); producer.send(swapRecord); } producer.flush(); Thread.sleep(1000); } } public static void main(String[] args) throws InterruptedException { writeDataToKafka(); } public static MetricEvent buildCpuData(String hostIp, Long timestamp) { MetricEvent metricEvent = new MetricEvent(); Map tags = new HashMap<>(); Map fields = new HashMap<>(); int used = random.nextInt(2048); int max = 2048; metricEvent.setName("cpu"); metricEvent.setTimestamp(timestamp); tags.put("cluster_name", "zhisheng"); tags.put("host_ip", hostIp); fields.put("usedPercent", (double) used / max * 100); fields.put("used", used); fields.put("max", max); metricEvent.setFields(fields); metricEvent.setTags(tags); return metricEvent; } public static MetricEvent buildLoadData(String hostIp, Long timestamp) { MetricEvent metricEvent = new MetricEvent(); Map tags = new HashMap<>(); Map fields = new HashMap<>(); metricEvent.setName("load"); metricEvent.setTimestamp(timestamp); tags.put("cluster_name", "zhisheng"); tags.put("host_ip", hostIp); fields.put("load1", random.nextInt(100)); fields.put("load5", random.nextInt(50)); fields.put("load15", random.nextInt(25)); metricEvent.setFields(fields); metricEvent.setTags(tags); return metricEvent; } public static MetricEvent buildSwapData(String hostIp, Long timestamp) { MetricEvent metricEvent = new MetricEvent(); Map tags = new HashMap<>(); Map fields = new HashMap<>(); int used = random.nextInt(1024); int max = 1024; metricEvent.setName("swap"); metricEvent.setTimestamp(timestamp); tags.put("cluster_name", "zhisheng"); tags.put("host_ip", hostIp); fields.put("usedPercent", (double) used / max * 100); fields.put("used", used); fields.put("max", max); metricEvent.setFields(fields); metricEvent.setTags(tags); return metricEvent; } public static MetricEvent buildMemData(String hostIp, Long timestamp) { MetricEvent metricEvent = new MetricEvent(); Map tags = new HashMap<>(); Map fields = new HashMap<>(); int used = random.nextInt(2048); int max = 2048; metricEvent.setName("mem"); metricEvent.setTimestamp(timestamp); tags.put("cluster_name", "zhisheng"); tags.put("host_ip", hostIp); fields.put("usedPercent", (double) used / max * 100); fields.put("used", used); fields.put("max", max); metricEvent.setFields(fields); metricEvent.setTags(tags); return metricEvent; } } ================================================ FILE: flink-learning-monitor/flink-learning-monitor-alert/src/test/java/DingDingMsgTest.java ================================================ import com.google.common.collect.Lists; import com.zhisheng.alert.utils.DingDingGroupMsgUtil; import java.util.ArrayList; import java.util.List; /** * Desc: 钉钉发送消息测试 * Created by zhisheng on 2019-06-20 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class DingDingMsgTest { public static void main(String[] args) { //要 @ 人的列表 List tels = new ArrayList<>(); tels.add("138xxxx6721"); tels.add("139xxxx6821"); //发送 markdown 消息 String markdownMsg = "【宕机告警】机器 300秒内无消息,疑似发生宕机\n\n集群:zhisheng\n\nIP:[10.117.0.118](http://xxxx_filter_host_ip=10.117.0.118&x_timestamp=1561105441347)\n\n最近消息时间:2019-06-21 16:24:01 对应数据如下:\n\n内存使用率:90.00%\n\nLoad 5分钟为:0.8\n\nSwap 内存使用率:21.1%\n\nCPU 剩余:21.98%\n\nIP:[10.117.0.118](https://xxx=alert&x_filter_host_ip=10.117.0.118&x_timestamp=1561105441347)\n\n最近消息时间:2019-06-21 16:24:01 对应数据如下:\n\n内存使用率:90.00%\n\nLoad 5分钟为:0.8\n\nSwap 内存使用率:21.1%\n\nCPU 剩余:21.98%\n\nIP:[10.117.0.118](https://xxxfilter_host_ip=10.117.0.118&x_timestamp=1561105441347)\n\n最近消息时间:2019-06-21 16:24:01 对应数据如下:\n\n内存使用率:90.00%\n\nLoad 5分钟为:0.8\n\nSwap 内存使用率:21.1%\n\nCPU 剩余:21.98%\n\nIP:[10.117.0.118](https://xxx_filter_host_ip=10.117.0.118&x_timestamp=1561105441347)\n\n最近消息时间:2019-06-21 16:24:01 对应数据如下:\n\n内存使用率:90.00%\n\nLoad 5分钟为:0.8\n\nSwap 内存使用率:21.1%\n\nCPU 剩余:21.98%"; String markdown = DingDingGroupMsgUtil.setMarkdownMessage(false, "markdown", markdownMsg, tels); String markdownResult = DingDingGroupMsgUtil.sendDingDingMsg("https://oapi.dingtalk.com/robot/send?access_token=32e9d99020641c46146174de1ea437fffb4122f957102078b3f1898009af7e40", markdown); System.out.println(markdownResult); //发送文本消息 String textMsg = "zhisheng 的博客 text 消息"; String text = DingDingGroupMsgUtil.setTextMessage(false, textMsg, new ArrayList<>()); String textResult = DingDingGroupMsgUtil.sendDingDingMsg("https://oapi.dingtalk.com/robot/send?access_token=32e9d99020641c46146174de1ea437fffb4122f957102078b3f1898009af7e40", text); System.out.println(textResult); //发送 link 消息 String linkMsg = "zhisheng 的博客 link 消息"; String link = DingDingGroupMsgUtil.setLinkMessage("http://www.54tianzhisheng.cn/", "http://www.54tianzhisheng.cn/img/avatar.png", linkMsg, "link 消息测试"); String linkResult = DingDingGroupMsgUtil.sendDingDingMsg("https://oapi.dingtalk.com/robot/send?access_token=32e9d99020641c46146174de1ea437fffb4122f957102078b3f1898009af7e40", link); System.out.println(linkResult); //同一个钉钉地址发送多条消息 String text1 = "111111"; String text2 = "222222"; String text3 = "333333"; String text4 = "444444"; DingDingGroupMsgUtil.sendDingDingMsg("https://oapi.dingtalk.com/robot/send?access_token=32e9d99020641c46146174de1ea437fffb4122f957102078b3f1898009af7e40", Lists.newArrayList(text1, text2, text3, text4)); //多个钉钉发送同一条消息 String hook1 = "https://oapi.dingtalk.com/robot/send?access_token=32e9d99020641c46146174de1ea437fffb4122f957102078b3f1898009af7e40"; String hook2 = "https://oapi.dingtalk.com/robot/send?access_token=32e9d99020641c46146174de1ea437fffb4122f957102078b3f1898009af7e40"; String hook3 = "https://oapi.dingtalk.com/robot/send?access_token=32e9d99020641c46146174de1ea437fffb4122f957102078b3f1898009af7e40"; String hook4 = "https://oapi.dingtalk.com/robot/send?access_token=32e9d99020641c46146174de1ea437fffb4122f957102078b3f1898009af7e40"; String msg = "zhisheng 的博客"; DingDingGroupMsgUtil.sendDingDingMsg(Lists.newArrayList(hook1, hook2, hook3, hook4), msg); //给多个钉钉群发送多条消息 DingDingGroupMsgUtil.sendDingDingMsg(Lists.newArrayList(hook1, hook2, hook3, hook4), Lists.newArrayList(text1, text2, text3, text4)); } } ================================================ FILE: flink-learning-monitor/flink-learning-monitor-alert/src/test/java/LogEventDataExample.java ================================================ import com.zhisheng.common.model.LogEvent; import com.zhisheng.common.utils.GsonUtil; import java.util.HashMap; import java.util.Map; /** * Desc: log event data example * Created by zhisheng on 2019/10/13 下午12:29 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class LogEventDataExample { public static void main(String[] args) { LogEvent logEvent = new LogEvent(); logEvent.setType("app"); Map tags = new HashMap<>(); tags.put("cluster_name", "zhisheng"); tags.put("host_ip", "127.0.0.1"); tags.put("app_id", "21"); tags.put("app_name", "zhisheng"); String message = "Exception in thread \"main\" java.lang.NoClassDefFoundError: org/apache/flink/api/common/ExecutionConfig$GlobalJobParameters"; LogEvent event = new LogEvent().builder() .type("app") .timestamp(System.currentTimeMillis()) .level("error") .message(message) .tags(tags).build(); System.out.println(GsonUtil.toJson(event)); } } ================================================ FILE: flink-learning-monitor/flink-learning-monitor-collector/README.md ================================================ ## Flink 监控数据采集 因为数据采集是非常重要的,如果在底层数据采集的过程中就断了,那么后面的存储和告警链路将全部失效,所以为了保证后面链路的可用性, 数据采集的话就不能也是一个 Flink Job,否则因为 Flink 挂了的话,那么就会导致数据采集的 Job 失效,导致整个监控告警链路失效。 另外,也不符合 Flink 的特点,Flink 其实更在于计算,有数据源(source)和下发处(sink),这里我们采集数据的话就自己写一个项目利用 Flink 自己暴露的 Rest API 去采集相关数据(JobManager、TaskManager、Job 等),将采集好的数据组织好成一个个 Metrics,然后发送到 Kafka。 ================================================ FILE: flink-learning-monitor/flink-learning-monitor-collector/flink_log_event.json ================================================ { "source": "flink-1.16.1-sql", "id": "d47d6b2d-c727-448d-b58b-4d2607df771f", "timestamp": 1741677244482, "content": "Request commitFiles return SUCCESS for 1741677153039-6421af218b4c8ce71454da9428a22759-0", "tags": { "task_name": "613750_1729863898342", "host_ip": "10.xxx.xxx.140", "node_ip": "10.xxx.xxx.22", "level": "INFO", "file_name": "Logging.scala", "task_id": "359158", "method_name": "logInfo", "line_number": "51", "thread_name": "celeborn-dispatcher-3", "container_type": "jobmanager", "logger_name": "org.apache.celeborn.client.commit.MapPartitionCommitHandler", "class_name": "org.apache.celeborn.common.internal.Logging", "app_id": "batch-flink-359158-1741677126342", "host_name": "batch-flink-359158-1741677126342-7d549799d4-q78jt", "container_id": "batch-flink-359158-1741677126342-7d549799d4-q78jt" } } ================================================ FILE: flink-learning-monitor/flink-learning-monitor-collector/flink_metrics_event.json ================================================ { "name": "taskmanager_job_task_isBackPressured", "timestamp": 1741677078908, "fields": { "value": "false" }, "tags": { "task_name": "Filter -> Map -> Filter -> Map -> Filter -> Map -> Filter -> Timestamps/Watermarks -> Sink: Unnamed", "task_attempt_id": "77ce8a827e33ef8582b0788c40d7193f", "node_ip": "10.xxx.xxx.13", "task_id": "20ba6b65f97481d5570070de90e4e791", "platform_task_name": "base_xxx-monitor", "task_attempt_num": "0", "job_name": "base_xxx_monitor_insert_to_es", "job_id": "a65ef348e256a85ff386cdbd3e8ea09a", "host": "10.xxx.xxx.251", "flink_version": "1.12.0", "container_type": "taskmanager", "app_id": "flink-2180-1732606614446", "platform_task_id": "2180", "container_id": "flink-2180-1732606614446-taskmanager-1-8", "subtask_index": "197" } } ================================================ FILE: flink-learning-monitor/flink-learning-monitor-collector/pom.xml ================================================ flink-learning-monitor com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 flink-learning-monitor-collector com.zhisheng.flink flink-learning-monitor-common ${project.version} ================================================ FILE: flink-learning-monitor/flink-learning-monitor-collector/src/main/java/com/zhisheng/collector/FlinkJobMetricCollect.java ================================================ package com.zhisheng.collector; import com.zhisheng.common.utils.HttpUtil; import com.zhisheng.common.utils.PropertiesUtil; public class FlinkJobMetricCollect { public static void main(String[] args) { String jobManagerHost = PropertiesUtil.defaultProp.get("flink.jobmanager.host").toString(); String jobOverviewResult = HttpUtil.doGet("http://" + jobManagerHost + "/jobs/overview"); } } ================================================ FILE: flink-learning-monitor/flink-learning-monitor-common/README.md ================================================ ## Flink 监控 common ================================================ FILE: flink-learning-monitor/flink-learning-monitor-common/pom.xml ================================================ flink-learning-monitor com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 flink-learning-monitor-common com.zhisheng.flink flink-learning-common ${project.version} ================================================ FILE: flink-learning-monitor/flink-learning-monitor-common/src/main/java/com/zhisheng/common/model/Job.java ================================================ package com.zhisheng.common.model; import lombok.Data; import lombok.NoArgsConstructor; @Data @NoArgsConstructor public class Job { /** * job id */ private String jid; /** * job name */ private String name; /** * job status */ private JobStatus state; /** * job start time */ private Long startTime; /** * job end time */ private Long endTime; /** * job duration time */ private Long duration; /** * job last modify time */ private Long lastModification; /** * job tasks */ private Task tasks; } ================================================ FILE: flink-learning-monitor/flink-learning-monitor-common/src/main/java/com/zhisheng/common/model/JobStatus.java ================================================ package com.zhisheng.common.model; public enum JobStatus { /** * Job is newly created, no task has started to run. */ CREATED, /** * Some tasks are scheduled or running, some may be pending, some may be finished. */ RUNNING, /** * The job has failed and is currently waiting for the cleanup to complete */ FAILING, /** * The job has failed with a non-recoverable task failure */ FAILED, /** * Job is being cancelled */ CANCELLING, /** * Job has been cancelled */ CANCELED, /** * All of the job's tasks have successfully finished */ FINISHED, /** * The job is currently undergoing a reset and total restart */ RESTARTING, /** * The job has been suspended and is currently waiting for the cleanup to complete */ SUSPENDING, /** * The job has been suspended which means that it has been stopped but not been removed from a * potential HA job store. */ SUSPENDED, /** * The job is currently reconciling and waits for task execution report to recover state. */ RECONCILING; } ================================================ FILE: flink-learning-monitor/flink-learning-monitor-common/src/main/java/com/zhisheng/common/model/Task.java ================================================ package com.zhisheng.common.model; import lombok.Data; import lombok.NoArgsConstructor; @Data @NoArgsConstructor public class Task { /** * task 总个数 */ private int total; /** * 处于 created 状态的 task 个数 */ private int created; /** * 处于 scheduled 状态的 task 个数 */ private int scheduled; /** * 处于 deploying 状态的 task 个数 */ private int deploying; /** * 处于 running 状态的 task 个数 */ private int running; /** * 处于 finished 状态的 task 个数 */ private int finished; /** * 处于 canceling 状态的 task 个数 */ private int canceling; /** * 处于 canceled 状态的 task 个数 */ private int canceled; /** * 处于 failed 状态的 task 个数 */ private int failed; /** * 处于 reconciling 状态的 task 个数 */ private int reconciling; } ================================================ FILE: flink-learning-monitor/flink-learning-monitor-common/src/main/java/com/zhisheng/common/utils/PropertiesUtil.java ================================================ package com.zhisheng.common.utils; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.util.*; import java.util.stream.Collectors; public class PropertiesUtil { /** * 默认属性集合(文件在Constants中配置) */ public static Properties defaultProp = null; /** * 所有读取过的属性集合 * 文件名 <-> 属性集合 */ public static Map allProps = new HashMap<>(); // 初始化默认的属性集合 static { if (defaultProp == null && isExistProperties("application.properties")) { defaultProp = loadProperties("application.properties"); allProps.put("application.properties", defaultProp); } } /** * 读取属性文件,并将读出来的属性集合添加到【allProps】当中 * 如果该属性文件之前已读取过,则直接从【allProps】获得 */ public static Properties getProperties(String fileName) { if (fileName == null || "".equals(fileName)) { return defaultProp; } else { Properties prop = allProps.get(fileName); if (prop == null) { prop = loadProperties(fileName); allProps.put(fileName, prop); } return prop; } } /** * 解析属性文件,将文件中的所有属性都读取到【Properties】当中 */ protected static Properties loadProperties(String fileName) { Properties prop = new Properties(); InputStream ins = null; ins = PropertiesUtil.class.getClassLoader().getResourceAsStream(fileName); if (ins == null) { System.err.println("Can not find the resource!"); } else { try { prop.load(ins); } catch (IOException e) { System.err.println("An error occurred when reading from the input stream, " + e.getMessage()); } catch (IllegalArgumentException e) { System.err.println("The input stream contains a malformed Unicode escape sequence, " + e.getMessage()); } } return prop; } public static Boolean isExistProperties(String filePath) { URL resource = PropertiesUtil.class.getClassLoader().getResource(filePath); return resource != null; } /** * 从指定的属性文件中获取某一属性值 * 如果属性文件不存在该属性则返回 null */ public static String getProperty(String fileName, String name) { return getProperties(fileName).getProperty(name); } /** * 从默认的属性文件中获取某一属性值 * 如果属性文件不存在该属性则返回 null */ public static String getProperty(String name) { return getProperties(null).getProperty(name); } /** * @param properties * @return */ public static Map getPropertyMap(Properties properties) { return properties.values() .stream() .collect(Collectors.toMap(k -> k.toString(), v -> v.toString())); } /** * @return */ public static Map getPropertyMap() { Map allPropsMap = new HashMap<>(); allProps.values().forEach(props -> { final Map mapProperties = getPropertyMap(props); allPropsMap.putAll(mapProperties); }); return allPropsMap; } /** * 获取所有的 key * @param fileName * @return * @throws IOException */ public static List getkeys(String fileName) throws IOException { Properties prop = new Properties(); //获取输入流 InputStream in = PropertiesUtil.class.getClassLoader().getResourceAsStream(fileName); //加载进去 prop.load(in); Set keyValue = prop.keySet(); List list = new ArrayList<>(); for (Iterator it = keyValue.iterator(); it.hasNext();) { String key = (String) it.next(); list.add(key); } return list; } //test public static void main(String[] args) throws IOException { List keys = getkeys("application.properties"); System.out.println(keys); for (String key:keys) { String value = PropertiesUtil.getProperty("application.properties", key); System.out.println(value); } loadProperties("application.properties").keySet().forEach(it -> System.out.println(it)); getkeys("application.properties").forEach(it -> System.out.println(it)); getProperties("application.properties").keySet().forEach(it-> System.out.println(it)); } } ================================================ FILE: flink-learning-monitor/flink-learning-monitor-dashboard/README.md ================================================ ## Flink Monitor Dashboard ================================================ FILE: flink-learning-monitor/flink-learning-monitor-dashboard/pom.xml ================================================ flink-learning-monitor com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 flink-learning-monitor-dashboard com.zhisheng.flink flink-learning-monitor-common ${project.version} ================================================ FILE: flink-learning-monitor/flink-learning-monitor-log/pom.xml ================================================ flink-learning-monitor com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 flink-learning-monitor-log com.zhisheng.flink flink-learning-monitor-common ${project.version} io.krakens java-grok 0.1.9 org.apache.flink flink-connector-elasticsearch6 ${flink-connector-elasticsearch6.version} org.apache.maven.plugins maven-shade-plugin 3.1.0 false package shade com.zhisheng.log.LogMain reference.conf *:*:*:* META-INF/*.SF META-INF/*.DSA META-INF/*.RSA ================================================ FILE: flink-learning-monitor/flink-learning-monitor-log/src/main/java/com/zhisheng/log/LogAlert.java ================================================ package com.zhisheng.log; import com.zhisheng.common.model.LogEvent; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.streaming.api.datastream.DataStream; /** * Desc: log alert * Created by zhisheng on 2019/10/26 下午7:23 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class LogAlert { public static void alert(DataStream logDataStream, ParameterTool parameterTool) { //异常日志事件 logDataStream.filter(logEvent -> "ERROR".equals(logEvent.getLevel().toUpperCase())) .print(); //告警事件与应用通知方式和收敛方式的策略数据关联 //sink 调用发送告警消息的接口 } } ================================================ FILE: flink-learning-monitor/flink-learning-monitor-log/src/main/java/com/zhisheng/log/LogMain.java ================================================ package com.zhisheng.log; import com.zhisheng.common.model.LogEvent; import com.zhisheng.common.utils.ExecutionEnvUtil; import com.zhisheng.log.function.OriLog2LogEventFlatMapFunction; import com.zhisheng.log.schema.OriginalLogEventSchema; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumer; import static com.zhisheng.common.utils.KafkaConfigUtil.buildKafkaProps; /** * process the log data stream * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class LogMain { public static void main(String[] args) throws Exception { final ParameterTool parameterTool = ExecutionEnvUtil.createParameterTool(args); StreamExecutionEnvironment env = ExecutionEnvUtil.prepare(parameterTool); SingleOutputStreamOperator logDataStream = env.addSource(new FlinkKafkaConsumer<>("zhisheng_log", new OriginalLogEventSchema(), buildKafkaProps(parameterTool))) .flatMap(new OriLog2LogEventFlatMapFunction()); //alert LogAlert.alert(logDataStream, parameterTool); // test // DataStreamSource logDataStream = env.addSource(new SourceFunction() { // @Override // public void run(SourceContext ctx) throws Exception { // Map tags = new HashMap<>(); // tags.put("host_name", "zhisheng"); // tags.put("kafka_tpoic", "zhisheng_log"); // tags.put("source", "/var/logs/middleware/kafka.log"); // while (true) { // LogEvent logEvent = LogEvent.builder().timestamp(System.currentTimeMillis()) // .level("INFO") // .type("APP") // .message("2019-10-26 19:53:05 INFO [GroupMetadataManager brokerId=0] Removed 0 expired offsets in 0 milliseconds. (kafka.coordinator.group.GroupMetadataManager)") // .tags(tags) // .build(); // ctx.collect(logEvent); // } // } // // @Override // public void cancel() { // // } // }); //sink to es LogSink2ES.sink2es(logDataStream, parameterTool); env.execute("flink learning monitor log"); } } ================================================ FILE: flink-learning-monitor/flink-learning-monitor-log/src/main/java/com/zhisheng/log/LogSink2ES.java ================================================ package com.zhisheng.log; import com.zhisheng.common.model.LogEvent; import com.zhisheng.common.utils.GsonUtil; import com.zhisheng.log.utils.ESSinkUtil; import lombok.extern.slf4j.Slf4j; import org.apache.flink.api.common.functions.RuntimeContext; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator; import org.apache.flink.streaming.connectors.elasticsearch.RequestIndexer; import org.apache.http.HttpHost; import org.elasticsearch.client.Requests; import org.elasticsearch.common.xcontent.XContentType; import java.net.MalformedURLException; import java.util.List; import static com.zhisheng.common.constant.PropertiesConstants.*; /** * Desc: sink log to es * Created by zhisheng on 2019/10/26 下午7:23 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ @Slf4j public class LogSink2ES { public static void sink2es(SingleOutputStreamOperator logDataStream, ParameterTool parameterTool) { List esAddresses; try { esAddresses = ESSinkUtil.getEsAddresses(parameterTool.get(ELASTICSEARCH_HOSTS)); } catch (MalformedURLException e) { log.error("get es address has an error", e); return; } int bulkSize = parameterTool.getInt(ELASTICSEARCH_BULK_FLUSH_MAX_ACTIONS, 40); int sinkParallelism = parameterTool.getInt(STREAM_SINK_PARALLELISM, 5); ESSinkUtil.addSink(esAddresses, bulkSize, sinkParallelism, logDataStream, (LogEvent logEvent, RuntimeContext runtimeContext, RequestIndexer requestIndexer) -> { requestIndexer.add(Requests.indexRequest() .index("zhisheng_log") .type(ZHISHENG) .source(GsonUtil.toJSONBytes(logEvent), XContentType.JSON)); }, parameterTool); } } ================================================ FILE: flink-learning-monitor/flink-learning-monitor-log/src/main/java/com/zhisheng/log/function/OriLog2LogEventFlatMapFunction.java ================================================ package com.zhisheng.log.function; import com.zhisheng.common.model.LogEvent; import com.zhisheng.common.utils.DateUtil; import com.zhisheng.log.model.OriginalLogEvent; import com.zhisheng.log.utils.GrokUtil; import org.apache.flink.api.common.functions.RichFlatMapFunction; import org.apache.flink.util.Collector; import java.util.HashMap; import java.util.Map; import static com.zhisheng.common.utils.DateUtil.YYYY_MM_DD_HH_MM_SS; /** * Desc: * Created by zhisheng on 2019/10/27 下午1:59 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class OriLog2LogEventFlatMapFunction extends RichFlatMapFunction { @Override public void flatMap(OriginalLogEvent originalLogEvent, Collector collector) throws Exception { if (originalLogEvent == null) { return; } LogEvent logEvent = new LogEvent(); String source = originalLogEvent.getSource(); if (source.contains("middleware")) { logEvent.setType("MIDDLEWARE"); } else if (source.contains("app")){ logEvent.setType("APP"); } else if (source.contains("docker")) { logEvent.setType("DOCKER"); } else { logEvent.setType("MACHINE"); } logEvent.setMessage(originalLogEvent.getMessage()); Map messageMap = GrokUtil.toMap("%{KAFKALOG}", originalLogEvent.getMessage()); logEvent.setTimestamp(DateUtil.format(messageMap.get("timestamp").toString(), YYYY_MM_DD_HH_MM_SS)); logEvent.setLevel(messageMap.get("level").toString()); Map tags = new HashMap<>(); tags.put("host_name", originalLogEvent.getHost().get("name")); tags.put("kafka_tpoic", originalLogEvent.getMetadata().get("topic")); tags.put("source", originalLogEvent.getSource()); //可以添加更多 message 解析出来的字段放在该 tags 里面 logEvent.setTags(tags); collector.collect(logEvent); } } ================================================ FILE: flink-learning-monitor/flink-learning-monitor-log/src/main/java/com/zhisheng/log/model/OriginalLogEvent.java ================================================ package com.zhisheng.log.model; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import org.apache.flink.shaded.jackson2.com.fasterxml.jackson.annotation.JsonProperty; import java.util.Map; /* Created by zhisheng on 2019/10/26 下午7:15 blog:http://www.54tianzhisheng.cn/ 微信公众号:zhisheng desc: Filebeat 发送到 Kafka 的日志如下 { "@timestamp": "2019-10-26T09:23:16.848Z", "@metadata": { "beat": "filebeat", "type": "doc", "version": "6.8.4", "topic": "zhisheng_log" }, "host": { "name": "VM_0_2_centos" }, "source": "/var/logs/controller.log", "message": "[2019-10-26 17:23:11,769] TRACE [Controller id=0] Leader imbalance ratio for broker 0 is 0.0 (kafka.controller.KafkaController)" } */ @Data @NoArgsConstructor @AllArgsConstructor @Builder public class OriginalLogEvent { @JsonProperty("@timestamp") private String timestamp; //use Jackson JsonProperty @JsonProperty("@metadata") private Map metadata; private Map host; private String source; private String message; } ================================================ FILE: flink-learning-monitor/flink-learning-monitor-log/src/main/java/com/zhisheng/log/schema/OriginalLogEventSchema.java ================================================ package com.zhisheng.log.schema; import com.zhisheng.log.model.OriginalLogEvent; import org.apache.flink.api.common.serialization.DeserializationSchema; import org.apache.flink.api.common.typeinfo.TypeInformation; import org.apache.flink.shaded.jackson2.com.fasterxml.jackson.databind.ObjectMapper; import java.io.IOException; /** * Desc: OriginalLogEvent Deserialization Schema * Created by zhisheng on 2019/10/26 下午7:15 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class OriginalLogEventSchema implements DeserializationSchema { private static final ObjectMapper mapper = new ObjectMapper(); @Override public OriginalLogEvent deserialize(byte[] bytes) throws IOException { return mapper.readValue(new String(bytes), OriginalLogEvent.class); } @Override public boolean isEndOfStream(OriginalLogEvent originalLogEvent) { return false; } @Override public TypeInformation getProducedType() { return TypeInformation.of(OriginalLogEvent.class); } } ================================================ FILE: flink-learning-monitor/flink-learning-monitor-log/src/main/java/com/zhisheng/log/utils/ESSinkUtil.java ================================================ package com.zhisheng.log.utils; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator; import org.apache.flink.streaming.connectors.elasticsearch.ElasticsearchSinkFunction; import org.apache.flink.streaming.connectors.elasticsearch6.ElasticsearchSink; import org.apache.http.HttpHost; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.List; /** * Desc: es sink util * Created by zhisheng on 2019/10/21 下午3:05 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class ESSinkUtil { /** * es sink * * @param hosts es hosts * @param bulkFlushMaxActions bulk flush size * @param parallelism 并行数 * @param data 数据 * @param func * @param */ public static void addSink(List hosts, int bulkFlushMaxActions, int parallelism, SingleOutputStreamOperator data, ElasticsearchSinkFunction func, ParameterTool parameterTool) { ElasticsearchSink.Builder esSinkBuilder = new ElasticsearchSink.Builder<>(hosts, func); esSinkBuilder.setBulkFlushMaxActions(bulkFlushMaxActions); esSinkBuilder.setFailureHandler(new RetryRequestFailureHandler()); //todo:xpack security data.addSink(esSinkBuilder.build()).setParallelism(parallelism); } /** * 解析配置文件的 es hosts * * @param hosts * @return * @throws MalformedURLException */ public static List getEsAddresses(String hosts) throws MalformedURLException { String[] hostList = hosts.split(","); List addresses = new ArrayList<>(); for (String host : hostList) { if (host.startsWith("http")) { URL url = new URL(host); addresses.add(new HttpHost(url.getHost(), url.getPort())); } else { String[] parts = host.split(":", 2); if (parts.length > 1) { addresses.add(new HttpHost(parts[0], Integer.parseInt(parts[1]))); } else { throw new MalformedURLException("invalid elasticsearch hosts format"); } } } return addresses; } } ================================================ FILE: flink-learning-monitor/flink-learning-monitor-log/src/main/java/com/zhisheng/log/utils/GrokUtil.java ================================================ package com.zhisheng.log.utils; import com.zhisheng.common.utils.DateUtil; import io.krakens.grok.api.Grok; import io.krakens.grok.api.GrokCompiler; import io.krakens.grok.api.Match; import java.util.HashMap; import java.util.Map; import static com.zhisheng.common.utils.DateUtil.YYYY_MM_DD_HH_MM_SS; /** * Desc: grok util * Created by zhisheng on 2019/10/26 下午11:50 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class GrokUtil { public static final GrokCompiler compiler = GrokCompiler.newInstance(); public static Grok grok = null; public static Map toMap(String pattern, String message) { compiler.registerPatternFromClasspath("/patterns/patterns"); grok = compiler.compile(pattern); if (grok != null) { Match match = grok.match(message); return match.capture(); } else { return new HashMap<>(); } } //test public static void main(String[] args) { String pattern = "%{KAFKALOG}"; String message = "2019-10-26 19:53:05 INFO [GroupMetadataManager brokerId=0] Removed 0 expired offsets in 0 milliseconds. (kafka.coordinator.group.GroupMetadataManager)"; Map messageMap = toMap(pattern, message); System.out.println(messageMap); System.out.println(messageMap.get("timestamp")); System.out.println(DateUtil.format(messageMap.get("timestamp").toString(), YYYY_MM_DD_HH_MM_SS)); } } ================================================ FILE: flink-learning-monitor/flink-learning-monitor-log/src/main/java/com/zhisheng/log/utils/RetryRequestFailureHandler.java ================================================ package com.zhisheng.log.utils; import lombok.extern.slf4j.Slf4j; import org.apache.flink.streaming.connectors.elasticsearch.ActionRequestFailureHandler; import org.apache.flink.streaming.connectors.elasticsearch.RequestIndexer; import org.apache.flink.util.ExceptionUtils; import org.elasticsearch.action.ActionRequest; import org.elasticsearch.common.util.concurrent.EsRejectedExecutionException; import java.io.IOException; import java.net.SocketTimeoutException; import java.util.Optional; /** * Desc: es sink Request Failure Handler * Created by zhisheng on 2019/10/21 下午3:07 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ @Slf4j public class RetryRequestFailureHandler implements ActionRequestFailureHandler { public RetryRequestFailureHandler() { } @Override public void onFailure(ActionRequest actionRequest, Throwable throwable, int i, RequestIndexer requestIndexer) throws Throwable { if (ExceptionUtils.findThrowable(throwable, EsRejectedExecutionException.class).isPresent()) { requestIndexer.add(new ActionRequest[]{actionRequest}); } else { if (ExceptionUtils.findThrowable(throwable, SocketTimeoutException.class).isPresent()) { return; } else { Optional exp = ExceptionUtils.findThrowable(throwable, IOException.class); if (exp.isPresent()) { IOException ioExp = exp.get(); if (ioExp != null && ioExp.getMessage() != null && ioExp.getMessage().contains("max retry timeout")) { log.error(ioExp.getMessage()); return; } } } throw throwable; } } } ================================================ FILE: flink-learning-monitor/flink-learning-monitor-log/src/main/resources/application.properties ================================================ kafka.brokers=xxx:9092 kafka.group.id=zhisheng-log-group kafka.zookeeper.connect=xxx:2181 metrics.topic=zhisheng logs.topic=zhisheng_log kafka.sink.brokers=localhost:9092 kafka.sink.topic=metric-test stream.parallelism=5 stream.checkpoint.interval=1000 stream.checkpoint.enable=false stream.sink.parallelism=5 elasticsearch.hosts=localhost:9200 elasticsearch.bulk.flush.max.actions=40 # \u6743\u9650 es.security.enable=false es.security.username=zhisheng es.security.password=zhisheng ================================================ FILE: flink-learning-monitor/flink-learning-monitor-log/src/main/resources/patterns/patterns ================================================ USERNAME [a-zA-Z0-9._-]+ USER %{USERNAME:UNWANTED} INT (?:[+-]?(?:[0-9]+)) BASE10NUM (?[+-]?(?:(?:[0-9]+(?:\.[0-9]+)?)|(?:\.[0-9]+))) NUMBER (?:%{BASE10NUM:UNWANTED}) BASE16NUM (?(?"(?>\\.|[^\\"]+)+"|""|(?>'(?>\\.|[^\\']+)+')|''|(?>`(?>\\.|[^\\`]+)+`)|``)) UUID [A-Fa-f0-9]{8}-(?:[A-Fa-f0-9]{4}-){3}[A-Fa-f0-9]{12} # Networking MAC (?:%{CISCOMAC:UNWANTED}|%{WINDOWSMAC:UNWANTED}|%{COMMONMAC:UNWANTED}) CISCOMAC (?:(?:[A-Fa-f0-9]{4}\.){2}[A-Fa-f0-9]{4}) WINDOWSMAC (?:(?:[A-Fa-f0-9]{2}-){5}[A-Fa-f0-9]{2}) COMMONMAC (?:(?:[A-Fa-f0-9]{2}:){5}[A-Fa-f0-9]{2}) IPV6 ((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)? IPV4 (?/(?>[\w_%!$@:.,~-]+|\\.)*)+ #UNIXPATH (?[A-Za-z]+:|\\)(?:\\[^\\?*]*)+ URIPROTO [A-Za-z]+(\+[A-Za-z+]+)? URIHOST %{IPORHOST}(?::%{POSINT:port})? # uripath comes loosely from RFC1738, but mostly from what Firefox # doesn't turn into %XX URIPATH (?:/[A-Za-z0-9$.+!*'(){},~:;=@#%_\-]*)+ #URIPARAM \?(?:[A-Za-z0-9]+(?:=(?:[^&]*))?(?:&(?:[A-Za-z0-9]+(?:=(?:[^&]*))?)?)*)? URIPARAM \?[A-Za-z0-9$.+!*'|(){},~@#%&/=:;_?\-\[\]]* URIPATHPARAM %{URIPATH}(?:%{URIPARAM})? URI %{URIPROTO}://(?:%{USER}(?::[^@]*)?@)?(?:%{URIHOST})?(?:%{URIPATHPARAM})? # Months: January, Feb, 3, 03, 12, December MONTH \b(?:Jan(?:uary)?|Feb(?:ruary)?|Mar(?:ch)?|Apr(?:il)?|May|Jun(?:e)?|Jul(?:y)?|Aug(?:ust)?|Sep(?:tember)?|Oct(?:ober)?|Nov(?:ember)?|Dec(?:ember)?)\b MONTHNUM (?:0?[1-9]|1[0-2]) MONTHNUM2 (?:0[1-9]|1[0-2]) MONTHDAY (?:(?:0[1-9])|(?:[12][0-9])|(?:3[01])|[1-9]) # Days: Monday, Tue, Thu, etc... DAY (?:Mon(?:day)?|Tue(?:sday)?|Wed(?:nesday)?|Thu(?:rsday)?|Fri(?:day)?|Sat(?:urday)?|Sun(?:day)?) # Years? YEAR (?>\d\d){1,2} # Time: HH:MM:SS #TIME \d{2}:\d{2}(?::\d{2}(?:\.\d+)?)? # I'm still on the fence about using grok to perform the time match, # since it's probably slower. # TIME %{POSINT<24}:%{POSINT<60}(?::%{POSINT<60}(?:\.%{POSINT})?)? HOUR (?:2[0123]|[01]?[0-9]) MINUTE (?:[0-5][0-9]) # '60' is a leap second in most time standards and thus is valid. SECOND (?:(?:[0-5]?[0-9]|60)(?:[:.,][0-9]+)?) TIME (?!<[0-9])%{HOUR}:%{MINUTE}(?::%{SECOND})(?![0-9]) # datestamp is YYYY/MM/DD-HH:MM:SS.UUUU (or something like it) DATE_US %{MONTHNUM}[/-]%{MONTHDAY}[/-]%{YEAR} DATE_EU %{MONTHDAY}[./-]%{MONTHNUM}[./-]%{YEAR} ISO8601_TIMEZONE (?:Z|[+-]%{HOUR}(?::?%{MINUTE})) ISO8601_SECOND (?:%{SECOND}|60) TIMESTAMP_ISO8601 %{YEAR}-%{MONTHNUM}-%{MONTHDAY}[T ]%{HOUR}:?%{MINUTE}(?::?%{SECOND})?%{ISO8601_TIMEZONE}? DATE %{DATE_US}|%{DATE_EU} DATESTAMP %{DATE}[- ]%{TIME} TZ (?:[PMCE][SD]T|UTC) DATESTAMP_RFC822 %{DAY} %{MONTH} %{MONTHDAY} %{YEAR} %{TIME} %{TZ} DATESTAMP_RFC2822 %{DAY}, %{MONTHDAY} %{MONTH} %{YEAR} %{TIME} %{ISO8601_TIMEZONE} DATESTAMP_OTHER %{DAY} %{MONTH} %{MONTHDAY} %{TIME} %{TZ} %{YEAR} DATESTAMP_EVENTLOG %{YEAR}%{MONTHNUM2}%{MONTHDAY}%{HOUR}%{MINUTE}%{SECOND} # Syslog Dates: Month Day HH:MM:SS SYSLOGTIMESTAMP %{MONTH} +%{MONTHDAY} %{TIME} PROG (?:[\w._/%-]+) SYSLOGPROG %{PROG:program}(?:\[%{POSINT:pid}\])? SYSLOGHOST %{IPORHOST} SYSLOGFACILITY <%{NONNEGINT:facility}.%{NONNEGINT:priority}> HTTPDATE %{MONTHDAY}/%{MONTH}/%{YEAR}:%{TIME} %{INT} # Shortcuts QS %{QUOTEDSTRING:UNWANTED} # Log formats SYSLOGBASE %{SYSLOGTIMESTAMP:timestamp} (?:%{SYSLOGFACILITY} )?%{SYSLOGHOST:logsource} %{SYSLOGPROG}: MESSAGESLOG %{SYSLOGBASE} %{DATA} COMMONAPACHELOG %{IPORHOST:clientip} %{USER:ident} %{USER:auth} \[%{HTTPDATE:timestamp}\] "(?:%{WORD:verb} %{NOTSPACE:request}(?: HTTP/%{NUMBER:httpversion})?|%{DATA:rawrequest})" %{NUMBER:response} (?:%{NUMBER:bytes}|-) COMBINEDAPACHELOG %{COMMONAPACHELOG} %{QS:referrer} %{QS:agent} COMMONAPACHELOG_DATATYPED %{IPORHOST:clientip} %{USER:ident;boolean} %{USER:auth} \[%{HTTPDATE:timestamp;date;dd/MMM/yyyy:HH:mm:ss Z}\] "(?:%{WORD:verb;string} %{NOTSPACE:request}(?: HTTP/%{NUMBER:httpversion;float})?|%{DATA:rawrequest})" %{NUMBER:response;int} (?:%{NUMBER:bytes;long}|-) # Log Levels LOGLEVEL ([A|a]lert|ALERT|[T|t]race|TRACE|[D|d]ebug|DEBUG|[N|n]otice|NOTICE|[I|i]nfo|INFO|[W|w]arn?(?:ing)?|WARN?(?:ING)?|[E|e]rr?(?:or)?|ERR?(?:OR)?|[C|c]rit?(?:ical)?|CRIT?(?:ICAL)?|[F|f]atal|FATAL|[S|s]evere|SEVERE|EMERG(?:ENCY)?|[Ee]merg(?:ency)?) JAVACLASS (?:[a-zA-Z$_][a-zA-Z$_0-9]*\.)*[a-zA-Z$_][a-zA-Z$_0-9]* #Space is an allowed character to match special cases like 'Native Method' or 'Unknown Source' JAVAFILE (?:[A-Za-z0-9_. -]+) #Allow special , methods JAVAMETHOD (?:(<(?:cl)?init>)|[a-zA-Z$_][a-zA-Z$_0-9]*) #Line number is optional in special cases 'Native method' or 'Unknown source' JAVASTACKTRACEPART %{SPACE}at %{JAVACLASS:class}\.%{JAVAMETHOD:method}\(%{JAVAFILE:file}(?::%{NUMBER:line})?\) # Java Logs JAVATHREAD (?:[A-Z]{2}-Processor[\d]+) JAVALOGMESSAGE (.*) # MMM dd, yyyy HH:mm:ss eg: Jan 9, 2014 7:13:13 AM CATALINA_DATESTAMP %{MONTH} %{MONTHDAY}, 20%{YEAR} %{HOUR}:?%{MINUTE}(?::?%{SECOND}) (?:AM|PM) # yyyy-MM-dd HH:mm:ss,SSS ZZZ eg: 2014-01-09 17:32:25,527 -0800 TOMCAT_DATESTAMP 20%{YEAR}-%{MONTHNUM}-%{MONTHDAY} %{HOUR}:?%{MINUTE}(?::?%{SECOND}) %{ISO8601_TIMEZONE} CATALINALOG %{CATALINA_DATESTAMP:timestamp} %{JAVACLASS:class} %{JAVALOGMESSAGE:logmessage} # 2014-01-09 20:03:28,269 -0800 | ERROR | com.example.service.ExampleService - something compeletely unexpected happened... TOMCATLOG %{TOMCAT_DATESTAMP:timestamp} \| %{LOGLEVEL:level} \| %{JAVACLASS:class} - %{JAVALOGMESSAGE:logmessage} # 2019-10-26 19:53:05,929 INFO [GroupMetadataManager brokerId=0] Removed 0 expired offsets in 0 milliseconds. (kafka.coordinator.group.GroupMetadataManager) KAFKALOG %{TIMESTAMP_ISO8601:timestamp} %{LOGLEVEL:level} %{JAVALOGMESSAGE:logmessage} ================================================ FILE: flink-learning-monitor/flink-learning-monitor-pvuv/README.md ================================================ ## Flink 监控 pv uv ================================================ FILE: flink-learning-monitor/flink-learning-monitor-pvuv/pom.xml ================================================ flink-learning-monitor com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 flink-learning-monitor-pvuv com.zhisheng.flink flink-learning-monitor-common ${project.version} org.apache.flink flink-connector-redis_2.10 1.1.5 redis.clients jedis 2.9.0 ================================================ FILE: flink-learning-monitor/flink-learning-monitor-pvuv/src/main/java/com/zhisheng/monitor/pvuv/HyperLogLogUvExample.java ================================================ package com.zhisheng.monitor.pvuv; import com.zhisheng.common.utils.GsonUtil; import com.zhisheng.monitor.pvuv.model.UserVisitWebEvent; import com.zhisheng.monitor.pvuv.utils.UvExampleUtil; import org.apache.flink.api.common.serialization.SimpleStringSchema; import org.apache.flink.api.common.typeinfo.TypeHint; import org.apache.flink.api.java.tuple.Tuple2; import org.apache.flink.streaming.api.CheckpointingMode; import org.apache.flink.streaming.api.environment.CheckpointConfig; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumer; import org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumerBase; import org.apache.flink.streaming.connectors.redis.RedisSink; import org.apache.flink.streaming.connectors.redis.common.config.FlinkJedisPoolConfig; import org.apache.flink.streaming.connectors.redis.common.mapper.RedisCommand; import org.apache.flink.streaming.connectors.redis.common.mapper.RedisCommandDescription; import org.apache.flink.streaming.connectors.redis.common.mapper.RedisMapper; import org.apache.kafka.clients.consumer.ConsumerConfig; import java.util.Properties; import java.util.concurrent.TimeUnit; /** * @author fanrui * @date 2019-10-27 01:40:50 * @desc 使用 Redis 的 HyperLogLog 数据结构来维护访问过网站各页面的 用户id */ public class HyperLogLogUvExample { public static void main(String[] args) throws Exception { final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.enableCheckpointing(TimeUnit.MINUTES.toMillis(1)); env.setParallelism(2); CheckpointConfig checkpointConf = env.getCheckpointConfig(); checkpointConf.setCheckpointingMode(CheckpointingMode.EXACTLY_ONCE); checkpointConf.enableExternalizedCheckpoints(CheckpointConfig.ExternalizedCheckpointCleanup.RETAIN_ON_CANCELLATION); Properties props = new Properties(); props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, UvExampleUtil.broker_list); props.put(ConsumerConfig.GROUP_ID_CONFIG, "app-uv-stat"); FlinkKafkaConsumerBase kafkaConsumer = new FlinkKafkaConsumer<>( UvExampleUtil.topic, new SimpleStringSchema(), props) .setStartFromLatest(); FlinkJedisPoolConfig conf = new FlinkJedisPoolConfig .Builder().setHost("192.168.30.244").build(); env.addSource(kafkaConsumer) .map(string -> { // 反序列化 JSON UserVisitWebEvent userVisitWebEvent = GsonUtil.fromJson( string, UserVisitWebEvent.class); // 生成 Redis key,格式为 日期_pageId,如: 20191026_0 String redisKey = userVisitWebEvent.getDate() + "_" + userVisitWebEvent.getPageId(); return Tuple2.of(redisKey, userVisitWebEvent.getUserId()); }) .returns(new TypeHint>() { }) .addSink(new RedisSink<>(conf, new RedisPfaddSinkMapper())); env.execute("Redis Set UV Stat"); } // 数据与 Redis key 的映射关系,并指定将数据 pfadd 到 Redis public static class RedisPfaddSinkMapper implements RedisMapper> { @Override public RedisCommandDescription getCommandDescription() { // 这里是 pfadd 操作 return new RedisCommandDescription(RedisCommand.PFADD); } @Override public String getKeyFromData(Tuple2 data) { return data.f0; } @Override public String getValueFromData(Tuple2 data) { return data.f1; } } } ================================================ FILE: flink-learning-monitor/flink-learning-monitor-pvuv/src/main/java/com/zhisheng/monitor/pvuv/MapStateUvExample.java ================================================ package com.zhisheng.monitor.pvuv; import com.zhisheng.common.utils.GsonUtil; import com.zhisheng.monitor.pvuv.model.UserVisitWebEvent; import com.zhisheng.monitor.pvuv.utils.UvExampleUtil; import org.apache.flink.api.common.functions.RichMapFunction; import org.apache.flink.api.common.serialization.SimpleStringSchema; import org.apache.flink.api.common.state.MapState; import org.apache.flink.api.common.state.MapStateDescriptor; import org.apache.flink.api.common.state.ValueState; import org.apache.flink.api.common.state.ValueStateDescriptor; import org.apache.flink.api.common.typeinfo.TypeHint; import org.apache.flink.api.common.typeinfo.TypeInformation; import org.apache.flink.api.java.tuple.Tuple2; import org.apache.flink.configuration.Configuration; import org.apache.flink.streaming.api.CheckpointingMode; import org.apache.flink.streaming.api.environment.CheckpointConfig; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumer; import org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumerBase; import org.apache.flink.streaming.connectors.redis.RedisSink; import org.apache.flink.streaming.connectors.redis.common.config.FlinkJedisPoolConfig; import org.apache.flink.streaming.connectors.redis.common.mapper.RedisCommand; import org.apache.flink.streaming.connectors.redis.common.mapper.RedisCommandDescription; import org.apache.flink.streaming.connectors.redis.common.mapper.RedisMapper; import org.apache.kafka.clients.consumer.ConsumerConfig; import java.util.Properties; import java.util.concurrent.TimeUnit; /** * @author fanrui * @date 2019-10-26 19:46:48 * @desc 使用 MapState 来维护访问过网站各页面的 用户id */ public class MapStateUvExample { public static void main(String[] args) throws Exception { final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.enableCheckpointing(TimeUnit.MINUTES.toMillis(1)); env.setParallelism(2); CheckpointConfig checkpointConf = env.getCheckpointConfig(); checkpointConf.setCheckpointingMode(CheckpointingMode.EXACTLY_ONCE); checkpointConf.enableExternalizedCheckpoints(CheckpointConfig.ExternalizedCheckpointCleanup.RETAIN_ON_CANCELLATION); Properties props = new Properties(); props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, UvExampleUtil.broker_list); props.put(ConsumerConfig.GROUP_ID_CONFIG, "app-uv-stat"); FlinkKafkaConsumerBase kafkaConsumer = new FlinkKafkaConsumer<>( UvExampleUtil.topic, new SimpleStringSchema(), props) .setStartFromGroupOffsets(); FlinkJedisPoolConfig conf = new FlinkJedisPoolConfig .Builder().setHost("192.168.30.244").build(); env.addSource(kafkaConsumer) .map(string -> GsonUtil.fromJson(string, UserVisitWebEvent.class)) // 反序列化 JSON .keyBy("date","pageId") // 按照 日期和页面 进行 keyBy .map(new RichMapFunction>() { // 存储当前 key 对应的 userId 集合 private MapState userIdState; // 存储当前 key 对应的 UV 值 private ValueState uvState; @Override public Tuple2 map(UserVisitWebEvent userVisitWebEvent) throws Exception { // 初始化 uvState if(null == uvState.value()){ uvState.update(0L); } // userIdState 中不包含当前访问的 userId,说明该用户今天还未访问过该页面 // 则将该 userId put 到 userIdState 中,并把 UV 值 +1 if(!userIdState.contains(userVisitWebEvent.getUserId())){ userIdState.put(userVisitWebEvent.getUserId(),null); uvState.update(uvState.value() + 1); } // 生成 Redis key,格式为 日期_pageId,如: 20191026_0 String redisKey = userVisitWebEvent.getDate() + "_" + userVisitWebEvent.getPageId(); System.out.println(redisKey + " ::: " + uvState.value()); return Tuple2.of(redisKey, uvState.value()); } @Override public void open(Configuration parameters) throws Exception { super.open(parameters); // 从状态中恢复 userIdState userIdState = getRuntimeContext().getMapState( new MapStateDescriptor<>("userIdState", TypeInformation.of(new TypeHint() {}), TypeInformation.of(new TypeHint() {}))); // 从状态中恢复 uvState uvState = getRuntimeContext().getState( new ValueStateDescriptor<>("uvState", TypeInformation.of(new TypeHint() {}))); } }) .addSink(new RedisSink<>(conf, new RedisSetSinkMapper())); env.execute("Redis Set UV Stat"); } // 数据与 Redis key 的映射关系,并指定将数据 set 到 Redis public static class RedisSetSinkMapper implements RedisMapper> { @Override public RedisCommandDescription getCommandDescription() { // 这里必须是 set 操作,通过 MapState 来维护用户集合, // 输出到 Redis 仅仅是为了展示结果供其他系统查询统计结果 return new RedisCommandDescription(RedisCommand.SET); } @Override public String getKeyFromData(Tuple2 data) { return data.f0; } @Override public String getValueFromData(Tuple2 data) { return data.f1.toString(); } } } ================================================ FILE: flink-learning-monitor/flink-learning-monitor-pvuv/src/main/java/com/zhisheng/monitor/pvuv/RedisSetUvExample.java ================================================ package com.zhisheng.monitor.pvuv; import com.zhisheng.common.utils.GsonUtil; import com.zhisheng.monitor.pvuv.model.UserVisitWebEvent; import com.zhisheng.monitor.pvuv.utils.UvExampleUtil; import org.apache.flink.api.common.serialization.SimpleStringSchema; import org.apache.flink.api.common.typeinfo.TypeHint; import org.apache.flink.api.java.tuple.Tuple2; import org.apache.flink.streaming.api.CheckpointingMode; import org.apache.flink.streaming.api.environment.CheckpointConfig; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumer; import org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumerBase; import org.apache.flink.streaming.connectors.redis.RedisSink; import org.apache.flink.streaming.connectors.redis.common.config.FlinkJedisPoolConfig; import org.apache.flink.streaming.connectors.redis.common.mapper.RedisCommand; import org.apache.flink.streaming.connectors.redis.common.mapper.RedisCommandDescription; import org.apache.flink.streaming.connectors.redis.common.mapper.RedisMapper; import org.apache.kafka.clients.consumer.ConsumerConfig; import java.util.Properties; import java.util.concurrent.TimeUnit; /** * @author fanrui * @date 2019-10-25 13:17:48 * @desc 使用 Redis 的 set 数据结构来维护访问过网站各页面的 用户id * 对 Redis Connector 不熟悉的请参阅 https://github.com/zhisheng17/flink-learning/blob/master/flink-learning-connectors/flink-learning-connectors-redis/src/main/java/com/zhisheng/connectors/redis/Main.java */ public class RedisSetUvExample { public static void main(String[] args) throws Exception { final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.enableCheckpointing(TimeUnit.MINUTES.toMillis(1)); env.setParallelism(2); CheckpointConfig checkpointConf = env.getCheckpointConfig(); checkpointConf.setCheckpointingMode(CheckpointingMode.EXACTLY_ONCE); checkpointConf.enableExternalizedCheckpoints(CheckpointConfig.ExternalizedCheckpointCleanup.RETAIN_ON_CANCELLATION); Properties props = new Properties(); props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, UvExampleUtil.broker_list); props.put(ConsumerConfig.GROUP_ID_CONFIG, "app-uv-stat"); FlinkKafkaConsumerBase kafkaConsumer = new FlinkKafkaConsumer<>( UvExampleUtil.topic, new SimpleStringSchema(), props) .setStartFromGroupOffsets(); FlinkJedisPoolConfig conf = new FlinkJedisPoolConfig .Builder().setHost("192.168.30.244").build(); env.addSource(kafkaConsumer) .map(string -> { // 反序列化 JSON UserVisitWebEvent userVisitWebEvent = GsonUtil.fromJson( string, UserVisitWebEvent.class); // 生成 Redis key,格式为 日期_pageId,如: 20191026_0 String redisKey = userVisitWebEvent.getDate() + "_" + userVisitWebEvent.getPageId(); return Tuple2.of(redisKey, userVisitWebEvent.getUserId()); }) .returns(new TypeHint>(){}) .addSink(new RedisSink<>(conf, new RedisSaddSinkMapper())); env.execute("Redis Set UV Stat"); } // 数据与 Redis key 的映射关系,并指定将数据 sadd 到 Redis public static class RedisSaddSinkMapper implements RedisMapper> { @Override public RedisCommandDescription getCommandDescription() { // 这里必须是 sadd 操作 return new RedisCommandDescription(RedisCommand.SADD); } @Override public String getKeyFromData(Tuple2 data) { return data.f0; } @Override public String getValueFromData(Tuple2 data) { return data.f1; } } } ================================================ FILE: flink-learning-monitor/flink-learning-monitor-pvuv/src/main/java/com/zhisheng/monitor/pvuv/model/UserVisitWebEvent.java ================================================ package com.zhisheng.monitor.pvuv.model; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; /** * @author fanrui * @date 2019-10-25 12:50:23 * @desc 用户访问网页的日志 */ @Data @NoArgsConstructor @AllArgsConstructor @Builder public class UserVisitWebEvent { /** * 日志的唯一 id */ private String id; /** * 日期,如:20191025 */ private String date; /** * 页面 id */ private Integer pageId; /** * 用户的唯一标识,用户 id */ private String userId; /** * 页面的 url */ private String url; } ================================================ FILE: flink-learning-monitor/flink-learning-monitor-pvuv/src/main/java/com/zhisheng/monitor/pvuv/utils/UvExampleUtil.java ================================================ package com.zhisheng.monitor.pvuv.utils; import com.zhisheng.common.utils.GsonUtil; import com.zhisheng.monitor.pvuv.model.UserVisitWebEvent; import org.apache.kafka.clients.producer.KafkaProducer; import org.apache.kafka.clients.producer.ProducerRecord; import org.joda.time.DateTime; import java.util.Properties; import java.util.Random; import java.util.UUID; /** * @author fanrui * @date 2019-10-25 13:21:11 * @desc 用于给统计 UV 的案例生成数据 */ public class UvExampleUtil { public static final String broker_list = "192.168.30.215:9092,192.168.30.216:9092,192.168.30.220:9092"; /** * kafka topic,Flink 程序中需要和这个统一 */ public static final String topic = "user-visit-log-topic"; public static final Random random = new Random(); public static void writeToKafka() throws InterruptedException { Properties props = new Properties(); props.put("bootstrap.servers", broker_list); props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer"); props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer"); KafkaProducer producer = new KafkaProducer(props); // 生成 0~9 的随机数做为 appId for(int i = 0; i<10; i++){ String yyyyMMdd = new DateTime(System.currentTimeMillis()).toString("yyyyMMdd"); int pageId = random.nextInt(10); // 随机生成页面 id int userId = random.nextInt(100); // 随机生成用户 id UserVisitWebEvent userVisitWebEvent = UserVisitWebEvent.builder() .id(UUID.randomUUID().toString()) // 日志的唯一 id .date(yyyyMMdd) // 日期 .pageId(pageId) // 页面 id .userId(Integer.toString(userId)) // 用户 id .url("url/" + pageId) // 页面的 url .build(); // 对象序列化为 JSON 发送到 Kafka ProducerRecord record = new ProducerRecord(topic, null, null, GsonUtil.toJson(userVisitWebEvent)); producer.send(record); System.out.println("发送数据: " + GsonUtil.toJson(userVisitWebEvent)); } producer.flush(); } public static void main(String[] args) throws InterruptedException { while (true) { Thread.sleep(100); writeToKafka(); } } } ================================================ FILE: flink-learning-monitor/flink-learning-monitor-storage/README.md ================================================ ## Flink 监控数据存储 ================================================ FILE: flink-learning-monitor/flink-learning-monitor-storage/flink_log_2es.sql ================================================ CREATE TABLE yarn_flink_warn_logs ( `source` STRING, `id` STRING, `timestamp` BIGINT, `content` STRING, `tags` ROW ) WITH ( 'connector' = 'kafka', 'topic' = 'yarn_flink_log', -- 'scan.startup.mode' = 'latest-offset', 'properties.bootstrap.servers' = 'logs-kafka1.xxx:9092,logs-kafka2.xxx:9092,logs-kafka3.xxx:9092', 'properties.group.id' = 'flink_warn_logs', 'scan.topic-partition-discovery.interval' = '10000 ms', 'format' = 'json', 'json.fail-on-missing-field' = 'false', 'json.ignore-parse-errors' = 'true' ); CREATE TABLE yarn_flink_warn_logs_es ( `source` STRING, `logTime` BIGINT, `log_time` TIMESTAMP(3), `content` STRING, `tags` ROW ) WITH ( 'connector' = 'elasticsearch-universal', 'hosts' = 'http://log-xxx-xxx.cn:9200', 'index' = 'yarn_flink_warn_logs-{log_time|yyyy.MM.dd}', -- 'socket.timeout' = '120', -- 'connection.timeout' = '120', -- 'connection.request-timeout' = '120', 'sink.parallelism' = '10', 'sink.bulk-flush.max-actions' = '2000', 'sink.bulk-flush.max-size' = '5MB', 'sink.bulk-flush.interval' = '10' ); insert into yarn_flink_warn_logs_es select `source`, `timestamp` as logTime, TO_TIMESTAMP(FROM_UNIXTIME(`timestamp` / 1000,'yyyy-MM-dd HH:mm:ss')) AS log_time, content, tags from yarn_flink_warn_logs where ( tags.level = 'WARN' or tags.level = 'ERROR' or tags.container_type = 'jobmanager' ) and ( REGEXP(tags.class_name, 'xxx') is false ) and (REGEXP(content, 'rror') is true or REGEXP(content, 'xception') is true or REGEXP(content, 'OOMKilled') is true) and ( REGEXP(content, 'No JAAS configuration') is false ) and ( REGEXP(content, 'error while sniffing nodes') is false ) and ( REGEXP(content, 'document_missing_exception') is false ) and ( REGEXP(content, 'pipeline.classpaths') is false ) and ( REGEXP(content, 'InstanceAlreadyExistsException') is false ) and ( REGEXP(content, 'metrics-flink-jobs') is false ) and ( REGEXP(content, 'exceeded the 80 characters length limit and was truncated') is false ) and ( REGEXP(content, 'Error while reporting metrics') is false ) and ( REGEXP(content, 'flink-conf.yaml') is false ) and ( REGEXP(content, 'Loading configuration') is false ) and ( REGEXP(content, 'PushGateway') is false ) ; ================================================ FILE: flink-learning-monitor/flink-learning-monitor-storage/flink_metrics_2es.sql ================================================ -- 打印 flink 1.16 任务的消费延迟 metrics 监控数据 CREATE TABLE metrics_yarn_flink_jobs ( name STRING, fields Map, tags Row ) WITH ( 'connector' = 'kafka', 'topic' = 'metrics-flink-jobs', 'properties.bootstrap.servers' = 'logs-kafka1.xxx:9092,logs-kafka2.xxx:9092,logs-kafka3.xxx:9092', 'properties.group.id' = 'test', 'format' = 'json' ); CREATE TABLE flink_jobs_metrics ( name STRING, fields Map, tags Row ) WITH ( 'connector' = 'print' ); insert into flink_jobs_metrics select name, fields, tags from metrics_yarn_flink_jobs ; ================================================ FILE: flink-learning-monitor/flink-learning-monitor-storage/pom.xml ================================================ flink-learning-monitor com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 flink-learning-monitor-storage com.zhisheng.flink flink-learning-monitor-common ${project.version} ================================================ FILE: flink-learning-monitor/flink_monitor_measurements.md ================================================ ### JobManager 监控指标 ``` jobmanager_Status_JVM_CPU_Load jobmanager_Status_JVM_CPU_Time jobmanager_Status_JVM_ClassLoader_ClassesLoaded jobmanager_Status_JVM_ClassLoader_ClassesUnloaded jobmanager_Status_JVM_GarbageCollector_PS_MarkSweep_Count jobmanager_Status_JVM_GarbageCollector_PS_MarkSweep_Time jobmanager_Status_JVM_GarbageCollector_PS_Scavenge_Count jobmanager_Status_JVM_GarbageCollector_PS_Scavenge_Time jobmanager_Status_JVM_Memory_Direct_Count jobmanager_Status_JVM_Memory_Direct_MemoryUsed jobmanager_Status_JVM_Memory_Direct_TotalCapacity jobmanager_Status_JVM_Memory_Heap_Committed jobmanager_Status_JVM_Memory_Heap_Max jobmanager_Status_JVM_Memory_Heap_Used jobmanager_Status_JVM_Memory_Mapped_Count jobmanager_Status_JVM_Memory_Mapped_MemoryUsed jobmanager_Status_JVM_Memory_Mapped_TotalCapacity jobmanager_Status_JVM_Memory_NonHeap_Committed jobmanager_Status_JVM_Memory_NonHeap_Max jobmanager_Status_JVM_Memory_NonHeap_Used jobmanager_Status_JVM_Threads_Count jobmanager_job_downtime jobmanager_job_fullRestarts jobmanager_job_lastCheckpointAlignmentBuffered jobmanager_job_lastCheckpointDuration jobmanager_job_lastCheckpointExternalPath jobmanager_job_lastCheckpointRestoreTimestamp jobmanager_job_lastCheckpointSize jobmanager_job_numberOfCompletedCheckpoints jobmanager_job_numberOfFailedCheckpoints jobmanager_job_numberOfInProgressCheckpoints jobmanager_job_restartingTime jobmanager_job_totalNumberOfCheckpoints jobmanager_job_uptime jobmanager_numRegisteredTaskManagers jobmanager_numRunningJobs jobmanager_taskSlotsAvailable jobmanager_taskSlotsTotal ``` ### TaskManager 监控指标 ``` taskmanager_Status_JVM_CPU_Load taskmanager_Status_JVM_CPU_Time taskmanager_Status_JVM_ClassLoader_ClassesLoaded taskmanager_Status_JVM_ClassLoader_ClassesUnloaded taskmanager_Status_JVM_GarbageCollector_G1_Old_Generation_Count taskmanager_Status_JVM_GarbageCollector_G1_Old_Generation_Time taskmanager_Status_JVM_GarbageCollector_G1_Young_Generation_Count taskmanager_Status_JVM_GarbageCollector_G1_Young_Generation_Time taskmanager_Status_JVM_Memory_Direct_Count taskmanager_Status_JVM_Memory_Direct_MemoryUsed taskmanager_Status_JVM_Memory_Direct_TotalCapacity taskmanager_Status_JVM_Memory_Heap_Committed taskmanager_Status_JVM_Memory_Heap_Max taskmanager_Status_JVM_Memory_Heap_Used taskmanager_Status_JVM_Memory_Mapped_Count taskmanager_Status_JVM_Memory_Mapped_MemoryUsed taskmanager_Status_JVM_Memory_Mapped_TotalCapacity taskmanager_Status_JVM_Memory_NonHeap_Committed taskmanager_Status_JVM_Memory_NonHeap_Max taskmanager_Status_JVM_Memory_NonHeap_Used taskmanager_Status_JVM_Threads_Count taskmanager_Status_Network_AvailableMemorySegments taskmanager_Status_Network_TotalMemorySegments taskmanager_Status_Shuffle_Netty_AvailableMemorySegments taskmanager_Status_Shuffle_Netty_TotalMemorySegments ``` ### Job 监控指标 ``` taskmanager_job_task_Shuffle_Netty_Input_Buffers_outPoolUsage taskmanager_job_task_Shuffle_Netty_Input_Buffers_outputQueueLength taskmanager_job_task_Shuffle_Netty_Output_Buffers_inPoolUsage taskmanager_job_task_Shuffle_Netty_Output_Buffers_inputExclusiveBuffersUsage taskmanager_job_task_Shuffle_Netty_Output_Buffers_inputFloatingBuffersUsage taskmanager_job_task_Shuffle_Netty_Output_Buffers_inputQueueLength taskmanager_job_task_Shuffle_Netty_Output_numBuffersInLocal taskmanager_job_task_Shuffle_Netty_Output_numBuffersInLocalPerSecond taskmanager_job_task_Shuffle_Netty_Output_numBuffersInRemote taskmanager_job_task_Shuffle_Netty_Output_numBuffersInRemotePerSecond taskmanager_job_task_Shuffle_Netty_Output_numBytesInLocal taskmanager_job_task_Shuffle_Netty_Output_numBytesInLocalPerSecond taskmanager_job_task_Shuffle_Netty_Output_numBytesInRemote taskmanager_job_task_Shuffle_Netty_Output_numBytesInRemotePerSecond taskmanager_job_task_buffers_inPoolUsage taskmanager_job_task_buffers_inputExclusiveBuffersUsage taskmanager_job_task_buffers_inputFloatingBuffersUsage taskmanager_job_task_buffers_inputQueueLength taskmanager_job_task_buffers_outPoolUsage taskmanager_job_task_buffers_outputQueueLength taskmanager_job_task_checkpointAlignmentTime taskmanager_job_task_currentInputWatermark taskmanager_job_task_numBuffersInLocal taskmanager_job_task_numBuffersInLocalPerSecond taskmanager_job_task_numBuffersInRemote taskmanager_job_task_numBuffersInRemotePerSecond taskmanager_job_task_numBuffersOut taskmanager_job_task_numBuffersOutPerSecond taskmanager_job_task_numBytesIn taskmanager_job_task_numBytesInLocal taskmanager_job_task_numBytesInLocalPerSecond taskmanager_job_task_numBytesInPerSecond taskmanager_job_task_numBytesInRemote taskmanager_job_task_numBytesInRemotePerSecond taskmanager_job_task_numBytesOut taskmanager_job_task_numBytesOutPerSecond taskmanager_job_task_numRecordsIn taskmanager_job_task_numRecordsInPerSecond taskmanager_job_task_numRecordsOut taskmanager_job_task_numRecordsOutPerSecond taskmanager_job_task_operator_currentInputWatermark taskmanager_job_task_operator_currentOutputWatermark taskmanager_job_task_operator_numLateRecordsDropped taskmanager_job_task_operator_numRecordsIn taskmanager_job_task_operator_numRecordsInPerSecond taskmanager_job_task_operator_numRecordsOut taskmanager_job_task_operator_numRecordsOutPerSecond ``` ================================================ FILE: flink-learning-monitor/pom.xml ================================================ flink-learning com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 flink-learning-monitor pom flink-learning-monitor-collector flink-learning-monitor-alert flink-learning-monitor-storage flink-learning-monitor-common flink-learning-monitor-pvuv flink-learning-monitor-dashboard flink-learning-monitor-log ================================================ FILE: flink-learning-project/README.md ================================================ ## Flink 项目 + [基于 Apache Flink 的日志实时处理系统](./flink-learning-project-log) + [基于 Apache Flink 的百亿数据去重实践](./flink-learning-project-deduplication) + [基于 Apache Flink 的监控告警系统](./flink-learning-project-monitor-alert) + [基于 Apache Flink 的实时风控系统](./flink-learning-project-risk-management) + [基于 Apache Flink 的实时大屏系统](./flink-learning-project-monitor-dashboard) + [Apache Flink 实时作业脚手架](./flink-learning-project-flink-job-scaffold) + [基于 Apache Flink 的实时数仓建设](./flink-learning-project-real-time-data-warehouse) + [基于 Apache Flink 的实时计算平台建设](./flink-learning-project-real-time-computing-platform) ================================================ FILE: flink-learning-project/flink-learning-project-common/pom.xml ================================================ flink-learning-project com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 flink-learning-project-common com.zhisheng.flink flink-learning-common ${project.version} ================================================ FILE: flink-learning-project/flink-learning-project-common/src/main/java/com/zhisheng/project/common/constant/ProjectConstants.java ================================================ package com.zhisheng.project.common.constant; /** * 项目模块共享常量 * * @author zhisheng */ public class ProjectConstants { private ProjectConstants() { } // Kafka topic 常量 public static final String TOPIC_LOG = "project-log-topic"; public static final String TOPIC_METRIC = "project-metric-topic"; public static final String TOPIC_ALERT = "project-alert-topic"; public static final String TOPIC_TRANSACTION = "project-transaction-topic"; public static final String TOPIC_PAGE_ACCESS = "project-page-access-topic"; public static final String TOPIC_ORDER = "project-order-topic"; public static final String TOPIC_ORDER_DETAIL = "project-order-detail-topic"; // DWD 层 topic public static final String TOPIC_DWD_ORDER_DETAIL = "dwd-order-detail-topic"; // DWS 层 topic public static final String TOPIC_DWS_ORDER_STATS = "dws-order-stats-topic"; // Kafka 默认配置 public static final String DEFAULT_BROKER_LIST = "localhost:9092"; public static final String DEFAULT_GROUP_ID = "flink-learning-project"; // 告警级别 public static final String ALERT_LEVEL_INFO = "INFO"; public static final String ALERT_LEVEL_WARNING = "WARNING"; public static final String ALERT_LEVEL_CRITICAL = "CRITICAL"; } ================================================ FILE: flink-learning-project/flink-learning-project-common/src/main/java/com/zhisheng/project/common/model/AlertEvent.java ================================================ package com.zhisheng.project.common.model; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; /** * 告警事件模型 * * @author zhisheng */ @Data @Builder @AllArgsConstructor @NoArgsConstructor public class AlertEvent { /** 告警 ID */ private String alertId; /** 告警级别:INFO, WARNING, CRITICAL */ private String level; /** 告警规则名称 */ private String ruleName; /** 告警消息 */ private String message; /** 触发告警的指标名称 */ private String metricName; /** 触发告警的指标值 */ private Double metricValue; /** 告警阈值 */ private Double threshold; /** 告警时间戳 */ private Long timestamp; /** 告警来源主机 */ private String host; } ================================================ FILE: flink-learning-project/flink-learning-project-common/src/main/java/com/zhisheng/project/common/model/AlertRule.java ================================================ package com.zhisheng.project.common.model; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; /** * 告警规则模型 * 用于动态告警规则的广播状态 * * @author zhisheng */ @Data @Builder @AllArgsConstructor @NoArgsConstructor public class AlertRule { /** 规则 ID */ private String ruleId; /** 规则名称 */ private String ruleName; /** 监控指标名称 */ private String metricName; /** 比较操作符:GT(大于), LT(小于), GTE(大于等于), LTE(小于等于) */ private String operator; /** 告警阈值 */ private Double threshold; /** 告警级别 */ private String alertLevel; /** 规则是否启用 */ private Boolean enabled; /** 窗口大小(秒) */ private Integer windowSizeSeconds; } ================================================ FILE: flink-learning-project/flink-learning-project-common/src/main/java/com/zhisheng/project/common/model/PageAccessEvent.java ================================================ package com.zhisheng.project.common.model; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; /** * 页面访问事件模型 * 用于实时大屏展示 PV/UV 统计 * * @author zhisheng */ @Data @Builder @AllArgsConstructor @NoArgsConstructor public class PageAccessEvent { /** 事件 ID */ private String eventId; /** 用户 ID */ private String userId; /** 页面 URL */ private String pageUrl; /** 页面标题 */ private String pageTitle; /** 页面类别,如:首页、商品详情页、购物车 */ private String pageCategory; /** 来源渠道:PC, APP, H5 */ private String channel; /** 停留时长(毫秒) */ private Long stayDuration; /** 访问时间戳 */ private Long timestamp; /** 用户设备类型 */ private String deviceType; /** 用户所在地区 */ private String region; } ================================================ FILE: flink-learning-project/flink-learning-project-common/src/main/java/com/zhisheng/project/common/model/ServerMetric.java ================================================ package com.zhisheng.project.common.model; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; /** * 服务器指标模型 * 用于监控告警系统和实时大屏 * * @author zhisheng */ @Data @Builder @AllArgsConstructor @NoArgsConstructor public class ServerMetric { /** 指标名称,如:cpu_usage, memory_usage, disk_io, network_in */ private String metricName; /** 指标值 */ private Double value; /** 主机名 */ private String host; /** 应用名称 */ private String application; /** 采集时间戳 */ private Long timestamp; /** 标签,可用于多维分析 */ private String tags; } ================================================ FILE: flink-learning-project/flink-learning-project-common/src/main/java/com/zhisheng/project/common/model/TransactionEvent.java ================================================ package com.zhisheng.project.common.model; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; /** * 交易事件模型 * 用于实时风控系统的交易数据 * * @author zhisheng */ @Data @Builder @AllArgsConstructor @NoArgsConstructor public class TransactionEvent { /** 交易 ID */ private String transactionId; /** 用户 ID */ private String userId; /** 交易金额 */ private Double amount; /** 交易类型:TRANSFER(转账), PAYMENT(支付), WITHDRAW(提现) */ private String transactionType; /** 来源账户 */ private String sourceAccount; /** 目标账户 */ private String targetAccount; /** 交易发生的 IP 地址 */ private String ipAddress; /** 交易发生的城市 */ private String city; /** 交易时间戳 */ private Long timestamp; /** 设备指纹 */ private String deviceFingerprint; } ================================================ FILE: flink-learning-project/flink-learning-project-common/src/main/java/com/zhisheng/project/common/utils/ProjectKafkaUtil.java ================================================ package com.zhisheng.project.common.utils; import org.apache.flink.api.common.serialization.SimpleStringSchema; import org.apache.flink.connector.kafka.source.KafkaSource; import org.apache.flink.connector.kafka.source.enumerator.initializer.OffsetsInitializer; import org.apache.flink.connector.kafka.sink.KafkaSink; import org.apache.flink.connector.kafka.sink.KafkaRecordSerializationSchema; import com.zhisheng.project.common.constant.ProjectConstants; /** * 项目 Kafka 工具类 * 封装 Kafka Source 和 Sink 的创建逻辑,简化各模块的 Kafka 接入 * * @author zhisheng */ public class ProjectKafkaUtil { private ProjectKafkaUtil() { } /** * 创建 Kafka String Source * * @param topic 消费的 topic * @param groupId 消费者组 ID * @param brokers Kafka broker 地址 * @return KafkaSource 实例 */ public static KafkaSource buildKafkaStringSource(String topic, String groupId, String brokers) { return KafkaSource.builder() .setBootstrapServers(brokers) .setTopics(topic) .setGroupId(groupId) .setStartingOffsets(OffsetsInitializer.latest()) .setValueOnlyDeserializer(new SimpleStringSchema()) .build(); } /** * 使用默认配置创建 Kafka String Source */ public static KafkaSource buildKafkaStringSource(String topic, String groupId) { return buildKafkaStringSource(topic, groupId, ProjectConstants.DEFAULT_BROKER_LIST); } /** * 创建 Kafka String Sink * * @param topic 写入的 topic * @param brokers Kafka broker 地址 * @return KafkaSink 实例 */ public static KafkaSink buildKafkaStringSink(String topic, String brokers) { return KafkaSink.builder() .setBootstrapServers(brokers) .setRecordSerializer(KafkaRecordSerializationSchema.builder() .setTopic(topic) .setValueSerializationSchema(new SimpleStringSchema()) .build()) .build(); } /** * 使用默认配置创建 Kafka String Sink */ public static KafkaSink buildKafkaStringSink(String topic) { return buildKafkaStringSink(topic, ProjectConstants.DEFAULT_BROKER_LIST); } } ================================================ FILE: flink-learning-project/flink-learning-project-deduplication/README.md ================================================ ### flink-learning-project-deduplication 基于 Flink 的百亿数据去重实践 ================================================ FILE: flink-learning-project/flink-learning-project-deduplication/pom.xml ================================================ flink-learning-project com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 flink-learning-project-deduplication com.zhisheng.flink flink-learning-project-common ${project.version} org.apache.flink flink-connector-redis_2.10 1.1.5 redis.clients jedis 2.9.0 org.apache.maven.plugins maven-shade-plugin 3.1.0 false package shade com.zhisheng.project.deduplication.KeyedStateDeduplication reference.conf *:*:*:* META-INF/*.SF META-INF/*.DSA META-INF/*.RSA ================================================ FILE: flink-learning-project/flink-learning-project-deduplication/src/main/java/com/zhisheng/project/deduplication/KeyedStateDeduplication.java ================================================ package com.zhisheng.project.deduplication; import com.zhisheng.common.utils.GsonUtil; import com.zhisheng.project.deduplication.model.UserVisitWebEvent; import com.zhisheng.project.deduplication.utils.DeduplicationExampleUtil; import org.apache.flink.api.common.serialization.SimpleStringSchema; import org.apache.flink.api.common.state.StateTtlConfig; import org.apache.flink.api.common.state.ValueState; import org.apache.flink.api.common.state.ValueStateDescriptor; import org.apache.flink.api.common.time.Time; import org.apache.flink.api.common.typeinfo.TypeHint; import org.apache.flink.api.common.typeinfo.TypeInformation; import org.apache.flink.api.java.functions.KeySelector; import org.apache.flink.configuration.Configuration; import org.apache.flink.contrib.streaming.state.PredefinedOptions; import org.apache.flink.contrib.streaming.state.RocksDBStateBackend; import org.apache.flink.streaming.api.CheckpointingMode; import org.apache.flink.streaming.api.environment.CheckpointConfig; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.api.functions.sink.RichSinkFunction; import org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumer; import org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumerBase; import org.apache.kafka.clients.consumer.ConsumerConfig; import java.util.Properties; import java.util.concurrent.TimeUnit; /** * @author fanrui * @date 2019-11-01 01:34:13 */ public class KeyedStateDeduplication { public static void main(String[] args) throws Exception { StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.setParallelism(6); // 使用 RocksDBStateBackend 做为状态后端,并开启增量 Checkpoint RocksDBStateBackend rocksDBStateBackend = new RocksDBStateBackend( "hdfs:///flink/checkpoints", true); rocksDBStateBackend.setNumberOfTransferingThreads(3); // 设置为机械硬盘+内存模式,强烈建议为 RocksDB 配备 SSD rocksDBStateBackend.setPredefinedOptions( PredefinedOptions.SPINNING_DISK_OPTIMIZED_HIGH_MEM); env.setStateBackend(rocksDBStateBackend); // Checkpoint 间隔为 10 分钟 env.enableCheckpointing(TimeUnit.MINUTES.toMillis(10)); // 配置 Checkpoint CheckpointConfig checkpointConf = env.getCheckpointConfig(); checkpointConf.setCheckpointingMode(CheckpointingMode.EXACTLY_ONCE); checkpointConf.setMinPauseBetweenCheckpoints(TimeUnit.MINUTES.toMillis(8)); checkpointConf.setCheckpointTimeout(TimeUnit.MINUTES.toMillis(20)); checkpointConf.enableExternalizedCheckpoints( CheckpointConfig.ExternalizedCheckpointCleanup.RETAIN_ON_CANCELLATION); // Kafka Consumer 配置 Properties props = new Properties(); props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, DeduplicationExampleUtil.broker_list); props.put(ConsumerConfig.GROUP_ID_CONFIG, "keyed-state-deduplication"); FlinkKafkaConsumerBase kafkaConsumer = new FlinkKafkaConsumer<>( DeduplicationExampleUtil.topic, new SimpleStringSchema(), props) .setStartFromGroupOffsets(); env.addSource(kafkaConsumer) .map(log -> GsonUtil.fromJson(log, UserVisitWebEvent.class)) // 反序列化 JSON .keyBy((KeySelector) UserVisitWebEvent::getId) .addSink(new KeyedStateSink()); env.execute("KeyedStateDeduplication"); } // 用来维护实现百亿去重逻辑的算子 public static class KeyedStateSink extends RichSinkFunction { // 使用该 ValueState 来标识当前 Key 是否之前存在过 private ValueState isExist; @Override public void open(Configuration parameters) throws Exception { super.open(parameters); ValueStateDescriptor keyedStateDuplicated = new ValueStateDescriptor<>("KeyedStateDeduplication", TypeInformation.of(new TypeHint() { })); // 状态 TTL 相关配置,过期时间设定为 36 小时 StateTtlConfig ttlConfig = StateTtlConfig .newBuilder(Time.hours(36)) .setUpdateType(StateTtlConfig.UpdateType.OnCreateAndWrite) .setStateVisibility( StateTtlConfig.StateVisibility.NeverReturnExpired) .cleanupInRocksdbCompactFilter(50000000L) .build(); // 开启 TTL keyedStateDuplicated.enableTimeToLive(ttlConfig); // 从状态后端恢复状态 isExist = getRuntimeContext().getState(keyedStateDuplicated); } @Override public void invoke(UserVisitWebEvent value, Context context) throws Exception { // 当前 key 第一次出现时,isExist.value() 会返回 null // key 第一次出现,说明当前 key 在之前没有被处理过, // 此时应该执行正常处理代码的逻辑,并给状态 isExist 赋值,标识当前 key 已经处理过了, // 下次再有相同的主键 时,isExist.value() 就不会为 null 了 if (null == isExist.value()) { // ... 这里执行代码处理的逻辑 // 执行完处理逻辑后,更新状态值 isExist.update(true); } else { // 如果 isExist.value() 不为 null,表示当前 key 在之前已经被处理过了, // 所以当前数据应该被过滤 } } } } ================================================ FILE: flink-learning-project/flink-learning-project-deduplication/src/main/java/com/zhisheng/project/deduplication/TuningKeyedStateDeduplication.java ================================================ package com.zhisheng.project.deduplication; import com.google.common.hash.Hashing; import com.zhisheng.common.utils.GsonUtil; import com.zhisheng.project.deduplication.model.UserVisitWebEvent; import com.zhisheng.project.deduplication.utils.DeduplicationExampleUtil; import org.apache.flink.api.common.serialization.SimpleStringSchema; import org.apache.flink.api.java.functions.KeySelector; import org.apache.flink.contrib.streaming.state.PredefinedOptions; import org.apache.flink.contrib.streaming.state.RocksDBStateBackend; import org.apache.flink.streaming.api.CheckpointingMode; import org.apache.flink.streaming.api.environment.CheckpointConfig; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumer; import org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumerBase; import org.apache.kafka.clients.consumer.ConsumerConfig; import java.util.Properties; import java.util.concurrent.TimeUnit; /** * @author fanrui * @date 2019-11-01 01:34:13 */ public class TuningKeyedStateDeduplication { private static boolean enableIncrementalCheckpointing = true; private static int numberOfTransferingThreads = 3; public static void main(String[] args) throws Exception{ final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.enableCheckpointing(TimeUnit.MINUTES.toMillis(10)); env.setParallelism(6); RocksDBStateBackend rocksDBStateBackend = new RocksDBStateBackend("hdfs:///flink/checkpoints", enableIncrementalCheckpointing); rocksDBStateBackend.setNumberOfTransferingThreads(numberOfTransferingThreads); rocksDBStateBackend.setPredefinedOptions(PredefinedOptions.SPINNING_DISK_OPTIMIZED_HIGH_MEM); env.setStateBackend(rocksDBStateBackend); CheckpointConfig checkpointConf = env.getCheckpointConfig(); checkpointConf.setCheckpointingMode(CheckpointingMode.EXACTLY_ONCE); checkpointConf.setMinPauseBetweenCheckpoints(TimeUnit.MINUTES.toMillis(8)); checkpointConf.setCheckpointTimeout(TimeUnit.MINUTES.toMillis(20)); checkpointConf.enableExternalizedCheckpoints(CheckpointConfig.ExternalizedCheckpointCleanup.RETAIN_ON_CANCELLATION); Properties props = new Properties(); props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, DeduplicationExampleUtil.broker_list); props.put(ConsumerConfig.GROUP_ID_CONFIG, "keyed-state-deduplication"); FlinkKafkaConsumerBase kafkaConsumer = new FlinkKafkaConsumer<>( DeduplicationExampleUtil.topic, new SimpleStringSchema(), props) .setStartFromLatest(); env.addSource(kafkaConsumer) .map(string -> GsonUtil.fromJson(string, UserVisitWebEvent.class)) // 反序列化 JSON // 这里将日志的主键 id 通过 murmur3_128 hash 后,将生成 long 类型数据当做 key .keyBy((KeySelector) log -> Hashing.murmur3_128(5).hashUnencodedChars(log.getId()).asLong()) .addSink(new KeyedStateDeduplication.KeyedStateSink()); env.execute("TuningKeyedStateDeduplication"); } } ================================================ FILE: flink-learning-project/flink-learning-project-deduplication/src/main/java/com/zhisheng/project/deduplication/model/UserVisitWebEvent.java ================================================ package com.zhisheng.project.deduplication.model; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; /** * @author fanrui * @date 2019-11-01 01:24:50 * @desc 用户访问网页的日志 */ @Data @NoArgsConstructor @AllArgsConstructor @Builder public class UserVisitWebEvent { /** * 日志的唯一 id */ private String id; /** * 日期,如:20191025 */ private String date; /** * 页面 id */ private Integer pageId; /** * 用户的唯一标识,用户 id */ private String userId; /** * 页面的 url */ private String url; } ================================================ FILE: flink-learning-project/flink-learning-project-deduplication/src/main/java/com/zhisheng/project/deduplication/utils/DeduplicationExampleUtil.java ================================================ package com.zhisheng.project.deduplication.utils; import com.zhisheng.common.utils.GsonUtil; import com.zhisheng.project.deduplication.model.UserVisitWebEvent; import org.apache.kafka.clients.producer.KafkaProducer; import org.apache.kafka.clients.producer.ProducerRecord; import org.joda.time.DateTime; import java.util.Properties; import java.util.Random; import java.util.UUID; /** * @author fanrui * @date 2019-11-01 01:26:17 * @desc 用于给去重的案例生成数据 */ public class DeduplicationExampleUtil { public static final String broker_list = "192.168.30.215:9092,192.168.30.216:9092,192.168.30.220:9092"; /** * kafka topic,Flink 程序中需要和这个统一 */ public static final String topic = "user-visit-log-topic"; public static final Random random = new Random(); public static void writeToKafka() throws InterruptedException { Properties props = new Properties(); props.put("bootstrap.servers", broker_list); props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer"); props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer"); KafkaProducer producer = new KafkaProducer(props); // 生成 0~9 的随机数做为 appId for(int i = 0; i<10; i++){ String yyyyMMdd = new DateTime(System.currentTimeMillis()).toString("yyyyMMdd"); int pageId = random.nextInt(10); // 随机生成页面 id int userId = random.nextInt(100); // 随机生成用户 id UserVisitWebEvent userVisitWebEvent = UserVisitWebEvent.builder() .id(UUID.randomUUID().toString()) // 日志的唯一 id .date(yyyyMMdd) // 日期 .pageId(pageId) // 页面 id .userId(Integer.toString(userId)) // 用户 id .url("url/" + pageId) // 页面的 url .build(); // 对象序列化为 JSON 发送到 Kafka ProducerRecord record = new ProducerRecord(topic, null, null, GsonUtil.toJson(userVisitWebEvent)); producer.send(record); System.out.println("发送数据: " + GsonUtil.toJson(userVisitWebEvent)); } producer.flush(); } public static void main(String[] args) throws InterruptedException { while (true) { Thread.sleep(100); writeToKafka(); } } } ================================================ FILE: flink-learning-project/flink-learning-project-flink-job-scaffold/README.md ================================================ ## Apache Flink 实时作业脚手架 这是一个生产级别的 Flink 作业模板,展示了 Flink 作业开发中的常见最佳实践。 ### 核心功能 - **Checkpoint 配置**:Exactly-Once 语义、定时 Checkpoint、取消时保留 - **重启策略**:固定延迟重启策略,确保作业容错 - **Watermark 策略**:处理乱序数据,支持 Idle Source 检测 - **Keyed State**:使用 ValueState 实现数据去重 - **窗口聚合**:自定义 AggregateFunction 进行增量聚合 - **Kafka Source**:从 Kafka 消费数据的标准模式 ### 使用方式 1. 复制本模板到新项目中 2. 根据业务需求修改数据模型(替换 `ServerMetric`) 3. 实现自己的处理逻辑(替换 `DeduplicateFunction`) 4. 配置合适的输出 Sink(替换 `print`) 5. 调整 Checkpoint、并行度等参数 ### 涉及的 Flink 知识点 | 知识点 | 说明 | |--------|------| | StreamExecutionEnvironment | Flink 流处理执行环境 | | CheckpointConfig | Checkpoint 配置(间隔、模式、超时) | | RestartStrategy | 重启策略(固定延迟、指数退避) | | WatermarkStrategy | 水位线策略(乱序处理、Idle Source) | | ValueState | 键控状态,存储单个值 | | AggregateFunction | 增量聚合函数(高效窗口计算) | | TumblingEventTimeWindow | 基于事件时间的滚动窗口 | | KafkaSource | 新版 Kafka Source API | ================================================ FILE: flink-learning-project/flink-learning-project-flink-job-scaffold/pom.xml ================================================ flink-learning-project com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 flink-learning-project-flink-job-scaffold com.zhisheng.flink flink-learning-project-common ${project.version} ================================================ FILE: flink-learning-project/flink-learning-project-flink-job-scaffold/src/main/java/com/zhisheng/project/scaffold/FlinkJobScaffold.java ================================================ package com.zhisheng.project.scaffold; import com.zhisheng.common.utils.GsonUtil; import com.zhisheng.project.common.constant.ProjectConstants; import com.zhisheng.project.common.model.ServerMetric; import com.zhisheng.project.common.utils.ProjectKafkaUtil; import org.apache.flink.api.common.eventtime.SerializableTimestampAssigner; import org.apache.flink.api.common.eventtime.WatermarkStrategy; import org.apache.flink.api.common.functions.AggregateFunction; import org.apache.flink.api.common.functions.RichFlatMapFunction; import org.apache.flink.api.common.state.ValueState; import org.apache.flink.api.common.state.ValueStateDescriptor; import org.apache.flink.api.java.tuple.Tuple2; import org.apache.flink.configuration.Configuration; import org.apache.flink.connector.kafka.source.KafkaSource; import org.apache.flink.streaming.api.CheckpointingMode; import org.apache.flink.streaming.api.datastream.DataStream; import org.apache.flink.streaming.api.environment.CheckpointConfig; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.api.windowing.assigners.TumblingEventTimeWindows; import org.apache.flink.streaming.api.windowing.time.Time; import org.apache.flink.util.Collector; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.time.Duration; import java.util.concurrent.TimeUnit; /** * Apache Flink 实时作业脚手架 * *

本作业展示了生产环境 Flink 作业的最佳实践,包含: *

    *
  • Checkpoint 配置(Exactly-Once、间隔、超时、保留策略)
  • *
  • 重启策略(固定延迟重启、指数退避)
  • *
  • Watermark 策略(处理乱序数据、允许延迟)
  • *
  • Keyed State 使用(ValueState 实现去重/缓存)
  • *
  • 自定义 AggregateFunction(增量聚合计算)
  • *
  • 窗口操作(Tumbling Window + Event Time)
  • *
  • 从 Kafka 消费数据
  • *
* *

使用方式:将此类作为新 Flink 作业的模板,根据业务需求修改数据源、处理逻辑和输出 * * @author zhisheng */ public class FlinkJobScaffold { private static final Logger LOG = LoggerFactory.getLogger(FlinkJobScaffold.class); public static void main(String[] args) throws Exception { // ===================== 1. 创建执行环境 ===================== StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); // ===================== 2. 配置 Checkpoint ===================== // 开启 Checkpoint,间隔 60 秒 env.enableCheckpointing(TimeUnit.SECONDS.toMillis(60)); CheckpointConfig checkpointConfig = env.getCheckpointConfig(); // 设置 Exactly-Once 语义,确保数据精确一次处理 checkpointConfig.setCheckpointingMode(CheckpointingMode.EXACTLY_ONCE); // 两次 Checkpoint 之间的最小间隔为 30 秒,防止 Checkpoint 过于频繁 checkpointConfig.setMinPauseBetweenCheckpoints(TimeUnit.SECONDS.toMillis(30)); // Checkpoint 超时时间为 10 分钟 checkpointConfig.setCheckpointTimeout(TimeUnit.MINUTES.toMillis(10)); // 同时只允许 1 个 Checkpoint 在进行 checkpointConfig.setMaxConcurrentCheckpoints(1); // 作业取消时保留 Checkpoint,用于手动恢复 checkpointConfig.setExternalizedCheckpointCleanup( CheckpointConfig.ExternalizedCheckpointCleanup.RETAIN_ON_CANCELLATION); // ===================== 3. 配置重启策略 ===================== // 固定延迟重启策略:最多重启 3 次,每次间隔 10 秒 env.setRestartStrategy( org.apache.flink.api.common.restartstrategy.RestartStrategies.fixedDelayRestart( 3, org.apache.flink.api.common.time.Time.seconds(10))); // ===================== 4. 设置并行度 ===================== env.setParallelism(4); // ===================== 5. 配置 Kafka Source ===================== KafkaSource kafkaSource = ProjectKafkaUtil.buildKafkaStringSource( ProjectConstants.TOPIC_METRIC, "scaffold-group"); // ===================== 6. 配置 Watermark 策略 ===================== // 允许最大 5 秒的乱序,用于处理网络延迟等导致的数据乱序 WatermarkStrategy watermarkStrategy = WatermarkStrategy .forBoundedOutOfOrderness(Duration.ofSeconds(5)) .withTimestampAssigner( (SerializableTimestampAssigner) (event, recordTimestamp) -> event.getTimestamp()) // 如果某个分区超过 30 秒没有数据,标记为 idle,避免阻塞 Watermark 推进 .withIdleness(Duration.ofSeconds(30)); // ===================== 7. 构建数据处理流水线 ===================== DataStream metricStream = env .fromSource(kafkaSource, WatermarkStrategy.noWatermarks(), "Kafka Source") // 反序列化 JSON 为 ServerMetric 对象 .map(json -> GsonUtil.fromJson(json, ServerMetric.class)) .assignTimestampsAndWatermarks(watermarkStrategy); // 7.1 使用 Keyed State 进行数据去重/过滤 DataStream dedupStream = metricStream .keyBy(ServerMetric::getHost) .flatMap(new DeduplicateFunction()); // 7.2 按照主机和指标名称分组,使用滚动窗口计算每分钟平均值 DataStream> avgMetricStream = dedupStream .keyBy(metric -> metric.getHost() + "_" + metric.getMetricName()) .window(TumblingEventTimeWindows.of(Time.minutes(1))) .aggregate(new MetricAvgAggregateFunction()); // 7.3 输出结果(生产环境可替换为写入 Kafka、ES、MySQL 等) avgMetricStream.print("avg-metric"); // ===================== 8. 启动作业 ===================== env.execute("Flink Job Scaffold - 生产级作业模板"); } /** * 去重函数:使用 ValueState 记录上次处理的数据时间戳, * 过滤掉重复或过期的数据 * *

知识点: *

    *
  • RichFlatMapFunction 可以访问 RuntimeContext,用于操作 Keyed State
  • *
  • ValueState 是最基本的 Keyed State,存储单个值
  • *
  • open() 方法在算子初始化时调用,用于注册 State
  • *
*/ public static class DeduplicateFunction extends RichFlatMapFunction { // 记录每个 key 上次处理的时间戳 private transient ValueState lastTimestampState; @Override public void open(Configuration parameters) throws Exception { ValueStateDescriptor descriptor = new ValueStateDescriptor<>( "last-timestamp", Long.class); lastTimestampState = getRuntimeContext().getState(descriptor); } @Override public void flatMap(ServerMetric value, Collector out) throws Exception { Long lastTimestamp = lastTimestampState.value(); // 如果是第一条数据或者时间戳比上次更新,则输出 if (lastTimestamp == null || value.getTimestamp() > lastTimestamp) { lastTimestampState.update(value.getTimestamp()); out.collect(value); } else { LOG.debug("过滤重复数据: host={}, metric={}, timestamp={}", value.getHost(), value.getMetricName(), value.getTimestamp()); } } } /** * 指标平均值聚合函数 * *

知识点: *

    *
  • AggregateFunction 是增量聚合函数,内存占用小,适合大数据量场景
  • *
  • 与 ReduceFunction 相比,AggregateFunction 的输入、中间状态、输出类型可以不同
  • *
  • ACC (累加器) 存储中间状态:(sum, count)
  • *
  • getResult() 在窗口触发时调用,计算最终结果
  • *
  • merge() 用于会话窗口的合并
  • *
*/ public static class MetricAvgAggregateFunction implements AggregateFunction, Tuple2> { @Override public Tuple2 createAccumulator() { // 初始化累加器:(sum=0.0, count=0) return Tuple2.of(0.0, 0L); } @Override public Tuple2 add(ServerMetric value, Tuple2 accumulator) { // 累加:sum += value, count += 1 return Tuple2.of(accumulator.f0 + value.getValue(), accumulator.f1 + 1); } @Override public Tuple2 getResult(Tuple2 accumulator) { // 计算平均值 double avg = accumulator.f1 > 0 ? accumulator.f0 / accumulator.f1 : 0.0; return Tuple2.of("avg", avg); } @Override public Tuple2 merge(Tuple2 a, Tuple2 b) { // 合并两个累加器(会话窗口场景) return Tuple2.of(a.f0 + b.f0, a.f1 + b.f1); } } } ================================================ FILE: flink-learning-project/flink-learning-project-log/README.md ================================================ ## 基于 Apache Flink 的实时日志分析系统 本模块实现了一个完整的实时日志处理与分析系统,从 Kafka 消费应用日志,进行实时分析和告警。 ### 核心功能 #### 1. LogAnalysisJob - 日志分析作业 - **日志分流**:使用 Side Output 将 ERROR/FATAL 日志分离到独立流中处理 - **窗口聚合统计**:按服务名称和日志级别,每分钟统计日志数量 - **增量聚合**:AggregateFunction + ProcessWindowFunction 组合使用 #### 2. ErrorLogAlertJob - 错误日志告警作业 - **错误检测**:实时监控每个服务的 ERROR 日志数量 - **定时器告警**:使用 KeyedProcessFunction + Timer 实现时间窗口内的计数告警 - **即时告警**:ERROR 数量达到阈值时立即触发告警 ### 涉及的 Flink 知识点 | 知识点 | 说明 | 所在类 | |--------|------|--------| | Side Output | 侧输出流,将数据分流到不同通道 | LogAnalysisJob | | AggregateFunction | 增量聚合函数,内存高效 | LogAnalysisJob | | ProcessWindowFunction | 获取窗口元信息的全量窗口函数 | LogAnalysisJob | | KeyedProcessFunction | 有状态的键控处理函数 | ErrorLogAlertJob | | Timer | 事件时间/处理时间定时器 | ErrorLogAlertJob | | ValueState | 键控状态,存储单个值 | ErrorLogAlertJob | | WatermarkStrategy | 水位线策略处理乱序数据 | 全部 | ### 数据流向 ``` Kafka (log-topic) │ ├── LogAnalysisJob │ ├── 主流 → 按 serviceName+level 窗口统计 → 输出 LogStatistics │ └── 侧输出 → ERROR/FATAL 日志 → 单独处理 │ └── ErrorLogAlertJob └── ERROR 日志 → 按 serviceName 分组 → 定时器计数 → 超阈值告警 → AlertEvent ``` ================================================ FILE: flink-learning-project/flink-learning-project-log/pom.xml ================================================ flink-learning-project com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 flink-learning-project-log com.zhisheng.flink flink-learning-project-common ${project.version} ================================================ FILE: flink-learning-project/flink-learning-project-log/src/main/java/com/zhisheng/project/log/ErrorLogAlertJob.java ================================================ package com.zhisheng.project.log; import com.zhisheng.common.utils.GsonUtil; import com.zhisheng.project.common.constant.ProjectConstants; import com.zhisheng.project.common.model.AlertEvent; import com.zhisheng.project.common.utils.ProjectKafkaUtil; import com.zhisheng.project.log.model.AppLogEvent; import org.apache.flink.api.common.eventtime.SerializableTimestampAssigner; import org.apache.flink.api.common.eventtime.WatermarkStrategy; import org.apache.flink.api.common.state.ValueState; import org.apache.flink.api.common.state.ValueStateDescriptor; import org.apache.flink.configuration.Configuration; import org.apache.flink.connector.kafka.source.KafkaSource; import org.apache.flink.streaming.api.datastream.DataStream; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.api.functions.KeyedProcessFunction; import org.apache.flink.util.Collector; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.time.Duration; import java.util.UUID; /** * 错误日志告警作业 * *

功能描述: * 监控每个服务在一定时间窗口内的 ERROR 日志数量,当 ERROR 数量超过阈值时触发告警 * *

涉及知识点: *

    *
  • KeyedProcessFunction:带有定时器的有状态处理
  • *
  • Timer(定时器):基于事件时间的定时触发
  • *
  • ValueState:键控状态存储 ERROR 计数
  • *
* * @author zhisheng */ public class ErrorLogAlertJob { private static final Logger LOG = LoggerFactory.getLogger(ErrorLogAlertJob.class); /** 告警阈值:1 分钟内超过此数量的 ERROR 日志触发告警 */ private static final int ERROR_THRESHOLD = 10; /** 统计窗口大小:60 秒 */ private static final long WINDOW_SIZE_MS = 60_000L; public static void main(String[] args) throws Exception { StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.setParallelism(4); KafkaSource kafkaSource = ProjectKafkaUtil.buildKafkaStringSource( ProjectConstants.TOPIC_LOG, "error-log-alert-group"); WatermarkStrategy watermarkStrategy = WatermarkStrategy .forBoundedOutOfOrderness(Duration.ofSeconds(5)) .withTimestampAssigner( (SerializableTimestampAssigner) (event, ts) -> event.getTimestamp()); // 消费日志数据 DataStream logStream = env .fromSource(kafkaSource, WatermarkStrategy.noWatermarks(), "Kafka Log Source") .map(json -> GsonUtil.fromJson(json, AppLogEvent.class)) .assignTimestampsAndWatermarks(watermarkStrategy); // 过滤 ERROR 日志,按服务名分组,使用定时器统计 ERROR 数量 DataStream alertStream = logStream .filter(log -> "ERROR".equals(log.getLevel()) || "FATAL".equals(log.getLevel())) .keyBy(AppLogEvent::getServiceName) .process(new ErrorCountAlertFunction()); alertStream.print("error-alert"); env.execute("错误日志告警系统"); } /** * 使用 KeyedProcessFunction + Timer 实现滑动计数告警 * *

实现思路: *

    *
  1. 每当收到一条 ERROR 日志,错误计数 +1
  2. *
  3. 如果是窗口内第一条 ERROR,注册一个事件时间定时器(当前时间 + 窗口大小)
  4. *
  5. 定时器触发时,检查窗口内累计的 ERROR 数量
  6. *
  7. 如果超过阈值则输出告警事件,然后重置计数器
  8. *
*/ public static class ErrorCountAlertFunction extends KeyedProcessFunction { /** 当前窗口内的 ERROR 计数 */ private transient ValueState errorCountState; /** 当前活跃的定时器时间戳 */ private transient ValueState timerTimestampState; @Override public void open(Configuration parameters) throws Exception { errorCountState = getRuntimeContext().getState( new ValueStateDescriptor<>("error-count", Long.class)); timerTimestampState = getRuntimeContext().getState( new ValueStateDescriptor<>("timer-timestamp", Long.class)); } @Override public void processElement(AppLogEvent log, Context ctx, Collector out) throws Exception { // 累加 ERROR 计数 Long currentCount = errorCountState.value(); long newCount = (currentCount == null ? 0 : currentCount) + 1; errorCountState.update(newCount); // 如果还没有注册定时器,注册一个 if (timerTimestampState.value() == null) { long timerTs = log.getTimestamp() + WINDOW_SIZE_MS; ctx.timerService().registerEventTimeTimer(timerTs); timerTimestampState.update(timerTs); } // 如果当前计数已经超过阈值,立即触发告警(不等待定时器) if (newCount == ERROR_THRESHOLD) { out.collect(buildAlertEvent(ctx.getCurrentKey(), newCount)); LOG.warn("服务 {} 在 1 分钟内 ERROR 数量达到 {} 条,触发告警", ctx.getCurrentKey(), newCount); } } @Override public void onTimer(long timestamp, OnTimerContext ctx, Collector out) throws Exception { Long count = errorCountState.value(); if (count != null && count > 0) { LOG.info("服务 {} 定时器触发,窗口内 ERROR 数量: {}", ctx.getCurrentKey(), count); } // 重置状态,开始新的窗口 errorCountState.clear(); timerTimestampState.clear(); } private AlertEvent buildAlertEvent(String serviceName, long errorCount) { return AlertEvent.builder() .alertId(UUID.randomUUID().toString()) .level(ProjectConstants.ALERT_LEVEL_CRITICAL) .ruleName("error-log-spike") .message(String.format("服务 [%s] 在 1 分钟内产生 %d 条 ERROR 日志,超过阈值 %d", serviceName, errorCount, ERROR_THRESHOLD)) .metricName("error_log_count") .metricValue((double) errorCount) .threshold((double) ERROR_THRESHOLD) .timestamp(System.currentTimeMillis()) .host(serviceName) .build(); } } } ================================================ FILE: flink-learning-project/flink-learning-project-log/src/main/java/com/zhisheng/project/log/LogAnalysisJob.java ================================================ package com.zhisheng.project.log; import com.zhisheng.common.utils.GsonUtil; import com.zhisheng.project.common.constant.ProjectConstants; import com.zhisheng.project.common.utils.ProjectKafkaUtil; import com.zhisheng.project.log.model.AppLogEvent; import com.zhisheng.project.log.model.LogStatistics; import org.apache.flink.api.common.eventtime.SerializableTimestampAssigner; import org.apache.flink.api.common.eventtime.WatermarkStrategy; import org.apache.flink.api.common.functions.AggregateFunction; import org.apache.flink.connector.kafka.source.KafkaSource; import org.apache.flink.streaming.api.datastream.DataStream; import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.api.functions.windowing.ProcessWindowFunction; import org.apache.flink.streaming.api.windowing.assigners.TumblingEventTimeWindows; import org.apache.flink.streaming.api.windowing.time.Time; import org.apache.flink.streaming.api.windowing.windows.TimeWindow; import org.apache.flink.util.Collector; import org.apache.flink.util.OutputTag; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.time.Duration; /** * 基于 Apache Flink 的实时日志分析系统 * *

功能描述: *

    *
  • 从 Kafka 实时消费应用日志
  • *
  • 使用侧输出流(Side Output)将 ERROR 日志分流
  • *
  • 按服务名称和日志级别进行窗口聚合统计
  • *
  • 使用 AggregateFunction + ProcessWindowFunction 获取窗口信息
  • *
* *

涉及知识点: *

    *
  • Side Output(侧输出流):将不同类型数据分流处理
  • *
  • Event Time + Watermark:基于事件时间的窗口处理
  • *
  • AggregateFunction + ProcessWindowFunction:增量聚合 + 全量窗口信息
  • *
  • Tumbling Window:滚动窗口按时间切分数据
  • *
* * @author zhisheng */ public class LogAnalysisJob { private static final Logger LOG = LoggerFactory.getLogger(LogAnalysisJob.class); /** ERROR 日志侧输出标签 */ private static final OutputTag ERROR_LOG_TAG = new OutputTag("error-log") {}; public static void main(String[] args) throws Exception { StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.setParallelism(4); // 配置 Kafka Source KafkaSource kafkaSource = ProjectKafkaUtil.buildKafkaStringSource( ProjectConstants.TOPIC_LOG, "log-analysis-group"); // Watermark 策略:允许 3 秒乱序 WatermarkStrategy watermarkStrategy = WatermarkStrategy .forBoundedOutOfOrderness(Duration.ofSeconds(3)) .withTimestampAssigner( (SerializableTimestampAssigner) (event, ts) -> event.getTimestamp()); // ========== 1. 数据源:从 Kafka 消费日志 ========== SingleOutputStreamOperator logStream = env .fromSource(kafkaSource, WatermarkStrategy.noWatermarks(), "Kafka Log Source") .map(json -> GsonUtil.fromJson(json, AppLogEvent.class)) .assignTimestampsAndWatermarks(watermarkStrategy) // ========== 2. 使用 Side Output 分流 ERROR 日志 ========== .process(new org.apache.flink.streaming.api.functions.ProcessFunction() { @Override public void processElement(AppLogEvent log, Context ctx, Collector out) { // 所有日志输出到主流 out.collect(log); // ERROR 和 FATAL 级别日志额外输出到侧输出流 if ("ERROR".equals(log.getLevel()) || "FATAL".equals(log.getLevel())) { ctx.output(ERROR_LOG_TAG, log); } } }); // ========== 3. 获取 ERROR 侧输出流,单独处理 ========== DataStream errorLogStream = logStream.getSideOutput(ERROR_LOG_TAG); errorLogStream.print("error-log"); // ========== 4. 按 serviceName + level 分组,每分钟统计日志数量 ========== DataStream logStats = logStream .keyBy(log -> log.getServiceName() + "|" + log.getLevel()) .window(TumblingEventTimeWindows.of(Time.minutes(1))) .aggregate(new LogCountAgg(), new LogStatsWindowFunction()); logStats.print("log-stats"); env.execute("实时日志分析系统"); } /** * 日志计数增量聚合函数 * 每条数据到来时只做 +1 操作,内存高效 */ public static class LogCountAgg implements AggregateFunction { @Override public Long createAccumulator() { return 0L; } @Override public Long add(AppLogEvent value, Long accumulator) { return accumulator + 1; } @Override public Long getResult(Long accumulator) { return accumulator; } @Override public Long merge(Long a, Long b) { return a + b; } } /** * 日志统计窗口函数 * *

知识点:AggregateFunction + ProcessWindowFunction 组合使用 *

    *
  • AggregateFunction 负责增量聚合,内存高效
  • *
  • ProcessWindowFunction 在窗口触发时获取窗口元信息(开始/结束时间)
  • *
  • 两者组合既保证了性能,又能获取窗口信息
  • *
*/ public static class LogStatsWindowFunction extends ProcessWindowFunction { @Override public void process(String key, Context context, Iterable elements, Collector out) { Long count = elements.iterator().next(); String[] parts = key.split("\\|", 2); String serviceName = parts.length > 0 ? parts[0] : "unknown"; String level = parts.length > 1 ? parts[1] : "unknown"; out.collect(LogStatistics.builder() .serviceName(serviceName) .level(level) .count(count) .windowStart(context.window().getStart()) .windowEnd(context.window().getEnd()) .build()); } } } ================================================ FILE: flink-learning-project/flink-learning-project-log/src/main/java/com/zhisheng/project/log/model/AppLogEvent.java ================================================ package com.zhisheng.project.log.model; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; /** * 应用日志事件模型 * * @author zhisheng */ @Data @Builder @AllArgsConstructor @NoArgsConstructor public class AppLogEvent { /** 日志 ID */ private String logId; /** 日志级别:DEBUG, INFO, WARN, ERROR, FATAL */ private String level; /** 日志消息 */ private String message; /** 产生日志的服务名称 */ private String serviceName; /** 产生日志的类名 */ private String className; /** 线程名 */ private String threadName; /** 异常堆栈信息(可选) */ private String exception; /** 日志时间戳 */ private Long timestamp; /** 主机名 */ private String host; /** Trace ID,用于链路追踪 */ private String traceId; } ================================================ FILE: flink-learning-project/flink-learning-project-log/src/main/java/com/zhisheng/project/log/model/LogStatistics.java ================================================ package com.zhisheng.project.log.model; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; /** * 日志统计结果模型 * * @author zhisheng */ @Data @Builder @AllArgsConstructor @NoArgsConstructor public class LogStatistics { /** 统计维度:服务名称 */ private String serviceName; /** 统计维度:日志级别 */ private String level; /** 统计数量 */ private Long count; /** 窗口开始时间 */ private Long windowStart; /** 窗口结束时间 */ private Long windowEnd; } ================================================ FILE: flink-learning-project/flink-learning-project-monitor-alert/README.md ================================================ ## 基于 Apache Flink 的监控告警系统 本模块实现了两种监控告警模式,展示了 Flink 在实时监控场景中的典型应用。 ### 核心功能 #### 1. DynamicAlertRuleJob - 动态规则告警 - **广播状态**:使用 Broadcast State 将告警规则广播到所有并行实例 - **动态更新**:支持运行时动态添加、修改、禁用告警规则,无需重启作业 - **规则匹配**:支持多种比较操作符(GT/LT/GTE/LTE) #### 2. MetricAggregateAlertJob - 滑动窗口聚合告警 - **滑动窗口**:5 分钟窗口、1 分钟滑动步长,平滑检测异常 - **增量聚合**:ReduceFunction 增量计算窗口内最大值 - **阈值告警**:CPU > 90%、内存 > 85% 时触发告警 ### 涉及的 Flink 知识点 | 知识点 | 说明 | 所在类 | |--------|------|--------| | Broadcast State | 广播状态,用于动态配置分发 | DynamicAlertRuleJob | | BroadcastProcessFunction | 处理广播流和数据流的连接 | DynamicAlertRuleJob | | MapStateDescriptor | 广播状态描述符 | DynamicAlertRuleJob | | SlidingEventTimeWindow | 滑动窗口(有重叠) | MetricAggregateAlertJob | | ReduceFunction | 增量聚合函数(输入输出类型相同) | MetricAggregateAlertJob | | ProcessWindowFunction | 全量窗口函数获取窗口元信息 | MetricAggregateAlertJob | ### 架构设计 ``` ┌─────────────────┐ ┌──────────────────────┐ │ 指标数据 (Kafka) │────>│ DynamicAlertRuleJob │───> 告警事件 └─────────────────┘ │ (Broadcast State) │ └──────────────────────┘ ┌─────────────────┐ ↑ │ 规则配置 (Kafka) │──────────┘ (广播) └─────────────────┘ ┌─────────────────┐ ┌──────────────────────┐ │ 指标数据 (Kafka) │────>│ MetricAggregateAlert │───> 告警事件 └─────────────────┘ │ (Sliding Window) │ └──────────────────────┘ ``` ================================================ FILE: flink-learning-project/flink-learning-project-monitor-alert/pom.xml ================================================ flink-learning-project com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 flink-learning-project-monitor-alert com.zhisheng.flink flink-learning-project-common ${project.version} ================================================ FILE: flink-learning-project/flink-learning-project-monitor-alert/src/main/java/com/zhisheng/project/monitor/alert/DynamicAlertRuleJob.java ================================================ package com.zhisheng.project.monitor.alert; import com.zhisheng.common.utils.GsonUtil; import com.zhisheng.project.common.constant.ProjectConstants; import com.zhisheng.project.common.model.AlertEvent; import com.zhisheng.project.common.model.AlertRule; import com.zhisheng.project.common.model.ServerMetric; import com.zhisheng.project.common.utils.ProjectKafkaUtil; import org.apache.flink.api.common.eventtime.SerializableTimestampAssigner; import org.apache.flink.api.common.eventtime.WatermarkStrategy; import org.apache.flink.api.common.state.BroadcastState; import org.apache.flink.api.common.state.MapStateDescriptor; import org.apache.flink.api.common.state.ReadOnlyBroadcastState; import org.apache.flink.api.common.typeinfo.BasicTypeInfo; import org.apache.flink.api.common.typeinfo.TypeInformation; import org.apache.flink.connector.kafka.source.KafkaSource; import org.apache.flink.streaming.api.datastream.BroadcastStream; import org.apache.flink.streaming.api.datastream.DataStream; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.api.functions.co.BroadcastProcessFunction; import org.apache.flink.util.Collector; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.time.Duration; import java.util.Map; import java.util.UUID; /** * 基于广播状态的动态告警规则系统 * *

功能描述: *

    *
  • 从 Kafka 消费服务器指标数据(CPU、内存、磁盘等)
  • *
  • 从另一个 Kafka Topic 消费告警规则配置(支持动态更新)
  • *
  • 使用 Broadcast State 将规则广播到所有算子实例
  • *
  • 根据规则实时判断指标是否触发告警
  • *
* *

核心知识点: *

    *
  • Broadcast State(广播状态):将配置/规则数据广播到所有并行实例
  • *
  • BroadcastProcessFunction:处理广播流和数据流的连接
  • *
  • MapStateDescriptor:广播状态的描述符
  • *
  • 动态配置更新:无需重启作业即可更新告警规则
  • *
* *

架构设计: *

 *   指标数据流 (Kafka: metric-topic) ──┐
 *                                      ├── connect ── BroadcastProcessFunction ── AlertEvent
 *   规则配置流 (Kafka: alert-topic) ───┘ (broadcast)
 * 
* * @author zhisheng */ public class DynamicAlertRuleJob { private static final Logger LOG = LoggerFactory.getLogger(DynamicAlertRuleJob.class); /** 广播状态描述符:key=规则ID, value=规则对象 */ private static final MapStateDescriptor RULE_STATE_DESCRIPTOR = new MapStateDescriptor<>( "alert-rules", BasicTypeInfo.STRING_TYPE_INFO, TypeInformation.of(AlertRule.class)); public static void main(String[] args) throws Exception { StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.setParallelism(4); // ========== 1. 指标数据流 ========== KafkaSource metricSource = ProjectKafkaUtil.buildKafkaStringSource( ProjectConstants.TOPIC_METRIC, "monitor-alert-metric-group"); WatermarkStrategy watermarkStrategy = WatermarkStrategy .forBoundedOutOfOrderness(Duration.ofSeconds(3)) .withTimestampAssigner( (SerializableTimestampAssigner) (event, ts) -> event.getTimestamp()); DataStream metricStream = env .fromSource(metricSource, WatermarkStrategy.noWatermarks(), "Metric Source") .map(json -> GsonUtil.fromJson(json, ServerMetric.class)) .assignTimestampsAndWatermarks(watermarkStrategy); // ========== 2. 告警规则配置流(广播流) ========== KafkaSource ruleSource = ProjectKafkaUtil.buildKafkaStringSource( ProjectConstants.TOPIC_ALERT, "monitor-alert-rule-group"); // 将规则流转换为广播流 BroadcastStream ruleBroadcastStream = env .fromSource(ruleSource, WatermarkStrategy.noWatermarks(), "Rule Source") .map(json -> GsonUtil.fromJson(json, AlertRule.class)) .broadcast(RULE_STATE_DESCRIPTOR); // ========== 3. 连接数据流和广播流,进行规则匹配 ========== DataStream alertStream = metricStream .connect(ruleBroadcastStream) .process(new AlertRuleBroadcastFunction()); alertStream.print("alert-output"); env.execute("动态告警规则系统"); } /** * 广播处理函数:处理指标数据流和规则广播流 * *

核心逻辑: *

    *
  • processElement:处理每条指标数据,遍历所有规则进行匹配
  • *
  • processBroadcastElement:处理规则更新,动态添加/删除/修改规则
  • *
* *

知识点: *

    *
  • processElement 中只能以只读方式访问广播状态(ReadOnlyBroadcastState)
  • *
  • processBroadcastElement 中可以读写广播状态(BroadcastState)
  • *
  • 广播状态会自动同步到所有并行实例,保证数据一致性
  • *
*/ public static class AlertRuleBroadcastFunction extends BroadcastProcessFunction { @Override public void processElement(ServerMetric metric, ReadOnlyContext ctx, Collector out) throws Exception { // 以只读方式获取广播状态中的所有规则 ReadOnlyBroadcastState ruleState = ctx.getBroadcastState(RULE_STATE_DESCRIPTOR); // 遍历所有规则,检查当前指标是否匹配 for (Map.Entry entry : ruleState.immutableEntries()) { AlertRule rule = entry.getValue(); // 只处理启用的规则,并且指标名称匹配 if (rule.getEnabled() && rule.getMetricName().equals(metric.getMetricName())) { // 根据比较操作符判断是否触发告警 boolean triggered = evaluateRule(metric.getValue(), rule.getOperator(), rule.getThreshold()); if (triggered) { AlertEvent alert = AlertEvent.builder() .alertId(UUID.randomUUID().toString()) .level(rule.getAlertLevel()) .ruleName(rule.getRuleName()) .message(String.format("规则 [%s] 触发:%s 的 %s = %.2f %s %.2f", rule.getRuleName(), metric.getHost(), metric.getMetricName(), metric.getValue(), rule.getOperator(), rule.getThreshold())) .metricName(metric.getMetricName()) .metricValue(metric.getValue()) .threshold(rule.getThreshold()) .timestamp(System.currentTimeMillis()) .host(metric.getHost()) .build(); out.collect(alert); LOG.warn("告警触发: {}", alert.getMessage()); } } } } @Override public void processBroadcastElement(AlertRule rule, Context ctx, Collector out) throws Exception { // 获取可写的广播状态 BroadcastState ruleState = ctx.getBroadcastState(RULE_STATE_DESCRIPTOR); if (rule.getEnabled()) { // 添加或更新规则 ruleState.put(rule.getRuleId(), rule); LOG.info("规则更新: {} - {}", rule.getRuleId(), rule.getRuleName()); } else { // 规则禁用时,从状态中移除 ruleState.remove(rule.getRuleId()); LOG.info("规则禁用: {} - {}", rule.getRuleId(), rule.getRuleName()); } } /** * 根据操作符和阈值评估规则 */ private boolean evaluateRule(double metricValue, String operator, double threshold) { switch (operator) { case "GT": return metricValue > threshold; case "LT": return metricValue < threshold; case "GTE": return metricValue >= threshold; case "LTE": return metricValue <= threshold; default: LOG.warn("未知的操作符: {}", operator); return false; } } } } ================================================ FILE: flink-learning-project/flink-learning-project-monitor-alert/src/main/java/com/zhisheng/project/monitor/alert/MetricAggregateAlertJob.java ================================================ package com.zhisheng.project.monitor.alert; import com.zhisheng.common.utils.GsonUtil; import com.zhisheng.project.common.constant.ProjectConstants; import com.zhisheng.project.common.model.AlertEvent; import com.zhisheng.project.common.model.ServerMetric; import com.zhisheng.project.common.utils.ProjectKafkaUtil; import org.apache.flink.api.common.eventtime.SerializableTimestampAssigner; import org.apache.flink.api.common.eventtime.WatermarkStrategy; import org.apache.flink.api.common.functions.ReduceFunction; import org.apache.flink.connector.kafka.source.KafkaSource; import org.apache.flink.streaming.api.datastream.DataStream; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.api.functions.windowing.ProcessWindowFunction; import org.apache.flink.streaming.api.windowing.assigners.SlidingEventTimeWindows; import org.apache.flink.streaming.api.windowing.time.Time; import org.apache.flink.streaming.api.windowing.windows.TimeWindow; import org.apache.flink.util.Collector; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.time.Duration; import java.util.UUID; /** * 基于滑动窗口的指标聚合告警 * *

功能描述: *

    *
  • 按主机和指标名称分组
  • *
  • 使用滑动窗口(5分钟窗口,1分钟滑动步长)计算指标最大值
  • *
  • ReduceFunction + ProcessWindowFunction 组合实现增量聚合
  • *
  • 窗口触发时判断最大值是否超过阈值
  • *
* *

知识点: *

    *
  • SlidingEventTimeWindow:滑动窗口(窗口大小 > 滑动步长,有重叠)
  • *
  • ReduceFunction:增量聚合函数,输入输出类型相同
  • *
  • ReduceFunction + ProcessWindowFunction:增量聚合 + 窗口元信息
  • *
* * @author zhisheng */ public class MetricAggregateAlertJob { private static final Logger LOG = LoggerFactory.getLogger(MetricAggregateAlertJob.class); /** CPU 使用率告警阈值 */ private static final double CPU_THRESHOLD = 90.0; /** 内存使用率告警阈值 */ private static final double MEMORY_THRESHOLD = 85.0; public static void main(String[] args) throws Exception { StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.setParallelism(4); KafkaSource kafkaSource = ProjectKafkaUtil.buildKafkaStringSource( ProjectConstants.TOPIC_METRIC, "metric-aggregate-alert-group"); WatermarkStrategy watermarkStrategy = WatermarkStrategy .forBoundedOutOfOrderness(Duration.ofSeconds(5)) .withTimestampAssigner( (SerializableTimestampAssigner) (event, ts) -> event.getTimestamp()); DataStream metricStream = env .fromSource(kafkaSource, WatermarkStrategy.noWatermarks(), "Metric Source") .map(json -> GsonUtil.fromJson(json, ServerMetric.class)) .assignTimestampsAndWatermarks(watermarkStrategy); // 使用滑动窗口聚合:窗口大小 5 分钟,滑动步长 1 分钟 // ReduceFunction 取最大值 + ProcessWindowFunction 获取窗口信息并判断阈值 DataStream alertStream = metricStream .keyBy(metric -> metric.getHost() + "|" + metric.getMetricName()) .window(SlidingEventTimeWindows.of(Time.minutes(5), Time.minutes(1))) .reduce(new MaxMetricReduceFunction(), new AlertWindowFunction()); alertStream.print("aggregate-alert"); env.execute("指标聚合告警系统"); } /** * 使用 ReduceFunction 增量计算最大值 * *

知识点:ReduceFunction 要求输入和输出类型相同 * 每条数据到来时只做一次比较操作,非常高效 */ public static class MaxMetricReduceFunction implements ReduceFunction { @Override public ServerMetric reduce(ServerMetric v1, ServerMetric v2) { // 保留值更大的那条指标 return v1.getValue() >= v2.getValue() ? v1 : v2; } } /** * 窗口处理函数:在窗口触发时判断最大值是否超过阈值 * *

与 ReduceFunction 组合使用时,Iterable 中只有一个元素(增量聚合的结果) */ public static class AlertWindowFunction extends ProcessWindowFunction { @Override public void process(String key, Context context, Iterable elements, Collector out) { ServerMetric maxMetric = elements.iterator().next(); double threshold = getThreshold(maxMetric.getMetricName()); // 判断最大值是否超过阈值 if (threshold > 0 && maxMetric.getValue() > threshold) { AlertEvent alert = AlertEvent.builder() .alertId(UUID.randomUUID().toString()) .level(ProjectConstants.ALERT_LEVEL_WARNING) .ruleName("metric-aggregate-" + maxMetric.getMetricName()) .message(String.format("主机 [%s] 的 %s 在最近 5 分钟内最大值 %.2f%% 超过阈值 %.2f%%", maxMetric.getHost(), maxMetric.getMetricName(), maxMetric.getValue(), threshold)) .metricName(maxMetric.getMetricName()) .metricValue(maxMetric.getValue()) .threshold(threshold) .timestamp(context.window().getEnd()) .host(maxMetric.getHost()) .build(); out.collect(alert); } } private double getThreshold(String metricName) { switch (metricName) { case "cpu_usage": return CPU_THRESHOLD; case "memory_usage": return MEMORY_THRESHOLD; default: return -1; } } } } ================================================ FILE: flink-learning-project/flink-learning-project-monitor-dashboard/README.md ================================================ ## 基于 Apache Flink 的实时大屏系统 本模块实现了实时大屏的核心功能:PV/UV 统计和热门页面 TopN 排行榜。 ### 核心功能 #### 1. RealTimeDashboardJob - PV/UV 统计 - **多维统计**:同时计算 PV、UV、平均停留时长 - **UV 去重**:在 AggregateFunction 累加器中使用 Set 去重 - **增量聚合**:AggregateFunction + ProcessWindowFunction #### 2. TopNHotPagesJob - 热门页面 TopN - **两阶段聚合**:先分组聚合,再全局排序 - **ListState 收集**:使用 ListState 收集同一窗口的所有分组结果 - **定时器排序**:Timer 触发后对收集的数据排序输出 TopN ### 涉及的 Flink 知识点 | 知识点 | 说明 | 所在类 | |--------|------|--------| | AggregateFunction | 复杂累加器设计(Tuple4) | RealTimeDashboardJob | | ProcessWindowFunction | 窗口元信息获取 | RealTimeDashboardJob | | SlidingEventTimeWindow | 滑动窗口 | TopNHotPagesJob | | WindowFunction | 窗口函数关联窗口信息 | TopNHotPagesJob | | ListState | 列表状态收集窗口数据 | TopNHotPagesJob | | Timer | 定时器触发排序 | TopNHotPagesJob | | KeyedProcessFunction | 两阶段聚合的第二阶段 | TopNHotPagesJob | ### 数据流向 ``` ┌──────────────────────┐ │ RealTimeDashboardJob │ 页面访问 ──────>│ 按类别窗口聚合 │──> PV/UV/停留时长 统计 (Kafka) │ └──────────────────────┘ │ ┌──────────────────────┐ └───>│ TopNHotPagesJob │ │ 两阶段聚合排序 │──> 热门页面 Top10 排行榜 └──────────────────────┘ ``` ================================================ FILE: flink-learning-project/flink-learning-project-monitor-dashboard/pom.xml ================================================ flink-learning-project com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 flink-learning-project-monitor-dashboard com.zhisheng.flink flink-learning-project-common ${project.version} ================================================ FILE: flink-learning-project/flink-learning-project-monitor-dashboard/src/main/java/com/zhisheng/project/dashboard/RealTimeDashboardJob.java ================================================ package com.zhisheng.project.dashboard; import com.zhisheng.common.utils.GsonUtil; import com.zhisheng.project.common.constant.ProjectConstants; import com.zhisheng.project.common.model.PageAccessEvent; import com.zhisheng.project.common.utils.ProjectKafkaUtil; import com.zhisheng.project.dashboard.model.PageViewStats; import org.apache.flink.api.common.eventtime.SerializableTimestampAssigner; import org.apache.flink.api.common.eventtime.WatermarkStrategy; import org.apache.flink.api.common.functions.AggregateFunction; import org.apache.flink.api.java.tuple.Tuple4; import org.apache.flink.connector.kafka.source.KafkaSource; import org.apache.flink.streaming.api.datastream.DataStream; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.api.functions.windowing.ProcessWindowFunction; import org.apache.flink.streaming.api.windowing.assigners.TumblingEventTimeWindows; import org.apache.flink.streaming.api.windowing.time.Time; import org.apache.flink.streaming.api.windowing.windows.TimeWindow; import org.apache.flink.util.Collector; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.time.Duration; import java.util.HashSet; import java.util.Set; /** * 实时大屏 - PV/UV 统计作业 * *

功能描述: *

    *
  • 从 Kafka 消费页面访问事件
  • *
  • 按页面类别分组
  • *
  • 使用 AggregateFunction 进行增量聚合,同时计算 PV、UV、平均停留时长
  • *
  • 每分钟输出一次统计结果
  • *
* *

核心知识点: *

    *
  • AggregateFunction 的复杂累加器设计(同时维护多个统计维度)
  • *
  • 使用 Set 在累加器中进行 UV 去重
  • *
  • AggregateFunction + ProcessWindowFunction 组合获取窗口信息
  • *
* * @author zhisheng */ public class RealTimeDashboardJob { private static final Logger LOG = LoggerFactory.getLogger(RealTimeDashboardJob.class); public static void main(String[] args) throws Exception { StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.setParallelism(4); KafkaSource kafkaSource = ProjectKafkaUtil.buildKafkaStringSource( ProjectConstants.TOPIC_PAGE_ACCESS, "dashboard-pv-uv-group"); WatermarkStrategy watermarkStrategy = WatermarkStrategy .forBoundedOutOfOrderness(Duration.ofSeconds(3)) .withTimestampAssigner( (SerializableTimestampAssigner) (event, ts) -> event.getTimestamp()); DataStream accessStream = env .fromSource(kafkaSource, WatermarkStrategy.noWatermarks(), "Page Access Source") .map(json -> GsonUtil.fromJson(json, PageAccessEvent.class)) .assignTimestampsAndWatermarks(watermarkStrategy); // 按页面类别分组,每分钟统计 PV/UV/平均停留时长 DataStream statsStream = accessStream .keyBy(PageAccessEvent::getPageCategory) .window(TumblingEventTimeWindows.of(Time.minutes(1))) .aggregate(new PvUvAggFunction(), new PvUvWindowFunction()); statsStream.print("dashboard-stats"); env.execute("实时大屏 - PV/UV 统计"); } /** * PV/UV 增量聚合函数 * *

累加器 Tuple4 的含义: *

    *
  • f0: PV 计数 (Long)
  • *
  • f1: UV 去重集合 (Set<String>)
  • *
  • f2: 总停留时长 (Long)
  • *
  • f3: 页面类别 (String)
  • *
* *

知识点:AggregateFunction 的累加器可以是任意复杂类型 * 这里使用 Tuple4 同时维护 PV、UV、停留时长和页面类别 */ public static class PvUvAggFunction implements AggregateFunction, Long, String>, Tuple4> { @Override public Tuple4, Long, String> createAccumulator() { return Tuple4.of(0L, new HashSet<>(), 0L, ""); } @Override public Tuple4, Long, String> add( PageAccessEvent event, Tuple4, Long, String> acc) { // PV +1 acc.f0 += 1; // UV 去重:将 userId 加入 Set acc.f1.add(event.getUserId()); // 累计停留时长 acc.f2 += (event.getStayDuration() != null ? event.getStayDuration() : 0); // 记录页面类别 acc.f3 = event.getPageCategory(); return acc; } @Override public Tuple4 getResult( Tuple4, Long, String> acc) { // 输出:(PV, UV, avgStayDuration, pageCategory) long avgStay = acc.f0 > 0 ? acc.f2 / acc.f0 : 0; return Tuple4.of(acc.f0, (long) acc.f1.size(), avgStay, acc.f3); } @Override public Tuple4, Long, String> merge( Tuple4, Long, String> a, Tuple4, Long, String> b) { a.f0 += b.f0; a.f1.addAll(b.f1); a.f2 += b.f2; return a; } } /** * 窗口处理函数:将聚合结果与窗口信息组合 */ public static class PvUvWindowFunction extends ProcessWindowFunction, PageViewStats, String, TimeWindow> { @Override public void process(String key, Context context, Iterable> elements, Collector out) { Tuple4 result = elements.iterator().next(); out.collect(PageViewStats.builder() .pageCategory(key) .pageUrl("") .pv(result.f0) .uv(result.f1) .avgStayDuration(result.f2) .windowStart(context.window().getStart()) .windowEnd(context.window().getEnd()) .build()); } } } ================================================ FILE: flink-learning-project/flink-learning-project-monitor-dashboard/src/main/java/com/zhisheng/project/dashboard/TopNHotPagesJob.java ================================================ package com.zhisheng.project.dashboard; import com.zhisheng.common.utils.GsonUtil; import com.zhisheng.project.common.constant.ProjectConstants; import com.zhisheng.project.common.model.PageAccessEvent; import com.zhisheng.project.common.utils.ProjectKafkaUtil; import com.zhisheng.project.dashboard.model.TopNResult; import org.apache.flink.api.common.eventtime.SerializableTimestampAssigner; import org.apache.flink.api.common.eventtime.WatermarkStrategy; import org.apache.flink.api.common.functions.AggregateFunction; import org.apache.flink.api.common.state.ListState; import org.apache.flink.api.common.state.ListStateDescriptor; import org.apache.flink.api.java.tuple.Tuple2; import org.apache.flink.configuration.Configuration; import org.apache.flink.connector.kafka.source.KafkaSource; import org.apache.flink.streaming.api.datastream.DataStream; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.api.functions.KeyedProcessFunction; import org.apache.flink.streaming.api.functions.windowing.WindowFunction; import org.apache.flink.streaming.api.windowing.assigners.SlidingEventTimeWindows; import org.apache.flink.streaming.api.windowing.time.Time; import org.apache.flink.streaming.api.windowing.windows.TimeWindow; import org.apache.flink.util.Collector; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.time.Duration; import java.util.ArrayList; import java.util.Comparator; import java.util.List; /** * 实时热门页面 TopN 排行榜 * *

功能描述: * 实时计算最近 5 分钟内访问量最高的 Top 10 热门页面 * *

实现思路: *

    *
  1. 按页面 URL 分组,滑动窗口聚合计算每个页面的访问量
  2. *
  3. 将窗口结果按窗口结束时间重新分组
  4. *
  5. 使用 KeyedProcessFunction + Timer 收集同一窗口的所有页面数据
  6. *
  7. 定时器触发时排序输出 TopN
  8. *
* *

核心知识点: *

    *
  • SlidingEventTimeWindow:滑动窗口
  • *
  • AggregateFunction + WindowFunction 组合
  • *
  • ListState:列表状态,收集同一窗口的所有分组结果
  • *
  • KeyedProcessFunction + Timer:定时器触发 TopN 排序
  • *
  • 两阶段聚合:先分组聚合,再全局排序
  • *
* * @author zhisheng */ public class TopNHotPagesJob { private static final Logger LOG = LoggerFactory.getLogger(TopNHotPagesJob.class); private static final int TOP_N = 10; public static void main(String[] args) throws Exception { StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.setParallelism(4); KafkaSource kafkaSource = ProjectKafkaUtil.buildKafkaStringSource( ProjectConstants.TOPIC_PAGE_ACCESS, "dashboard-topn-group"); WatermarkStrategy watermarkStrategy = WatermarkStrategy .forBoundedOutOfOrderness(Duration.ofSeconds(5)) .withTimestampAssigner( (SerializableTimestampAssigner) (event, ts) -> event.getTimestamp()); DataStream accessStream = env .fromSource(kafkaSource, WatermarkStrategy.noWatermarks(), "Page Access Source") .map(json -> GsonUtil.fromJson(json, PageAccessEvent.class)) .assignTimestampsAndWatermarks(watermarkStrategy); // ========== 第一阶段:按页面 URL 分组,滑动窗口聚合 ========== DataStream> pageCountStream = accessStream .keyBy(PageAccessEvent::getPageUrl) .window(SlidingEventTimeWindows.of(Time.minutes(5), Time.minutes(1))) .aggregate(new PageCountAgg(), new PageCountWindowFunction()); // ========== 第二阶段:按窗口结束时间分组,收集后排序输出 TopN ========== DataStream topNStream = pageCountStream .keyBy(t -> t.f1) // 按窗口结束时间分组 .process(new TopNProcessFunction()); topNStream.print("top-n-pages"); env.execute("实时热门页面 TopN"); } /** * 页面访问量计数 */ public static class PageCountAgg implements AggregateFunction { @Override public Long createAccumulator() { return 0L; } @Override public Long add(PageAccessEvent value, Long acc) { return acc + 1; } @Override public Long getResult(Long acc) { return acc; } @Override public Long merge(Long a, Long b) { return a + b; } } /** * WindowFunction 用于将聚合结果与窗口信息关联 * 输出 Tuple2(pageUrl_count, windowEnd) * 这里将 pageUrl 和 count 编码到第一个字段中 */ public static class PageCountWindowFunction implements WindowFunction, String, TimeWindow> { @Override public void apply(String pageUrl, TimeWindow window, Iterable input, Collector> out) { Long count = input.iterator().next(); // 编码为 "pageUrl|count" 和窗口结束时间 out.collect(Tuple2.of(pageUrl + "|" + count, window.getEnd())); } } /** * TopN 处理函数 * *

实现思路: *

    *
  1. 每条数据到来时存入 ListState
  2. *
  3. 注册一个窗口结束时间 +1 的定时器(等待同一窗口所有数据到齐)
  4. *
  5. 定时器触发时,从 ListState 取出所有数据,排序输出 TopN
  6. *
*/ public static class TopNProcessFunction extends KeyedProcessFunction, TopNResult> { private transient ListState pageCountListState; @Override public void open(Configuration parameters) throws Exception { pageCountListState = getRuntimeContext().getListState( new ListStateDescriptor<>("page-count-list", String.class)); } @Override public void processElement(Tuple2 value, Context ctx, Collector out) throws Exception { // 将 "pageUrl|count" 存入 ListState pageCountListState.add(value.f0); // 注册定时器:窗口结束时间 + 1ms,确保同一窗口的所有数据都到齐 ctx.timerService().registerEventTimeTimer(value.f1 + 1); } @Override public void onTimer(long timestamp, OnTimerContext ctx, Collector out) throws Exception { // 从 ListState 中取出所有数据 List> pageCountList = new ArrayList<>(); for (String encoded : pageCountListState.get()) { String[] parts = encoded.split("\\|"); if (parts.length == 2) { pageCountList.add(Tuple2.of(parts[0], Long.parseLong(parts[1]))); } } // 清空 ListState pageCountListState.clear(); // 按访问量降序排序 pageCountList.sort(Comparator.comparingLong((Tuple2 t) -> t.f1).reversed()); // 构建 TopN 结果 List items = new ArrayList<>(); for (int i = 0; i < Math.min(TOP_N, pageCountList.size()); i++) { items.add(TopNResult.RankItem.builder() .rank(i + 1) .name(pageCountList.get(i).f0) .count(pageCountList.get(i).f1) .build()); } out.collect(TopNResult.builder() .rankName("热门页面 Top " + TOP_N) .items(items) .windowEnd(ctx.getCurrentKey()) .build()); } } } ================================================ FILE: flink-learning-project/flink-learning-project-monitor-dashboard/src/main/java/com/zhisheng/project/dashboard/model/PageViewStats.java ================================================ package com.zhisheng.project.dashboard.model; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; /** * 页面访问统计结果 * 用于实时大屏展示 * * @author zhisheng */ @Data @Builder @AllArgsConstructor @NoArgsConstructor public class PageViewStats { /** 页面 URL */ private String pageUrl; /** 页面类别 */ private String pageCategory; /** PV(页面浏览量) */ private Long pv; /** UV(独立访客数) */ private Long uv; /** 平均停留时长(毫秒) */ private Long avgStayDuration; /** 窗口开始时间 */ private Long windowStart; /** 窗口结束时间 */ private Long windowEnd; } ================================================ FILE: flink-learning-project/flink-learning-project-monitor-dashboard/src/main/java/com/zhisheng/project/dashboard/model/TopNResult.java ================================================ package com.zhisheng.project.dashboard.model; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import java.util.List; /** * TopN 排行榜结果 * * @author zhisheng */ @Data @Builder @AllArgsConstructor @NoArgsConstructor public class TopNResult { /** 排行榜名称 */ private String rankName; /** 排行榜数据 */ private List items; /** 窗口结束时间 */ private Long windowEnd; @Data @Builder @AllArgsConstructor @NoArgsConstructor public static class RankItem { /** 排名 */ private Integer rank; /** 页面/项目名称 */ private String name; /** 访问量 */ private Long count; } } ================================================ FILE: flink-learning-project/flink-learning-project-real-time-computing-platform/README.md ================================================ ## 基于 Apache Flink 的实时计算平台 本模块展示了如何使用 Flink SQL 和 Table API 构建实时计算平台,是数据分析师和平台开发者的参考实现。 ### 核心功能 #### 1. FlinkSqlPlatformJob - Flink SQL 方式 - **DDL 建表**:通过 SQL DDL 定义 Kafka Source/Sink 表 - **Watermark 定义**:在 DDL 中声明 Watermark 策略 - **TUMBLE 窗口**:SQL 中的滚动窗口聚合 - **COUNT DISTINCT**:SQL 中的 UV 去重 - **INSERT INTO**:SQL 数据写入 #### 2. TableApiExampleJob - Table API 编程方式 - **流表转换**:DataStream ↔ Table 互相转换 - **编程查询**:filter、select、groupBy、window - **Changelog 流**:动态表转回 DataStream ### Flink SQL vs Table API 对比 | 特性 | Flink SQL | Table API | |------|-----------|-----------| | 使用方式 | 纯 SQL 字符串 | Java/Scala 编程 | | 适用人群 | 数据分析师 | 平台开发者 | | 灵活性 | 中等 | 高 | | 可调试性 | 低 | 高 | | 动态提交 | 容易 | 较难 | ### 涉及的 Flink 知识点 | 知识点 | 说明 | 所在类 | |--------|------|--------| | StreamTableEnvironment | 流表桥接环境 | 全部 | | SQL DDL | 用 SQL 定义表结构和连接器 | FlinkSqlPlatformJob | | Kafka Connector DDL | SQL 中配置 Kafka 连接器 | FlinkSqlPlatformJob | | TUMBLE Window | SQL 滚动窗口函数 | FlinkSqlPlatformJob | | COUNT DISTINCT | SQL UV 去重 | FlinkSqlPlatformJob | | fromDataStream | DataStream 转 Table | TableApiExampleJob | | toChangelogStream | Table 转 DataStream | TableApiExampleJob | | Expression API | $("field") 字段引用 | TableApiExampleJob | ================================================ FILE: flink-learning-project/flink-learning-project-real-time-computing-platform/pom.xml ================================================ flink-learning-project com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 flink-learning-project-real-time-computing-platform com.zhisheng.flink flink-learning-project-common ${project.version} org.apache.flink flink-table-api-java-bridge ${flink.version} provided ================================================ FILE: flink-learning-project/flink-learning-project-real-time-computing-platform/src/main/java/com/zhisheng/project/platform/FlinkSqlPlatformJob.java ================================================ package com.zhisheng.project.platform; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.table.api.EnvironmentSettings; import org.apache.flink.table.api.Table; import org.apache.flink.table.api.TableResult; import org.apache.flink.table.api.bridge.java.StreamTableEnvironment; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * 基于 Flink SQL 的实时计算平台示例 * *

功能描述: * 本作业演示如何使用 Flink SQL / Table API 构建实时计算任务,包含: *

    *
  • 使用 DDL 创建 Kafka Source 表
  • *
  • 使用 DDL 创建 Kafka Sink 表
  • *
  • 使用 SQL 查询进行实时数据处理
  • *
  • Tumble 窗口聚合查询
  • *
  • 动态表与流的转换
  • *
* *

核心知识点: *

    *
  • StreamTableEnvironment:流和表的桥接环境
  • *
  • Flink SQL DDL:用 SQL 定义数据源和数据汇
  • *
  • Connector DDL:Kafka connector 的 SQL 配置方式
  • *
  • TUMBLE 窗口函数:SQL 中的滚动窗口
  • *
  • Watermark 在 DDL 中的定义
  • *
  • Table API:编程式查询
  • *
* *

适用场景: * 在实时计算平台中,用户通过 SQL 方式提交实时计算任务,平台负责解析和执行 * * @author zhisheng */ public class FlinkSqlPlatformJob { private static final Logger LOG = LoggerFactory.getLogger(FlinkSqlPlatformJob.class); public static void main(String[] args) throws Exception { // ========== 1. 创建 StreamTableEnvironment ========== StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.setParallelism(4); EnvironmentSettings settings = EnvironmentSettings.newInstance() .inStreamingMode() .build(); StreamTableEnvironment tableEnv = StreamTableEnvironment.create(env, settings); // ========== 2. 使用 DDL 创建 Kafka Source 表(订单数据) ========== // 知识点:通过 SQL DDL 定义表结构、连接器配置、Watermark 策略 String orderSourceDDL = "CREATE TABLE ods_order (\n" + " order_id STRING,\n" + " user_id STRING,\n" + " product_name STRING,\n" + " category STRING,\n" + " price DOUBLE,\n" + " quantity INT,\n" + " total_amount DOUBLE,\n" + " province STRING,\n" + " create_time BIGINT,\n" + " -- 从 BIGINT 时间戳生成事件时间属性\n" + " ts AS TO_TIMESTAMP_LTZ(create_time, 3),\n" + " -- 定义 Watermark:允许 5 秒乱序\n" + " WATERMARK FOR ts AS ts - INTERVAL '5' SECOND\n" + ") WITH (\n" + " 'connector' = 'kafka',\n" + " 'topic' = 'project-order-topic',\n" + " 'properties.bootstrap.servers' = 'localhost:9092',\n" + " 'properties.group.id' = 'sql-platform-group',\n" + " 'scan.startup.mode' = 'latest-offset',\n" + " 'format' = 'json',\n" + " 'json.fail-on-missing-field' = 'false',\n" + " 'json.ignore-parse-errors' = 'true'\n" + ")"; tableEnv.executeSql(orderSourceDDL); LOG.info("创建 ODS 订单源表成功"); // ========== 3. 使用 DDL 创建 Print Sink 表(用于调试输出) ========== String printSinkDDL = "CREATE TABLE print_sink (\n" + " category STRING,\n" + " window_start TIMESTAMP(3),\n" + " window_end TIMESTAMP(3),\n" + " order_count BIGINT,\n" + " total_amount DOUBLE,\n" + " unique_users BIGINT\n" + ") WITH (\n" + " 'connector' = 'print'\n" + ")"; tableEnv.executeSql(printSinkDDL); LOG.info("创建 Print Sink 表成功"); // ========== 4. 执行 SQL 查询:按类别每 5 分钟统计订单 ========== // 知识点: // - TUMBLE 窗口函数:SQL 中的滚动窗口 // - GROUP BY 窗口 + 维度:多维窗口聚合 // - COUNT(DISTINCT):SQL 中的 UV 去重 String aggregationSQL = "INSERT INTO print_sink\n" + "SELECT\n" + " category,\n" + " TUMBLE_START(ts, INTERVAL '5' MINUTE) AS window_start,\n" + " TUMBLE_END(ts, INTERVAL '5' MINUTE) AS window_end,\n" + " COUNT(*) AS order_count,\n" + " SUM(total_amount) AS total_amount,\n" + " COUNT(DISTINCT user_id) AS unique_users\n" + "FROM ods_order\n" + "WHERE total_amount > 0\n" + "GROUP BY category, TUMBLE(ts, INTERVAL '5' MINUTE)"; LOG.info("执行聚合 SQL:\n{}", aggregationSQL); TableResult result = tableEnv.executeSql(aggregationSQL); // 注意:INSERT INTO 是异步执行的,会返回一个 TableResult // 在流模式下,作业会持续运行直到被取消 result.print(); } } ================================================ FILE: flink-learning-project/flink-learning-project-real-time-computing-platform/src/main/java/com/zhisheng/project/platform/TableApiExampleJob.java ================================================ package com.zhisheng.project.platform; import com.zhisheng.common.utils.GsonUtil; import com.zhisheng.project.common.constant.ProjectConstants; import com.zhisheng.project.common.model.PageAccessEvent; import com.zhisheng.project.common.utils.ProjectKafkaUtil; import org.apache.flink.api.common.eventtime.SerializableTimestampAssigner; import org.apache.flink.api.common.eventtime.WatermarkStrategy; import org.apache.flink.connector.kafka.source.KafkaSource; import org.apache.flink.streaming.api.datastream.DataStream; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.table.api.Expressions; import org.apache.flink.table.api.Table; import org.apache.flink.table.api.Tumble; import org.apache.flink.table.api.bridge.java.StreamTableEnvironment; import org.apache.flink.types.Row; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.time.Duration; import static org.apache.flink.table.api.Expressions.$; import static org.apache.flink.table.api.Expressions.lit; /** * Flink Table API 编程示例 * *

功能描述: * 展示如何使用 Table API(编程方式)进行实时数据处理,与 SQL 方式形成对比 * *

核心知识点: *

    *
  • DataStream 与 Table 的互相转换
  • *
  • Table API 的 select、filter、groupBy、window 操作
  • *
  • Table API 的 Tumble 窗口
  • *
  • Table 转回 DataStream 进行后续处理
  • *
  • Expression API:$("field") 引用字段
  • *
* * @author zhisheng */ public class TableApiExampleJob { private static final Logger LOG = LoggerFactory.getLogger(TableApiExampleJob.class); public static void main(String[] args) throws Exception { StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.setParallelism(4); StreamTableEnvironment tableEnv = StreamTableEnvironment.create(env); // ========== 1. 从 Kafka 消费数据并创建 DataStream ========== KafkaSource kafkaSource = ProjectKafkaUtil.buildKafkaStringSource( ProjectConstants.TOPIC_PAGE_ACCESS, "table-api-example-group"); WatermarkStrategy watermarkStrategy = WatermarkStrategy .forBoundedOutOfOrderness(Duration.ofSeconds(5)) .withTimestampAssigner( (SerializableTimestampAssigner) (event, ts) -> event.getTimestamp()); DataStream accessStream = env .fromSource(kafkaSource, WatermarkStrategy.noWatermarks(), "Page Access Source") .map(json -> GsonUtil.fromJson(json, PageAccessEvent.class)) .assignTimestampsAndWatermarks(watermarkStrategy); // ========== 2. DataStream → Table 转换 ========== // 知识点:fromDataStream 将 DataStream 转换为 Table Table accessTable = tableEnv.fromDataStream(accessStream, $("eventId"), $("userId"), $("pageUrl"), $("pageTitle"), $("pageCategory"), $("channel"), $("stayDuration"), $("timestamp").as("ts"), $("deviceType"), $("region")); // ========== 3. Table API 查询 ========== // 3.1 过滤 + 选择 Table filteredTable = accessTable .filter($("channel").isNotNull()) .select($("pageCategory"), $("channel"), $("userId"), $("stayDuration"), $("region")); // 3.2 分组聚合:按渠道统计 Table channelStats = filteredTable .groupBy($("channel")) .select( $("channel"), $("userId").count().as("pv"), $("stayDuration").avg().as("avg_stay") ); // ========== 4. Table → DataStream 转换 ========== // 知识点:toChangelogStream 将动态表(有更新)转回 DataStream DataStream resultStream = tableEnv.toChangelogStream(channelStats); resultStream.print("channel-stats"); env.execute("Table API 编程示例"); } } ================================================ FILE: flink-learning-project/flink-learning-project-real-time-data-warehouse/README.md ================================================ ## 基于 Apache Flink 的实时数仓建设 本模块实现了实时数据仓库的分层架构(ODS → DWD → DWS),展示了 Flink 在实时数仓场景的典型应用。 ### 数仓分层架构 ``` ┌──────────────────────────────────────────────────────┐ │ ADS(应用层) │ │ 数据服务/API/实时大屏 │ ├──────────────────────────────────────────────────────┤ │ DWS(汇总层) │ │ DwsOrderStatsJob: 按类别、5分钟窗口汇总统计 │ ├──────────────────────────────────────────────────────┤ │ DWD(明细层) │ │ OdsToKafkaJob: 数据清洗、标准化、脏数据分流 │ ├──────────────────────────────────────────────────────┤ │ ODS(原始层) │ │ Kafka 原始订单数据 │ └──────────────────────────────────────────────────────┘ ``` ### 核心功能 #### 1. OdsToKafkaJob - ODS → DWD 数据清洗 - **数据校验**:订单ID、金额等关键字段校验 - **字段标准化**:空值填充、默认值设置 - **脏数据处理**:Side Output 分流到独立通道 - **写入 DWD**:使用 KafkaSink 写入清洗后数据 #### 2. DwsOrderStatsJob - DWD → DWS 汇总统计 - **多维度聚合**:订单数、商品数、销售额、独立用户数 - **UV 去重**:在 AggregateFunction 中使用 Set 去重 - **窗口聚合**:每 5 分钟一个窗口汇总 ### 涉及的 Flink 知识点 | 知识点 | 说明 | 所在类 | |--------|------|--------| | Side Output | 脏数据分流 | OdsToKafkaJob | | KafkaSink | 新版 Kafka Sink API | OdsToKafkaJob | | ProcessFunction | 数据清洗与校验 | OdsToKafkaJob | | AggregateFunction | 复杂累加器多维聚合 | DwsOrderStatsJob | | Set 去重 | UV 去重技术 | DwsOrderStatsJob | | TumblingWindow | 滚动窗口汇总 | DwsOrderStatsJob | ================================================ FILE: flink-learning-project/flink-learning-project-real-time-data-warehouse/pom.xml ================================================ flink-learning-project com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 flink-learning-project-real-time-data-warehouse com.zhisheng.flink flink-learning-project-common ${project.version} org.apache.flink flink-table-api-java-bridge ${flink.version} provided ================================================ FILE: flink-learning-project/flink-learning-project-real-time-data-warehouse/src/main/java/com/zhisheng/project/warehouse/DwsOrderStatsJob.java ================================================ package com.zhisheng.project.warehouse; import com.zhisheng.common.utils.GsonUtil; import com.zhisheng.project.common.constant.ProjectConstants; import com.zhisheng.project.common.utils.ProjectKafkaUtil; import com.zhisheng.project.warehouse.model.OrderDetail; import com.zhisheng.project.warehouse.model.OrderStats; import org.apache.flink.api.common.eventtime.SerializableTimestampAssigner; import org.apache.flink.api.common.eventtime.WatermarkStrategy; import org.apache.flink.api.common.functions.AggregateFunction; import org.apache.flink.api.java.tuple.Tuple4; import org.apache.flink.connector.kafka.source.KafkaSource; import org.apache.flink.streaming.api.datastream.DataStream; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.api.functions.windowing.ProcessWindowFunction; import org.apache.flink.streaming.api.windowing.assigners.TumblingEventTimeWindows; import org.apache.flink.streaming.api.windowing.time.Time; import org.apache.flink.streaming.api.windowing.windows.TimeWindow; import org.apache.flink.util.Collector; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.time.Duration; import java.util.HashSet; import java.util.Set; /** * DWD 层 → DWS 层:订单汇总统计 * *

功能描述: *

    *
  • 从 DWD 层消费清洗后的订单明细数据
  • *
  • 按商品类别分组,每 5 分钟一个窗口进行聚合统计
  • *
  • 计算订单数、商品总量、销售总额、独立用户数等指标
  • *
* *

核心知识点: *

    *
  • 复杂 AggregateFunction:多维度聚合(订单数、商品数、金额、UV)
  • *
  • 在 AggregateFunction 中使用 Set 进行 UV 去重
  • *
  • TumblingEventTimeWindow:基于事件时间的滚动窗口
  • *
  • 数仓 DWS 层设计:按维度汇总的轻度汇总表
  • *
* * @author zhisheng */ public class DwsOrderStatsJob { private static final Logger LOG = LoggerFactory.getLogger(DwsOrderStatsJob.class); public static void main(String[] args) throws Exception { StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.setParallelism(4); // 从 DWD 层消费清洗后的订单数据 KafkaSource dwdSource = ProjectKafkaUtil.buildKafkaStringSource( ProjectConstants.TOPIC_DWD_ORDER_DETAIL, "dws-order-stats-group"); WatermarkStrategy watermarkStrategy = WatermarkStrategy .forBoundedOutOfOrderness(Duration.ofSeconds(5)) .withTimestampAssigner( (SerializableTimestampAssigner) (event, ts) -> event.getCreateTime()); DataStream orderStream = env .fromSource(dwdSource, WatermarkStrategy.noWatermarks(), "DWD Order Source") .map(json -> GsonUtil.fromJson(json, OrderDetail.class)) .assignTimestampsAndWatermarks(watermarkStrategy); // 按商品类别分组,每 5 分钟统计 DataStream statsStream = orderStream .keyBy(OrderDetail::getCategory) .window(TumblingEventTimeWindows.of(Time.minutes(5))) .aggregate(new OrderStatsAggFunction(), new OrderStatsWindowFunction()); statsStream.print("dws-order-stats"); env.execute("DWD → DWS 订单汇总统计"); } /** * 订单汇总增量聚合函数 * *

累加器使用 Tuple4 + Set 存储中间状态: *

    *
  • f0: 订单数 (Long)
  • *
  • f1: 商品总量 (Long)
  • *
  • f2: 总金额 (Double)
  • *
  • f3: 用户去重集合 (Set<String>)
  • *
*/ public static class OrderStatsAggFunction implements AggregateFunction>, Tuple4> { @Override public Tuple4> createAccumulator() { return Tuple4.of(0L, 0L, 0.0, new HashSet<>()); } @Override public Tuple4> add( OrderDetail order, Tuple4> acc) { acc.f0 += 1; // 订单数 +1 acc.f1 += (order.getQuantity() != null ? order.getQuantity() : 0); // 商品数量累加 acc.f2 += (order.getTotalAmount() != null ? order.getTotalAmount() : 0); // 金额累加 acc.f3.add(order.getUserId()); // 用户 ID 去重 return acc; } @Override public Tuple4 getResult( Tuple4> acc) { return Tuple4.of(acc.f0, acc.f1, acc.f2, (long) acc.f3.size()); } @Override public Tuple4> merge( Tuple4> a, Tuple4> b) { a.f0 += b.f0; a.f1 += b.f1; a.f2 += b.f2; a.f3.addAll(b.f3); return a; } } /** * 窗口函数:将聚合结果与窗口信息组合成 OrderStats 对象 */ public static class OrderStatsWindowFunction extends ProcessWindowFunction, OrderStats, String, TimeWindow> { @Override public void process(String category, Context context, Iterable> elements, Collector out) { Tuple4 result = elements.iterator().next(); out.collect(OrderStats.builder() .category(category) .orderCount(result.f0) .totalQuantity(result.f1) .totalAmount(result.f2) .uniqueUserCount(result.f3) .windowStart(context.window().getStart()) .windowEnd(context.window().getEnd()) .build()); } } } ================================================ FILE: flink-learning-project/flink-learning-project-real-time-data-warehouse/src/main/java/com/zhisheng/project/warehouse/OdsToKafkaJob.java ================================================ package com.zhisheng.project.warehouse; import com.zhisheng.common.utils.GsonUtil; import com.zhisheng.project.common.constant.ProjectConstants; import com.zhisheng.project.common.utils.ProjectKafkaUtil; import com.zhisheng.project.warehouse.model.OrderDetail; import org.apache.flink.api.common.eventtime.WatermarkStrategy; import org.apache.flink.api.common.functions.MapFunction; import org.apache.flink.connector.kafka.sink.KafkaSink; import org.apache.flink.connector.kafka.source.KafkaSource; import org.apache.flink.streaming.api.datastream.DataStream; import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.api.functions.ProcessFunction; import org.apache.flink.util.Collector; import org.apache.flink.util.OutputTag; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * ODS 层 → DWD 层:原始数据清洗与标准化 * *

功能描述: *

    *
  • 从 Kafka ODS 层消费原始订单数据
  • *
  • 进行数据清洗(过滤无效数据、标准化字段)
  • *
  • 将清洗后的数据写入 DWD 层的 Kafka Topic
  • *
  • 使用 Side Output 将脏数据分流到专门的 Topic
  • *
* *

核心知识点: *

    *
  • 数据清洗:使用 filter/map 进行 ETL
  • *
  • Side Output:将脏数据分流处理
  • *
  • KafkaSink(新 API):写入 Kafka
  • *
  • 数据质量保证:空值检查、字段标准化
  • *
* *

数仓分层: *

 *   ODS(原始层)→ DWD(明细层)→ DWS(汇总层)→ ADS(应用层)
 *   本作业负责 ODS → DWD 的清洗过程
 * 
* * @author zhisheng */ public class OdsToKafkaJob { private static final Logger LOG = LoggerFactory.getLogger(OdsToKafkaJob.class); /** 脏数据侧输出标签 */ private static final OutputTag DIRTY_DATA_TAG = new OutputTag("dirty-data") {}; public static void main(String[] args) throws Exception { StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.setParallelism(4); // ========== 1. ODS 层:消费原始订单数据 ========== KafkaSource odsSource = ProjectKafkaUtil.buildKafkaStringSource( ProjectConstants.TOPIC_ORDER, "ods-order-group"); // ========== 2. 数据清洗:ODS → DWD ========== SingleOutputStreamOperator processedStream = env .fromSource(odsSource, WatermarkStrategy.noWatermarks(), "ODS Order Source") .process(new ProcessFunction() { @Override public void processElement(String json, Context ctx, Collector out) { try { OrderDetail order = GsonUtil.fromJson(json, OrderDetail.class); // 数据质量校验 if (order.getOrderId() == null || order.getOrderId().isEmpty()) { ctx.output(DIRTY_DATA_TAG, "缺少订单ID: " + json); return; } if (order.getTotalAmount() == null || order.getTotalAmount() < 0) { ctx.output(DIRTY_DATA_TAG, "金额异常: " + json); return; } // 字段标准化 if (order.getProvince() == null || order.getProvince().isEmpty()) { order.setProvince("未知"); } if (order.getPaymentMethod() == null) { order.setPaymentMethod("OTHER"); } out.collect(order); } catch (Exception e) { ctx.output(DIRTY_DATA_TAG, "解析失败: " + json); } } }); // ========== 3. 清洗后的数据写入 DWD 层 Kafka ========== DataStream dwdStream = processedStream .map((MapFunction) GsonUtil::toJson); KafkaSink dwdSink = ProjectKafkaUtil.buildKafkaStringSink( ProjectConstants.TOPIC_DWD_ORDER_DETAIL); dwdStream.sinkTo(dwdSink); // ========== 4. 脏数据单独处理(可写入另一个 Kafka Topic 或日志) ========== DataStream dirtyStream = processedStream.getSideOutput(DIRTY_DATA_TAG); dirtyStream.print("dirty-data"); env.execute("ODS → DWD 数据清洗作业"); } } ================================================ FILE: flink-learning-project/flink-learning-project-real-time-data-warehouse/src/main/java/com/zhisheng/project/warehouse/model/OrderDetail.java ================================================ package com.zhisheng.project.warehouse.model; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; /** * DWD 层 - 订单明细宽表 * 将订单主表和商品信息关联后的宽表模型 * * @author zhisheng */ @Data @Builder @AllArgsConstructor @NoArgsConstructor public class OrderDetail { /** 订单 ID */ private String orderId; /** 用户 ID */ private String userId; /** 商品 ID */ private String productId; /** 商品名称 */ private String productName; /** 商品类别 */ private String category; /** 商品单价 */ private Double price; /** 购买数量 */ private Integer quantity; /** 订单金额 */ private Double totalAmount; /** 支付方式:ALIPAY, WECHAT, CREDIT_CARD */ private String paymentMethod; /** 订单状态:CREATED, PAID, SHIPPED, COMPLETED, CANCELLED */ private String orderStatus; /** 用户所在省份 */ private String province; /** 订单创建时间 */ private Long createTime; } ================================================ FILE: flink-learning-project/flink-learning-project-real-time-data-warehouse/src/main/java/com/zhisheng/project/warehouse/model/OrderStats.java ================================================ package com.zhisheng.project.warehouse.model; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; /** * DWS 层 - 订单统计汇总 * 按类别和时间窗口的订单聚合统计 * * @author zhisheng */ @Data @Builder @AllArgsConstructor @NoArgsConstructor public class OrderStats { /** 商品类别 */ private String category; /** 订单数量 */ private Long orderCount; /** 商品总数量 */ private Long totalQuantity; /** 总销售金额 */ private Double totalAmount; /** 独立用户数 */ private Long uniqueUserCount; /** 窗口开始时间 */ private Long windowStart; /** 窗口结束时间 */ private Long windowEnd; } ================================================ FILE: flink-learning-project/flink-learning-project-risk-management/README.md ================================================ ## 基于 Apache Flink 的实时风控系统 本模块实现了两种风控检测方式,展示了 Flink 在金融风控领域的典型应用。 ### 核心功能 #### 1. FraudDetectionCepJob - CEP 欺诈检测 - **模式匹配**:检测"多次小额试探 + 大额盗刷"的欺诈模式 - **CEP 规则**:3 次以上小额交易(< 10 元)后紧跟大额交易(> 5000 元),5 分钟内 - **超时处理**:模式部分匹配但超时的可疑事件通过侧输出流处理 #### 2. RiskScoreJob - 实时风险评分 - **多因素评分**:综合交易金额异常、交易频率、城市变更三个因素 - **用户画像**:使用 ValueState + MapState 维护用户历史行为画像 - **实时判定**:评分 ≥ 50 为中风险,≥ 80 为高风险 ### 涉及的 Flink 知识点 | 知识点 | 说明 | 所在类 | |--------|------|--------| | Flink CEP | 复杂事件处理库 | FraudDetectionCepJob | | Pattern API | 定义事件匹配模式 | FraudDetectionCepJob | | SimpleCondition | CEP 简单条件 | FraudDetectionCepJob | | PatternSelectFunction | 处理匹配到的事件序列 | FraudDetectionCepJob | | PatternTimeoutFunction | 处理超时的部分匹配 | FraudDetectionCepJob | | OutputTag (Side Output) | 侧输出流处理超时事件 | FraudDetectionCepJob | | ValueState | 键控状态存储单个值 | RiskScoreJob | | MapState | 键控状态存储键值对 | RiskScoreJob | | KeyedProcessFunction | 有状态键控处理函数 | RiskScoreJob | ### 架构设计 ``` ┌────────────────────────┐ ┌───>│ FraudDetectionCepJob │──> 欺诈告警 (RiskEvent) │ │ (CEP Pattern 模式匹配) │──> 超时可疑事件 (Side Output) 交易数据 ────────┤ └────────────────────────┘ (Kafka) │ ┌────────────────────────┐ └───>│ RiskScoreJob │──> 风险评分 > 50 的事件 │ (多因素实时评分) │ └────────────────────────┘ ``` ================================================ FILE: flink-learning-project/flink-learning-project-risk-management/pom.xml ================================================ flink-learning-project com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 flink-learning-project-risk-management com.zhisheng.flink flink-learning-project-common ${project.version} org.apache.flink flink-cep ${flink.version} ================================================ FILE: flink-learning-project/flink-learning-project-risk-management/src/main/java/com/zhisheng/project/risk/FraudDetectionCepJob.java ================================================ package com.zhisheng.project.risk; import com.zhisheng.common.utils.GsonUtil; import com.zhisheng.project.common.constant.ProjectConstants; import com.zhisheng.project.common.model.TransactionEvent; import com.zhisheng.project.common.utils.ProjectKafkaUtil; import com.zhisheng.project.risk.model.RiskEvent; import org.apache.flink.api.common.eventtime.SerializableTimestampAssigner; import org.apache.flink.api.common.eventtime.WatermarkStrategy; import org.apache.flink.cep.CEP; import org.apache.flink.cep.PatternSelectFunction; import org.apache.flink.cep.PatternStream; import org.apache.flink.cep.PatternTimeoutFunction; import org.apache.flink.cep.pattern.Pattern; import org.apache.flink.cep.pattern.conditions.SimpleCondition; import org.apache.flink.connector.kafka.source.KafkaSource; import org.apache.flink.streaming.api.datastream.DataStream; import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.api.windowing.time.Time; import org.apache.flink.util.OutputTag; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.time.Duration; import java.util.List; import java.util.Map; import java.util.UUID; import java.util.stream.Collectors; /** * 基于 Flink CEP 的实时欺诈检测系统 * *

功能描述: * 使用复杂事件处理(CEP)检测交易中的异常行为模式 * *

检测规则 - 短时间内连续小额交易后跟随大额交易: *

    *
  1. 用户在 5 分钟内进行 3 次以上小额交易(< 10 元)
  2. *
  3. 紧接着进行一笔大额交易(> 5000 元)
  4. *
  5. 这种模式通常是欺诈者在试探账户后进行大额盗刷
  6. *
* *

核心知识点: *

    *
  • Flink CEP:复杂事件处理库
  • *
  • Pattern API:定义事件匹配模式
  • *
  • SimpleCondition:简单条件过滤
  • *
  • Pattern 量词:times()、oneOrMore()、timesOrMore()
  • *
  • Pattern 时间约束:within() 设置模式匹配的时间窗口
  • *
  • 超时处理:处理部分匹配但超时的事件
  • *
* * @author zhisheng */ public class FraudDetectionCepJob { private static final Logger LOG = LoggerFactory.getLogger(FraudDetectionCepJob.class); /** 小额交易阈值 */ private static final double SMALL_AMOUNT_THRESHOLD = 10.0; /** 大额交易阈值 */ private static final double LARGE_AMOUNT_THRESHOLD = 5000.0; /** 超时事件侧输出标签 */ private static final OutputTag TIMEOUT_TAG = new OutputTag("timeout") {}; public static void main(String[] args) throws Exception { StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.setParallelism(4); KafkaSource kafkaSource = ProjectKafkaUtil.buildKafkaStringSource( ProjectConstants.TOPIC_TRANSACTION, "fraud-detection-group"); WatermarkStrategy watermarkStrategy = WatermarkStrategy .forBoundedOutOfOrderness(Duration.ofSeconds(5)) .withTimestampAssigner( (SerializableTimestampAssigner) (event, ts) -> event.getTimestamp()); DataStream transactionStream = env .fromSource(kafkaSource, WatermarkStrategy.noWatermarks(), "Transaction Source") .map(json -> GsonUtil.fromJson(json, TransactionEvent.class)) .assignTimestampsAndWatermarks(watermarkStrategy) // 按用户 ID 分组,检测每个用户的交易模式 .keyBy(TransactionEvent::getUserId); // ========== 定义 CEP 模式 ========== // 模式:3 次以上小额交易 → 1 次大额交易,在 5 分钟内完成 Pattern fraudPattern = Pattern .begin("small-amounts") .where(new SimpleCondition() { @Override public boolean filter(TransactionEvent event) { return event.getAmount() < SMALL_AMOUNT_THRESHOLD; } }) .timesOrMore(3) // 3 次或更多小额交易 .greedy() // 贪婪匹配,尽可能多匹配 .followedBy("large-amount") // 后面跟着一笔大额交易 .where(new SimpleCondition() { @Override public boolean filter(TransactionEvent event) { return event.getAmount() > LARGE_AMOUNT_THRESHOLD; } }) .within(Time.minutes(5)); // 整个模式在 5 分钟内完成 // ========== 应用 CEP 模式到数据流 ========== PatternStream patternStream = CEP.pattern(transactionStream, fraudPattern); // ========== 处理匹配结果和超时事件 ========== SingleOutputStreamOperator fraudStream = patternStream.select( TIMEOUT_TAG, // 处理超时事件(只匹配到小额交易,没等到大额交易) (PatternTimeoutFunction) (pattern, timeoutTimestamp) -> { List smallTxns = pattern.get("small-amounts"); String txnIds = smallTxns.stream() .map(TransactionEvent::getTransactionId) .collect(Collectors.joining(",")); return RiskEvent.builder() .riskId(UUID.randomUUID().toString()) .userId(smallTxns.get(0).getUserId()) .riskType("SUSPICIOUS") .riskLevel("MEDIUM") .description(String.format("用户在 5 分钟内进行了 %d 次小额交易(模式超时,未检测到大额交易)", smallTxns.size())) .amount(smallTxns.stream().mapToDouble(TransactionEvent::getAmount).sum()) .timestamp(timeoutTimestamp) .relatedTransactionIds(txnIds) .build(); }, // 处理完全匹配的事件(小额 + 大额) (PatternSelectFunction) pattern -> { List smallTxns = pattern.get("small-amounts"); TransactionEvent largeTxn = pattern.get("large-amount").get(0); String allTxnIds = smallTxns.stream() .map(TransactionEvent::getTransactionId) .collect(Collectors.joining(",")) + "," + largeTxn.getTransactionId(); return RiskEvent.builder() .riskId(UUID.randomUUID().toString()) .userId(largeTxn.getUserId()) .riskType("FRAUD") .riskLevel("HIGH") .description(String.format( "疑似盗刷:用户先进行了 %d 次小额试探交易,随后进行了 %.2f 元的大额交易", smallTxns.size(), largeTxn.getAmount())) .amount(largeTxn.getAmount()) .timestamp(largeTxn.getTimestamp()) .relatedTransactionIds(allTxnIds) .build(); } ); // 输出欺诈检测结果 fraudStream.print("fraud-detected"); // 输出超时的可疑事件 DataStream timeoutStream = fraudStream.getSideOutput(TIMEOUT_TAG); timeoutStream.print("suspicious-timeout"); env.execute("CEP 欺诈检测系统"); } } ================================================ FILE: flink-learning-project/flink-learning-project-risk-management/src/main/java/com/zhisheng/project/risk/RiskScoreJob.java ================================================ package com.zhisheng.project.risk; import com.zhisheng.common.utils.GsonUtil; import com.zhisheng.project.common.constant.ProjectConstants; import com.zhisheng.project.common.model.TransactionEvent; import com.zhisheng.project.common.utils.ProjectKafkaUtil; import com.zhisheng.project.risk.model.RiskEvent; import org.apache.flink.api.common.eventtime.SerializableTimestampAssigner; import org.apache.flink.api.common.eventtime.WatermarkStrategy; import org.apache.flink.api.common.state.MapState; import org.apache.flink.api.common.state.MapStateDescriptor; import org.apache.flink.api.common.state.ValueState; import org.apache.flink.api.common.state.ValueStateDescriptor; import org.apache.flink.configuration.Configuration; import org.apache.flink.connector.kafka.source.KafkaSource; import org.apache.flink.streaming.api.datastream.DataStream; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.api.functions.KeyedProcessFunction; import org.apache.flink.util.Collector; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.time.Duration; import java.util.UUID; /** * 实时风险评分系统 * *

功能描述: * 对每笔交易进行实时风险评分,综合考虑多个风险因素: *

    *
  • 交易金额异常:与用户历史平均金额相比的偏离程度
  • *
  • 交易频率:短时间内交易次数
  • *
  • IP/城市变更:交易地点突然变化
  • *
* *

核心知识点: *

    *
  • 多种 Keyed State 组合使用(ValueState + MapState)
  • *
  • ValueState:存储单个值(交易计数、金额总和)
  • *
  • MapState:存储键值对(IP 地址、城市出现次数)
  • *
  • KeyedProcessFunction:有状态的键控处理函数
  • *
  • 实时评分算法:多因素加权评分
  • *
* * @author zhisheng */ public class RiskScoreJob { private static final Logger LOG = LoggerFactory.getLogger(RiskScoreJob.class); /** 高风险阈值 */ private static final double HIGH_RISK_THRESHOLD = 80.0; /** 中风险阈值 */ private static final double MEDIUM_RISK_THRESHOLD = 50.0; public static void main(String[] args) throws Exception { StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.setParallelism(4); KafkaSource kafkaSource = ProjectKafkaUtil.buildKafkaStringSource( ProjectConstants.TOPIC_TRANSACTION, "risk-score-group"); WatermarkStrategy watermarkStrategy = WatermarkStrategy .forBoundedOutOfOrderness(Duration.ofSeconds(5)) .withTimestampAssigner( (SerializableTimestampAssigner) (event, ts) -> event.getTimestamp()); DataStream transactionStream = env .fromSource(kafkaSource, WatermarkStrategy.noWatermarks(), "Transaction Source") .map(json -> GsonUtil.fromJson(json, TransactionEvent.class)) .assignTimestampsAndWatermarks(watermarkStrategy); // 按用户 ID 分组,进行风险评分 DataStream riskStream = transactionStream .keyBy(TransactionEvent::getUserId) .process(new RiskScoreFunction()); riskStream.print("risk-score"); env.execute("实时风险评分系统"); } /** * 风险评分函数 * *

使用多种 Keyed State 维护用户的历史行为画像: *

    *
  • transactionCountState:用户历史交易次数
  • *
  • totalAmountState:用户历史交易总金额
  • *
  • cityFrequencyState:用户在各城市的交易频次
  • *
  • lastTransactionTimeState:上次交易时间
  • *
*/ public static class RiskScoreFunction extends KeyedProcessFunction { /** 交易次数状态 */ private transient ValueState transactionCountState; /** 交易总金额状态 */ private transient ValueState totalAmountState; /** 城市交易频次状态(MapState 示例) */ private transient MapState cityFrequencyState; /** 上次交易时间状态 */ private transient ValueState lastTransactionTimeState; @Override public void open(Configuration parameters) throws Exception { // 注册各种 State transactionCountState = getRuntimeContext().getState( new ValueStateDescriptor<>("txn-count", Long.class)); totalAmountState = getRuntimeContext().getState( new ValueStateDescriptor<>("total-amount", Double.class)); cityFrequencyState = getRuntimeContext().getMapState( new MapStateDescriptor<>("city-frequency", String.class, Long.class)); lastTransactionTimeState = getRuntimeContext().getState( new ValueStateDescriptor<>("last-txn-time", Long.class)); } @Override public void processElement(TransactionEvent txn, Context ctx, Collector out) throws Exception { // ========== 1. 更新用户画像状态 ========== Long count = transactionCountState.value(); long newCount = (count == null ? 0 : count) + 1; transactionCountState.update(newCount); Double totalAmount = totalAmountState.value(); double newTotalAmount = (totalAmount == null ? 0.0 : totalAmount) + txn.getAmount(); totalAmountState.update(newTotalAmount); // 读取城市交易频次(更新前),用于后续风险评分 String city = txn.getCity() != null ? txn.getCity() : "unknown"; Long cityCount = cityFrequencyState.get(city); long previousCityCount = cityCount == null ? 0 : cityCount; // 更新城市交易频次 cityFrequencyState.put(city, previousCityCount + 1); // ========== 2. 计算风险评分(0-100 分,分越高越危险) ========== double riskScore = 0.0; // 因素 1:金额异常度(权重 40%) // 如果交易金额超过历史平均的 3 倍,得满分 if (newCount > 1) { double avgAmount = newTotalAmount / newCount; double amountRatio = txn.getAmount() / Math.max(avgAmount, 1.0); // 金额比率超过 3 倍,得满分 40 分;1~3 倍按比例得分 riskScore += Math.min(40.0, (amountRatio - 1) * 20.0); } // 因素 2:交易频率(权重 30%) // 如果距离上次交易不到 1 分钟,得满分 Long lastTime = lastTransactionTimeState.value(); if (lastTime != null) { long interval = txn.getTimestamp() - lastTime; if (interval < 60_000) { // 不到 1 分钟 riskScore += 30.0; } else if (interval < 300_000) { // 不到 5 分钟 riskScore += 15.0; } } lastTransactionTimeState.update(txn.getTimestamp()); // 因素 3:城市变更(权重 30%) // 如果用户从未在此城市交易过(更新前计数为 0),加分 if (previousCityCount == 0 && newCount > 3) { riskScore += 30.0; } // 确保风险评分在 0-100 之间 riskScore = Math.max(0, Math.min(100, riskScore)); // ========== 3. 根据风险评分决定是否输出风险事件 ========== if (riskScore >= MEDIUM_RISK_THRESHOLD) { String riskLevel; if (riskScore >= HIGH_RISK_THRESHOLD) { riskLevel = "HIGH"; } else { riskLevel = "MEDIUM"; } out.collect(RiskEvent.builder() .riskId(UUID.randomUUID().toString()) .userId(txn.getUserId()) .riskType("SUSPICIOUS") .riskLevel(riskLevel) .description(String.format( "交易风险评分 %.1f:金额=%.2f, 城市=%s, 交易次数=%d", riskScore, txn.getAmount(), city, newCount)) .amount(txn.getAmount()) .timestamp(txn.getTimestamp()) .relatedTransactionIds(txn.getTransactionId()) .build()); } } } } ================================================ FILE: flink-learning-project/flink-learning-project-risk-management/src/main/java/com/zhisheng/project/risk/model/RiskEvent.java ================================================ package com.zhisheng.project.risk.model; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; /** * 风险事件模型 * 风控系统检测到异常时输出的事件 * * @author zhisheng */ @Data @Builder @AllArgsConstructor @NoArgsConstructor public class RiskEvent { /** 风险事件 ID */ private String riskId; /** 用户 ID */ private String userId; /** 风险类型:FRAUD(欺诈), SUSPICIOUS(可疑), HIGH_FREQUENCY(高频) */ private String riskType; /** 风险等级:LOW, MEDIUM, HIGH */ private String riskLevel; /** 风险描述 */ private String description; /** 触发风险的交易金额 */ private Double amount; /** 检测时间戳 */ private Long timestamp; /** 关联的交易 ID 列表(逗号分隔) */ private String relatedTransactionIds; } ================================================ FILE: flink-learning-project/pom.xml ================================================ flink-learning com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 flink-learning-project pom flink-learning-project-deduplication flink-learning-project-common flink-learning-project-monitor-alert flink-learning-project-real-time-data-warehouse flink-learning-project-log flink-learning-project-risk-management flink-learning-project-monitor-dashboard flink-learning-project-flink-job-scaffold flink-learning-project-real-time-computing-platform ================================================ FILE: flink-learning-sql/README.md ================================================ ### Flink-learning-sql Flink Table API & SQL + [flink sql ago](./flink-learning-sql-ago) + [flink sql blink](./flink-learning-sql-blink) https://github.com/ververica/sql-training ================================================ FILE: flink-learning-sql/flink-learning-sql-blink/README.md ================================================ ### flink-learning-sql-blink 添加 Blink planner 的依赖: ```xml org.apache.flink flink-table-planner_${scala.binary.version} ${flink.version} ``` ================================================ FILE: flink-learning-sql/flink-learning-sql-blink/pom.xml ================================================ flink-learning-sql com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 flink-learning-sql-blink com.zhisheng.flink flink-learning-sql-common ${project.version} org.apache.flink flink-table-planner_${scala.binary.version} ${flink.version} org.apache.flink flink-connector-hive_${scala.binary.version} ${flink.version} org.apache.flink flink-json ${flink.version} org.apache.flink flink-connector-elasticsearch6 ${flink-connector-elasticsearch6.version} org.apache.flink flink-connector-hbase-2.2 ${flink-connector-hbase.version} org.apache.hadoop hadoop-common 3.4.0 org.apache.flink flink-connector-jdbc ${flink-connector-jdbc.version} org.postgresql postgresql 42.3.3 org.apache.maven.plugins maven-shade-plugin 3.1.0 false package shade com.zhisheng.sql.blink reference.conf *:*:*:* META-INF/*.SF META-INF/*.DSA META-INF/*.RSA ================================================ FILE: flink-learning-sql/flink-learning-sql-blink/src/main/java/com/zhisheng/sql/blink/stream/catalog/CatalogAPI.java ================================================ package com.zhisheng.sql.blink.stream.catalog; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.table.api.EnvironmentSettings; import org.apache.flink.table.api.bridge.java.StreamTableEnvironment; /** * Desc: Catalog API * Created by zhisheng on 2020-01-26 21:45 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class CatalogAPI { public static void main(String[] args) { StreamExecutionEnvironment blinkStreamEnv = StreamExecutionEnvironment.getExecutionEnvironment(); blinkStreamEnv.setParallelism(1); EnvironmentSettings blinkStreamSettings = EnvironmentSettings.newInstance() .build(); StreamTableEnvironment blinkStreamTableEnv = StreamTableEnvironment.create(blinkStreamEnv, blinkStreamSettings); //Changing the Current Catalog And Database blinkStreamTableEnv.useCatalog("zhisheng"); blinkStreamTableEnv.useDatabase("zhisheng"); blinkStreamTableEnv.scan("not_the_current_catalog", "not_the_current_db", "zhisheng"); //List Available Catalogs/Databases/Tables blinkStreamTableEnv.listCatalogs(); blinkStreamTableEnv.listDatabases(); blinkStreamTableEnv.listTables(); } } ================================================ FILE: flink-learning-sql/flink-learning-sql-blink/src/main/java/com/zhisheng/sql/blink/stream/catalog/CatalogTypes.java ================================================ package com.zhisheng.sql.blink.stream.catalog; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.table.api.EnvironmentSettings; import org.apache.flink.table.api.bridge.java.StreamTableEnvironment; import org.apache.flink.table.catalog.GenericInMemoryCatalog; /** * Desc: Catalog Types * Created by zhisheng on 2020-01-26 21:30 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class CatalogTypes { public static void main(String[] args) { StreamExecutionEnvironment blinkStreamEnv = StreamExecutionEnvironment.getExecutionEnvironment(); blinkStreamEnv.setParallelism(1); EnvironmentSettings blinkStreamSettings = EnvironmentSettings.newInstance() .build(); StreamTableEnvironment blinkStreamTableEnv = StreamTableEnvironment.create(blinkStreamEnv, blinkStreamSettings); blinkStreamTableEnv.registerCatalog("zhisheng", new GenericInMemoryCatalog("zhisheng")); //GenericInMemoryCatalog,默认的 catalog //HiveCatalog,这个需要添加 Hive connector 和 Hive 的依赖 // blinkStreamTableEnv.registerCatalog("zhisheng", new HiveCatalog("zhisheng", "zhisheng", "~/zhisheng/hive/conf", "2.3.4")); } } ================================================ FILE: flink-learning-sql/flink-learning-sql-blink/src/main/java/com/zhisheng/sql/blink/stream/example/FlinkSQLDistinctExample.java ================================================ package com.zhisheng.sql.blink.stream.example; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.table.api.EnvironmentSettings; import org.apache.flink.table.api.Table; import org.apache.flink.table.api.bridge.java.StreamTableEnvironment; import org.apache.flink.types.Row; /** * Desc: flink sql distinct & count demo * Created by zhisheng on 2020-03-18 23:41 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class FlinkSQLDistinctExample { public static void main(String[] args) throws Exception { StreamExecutionEnvironment blinkStreamEnv = StreamExecutionEnvironment.getExecutionEnvironment(); blinkStreamEnv.setParallelism(1); EnvironmentSettings blinkStreamSettings = EnvironmentSettings.newInstance() .build(); StreamTableEnvironment blinkStreamTableEnv = StreamTableEnvironment.create(blinkStreamEnv, blinkStreamSettings); String ddlSource = "CREATE TABLE user_behavior (\n" + " user_id BIGINT,\n" + " item_id BIGINT,\n" + " category_id BIGINT,\n" + " behavior STRING,\n" + " ts TIMESTAMP(3)\n" + ") WITH (\n" + " 'connector.type' = 'kafka',\n" + " 'connector.version' = '0.11',\n" + " 'connector.topic' = 'user_behavior',\n" + " 'connector.startup-mode' = 'latest-offset',\n" + " 'connector.properties.zookeeper.connect' = 'localhost:2181',\n" + " 'connector.properties.bootstrap.servers' = 'localhost:9092',\n" + " 'format.type' = 'json'\n" + ")"; String countSql = "select user_id, count(user_id) from user_behavior group by user_id"; blinkStreamTableEnv.executeSql(ddlSource); Table countTable = blinkStreamTableEnv.sqlQuery(countSql); blinkStreamTableEnv.toRetractStream(countTable, Row.class).print(); String distinctSql = "select distinct(user_id) from user_behavior"; Table distinctTable = blinkStreamTableEnv.sqlQuery(distinctSql); blinkStreamTableEnv.toRetractStream(distinctTable, Row.class).print("=="); blinkStreamEnv.execute("Blink Stream SQL count/distinct demo"); } } ================================================ FILE: flink-learning-sql/flink-learning-sql-blink/src/main/java/com/zhisheng/sql/blink/stream/example/SQLExampleData2PG.java ================================================ package com.zhisheng.sql.blink.stream.example; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.table.api.EnvironmentSettings; import org.apache.flink.table.api.bridge.java.StreamTableEnvironment; /** * Desc: sink in PG * Created by zhisheng on 2020-03-19 08:36 */ public class SQLExampleData2PG { public static void main(String[] args) throws Exception { StreamExecutionEnvironment blinkStreamEnv = StreamExecutionEnvironment.getExecutionEnvironment(); blinkStreamEnv.setParallelism(1); EnvironmentSettings blinkStreamSettings = EnvironmentSettings.newInstance() .build(); StreamTableEnvironment blinkStreamTableEnv = StreamTableEnvironment.create(blinkStreamEnv, blinkStreamSettings); String ddlSource = "CREATE TABLE user_behavior (\n" + " score numeric(38, 18)\n" + ") WITH (\n" + " 'connector.type' = 'kafka',\n" + " 'connector.version' = '0.11',\n" + " 'connector.topic' = 'user_behavior',\n" + " 'connector.startup-mode' = 'latest-offset',\n" + " 'connector.properties.zookeeper.connect' = 'localhost:2181',\n" + " 'connector.properties.bootstrap.servers' = 'localhost:9092',\n" + " 'format.type' = 'json'\n" + ")"; String ddlSink = "CREATE TABLE user_behavior_aggregate (\n" + " score numeric(38, 18)\n" + ") WITH (\n" + " 'connector.type' = 'jdbc',\n" + " 'connector.driver' = 'org.postgresql.Driver',\n" + " 'connector.url' = 'jdbc:postgresql://localhost:3600/user',\n" + " 'connector.table' = 't_hitch_user_ltv_aggregate', \n" + " 'connector.username' = 'user', \n" + " 'connector.password' = 'user',\n" + " 'connector.write.flush.max-rows' = '1' \n" + ")"; String sql = "insert into user_behavior_aggregate select yidun_score from user_behavior"; blinkStreamTableEnv.executeSql(ddlSource); blinkStreamTableEnv.executeSql(ddlSink); blinkStreamTableEnv.executeSql(sql).await(); } } ================================================ FILE: flink-learning-sql/flink-learning-sql-blink/src/main/java/com/zhisheng/sql/blink/stream/example/SQLExampleKafkaData2ES.java ================================================ package com.zhisheng.sql.blink.stream.example; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.table.api.EnvironmentSettings; import org.apache.flink.table.api.bridge.java.StreamTableEnvironment; /** * Desc: Blink Stream SQL Job, 读取 Kafka 数据,然后写入到 ES 6 和 ES 7 * Created by zhisheng on 2019/11/3 下午1:14 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class SQLExampleKafkaData2ES { public static void main(String[] args) throws Exception { StreamExecutionEnvironment blinkStreamEnv = StreamExecutionEnvironment.getExecutionEnvironment(); blinkStreamEnv.setParallelism(1); EnvironmentSettings blinkStreamSettings = EnvironmentSettings.newInstance() .build(); StreamTableEnvironment blinkStreamTableEnv = StreamTableEnvironment.create(blinkStreamEnv, blinkStreamSettings); String ddlSource = "CREATE TABLE user_behavior (\n" + " user_id BIGINT,\n" + " item_id BIGINT,\n" + " category_id BIGINT,\n" + " behavior STRING,\n" + " ts TIMESTAMP(3)\n" + ") WITH (\n" + " 'connector.type' = 'kafka',\n" + " 'connector.version' = '0.11',\n" + " 'connector.topic' = 'user_behavior',\n" + " 'connector.startup-mode' = 'latest-offset',\n" + " 'connector.properties.zookeeper.connect' = 'localhost:2181',\n" + " 'connector.properties.bootstrap.servers' = 'localhost:9092',\n" + " 'format.type' = 'json'\n" + ")"; String ddlSink = "CREATE TABLE user_behavior_es (\n" + " user_id BIGINT,\n" + " item_id BIGINT\n" + ") WITH (\n" + " 'connector.type' = 'elasticsearch',\n" + " 'connector.version' = '6',\n" + " 'connector.hosts' = 'http://localhost:9200',\n" + " 'connector.index' = 'user_behavior_es',\n" + " 'connector.document-type' = 'user_behavior_es',\n" + " 'format.type' = 'json',\n" + " 'update-mode' = 'append',\n" + " 'connector.bulk-flush.max-actions' = '10'\n" + ")"; //提取读取到的数据,然后只要两个字段,写入到 ES String sql = "insert into user_behavior_es select user_id, item_id from user_behavior"; System.out.println(ddlSource); System.out.println(ddlSink); blinkStreamTableEnv.executeSql(ddlSource); blinkStreamTableEnv.executeSql(ddlSink); blinkStreamTableEnv.executeSql(sql).await(); } } ================================================ FILE: flink-learning-sql/flink-learning-sql-blink/src/main/java/com/zhisheng/sql/blink/stream/example/SQLExampleKafkaData2HBase.java ================================================ package com.zhisheng.sql.blink.stream.example; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.table.api.EnvironmentSettings; import org.apache.flink.table.api.bridge.java.StreamTableEnvironment; /** * Desc: Blink Stream SQL Job, 读取 Kafka 数据,然后写入到 HBase * Created by zhisheng on 2019/11/3 下午1:14 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class SQLExampleKafkaData2HBase { public static void main(String[] args) throws Exception { StreamExecutionEnvironment blinkStreamEnv = StreamExecutionEnvironment.getExecutionEnvironment(); blinkStreamEnv.setParallelism(1); EnvironmentSettings blinkStreamSettings = EnvironmentSettings.newInstance() .build(); StreamTableEnvironment blinkStreamTableEnv = StreamTableEnvironment.create(blinkStreamEnv, blinkStreamSettings); String ddlSource = "CREATE TABLE user_behavior (\n" + " user_id BIGINT,\n" + " item_id BIGINT,\n" + " category_id BIGINT,\n" + " behavior STRING,\n" + " ts TIMESTAMP(3)\n" + ") WITH (\n" + " 'connector.type' = 'kafka',\n" + " 'connector.version' = '0.11',\n" + " 'connector.topic' = 'user_behavior',\n" + " 'connector.startup-mode' = 'latest-offset',\n" + " 'connector.properties.zookeeper.connect' = 'localhost:2181',\n" + " 'connector.properties.bootstrap.servers' = 'localhost:9092',\n" + " 'format.type' = 'json'\n" + ")"; String ddlSink = "CREATE TABLE user_behavior_hbase (\n" + " rowkey BIGINT,\n" + " cf ROW\n" + ") WITH (\n" + " 'connector.type' = 'hbase',\n" + " 'connector.version' = '1.4.3',\n" + " 'connector.table-name' = 'zhisheng01',\n" + " 'connector.zookeeper.quorum' = 'localhost:2181',\n" + " 'connector.zookeeper.znode.parent' = '/hbase',\n" + " 'connector.write.buffer-flush.max-size' = '2mb',\n" + " 'connector.write.buffer-flush.max-rows' = '1000',\n" + " 'connector.write.buffer-flush.interval' = '2s'\n" + ")"; //提取读取到的数据,然后只要两个字段,写入到 HBase String sql = "insert into user_behavior_hbase select user_id, ROW(item_id, category_id) from user_behavior"; System.out.println(ddlSource); System.out.println(ddlSink); blinkStreamTableEnv.executeSql(ddlSource); blinkStreamTableEnv.executeSql(ddlSink); blinkStreamTableEnv.executeSql(sql).await(); } } ================================================ FILE: flink-learning-sql/flink-learning-sql-blink/src/main/java/com/zhisheng/sql/blink/stream/example/SQLExampleKafkaData2Kafka.java ================================================ package com.zhisheng.sql.blink.stream.example; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.table.api.EnvironmentSettings; import org.apache.flink.table.api.bridge.java.StreamTableEnvironment; /** * Desc: Blink Stream SQL Job, 读取 Kafka 数据,然后写入到 Kafka * Created by zhisheng on 2019/11/3 下午1:14 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class SQLExampleKafkaData2Kafka { public static void main(String[] args) throws Exception { StreamExecutionEnvironment blinkStreamEnv = StreamExecutionEnvironment.getExecutionEnvironment(); blinkStreamEnv.setParallelism(1); EnvironmentSettings blinkStreamSettings = EnvironmentSettings.newInstance() .build(); StreamTableEnvironment blinkStreamTableEnv = StreamTableEnvironment.create(blinkStreamEnv, blinkStreamSettings); String ddlSource = "CREATE TABLE user_behavior (\n" + " user_id BIGINT,\n" + " item_id BIGINT,\n" + " category_id BIGINT,\n" + " behavior STRING,\n" + " ts TIMESTAMP(3)\n" + ") WITH (\n" + " 'connector.type' = 'kafka',\n" + " 'connector.version' = '0.11',\n" + " 'connector.topic' = 'user_behavior',\n" + " 'connector.startup-mode' = 'latest-offset',\n" + " 'connector.properties.zookeeper.connect' = 'localhost:2181',\n" + " 'connector.properties.bootstrap.servers' = 'localhost:9092',\n" + " 'format.type' = 'json'\n" + ")"; String ddlSink = "CREATE TABLE user_behavior_sink (\n" + " user_id BIGINT,\n" + " item_id BIGINT\n" + ") WITH (\n" + " 'connector.type' = 'kafka',\n" + " 'connector.version' = '0.11',\n" + " 'connector.topic' = 'user_behavior_sink',\n" + " 'connector.properties.zookeeper.connect' = 'localhost:2181',\n" + " 'connector.properties.bootstrap.servers' = 'localhost:9092',\n" + " 'format.type' = 'json',\n" + " 'update-mode' = 'append'\n" + ")"; //提取读取到的数据,然后只要两个字段,重新发送到 Kafka 新 topic String sql = "insert into user_behavior_sink select user_id, item_id from user_behavior"; System.out.println(ddlSource); System.out.println(ddlSink); blinkStreamTableEnv.executeSql(ddlSource); blinkStreamTableEnv.executeSql(ddlSink); blinkStreamTableEnv.executeSql(sql).await(); } } ================================================ FILE: flink-learning-sql/flink-learning-sql-blink/src/main/java/com/zhisheng/sql/blink/stream/example/SQLExampleKafkaRowData2ES.java ================================================ package com.zhisheng.sql.blink.stream.example; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.table.api.EnvironmentSettings; import org.apache.flink.table.api.bridge.java.StreamTableEnvironment; /** * Desc: Blink Stream SQL Job, 读取 Kafka 嵌套 JSON 数据,然后写入到 ES 6 和 ES 7 *

* Created by zhisheng on 2019/11/3 下午1:14 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ /* 嵌套 JSON 数据如下: { "userDetail": { "userId": 666, "name": "zhisheng", "age": 18 }, "item_id": 890, "category_id": 210, "behavior": "pv" } */ public class SQLExampleKafkaRowData2ES { public static void main(String[] args) throws Exception { StreamExecutionEnvironment blinkStreamEnv = StreamExecutionEnvironment.getExecutionEnvironment(); blinkStreamEnv.setParallelism(1); EnvironmentSettings blinkStreamSettings = EnvironmentSettings.newInstance() .build(); StreamTableEnvironment blinkStreamTableEnv = StreamTableEnvironment.create(blinkStreamEnv, blinkStreamSettings); String ddlSource = "CREATE TABLE user_behavior (\n" + " userDetail Row,\n" + " item_id BIGINT,\n" + " category_id BIGINT,\n" + " behavior STRING\n" + ") WITH (\n" + " 'connector.type' = 'kafka',\n" + " 'connector.version' = '0.11',\n" + " 'connector.topic' = 'user_behavior',\n" + " 'connector.startup-mode' = 'latest-offset',\n" + " 'connector.properties.zookeeper.connect' = 'localhost:2181',\n" + " 'connector.properties.bootstrap.servers' = 'localhost:9092',\n" + " 'format.type' = 'json'\n" + ")"; String ddlSink = "CREATE TABLE user_behavior_es (\n" + " user_id BIGINT,\n" + " item_id BIGINT\n" + ") WITH (\n" + " 'connector.type' = 'elasticsearch',\n" + " 'connector.version' = '6',\n" + " 'connector.hosts' = 'http://localhost:9200',\n" + " 'connector.index' = 'user_behavior_es',\n" + " 'connector.document-type' = 'user_behavior_es',\n" + " 'format.type' = 'json',\n" + " 'update-mode' = 'append',\n" + " 'connector.bulk-flush.max-actions' = '10'\n" + ")"; //提取读取到的数据,然后只要两个字段,写入到 ES String sql = "insert into user_behavior_es select userDetail.userId, item_id from user_behavior"; System.out.println(ddlSource); System.out.println(ddlSink); blinkStreamTableEnv.executeSql(ddlSource); blinkStreamTableEnv.executeSql(ddlSink); blinkStreamTableEnv.executeSql(sql).await(); } } ================================================ FILE: flink-learning-sql/flink-learning-sql-blink/src/main/java/com/zhisheng/sql/blink/stream/example/StreamWindowSQLExample.java ================================================ package com.zhisheng.sql.blink.stream.example; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.table.api.EnvironmentSettings; import org.apache.flink.table.api.Table; import org.apache.flink.table.api.bridge.java.StreamTableEnvironment; import org.apache.flink.types.Row; import org.apache.flink.util.FileUtils; import java.io.File; import java.io.IOException; /** * Simple example for demonstrating the use of SQL in Java. * *

Usage: {@code ./bin/flink run ./examples/table/StreamWindowSQLExample.jar} * *

This example shows how to: * - Register a table via DDL * - Declare an event time attribute in the DDL * - Run a streaming window aggregate on the registered table */ public class StreamWindowSQLExample { public static void main(String[] args) throws Exception { // set up execution environment StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); EnvironmentSettings blinkStreamSettings = EnvironmentSettings.newInstance() .build(); StreamTableEnvironment tEnv = StreamTableEnvironment.create(env, blinkStreamSettings); // write source data into temporary file and get the absolute path String contents = "1,beer,3,2019-12-12 00:00:01\n" + "1,diaper,4,2019-12-12 00:00:02\n" + "2,pen,3,2019-12-12 00:00:04\n" + "2,rubber,3,2019-12-12 00:00:06\n" + "3,rubber,2,2019-12-12 00:00:05\n" + "4,beer,1,2019-12-12 00:00:08"; String path = createTempFile(contents); // register table via DDL with watermark, // the events are out of order, hence, we use 3 seconds to wait the late events String ddl = "CREATE TABLE orders (\n" + " user_id INT,\n" + " product STRING,\n" + " amount INT,\n" + " ts TIMESTAMP(3),\n" + " WATERMARK FOR ts AS ts - INTERVAL '3' SECOND\n" + ") WITH (\n" + " 'connector.type' = 'filesystem',\n" + " 'connector.path' = '" + path + "',\n" + " 'format.type' = 'csv'\n" + ")"; tEnv.executeSql(ddl); // run a SQL query on the table and retrieve the result as a new Table String query = "SELECT\n" + " CAST(TUMBLE_START(ts, INTERVAL '5' SECOND) AS STRING) window_start,\n" + " COUNT(*) order_num,\n" + " SUM(amount) total_amount,\n" + " COUNT(DISTINCT product) unique_products\n" + "FROM orders\n" + "GROUP BY TUMBLE(ts, INTERVAL '5' SECOND)"; Table result = tEnv.sqlQuery(query); tEnv.toAppendStream(result, Row.class).print(); // after the table program is converted to DataStream program, // we must use `env.execute()` to submit the job. env.execute("Streaming Window SQL Job"); // should output: // 2019-12-12 00:00:00.000,3,10,3 // 2019-12-12 00:00:05.000,3,6,2 } /** * Creates a temporary file with the contents and returns the absolute path. */ private static String createTempFile(String contents) throws IOException { File tempFile = File.createTempFile("orders", ".csv"); tempFile.deleteOnExit(); FileUtils.writeFileUtf8(tempFile, contents); return tempFile.toURI().toString(); } } ================================================ FILE: flink-learning-sql/flink-learning-sql-blink/src/main/resources/application.properties ================================================ kafka.brokers=localhost:9092 kafka.group.id=zhisheng kafka.zookeeper.connect=localhost:2181 kafka.topic=zhisheng ================================================ FILE: flink-learning-sql/flink-learning-sql-blink/src/main/resources/words.txt ================================================ Hello World Hello Hello World Hello Flink ================================================ FILE: flink-learning-sql/flink-learning-sql-blink/src/test/java/test/TableEnvironmentExample1.java ================================================ package test; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.table.api.EnvironmentSettings; import org.apache.flink.table.api.TableConfig; import org.apache.flink.table.api.bridge.java.StreamTableEnvironment; /** * Desc: blink planner TableEnvironment * Created by zhisheng on 2019/11/3 下午2:23 * blog:http://www.54tianzhisheng.cn/ * 微信公众号:zhisheng */ public class TableEnvironmentExample1 { public static void main(String[] args) { //流作业 StreamTableEnvironment.create(StreamExecutionEnvironment.getExecutionEnvironment()); //use EnvironmentSettings StreamTableEnvironment.create(StreamExecutionEnvironment.getExecutionEnvironment(), EnvironmentSettings.newInstance().build()); StreamTableEnvironment.create(StreamExecutionEnvironment.getExecutionEnvironment(), EnvironmentSettings.newInstance().build()); //use table config StreamTableEnvironment.create(StreamExecutionEnvironment.getExecutionEnvironment(), TableConfig.getDefault()); } } ================================================ FILE: flink-learning-sql/flink-learning-sql-client/README.md ================================================ k8s application 任务提交命令 ```shell /app/flink-1.16.1-sql/bin/flink run-application -t kubernetes-application -Dkubernetes.cluster-id=batch-flink-373362-1741687273399 -Dexecution.job-listeners=com.zhisheng.plugins.jobStatusListener -Djob.alert.ownerList=xxx -Denv.java.opts.taskmanager="-DtaskName=1618789_1739795455429 -DtaskId=373362" -Denv.java.opts.jobmanager="-DtaskName=1618789_1739795455429 -DtaskId=373362" -Denv.java.opts.client="-DtaskId=373362" -Dkubernetes.container.image=harbor.xxx/bigdata/links_sql_116_common_image_pro:2024122414 -Dkubernetes.config.file=/app/zhisheng/k8s/config_idc2 -Dkubernetes.namespace=dataxxx -Dcontainerized.master.env.LINKS_JOB_FILE=oss://xxx/release/373362.sql -Dcontainerized.master.env.relayJarsOssPath=oss:///functions/FlinkUDF-flink-dataman-udf.jar,oss://prod/external/iceberg_flink1.16/iceberg-flink-runtime-1.16-1.4.0-tag-hellobike-20231027.jar,oss://functions/celeborn-client-flink-1.16-shaded_2.12-0.5.0.jar -Dcontainerized.master.env.LINKS_USER_NAME=xxx_deploy -Dcontainerized.taskmanager.env.LINKS_JOB_FILE=oss://sql/xxx_deploy/release/373362.sql -Dcontainerized.taskmanager.env.relayJarsOssPath=oss://functions/FlinkUDF-flink-dataman-udf.jar,oss://external/iceberg_flink1.16/iceberg-flink-runtime-1.16-1.4.0-tag-hellobike-20231027.jar,oss://functions/celeborn-client-flink-1.16-shaded_2.12-0.5.0.jar -Dcontainerized.taskmanager.env.LINKS_USER_NAME=xxx_deploy -Dtaskmanager.numberOfTaskSlots=1 -Djobmanager.memory.process.size=2048m -Dtaskmanager.memory.process.size=3072m -Dparallelism.default=1 local:///opt/flink/usrlib/links-sql.jar -w /opt/flink/usrlib -f 373362.sql -t false -k8d batch-flink-373362-1741687273399 -b true ``` 注意: `-w /opt/flink/usrlib -f 373362.sql -t false -k8d batch-flink-373362-1741687273399 -b true` 这段内容是 Flink SQL 命令,其中 `-f 373362.sql` 是指 SQL 文件的路径,`-t false` 是指是否是临时任务,`-k8d batch-flink-373362-1741687273399` 是指 Flink 的 Appliation ID,`-b true` 是指是否是 Batch 任务。 ================================================ FILE: flink-learning-sql/flink-learning-sql-client/pom.xml ================================================ flink-learning-sql com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 flink-learning-sql-client Archetype - flink-learning-sql-client UTF-8 com.alibaba fastjson 1.2.83 org.slf4j slf4j-api 1.7.36 org.antlr antlr4 4.7.2 org.apache.flink flink-table-common ${flink.version} provided org.apache.flink flink-streaming-java ${flink.version} provided org.apache.flink flink-table-api-java ${flink.version} provided org.apache.flink flink-table-planner_${scala.binary.version} ${flink.version} provided org.apache.flink flink-clients ${flink.version} provided org.apache.flink flink-java ${flink.version} provided org.apache.flink flink-table-api-java-bridge ${flink.version} provided org.apache.flink flink-json ${flink.version} provided org.apache.flink flink-sql-orc ${flink.version} provided org.apache.flink flink-sql-avro ${flink.version} provided org.apache.flink flink-sql-parquet ${flink.version} provided junit junit 4.13.1 test mysql mysql-connector-java 5.1.44 provided org.postgresql postgresql 42.3.9 provided org.apache.flink flink-sql-connector-elasticsearch7 ${flink-connector-elasticsearch7.version} provided org.apache.flink flink-sql-connector-kafka ${flink-connector-kafka.version} provided org.apache.flink flink-sql-connector-hbase-2.2 ${flink-connector-hbase.version} provided org.apache.flink flink-sql-connector-hive-3.1.3_${scala.binary.version} ${flink.version} provided dev true src/main/resources/dev **/* true fat src/main/resources/fat **/* true prod src/main/resources/prod **/* true org.apache.maven.plugins maven-compiler-plugin 3.6.0 11 11 org.apache.maven.plugins maven-shade-plugin zhisheng-sql package shade ${project.name} com.zhisheng.sql.SqlSubmit META-INF/services/org.apache.flink.table.factories.TableFactory maven-assembly-plugin com.zhisheng.sql.SqlSubmit . ${project.name} jar-with-dependencies make-assembly package single ================================================ FILE: flink-learning-sql/flink-learning-sql-client/src/main/java/com/zhisheng/sql/SqlSubmit.java ================================================ package com.zhisheng.sql; import com.zhisheng.sql.cli.CliOptions; import com.zhisheng.sql.cli.CliOptionsParser; import com.zhisheng.sql.planner.BatchPlanner; import com.zhisheng.sql.planner.Planner; import com.zhisheng.sql.planner.StreamingPlanner; public class SqlSubmit { public static void main(String[] args) throws Exception { final CliOptions options = CliOptionsParser.parseClient(args); Planner planner; if (!Boolean.parseBoolean(options.getIsBatch())) { planner = StreamingPlanner.build(options); } else { planner = BatchPlanner.build(options); } planner.run(); } } ================================================ FILE: flink-learning-sql/flink-learning-sql-client/src/main/java/com/zhisheng/sql/cli/CliOptions.java ================================================ package com.zhisheng.sql.cli; public class CliOptions { private final String sqlFilePath; private final String workingSpace; private final String isTest; private final String isBatch; private final String k8sClusterId; public CliOptions(String sqlFilePath, String workingSpace, String isTest, String isBatch, String k8sClusterId) { this.sqlFilePath = sqlFilePath; this.workingSpace = workingSpace; this.isTest = isTest; this.isBatch = isBatch; this.k8sClusterId = k8sClusterId; } public String getSqlFilePath() { return sqlFilePath; } public String getWorkingSpace() { return workingSpace; } public String getIsTest() { return isTest; } public String getIsBatch() { return isBatch; } public String getK8sClusterId() { return k8sClusterId; } } ================================================ FILE: flink-learning-sql/flink-learning-sql-client/src/main/java/com/zhisheng/sql/cli/CliOptionsParser.java ================================================ package com.zhisheng.sql.cli; import com.zhisheng.sql.exception.SqlParserException; import org.apache.commons.cli.*; public class CliOptionsParser { public static final Option OPTION_WORKING_SPACE = Option .builder("w") .required(true) .longOpt("working_space") .numberOfArgs(1) .argName("working space dir") .desc("The working space dir.") .build(); public static final Option OPTION_SQL_FILE = Option .builder("f") .required(true) .longOpt("file") .numberOfArgs(1) .argName("SQL file path") .desc("The SQL file path.") .build(); public static final Option OPTION_ISTEST = Option .builder("t") .required(false) .longOpt("file") .numberOfArgs(1) .argName("IS TEST RUN") .desc("IS TEST RUN") .build(); public static final Option OPTION_ISBATCH = Option .builder("b") .required(false) .longOpt("file") .numberOfArgs(1) .argName("IS BACTH MODE") .desc("IS BACTH MODE") .build(); public static final Option OPTION_K8S_ID = Option .builder("k8d") .required(false) .longOpt("file") .numberOfArgs(1) .argName("k8s id") .desc("k8s id") .build(); public static final Options CLIENT_OPTIONS = getClientOptions(new Options()); public static Options getClientOptions(Options options) { options.addOption(OPTION_SQL_FILE); options.addOption(OPTION_WORKING_SPACE); options.addOption(OPTION_ISTEST); options.addOption(OPTION_ISBATCH); options.addOption(OPTION_K8S_ID); return options; } // -------------------------------------------------------------------------------------------- // Line Parsing // -------------------------------------------------------------------------------------------- public static CliOptions parseClient(String[] args) { if (args.length < 1) { throw new RuntimeException("./sql-submit -w -f -t -b true -k8d flink"); } try { DefaultParser parser = new DefaultParser(); CommandLine line = parser.parse(CLIENT_OPTIONS, args, true); return new CliOptions( line.getOptionValue(CliOptionsParser.OPTION_SQL_FILE.getOpt()), line.getOptionValue(CliOptionsParser.OPTION_WORKING_SPACE.getOpt()), line.getOptionValue(CliOptionsParser.OPTION_ISTEST.getOpt()), line.getOptionValue(CliOptionsParser.OPTION_ISBATCH.getOpt()), line.getOptionValue(CliOptionsParser.OPTION_K8S_ID.getOpt()) ); } catch (ParseException e) { throw new SqlParserException(e.getMessage()); } } } ================================================ FILE: flink-learning-sql/flink-learning-sql-client/src/main/java/com/zhisheng/sql/cli/SqlCommandParser.java ================================================ package com.zhisheng.sql.cli; import com.zhisheng.sql.exception.SqlParserException; import java.util.*; import java.util.function.Function; import java.util.regex.Matcher; import java.util.regex.Pattern; public final class SqlCommandParser { private SqlCommandParser() { } public static List parse(List lines) { List calls = new ArrayList<>(); StringBuilder stmt = new StringBuilder(); for (String line : lines) { if (line.trim().isEmpty() || line.startsWith("--")) { continue; } stmt.append("\n").append(line); if (line.trim().endsWith(";")) { Optional optionalCall = parse(stmt.toString()); if (optionalCall.isPresent()) { calls.add(optionalCall.get()); } else { throw new SqlParserException("Unsupported command '" + stmt.toString() + "'"); } stmt.setLength(0); } } return calls; } public static Optional parse(String stmt) { stmt = stmt.trim(); if (stmt.endsWith(";")) { stmt = stmt.substring(0, stmt.length() - 1).trim(); } for (SqlCommand cmd : SqlCommand.values()) { final Matcher matcher = cmd.pattern.matcher(stmt); if (matcher.matches()) { final String[] groups = new String[matcher.groupCount()]; for (int i = 0; i < groups.length; i++) { groups[i] = matcher.group(i + 1); } return cmd.operandConverter.apply(groups) .map((operands) -> new SqlCommandCall(cmd, operands)); } } return Optional.empty(); } // -------------------------------------------------------------------------------------------- private static final Function> NO_OPERANDS = (operands) -> Optional.of(new String[0]); private static final Function> SINGLE_OPERAND = (operands) -> Optional.of(new String[]{operands[0]}); private static final int DEFAULT_PATTERN_FLAGS = Pattern.CASE_INSENSITIVE | Pattern.DOTALL; public enum SqlCommand { INSERT_INTO( "(INSERT\\s+INTO.*)", SINGLE_OPERAND), INSERT_OVERWRITE( "(INSERT\\s+OVERWRITE.*)", SINGLE_OPERAND), CREATE_TABLE( "(CREATE\\s+TABLE.*)", SINGLE_OPERAND), CREATE_CATALOG( "(CREATE\\s+CATALOG.*)", SINGLE_OPERAND), USER_CATALOG( "(USE\\s+([\\s\\S]*))", SINGLE_OPERAND), CREATE_VIEW( "(CREATE\\s+VIEW.*)", SINGLE_OPERAND), CREATE_FUNCTION( "(CREATE\\s+TEMPORARY\\s+FUNCTION.*)", SINGLE_OPERAND), EXPLAIN_FOR( "(EXPLAIN\\s+PLAN\\s+FOR.*)", SINGLE_OPERAND), SET( "SET(\\s+(\\S+)\\s*=(.*))?", (operands) -> { if (operands.length < 3) { return Optional.empty(); } else if (operands[0] == null) { return Optional.of(new String[0]); } return Optional.of(new String[]{operands[1], operands[2]}); }); public final Pattern pattern; public final Function> operandConverter; SqlCommand(String matchingRegex, Function> operandConverter) { this.pattern = Pattern.compile(matchingRegex, DEFAULT_PATTERN_FLAGS); this.operandConverter = operandConverter; } @Override public String toString() { return super.toString().replace('_', ' '); } public boolean hasOperands() { return operandConverter != NO_OPERANDS; } } public static class SqlCommandCall { public final SqlCommand command; public final String[] operands; public SqlCommandCall(SqlCommand command, String[] operands) { this.command = command; this.operands = operands; } public SqlCommandCall(SqlCommand command) { this(command, new String[0]); } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } SqlCommandCall that = (SqlCommandCall) o; return command == that.command && Arrays.equals(operands, that.operands); } @Override public int hashCode() { int result = Objects.hash(command); result = 31 * result + Arrays.hashCode(operands); return result; } @Override public String toString() { return command + "(" + Arrays.toString(operands) + ")"; } } } ================================================ FILE: flink-learning-sql/flink-learning-sql-client/src/main/java/com/zhisheng/sql/constant/Constant.java ================================================ package com.zhisheng.sql.constant; public class Constant { public static final String IDLE_STATERETENTIOO_TIME = "idle.state.retention.time"; } ================================================ FILE: flink-learning-sql/flink-learning-sql-client/src/main/java/com/zhisheng/sql/constant/UnitEnum.java ================================================ package com.zhisheng.sql.constant; public enum UnitEnum { m, h, d } ================================================ FILE: flink-learning-sql/flink-learning-sql-client/src/main/java/com/zhisheng/sql/exception/SqlParserException.java ================================================ package com.zhisheng.sql.exception; @SuppressWarnings("serial") public class SqlParserException extends RuntimeException { public SqlParserException(String msg) { super(msg); } } ================================================ FILE: flink-learning-sql/flink-learning-sql-client/src/main/java/com/zhisheng/sql/planner/BatchPlanner.java ================================================ package com.zhisheng.sql.planner; import com.zhisheng.sql.cli.CliOptions; import com.zhisheng.sql.cli.SqlCommandParser; import org.apache.flink.table.api.EnvironmentSettings; import org.apache.flink.table.api.TableEnvironment; import org.apache.flink.table.api.TableResult; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; import java.util.List; import java.util.concurrent.ExecutionException; public class BatchPlanner extends Planner { public static BatchPlanner build(CliOptions options) { return new BatchPlanner(options); } private BatchPlanner(CliOptions options) { this.sqlFilePath = options.getSqlFilePath(); this.workSpace = options.getWorkingSpace(); this.isTest = options.getIsTest(); this.k8sClusterId = options.getK8sClusterId(); } @Override public void run() throws IOException, ExecutionException, InterruptedException { EnvironmentSettings bbSettings = EnvironmentSettings.newInstance().inBatchMode().build(); this.tEnv = TableEnvironment.create(bbSettings); this.statementSet = tEnv.createStatementSet(); List sql = Files.readAllLines(Paths.get(workSpace + "/" + sqlFilePath)); List calls = SqlCommandParser.parse(sql); for (SqlCommandParser.SqlCommandCall call : calls) { callCommand(call); } if (!isExplain) { TableResult execute = statementSet.execute(); if (k8sClusterId != null) { getK8sStatus(execute); } else { execute.await(); } } LOG.info("Job submitted SUCCESS"); } } ================================================ FILE: flink-learning-sql/flink-learning-sql-client/src/main/java/com/zhisheng/sql/planner/Planner.java ================================================ package com.zhisheng.sql.planner; import com.zhisheng.sql.cli.SqlCommandParser; import com.zhisheng.sql.constant.Constant; import com.zhisheng.sql.constant.UnitEnum; import com.zhisheng.sql.utils.CloseableRowIteratorWrapper; import com.zhisheng.sql.utils.Config; import com.zhisheng.sql.utils.HttpClient; import org.apache.flink.api.common.JobStatus; import org.apache.flink.core.execution.JobClient; import org.apache.flink.table.api.SqlParserException; import org.apache.flink.table.api.StatementSet; import org.apache.flink.table.api.TableEnvironment; import org.apache.flink.table.api.TableResult; import org.apache.flink.types.Row; import org.apache.flink.util.CloseableIterator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.time.Duration; import java.util.Optional; import java.util.concurrent.ExecutionException; public abstract class Planner { protected static final Logger LOG = LoggerFactory.getLogger(Planner.class); protected String sqlFilePath; protected String workSpace; protected String isTest; protected String k8sClusterId; protected boolean isExplain; protected StatementSet statementSet; protected TableEnvironment tEnv; public abstract void run() throws IOException, ExecutionException, InterruptedException; protected void getK8sStatus(TableResult execute) { //todo:注入状态监听器上报任务状态 CloseableIterator collect = execute.collect(); CloseableRowIteratorWrapper data = new CloseableRowIteratorWrapper(collect); Optional jobClient = execute.getJobClient(); String finalJobStatus = null; while (true) { try { if (jobClient.isPresent()) { JobClient jobClientTrue = jobClient.get(); JobStatus jobStatus = jobClientTrue.getJobStatus().get(); finalJobStatus = jobStatus.name(); Thread.sleep(500); } if (data.isFirstRowReady() && (JobStatus.FAILED.name().equals(finalJobStatus) || JobStatus.FINISHED.name().equals(finalJobStatus))) { String httpUrl = Config.getString("httpUrl"); LOG.info("== sql状态监听 =="); LOG.info(String.format("== 请求 %s ==", httpUrl)); LOG.info(String.format("== 状态为 %s ==", finalJobStatus)); // 调用外部实时平台接口通知状态 HttpClient.doGet(httpUrl + "?k8sClusterId=" + k8sClusterId + "&monitorType=1&" + "flinkStatus=" + finalJobStatus); break; } } catch (Exception e) { break; } } } protected void callCommand(SqlCommandParser.SqlCommandCall cmdCall) { switch (cmdCall.command) { case SET: callSet(cmdCall); break; case CREATE_TABLE: case CREATE_CATALOG: case USER_CATALOG: case CREATE_VIEW: callUpdate(cmdCall); break; case EXPLAIN_FOR: explain(cmdCall); break; case INSERT_INTO: case INSERT_OVERWRITE: callInsert(cmdCall); break; case CREATE_FUNCTION: callCreateFunction(cmdCall); break; default: throw new RuntimeException("Unsupported command: " + cmdCall.command); } } private void callInsert(SqlCommandParser.SqlCommandCall cmdCall) { String dml = cmdCall.operands[0]; statementSet.addInsertSql(dml); } private void callSet(SqlCommandParser.SqlCommandCall cmdCall) { LOG.info("set:" + cmdCall.operands[0] + "=" + cmdCall.operands[1]); String key = cmdCall.operands[0]; String value = cmdCall.operands[1]; if (Constant.IDLE_STATERETENTIOO_TIME.equals(key)) { String unit = value.replaceAll("\\d+", ""); String number = value.replaceAll("\\w+", ""); UnitEnum duration = UnitEnum.valueOf(unit); switch (duration) { case m: tEnv.getConfig().setIdleStateRetention(Duration.ofMinutes(Long.parseLong(number))); break; case h: tEnv.getConfig().setIdleStateRetention(Duration.ofHours(Long.parseLong(number))); break; case d: tEnv.getConfig().setIdleStateRetention(Duration.ofDays(Long.parseLong(number))); break; default: break; } } else { tEnv.getConfig().getConfiguration().setString(key, value); } } private void callUpdate(SqlCommandParser.SqlCommandCall cmdCall) { String ddl = cmdCall.operands[0]; try { tEnv.executeSql(ddl); } catch (SqlParserException e) { throw new RuntimeException("SQL parse failed:\n" + ddl + "\n", e); } } private void explain(SqlCommandParser.SqlCommandCall cmdCall) { String ddl = cmdCall.operands[0]; try { this.isExplain = true; tEnv.executeSql(ddl).print(); } catch (SqlParserException e) { throw new RuntimeException("SQL parse failed:\n" + ddl + "\n", e); } } private void callCreateFunction(SqlCommandParser.SqlCommandCall cmdCall) { LOG.info("create function:" + cmdCall.operands[0]); String ddl = cmdCall.operands[0]; try { tEnv.executeSql(ddl); } catch (Exception e) { throw new RuntimeException("SQL parse failed:\n" + ddl + "\n", e); } } } ================================================ FILE: flink-learning-sql/flink-learning-sql-client/src/main/java/com/zhisheng/sql/planner/StreamingPlanner.java ================================================ package com.zhisheng.sql.planner; import com.zhisheng.sql.cli.CliOptions; import com.zhisheng.sql.cli.SqlCommandParser; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.table.api.EnvironmentSettings; import org.apache.flink.table.api.TableResult; import org.apache.flink.table.api.bridge.java.StreamTableEnvironment; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; import java.util.List; import java.util.concurrent.ExecutionException; public class StreamingPlanner extends Planner { public static StreamingPlanner build(CliOptions options) { return new StreamingPlanner(options); } private StreamingPlanner(CliOptions options) { this.sqlFilePath = options.getSqlFilePath(); this.workSpace = options.getWorkingSpace(); this.isTest = options.getIsTest(); this.k8sClusterId = options.getK8sClusterId(); } @Override public void run() throws IOException, ExecutionException, InterruptedException { StreamExecutionEnvironment bsEnv = StreamExecutionEnvironment.getExecutionEnvironment(); EnvironmentSettings settings = EnvironmentSettings.newInstance().build(); this.tEnv = StreamTableEnvironment.create(bsEnv, settings); this.statementSet = tEnv.createStatementSet(); List sql = Files.readAllLines(Paths.get(workSpace + "/" + sqlFilePath)); List calls = SqlCommandParser.parse(sql); for (SqlCommandParser.SqlCommandCall call : calls) { callCommand(call); } if (!isExplain) { TableResult execute = statementSet.execute(); if (k8sClusterId != null) { getK8sStatus(execute); } } LOG.info("Job submitted SUCCESS"); } } ================================================ FILE: flink-learning-sql/flink-learning-sql-client/src/main/java/com/zhisheng/sql/utils/CloseableRowIteratorWrapper.java ================================================ package com.zhisheng.sql.utils; import org.apache.flink.types.Row; import org.apache.flink.util.CloseableIterator; public class CloseableRowIteratorWrapper implements CloseableIterator { private final CloseableIterator iterator; private boolean isFirstRowReady = false; public CloseableRowIteratorWrapper(CloseableIterator iterator) { this.iterator = iterator; } @Override public void close() throws Exception { iterator.close(); } @Override public boolean hasNext() { boolean hasNext = iterator.hasNext(); isFirstRowReady = isFirstRowReady || hasNext; return hasNext; } @Override public Row next() { Row next = iterator.next(); isFirstRowReady = true; return next; } public boolean isFirstRowReady() { return isFirstRowReady || hasNext(); } } ================================================ FILE: flink-learning-sql/flink-learning-sql-client/src/main/java/com/zhisheng/sql/utils/Config.java ================================================ package com.zhisheng.sql.utils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.util.Properties; public class Config { protected static final Logger LOG = LoggerFactory.getLogger(Config.class); private static Properties p = new Properties(); private static String paramsFile = "/conf.properties"; static { try { p.load(Config.class.getResourceAsStream(paramsFile)); } catch (IOException e) { LOG.error("Config读取配置出错:" + e.getMessage()); } } public static String getString(String key) { return p.getProperty(key); } } ================================================ FILE: flink-learning-sql/flink-learning-sql-client/src/main/java/com/zhisheng/sql/utils/HttpClient.java ================================================ package com.zhisheng.sql.utils; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.URL; public class HttpClient { public static String doGet(String httpurl) { HttpURLConnection connection = null; InputStream is = null; BufferedReader br = null; String result = null; try { URL url = new URL(httpurl); connection = (HttpURLConnection) url.openConnection(); connection.setRequestProperty("token", "BBFA9E619F980ECDDE7A2CAB28247968"); connection.setRequestMethod("GET"); connection.setConnectTimeout(15000); connection.setReadTimeout(60000); connection.connect(); if (connection.getResponseCode() == 200) { is = connection.getInputStream(); br = new BufferedReader(new InputStreamReader(is, "UTF-8")); StringBuffer sbf = new StringBuffer(); String temp = null; while ((temp = br.readLine()) != null) { sbf.append(temp); sbf.append("\r\n"); } result = sbf.toString(); } } catch (IOException e) { e.printStackTrace(); } finally { if (null != br) { try { br.close(); } catch (IOException e) { e.printStackTrace(); } } if (null != is) { try { is.close(); } catch (IOException e) { e.printStackTrace(); } } connection.disconnect(); } return result; } } ================================================ FILE: flink-learning-sql/flink-learning-sql-client/src/main/resources/dev/conf.properties ================================================ httpUrl=https://fat-zhisheng-sql.cn/index/k8sFlinkStatus ================================================ FILE: flink-learning-sql/flink-learning-sql-client/src/main/resources/dev/logback.xml ================================================ links %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n INFO ACCEPT DENY ${LOG_HOME}/%d{yyyy-MM-dd}/demo.info.%i.log.zip 10MB 60 1GB %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n WARN ACCEPT DENY ${LOG_HOME}/%d{yyyy-MM-dd}/call.warn.%i.log 10MB 60 1GB %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n ERROR ACCEPT DENY ${LOG_HOME}/%d{yyyy-MM-dd}/call.error.%i.log 10MB 60 1GB %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n ================================================ FILE: flink-learning-sql/flink-learning-sql-client/src/main/resources/pre/conf.properties ================================================ httpUrl=https://pre-zhisheng-sql.cn/index/k8sFlinkStatus ================================================ FILE: flink-learning-sql/flink-learning-sql-client/src/main/resources/pre/logback.xml ================================================ links %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n INFO ACCEPT DENY ${LOG_HOME}/%d{yyyy-MM-dd}/demo.info.%i.log.zip 10MB 60 1GB %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n WARN ACCEPT DENY ${LOG_HOME}/%d{yyyy-MM-dd}/call.warn.%i.log 10MB 60 1GB %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n ERROR ACCEPT DENY ${LOG_HOME}/%d{yyyy-MM-dd}/call.error.%i.log 10MB 60 1GB %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n ================================================ FILE: flink-learning-sql/flink-learning-sql-client/src/main/resources/prod/conf.properties ================================================ httpUrl=https://pro-zhisheng-sql.cn/index/k8sFlinkStatus ================================================ FILE: flink-learning-sql/flink-learning-sql-client/src/main/resources/prod/logback.xml ================================================ links %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n INFO ACCEPT DENY ${LOG_HOME}/%d{yyyy-MM-dd}/demo.info.%i.log.zip 10MB 60 1GB %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n WARN ACCEPT DENY ${LOG_HOME}/%d{yyyy-MM-dd}/call.warn.%i.log 10MB 60 1GB %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n ERROR ACCEPT DENY ${LOG_HOME}/%d{yyyy-MM-dd}/call.error.%i.log 10MB 60 1GB %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n ================================================ FILE: flink-learning-sql/flink-learning-sql-client/src/main/resources/sql/124563.sql ================================================ CREATE TEMPORARY FUNCTION json_value_udf AS 'com.zhisheng.udf.JsonValueUdf'; CREATE TABLE metrics_flink_jobs ( name STRING, fields Map, tags Row ) WITH ( 'connector' = 'kafka', 'topic' = 'metrics-flink-jobs', 'properties.bootstrap.servers' = 'logs-kafka1.xxx:9092,logs-kafka2.xxx:9092,logs-kafka3.xxx:9092', 'properties.group.id' = 'test', 'format' = 'json' ); CREATE TABLE flink_jobs_metrics ( name STRING, fields Map, tags Row ) WITH ( 'connector' = 'print' ); insert into flink_jobs_metrics select name, fields, tags from metrics_flink_jobs ; ================================================ FILE: flink-learning-sql/flink-learning-sql-client/src/test/java/SqlSubmitTest.java ================================================ import com.zhisheng.sql.SqlSubmit; import com.zhisheng.sql.cli.CliOptions; import org.junit.Test; import static org.junit.Assert.assertEquals; public class SqlSubmitTest { @Test public void testMain() throws Exception { String[] args = new String[]{"-w", "src/test/resources/sql", "-f", "test.sql", "-t", "false", "-b", "false", "-k8d", "flink-373362-1741687273399"}; SqlSubmit.main(args); } @Test public void testCliOptions() { CliOptions cliOptions = new CliOptions("sqlFilePath", "workingSpace", "isTest", "isBatch", "k8sClusterId"); assertEquals("sqlFilePath", cliOptions.getSqlFilePath()); assertEquals("workingSpace", cliOptions.getWorkingSpace()); assertEquals("isTest", cliOptions.getIsTest()); assertEquals("isBatch", cliOptions.getIsBatch()); assertEquals("k8sClusterId", cliOptions.getK8sClusterId()); } } ================================================ FILE: flink-learning-sql/flink-learning-sql-client/src/test/resources/dev/conf.properties ================================================ httpUrl=https://fat-zhisheng-sql.cn/index/k8sFlinkStatus ================================================ FILE: flink-learning-sql/flink-learning-sql-client/src/test/resources/dev/logback.xml ================================================ links %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n INFO ACCEPT DENY ${LOG_HOME}/%d{yyyy-MM-dd}/demo.info.%i.log.zip 10MB 60 1GB %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n WARN ACCEPT DENY ${LOG_HOME}/%d{yyyy-MM-dd}/call.warn.%i.log 10MB 60 1GB %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n ERROR ACCEPT DENY ${LOG_HOME}/%d{yyyy-MM-dd}/call.error.%i.log 10MB 60 1GB %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n ================================================ FILE: flink-learning-sql/flink-learning-sql-client/src/test/resources/sql/test.sql ================================================ -- 打印 flink 1.16 任务的消费延迟 metrics 监控数据 CREATE TABLE metrics_flink_jobs ( name STRING, fields Map, tags Row ) WITH ( 'connector' = 'kafka', 'topic' = 'metrics-flink-jobs', 'properties.bootstrap.servers' = 'logs-kafka1.xxx:9092,logs-kafka2.xxx:9092,logs-kafka3.xxx:9092', 'properties.group.id' = 'test', 'format' = 'json' ); CREATE TABLE flink_jobs_metrics ( name STRING, fields Map, tags Row ) WITH ( 'connector' = 'print' ); insert into flink_jobs_metrics select name, fields, tags from metrics_flink_jobs ; ================================================ FILE: flink-learning-sql/flink-learning-sql-common/README.md ================================================ ### flink-learning-sql-common 添加 Table API & SQL 的公共依赖: ```xml org.apache.flink flink-table-api-java-bridge_${scala.binary.version} ${flink.version} org.apache.flink flink-streaming-scala_${scala.binary.version} ${flink.version} org.apache.flink flink-table-common ${flink.version} ``` ================================================ FILE: flink-learning-sql/flink-learning-sql-common/pom.xml ================================================ flink-learning-sql com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 flink-learning-sql-common org.apache.flink flink-table-api-java-bridge ${flink.version} org.apache.flink flink-streaming-scala_${scala.binary.version} ${flink.version} org.apache.flink flink-table-common ${flink.version} com.zhisheng.flink flink-learning-common ${project.version} ================================================ FILE: flink-learning-sql/pom.xml ================================================ flink-learning com.zhisheng.flink 1.0-SNAPSHOT 4.0.0 flink-learning-sql pom flink-learning-sql-blink flink-learning-sql-common flink-learning-sql-client ================================================ FILE: paper/paper.md ================================================ 这是一份流处理系统相关 paper,看到 [@lw-lin](https://github.com/lw-lin) 维护的,比较好,可以加深对流处理引擎的认识。 原仓库地址:[https://github.com/lw-lin/streaming-readings](https://github.com/lw-lin/streaming-readings) 如果你认为还有比较好的论文应当加入到这个列表中,请提交一个 pull request,谢谢! # Readings in Streaming Systems 这是一份 streaming systems 领域相关的论文列表 20+ 篇,涉及 streaming systems 的设计,实现,故障恢复,弹性扩展等各方面。也包含自 2014 年以来 streaming system 和 batch system 的统一模型的论文。 ## 2016 年 - [Drizzle: Fast and Adaptable Stream Processing at Scale](http://shivaram.org/drafts/drizzle.pdf) (Draft): Record-at-a-time 的系统,如 Naiad, Flink,处理延迟较低、但恢复延迟较高;micro-batch 系统,如 Spark Streaming,恢复延迟低但处理延迟略高。Drizzle 则采用 group scheduling + pre-scheduling shuffles 的方式对 Spark Streaming 做了改进,保留低恢复延迟的同时,降低了处理延迟至 100ms 量级。 - [Realtime Data Processing at Facebook](https://research.fb.com/wp-content/uploads/2016/11/realtime_data_processing_at_facebook.pdf) (SIGMOD): Facebook 明确自己实时的使用场景是 seconds of latency, not milliseconds,并基于自己的需求构建了 3 个实时处理组件:Puma, Swift, 以及 Stylus。Puma, Swift 和 Stylus 都从 Scribe 读数据,并可向 Scribe 写回数据(Scribe 是 Facebook 内部的分布式消息系统,类似 Kafka)。 ## 2015 年 - [The Dataflow Model: A Practical Approach to Balancing Correctness, Latency, and Cost in Massive-Scale, Unbounded, Out-of-Order Data Processing](http://people.csail.mit.edu/matei/courses/2015/6.S897/readings/google-dataflow.pdf) (VLDB): 来自 Google 的将 stream processing 模型和 batch processing 模型统一的尝试。在 Dataflow model 下,底层依赖 FlumeJava 支持 batch processing,依赖 MillWheel 支持 stream processing。Dataflow model 的开源实现是 Apache Beam 项目。 - [Apache Flink: Stream and Batch Processing in a Single Engine](https://www.researchgate.net/publication/308993790_Apache_Flink_Stream_and_Batch_Processing_in_a_Single_Engine): Apache Flink 是一个处理 streaming data 和 batch data 的开源系统。Flink 的设计哲学是,包括实时分析 (real-time analytics)、持续数据处理 (continuous data pipelines)、历史数据处理 (historic data processing / batch)、迭代式算法 (iterative algorithms - machine learning, graph analysis) 等的很多类数据处理应用,都能用 pipelined fault-tolerant 的 dataflows 执行模型来表达。 - [Lightweight asynchronous snapshots for distributed dataflows](https://arxiv.org/pdf/1506.08603.pdf): Apache Flink 所实现的一个轻量级的、异步做状态快照的方法。基于此,Flink 得以保证分布式状态的一致性,从而保证整个系统的 exactly-once 语义。具体的,Flink 会持续性的在 stream 里插入 barrier markers,制造一个分布式的顺序关系,使得不同的节点能够在同一批 barrier marker 上达成整个系统的一致性状态。 - [Twitter Heron: Stream Processing at Scale](https://pdfs.semanticscholar.org/e847/c3ec130da57328db79a7fea794b07dbccdd9.pdf) (SIGMOD): Heron 是 Twitter 开发的用于代替 Storm 的实时处理系统,解决了 Storm 在扩展性、调试能力、性能、管理方式上的一些问题。Heron 实现了 Storm 的接口,因此对 Storm 有很好的兼容性,也成为了 Twitter 内部实时处理系统的事实上的标准。 ## 2014 年 - [Trill: A High-Performance Incremental Query Processor for Diverse Analytics](http://www.vldb.org/pvldb/vol8/p401-chandramouli.pdf) (VLDB): 此篇介绍了 Microsoft 的 Trill - 一个新的分析查询处理器。Trill 很好的结合以下 3 方面需求:(1) *Query Model*: Trill 是基于时间-关系 (tempo-relational) 模型,所以很好的支持从实时到离线计算的延迟需求;(2) *Fabric and Language Integration*: Trill 作为一个类库,可以很好的与高级语言、已有类库结合;以及 (3) *Performance*: 无论实时还是离线,Trill 的 throughput 都很高 —— 实时计算比流处理引擎高 2-4 个数量级,离线计算与商业的列式 DBMS 同等。从实现角度讲,包括 punctuation 的使用来分 batch 满足 latency 需求,batch 内使用列式存储、code-gen 等技术来提高 performance,都具有很好的借鉴意义 —— 尤其注意这是 2014 年发表的论文。 - [Summingbird: A Framework for Integrating Batch and Online MapReduce Computations](http://www.vldb.org/pvldb/vol7/p1441-boykin.pdf) (VLDB): Twitter 开发的目标是将 online Storm 计算和 batch MapReduce 计算逻辑统一描述的一套 domain-specific language。Summingbird 抽象了 sources, sinks, 以及 stores 等,基于此抽象,上层应用就不必为 streaming 和 batch 维护两套计算逻辑,而可以使用同一套计算逻辑,只在运行时分别编译后跑在 streaming 的 Storm 上和 batch 的 MapReduce 上。 - [Storm@Twitter](http://db.cs.berkeley.edu/cs286/papers/storm-sigmod2014.pdf) (SIGMOD): 这是一篇来迟的论文。Apache Storm 最初在 Backtype 及 Twitter,而后在业界范围都有广泛的应用,甚至曾经一度也是事实上的流处理系统标准。此篇介绍了 Storm 的设计,及在 Twitter 内部的应用情况。当然后面我们知道 Apache Storm 也暴露出一些问题,业界也出现了一些更优秀的流处理系统。Twitter 虽没有在 2012 年 Storm 时代开启时发声,但在 2014 年 Storm 落幕时以此文发声向其致敬,也算是弥补了些许遗憾吧。 ## 2013 年 - [Discretized Streams: Fault-Tolerant Streaming Computation at Scale](https://people.csail.mit.edu/matei/papers/2013/sosp_spark_streaming.pdf) (SOSP): Spark Streaming 是基于 Spark 执行引擎、micro-batch 模式的准实时处理系统。对比 RDD 是 Spark 引擎的数据抽象,DStream (Discretized Stream) 则是 Spark Streaming 引擎的数据抽象。DStream 像 RDD 一样,具有分布式、可故障恢复的特点,并且能够充分利用 Spark 引擎的推测执行,应对 straggler 的出现。 - [MillWheel: Fault-Tolerant Stream Processing at Internet Scale](https://static.googleusercontent.com/media/research.google.com/zh-CN//pubs/archive/41378.pdf) (VLDB): MillWheel 是 Google 内部研发的实时流数据处理系统,具有分布式、低延迟、高可用、支持 exactly-once 语义的特点。不出意外,MillWheel 是 Google 强大 infra structure 和强大 engeering 能力的综合体现 —— 利用 Bigtable/Spanner 作为后备状态存储、保证 exactly-once 特性等等。另外,MillWheel 将 watermark 机制发扬光大,对 event time 有着非常好的支持。推荐对 streaming system 感兴趣的朋友一定多读几遍此篇论文 —— 虽然此篇已经发表了几年,但工业界开源的系统尚未完全达到 MillWheel 的水平。 - [Integrating Scale Out and Fault Tolerance in Stream Processing using Operator State Management](http://openaccess.city.ac.uk/8175/1/sigmod13-seep.pdf) (SIGMOD): 针对有状态的算子的状态,此篇的基本洞察是,scale out 和 fault tolerance 其实很相通,应该结合到一起考虑和实现,而不是将其割裂开来。文章提出了算子的 3 类状态:(a) processing state, (b) buffer state, 和 (c) routing state,并提出了算子状态的 4 个操作原语:(1) checkpoint state, (2) backup state, (3) restore state, (4) partition state。 ## 2010 年 - [S4: Distributed Stream Computing Platform](https://pdfs.semanticscholar.org/53a8/7ccd0ecbad81949c688c2240f2c0c321cdb1.pdf) (ICDMW): 2010 年算是 general stream processing engine 元年 —— Yahoo! 研发并发布了 S4, Backtype 开始研发了 Storm 并将在 1 年后(由 Twitter)将其开源。S4 和 Storm 都是 general-purpose 的 stream processing engine,允许用户通过代码自定义计算逻辑,而不是仅仅是使用声明式的语言或算子。 ## 2008 年 - [Out-of-Order Processing: A New Architecture for HighPerformance Stream System](https://www.researchgate.net/publication/220538528_Out-of-Order_Processing_a_New_Architecture_for_High-Performance_Stream_Systems) (VLDB): 这篇文章提出了一种新的处理模型,即 out-of-order processing (OOP),取消了以往 streaming system 里对事件有序的假设。重要的是,这篇文章提出了并实现了 low watermark: lwm(n, S, A) is the smallest value for A that occurs after prefix Sn of stream S。我们看到,在 2 年后 Google 开始研发的 MillWheel 里,watermark 将被发扬光大。 - [Fast and Highly-Available Stream Processing over Wide Area Networks](http://cs.brown.edu/~ugur/fast.pdf) (ICDE): 针对广域网 (wide area networks) 的 stream processing 设计的快速、高可用方案。主要思想是依靠 replication。 ## 2007 年 - [A Cooperative, Self-Configuring High-Availability Solution for Stream Processing](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.89.2978&rep=rep1&type=pdf) (ICDE): 与 2005 年 ICDE 的文章一样,此篇也讨论 stream processing 的高可用问题。与 2005 年文章做法不同的是,此篇的 checkpointing 方法更细粒度一些,所以一个节点上的不同状态能够备份到不同的节点上去,因而在恢复的时候能够并行恢复以提高速度。 ## 2005 年 - [The 8 Requirements of Real-Time Stream Processing](https://ix.cs.uoregon.edu/~jsventek/papers/02bStonebraker2005.pdf) (SIGMOD): 图领奖得主 Michael Stonebraker 老爷子与他在 StreamBase 的小伙伴们勾画的 stream processing applications 应当满足的 8 条规则,如 Rule 1: Keep the Data Moving, Rule 2: Query using SQL on Streams (StreamSQL), Rule 3: Handle Stream Imperfections (Delayed, Missing and Out-of-Order Data) … 等等。虽然此篇有引导舆论的嫌疑 —— 不知是先有了这流 8 条、再有了 StreamBase,还是先有了 StreamBase、再有了这流 8 条 —— 但其内容还是有相当的借鉴意义。 - [The Design of the Borealis Stream Processing Engine](http://cs.brown.edu/research/aurora/cidr05.borealis.pdf) (CIDR): Borealis 是 Aurora 的分布式、更优化版本的续作。Borealis 提出并解决了 3 个新一代系统的基础问题:(1) dynamic revision of query results, (2) dynamic query modification, 以及 (3) flexible and highly-scalable optimization. 此篇讲解了 Borealis 的设计与实现 —— p.s. 下,Aurora 及续作 Borealis 的命名还真是非常讲究,是学院派的风格 :-D - [High-availability algorithms for distributed stream processing](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.162.306&rep=rep1&type=pdf) (ICDE): 此篇主要聚焦在 streaming system 的高可用性,即故障恢复。文章提出了 3 种 recovery types: (a) precise, (b) gap, 和 (c) rollback,并通过 (1) passive standby, (2) upstream backup, (3) active standby 的方式进行 recover。可与 2007 年 ICDE 的文章对比阅读。 ## 2004 年 - [STREAM: The Stanford Data Stream Management System](http://ilpubs.stanford.edu:8090/641/1/2004-20.pdf) (Technique Report): 这篇 technique report 定义了一种 Continuous Query Language (CQL),讲解了 Query Plans 和 Execution,讨论了一些 Performance Issues。系统也注意到并讨论了 Adaptivity 和 Approximation 的问题。从这篇 technique report 可以看出,这时的流式计算,更多是传统 RDBMS 的思路,扩展到了处理实时流式数据;这大约也是 2010 以前的 stream processing 相关研究的缩影。 ## 2002 年 - [Monitoring Streams – A New Class of Data Management Applications](http://www.vldb.org/conf/2002/S07P02.pdf) (VLDB): 大约在 2002 年前后,从实时数据监控(如监控 sensors 数据等)应用出发,大家已经开始区分传统的查询主动、数据被动 (Human-Active, DBMS-Passive) 模式和新兴的数据主动、查询被动 (DBMS-Active, Human-Passive) 模式的区别 —— 此篇即是其中的典型代表。此篇提出了新式的 DBMS 的 Aurora,描述了其基本系统模型、面向流式数据的操作算子集、 优化策略、及实时应用。 - [Exploiting Punctuation Semantics in Continuous Data Streams](http://www.whitworth.edu/academic/department/mathcomputerscience/faculty/tuckerpeter/pdf/117896_final.pdf) (TKDE): 此篇很早的注意到了一些传统的操作算子不能用于无尽的数据流入的场景,因为将导致无尽的状态(考虑 outer join),或者无尽的阻塞(考虑 count 或 max)等。此篇提出,如果在 stream 里加入一些特殊的 punctuation,来标识一段一段的数据,那么我们就可以把无限的 stream 划分为多个有限的数据集的集合,从而使得之前提到的算子变得可用。此篇的价值更多体现在给了 2008 年 watermark 相关的文章以基础,乃至集大成在了 2010 年 Google MillWheel 中。 ================================================ FILE: pom.xml ================================================ 4.0.0 com.zhisheng.flink flink-learning pom 1.0-SNAPSHOT flink-learning-common flink-learning-connectors flink-learning-sql flink-learning-examples flink-learning-monitor flink-learning-core flink-learning-project flink-learning-configuration-center flink-learning-basic flink-learning-cdc flink-learning-datalake flink-learning-extends flink-learning-k8s UTF-8 11 ${target.java.version} ${target.java.version} 1.20.3 2.12 2.25.3 3.4.0-1.20 3.3.0-1.20 3.1.0-1.20 3.1.0-1.20 4.0.0-1.18 3.2.0-1.19 3.0.1-1.17 4.1.0-1.18 3.1.0-1.19 3.0.1 org.apache.flink flink-connector-kafka ${flink-connector-kafka.version} org.apache.flink flink-streaming-java ${flink.version} provided org.apache.flink flink-clients ${flink.version} provided org.apache.logging.log4j log4j-slf4j-impl ${log4j.version} runtime org.apache.logging.log4j log4j-api ${log4j.version} runtime org.apache.logging.log4j log4j-core ${log4j.version} runtime add-dependencies-for-IDEA idea.version org.apache.flink flink-streaming-java ${flink.version} compile org.apache.flink flink-clients ${flink.version} compile ================================================ FILE: tree.md ================================================ ```text . ├── LICENSE ├── README.md ├── flink-learning-common ├── flink-learning-connectors │   ├── flink-learning-connectors-activemq │   ├── flink-learning-connectors-cassandra │   ├── flink-learning-connectors-clickhouse │   ├── flink-learning-connectors-es5 │   ├── flink-learning-connectors-es6 │   ├── flink-learning-connectors-es7 │   ├── flink-learning-connectors-flume │   ├── flink-learning-connectors-gcp-pubsub │   ├── flink-learning-connectors-hbase │   ├── flink-learning-connectors-hdfs │   ├── flink-learning-connectors-hive │   ├── flink-learning-connectors-influxdb │   ├── flink-learning-connectors-kafka │   ├── flink-learning-connectors-kudu │   ├── flink-learning-connectors-mysql │   ├── flink-learning-connectors-netty │   ├── flink-learning-connectors-nifi │   ├── flink-learning-connectors-pulsar │   ├── flink-learning-connectors-rabbitmq │   ├── flink-learning-connectors-redis │   └── flink-learning-connectors-rocketmq ├── flink-learning-core ├── flink-learning-data-sinks ├── flink-learning-data-sources ├── flink-learning-examples ├── flink-learning-libraries │   ├── flink-learning-libraries-cep │   ├── flink-learning-libraries-machine-learning │   └── flink-learning-libraries-state-processor-api ├── flink-learning-metrics ├── flink-learning-monitor │   ├── flink-learning-monitor-alert │   ├── flink-learning-monitor-collector │   ├── flink-learning-monitor-common │   ├── flink-learning-monitor-dashboard │   ├── flink-learning-monitor-log │   ├── flink-learning-monitor-pvuv │   └── flink-learning-monitor-storage ├── flink-learning-project │   ├── flink-learning-project-common │   └── flink-learning-project-deduplication ├── flink-learning-sql │   ├── flink-learning-sql-blink │   └── flink-learning-sql-common ├── flink-learning-state ├── flink-learning-window ├── paper ├── pics └── tree.txt 74 directories, 50 files ``` 生成项目结构命令: ``` tree -N -L 2 >tree.md ``` ![](http://zhisheng-blog.oss-cn-hangzhou.aliyuncs.com/img/2019-08-04-120123.jpg)