一. 环境: 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"))