Develop a JAR job

更新时间:
复制 MD 格式

The Flink DataStream API offers a flexible programming model for creating custom data transformations, operations, and operators to handle complex business logic and data processing.

Apache Flink compatibility

The DataStream API supported by Realtime Compute for Apache Flink is fully compatible with the open source Apache Flink version. For more information, see What is Apache Flink? and Flink DataStream API Programming Guide.

Prerequisites

  • An integrated development environment (IDE), such as IntelliJ IDEA, is installed.

  • Maven 3.6.3 or later is installed.

  • Job development supports only JDK 8 and JDK 11.

  • Develop your JAR job locally before deploying and running it in the Realtime Compute for Apache Flink console.

Before you begin

Prepare the required data sources in advance, as this example uses data source connectors.

Note
  • This example uses ApsaraMQ for Kafka (2.6.2) and ApsaraDB RDS for MySQL (8.0) as data sources.

  • If you have self-managed data sources that require public network access or cross-VPC access, see Network connectivity options.

  • If you do not have an ApsaraMQ for Kafka data source, purchase and deploy an instance. For more information, see Step 2: Purchase and deploy an instance. When you deploy the instance, make sure it is in the same VPC as your Realtime Compute for Apache Flink workspace.

  • If you do not have an ApsaraDB RDS for MySQL data source, purchase an ApsaraDB RDS for MySQL instance. For more information, see Step 1: Create an ApsaraDB RDS for MySQL instance and configure a database. When you purchase the instance, make sure it is in the same region and VPC as your Realtime Compute for Apache Flink workspace.

Develop the job

Configure Maven (optional)

Configure the Maven settings.xml file. If you experience slow speeds or failures when downloading from the Maven central repository, you can switch to the Alibaba Cloud Maven mirror.

  <mirror>
      <id>aliyunmaven</id>
        <mirrorOf>central</mirrorOf>
        <name>Aliyun Maven</name>
        <url>https://maven.aliyun.com/repository/public</url>
    </mirror>

Configure Flink environment dependencies

Note

To avoid JAR dependency conflicts, follow these guidelines:

  • ${flink.version} specifies the Flink version for the job execution. This version must be consistent with the Flink version of the VVR engine that you selected on the job deployment page. For example, if you select the vvr-8.0.9-flink-1.17 engine on the job deployment page, its corresponding Flink version is 1.17.2. For more information about VVR engine versions, see How do I check the Flink version of the current job?.

  • For Flink dependencies, set the scope to provided by adding <scope>provided</scope>. These mainly include non-Connector dependencies in the org.apache.flink group that start with flink-.

  • In the Flink source code, only methods explicitly annotated with @Public or @PublicEvolving are public APIs. Realtime Compute for Apache Flink guarantees compatibility only for these methods.

  • If you use the DataStream API of a built-in Flink connector, use the provided dependencies.

The following are basic Flink dependencies. You may also need to add logging dependencies. For a complete list of dependencies, see the Complete code example at the end of this topic.

Flink dependencies
         <!-- Apache Flink dependencies -->
        <!-- These dependencies are set to 'provided' because they should not be packaged into the JAR file. -->
        <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-java</artifactId>
            <version>${flink.version}</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-streaming-java</artifactId>
            <version>${flink.version}</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-clients</artifactId>
            <version>${flink.version}</version>
            <scope>provided</scope>
        </dependency>

Connector dependencies and usage

To read and write data with the DataStream API, use a DataStream connector to connect to Realtime Compute for Apache Flink. The Maven Central Repository provides VVR DataStream connectors that you can use during development.

Important

Use only the connectors that are specified to support the DataStream API in Supported connectors. Do not use a connector if it is not explicitly marked for DataStream API support, because its interfaces and parameters might change in the future.

You can use a connector in one of the following ways:

