flink 同步kafka数据到mysql

前言:最近项目中要用flink同步上游数据,临时突击学习了java版本的flink使用,本文介绍一些在同步数据中遇到的一些问题,有些思路是本人原创,在查找了很多资料后做出的选择,如果对您有帮助,感谢点赞,一键三连。

flink 介绍

Flink是一个框架和分布式处理引擎,用于对无限制和有限制的数据留进行有状态的计算。Flink被设计为可在所有常见的集群环境中运行,以内存速度和任何规模执行计算。

导入kafka-flink依赖

<dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-connector-kafka_2.11</artifactId>
            <version>1.13.2</version>
        </dependency>

核心代码

代码部分是集成了springboot的实现类,里面介绍了连接kafka的流程

@Component
@ConditionalOnProperty(name = "mysql-status", havingValue = "true", matchIfMissing = false)
public class MysqlKafkaSource {
    private static final Logger log = LoggerFactory.getLogger(MysqlKafkaSource.class);

    @Value("${spring.kafka.bootstrap-servers}")
    private String kafkaServer;
    @Value("${customer.flink.topic}")
    private String topic;
    @Autowired
    private ApplicationContext applicationContext;
    @Resource
    private RedisTemplate redisTemplate;

    /**
     * 执行方法
     *
     * @throws Exception 异常
     */
    @PostConstruct
    public void execute() throws Exception {
        final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(1);
        Properties properties = new Properties();
        properties.setProperty("topic", topic);
        properties.setProperty("bootstrap.servers", kafkaServer);
        properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
        properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, ObjectDeserializer.class.getName());

        DataStream<String> messageStream = env.addSource(new FlinkKafkaConsumer<String>(properties.getProperty("topic"),
                new SimpleStringSchema(), properties));
        //对数据做ETL,将数据封装成Bean
        SingleOutputStreamOperator<ResultEngineInfo> dataStreamSource = messageStream.map(new MapFunction<String, ResultEngineInfo>() {
            @Override
            public ResultEngineInfo map(String value) throws Exception {
                ResultEngineInfo resultEngineInfo = new ResultEngineInfo();
                EngineInfo result = JSONUtil.toBean(value, EngineInfo.class);
                // 处理数据
                return resultEngineInfo;
            }
        });
        // 判断redis里面的数据是否等齐了,如果没等齐则入redis,同时不入mysql
        Map key = new HashMap(32);
        dataStreamSource.filter(item -> {
            if (Objects.nonNull(RedisUtil.getJedisClient().hget("lid", item.getUuid()))) {
                return true;
            } else {
                key.put(item.getUuid(), item.toString());
                return false;
            }
        }).returns(ResultEngineInfo.class);
        redisTemplate.opsForHash().putAll("key", key);

        //写入数据库
        dataStreamSource.addSink((SinkFunction) applicationContext.getBean("resultSink"));
        //启动任务
        new Thread(() -> {
            try {
                env.execute("job");
            } catch (Exception e) {
                log.error(e.toString(), e);
            }
        }).start();
    }
}

重点介绍遇到的问题,在flink流里面进行数据处理的时候,流里面只能使用静态的方法,而我的业务是要在流里面判断redis里面是否有这个数据,但是redisTemplate不能写在流里面,最后想到了处理方式就是使用工具类连接redis,不用redisTemplate进行连接。

同时我这里需要处理的业务是要连接mysql数据库查询业务表对应的信息,也不能引用service,需要采用Jdbc工具类进行连接,以下是部分代码。

以下是redis连接工具类

public class RedisUtil {
    private static JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
    private static JedisPool jedisPool = new JedisPool(jedisPoolConfig, "127.0.0.1");

    // 直接得到一个 Redis 的连接
    public static Jedis getJedisClient(){
        return jedisPool.getResource();
    }

    public static void main(String[] args) {
        RedisUtil.getJedisClient().hset("key","field","value");
        System.out.println(RedisUtil.getJedisClient().exists("key"));
        System.out.println(RedisUtil.getJedisClient().hkeys("key"));
    }
}

public class JDBCUtil {

