本文记录了学习使用Spark Streaming通过JDBC操作数据库的过程,源数据从Kafka中读取。

Kafka从0.10版本提供了一种新的消费者API,和0.8不同,因此Spark Streaming也提供了两种API与之对应,其中spark-streaming-kafka-0-8支持Kafka 0.8.2.1以后的Broker;spark-streaming-kafka-0-10支持0.10.0以上Broker,处于实验阶段。两者的对比如下表所示。

|spark-streaming-kafka-0-8 | spark-streaming-kafka-0-10
--------------- |------------------------ |--------------------------
Broker Version | 0.8.2.1 or higher | 0.10.0 or higher
Api Stability | Stable | Experimental
Language Support | Scala, Java, Python | Scala, Java
Receiver DStream | Yes | No
Direct DStream | Yes | Yes
SSL / TLS Support | No | Yes
Offset Commit Api | No | Yes
Dynamic Topic Subscription | No | Yes

Spark Streaming集成Kafka的说明可以参考如下资料:

spark-streaming-kafka-0-10 http://spark.apache.org/docs/latest/streaming-kafka-0-10-integration.html

spark-streaming-kafka-0-8 http://spark.apache.org/docs/latest/streaming-kafka-0-8-integration.html

1.2 运行组件

本文所写的例子基于JDK1.8和Scala 2.11,运行依赖组件的情况如下表所示。

组件 部署方式 IP地址 操作系统
spark-2.0.1 伪分布式 192.168.1.91 CentOS 7.1
Kafka-0.10.0.1 伪分布式 192.168.1.90 CentOS 7.1
postgresql-9.4.5 单机 192.168.1.213 CentOS 7.1

数据库中创建了一张名为kafka_message的表,共有三个字段,都是varchar类型。

CREATE TABLE kafka_message (
timeseq varchar(16),
thread varchar(32),
message varchar(255)
);

2. 代码

2.1 pom.xml

依赖的lib如下。

<dependencies>
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-streaming_2.11</artifactId>
<version>2.0.1</version>
</dependency>
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-streaming-kafka-0-10_2.11</artifactId>
<version>2.0.1</version>
</dependency>
<dependency>
<groupId>com.jolbox</groupId>
<artifactId>bonecp</artifactId>
<version>0.8.0.RELEASE</version>
</dependency>
<dependency>
<groupId>postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>9.1-901-1.jdbc4</version>
</dependency>
</dependencies>

2.2 数据库连接池

import java.sql.Connection

import com.jolbox.bonecp.{BoneCP, BoneCPConfig}
import org.slf4j.LoggerFactory /**
* 数据库连接池,使用了BoneCP
*/
object ConnectionPool { val logger = LoggerFactory.getLogger(this.getClass) //连接池配置
private val connectionPool: Option[BoneCP] = {
try{
Class.forName("org.postgresql.Driver")
val config = new BoneCPConfig()
config.setJdbcUrl("jdbc:postgresql://192.168.1.213/yourdb")
config.setUsername("postgres")
config.setPassword("******")
config.setLazyInit(true) config.setMinConnectionsPerPartition(3)
config.setMaxConnectionsPerPartition(5)
config.setPartitionCount(5)
config.setCloseConnectionWatch(true)
config.setLogStatementsEnabled(false)
Some(new BoneCP(config))
}catch {
case exception: Exception =>
logger.warn("Create Connection Error: \n" + exception.printStackTrace())
None
}
} // 获取数据库连接
def getConnection: Option[Connection] = {
connectionPool match {
case Some(pool) => Some(pool.getConnection)
case None => None
}
} // 释放数据库连接
def closeConnection(connection:Connection): Unit = {
if(!connection.isClosed) {
connection.close()
}
}

2.3 Kafka -> Spark-Streaming -> JDBC

Spark Streaming从Kafka中读取数据,并把数据写入数据库。SPark Streaming编程的基本顺序是:

  1. 创建Spark Streaming上下文
  2. 从数据源接口创建DStream
  3. 在DStream上做转换(Transformations)
  4. 指定计算结果存储的位置
  5. 启动计算

代码如下,详见注释。

import java.sql.Connection

import org.apache.kafka.common.serialization.StringDeserializer
import org.apache.spark.SparkConf
import org.apache.spark.streaming.kafka010.ConsumerStrategies._
import org.apache.spark.streaming.kafka010.KafkaUtils
import org.apache.spark.streaming.kafka010.LocationStrategies._
import org.apache.spark.streaming.{Seconds, StreamingContext}
import org.slf4j.LoggerFactory /**
* 从Kafka中读取数据,并把数据写入数据库。
*/
object KafkaToDB { val logger = LoggerFactory.getLogger(this.getClass) def main(args: Array[String]): Unit = {
// 参数校验
if (args.length < 2) {
System.err.println(
s"""
|Usage: KafkaToDB <brokers> <topics>
| <brokers> broker列表,至少1个,以英文逗号分割
| <topics> topic列表,至少一个,以英文逗号分割
|""".stripMargin)
System.exit(1)
} // 处理参数
val Array(brokers, topics) = args
val topicSet: Set[String] = topics.split(",").toSet
val kafkaParams: Map[String, Object] = Map[String, Object](
"bootstrap.servers" -> brokers,
"key.deserializer" -> classOf[StringDeserializer],
"value.deserializer" -> classOf[StringDeserializer],
"group.id" -> "example",
"auto.offset.reset" -> "latest",
"enable.auto.commit" -> (false: java.lang.Boolean)
) // 1.创建上下文,以每1秒间隔的数据作为一个批次
val sparkConf = new SparkConf().setAppName("KafkaToDB")
val streamingContext = new StreamingContext(sparkConf, Seconds(1)) // 2.创建输入流,获取数据。流操作基于DStream,InputDStream继承于DStream
val stream = KafkaUtils.createDirectStream[String, String](
streamingContext,
PreferConsistent,
Subscribe[String, String](topicSet, kafkaParams)
) // 3. DStream上的转换操作
// 取消息中的value数据,以英文逗号分割,并转成Tuple3
val values = stream.map(_.value.split(","))
.filter(x => x.length == 3)
.map(x => new Tuple3[String, String, String](x(0), x(1), x(2))) // 输入前10条到控制台,方便调试
values.print() // 4.同foreachRDD保存到数据库
val sql = "insert into kafka_message(timeseq, thread, message) values (?,?,?)"
values.foreachRDD(rdd => {
val count = rdd.count()
println("-----------------count:" + count)
if (count > 0) {
rdd.foreachPartition(partitionOfRecords => {
val conn = ConnectionPool.getConnection.orNull
if (conn != null) {
partitionOfRecords.foreach(data => insert(conn, sql, data))
ConnectionPool.closeConnection(conn)
}
})
}
}) // 5. 启动计算
streamingContext.start()
streamingContext.awaitTermination() // 等待中断结束计算
} /**
* 保存数据到数据库
*
* @param conn 数据库连接
* @param sql prepared statement sql
* @param data 要保存的数据,Tuple3结构
*/
def insert(conn: Connection, sql: String, data: (String, String, String)): Unit = {
try {
val ps = conn.prepareStatement(sql)
ps.setString(1, data._1)
ps.setString(2, data._2)
ps.setString(3, data._3)
ps.executeUpdate()
ps.close()
} catch {
case e: Exception =>
logger.error("Error in execution of insert. " + e.getMessage)
}
} }

3. 任务运行

3.1 数据库驱动配置

由于本次程序运行环境是spark standalone 的伪分布式,指定SPARK_CLASSPATH时,相当于同时指定了driver和executor的classpath。

编辑spark-env.sh。

vi $SPARK_HOME/conf/spark-env.sh

输入以下内容,注意把postgresql驱动包放在对应的位置。

export SPARK_CLASSPATH=$SPARK_CLASSPATH:/opt/jobs/postgresql-9.1-901-1.jdbc4.jar

实际上,Spark已经不推荐使用SPARK_CLASSPATH参数了,启动的时候,我们会发现如下的日志,提示我们用spark.executor.extraClassPathspark.driver.extraClassPath来代替。如果是spark local模式,只需指定spark.driver.extraClassPath即可。

日志如下。

16/10/21 15:15:33 WARN SparkConf:
SPARK_CLASSPATH was detected (set to ':/opt/jobs/postgresql-9.1-901-1.jdbc4.jar').
This is deprecated in Spark 1.0+. Please instead use:
- ./spark-submit with --driver-class-path to augment the driver classpath
- spark.executor.extraClassPath to augment the executor classpath 16/10/21 15:15:33 WARN SparkConf: Setting 'spark.executor.extraClassPath' to ':/opt/jobs/
postgresql-9.1-901-1.jdbc4.jar' as a work-around.
16/10/21 15:15:33 WARN SparkConf: Setting 'spark.driver.extraClassPath' to ':/opt/jobs/
postgresql-9.1-901-1.jdbc4.jar' as a work-around.

3.2 启动任务

$SPARK_HOME/bin/spark-submit \
--master spark://192.168.1.91:7077
--class com.xxx.streaming.KafkaToDB \
spark-streaming-demo.jar 192.168.1.90:9092 my-topic

在192.168.1.90上,通过kafka命令发送消息,命令如下:

bin/kafka-console-producer.sh --broker-list 192.168.1.90:9092 --topic mytopic

消息如下:

1475589918658    thread-15    msg-0
1475589920177 thread-15 msg-1

之后,我们可以在控制台看到消息被输出,也可以在数据库中查询到这些数据。

4.Spark程序找不到JDBC驱动的问题

控制台曾经报出找不到JDBC驱动的异常,日志如下。

java.sql.SQLException: No suitable driver found for jdbc:……

该问题可以用前文所述的数据库驱动配置办法解决。在这里推荐一篇博客,对这个问题给出的很好的解释。
http://www.codexiu.cn/spark/blog/12672/

博客来源:https://www.jianshu.com/p/a73c0c95d2fe

Spark Streaming通过JDBC操作数据库的更多相关文章

  1. Spark Streaming中的操作函数讲解

