Spark Streaming可以用于实时流项目的开发,实时流项目的数据源除了可以来源于日志、文件、网络端口等,常常也有这种需求,那就是实时分析处理MySQL中的增量数据。面对这种需求当然我们可以通过JDBC的方式定时查询Mysql,然后再对查询到的数据进行处理也能得到预期的结果,但是Mysql往往还有其他业务也在使用,这些业务往往比较重要,通过JDBC方式频繁查询会对Mysql造成大量无形的压力,甚至可能会影响正常业务的使用,在基本不影响其他Mysql正常使用的情况下完成对增量数据的处理,那就需要 Canal 了。

假设Mysql中 canal_test 库下有一张表 policy_cred ,需要统计实时统计 policy_status 状态为1的 mor_rate 的的变化趋势,并标注比率的风险预警等级。

1. Canal
Canal [kə'næl] 是阿里巴巴开源的纯java开发的基于数据库binlog的增量订阅&消费组件。Canal的原理是模拟为一个Mysql slave的交互协议,伪装自己为MySQL slave,向Mysql Master发送dump协议,然后Mysql master接收到这个请求后将binary log推送给slave(也就是Canal),Canal解析binary log对象。详细可以查阅Canal的官方文档[alibaba/canal wiki]。
1.1 Canal 安装
Canal的server mode在1.1.x版本支持的有TPC、Kafka、RocketMQ。本次安装的canal版本为1.1.2,Canal版本最后在1.1.1之后。server端采用MQ模式,MQ选用Kafka。服务器系统为Centos7,其他环境为:jdk8、Scala 2.11、Mysql、Zookeeper、Kafka。
1.1.1 准备
安装Canal之前我们先把如下安装好
Mysql
a. 如果没有Mysql: 详细的安装过程可参考我的另一篇博客[Centos7环境下离线安装mysql 5.7 / mysql 8.0]
b. 开启Mysql的binlog。修改/etc/my.cnf,在[mysqld]下添加如下配置,改完之后重启 Mysql  /etc/init.d/mysql restart

  1. [mysqld]
  2. #添加这一行就ok
  3. log-bin=mysql-bin
  4. #选择row模式
  5. binlog-format=ROW
  6. #配置mysql replaction需要定义,不能和canal的slaveId重复
    server_id=1
  1. c. 创建一个Mysql用户并赋予相应权限,用于Canal使用
  1. mysql> CREATE USER canal IDENTIFIED BY 'canal';
  2. mysql> GRANT SELECT, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'canal'@'%';
  3. mysql> GRANT ALL PRIVILEGES ON *.* TO 'canal'@'%' ;
  4. mysql> FLUSH PRIVILEGES;

Zookeeper
因为安装Kafka时需要Zookeeper,例如ZK安装后地址为:cdh3:2181,cdh4:2181,cdh5:2181
Kafka
例如安装后的地址为:node1:9092,node2:9092,node3:9092
安装后创建一个Topic,例如创建一个 example

  1. kafka-topics.sh --create --zookeeper cdh3:,cdh4:,cdh5: --partitions --replication-factor --topic example

1.1.2 安装Canal

  1. 1. 下载Canal
  2. 访问CanalRelease canal v1.1.2
  1. wget https://github.com/alibaba/canal/releases/download/canal-1.1.2/canal.deployer-1.1.2.tar.gz
  1. 2. 解压
  2. 注意 这里一定要先创建出一个目录,直接解压会覆盖文件
  1. mkdir -p /usr/local/canal
  2. mv canal.deployer-1.1..tar.gz /usr/local/canal/
  3. tar -zxvf canal.deployer-1.1..tar.gz
  1. 3. 修改instance 配置文件
  2. vim $CANAL_HOME/conf/example/instance.properties,修改如下项,其他默认即可
  1. ## mysql serverId , v1.0.26+ will autoGen , 不要和server_id重复
  2. canal.instance.mysql.slaveId=
  3.  
  4. # position info。Mysql的url
  5. canal.instance.master.address=node1:
  6.  
  7. # table meta tsdb info
  8. canal.instance.tsdb.enable=false
  9.  
  10. # 这里配置前面在Mysql分配的用户名和密码
  11. canal.instance.dbUsername=canal
  12. canal.instance.dbPassword=canal
  13. canal.instance.connectionCharset=UTF-
  14. # 配置需要检测的库名,可以不配置,这里只检测canal_test库
  15. canal.instance.defaultDatabaseName=canal_test
  16. # enable druid Decrypt database password
  17. canal.instance.enableDruid=false
  18.  
  19. # 配置过滤的正则表达式,监测canal_test库下的所有表
  20. canal.instance.filter.regex=canal_test\\..*
  21.  
  22. # 配置MQ
  23. ## 配置上在Kafka创建的那个Topic名字
  24. canal.mq.topic=example
  25. ## 配置分区编号为1
  26. canal.mq.partition=
  27. . 修改canal.properties配置文件
  28. vim $CANAL_HOME/conf/canal.properties,修改如下项,其他默认即可
  29. # 这个是如果开启的是tcp模式,会占用这个11111端口,canal客户端通过这个端口获取数据
  30. canal.port =
  31.  
  32. # 可以配置为:tcp, kafka, RocketMQ,这里配置为kafka
  33. canal.serverMode = kafka
  34.  
  35. # 这里将这个注释掉,否则启动会有一个警告
  36. #canal.instance.tsdb.spring.xml = classpath:spring/tsdb/h2-tsdb.xml
  37.  
  38. ##################################################
  39. ######### MQ #############
  40. ##################################################
  41. canal.mq.servers = node1:,node2:,node3:
  42. canal.mq.retries =
  43. canal.mq.batchSize =
  44. canal.mq.maxRequestSize =
  45. canal.mq.lingerMs =
  46. canal.mq.bufferMemory =
  47. # Canal的batch size, 默认50K, 由于kafka最大消息体限制请勿超过1M(900K以下)
  48. canal.mq.canalBatchSize =
  49. # Canal get数据的超时时间, 单位: 毫秒, 空为不限超时
  50. canal.mq.canalGetTimeout =
  51. # 是否为flat json格式对象
  52. canal.mq.flatMessage = true
  53. canal.mq.compressionType = none
  54. canal.mq.acks = all
  55. # kafka消息投递是否使用事务
  56. #canal.mq.transaction = false
  1. 5. 启动Canal
  1. $CANAL_HOME/bin/startup.sh

6. 验证
查看日志
启动后会在logs下生成两个日志文件:logs/canal/canal.log、logs/example/example.log,查看这两个日志,保证没有报错日志。
如果是在虚拟机安装,最好给2个核数以上。
确保登陆的系统的hostname可以ping通。

在Mysql数据库中进行增删改查的操作,然后查看Kafka的topic为 example 的数据

  1. kafka-console-consumer.sh --bootstrap-server node1:,node2:,node3: --from-beginning --topic example

  1. 7. 关闭Canal
  2. 不用的时候一定要通过这个命令关闭,如果是用kill或者关机,当再次启动依然会提示要先执行stop.sh脚本后才能再启动。
  1. $CANAL_HOME/bin/stop.sh

*1.2 Canal 客户端代码
如果我们不使用Kafka作为Canal客户端,我们也可以用代码编写自己的Canal客户端,然后在代码中指定我们的数据去向。此时只需要将canal.properties配置文件中的canal.serverMode值改为tcp。编写我们的客户端代码。
在Maven项目的pom中引入:

  1. <dependency>
  2. <groupId>com.alibaba.otter</groupId>
  3. <artifactId>canal.client</artifactId>
  4. <version>1.1.</version>
  5. </dependency>
  1. 编写代码:
  1. /**
  2. * Canal客户端。
  3. * 注意:canal服务端只会连接一个客户端,当启用多个客户端时,其他客户端是就无法获取到数据。所以启动一个实例即可
  4. *
  5. * @see <a href="https://github.com/alibaba/canal/wiki/ClientExample">官方文档:ClientSample代码</a>
  6. *
  7. * Created by yore on 2019/3/16 10:50
  8. */
  9. public class SimpleCanalClientExample {
  10.  
  11. public static void main(String args[]) {
  12.  
  13. /**
  14. * 创建链接
  15. * SocketAddress: 如果提交到canal服务端所在的服务器上运行这里可以改为 new InetSocketAddress(AddressUtils.getHostIp(), 11111)
  16. * destination 通服务端canal.properties中的canal.destinations = example配置对应
  17. * username:
  18. * password:
  19. */
  20. CanalConnector connector = CanalConnectors.newSingleConnector(
  21. new InetSocketAddress("node1", ),
  22. "example", "", "");
  23. int batchSize = ;
  24. int emptyCount = ;
  25. try {
  26. connector.connect();
  27. connector.subscribe(".*\\..*");
  28. connector.rollback();
  29. int totalEmptyCount = ;
  30. while (emptyCount < totalEmptyCount) {
  31. Message message = connector.getWithoutAck(batchSize); // 获取指定数量的数据
  32. long batchId = message.getId();
  33. int size = message.getEntries().size();
  34. if (batchId == - || size == ) {
  35. emptyCount++;
  36. System.out.println("empty count : " + emptyCount);
  37. try {
  38. Thread.sleep();
  39. } catch (InterruptedException e) {
  40. }
  41. } else {
  42. emptyCount = ;
  43. // System.out.printf("message[batchId=%s,size=%s] \n", batchId, size);
  44. printEntry(message.getEntries());
  45. }
  46.  
  47. connector.ack(batchId); // 提交确认
  48. // connector.rollback(batchId); // 处理失败, 回滚数据
  49. }
  50.  
  51. System.out.println("empty too many times, exit");
  52. } finally {
  53. connector.disconnect();
  54. }
  55. }
  56.  
  57. private static void printEntry(List<Entry> entrys) {
  58. for (Entry entry : entrys) {
  59. if (entry.getEntryType() == EntryType.TRANSACTIONBEGIN || entry.getEntryType() == EntryType.TRANSACTIONEND) {
  60. continue;
  61. }
  62.  
  63. RowChange rowChage = null;
  64. try {
  65. rowChage = RowChange.parseFrom(entry.getStoreValue());
  66. } catch (Exception e) {
  67. throw new RuntimeException("ERROR ## parser of eromanga-event has an error , data:" + entry.toString(),
  68. e);
  69. }
  70.  
  71. EventType eventType = rowChage.getEventType();
  72. System.out.println(String.format("================&gt; binlog[%s:%s] , name[%s,%s] , eventType : %s",
  73. entry.getHeader().getLogfileName(), entry.getHeader().getLogfileOffset(),
  74. entry.getHeader().getSchemaName(), entry.getHeader().getTableName(),
  75. eventType));
  76.  
  77. /**
  78. * 如果只对某些库的数据操作,可以加如下判断:
  79. * if("库名".equals(entry.getHeader().getSchemaName())){
  80. * //TODO option
  81. * }
  82. *
  83. * 如果只对某些表的数据变动操作,可以加如下判断:
  84. * if("表名".equals(entry.getHeader().getTableName())){
  85. * //todo option
  86. * }
  87. *
  88. */
  89.  
  90. for (RowData rowData : rowChage.getRowDatasList()) {
  91. if (eventType == EventType.DELETE) {
  92. printColumn(rowData.getBeforeColumnsList());
  93. } else if (eventType == EventType.INSERT) {
  94. printColumn(rowData.getAfterColumnsList());
  95. } else {
  96. System.out.println("-------&gt; before");
  97. printColumn(rowData.getBeforeColumnsList());
  98. System.out.println("-------&gt; after");
  99. printColumn(rowData.getAfterColumnsList());
  100. }
  101. }
  102. }
  103. }
  104.  
  105. private static void printColumn(List<Column> columns) {
  106. for (Column column : columns) {
  107. System.out.println(column.getName() + " : " + column.getValue() + " update=" + column.getUpdated());
  108. }
  109. }
  110.  
  111. }
  1. 本地运行上述代码,我们修改Mysql数据中的数据,可在控制台中看到数据的改变:
  1. empty count :
  2. empty count :
  3. empty count :
  4. ================&gt; binlog[mysql-bin.:] , name[canal_test,customer] , eventType : INSERT
  5. id : update=true
  6. name : spark update=true
  7. empty count :
  8. empty count :
  9. empty count :

2. Spark 
通过上一步我们已经能够获取到 canal_test 库的变化数据,并且已经可将将变化的数据实时推送到Kafka中,Kafka中接收到的数据是一条Json格式的数据,我们需要对 INSERT 和 UPDATE 类型的数据处理,并且只处理状态为1的数据,然后需要计算 mor_rate 的变化,并判断 mor_rate 的风险等级,0-75%为G1等级,75%-80%为R1等级,80%-100%为R2等级。最后将处理的结果保存到DB,可以保存到Redis、Mysql、MongoDB,或者推送到Kafka都可以。这里是将结果数据保存到了Mysql。
2.1 在Mysql中创建如下两张表

  1. -- canal_test库下创建表
  2. CREATE TABLE `policy_cred` (
  3. p_num varchar() NOT NULL,
  4. policy_status varchar() DEFAULT NULL COMMENT '状态:0、1',
  5. mor_rate decimal(,) DEFAULT NULL,
  6. load_time datetime DEFAULT NULL,
  7. PRIMARY KEY (`p_num`)
  8. ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
  9.  
  10. -- real_result库下创建表
  11. CREATE TABLE `real_risk` (
  12. p_num varchar() NOT NULL,
  13. risk_rank varchar() DEFAULT NULL COMMENT '等级:G1、R1、R2',
  14. mor_rate decimal(,) ,
  15. ch_mor_rate decimal(,),
  16. load_time datetime DEFAULT NULL
  17. ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

2.2 Spark代码开发:

2.2.1 在resources下new一个项目的配置文件my.properties

  1. ## spark
  2. # spark://cdh3:7077
  3. spark.master=local[]
  4. spark.app.name=m_policy_credit_app
  5. spark.streaming.durations.sec=
  6. spark.checkout.dir=src/main/resources/checkpoint
  7.  
  8. ## Kafka
  9. bootstrap.servers=node1:,node2:,node3:
  10. group.id=m_policy_credit_gid
  11. # latest, earliest, none
  12. auto.offset.reset=latest
  13. enable.auto.commit=false
  14. kafka.topic.name=example
  15.  
  16. ## Mysql
  17. mysql.jdbc.driver=com.mysql.jdbc.Driver
  18. mysql.db.url=jdbc:mysql://node1:3306/real_result
  19. mysql.user=root
  20. mysql.password=
  21. mysql.connection.pool.size=

2.2.2 在pom.xml文件中引入如下

  1. <properties>
  2. <project.build.sourceEncoding>UTF-</project.build.sourceEncoding>
  3. <project.reporting.outputEncoding>UTF-</project.reporting.outputEncoding>
  4. <maven.compiler.source>1.8</maven.compiler.source>
  5. <maven.compiler.target>1.8</maven.compiler.target>
  6. <scala.version>2.11.</scala.version>
  7. <spark.version>2.4.</spark.version>
  8. <canal.client.version>1.1.</canal.client.version>
  9. </properties>
  10.  
  11. <dependencies>
  12. <dependency>
  13. <groupId>com.alibaba.otter</groupId>
  14. <artifactId>canal.client</artifactId>
  15. <version>${canal.client.version}</version>
  16. <exclusions>
  17. <exclusion>
  18. <groupId>io.netty</groupId>
  19. <artifactId>netty-all</artifactId>
  20. </exclusion>
  21. </exclusions>
  22. </dependency>
  23.  
  24. <dependency>
  25. <groupId>org.scala-lang</groupId>
  26. <artifactId>scala-library</artifactId>
  27. <version>${scala.version}</version>
  28. </dependency>
  29.  
  30. <!-- Spark -->
  31. <!-- spark-core -->
  32. <dependency>
  33. <groupId>org.apache.spark</groupId>
  34. <artifactId>spark-core_2.</artifactId>
  35. <version>${spark.version}</version>
  36. </dependency>
  37. <!-- spark-streaming -->
  38. <dependency>
  39. <groupId>org.apache.spark</groupId>
  40. <artifactId>spark-streaming_2.</artifactId>
  41. <version>${spark.version}</version>
  42. </dependency>
  43. <!-- spark-streaming-kafka -->
  44. <dependency>
  45. <groupId>org.apache.spark</groupId>
  46. <artifactId>spark-streaming-kafka--10_2.</artifactId>
  47. <version>${spark.version}</version>
  48. </dependency>
  49. <!-- spark-sql -->
  50. <dependency>
  51. <groupId>org.apache.spark</groupId>
  52. <artifactId>spark-sql_2.</artifactId>
  53. <version>${spark.version}</version>
  54. </dependency>
  55.  
  56. <dependency>
  57. <groupId>org.apache.hadoop</groupId>
  58. <artifactId>hadoop-client</artifactId>
  59. <version>2.6.</version>
  60. </dependency>
  61.  
  62. <dependency>
  63. <groupId>com.alibaba</groupId>
  64. <artifactId>fastjson</artifactId>
  65. <version>1.2.</version>
  66. </dependency>
  67.  
  68. <dependency>
  69. <groupId>mysql</groupId>
  70. <artifactId>mysql-connector-java</artifactId>
  71. <version>5.1.</version>
  72. </dependency>
  73.  
  74. </dependencies>
  1. package yore.spark
  2.  
  3. import java.util.Properties
  4.  
  5. /**
  6. * Properties的工具类
  7. *
  8. * Created by yore on 2018-06-29 14:05
  9. */
  10. object PropertiesUtil {
  11.  
  12. private val properties: Properties = new Properties
  13.  
  14. /**
  15. *
  16. * 获取配置文件Properties对象
  17. *
  18. * @author yore
  19. * @return java.util.Properties
  20. * date 2018/6/29 14:24
  21. */
  22. def getProperties() :Properties = {
  23. if(properties.isEmpty){
  24. //读取源码中resource文件夹下的my.properties配置文件
  25. val reader = getClass.getResourceAsStream("/my.properties")
  26. properties.load(reader)
  27. }
  28. properties
  29. }
  30.  
  31. /**
  32. *
  33. * 获取配置文件中key对应的字符串值
  34. *
  35. * @author yore
  36. * @return java.util.Properties
  37. * @date 2018/6/29 14:24
  38. */
  39. def getPropString(key : String) : String = {
  40. getProperties().getProperty(key)
  41. }
  42.  
  43. /**
  44. *
  45. * 获取配置文件中key对应的整数值
  46. *
  47. * @author yore
  48. * @return java.util.Properties
  49. * @date 2018/6/29 14:24
  50. */
  51. def getPropInt(key : String) : Int = {
  52. getProperties().getProperty(key).toInt
  53. }
  54.  
  55. /**
  56. *
  57. * 获取配置文件中key对应的布尔值
  58. *
  59. * @author yore
  60. * @return java.util.Properties
  61. * @date 2018/6/29 14:24
  62. */
  63. def getPropBoolean(key : String) : Boolean = {
  64. getProperties().getProperty(key).toBoolean
  65. }
  66.  
  67. }

2.2.4 在scala源码目录下的包下编写数据库操作的工具类

  1. package yore.spark
  2.  
  3. import java.sql.{Connection, DriverManager, PreparedStatement, ResultSet, SQLException}
  4. import java.util.concurrent.LinkedBlockingDeque
  5.  
  6. import scala.collection.mutable.ListBuffer
  7.  
  8. /**
  9. *
  10. * Created by yore on 2018/11/14 20:34
  11. */
  12. object JDBCWrapper {
  13. private var jdbcInstance : JDBCWrapper = _
  14. def getInstance() : JDBCWrapper = {
  15. synchronized{
  16. if(jdbcInstance == null){
  17. jdbcInstance = new JDBCWrapper()
  18. }
  19. }
  20. jdbcInstance
  21. }
  22.  
  23. }
  24.  
  25. class JDBCWrapper {
  26. // 连接池的大小
  27. val POOL_SIZE : Int = PropertiesUtil.getPropInt("mysql.connection.pool.size")
  28.  
  29. val dbConnectionPool = new LinkedBlockingDeque[Connection](POOL_SIZE)
  30. try
  31. Class.forName(PropertiesUtil.getPropString("mysql.jdbc.driver"))
  32. catch {
  33. case e: ClassNotFoundException => e.printStackTrace()
  34. }
  35.  
  36. for(i <- until POOL_SIZE){
  37. try{
  38. val conn = DriverManager.getConnection(
  39. PropertiesUtil.getPropString("mysql.db.url"),
  40. PropertiesUtil.getPropString("mysql.user"),
  41. PropertiesUtil.getPropString("mysql.password"));
  42. dbConnectionPool.put(conn)
  43. }catch {
  44. case e : Exception => e.printStackTrace()
  45. }
  46. }
  47.  
  48. def getConnection(): Connection = synchronized{
  49. while ( == dbConnectionPool.size()){
  50. try{
  51. Thread.sleep()
  52. }catch {
  53. case e : InterruptedException => e.printStackTrace()
  54. }
  55. }
  56. dbConnectionPool.poll()
  57. }
  58.  
  59. /**
  60. * 批量插入
  61. *
  62. * @param sqlText sql语句字符
  63. * @param paramsList 参数列表
  64. * @return Array[Int]
  65. */
  66. def doBatch(sqlText: String, paramsList: ListBuffer[ParamsList]): Array[Int] = {
  67. val conn: Connection = getConnection()
  68. var ps: PreparedStatement = null
  69. var result: Array[Int] = null
  70.  
  71. try{
  72. conn.setAutoCommit(false)
  73. ps = conn.prepareStatement(sqlText)
  74.  
  75. for (paramters <- paramsList) {
  76. paramters.params_Type match {
  77. case "real_risk" => {
  78. println("$$$\treal_risk\t" + paramsList)
  79. // // p_num, risk_rank, mor_rate, ch_mor_rate, load_time
  80. ps.setObject(, paramters.p_num)
  81. ps.setObject(, paramters.risk_rank)
  82. ps.setObject(, paramters.mor_rate)
  83. ps.setObject(, paramters.ch_mor_rate)
  84. ps.setObject(, paramters.load_time)
  85. }
  86. }
  87. ps.addBatch()
  88. }
  89. result = ps.executeBatch
  90. conn.commit()
  91. } catch {
  92. case e: Exception => e.printStackTrace()
  93. } finally {
  94. if (ps != null) try {
  95. ps.close()
  96. } catch {
  97. case e: SQLException => e.printStackTrace()
  98. }
  99.  
  100. if (conn != null) try {
  101. dbConnectionPool.put(conn)
  102. } catch {
  103. case e: InterruptedException => e.printStackTrace()
  104. }
  105. }
  106. result
  107. }
  108.  
  109. }

2.2.5 在scala源码目录下的包下编写Spark程序代码

  1. package yore.spark
  2.  
  3. import com.alibaba.fastjson.{JSON, JSONArray, JSONObject}
  4. import org.apache.kafka.common.serialization.StringDeserializer
  5. import org.apache.log4j.{Level, Logger}
  6. import org.apache.spark.SparkConf
  7. import org.apache.spark.streaming.kafka010.ConsumerStrategies.Subscribe
  8. import org.apache.spark.streaming.kafka010.KafkaUtils
  9. import org.apache.spark.streaming.kafka010.LocationStrategies.PreferConsistent
  10. import org.apache.spark.streaming.{Seconds, StreamingContext}
  11.  
  12. import scala.collection.mutable.ListBuffer
  13.  
  14. /**
  15. *
  16. * Created by yore on 2019/3/16 15:11
  17. */
  18. object M_PolicyCreditApp {
  19.  
  20. def main(args: Array[String]): Unit = {
  21.  
  22. // 设置日志的输出级别
  23. Logger.getLogger("org").setLevel(Level.ERROR)
  24.  
  25. val conf = new SparkConf()
  26. .setMaster(PropertiesUtil.getPropString("spark.master"))
  27. .setAppName(PropertiesUtil.getPropString("spark.app.name"))
  28. // !!必须设置,否则Kafka数据会报无法序列化的错误
  29. .set("spark.serializer", "org.apache.spark.serializer.KryoSerializer")
  30. //如果环境中已经配置HADOOP_HOME则可以不用设置hadoop.home.dir
  31. System.setProperty("hadoop.home.dir", "/Users/yoreyuan/soft/hadoop-2.9.2")
  32.  
  33. val ssc = new StreamingContext(conf, Seconds(PropertiesUtil.getPropInt("spark.streaming.durations.sec").toLong))
  34. ssc.sparkContext.setLogLevel("ERROR")
  35. ssc.checkpoint(PropertiesUtil.getPropString("spark.checkout.dir"))
  36.  
  37. val kafkaParams = Map[String, Object](
  38. "bootstrap.servers" -> PropertiesUtil.getPropString("bootstrap.servers"),
  39. "key.deserializer" -> classOf[StringDeserializer],
  40. "value.deserializer" -> classOf[StringDeserializer],
  41. "group.id" -> PropertiesUtil.getPropString("group.id"),
  42. "auto.offset.reset" -> PropertiesUtil.getPropString("auto.offset.reset"),
  43. "enable.auto.commit" -> (PropertiesUtil.getPropBoolean("enable.auto.commit"): java.lang.Boolean)
  44. )
  45. val topics = Array(PropertiesUtil.getPropString("kafka.topic.name"))
  46.  
  47. val kafkaStreaming = KafkaUtils.createDirectStream[String, String](
  48. ssc,
  49. PreferConsistent,
  50. Subscribe[String, String](topics, kafkaParams)
  51. )
  52.  
  53. kafkaStreaming.map[JSONObject](line => { // str转成JSONObject
  54. println("$$$\t" + line.value())
  55. JSON.parseObject(line.value)
  56. }).filter(jsonObj =>{ // 过滤掉非 INSERT和UPDATE的数据
  57. if(null == jsonObj || !"canal_test".equals(jsonObj.getString("database")) ){
  58. false
  59. }else{
  60. val chType = jsonObj.getString("type")
  61. if("INSERT".equals(chType) || "UPDATE".equals(chType)){
  62. true
  63. }else{
  64. false
  65. }
  66. }
  67. }).flatMap[(JSONObject, JSONObject)](jsonObj => { // 将改变前和改变后的数据转成Tuple
  68. var oldJsonArr: JSONArray = jsonObj.getJSONArray("old")
  69. val dataJsonArr: JSONArray = jsonObj.getJSONArray("data")
  70. if("INSERT".equals(jsonObj.getString("type"))){
  71. oldJsonArr = new JSONArray()
  72. val oldJsonObj2 = new JSONObject()
  73. oldJsonObj2.put("mor_rate", "")
  74. oldJsonArr.add(oldJsonObj2)
  75. }
  76.  
  77. val result = ListBuffer[(JSONObject, JSONObject)]()
  78.  
  79. for(i <- until oldJsonArr.size ) {
  80. val jsonTuple = (oldJsonArr.getJSONObject(i), dataJsonArr.getJSONObject(i))
  81. result += jsonTuple
  82. }
  83. result
  84. }).filter(t => { // 过滤状态不为1的数据,和mor_rate没有改变的数据
  85. val policyStatus = t._2.getString("policy_status")
  86. if(null != policyStatus && "".equals(policyStatus) && null!= t._1.getString("mor_rate")){
  87. true
  88. }else{
  89. false
  90. }
  91. }).map(t => {
  92. val p_num = t._2.getString("p_num")
  93. val nowMorRate = t._2.getString("mor_rate").toDouble
  94. val chMorRate = nowMorRate - t._1.getDouble("mor_rate")
  95. val riskRank = gainRiskRank(nowMorRate)
  96.  
  97. // p_num, risk_rank, mor_rate, ch_mor_rate, load_time
  98. (p_num, riskRank, nowMorRate, chMorRate, new java.util.Date)
  99. }).foreachRDD(rdd => {
  100. rdd.foreachPartition(p => {
  101. val paramsList = ListBuffer[ParamsList]()
  102. val jdbcWrapper = JDBCWrapper.getInstance()
  103. while (p.hasNext){
  104. val record = p.next()
  105. val paramsListTmp = new ParamsList
  106. paramsListTmp.p_num = record._1
  107. paramsListTmp.risk_rank = record._2
  108. paramsListTmp.mor_rate = record._3
  109. paramsListTmp.ch_mor_rate = record._4
  110. paramsListTmp.load_time = record._5
  111. paramsListTmp.params_Type = "real_risk"
  112. paramsList += paramsListTmp
  113. }
  114. /**
  115. * VALUES(p_num, risk_rank, mor_rate, ch_mor_rate, load_time)
  116. */
  117. val insertNum = jdbcWrapper.doBatch("INSERT INTO real_risk VALUES(?,?,?,?,?)", paramsList)
  118. println("INSERT TABLE real_risk: " + insertNum.mkString(", "))
  119. })
  120. })
  121.  
  122. ssc.start()
  123. ssc.awaitTermination()
  124.  
  125. }
  126.  
  127. def gainRiskRank(rate: Double): String = {
  128. var result = ""
  129. if(rate>=0.75 && rate<0.8){
  130. result = "R1"
  131. }else if(rate >=0.80 && rate<=){
  132. result = "R2"
  133. }else{
  134. result = "G1"
  135. }
  136. result
  137. }
  138.  
  139. }
  140.  
  141. /**
  142. * 结果表对应的参数实体对象
  143. */
  144. class ParamsList extends Serializable{
  145. var p_num: String = _
  146. var risk_rank: String = _
  147. var mor_rate: Double = _
  148. var ch_mor_rate: Double = _
  149. var load_time:java.util.Date = _
  150. var params_Type : String = _
  151. override def toString = s"ParamsList($p_num, $risk_rank, $mor_rate, $ch_mor_rate, $load_time)"
  152. }
  153.  

3. 测试
启动 ZK、Kafka、Canal。
在 canal_test 库下的 policy_cred 表中插入或者修改数据,
然后查看 real_result 库下的 real_risk 表中结果。

更新一条数据时Kafka接收到的json数据如下(这是canal投送到Kafka中的数据格式,包含原始数据、修改后的数据、库名、表名等信息):

  1. {
  2. "data": [
  3. {
  4. "p_num": "",
  5. "policy_status": "",
  6. "mor_rate": "0.8800",
  7. "load_time": "2019-03-17 12:54:57"
  8. }
  9. ],
  10. "database": "canal_test",
  11. "es": ,
  12. "id": ,
  13. "isDdl": false,
  14. "mysqlType": {
  15. "p_num": "varchar(22)",
  16. "policy_status": "varchar(2)",
  17. "mor_rate": "decimal(20,4)",
  18. "load_time": "datetime"
  19. },
  20. "old": [
  21. {
  22. "mor_rate": "0.5500"
  23. }
  24. ],
  25. "sql": "",
  26. "sqlType": {
  27. "p_num": ,
  28. "policy_status": ,
  29. "mor_rate": ,
  30. "load_time":
  31. },
  32. "table": "policy_cred",
  33. "ts": ,
  34. "type": "UPDATE"
  35. }
  1. 查看Mysql中的结果表

4、出现的问题

在开发Spark代码有时项目可能会引入大量的依赖包,依赖包之间可能就会发生冲突,比如发生如下错误:

  1. Exception in thread "main" java.lang.NoSuchMethodError: io.netty.buffer.PooledByteBufAllocator.<init>(ZIIIIIIIZ)V
  2. at org.apache.spark.network.util.NettyUtils.createPooledByteBufAllocator(NettyUtils.java:)
  3. at org.apache.spark.network.client.TransportClientFactory.<init>(TransportClientFactory.java:)
  4. at org.apache.spark.network.TransportContext.createClientFactory(TransportContext.java:)
  5. at org.apache.spark.rpc.netty.NettyRpcEnv.<init>(NettyRpcEnv.scala:)
  6. at org.apache.spark.rpc.netty.NettyRpcEnvFactory.create(NettyRpcEnv.scala:)
  7. at org.apache.spark.rpc.RpcEnv$.create(RpcEnv.scala:)
  8. at org.apache.spark.SparkEnv$.create(SparkEnv.scala:)
  9. at org.apache.spark.SparkEnv$.createDriverEnv(SparkEnv.scala:)
  10. at org.apache.spark.SparkContext.createSparkEnv(SparkContext.scala:)
  11. at org.apache.spark.SparkContext.<init>(SparkContext.scala:)
  12. at org.apache.spark.streaming.StreamingContext$.createNewSparkContext(StreamingContext.scala:)
  13. at org.apache.spark.streaming.StreamingContext.<init>(StreamingContext.scala:)
  14. at yore.spark.M_PolicyCreditApp$.main(M_PolicyCreditApp.scala:)
  15. at yore.spark.M_PolicyCreditApp.main(M_PolicyCreditApp.scala)

我们可以在项目的根目录下的命令窗口中输人:mvn dependency:tree -Dverbose> dependency.log

然后可以在项目根目录下生产一个dependency.log文件,查看这个文件,在文件中搜索 io.netty 关键字,找到其所在的依赖包:

然就在canal.client将io.netty排除掉

  1. <dependency>
  2. <groupId>com.alibaba.otter</groupId>
  3. <artifactId>canal.client</artifactId>
  4. <version>${canal.client.version}</version>
  5. <exclusions>
  6. <exclusion>
  7. <groupId>io.netty</groupId>
  8. <artifactId>netty-all</artifactId>
  9. </exclusion>
  10. </exclusions>
  11. </dependency>
  1.  

基于Spark Streaming + Canal + Kafka对Mysql增量数据实时进行监测分析的更多相关文章

  1. Canal:同步mysql增量数据工具,一篇详解核心知识点

    老刘是一名即将找工作的研二学生,写博客一方面是总结大数据开发的知识点,一方面是希望能够帮助伙伴让自学从此不求人.由于老刘是自学大数据开发,博客中肯定会存在一些不足,还希望大家能够批评指正,让我们一起进 ...

  2. Spark Streaming、Kafka结合Spark JDBC External DataSouces处理案例

    场景:使用Spark Streaming接收Kafka发送过来的数据与关系型数据库中的表进行相关的查询操作: Kafka发送过来的数据格式为:id.name.cityId,分隔符为tab zhangs ...

  3. 苏宁基于Spark Streaming的实时日志分析系统实践 Spark Streaming 在数据平台日志解析功能的应用

    https://mp.weixin.qq.com/s/KPTM02-ICt72_7ZdRZIHBA 苏宁基于Spark Streaming的实时日志分析系统实践 原创: AI+落地实践 AI前线 20 ...

  4. Spark streaming消费Kafka的正确姿势

    前言 在游戏项目中,需要对每天千万级的游戏评论信息进行词频统计,在生产者一端,我们将数据按照每天的拉取时间存入了Kafka当中,而在消费者一端,我们利用了spark streaming从kafka中不 ...

  5. spark streaming 对接kafka记录

    spark streaming 对接kafka 有两种方式: 参考: http://group.jobbole.com/15559/ http://blog.csdn.net/kwu_ganymede ...

  6. 【转】Spark Streaming和Kafka整合开发指南

    基于Receivers的方法 这个方法使用了Receivers来接收数据.Receivers的实现使用到Kafka高层次的消费者API.对于所有的Receivers,接收到的数据将会保存在Spark ...

  7. spark streaming集成kafka

    Kakfa起初是由LinkedIn公司开发的一个分布式的消息系统,后成为Apache的一部分,它使用Scala编写,以可水平扩展和高吞吐率而被广泛使用.目前越来越多的开源分布式处理系统如Clouder ...

  8. spark streaming 整合 kafka(一)

    转载:https://www.iteblog.com/archives/1322.html Apache Kafka是一个分布式的消息发布-订阅系统.可以说,任何实时大数据处理工具缺少与Kafka整合 ...

  9. 【自动化】基于Spark streaming的SQL服务实时自动化运维

    设计背景 spark thriftserver目前线上有10个实例,以往通过监控端口存活的方式很不准确,当出故障时进程不退出情况很多,而手动去查看日志再重启处理服务这个过程很低效,故设计利用Spark ...

随机推荐

  1. prometheus杂碎

    一个监控及告警的系统,内含一个TSDB(时序数据库).在我而言是一个数采程序 重要成员分三块 exploter:实际是外部接口,让各个程序实现这个接口,供普罗米修斯定时从此接口中取数 alert:告警 ...

  2. fiddler 中显示请求 IP

    在 Rules -> Customize Rules... 中,static function Main() 中加一行 FiddlerObject.UI.lvSessions.AddBoundC ...

  3. opencv wlsfilter depth refinement demo

    参考 https://docs.opencv.org/3.2.0/d3/d14/tutorial_ximgproc_disparity_filtering.html // This file is p ...

  4. 【分享】Asp.net Core相关教程及开源项目

    入门 全新的ASP.NET:  https://www.cnblogs.com/Leo_wl/p/5654828.html 在IIS上部署你的ASP.NET Core项目: https://www.c ...

  5. SpringCloud(一)浅谈SpringCloud

    前言 现在微服务实在是太火了,所以我们必不可少的是要学习一下SpringCloud了,服务化的核心就是将传统的一站式应用 根据业务拆分成一个一个的服务,而微服务在这个基础上要更彻底地去耦合(不再共享D ...

  6. sbt spark2.3.1 idea打包Caused by: java.lang.ClassNotFoundException: scala.Product$class

    今天同事在服务区上面装的是最新版本的hadoop3.10和spark2.3.1,因为用scala开发, 所以我想用sbt进行开发.过程中遇到各种坑,刚开始用的jdk10,结果也报错,后来改成jdk1. ...

  7. 【CentOS-7+ Ambari 2.7.0 + HDP 3.0+HAWQ2.3.00】遭遇问题及解决记录

    一.zookeeper超出最大连接限制:ambari server检测到critical错误, zookeeper server on ep-bd01:2181 连接被积极拒绝,翻看主机上zookee ...

  8. 我的预约订单页面List

    <%@ page language="java" contentType="text/html;charset=UTF-8"%> <%@ ta ...

  9. PXC 57 二进制安装

    1.准备阶段 1.1 在三个节点上分别创建:用户组 用户组 目录 --用户组 用户组 #/usr/sbin/groupadd mysql #/usr/sbin/useradd -g mysql mys ...

  10. SpringBoot入坑-请求参数传递

    前一篇我们探讨了关于springboot的配置文件和Controller的使用,本篇我们来一起探讨一下关于springboot如何传递参数的知识. 参数传递我们最常见的就是在url后通过?/& ...