(Recommended) Upload as additional dependency

  1. In your job's pom.xml file, add the required connector as a project dependency with the "provided" scope. For the complete dependency file, see the Complete code example at the end of this topic.

    Note
    • ${vvr.version} is the job runtime engine version. For example, if your job runs on the vvr-8.0.9-flink-1.17 engine, the corresponding Flink version is 1.17.2. We recommend that you use the latest engine. For information about specific versions, see Engine.

    • Because the connector's JAR package is added as an additional dependency, it does not need to be bundled in the application JAR. Therefore, you must declare its scope as provided.

            <!-- Kafka connector dependency -->
            <dependency>
                <groupId>com.alibaba.ververica</groupId>
                <artifactId>ververica-connector-kafka</artifactId>
                <version>${vvr.version}</version>
                <scope>provided</scope>
            </dependency>
            <!-- MySQL connector dependency -->
            <dependency>
                <groupId>com.alibaba.ververica</groupId>
                <artifactId>ververica-connector-mysql</artifactId>
                <version>${vvr.version}</version>
                <scope>provided</scope>
            </dependency>
  2. If you need to develop new connectors or extend the functionality of existing connectors, your project also needs to depend on the common connector packages flink-connector-base or ververica-connector-common.

            <!-- Basic dependency for Flink connector public interfaces -->
            <dependency>
                <groupId>org.apache.flink</groupId>
                <artifactId>flink-connector-base</artifactId>
                <version>${flink.version}</version>
            </dependency>
            <!-- Basic dependency for Alibaba Cloud connector public interfaces -->
            <dependency>
                <groupId>com.alibaba.ververica</groupId>
                <artifactId>ververica-connector-common</artifactId>
                <version>${vvr.version}</version>
            </dependency>
  3. For DataStream connection configurations and code examples, see the documentation for the corresponding DataStream connector.

    For a list of connectors that support the DataStream API, see Supported connectors.

  4. Deploy the job and add the corresponding connector JAR packages in the Additional Dependencies field. For more information, see Deploy a JAR job. You can upload your custom connectors or the connectors provided by Realtime Compute for Apache Flink. To download the connectors, see Connectors.

    For example, upload the ververica-connector-mysql-1.17-vvr-8.0.4-1.jar and ververica-connector-kafka-1.17-vvr-8.0.4-1.jar connector JAR packages, and the config.properties configuration file.

Package in job JAR

  1. Add the required connectors as project dependencies in your job's pom.xml file. The following code shows an example for the Kafka and MySQL connectors.

    Note
    • ${vvr.version} is the engine version of the job's runtime environment. For example, if your job runs on the vvr-8.0.9-flink-1.17 engine version, its corresponding Flink version is 1.17.2. We recommend that you use the latest engine. For more information, see Engines.

    • If you package connectors into the job JAR as project dependencies, they must be in the default scope (compile).

            <!-- Kafka connector dependency -->
            <dependency>
                <groupId>com.alibaba.ververica</groupId>
                <artifactId>ververica-connector-kafka</artifactId>
                <version>${vvr.version}</version>
            </dependency>
            <!-- MySQL connector dependency -->
            <dependency>
                <groupId>com.alibaba.ververica</groupId>
                <artifactId>ververica-connector-mysql</artifactId>
                <version>${vvr.version}</version>
            </dependency>
  2. If you need to develop new connectors or extend the functionality of existing connectors, your project also requires the common connector package flink-connector-base or ververica-connector-common.

            <!-- Basic dependency for Flink connector public interfaces -->
            <dependency>
                <groupId>org.apache.flink</groupId>
                <artifactId>flink-connector-base</artifactId>
                <version>${flink.version}</version>
            </dependency>
            <!-- Basic dependency for Alibaba Cloud connector public interfaces -->
            <dependency>
                <groupId>com.alibaba.ververica</groupId>
                <artifactId>ververica-connector-common</artifactId>
                <version>${vvr.version}</version>
            </dependency>
  3. For DataStream connection configurations and code examples, see the documentation for the corresponding DataStream connector.

    For a list of connectors that support the DataStream API, see Supported connectors.

Read additional dependencies from OSS