    //1.加载驱动
    static{
        try {
            Class.forName("com.mysql.jdbc.Driver");
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    //2.获取连接
    public static Connection getConnection(){

        Connection conn = null;
        try {
            conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/test", "root", "123456");
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return conn;
    }

    //3.关闭连接
    public static void close(Connection conn, Statement st, ResultSet rs){
        //关闭连接
        if(conn != null){
            try {
                conn.close();
            } catch (SQLException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        //关闭statement
        if(st != null){
            try {
                st.close();
            } catch (SQLException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        //关闭结果集
        if(rs != null){
            try {
                rs.close();
            } catch (SQLException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }

    //-------------------------------封装sql操作------------------------------
    //查询返回List集合
    public static <T> List<T> getList(Class<T> cls, String sql, Object... obj){

        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;

        try{
            //1.获取连接
            conn = getConnection();
            //2.获取预处理对象
            ps = conn.prepareStatement(sql);
            //循环参数,如果没有就不走这里
            for (int i = 1; i <= obj.length; i++) {
                //注意:数组下标从0开始,预处理参数设置从1开始
                ps.setObject(i, obj[i-1]);
            }
            //3.执行SQL语句
            System.out.println(sql);
            rs = ps.executeQuery();
            //4.遍历结果集
            //遍历之前准备:因为封装不知道未来会查询多少列,所以我们需要指定有多少列
            ResultSetMetaData date = rs.getMetaData();//获取ResultSet对象的列编号、类型和属性

            int column = date.getColumnCount();//获取列数

            Field[] fields = cls.getDeclaredFields();//获取本类所有的属性

            //创建一个list集合对象来存储查询数据
            List<T> list = new ArrayList<T>();

            //开始遍历结果集
            while(rs.next()){

                //创建类类型实例
                T t = cls.newInstance();

                for(int i = 1; i <= column; i++){

                    Object value = rs.getObject(i);//每一列的值

                    /**
                     *String columnName = date.getColumnName(i);//获取每一列名称
                     * 关于获取每一列名称,如果列取了别名的话,则不能用上面的方法取列的名称
                     * 用下面的方法
                     */
                    String columnName = date.getColumnLabel(i);//获取每一列名称(别名)

                    //遍历所有属性对象
                    for (Field field : fields) {
                        //获取属性名
                        String name = field.getName();

                        field.setAccessible(true);//打破封装,忽略对封装修饰符的检测

						/*if (name.equals(columnName)) {

							String string = date.getColumnTypeName(i);//获取列类型名称

							//如果列类型是Date类型,转换成字符串表现形式
							SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
							String d = sdf.format(value);
							//赋值:将数据库中查询的字段赋值给对应名称的属性
							field.set(t, d);
						}else{
							field.set(t, value);
						}*/

                        if (name.equals(columnName)) {
                            BeanUtils.copyProperty(t, name, value);
                            break;//增加效率,避免不必要的循环
                        }
                    }
                }
                list.add(t);
            }
            return list;
            //5.关闭连接
        }catch(Exception e){
            e.printStackTrace();
        }finally{
            JDBCUtil.close(conn, ps, rs);
        }
        return null;
    }
    /**
     * 增加、删除、修改
     * @param sql sql语句
     * @param obj 参数
     * @return
     */
    public static boolean getDML(String sql,Object... obj){

        Connection conn = null;
        PreparedStatement ps = null;

        try{
            conn = getConnection();
            ps = conn.prepareStatement(sql);

            for (int i = 1; i <= obj.length; i++) {
                ps.setObject(i, obj[i-1]);
            }
            System.out.println(sql);
            int update = ps.executeUpdate();

            if (update > 0) {
                return true;
            }
        }catch(Exception e){
            e.printStackTrace();
        }finally{
            close(conn, ps, null);
        }
        return false;
    }

    //查询返回单个对象
    public static <T> T getOneObject(Class<T> cls,String sql,Object... obj){

        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;

        try{
            //1.获取连接
            conn = getConnection();
            //2.获取预处理对象
            ps = conn.prepareStatement(sql);
            //循环参数,如果没有就不走这里
            for (int i = 1; i <= obj.length; i++) {
                //注意:数组下标从0开始,预处理参数设置从1开始
                ps.setObject(i, obj[i-1]);
            }
            //3.执行SQL语句
            System.out.println(sql);
            rs = ps.executeQuery();
            //4.遍历结果集
            //遍历之前准备:因为封装不知道未来会查询多少列,所以我们需要指定有多少列
            ResultSetMetaData date = rs.getMetaData();//获取ResultSet对象的列编号、类型和属性

            int column = date.getColumnCount();//获取列数

            Field[] fields = cls.getDeclaredFields();//获取本类所有的属性

            //开始遍历结果集
            if(rs.next()){

                //创建类类型实例
                T t = cls.newInstance();

                for(int i = 1; i <= column; i++){

                    Object value = rs.getObject(i);//每一列的值

                    String columnName = date.getColumnName(i);//获取每一列名称

                    //遍历所有属性对象
                    for (Field field : fields) {
                        //获取属性名
                        String name = field.getName();

                        field.setAccessible(true);//打破封装,忽略对封装修饰符的检测

                        if (name.equals(columnName)) {
                            BeanUtils.copyProperty(t, name, value);
                        }
                    }
                }
                return t;
            }
            //5.关闭连接
        }catch(Exception e){
            e.printStackTrace();
        }finally{
            JDBCUtil.close(conn, ps, rs);
        }
        return null;
    }
    //查询总记录数
    public static Integer getCount(String sql,Object... obj){

        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;

        try{
            //1.获取连接
            conn = getConnection();
            //2.获取预处理对象
            ps = conn.prepareStatement(sql);
            //循环参数,如果没有就不走这里
            for (int i = 1; i <= obj.length; i++) {
                //注意:数组下标从0开始,预处理参数设置从1开始
                ps.setObject(i, obj[i-1]);
            }
            //3.执行SQL语句
            System.out.println(sql);
            rs = ps.executeQuery();

            //开始遍历结果集
            if(rs.next()){

                return rs.getInt(1);
            }
            //5.关闭连接
        }catch(Exception e){
            e.printStackTrace();
        }finally{
            JDBCUtil.close(conn, ps, rs);
        }
        return null;
    }

}

### 回答1: Flink可以通过Kafka Connector来消费Kafka数据,并将数据写入MySQL数据库。具体步骤如下: 1. 在Flink程序中引入Kafka Connector的依赖。 2. 创建一个Kafka Consumer,并设置相关的参数,如Kafka的地址、消费的Topic等。 3. 将Kafka Consumer读取到的数据进行处理,可以使用Flink提供的各种算子进行数据转换、过滤、聚合等操作。 4. 将处理后的数据写入MySQL数据库,可以使用Flink提供的JDBC Sink将数据写入MySQL中。 需要注意的是,Flink消费Kafka数据MySQL时,需要考虑数据的一致性和可靠性,可以使用Flink提供的Checkpoint机制来保证数据的一致性和容错性。同时,还需要考虑MySQL数据库的性能和可用性,可以使用连接池等技术来提高MySQL的性能和可用性。 ### 回答2: Apache Flink是一个流处理框架,可以方便地消费Kafka数据并将其写入MySQL数据库。Flink提供了Kafka数据源API来处理Kafka数据并将其转换为Flink数据流。Flink还提供了MySQL Sink API,可将Flink数据流转换为MySQL查询,并将其写入MySQL表中。 为了使用Kafka数据源API,需要使用以下代码创建KafkaSource: ``` FlinkKafkaConsumer consumer = new FlinkKafkaConsumer( "my-topic", new SimpleStringSchema(), properties); ``` 在上面的代码中,“my-topic”是Kafka主题名称,SimpleStringSchema是序列化程序,properties是Kafka消费者的配置属性。 接下来,您可以使用DataStreamAPI将Kafka数据源转换为DataStream: ``` DataStream<String> stream = env.addSource(consumer); ``` 在上面的代码中,env是Flink执行环境。 一旦您有了一个数据流,您可以使用MySQL Sink API将数据流写入MySQL数据库。使用以下代码创建MySQL Sink: ``` JDBCAppendTableSink sink = JDBCAppendTableSink.builder() .setDrivername("com.mysql.jdbc.Driver") .setDBUrl("jdbc:mysql://localhost:3306/mydatabase") .setUsername("myusername") .setPassword("mypassword") .setQuery("INSERT INTO mytable (id, name) VALUES (?, ?)") .setParameterTypes(Types.INT, Types.STRING) .build(); ``` 在上面的代码中,query是MySQL插入查询,setParameterTypes指定插入的参数类型。 接下来,你可以使用DataStreamAPI将数据写入MySQL Sink: ``` stream.addSink(sink); ``` 在上面的代码中,stream是上面创建的数据流。 最后,您需要启动Flink程序来开始消费Kafka数据并将其写入MySQL数据库: ``` env.execute(); ``` 现在,您已经成功地消耗了来自Kafka数据,并将其写入MySQL数据库。 ### 回答3: Flink是一个分布式实时计算引擎,它能够读取多种数据源,其中包括Kafka消息队列。在Flink中消费Kafka数据并将其写入MySQL数据库的步骤如下: 1. 添加依赖库 首先,需要在项目中添加FlinkKafka的依赖库,可以通过Maven或Gradle添加相关依赖库。例如,在Maven项目中添加以下依赖库: ```xml <!-- Flink --> <dependency> <groupId>org.apache.flink</groupId> <artifactId>flink-core</artifactId> <version>${flink.version}</version> </dependency> <dependency> <groupId>org.apache.flink</groupId> <artifactId>flink-streaming-java_${scala.binary.version}</artifactId> <version>${flink.version}</version> </dependency> <!-- Kafka --> <dependency> <groupId>org.apache.kafka</groupId> <artifactId>kafka-clients</artifactId> <version>${kafka.version}</version> </dependency> ``` 其中,`${flink.version}`和`${kafka.version}`需要根据实际情况替换为对应的版本号。 2. 创建Kafka数据源 然后,需要创建FlinkKafka数据源,可以通过以下方式实现: ```java Properties properties = new Properties(); properties.setProperty("bootstrap.servers", "localhost:9092"); properties.setProperty("group.id", "flink-group"); properties.setProperty("auto.offset.reset", "latest"); DataStream<String> stream = env .addSource(new FlinkKafkaConsumer<>( "topic-name", new SimpleStringSchema(), properties)); ``` 以上代码中,我们创建了一个名为`stream`的DataStream对象,并且通过FlinkKafkaConsumer将它和Kafka的消息队列连接起来。其中,`properties`中设置了Kafka的连接参数,`"topic-name"`指定了要消费的Kafka主题名,`SimpleStringSchema`表示我们只关注字符串类型的Kafka消息。 3. 解析Kafka数据 接下来,需要对Kafka中的数据进行解析和转换。例如,我们将Kafka消息中的JSON字符串转换为Java对象: ```java DataStream<Message> messages = stream.map(value -> { ObjectMapper mapper = new ObjectMapper(); return mapper.readValue(value, Message.class); }); ``` 这里,我们使用了Jackson库来将JSON字符串转换为Java对象,`Message.class`表示要转换成的对象类型。 4. 写入MySQL数据库 最后一步是将解析并转换后的数据写入MySQL数据库,可以通过JDBC实现。以下是简单的JDBC写入数据示例: ```java messages.addSink(new RichSinkFunction<Message>() { private Connection connection = null; private PreparedStatement statement = null; @Override public void open(Configuration parameters) throws Exception { super.open(parameters); Class.forName("com.mysql.jdbc.Driver"); connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/db_name", "user", "password"); statement = connection.prepareStatement("INSERT INTO messages (id, content) VALUES (?, ?)"); } @Override public void close() throws Exception { super.close(); if (statement != null) { statement.close(); } if (connection != null) { connection.close(); } } @Override public void invoke(Message message, Context context) throws Exception { statement.setInt(1, message.getId()); statement.setString(2, message.getContent()); statement.executeUpdate(); } }); ``` 以上代码中,`RichSinkFunction`表示数据写入器,`open`方法中创建了JDBC连接对象,`close`方法中关闭了连接对象,`invoke`方法中对每个解析的Message对象执行插入数据的操作。需要注意的是,需要将`jdbc:mysql://localhost:3306/db_name`中的`db_name`、`user`和`password`替换为实际MySQL数据库的值。 同时还需要添加对应的MySQL JDBC依赖库。 通过以上步骤,就可以使用FlinkKafka消息消费并写入MySQL数据库了。同时,还可以进行更多的数据转换和处理操作,例如过滤、分组、聚合等,从而实现更复杂的实时数据分析和计算。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值