背景
项目中已提供海量日志数据的多维实时查询, 客户提出新需求: 将数据导出.
将数据导出分两步:
查询大量数据
将数据生成文件并下载
本文主要探讨第一步, 在 es 中查询大量数据或者说查询大数据集.
es 支持的查询数量
es 默认支持的查询数量或者说查询深度是 10,000.
可以动态修改 max_result_window 这个参数的设置, 默认为 10,000.
- PUT xz - logs / _settings ? preserve_existing = true {
- "index.max_result_window": "10000000"
- }
- es search api from + size GET / _search {
- "from": 0,
- "size": 10,
- "query": {
- "term": {
- "user": "kimchy"
- }
- }
- }
当 Elasticsearch 响应请求时, 它必须确定 docs 的顺序, 全局排序响应结果.
如果请求的页数较少时, 假设每页 10 个 docs-- 即 pageSize=10, 此时 Elasticsearch 不会有什么问题.
但若取的页数较大时 (深分页), 如请求第 20 页, Elasticsearch 不得不取出所有分片上的第 1 页到第 20 页的所有 docs, 假设你有 16 个分片, 则需要在 coordinate node 汇总到 shards* (from+size) 条记录, 即需要 16*(20+10)记录后做一次全局排序, 再最终取出 from 后的 size 条结果作为最终的响应.
所以: 当索引非常非常大(千万或亿), 是无法安装 from + size 做深分页的, 分页越深则越容易 OOM, 即便不 OOM, 也是很消耗 CPU 和内存资源的.
https://www.elastic.co/guide/cn/elasticsearch/guide/current/scroll.html
scroll 类似于数据库中的游标.
游标查询允许我们 先做查询初始化, 然后再批量地拉取结果. 这有点儿像传统数据库中的 cursor .
游标查询会取某个时间点的快照数据. 查询初始化之后索引上的任何变化会被它忽略. 它通过保存旧的数据文件来实现这个特性, 结果就像保留初始化时的索引 视图 一样.
深度分页的代价根源是结果集全局排序, 如果去掉全局排序的特性的话查询结果的成本就会很低. 游标查询用字段 _doc 来排序. 这个指令让 Elasticsearch 仅仅从还有结果的分片返回下一批结果.
第一次查询
- GET /old_index/_search?scroll=1m
- {
- "query": { "match_all": {}},
- "sort" : ["_doc"],
- "size": 1000
- }
第二次查询
- GET /_search/scroll
- {
- "scroll": "1m",
- "scroll_id" : "cXVlcnlUaGVuRmV0Y2g7NTsxMDk5NDpkUmpiR2FjOFNhNnlCM1ZDMWpWYnRROzEwOTk1OmRSamJHYWM4U2E2eUIzVkMxalZidFE7MTA5OTM6ZFJqYkdhYzhTYTZ5QjNWQzFqVmJ0UTsxMTE5MDpBVUtwN2lxc1FLZV8yRGVjWlI2QUVBOzEwOTk2OmRSamJHYWM4U2E2eUIzVkMxalZidFE7MDs="
- }
- Scanning Scroll API
如果只对查询结果感兴趣而不关心结果的顺序, 可以使用更高效的 scanning scroll. 使用方法非常简单, 只需在查询语句后加上 "search_type=scan" 即可.
- search after(5.0 新特性) https://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-search-after.html
- search_after is not a solution to jump freely to a random page but rather to scroll many queries in parallel. It is very similar to the scroll API but unlike it, the search_after parameter is stateless, it is always resolved against the latest version of the searcher. For this reason the sort order may change during a walk depending on the updates and deletes of your index.
search_after 类似于 scroll, 不同之处是: search_after 是无状态的, 它总是针对最新版本的搜索器进行解析. 由于更新或者删除索引, 搜索的排序结果可能会发生变化.
bulk
bulk 是将多个请求合并成一个请求, 如下所示:
- POST _bulk
- { "index" : { "_index" : "test", "_type" : "_doc", "_id" : "1" } }
- { "field1" : "value1" }
- { "delete" : { "_index" : "test", "_type" : "_doc", "_id" : "2" } }
- { "create" : { "_index" : "test", "_type" : "_doc", "_id" : "3" } }
- { "field1" : "value3" }
- { "update" : {"_id" : "1", "_type" : "_doc", "_index" : "test"} }
- { "doc" : {"field2" : "value2"} }
其他
插件: elasticsearch-dataformat
实际使用过程中, 该插件不好用. 如果带查询条件, 数据无法导出. 查看其依赖的 jar 包, 估计其调用 poi 来生成 csv 文件, 估计速度快不了.
这种插件做 demo 可以, 实际生成中, 不太敢使用, 因为不可控因素太多.
总结
综上所述, 最后采用 scroll api 来解决 es 查询大量数据的问题. 不过数据量大一点, 查询时间就比较长, 在本人的集群中, 查询 10w 条, 需要将近 1 分钟的时间.(附本人集群: 3 个节点. 每个节点配置为 cpu 8 核, heap size 16G, 每个索引有 5 个分片, 1 个副本. 数据量每天 4500w)
来源: http://www.bubuko.com/infodetail-2550815.html