Flink JAR jobs cannot read local configuration files from the main method. Instead, upload the configuration file to your workspace's OSS bucket, add it as an additional dependency during deployment, and read it at runtime. The following section provides an example.

  1. Create a configuration file named config.properties to avoid hardcoding credentials in your code.

    # Kafka 
    bootstrapServers=host1:9092,host2:9092,host3:9092
    inputTopic=topic
    groupId=groupId
    # MySQL
    database.url=jdbc:mysql://localhost:3306/my_database
    database.username=username
    database.password=password
  2. In your JAR job, use code to read the config.properties file stored in the OSS bucket.

    Method 1: Read from workspace bucket

    1. In the left-side navigation pane of the Realtime Compute for Apache Flink console, go to the Artifacts page and upload the file.

    2. At runtime, files added in the Additional Dependencies field are loaded into the /flink/usrlib directory of the pod where the job runs.

    3. The following code provides an example of how to read this configuration file.

                  Properties properties = new Properties();
                  Map<String,String> configMap = new HashMap<>();
                  try (InputStream input = new FileInputStream("/flink/usrlib/config.properties")) {
                      // Load the property file.
                      properties.load(input);
                      // Obtain the property values.
                      configMap.put("bootstrapServers",properties.getProperty("bootstrapServers")) ;
                      configMap.put("inputTopic",properties.getProperty("inputTopic"));
                      configMap.put("groupId",properties.getProperty("groupId"));
                      configMap.put("url",properties.getProperty("database.url")) ;
                      configMap.put("username",properties.getProperty("database.username"));
                      configMap.put("password",properties.getProperty("database.password"));
                  } catch (IOException ex) {
                      ex.printStackTrace();
                  }

    Method 2: Read from authorized bucket

    1. Upload the configuration file to the target OSS bucket.

    2. Use OSSClient to read the file directly from OSS. For more information, see Streaming download and Manage access credentials. The following code provides an example.

      OSS ossClient = new OSSClientBuilder().build("Endpoint", "AccessKeyId", "AccessKeySecret");
      try (OSSObject ossObject = ossClient.getObject("examplebucket", "exampledir/config.properties");
           BufferedReader reader = new BufferedReader(new InputStreamReader(ossObject.getObjectContent()))) {
          // read file and process ...
      } finally {
          if (ossClient != null) {
              ossClient.shutdown();
          }
      }

Write the business logic

  1. You can integrate external data sources into Flink data stream programs. A Watermark is a Flink computation strategy that is based on time semantics and is often used with timestamps. Therefore, this example does not use a watermark strategy. For more information, see Watermark strategy.

             // Integrate the external data source into the Flink data stream program.
            // WatermarkStrategy.noWatermarks() indicates that no watermark strategy is used.
            DataStreamSource<String> stream = env.fromSource(kafkaSource, WatermarkStrategy.noWatermarks(), "kafka Source");
  2. Operator transformation converts a DataStream<String> to a DataStream<Student> in this example. For more complex operator transformations and processing methods, see Flink Operators.

              // Operator that converts the data structure to Student.
              DataStream<Student> source = stream
                    .map(new MapFunction<String, Student>() {
                        @Override
                        public Student map(String s) throws Exception {
                            // Data is separated by commas.
                            String[] data = s.split(",");
                            return new Student(Integer.parseInt(data[0]), data[1], Integer.parseInt(data[2]));
                        }
                    }).filter(student -> student.score >=60); // Filter for data where the score is 60 or higher.

Package the job

Package the job by using the maven-shade-plugin.

Important
  • If you add the connector as an additional dependency, ensure that the scope of the connector dependencies is set to provided when you package the job.

  • If you package connectors into the job JAR, use the default scope (compile).

Reference for maven-shade-plugin dependencies

