针对目前工作每日处理上亿数据的经历,作了几点总结
增删改查
- 增用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
2curl -XPUT '192.168.80.10:9200/liuch/' -d
'{"settings":{"number_of_shards":3,"number_of_replicas":0}}'操作已存在索引:
1
2curl -XPUT '192.168.80.10:9200/zhouls/_settings' -d
'{"index":{"number_of_replicas":1}}'
操作mapping
操作不存在的索引:
1
2curl -XPUT '192.168.80.10:9200/zhouls' -d
'{"mappings":{"emp":{"properties":{"name":{"type":"string","analyzer": "ik_max_word"}}}}}'操作已存在的索引:
1
2curl -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
4ip:port/index/_search?scroll=1m
{
"size":10
}
之后:1
2
3
4
5
6ip: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
11post index/_mapping/type
{
"type":{
"properties":{
"字段名":{
"type":"string",
"index":"not_analyzed"
}
}
}
}
ES按条件更新字段:1
2
3
4
5
6
7
8
9post 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
20get 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
22http://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,而must
和should
使用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 | http://localhost:9200/_reindex POST |
painless开启split功能
在painless中没有支持java string的split方法,但可以修改es配置开启正则的split语法
但在实际使用中发现性能损耗很大,一条执行一百毫秒左右的搜索语句,加上split后搜索时间升到七百毫秒左右,所以官方在未支持split方法时也指出本身string的split性能也不是很好,尽量避免使用
ES海量查询技巧
对于ES等大数据库,对于海量数据,查询需要花费时间常查的条件结果集可重入一个“缓存”索引,后续查询时,如果条件包含之前的条件,则可在缓存索引中查询(并带上剩余的条件)。
更多文章,请关注:开猿笔记