学习记录:Scala通过ElasticSearch RestfulAPI使用scroll查询数据

在一次本地IDE调试运行数据处理任务时,需要全量查询数据再转换成DataFrame进行操作,但因为ElasticSearch默认设定一次查询的最大条数是10000,因此没法一次查询全量数据。查询了资料后发现可以使用scroll(滚动)查询来获取全部数据,类似与oracle的游标查询。

Scroll查询在kibana的DevTools中用法如下所示:

// 设定scroll=1m表示scroll查询的过期时间为1分钟
// 意思是在1分钟之内根据上次查询返回的scroll_id可以往下查询数据
GET /sqxzcf/_search?scroll=1m
{
  "query": {
    "match_all": {}
  },
  "size": 10000,
  "sort": [
    "_doc"
  ]
}

// 第二次及之后的查询只需要传入scroll和scroll_id参数即可,其他查询设定沿用第一次的查询条件
GET /_search/scroll
{
  "scroll" : "1m",
  "scroll_id" : "DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAQ1SFktGR1M1NlJjVHRpakQwSTVYRjlIREEAAAAAAAgm5RZBN09RZHlZWFJ5R3dhVlpYS2JSMktBAAAAAAAIT80WMWI1THcyT1BTeFNPN0ZwS3pYZy02UQAAAAAACCbmFkE3T1FkeVlYUnlHd2FWWlhLYlIyS0EAAAAAAAEg9RZUemc0dDNFZFF0dWVMaWNnSWxRcUV3"
}

具体scala代码如下(使用了JSON解析相关API,详细解释可以看上篇文章):

// 此部分代码为main方法的部分片段,导入JSON解析相关依赖包
import net.minidev.json.parser.JSONParser
import net.minidev.json.{JSONArray, JSONObject}
// 定义Object全局变量
var scrollId: String = ""
var hitSize: Int = 0
var totalDataFrame: DataFrame = null

var response: String = Utils.postBody(
      url = s"${esProps.getProperty("es.rest")}/bi_enterprise/_search?scroll=1m",
      body = "{\"query\": {\"match_all\": {}},\"size\": 10000,\"sort\": [\"_doc\"]}",
      encoding = "utf-8",
      printResponse = true)
// 使用json-smart解析返回的JSON字符串
val jsonParser = new JSONParser(-1)
val jsonObj: JSONObject = jsonParser.parse(response).asInstanceOf[JSONObject]
// 取得_scroll_id用于下次查询请求用
scrollId = jsonObj.get("_scroll_id").toString
// 解析查询内容并调用自定义方法转换成DataFrame
val firstDf: DataFrame = parseJSONObjectToDataFrame(jsonObj, jsonParser, spark)
totalDataFrame = firstDf
// 如果查询返回内容不为空则继续发送查询请求
while (hitSize != 0) {
    var responseNext: String = Utils.postBody(
            url = s"${esProps.getProperty("es.rest")}/_search/scroll",
            body = "{\"scroll\":\"1m\",\"scroll_id\":\"" + scrollId + "\"}",
            encoding = "utf-8",
            printResponse = true)
    val jsonObjNext: JSONObject = jsonParser.parse(responseNext).asInstanceOf[JSONObject]
    // 取得_scroll_id并更新scrollId变量用于下次查询请求用
    scrollId = jsonObj.get("_scroll_id").toString
    val nextDf: DataFrame = parseJSONObjectToDataFrame(jsonObjNext, jsonParser, spark)
    if (nextDf.count() > 0L) {
        // 合并多次查询返回内容
        totalDataFrame = totalDataFrame.union(nextDf)
    }
}

.......

// 将JSONObject解析转换成DataFrame
def parseJSONObjectToDataFrame(jsonObj: JSONObject, jsonParser: JSONParser, spark: SparkSession): DataFrame = {
    // 获取外层hits内容
    val hits = jsonObj.get("hits").toString
    val jsonObjHits: JSONObject = jsonParser.parse(hits).asInstanceOf[JSONObject]
    // 获取内层hits内容
    val hitsArray = jsonObjHits.get("hits").toString
    val jsonObjSource: JSONArray = jsonParser.parse(hitsArray).asInstanceOf[JSONArray]
    // 更新hitSize,hitSize用于判断是否需要继续发送scroll查询
    hitSize = jsonObjSource.size()
    var strList = List.empty[String]
    var array = jsonObjSource.toArray()
    // 遍历内层hits获取_source内容并拼接成List[String]
    for (i <- 0 to array.length - 1) {
      val Obj: JSONObject = array(i).asInstanceOf[JSONObject]
      strList = strList :+ Obj.get("_source").toString
    }
    val rddData: RDD[String] = spark.sparkContext.parallelize(strList)
    val resultDF = spark.read.json(rddData)
    resultDF
  }

......
// 发送Http请求
def postBody(url: String, body: String, encoding: String, printResponse: Boolean): String = {
    val httpClient = HttpClients.createDefault
    var re: String = ""
    try {
      val httpPost: HttpPost = new HttpPost(url)
      httpPost.setEntity(new StringEntity(body, encoding))
      val httpResponse: HttpResponse = httpClient.execute(httpPost)
      val entity: HttpEntity = httpResponse.getEntity
      if (entity != null) {
        re = EntityUtils.toString(entity, encoding)
        if (printResponse) Logger.debug(re)
      }
    } catch {
      case e: Exception =>
        e.printStackTrace(System.out)
    } finally try
      httpClient.close()
    catch {
      case e: IOException =>
        e.printStackTrace(System.out)
    }
    re
  }

 

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值