针对目前工作每日处理上亿数据的经历,作了几点总结


增删改查

  • 增用put(除了自动生成ID的创建文档)
  • 删用delete
  • 改用post
  • 查用get
  • URL只要是加下划线的,都用POST请求

分词查询

term是代表完全匹配,即不进行分词器分析,文档中必须包含整个搜索的词汇,那么设置索引mapping时,需要注意:是否需要对term查询的字段设置不进行分析,类如:

1
2
3
4
"name": {
"type": "string",
"index": "not_analyzed"
}

那么”name”: “Quick Foxes!”内容被存放到索引中将变成[quick, foxes],即进行了分词。那么使用term查询Quick Foxes将不能查到该结果。另外,5.6.8之后,分词not_analyzed改为用keyword代替

搜索格式

  • ip:port/index/type/_search,表示在该index的type中搜索;
  • ip:port/index/_search,表示在该index中搜索;
  • ip:port/_search,表示在全文搜索;

settings vs mappings

  • settings是修改分片和副本数的。
  • mappings是修改字段和类型的。

?pretty作用

URL后加上?pretty表示将返回的json进行格式化,这时解析注意\n

副本与分片

不存在索引时,可以指定副本和分片(不指定默认是5分片,1副本),如果已经存在,则只能修改副本。

操作setting

  • 操作不存在索引:

    1
    2
    curl -XPUT '192.168.80.10:9200/liuch/' -d
    '{"settings":{"number_of_shards":3,"number_of_replicas":0}}'
  • 操作已存在索引:

    1
    2
    curl -XPUT '192.168.80.10:9200/zhouls/_settings' -d
    '{"index":{"number_of_replicas":1}}'

操作mapping

  • 操作不存在的索引:

    1
    2
    curl -XPUT '192.168.80.10:9200/zhouls' -d
    '{"mappings":{"emp":{"properties":{"name":{"type":"string","analyzer": "ik_max_word"}}}}}'
  • 操作已存在的索引:

    1
    2
    curl -XPOST http://192.168.80.10:9200/zhouls/emp/_mapping -d
    '{"properties":{"name":{"type":"string","analyzer": "ik_max_word"}}}'

ES后面参数:

  • p=param:请求参数方式
  • 空格 -d body:请求体方式
  • v:输出详细
  • h=字段1,字段2:只输出该字段
  • filter_path(如:hits.hits._source.username):只返回指定path的字段,高版本支持设置”_source”:[“username”,”pass”]
  • search_type=count:任何时候都不返回hits部分,减少返回内容,只计数(注意,5版本之后已移除该设置)
  • refresh:时刻刷新,为了实时获取最新数据,默认刷新间隔为1秒

size

size设置为0,表示不返回文档的信息,一般用在聚合操作,在只关心聚合的结果而不关心查询结果的情况下使用。

Scroll查询示例:

首次:

1
2
3
4
ip:port/index/_search?scroll=1m
{
"size":10
}

之后:

1
2
3
4
5
6
ip:port/_search/scroll    //注意此处没有了index,scroll格式也变化了
{
//注意此处没有了size
"scroll":1m,
"scroll_id":"上次查询返回的_scroll_id"
}

ID特殊字符

如果根据ID搜索文档时,ID含有特殊字符,如\、/等,可以先用URLEncoder进行编码,编码后仍然查询有效

Term vs Terms

  • 对于查询多个字段,用Terms,而单个字段要用Term
  • 对于排序和script应该用Terms
  • 对于聚合分组,用Terms

Terms查询数组长度过大问题

14.ElasticSearch使用terms查询时,如果值为数组且长度大于1024,可能出现:【maxclauseCount is set to 1024】错误,可以使用filter来解决