    Spark Streaming中的操作函数讲解 根据根据Spark官方文档中的描述,在Spark Streaming应用中,一个DStream对象可以调用多种操作,主要分为以下几类 Transform ...

  2. 用于JDBC操作数据库的公共类

    /* * @(#)CommonSql.java 2011-9-5 * * Copyright 2011 Bianjing,All rights reserved. */ import java.sql ...

  3. JDBC操作数据库的学习(2)

    在上一篇博客<JDBC操作数据库的学习(1)>中通过对例1,我们已经学习了一个Java应用如何在程序中通过JDBC操作数据库的步骤流程,当然我们也说过这样的例子是无法在实际开发中使用的,本 ...

  4. JDBC操作数据库的学习(1)

    单单对数据库的操作,比如说MySQL,我们可以在命令行窗口中执行,但是一般是应用程序要操作数据库,因此我们应该在程序中的代码上体现对数据库的操作,那么使用程序应用如何操作数据库呢?那就要使用到数据库的 ...

  5. JDBC操作数据库的三种方式比较

    JDBC(java Database Connectivity)java数据库连接,是一种用于执行上sql语句的javaAPI,可以为多种关系型数据库提供统一访问接口.我们项目中经常用到的MySQL. ...

  6. JDBC操作数据库的基本步骤:

    JDBC操作数据库的基本步骤: 1)加载(注册)数据库驱动(到JVM). 2)建立(获取)数据库连接. 3)创建(获取)数据库操作对象. 4)定义操作的SQL语句. 5)执行数据库操作. 6)获取并操 ...

  7. springboot学习-jdbc操作数据库--yml注意事项--controller接受参数以及参数校验--异常统一管理以及aop的使用---整合mybatis---swagger2构建api文档---jpa访问数据库及page进行分页---整合redis---定时任务

    springboot学习-jdbc操作数据库--yml注意事项--controller接受参数以及参数校验-- 异常统一管理以及aop的使用---整合mybatis---swagger2构建api文档 ...

  8. Spring入门(十五):使用Spring JDBC操作数据库

    在本系列的之前博客中,我们从没有讲解过操作数据库的方法,但是在实际的工作中,几乎所有的系统都离不开数据的持久化,所以掌握操作数据库的使用方法就非常重要. 在Spring中,操作数据库有很多种方法,我们 ...

  9. Java笔记(第七篇 JDBC操作数据库)

    JDBC是连接数据库和java程序的桥梁,通过JDBC API可以方便地实现对各种主流数据库的操作.学习java语言,必须学习JDBC技术,因为JDBC技术实在java语言中被广泛使用的一种操作数据库 ...

随机推荐

  1. ArrayList的底层实现

    package zy809; public class myArrayList { /** 存放元素 */ private Object[] data;// 创建一个数组引用. /** 元素的个数 * ...

  2. python中requests的用法总结

    requests是一个很实用的Python HTTP客户端库,编写爬虫和测试服务器响应数据时经常会用到.可以说,Requests 完全满足如今网络的需求 本文全部来源于官方文档 http://docs ...

  3. ECLIPSE最常用快捷键排名

    快捷键应该是节省时间的一个要素 ,所以自己在网上找了下面这些快捷键, 以便让更多的程序员更快捷的使用Eclipse. ECLIPSE最常用快捷键排名 1. Alt + / 自动完成 2. Ctrl + ...

  4. shell编程 之 ssh远程连接

    1,ssh理解 有两个服务器,一个是本地,一个是云端的,都是linux系统的,如果我们想要通过本地访问云端的系统,那我们可以用ssh命令,可以实现本地登入远程连接,上传或者下载文件到远程服务器. ss ...

  5. 20165237 2017-2018-2 《Java程序设计》第7周学习总结

    20165237 2017-2018-2 <Java程序设计>第7周学习总结 教材学习内容总结 1.MySQL数据库管理系统,简称MySQL,是世界上最流行的开源数据库管理系统. 2.启动 ...

  6. Linux 磁盘分区,文件系统创建、挂载、开机自动挂载和卸载

    创建分区 (fdisk): 第一步先在Linux的虚拟机上添加一块硬盘,添加完成后需要重启虚拟机才能够检测识别到新硬盘. 重启系统后可以使用 fdisk -l 命令查看当前所有磁盘分区情况,sdb为我 ...

  7. SQLServer语法常用总结

    1. 有时候查看SQL的时候表名或者字段名需要加[],这是因为有时候你的表名或者字段名正好与sqlserver的保留字段重了 比如:有一个user表,直接select会报错 select * from ...

  8. python 携带cookie获取页面内容

    有时会遇到爬取的页面需要登录,这就要带上cookie了. 下面记录了几种携带cookie的方法 # coding=utf-8 import requests s = requests.Session( ...

  9. jquery简单使用入门

    <!DOCTYPE html> <html> <head> <title>jquery</title> <meta charset=& ...

  10. mysql5.7 闪回数据(update delete insert)

    本次测试用Myflash闪回dml操作,有个前提条件是log_bin开启并且log模式是row: mysql> show global variables like "binlog%& ...