本文將詳細介紹利用 ES-Hadoop 將 Spark 處理的資料寫入到 ES 中。
一、開發環境
1、元件版本
- CDH 叢集版本:6.0.1
- Spark 版本:2.2.0
- Kafka 版本:1.0.1
- ES 版本:6.5.1
2、Maven 依賴
<!-- scala -->
<dependency>
<groupId>org.scala-lang</groupId>
<artifactId>scala-library</artifactId>
<version>2.11.8</version>
</dependency>
<!-- spark 基礎依賴 -->
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-core_2.11</artifactId>
<version>2.2.0</version>
</dependency>
<!-- spark-streaming 相關依賴 -->
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-streaming_2.11</artifactId>
<version>2.2.0</version>
</dependency>
<!-- spark-streaming-kafka 相關依賴 -->
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-streaming-kafka-0-10_2.11</artifactId>
<version>2.2.0</version>
</dependency>
<!-- zookeeper 相關依賴 -->
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.5-cdh6.0.1</version>
</dependency>
<!-- Spark-ES 相關依賴 -->
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch-spark-20_2.11</artifactId>
<version>6.5.4</version>
</dependency>
<!-- Spark-ES 依賴的 HTTP 傳輸元件 -->
<dependency>
<groupId>commons-httpclient</groupId>
<artifactId>commons-httpclient</artifactId>
<version>3.1</version>
</dependency>
複製程式碼
3、注意事項
如果使用 CDH 版本的 Spark,則在除錯及實際部署執行的時候會出現下面的錯誤:
java.lang.ClassNotFoundException: org.apache.commons.httpclient.protocol.Protocol
複製程式碼
很顯然是缺少 httpclient 相關依賴造成的,對比開源版本與 CDH 版本的 Spark,發現開源版本多出了 commons-httpclient-3.1.jar
,因此上述 Maven 的 pom 檔案新增上對其依賴即可。
二、ES-Hadoop
1、簡介
ES-Hadoop 實現了 Hadoop 生態(Hive、Spark、Pig、Storm 等)與 ElasticSearch 之間的資料互動,藉助該元件可以將 Hadoop 生態的資料寫入到 ES 中,然後藉助 ES 對資料快速進行搜尋、過濾、聚合等分析,進一步可以通過 Kibana 來實現資料的視覺化。
同時,也可以藉助 ES 作為資料儲存層(類似數倉的 Stage 層或者 ODS 層),然後藉助 Hadoop 生態的資料處理工具(Hive、MR、Spark 等)將處理後的資料寫入到 HDFS 中。
使用 ES 做為原始資料的儲存層,可以很好的進行資料去重、資料質量分析,還可以提供一些即時的資料服務,例如趨勢展示、彙總分析等。
2、組成
ES-Hadoop 是一個整合性質的元件,它封裝了 Hadoop 生態的多種元件與 ES 互動的 API,如果你只需要部分功能,可以使用細分的元件:
- elasticsearch-hadoop-mr
- elasticsearch-hadoop-hive
- elasticsearch-hadoop-pig
- elasticsearch-spark-20_2.10
- elasticsearch-hadoop-cascading
- elasticsearch-storm
三、elasticsearch-spark
1、配置
es-hadoop 核心是通過 es 提供的 restful 介面來進行資料互動,下面是幾個重要配置項,更多配置資訊請參閱官方說明:
es.nodes
:需要連線的 es 節點(不需要配置全部節點,預設會自動發現其他可用節點);es.port
:節點 http 通訊埠;es.nodes.discovery
:預設為 true,表示自動發現叢集可用節點;es.nodes.wan.only
:預設為 false,設定為 true 之後,會關閉節點的自動 discovery,只使用es.nodes
宣告的節點進行資料讀寫操作;如果你需要通過域名進行資料訪問,則設定該選項為 true,否則請務必設定為 false;es.index.auto.create
:是否自動建立不存在的索引,預設為 true;es.net.http.auth.user
:Basic 認證的使用者名稱;es.net.http.auth.pass
:Basic 認證的密碼。
val conf = new SparkConf().setIfMissing("spark.app.name","rt-data-loader").setIfMissing("spark.master", "local[5]")
conf.set(ConfigurationOptions.ES_NODES, esNodes)
conf.set(ConfigurationOptions.ES_PORT, esPort)
conf.set(ConfigurationOptions.ES_NODES_WAN_ONLY, "true")
conf.set(ConfigurationOptions.ES_INDEX_AUTO_CREATE, "true")
conf.set(ConfigurationOptions.ES_NODES_DISCOVERY, "false")
conf.set(ConfigurationOptions.ES_NET_HTTP_AUTH_USER, esUser)
conf.set(ConfigurationOptions.ES_NET_HTTP_AUTH_PASS, esPwd)
conf.set("es.write.rest.error.handlers", "ignoreConflict")
conf.set("es.write.rest.error.handler.ignoreConflict", "com.jointsky.bigdata.handler.IgnoreConflictsHandler")
複製程式碼
特別需要注意的配置項為 es.nodes.wan.only
,由於在雲伺服器環境中,配置檔案使用的一般為內網地址,而本地除錯的時候一般使用外網地址,這樣將 es.nodes
配置為外網地址後,最後會出現節點找不到的問題(由於會使用節點配置的內網地址去進行連線):
org.elasticsearch.hadoop.EsHadoopIllegalArgumentException: No data nodes with HTTP-enabled available;
node discovery is disabled and none of nodes specified fit the criterion [xxx.xx.x.xx:9200]
複製程式碼
此時將 es.nodes.wan.only
設定為 true 即可。推薦開發測試時使用域名,叢集部署的時候將該選項置為 false。
2、遮蔽寫入衝突
如果資料存在重複,寫入 ES 時往往會出現資料寫入衝突的錯誤,此時有兩種解決方法。
方法一:設定 es.write.operation
為 upsert,這樣達到的效果為如果存在則更新,不存在則進行插入,該配置項預設值為 index。
方法二:自定義衝突處理類,類似上述配置中設定了自定義的 error.handlers
,通過自定義類來處理相關錯誤,例如忽略衝突等:
public class IgnoreConflictsHandler extends BulkWriteErrorHandler {
public HandlerResult onError(BulkWriteFailure entry, DelayableErrorCollector<byte[]> collector) throws Exception {
if (entry.getResponseCode() == 409) {
StaticLog.warn("Encountered conflict response. Ignoring old data.");
return HandlerResult.HANDLED;
}
return collector.pass("Not a conflict response code.");
}
}
複製程式碼
方法二可以遮蔽寫入版本比預期的小之類的版本衝突問題。
3、RDD 寫入 ES
EsSpark 提供了兩種主要方法來實現資料寫入:
saveToEs
:RDD 內容為Seq[Map]
,即一個 Map 物件集合,每個 Map 對應一個文件;saveJsonToEs
:RDD 內容為Seq[String]
,即一個 String 集合,每個 String 是一個 JSON 字串,代表一條記錄(對應 ES 的 _source)。
資料寫入可以指定很多配置資訊,例如:
es.resource
:設定寫入的索引和型別,索引和型別名均支援動態變數;es.mapping.id
:設定文件 _id 對應的欄位名;es.mapping.exclude
:設定寫入時忽略的欄位,支援萬用字元。
val itemRdd = rdd.flatMap(line => {
val topic = line.topic()
println("正在處理:" + topic + " - " + line.partition() + " : " + line.offset())
val jsonArray = JSON.parseArray(line.value()).toJavaList(classOf[JSONObject]).asScala
val resultMap = jsonArray.map(jsonObj =>{
var tmpId = "xxx"
var tmpIndex = "xxxxxx"
jsonObj.put("myTmpId", tmpId)
jsonObj.put("myTmpIndex", tmpIndex)
jsonObj.getInnerMap
})
resultMap
})
val mapConf = Map(
("es.resource" , "{myTmpIndex}/doc"),
("es.write.operation" , "upsert"),
("es.mapping.id" , "myTmpId"),
("es.mapping.exclude" , "myTmp*")
)
EsSpark.saveToEs(itemRdd, mapConf)
複製程式碼
es.mapping.exclude
只支援 RDD 為 Map 集合(saveToEs),當為 Json 字串集合時(saveJsonToEs)會提示不支援的錯誤資訊;這個配置項非常有用,例如 myTmpId 作為文件 id,因此沒有必要重複儲存到 _source 裡面了,可以配置到這個配置項,將其從 _source 中排除。
Any Code,Code Any!
掃碼關注『AnyCode』,程式設計路上,一起前行。