1
2
3
4
5
6
7
8
9
10
11
"query":{
"bool":{
"must":{
"match_all":{}
},
"filter":{
"terms":{
"dip":["0.0.0.0",……]
}
}
}

Scroll + Scan

使用scroll搜索时,首次搜索和之后的搜索都会返回hit,但是如果加入了scan,需注意

  • 首次不会返回hit,只会返回_scroll_id,然后第二次才开始返回值
  • 比如设置size=1000,则实际返回的数量是size*分片数
  • scanning scroll 查询不支持聚合
  • scanning scroll 查询结果没有排序,结果的顺序是doc入库时的顺序

聚合中的size

聚合中,使用terms分组默认返回每个组的10条数组,如果需要返回所有数据,在terms内添加字段:"size":0,当然也可以指定返回需要的长度的数据

但是注意:size为0是早期版本,0代表全部;5.x,6.x都不允许为0了。那么对于5.x和6.x设置为全量聚合:"size":2147483647

Restclient 查询数据过大

使用restclient,查询数据过大,报:entity content is too long xxxx for the configured buffer limit 104857600

1
2
3
//Overiding the 100MB Buffer Limit to 1GB
HttpEntity entity=new NStringEntity(params,ContentType.APPLICATION_JSON);
response = client.performRequest("POST", endPoint, new HashMap<>(), entity, new HeapBufferedResponseConsumerFactory(1024 * 1024 * 1024));

注意:es rest clent依赖包版本需要5.6以上才能使用HeapBufferedResponseConsumerFactory

广度优先聚合

在海量数据下使用聚合嵌套聚合导致ES崩溃:考虑使用广度优先聚合:

1
2
3
4
5
{ "aggs" :
{ "actors" :
{ "terms" :
{ "field" :"actors" ,"size" :10 ,"collect_mode":"breadth_first"
…………

注意:广度优先聚合的内存要求与修剪前每个桶中的文档数量成线性关系,如果每个桶中的文档有数千或数十万,也不考虑使用广度优先。这也是为什么深度优先是默认选择的原因。

_source vs doc

script使用_source[‘key’]速度比doc[‘key’].value慢。对于上亿数据,实测_source 8秒,doc 0.8秒。

注意:

  • es低版本:doc[‘key’].value _source[‘key’]
  • es高版本:doc[‘key’].value params._source[‘key’]
  • 高版本的doc[‘key’].value中的key不再支持IP类型

ID查找

在数据量非常巨大的情况下,通过ID查找文档最好使用官方的GET index/type/id,而非POST query:{term:{id:14}}

scroll:timevalue vs size

使用scroll时,timevalue应与size成正比,size越大越易超时,故timevalue应越大,不过timevalue最大到5m就差不多了

Scroll自动关闭

ES5.6以上,RestClient Scroll貌似查询到结尾会自动关闭

Scroll查询缓存

ES Scroll查询时,其实返回一批数据后,后面(分片数-1)批数据已经缓存好了,所以后面的(分片数-1)批获取也很快!故查询时,单次查询数量越多整个查询耗时越低,不过也要注意以下情况适当调整单次查询数量:

  • 网络较慢
  • ES负载较大
  • RestClilent方式(数据过大容易造成Timeout或者数据过大接收不了错误【高版本可设置HeapBufferResponseConsumerFactory来调大】)

加快Scroll速度:

  • search_type=scan
  • sort:”_doc” //不过如果size本身就是1的话就没必要了,一条记录不涉及到排序

ES常用字段操作

增加字段:

1
2
3
4
5
6
7
8
9
10
11
post  index/_mapping/type
{
"type":{
"properties":{
"字段名":{
"type":"string",
"index":"not_analyzed"
}
}
}
}

ES按条件更新字段:

1
2
3
4
5
6
7
8
9
post  index/type/_update_by_query
{
"script":{
"inline":"if(!ctx._source.containsKey(\"urllevel\")){ctx._source.attending=\"urllevel\"};ctx._source.urllevel=ctx._source.fullurl.split(\"/\").size();"
},
"query":{
"match_all":{}
}
}

ES Groovy截取字符串搜索:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
get index/_search
{
"query":{
“bool”:{
“must”:[
{
"term":{
"field":"h-req-refer"
}
},
{
"script":{
"inline":"doc['h-req-refer'].value.substring(0,doc['h-req-refer'].value.firstIndexOf('/'))==doc['h-req-host']",
"lang":"groovy"
}
}
]
}
}
}

ES聚合后,按照某字段内容总长度进行排序【自定义排序】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
http://localhost:9200/index/type/_search   POST
{
"size":0,
"aggs":{
"mydata":{
"terms":{
"field":"h-req-data",
"order":{
"max_length":"desc"
},
"size":3
},
"aggs":{
"max_length":{
"max":{
"script":"doc['h-req-data'].value==null?0:doc['h-req-data'].value.split('[^a-zA-Z0-9=/%\\\\+]').sort(a,b->a.length()<==>b.length())[0].length()"
}
}
}
}
}
}

ES与多线程

处理ES时,并不是线程越多越好,应该让多线程做该做的事情:让多线程去处理逻辑判断,单线程或少量线程去请求IO(网络IO),因为IO总是瓶颈。
读程序:

while
单线程获取ES数据(可分页或scroll分批获取)
多线程逻辑处理ES数据,处理完成后put某个阻塞队列queue

写程序:

while
数据 = queue.take()
单线程将处理后的数据存入ES(增删改等)

ES重试

ES如果数据量巨大,经常发生超时现象(包括scroll时),可代码实现重试操作,就算是scroll超时,scroll_id传入ES,返回数据时超时,也不用担心,该scroll_id仍然可用,可重新使用该scroll_id进行查询。
注意:restclient可设置超时时间,而transport默认超时时间很长,但一旦超时,会一直卡死

各关键字查询效率

在布尔查询中,filter参数和must_not参数使用Filter Context,而mustshould使用Query Context,经常使用Filter Context,引擎会自动缓存数据,提高查询性能。

ES的更新

ES的更新分为全局更新和局部更新:

1
2
3
4
5
-XPOST 'http://192.168.80.200:9200/zhouls/emp/1/_update' -d
'{"doc":{"name":"mack"}}'

-XPOST 'http://192.168.80.200:9200/zhouls/emp/1' -d
'{"name":"mack"}'

后面的是全局更新,更新后其他字段值将不存在,只有name字段值

ES索引复制:

1
2
3
4
5
6
7
8
9
10
http://localhost:9200/_reindex   POST
{
"source": {
"index": "old_index"
},
"dest": {
"index": "new_index",
"op_type": "create"
}
}

painless开启split功能

在painless中没有支持java string的split方法,但可以修改es配置开启正则的split语法

但在实际使用中发现性能损耗很大,一条执行一百毫秒左右的搜索语句,加上split后搜索时间升到七百毫秒左右,所以官方在未支持split方法时也指出本身string的split性能也不是很好,尽量避免使用

ES海量查询技巧

对于ES等大数据库,对于海量数据,查询需要花费时间常查的条件结果集可重入一个“缓存”索引,后续查询时,如果条件包含之前的条件,则可在缓存索引中查询(并带上剩余的条件)。