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
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1. 环境准备 - 安装 MySQL,创建测试数据库和表,并插入数据 - 安装 Kafka,并创建一个 topic - 安装 Flink 2. 创建 Flink 项目 - 在 Flink 的 bin 目录下执行 flink new myflinkproject 创建一个新的 Flink 项目 - 在 pom.xml 中添加以下依赖 ``` <dependency> <groupId>org.apache.flink</groupId> <artifactId>flink-table-api-java-bridge</artifactId> <version>${flink.version}</version> </dependency> <dependency> <groupId>org.apache.flink</groupId> <artifactId>flink-connector-jdbc</artifactId> <version>${flink.version}</version> </dependency> <dependency> <groupId>org.apache.flink</groupId> <artifactId>flink-connector-kafka_2.11</artifactId> <version>${flink.version}</version> </dependency> <dependency> <groupId>org.apache.flink</groupId> <artifactId>flink-json</artifactId> <version>${flink.version}</version> </dependency> ``` - 在 src/main/java 下创建一个 Java 类,例如 SyncMySQLToKafka.java 3. 编写 Flink SQL 在 SyncMySQLToKafka.java 中编写以下代码: ``` public class SyncMySQLToKafka { public static void main(String[] args) throws Exception { StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); EnvironmentSettings settings = EnvironmentSettings.newInstance().useBlinkPlanner().inStreamingMode().build(); StreamTableEnvironment tableEnv = StreamTableEnvironment.create(env, settings); tableEnv.executeSql("CREATE TABLE mysql_table (id INT, name STRING) " + "WITH (" + " 'connector.type' = 'jdbc'," + " 'connector.url' = 'jdbc:mysql://localhost:3306/test?characterEncoding=utf-8'," + " 'connector.table' = 'test_table'," + " 'connector.driver' = 'com.mysql.jdbc.Driver'," + " 'connector.username' = 'root'," + " 'connector.password' = 'root'" + ")"); tableEnv.executeSql("CREATE TABLE kafka_table (id INT, name STRING) " + "WITH (" + " 'connector.type' = 'kafka'," + " 'connector.version' = 'universal'," + " 'connector.topic' = 'test_topic'," + " 'connector.properties.bootstrap.servers' = 'localhost:9092'," + " 'connector.properties.group.id' = 'test_group'," + " 'format.type' = 'json'," + " 'update-mode' = 'append'" + ")"); tableEnv.executeSql("INSERT INTO kafka_table SELECT id, name FROM mysql_table"); env.execute(); } } ``` - 创建一个 MySQLmysql_table,指定连接信息和表名 - 创建一个 Kafkakafka_table,指定连接信息、topic 和数据格式 - 将 mysql_table 中的数据插入到 kafka_table 中 4. 运行程序 - 在命令行中进入项目根目录,执行 mvn clean package 编译项目 - 执行以下命令运行程序 ``` ./bin/flink run -c SyncMySQLToKafka target/myflinkproject-1.0-SNAPSHOT.jar ``` 5. 验证结果 - 在 Kafka 中查看是否有数据写入到 test_topic 中 - 修改 MySQL 表中的数据,查看是否能同步Kafka 中 以上就是使用 Flink SQL 实现 MySQL 同步Kafka 的简单示例。需要注意的是,本示例仅供参考,实际应用中需要根据具体需求进行修改和优化。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值