Maxwell监听mysql的binlog日志变化写入kafka消费者

一. 环境: maxwell:v1.29.2   (从1.30开始maxwell停止了对java8的使用,改为为11)

 maxwell1.29.2这个版本对mysql8.0以后的缺少utf8mb3字符的解码问题,需要对原码中加上一个部分内容 :具体也给大家做了总结 : 

关于v1.29.2 版本的Maxwell存在于mysql8.0后版本部分源码字符集处理确实问题-CSDN博客

二. 程序这里还是那一个kafka模拟器来实时向mysql写数据做测试:

               

 maxwell可以成功将监听到的binlog日志信息写入到kafka的主题中去消费

三. 关于maxwell的配置以及启动方式:

1. 配置mysql的my.cnf配置文件开启binlog日志

 sudo  vim  /etc/my.cnf


在[mysqld]模块下添加一下内容

[mysqld]
server_id=1
log-bin=mysql-bin
binlog_format=row

#binlog-do-db=test_maxwell
 


然后并重启 Mysql 服务
sudo  systemctl  restart  mysqld
 


登录 mysql 并查看是否修改完成 使用:
mysql>   show  variables  like  '%binlog%';
查看下列属性

binlog_format	|   ROW

 2.查看mysql生成的binlog日志文件:正常会生成二个文件 一个初始化文件,一个索引记录文件

cd  /var/lib/mysql
 sudo  ls  -l |grep binlog
总用量  188500
-rw-r-----.   1   mysql   mysql	154   11 月  17   16:30   mysql-
bin.000001
-rw-r-----.   1   mysql   mysql	19   11 月  17   16:30   mysql-
bin.index

 3.初始化Maxwell元数据库

(1)建立一个 maxwell 库用于存储 Maxwell 的元数据
mysql>   CREATE   DATABASE   maxwell;
(2)设置 mysql 用户密码安全级别感觉8.1后也不管用啊
mysql> set   global   validate_password_length=4;
mysql> set   global   validate_password_policy=0;
(3)创建一个maxwell账号可以操作该数据库 :user=maxwell ,PW:123456
mysql> GRANT ALL ON maxwell.* TO 'maxwell'@'%' IDENTIFIED BY '123456';
(4)分配这个账号可以监控其他数据库的权限,查看其对应的binlog日志
mysql> GRANT SELECT ,REPLICATION SLAVE , REPLICATION CLIENT ON
*.*  TO  maxwell@'%';
(5)最后记得刷新权限即可
mysql>flush   privileges;

4.启动maxwell的方式有二种:

4.1 .纯指令式

4.1.1 : 打印在console

bin/maxwell 
--user='maxwell' 
--password='123456' 
--host='hadoop102' 
--producer=stdout

4.1.2  : binlog消费到kafka的topic中

bin/maxwell 
--user='maxwell'
--password='123456!'
--host='localhost'
--producer=kafka 
--kafka.bootstrap.servers=hadoop103:9092,hadoop104:9092
--kafka_topic=maxwelltest

4.2 指定config文件式

vim  config.properties
 

#   tl;dr   config
log_level=info
producer=kafka
 
#   mysql   login   info
host=hadoop102
user=maxwell
password=123456

#	***   kafka   ***
#   list   of   kafka   brokers

kafka.bootstrap.servers=hosta:9092,hostb:9092
kafka_topic=maxwell
 

这种config式的配置之后在使用指令式的时候不能再加host参数和指定value了会报错

 5.开启kafka消费者来消费binlog二进制数据

kafka-console-consumer.sh --bootstrap-server hadoop102:9092 --topic topic_name

# 如果要kafka消费的数据直接写入hdfs:

kafka-console-consumer.sh --bootstrap-server hadoop103:9092,hadoop104:9092 --topic topic-name | hadoop fs -put - /dataset/lixianData/text.txt  # 这里写入到这个txt文件中,提前建好

原码:获取实时json写入mysql maxwell监听


import org.apache.spark.sql.types.{IntegerType, StringType, StructType}
import org.apache.spark.sql.{DataFrame, Dataset, ForeachWriter, Row, SparkSession}
import org.apache.spark.sql.functions.{col, from_json}
import org.apache.spark.sql.streaming.{OutputMode, Trigger}

import java.sql.{Connection, DriverManager, Statement}

