一、kafka 模擬數據:
【1】模擬數據實體類:
public class CarDataTest {
private String lat;
private String lon;
private String location;
private String status;
private String terminaltype;
-------有很多字段 此處省略不寫了 下面 get set 也省略不寫了------
public String getRecordvelocity() {
return recordvelocity;
}
public void setRecordvelocity(String recordvelocity) {
this.recordvelocity = recordvelocity;
}
public String getDepid() {
return depid;
}
public void setDepid(String depid) {
this.depid = depid;
}
}
【2】kafka 生成模擬數據
public class kafkaTest {
private static String carrDatas(){
CarDataTest carDataTest = new CarDataTest();
String randomNum_10 = String.valueOf(new Random().nextInt(9));
String randomNum_100 = String.valueOf(new Random().nextInt(100));
String randomNum_1 = String.valueOf(new Random().nextInt(2));
long end = System.currentTimeMillis();
Date date = new Date(end);
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String dateStr = df.format(date);
carDataTest.setLat("23.111111");
carDataTest.setLon("113.112233");
carDataTest.setLocation("23.111111,113.22222");
--------------省略部分-----------
carDataTest.setSvrtime(dateStr);
carDataTest.setTerminaltype(randomNum_1);
JSONObject object = JSONObject.fromObject(carDataTest);
String json = object.toString();
System.out.println(json);
return json;
}
public static void main (String args[]) throws Exception{
/**
kafka 序列化 相關類指定
*/
String serializerClassName = "org.apache.kafka.common.serialization.StringSerializer";
/**
kafka 的 bootsServers 沒啥好說的
*/
String kafkaBootsrapServers ="machen1:9092,machen2:9092,machen3:9092";
/**
構建 kafka 相關參數
*/
Properties properties = new Properties();
properties.put("bootstrap.servers",kafkaBootsrapServers);
properties.put("acks","all");
properties.put("retries",0);
properties.put("batch.size",16384);
properties.put("linger.ms",1);
properties.put("buffer.memory",33554432);
properties.put("key.serializer" ,serializerClassName);
properties.put("value.serializer",serializerClassName);
KafkaProducer<String,String> kafkaProducer = new KafkaProducer<String, String>(properties);
/**
指定topic
*/
String topic = "police_data_test";
boolean flag = true;
while( flag){
for(int i=0;i<5;i++){
kafkaProducer.send(
new ProducerRecord(
topic,
carrDatas()
));
}
kafkaProducer.flush();
System.out.println(" machen kafka 生成數據完畢");
Thread.sleep(10000);
}
kafkaProducer.close();
}
}
二、spark 讀取【oracle 和 kafka】數據 並插入 ES
【1】ES 索引創建
首先解釋一下 type 的類型,date ,keyword,folat 沒什麼好說的
geo_point 是 Geopoint 的類型 是ES 用於空間查詢的一種類型
一定要注意、一定要注意、一定要注意、
geopoint 的 格式 爲 “lat,lon” 分別代表 維度和經度 比如說 “23.33445,113.223214” 第一個數字的小數點前面是 兩位數,第二個數字的小數點前面是3位數 ,否則是插不進去ES的 ,而且會一直報 非常詭異的錯誤 :
(我之前一直插不進去就是不小心 寫成 “113.22222,23.22222” 這種錯誤形式了)
反正日誌中你看不到任何關於 geopoint 的格式錯誤的提示
他的值是 有 "lat" 和 "lon" 組合而成,也就是經緯度的表達
PUT test_police_car
{
"mapping":{
"doc":{
"properties":{
"lat":{
"type":"float"
},
"lon":{
"type":"float"
},
"location":{
"type":"geo_point"
},
"rectime":{
"type":"date"
},
"address":{
"type":"keyword"
},
"name":{
"type":"machen"
}
}
}
}
}
【2】索引表結構展示:
關閉 表限制:
【3】插入數據
object FromOrcaleToEs {
/**
* spark 的配置 包括鏈接ES 的一些參數設定
* 主要注意點: 設置序列化的類 spark.serializer
*/
val sparkConf = new SparkConf().setAppName("machen_work_test").setMaster("local[2]")
sparkConf.set("spark.serializer", "org.apache.spark.serializer.KryoSerializer")
sparkConf.set("cluster.name","es")
// 原創鏈接---
sparkConf.set("es.index.auto.create","true")
sparkConf.set("es.nodes","machen1,machen2,machen3")
sparkConf.set("es.port","9200")
sparkConf.set("es.index.read.missing.as.empty","true")
sparkConf.set("es.nodes.wan.only","true")
/**
* Oracle 參數設定
*/
val property = new Properties()
property.put("user","XXXX")
property.put("password", "yyyy")
property.put("driver", "oracle.jdbc.OracleDriver")
val oracle_url = "jdbc:oracle:thin:@172.0.0.0:1521/datat_machen"
val schema = StructType(Seq(
StructField("lat",StringType,true),
StructField("lon",StringType,true),
StructField("location",StringType,true),
-------- 省略部分 ---------
StructField("recordvelocity",StringType,true),
StructField("depid",StringType,true)
))
//kafka
def getKafkaData(topic: String, broker: String, scc: StreamingContext) = {
val topicMap: Set[String] = List(topic).toSet
val kafkaParam: Map[String, Object] = getKafkaParam(broker)
val data = KafkaUtils.createDirectStream[String, String](scc,
PreferConsistent,
Subscribe[String, String](topicMap, kafkaParam)
)
data
}
def getKafkaParam(broker: String) = {
val kafkaParam = Map[String, Object](
"bootstrap.servers" -> broker,
"group.id" -> "console-consumer-oracl-001",
"key.deserializer" -> classOf[StringDeserializer],
"value.deserializer" -> classOf[StringDeserializer],
"enable.auto.commit" -> (false: java.lang.Boolean),
"auto.offset.reset" -> "latest",// latest , earliest
// "security.protocol" -> "SASL_PLAINTEXT",
// "sasl.mechanism" -> "PLAIN",
"max.poll.records"->"5"
)
kafkaParam
}
/**
* ES 中的時間格式 都知道 是時區時間 也就是說表面上要比真實時間少 8個小時
* 因此要把 準備插入的時間數據(String 類型)(比如說 2019-10-11 18:09:09)先變成ES 時間再插入
* 即 減去8小時 ,再轉換成時區格式 (2019-10-11T10:09:09.000Z)。
* 注意點:這是對於數據是 String 類型的,如果時間是Date 類型 直接插入(比如果用ES 自己的API
* 或是kibana 方式的話 是沒有必要轉化的 直接插入就行)
*/
def dateForES (initdate:String) ={
val initdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
val time = initdf.parse(initdate).getTime-8*60*60*1000
val estimedf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
val newtime = estimedf.format(new Date(time))
newtime
}
def main(args: Array[String]): Unit = {
val conf = sparkConf
val sc = new SparkContext(conf)
val spark = SparkSession
.builder
.master("local[2]")
.appName("From_oracle")
.config(sparkConf)
.getOrCreate()
val ssc = new StreamingContext(sc,Seconds(10))
//oracle 數據 轉成 DF
val jdbcdata = spark.read.jdbc(oracle_url,"my_oracle.test_data",property)
jdbcdata.registerTempTable("orc_data")
val oracle_data:DataFrame = spark.sql("select simno,plateno from orc_data limit 2")
println("測試 oracle 數據:"+oracle_data.show())
//kafka 數據 轉成 DF
val topic = "police_data_test"
val broker = "machen1:9092,machen2:9092,machen3:9092"
val lines = getKafkaData(topic,broker,ssc)
lines.foreachRDD(rdd => {
val offsetRanges = rdd.asInstanceOf[HasOffsetRanges].offsetRanges
lines.asInstanceOf[CanCommitOffsets].commitAsync(offsetRanges)
})
lines.foreachRDD{ rdd =>
val data:RDD[Row] = rdd.map { x =>
val buffer = ArrayBuffer.empty[Any]
val jsons = JSONObject.fromObject(x.value())
println(jsons)
buffer.append(jsons.getString("lat"))
buffer.append(jsons.getString("lon"))
buffer.append(jsons.getString("location"))
--------省略部分---------------
buffer.append(dateForES(jsons.getString("rectime")))
buffer.append(jsons.getString("depid"))
println("轉換完畢---------")
Row.fromSeq(buffer)
}
val df :DataFrame = spark.createDataFrame(data,schema)
df.registerTempTable("kafka_data")
val joindata = spark.sql("select k.*,o.plateno from kafka_data as k join orc_data as o on k.simid=o.simid ")
val jsonArray = new JSONArray()
val rddData = joindata.toJSON.rdd
println("準備插入--------------------------------------------------")
EsSpark.saveJsonToEs(rddData,"test_police_car/doc")
println("插入成功--------------------------------------------------")
joindata.show()
}
/** 第二種方式:
* 上面是先行 forechRDD 然後再map 操作 ,在map操作時候,已經是rdd 的類型了
* 所以只能用 EsSpark.saveJsonToEs
* 如果 先用 map 那麼返回的還是 Dstream[] 再用下面即可 x.value() 是json 格式
* val json = lines.map(x=>{
* val jsons = JSONObject.fromObject(x.value())
* jsons
* })
* EsSparkStreaming.saveJsonToEs(json,"index")
*/
ssc.start()
ssc.awaitTermination()
ssc.stop()
}
}
【4】運行 相關的日誌輸出:
kafka 數據
測試類日誌:【相關真實數據不方便暴漏,請理解】
join 之後的 df 【太長了,只展示後面字段】
ES 數據再次查詢:
能看到join 之後,並且插入的數據 (數據不方便暴漏 請理解)