<build>
        <plugins>
            <!-- Java Compiler -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.11.0</version>
                <configuration>
                    <source>${target.java.version}</source>
                    <target>${target.java.version}</target>
                </configuration>
            </plugin>
            <!-- We use the maven-shade-plugin to create a fat JAR that contains all necessary dependencies. -->
            <!-- Modify the value of <mainClass> if your program's entry point changes. -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>3.2.0</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                        <!-- Exclude unnecessary dependencies. -->
                        <configuration>
                            <artifactSet>
                                <excludes>
                                    <exclude>org.apache.flink:force-shading</exclude>
                                    <exclude>com.google.code.findbugs:jsr305</exclude>
                                    <exclude>org.slf4j:*</exclude>
                                    <exclude>org.apache.logging.log4j:*</exclude>
                                </excludes>
                            </artifactSet>
                            <filters>
                                <filter>
                                    <!-- Do not copy the signatures from the META-INF directory.
                                    Otherwise, security exceptions may be thrown when you use the JAR file. -->
                                    <artifact>*:*</artifact>
                                    <excludes>
                                        <exclude>META-INF/*.SF</exclude>
                                        <exclude>META-INF/*.DSA</exclude>
                                        <exclude>META-INF/*.RSA</exclude>
                                    </excludes>
                                </filter>
                            </filters>
                            <transformers>
                                <transformer
                                        implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                                    <mainClass>com.aliyun.FlinkDemo</mainClass>
                                </transformer>
                            </transformers>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

Test and deploy the job

  • By default, Realtime Compute for Apache Flink cannot access the public internet, which prevents direct testing in a local environment. We recommend that you perform unit tests separately. For more information, see Run and debug a job with connectors locally.

  • To deploy the JAR job, see Deploy a JAR job.

    Note
    • During deployment, if you use connectors as additional dependencies, ensure you upload the relevant JAR packages.

    • If you need to read a configuration file, you must also upload it as an additional dependency.

Complete code example

This example processes data from an ApsaraMQ for Kafka source and writes the result to an ApsaraDB RDS for MySQL sink. This example is for reference only. For more code style and quality guidelines, see Code style and quality guide.

Note

This example omits configurations for runtime parameters such as checkpoint, TTL, and restart strategy. You can configure these settings on the deployment details page after the job is deployed. Because settings in code have higher priority, we recommend configuring them after deployment to simplify future updates. For more information, see Configure job deployment information.

FlinkDemo.java

package com.aliyun;
import org.apache.flink.api.common.eventtime.WatermarkStrategy;
import org.apache.flink.api.common.functions.MapFunction;
import org.apache.flink.connector.jdbc.JdbcConnectionOptions;
import org.apache.flink.connector.jdbc.JdbcExecutionOptions;
import org.apache.flink.connector.jdbc.JdbcSink;
import org.apache.flink.connector.jdbc.JdbcStatementBuilder;
import org.apache.flink.connector.kafka.source.KafkaSource;
import org.apache.flink.connector.kafka.source.enumerator.initializer.OffsetsInitializer;
import org.apache.flink.connector.kafka.source.reader.deserializer.KafkaRecordDeserializationSchema;
import org.apache.flink.kafka.shaded.org.apache.kafka.common.serialization.StringDeserializer;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
public class FlinkDemo {
    // Define the data structure.
    public static class Student {
        public int id;
        public String name;
        public int score;
        public Student(int id, String name, int score) {
            this.id = id;
            this.name = name;
            this.score = score;
        }
    }
    public static void main(String[] args) throws Exception {
        // Create a Flink execution environment.
        final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        Properties properties = new Properties();
        Map<String,String> configMap = new HashMap<>();
        try (InputStream input = new FileInputStream("/flink/usrlib/config.properties")) {
            // Load the property file.
            properties.load(input);
            // Obtain the property values.
            configMap.put("bootstrapServers",properties.getProperty("bootstrapServers")) ;
            configMap.put("inputTopic",properties.getProperty("inputTopic"));
            configMap.put("groupId",properties.getProperty("groupId"));
            configMap.put("url",properties.getProperty("database.url")) ;
            configMap.put("username",properties.getProperty("database.username"));
            configMap.put("password",properties.getProperty("database.password"));
        } catch (IOException ex) {
            ex.printStackTrace();
        }
        // Build Kafka source
        KafkaSource<String> kafkaSource = KafkaSource.<String>builder()
                        .setBootstrapServers(configMap.get("bootstrapServers"))
                        .setTopics(configMap.get("inputTopic"))
                        .setStartingOffsets(OffsetsInitializer.latest())
                        .setGroupId(configMap.get("groupId"))
                        .setDeserializer(KafkaRecordDeserializationSchema.valueOnly(StringDeserializer.class))
                        .build();
        // Integrate the external data source into the Flink data stream program.
        // WatermarkStrategy.noWatermarks() indicates that no watermark strategy is used.
        DataStreamSource<String> stream = env.fromSource(kafkaSource, WatermarkStrategy.noWatermarks(), "kafka Source");
        // Filter for scores of 60 or higher.
        DataStream<Student> source = stream
                .map(new MapFunction<String, Student>() {
                    @Override
                    public Student map(String s) throws Exception {
                        String[] data = s.split(",");
                        return new Student(Integer.parseInt(data[0]), data[1], Integer.parseInt(data[2]));
                    }
                }).filter(Student -> Student.score >=60);
        source.addSink(JdbcSink.sink("INSERT IGNORE INTO student (id, username, score) VALUES (?, ?, ?)",
                new JdbcStatementBuilder<Student>() {
                    public void accept(PreparedStatement ps, Student data) {
                        try {
                            ps.setInt(1, data.id);
                            ps.setString(2, data.name);
                            ps.setInt(3, data.score);
                        } catch (SQLException e) {
                            throw new RuntimeException(e);
                        }
                    }
                },
                new JdbcExecutionOptions.Builder()
                        .withBatchSize(5) // Number of records per batch write.
                        .withBatchIntervalMs(2000) // Batch interval in milliseconds.
                        .build(),
                new JdbcConnectionOptions.JdbcConnectionOptionsBuilder()
                        .withUrl(configMap.get("url"))
                        .withDriverName("com.mysql.cj.jdbc.Driver")
                        .withUsername(configMap.get("username"))
                        .withPassword(configMap.get("password"))
                        .build()
        )).name("Sink MySQL");
        env.execute("Flink Demo");
    }
}

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.aliyun</groupId>
    <artifactId>FlinkDemo</artifactId>
    <version>1.0-SNAPSHOT</version>
    <name>FlinkDemo</name>
    <packaging>jar</packaging>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <flink.version>1.17.1</flink.version>
        <vvr.version>1.17-vvr-8.0.4-1</vvr.version>
        <target.java.version>1.8</target.java.version>
        <maven.compiler.source>${target.java.version}</maven.compiler.source>
        <maven.compiler.target>${target.java.version}</maven.compiler.target>
        <log4j.version>2.14.1</log4j.version>
    </properties>
    <dependencies>
        <!-- Apache Flink dependencies -->
        <!-- These dependencies are set to 'provided' because they should not be packaged into the JAR file. -->
        <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-java</artifactId>
            <version>${flink.version}</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-streaming-java</artifactId>
            <version>${flink.version}</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-clients</artifactId>
            <version>${flink.version}</version>
            <scope>provided</scope>
        </dependency>
        <!-- Add connector dependencies here. They must be in the default scope (compile). -->
        <dependency>
            <groupId>com.alibaba.ververica</groupId>
            <artifactId>ververica-connector-kafka</artifactId>
            <version>${vvr.version}</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba.ververica</groupId>
            <artifactId>ververica-connector-mysql</artifactId>
            <version>${vvr.version}</version>
        </dependency>
        <!-- Add a logging framework to generate console output at runtime. -->
        <!-- By default, these dependencies are excluded from the application JAR. -->
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-slf4j-impl</artifactId>
            <version>${log4j.version}</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-api</artifactId>
            <version>${log4j.version}</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>${log4j.version}</version>
            <scope>runtime</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <!-- Java Compiler -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.11.0</version>
                <configuration>
                    <source>${target.java.version}</source>
                    <target>${target.java.version}</target>
                </configuration>
            </plugin>
            <!-- We use the maven-shade-plugin to create a fat JAR that contains all necessary dependencies. -->
            <!-- Modify the value of <mainClass> if your program's entry point changes. -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>3.2.0</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                        <!-- Exclude unnecessary dependencies. -->
                        <configuration>
                            <artifactSet>
                                <excludes>
                                    <exclude>org.apache.flink:force-shading</exclude>
                                    <exclude>com.google.code.findbugs:jsr305</exclude>
                                    <exclude>org.slf4j:*</exclude>
                                    <exclude>org.apache.logging.log4j:*</exclude>
                                </excludes>
                            </artifactSet>
                            <filters>
                                <filter>
                                    <!-- Do not copy the signatures from the META-INF directory.
                                    Otherwise, security exceptions may be thrown when you use the JAR file. -->
                                    <artifact>*:*</artifact>
                                    <excludes>
                                        <exclude>META-INF/*.SF</exclude>
                                        <exclude>META-INF/*.DSA</exclude>
                                        <exclude>META-INF/*.RSA</exclude>
                                    </excludes>
                                </filter>
                            </filters>
                            <transformers>
                                <transformer
                                        implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                                    <mainClass>com.aliyun.FlinkDemo</mainClass>
                                </transformer>
                            </transformers>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

Related documentation