/**
 * ClassName: stramLuoMysql
 * Description:   kafka 模拟器产生数据 linux 启动消费者——> 消费到数据 -->structuredStreaming handle 数据 并存储到mysql
 *
 * @Author 数开_11
 * @Create 2024-03-15 19:39
 * @Version 1.0
 */


object stramLuoMysql {
  def main(args: Array[String]): Unit = {


    val spark: SparkSession = SparkSession.builder()
      .appName("luodimysql_second")
//      .master("local[3]")
      .getOrCreate()



    import spark.implicits._

    // 指定json的schema
    val schema = new StructType()
      .add("id",dataType = IntegerType)
      .add("name",dataType = StringType)
      .add("sorce",dataType = IntegerType)


    val source: DataFrame = spark.readStream
      .format("kafka")
      .option("kafka.bootstrap.servers", "102:9092,hadoop103:9092,hadoop104:9092")
      .option("subscribe", "topic-name")
      .option("startingOffsets", "latest")
      .option("failOnDataLoss",false)
      .load()

    val result: DataFrame = source.selectExpr("cast(value as String) as message")
      .select(from_json('message, schema).alias("data"))

//    val restult1: Dataset[Row] = result.coalesce(1)   // 合并分区数 , 但此程序不需要

    // 重写连接数据库的参数参数
    class MysqlWriter extends  ForeachWriter[Row] {
      /**
       *   1. driver
       *   2. connection
       *   3.statement
       *   4.url
       */
        val driver = "com.mysql.cj.jdbc.Driver"
      private var connection: Connection = _
       private var statement: Statement= _
       private val url = "jdbc:mysql://hadoop103:3306/kafkastream"


      override def open(partitionId: Long, epochId: Long): Boolean = {

        try {
          /**
           *  1.  获取driver驱动
           *  2.  获取数据库连接对象
           *  3. 关闭自动提交(必须) , 确保数据一致性
           *  4.  创建一个用于执行sql语句的对象 statement
           */
          Class.forName(driver)
          connection = DriverManager.getConnection(url,"user","pw")
          connection.setAutoCommit(false) // 手动管理事务,取消自动提交,确保数据一致性  很重要!!!
          statement = connection.createStatement()
          true
        } catch {
          case e: Exception =>
            false
        }
      }

      override def process(value: Row): Unit = {
        // sql插入过程
        //   data -> id,name,score
        // so 这里先的获取data

        val data = value.getAs[Row]("data")
        if (data != null) {
          val id = data.getAs[Int]("id")
          val name = data.getAs[String]("name")
          val sorce = data.getAs[Int]("sorce")

          if (id != null && name != null && sorce != null) {
            // 1. 常规sql写法:
//            val insertSql = s"insert into streamdata(id, name, sorce) values (${id}, '${name}', ${sorce})"
//            statement.executeUpdate(insertSql)
            // 2. 预处理写法
            val statement1 = connection.prepareStatement("insert into streamdata(id, name, sorce)values(?,?,?)")
            statement1.setInt(1,id)
            statement1.setString(2,name)
            statement1.setInt(3,sorce)
            // 3.一定要执行更新操作
            statement1.executeUpdate()
          } else {
            println("Some field values were null.")
          }
        } else {
          println("No data_value found.")
        }
      }

      override def close(errorOrNull: Throwable): Unit = {

        // 这里的逻辑很重要否则就是空指针报错
        if (errorOrNull ==null){
          connection.commit()
        }else{
          connection.rollback()
        }
        if(statement != null){
          statement.close()
        }
        if(connection != null){
          connection.close()
        }
      }
    }

    // stream写入
      //
    result
      .writeStream
      .foreach(new MysqlWriter)
      .trigger(Trigger.ProcessingTime("1 second"))   // 毫秒级的响应处理
      .outputMode(OutputMode.Append())
      .option("checkpointLocation","hdfs://hadoop102:8020/checkpoint")
      .start()
      .awaitTermination()


  }

补:

    /**
     * 处理非常规json
     * 定义一个udf函数处理不正常的json
     */
      def replacesingle(s:String): String ={
        s.replaceAll("'"," \" ")
      }   // 处理json
    val repalceudf: UserDefinedFunction = udf(replacesingle _)
    val repalcemessge: DataFrame = source.select(repalceudf(col("value")).as("message"))
    repalcemessge.select(from_json(col("message"),schema).alias("data"))

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值