# ElasticSearch学习文档
## 核心概念
ES属于面向文档型的数据库:索引>>数据库、类型>>表、文档>>行、字段>>列,由于使用了*倒排索引*的概念,则类型(Type)的概念被弱化。
倒排索引:通过文档内容关联文档id。
映射:表示索引的数据结构信息
分片:类似分表,由于单索引数据量巨大,使用时受限于单节点的硬件性能,同时从多个节点的分片中读取索引数据时性能将会提高。
副本:为防止节点数据丢失,在其他节点建立分片的副本。副本将会同步主分片的数据。
## 映射相关
text:text类型的字段会默认被分词,如果存入一个text类型字段某个值时,值的本身将不存在于倒排索引中。例如存入content字段“hello world”,则倒排索引中只会存在hello和world,此时通过term查询“hello world”查询不到文档。在es5版本之后,如果一个字段被定义为text类型,则将会默认为其生成content.keyword字段,类型为keyword,不进行分词,默认保留256个字符直接放入倒排索引中。解决了上述问题。考虑到仅保存256个字符,如果存在content字段存在大量文本,则需手动建立content.keyword设置其 not_analyzed
keyword:存入类型为keyword的字段的值将不会被分词。
## 分词相关
在插入字段时,分词器将会对值进行分词,将分词结果保存到倒排索引中。
english analyzer会将词还原为原始形态,例如learning>learn,courses>course。
ik:中文分词器。分为ik_max_word(常用,细粒度)和ik_smart(不常用,粗粒度)。
配置文件:${es_root}/plugins/${ik_root}/config 下:
IKAnalyzer.cfg.xml:用来配置自定义词库
main.dic:原生词库。
stopword.dic:英文停用词。 不能
## filter查询原理
1. 在倒排索引中查找搜索串,获取document list
2. 使用找到的doc list,构建bitset,就是一个二进制数组,用来标识一个doc对filter是否匹配。[0,1,0,1,0,0]
3. 遍历每个过滤条件对应的bitset(每个filter都会构建bitset),优先从最稀疏(数组中0最多的)的开始搜索,先过滤掉尽可能多的数据,查找满足所有条件的doc返回给client。
4. caching bitset,跟踪filter,在最近的256个filter中,某个filter超出一定次数将会缓存其对应的bitset。对于小segment(<1000或<3%),不缓存。filter相比与query的好处就在于会caching。
5. filter大部分情况下会在query前执行,先尽量过滤掉尽可能多的数据。query会计算doc对搜索条件的相关度,还根据score排序。
6. 如果document有新增或者修改时,cached bitset会被自动更新。
7. 以后只要有相同条件的filter,将会直接使用对应的bitset。
## 查询相关
1. bool:bool内可以与must,must_not,should组合过滤,同时三个子对象可以嵌套bool。
2. 当should和must同时使用时,should中的条件可以都不满足,仅影响相关分数;但当should单独使用时,必须满足其中一个条件才可以。可以同"minimum_should_match"搭配使用。
3. term :对搜索文本不分词,直接拿去倒排索引中匹配。对数字、bool、date天然支持
4. terms:如果字段类型类似数组形式时,使用terms来匹配数组内的任意元素。
```json
"terms":{"tags":["ydsmp"]}
//使用term及terms查询时,一定要注意查询条件是否在倒排索引中存在。例如:
"tags":[ "app=ydsmp", "ip=10.127.25.155","host=ydsmp-hu2kukd"]
//此文档的分词结果为【app,ydsmp,ip,10.127.25.155,host,hu2kukd】,此时如果使用
"terms":["tags":["app=ydsmp"]]
//将不会有任何结果。
```
5. filter:filter内可以与bool,range组合过滤(同一级别只能使用一个?),支持嵌套。不计算相关分数
6. match:匹配查询,查询条件将会被分词。精确度低。例:
```json
{
"match":{
"$field":"$value"
}
}
//或
{
"match":{
"$field":{
"query":"$value",
"operator":"or",
"minimum_should_match":"75%(最少应满足)"
}
}
}
```
在使用match匹配时,es会在底层将match转换为bool、should/must、term转换,例:
```json
{
"match":{
"title":{
"query":"java go",
"operator":"or",
"minimum_should_match":"50%"
}
}
}
//转换为
{
"bool":{
"should":[
{
"term":{
"title":"java"
}
},
{
"term":{
"title":"go"
}
}
],
"mimimum_should_match":1
}
}
```
7. boost:权重。可以将某个搜索条件的权重加大,计算score时会更高,默认为1。
8. dis_max:为*Disjunction Max Query* ,分离最大化查询指的是,将任何与任一查询匹配的文档作为返回结果,但只将最佳匹配的评分作为查询的评分结果返回。通过使用*tie_breaker*选项,也会考虑其他匹配的评分乘以参数***0.2***计算进去。
```json
{
"query":{
"dis_max":{
"queries":[
{
"match":{}
},
{
"match":{}
}
],
"tie_breaker":0.2
}
}
}
```
9. multi_match:基于dis_max、match(minimum_should_match、boost)、tie_breaker整合的匹配命令。转换关系如下:
```json
{
"query":{
"multi_match":{
"query":"java solution",
"type":"best_fields",
"fields":["title^2","content"],
"tie_breaker":0.3,
"minimum_should_match":"50%"
}
}
}
//转换为
{
"query":{
"dis_max":{
"queries":[
{
"match":{
"title":{
"query":"java solution",
"minimum_should_match":"50%",
"boost":2
}
}
},
{
"match":{
"content":{
"query":"java solution",
"minimum_should_match":"50%"
}
}
}
],
"tie_breaker":0.3
}
}
}
```
10. best_fields与most_fields策略
11. match_phrase:匹配短语。召回率低。由于要计算分词的position和slop,所以性能比单纯的match低很多。
```json
//省略上级
{
"match_phrase":{
"title":"java spark"
}
}
// 首先,match_phrase的条件仍然是分词的,将查询条件java spark分词为【java、spark】
// 然后,用term分别匹配两个词,即
{
"must":[
{
"term":{"title":"java"}
},
{
"term":{"title":"spark"}
}
]
}
// 最后,判断过滤出的文档中的title值,在倒排索引中,spark.position刚好比java.position大1的,即为满足查询条件。
```
12. 使用match_phrase+slop做近似匹配。 slop的值表示分词的偏移量。
13. 混合使用match是match_phrase+slop做召回率(通过match)和精确度(通过match_phrase+slop)的平衡。
```json
{
"query":{
"bool":{
"must":[
{
"match":{// 过滤出满足条件的doc
"title":{
"query":"java spark"
}
}
}
],
"should":[
{
"match_phrase":{// 在slop以内,匹配的文档如果 java和spark靠的越近,则获得的分数越高,排序将越靠前
// 针对大量文档,如果通通计算其position和slop时,将会耗费时间过长,可通过rescore选项来提高效率,具体baidu
"title":{
"query":"java spark",
"slop":50
}
}
}
]
}
}
}
```
## 模糊查询
1. prefix:用户同match,前缀搜索。前缀搜索不计算相关分数,即query.prefix同filter.prefix的唯一区别就是filter会cache bitset。prefix会扫描全部的倒排索引,性能差。
```json
{
"query":{
"prefix":{
"title":{
"value":"C3"
}
}
}
}
```
2. wildcard:通配符搜索。需扫描全部倒排索引,性能差。
```json
{
"query":{
"wildcard":{
"title":{
"value":"*5?5"
}// * 表示任意多个字符;? 表示一个字符
}
}
}
```
3. regexp:正则表达式。同上。
```json
{
"query":{
"regexp":{
"title":"C[0-9].+"
}
}
}
```
4. match_phrase_prefix:搜索推荐。
```json
{ // 将条件分词为 hello w,先根据hello,match_phrase出docs1, 然后根据w做 prefix前缀搜索,过滤出docs2,取交集
// 再通过slop去计算 hello和w 的匹配度
"match_phrase_prefix":{
"title":{
"query":"hello w",
"slop":10,
"max_expansions":50 //指定prefix在倒排索引中最多匹配到50个文档后,不继续匹配,提高性能。
}
}
}
```
5. ngram模型:百度。使用edge ngram进一步分词增加倒排索引容量,此时在实现搜索推荐功能时,使用match即可。
6. fuzzy:自动将拼写错误的搜索文本自动纠正,纠正后尝试匹配倒排索引。
```json
{
"query":{
"fuzzy":{
"title":{
"value":"hallo",
"fuzziness":2 // 指的是搜索文本最多纠正几个字符来匹配,默认2
}
}
}
}
```
## 聚合基础
两个核心概念:bucket和metric
bucket:一个数据分组,例如基于某个字段分组,字段值相同的文档将会被划分到一个bucket中。
metric:对一个分组内的数据进行某种统计。比如计数,取和,最大值等。
1. 统计每个应用的SQL执行时间平均值,并降序排序
```json
{
"size": 0,
"query": { //过滤掉错误数据, 下面的聚合操作需要在此Scope下执行
"bool": {
"must": [
{
"exists": {
"field": "ExecSQL"
}
}
]
}
},
"aggs": {
"app_group": {// 分组的名称
"terms": {
"field": "APP.keyword", // 根据此字段分组,分成多个bucket
"order": {
"avg_time": "desc" //按照aggs.name为avg_time的metric降序
}
},
"aggs": { // 对上面每个bucket执行运算(metric)
"avg_time": {
"avg": { //
"field": "costTime" // 对costTime字段进行平均值计算
}
},
"max_time":{
"max":{
"field":"costTime"
}
}
}
}
}
}
```
2. 统计每个应用的各个服务的SQL执行时间平均值。执行两次分组,在1中APP进行分组完毕后,再次进行服务的分组,最后计算metric。
```json
{
"size": 0,
"query": {
"bool": {
"must": [
{
"exists": {
"field": "ExecSQL"
}
}
]
}
},
"aggs": {
"app_group": {
"terms": {
"field": "APP.keyword"
},
"aggs": {
"avg_time": {
"avg": {
"field": "costTime"
}
},
"service_group": { //以下为下钻内容。 一个aggs.name下可以同时包含多个metric或一个bucket(见1)。一个aggs可以包含多个aggName(见6)
"terms": {
"field": "eventCode.keyword"
},
"aggs": {
"avg_time": {
"avg": {
"field": "costTime"
}
}
}
}
}
}
}
}
```
3. 计算执行时间在某个时间段内的平均值。引入histogram:类似于terms,也是进行bucket分组操作,会接收一个field,按照字段值的各个范围区间进行分组操作。
```json
{
"size": 0,
"query": {
"bool": {
"must": [
{
"exists": {
"field": "ExecSQL"
}
}
]
}
},
"aggs": {
"timeRange": {
"histogram": {
"field": "costTime", // 通过costTime字段划分
"interval": 100 // 划分方式0-100,100-200,200-300等
},
"aggs": {// 子聚合
"app_group": {
"terms": {// 通过APP分组
"field": "APP.keyword"
},
"aggs": {
"avg_time": {
"avg": { // 计算各个APP在0-100区间内的平均值(属于下钻)
"field": "costTime"
}
}
}
},
"avg_time": {
"avg": { // 计算0-100区间内的平均值(区别于app_group内部的aggs)
"field": "costTime"
}
}
}
}
}
}
```
4. 计算时间段内,各个APP的SQL执行时间平均值。引入date histogram,同上,是进行bucket分组的操作。
```json
{
"size": 0,
"query": {
"bool": {
"must": [
{
"exists": {
"field": "ExecSQL"
}
}
]
}
},
"aggs": {
"dateTime": {
"date_histogram": {
"field": "@timestamp", //划分时间的字段
"interval": "hour", // 步长
"min_doc_count": 1 // 某时间区间的文档最小数量,防止某个无数据区间段被放弃
},
"aggs": {
"app_group": {
"terms": {
"field": "APP.keyword"
},
"aggs": {
"avg_time": {
"avg": {
"field": "costTime"
}
}
}
},
"avg_time": {
"avg": {
"field": "costTime"
}
}
}
}
}
}
```
5. 某应用的SQL执行时间与整个系统所有SQL执行时间进行对比分析。引入:global bucket概念。
```json
{
"size": 0,
"query": { //确定聚合的scope, app=m-bs-orgmanage,且ExecSQL存在
"bool": {
"must": [
{
"exists": {
"field": "ExecSQL"
}
},
{
"match_phrase": {
"APP": "m-bs-orgmanage"
}
}
]
}
},
"aggs": {
"app_avg_time": { //计算scope下的平均值
"avg": {
"field": "costTime"
}
},
"allApp_avg_time": { //重新建立bucket
"global": {}, // 明确使用所有文档,json体为空
"aggs": { // 在global.scope下计算聚合
"avg": {
"avg": {
"field": "costTime"
}
}
}
}
}
}
```
6. 应用内,执行时间小于500ms的SQL,计算平均值;执行时间在500-1000ms的SQL,计算其平均值;执行之间大于1000ms的SQL,计算平均值。引入aggs.filter,表示在桶内执行的filter(局部过滤)。
```json
{
"size": 0,
"query": {
"bool": {
"must": [
{
"exists": {
"field": "ExecSQL"
}
}
]
}
},
"aggs": {
"lte500": {
"filter": {
"range": {
"costTime": {
"lte": 500
}
}
},
"aggs": {
"avg_time": {
"avg": {
"field": "costTime"
}
}
}
},
"500-1000": {
"filter": {
"range": {
"costTime": {
"gt": 500,
"lte": 1000
}
}
},
"aggs": {
"avg_time": {
"avg": {
"field": "costTime"
}
}
}
},
"gt1000": {
"filter": {
"range": {
"costTime": {
"gt": 1000
}
}
},
"aggs": {
"avg_time": {
"avg": {
"field": "costTime"
}
}
}
}
}
}
```