1.这个记录的实例就是说,接了上一篇的代码修改,加了广播变量的东西。
我在mysql存的某个规则,
我启动sparkStreaming程序的时候使用broadcast广播出去,注意这个就仅执行一次的
然后吧在redis有这么一个kv作为标志,比如说flag=true,每次sparkStreaming程序程序处理数据用到规则之前,都要先到redis看一下这个标志位变没变。
如果有其他程序或者人为修改了mysql数据中的规则,它会一同修改redis的标志位(这个不是本程序做的事)。
如果变了,则重新读取mysql规则;广播变量删除;重新广播;标志复位;
就这么事。
就是 广播变量的变化与redis的结合
package Kafka010
import Kafka010.Utils.{MyKafkaUtils, RedisUtilsDemo}
import cn.bigdata.antispider.common.util.jedis.JedisConnectionUtil
import database.QueryDB
import org.apache.kafka.clients.consumer.ConsumerRecord
import org.apache.kafka.common.TopicPartition
import org.apache.spark.SparkConf
import org.apache.spark.streaming.dstream.InputDStream
import org.apache.spark.streaming.kafka010._
import org.apache.spark.streaming.{Seconds, StreamingContext}
import scala.collection.mutable.ArrayBuffer
/**
* Created by Shi shuai RollerQing on 2019/12/24 19:47
*
* kakfa的API 0-10版本
*
* 广播变量的变化重置 与redis的结合
*/
object Kafka010Demo04 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setMaster("local[*]").setAppName(s"${this.getClass.getCanonicalName}")
val ssc = new StreamingContext(conf, Seconds(5))
// ssc.addStreamingListener()
val groupID = "SparkKafka010"
val topics = List("topicB")
val kafkaParams: Map[String, String] = MyKafkaUtils.getKafkaConsumerParams(groupID, "false")
//从外部存储中获取offset
val offsets: Map[TopicPartition, Long] = RedisUtilsDemo.getOffsetFromRedis("topicB", groupID)
// 获取规则;仅执行一次
val sql = "select value from nh_filter_rule"
val rules: ArrayBuffer[String] = QueryDB.queryData(sql, "value")
@volatile var bc = ssc.sparkContext.broadcast(rules)
val jedis = JedisConnectionUtil.getJedisCluster
val ds: InputDStream[ConsumerRecord[String, String]] = KafkaUtils.createDirectStream[String, String](
ssc,
LocationStrategies.PreferConsistent,
ConsumerStrategies.Subscribe[String, String](topics, kafkaParams, offsets)//要的就是这样的类型
)
ds.foreachRDD(rdd => {
val ranges: Array[OffsetRange] = rdd.asInstanceOf[HasOffsetRanges].offsetRanges
//监控redis中的标志
val flag = jedis.get("flag")
// 标志变化。 重新读数;广播变量删除;重新广播;标志复位;
if(flag != null && flag.toBoolean) {
val rules: ArrayBuffer[String] = QueryDB.queryData(sql, "value") //变了就重新查一遍 更新一下数据
bc.unpersist()
bc = ssc.sparkContext.broadcast(rules)
jedis.set("flag", "false")
}
//代表对数据进行处理 这里就是随便写了 真实的环境就是会用到广播的rules进行过滤等操作的
if(! rdd.isEmpty())
println(rdd.count())
//代表对offset进行处理
ranges.foreach(offset => {
println(s"${offset.partition}, ${offset.fromOffset}, ${offset.untilOffset}")
})
RedisUtilsDemo.saveOffsetToRedis(ranges, groupID)
})
ssc.start()
ssc.awaitTermination()
}
运行没有问题。
//unpersist用于删除磁盘、内存中的相关序列化对象。
//persist用于设置RDD的StorageLevel。
//不带参数的persist和cache函数,相当于persist(StorageLevel.MEMORY_ONLY)。
//getStorageLevel用于查询getStorageLevel信息。
//注意:RDD的StorageLevel只能设置一次。
//@volatile var bc = ssc.sparkContext.broadcast(rules)
//实际上这个注解或是关键字,大多用于被并发访问的共享变量
//对于volatile变量,同一变量的写操作总是先于读操作。
//volatile 关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改,
// 比如:操作系统、硬件或者其他线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。
package database
import java.sql.{Connection, PreparedStatement, ResultSet}
import scala.collection.mutable.ArrayBuffer
/**
* 工具类的作用:通过sql和value读取数据库的某一个字段
*
*/
object QueryDB {
def queryData(sql: String, field: String): ArrayBuffer[String] = {
//创建ab,用来封装数据
val arr = new ArrayBuffer[String]()
//获取连接
val conn: Connection = c3p0Util.getConnection
//执行sql语句
val ps = conn.prepareStatement(sql)
val rs: ResultSet = ps.executeQuery()
//封装数据
while (rs.next()){
arr .+= (rs.getString(field)) //获取数据库中的某个字段的值 以数组形式返回
}
c3p0Util.close(conn, ps, rs)
//返回结果
arr
}
}
package database;
import cn.bigdata.antispider.common.util.jedis.PropertiesUtil;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties;
public class c3p0Util {
public static ComboPooledDataSource cpds = null;
/**
* 创建c3p0连接池
*/
private static void createConnectionPool(){
Properties prop = PropertiesUtil.getProperties("c3p0.properties");
cpds = new ComboPooledDataSource(true);
try {
cpds.setJdbcUrl(prop.getProperty("jdbcUrl"));
cpds.setDriverClass(prop.getProperty("driverClass"));
cpds.setUser(prop.getProperty("username"));
cpds.setPassword(prop.getProperty("password"));
cpds.setMinPoolSize(new Integer(prop.getProperty("minPoolSize")));
cpds.setMaxPoolSize(new Integer(prop.getProperty("maxPoolSize")));
cpds.setInitialPoolSize(new Integer(prop.getProperty("initialPoolSize")));
cpds.setMaxIdleTime(new Integer(prop.getProperty("maxIdleTime")));
cpds.setAcquireIncrement(new Integer(prop.getProperty("acquireIncrement")));
} catch (Exception e) {
e.printStackTrace();
}
}
private static synchronized void connectionPoolInit(){
if(cpds == null){
createConnectionPool();
}
}
/**
* 获取连接池
* @return 返回一个连接
*/
public static Connection getConnection(){
try{
if(cpds == null){
connectionPoolInit();
}
Connection conn = cpds.getConnection();
return conn;
}catch (Exception e){
e.printStackTrace();
}
return null;
}
/**
* 关闭连接
* @param conn Connection
* @param pst PreparedStatement
* @param rs ResultSet
*/
public static void close(Connection conn,PreparedStatement pst,ResultSet rs){
if(rs!=null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(pst!=null){
try {
pst.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn!=null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
public static void close(Connection conn,PreparedStatement pst){
if(pst!=null){
try {
pst.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn!=null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
public static void close(Connection conn){
if(conn!=null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
c3p0.properties
#c3p0数据库连接池配置
#mysql驱动
driverClass=com.mysql.jdbc.Driver
#mysql数据库连接地址
jdbcUrl=jdbc:mysql://hadoop01:3306/gciantispider?useUnicode=true&characterEncoding=utf8
#用户名
username=root
#密码
password=root
#初始化的连接数,取值应在minPoolSize与maxPoolSize之间,Default: 3
initialPoolSize=10
#连接池中保留的最小连接
minPoolSize=10
#连接池中保留的最大连接
maxPoolSize=100
#当连接池中的连接耗尽的时候c3p0一次同时获取的连接数,Default:3
acquireIncrement=3
#最大空闲时间
maxIdleTime=1000
#定义在从数据库获取新连接失败后重复尝试的次数,Default: 30
acquireRetryAttempts=30
#两次连接中间隔时间,单位毫秒,Default: 1000
acquireRetryDelay=1000
其他配置文件与代码去上一篇找,都在后面了 Kafka0-10版本之03