目录
Elasticsearch相关优化
/    

Elasticsearch相关优化

Elasticsearch 相关优化

写入优化

使用默认的 _id

_id 字段的使用,应尽可能避免我们自己自定义_id, 以避免针对 ID 的版本管理;建议使用 ES 的默认 ID 生成策略或使用数字类型 ID 做为主键。

在索引具有显式 id 的文档时,Elasticsearch 需要检查具有相同 id 的文档是否已经存在于同一个分片中,这是一项代价高昂的操作,并且随着索引的增长而变得更加代价高昂。通过使用自动生成的 id,Elasticsearch 可以跳过此检查,从而加快索引速度。

索引初始化禁用副本

如果您有大量数据想要一次性全部加载到 Elasticsearch 中,那么设置 index.number_of_replicas0 以加快索引速度可能会有所帮助。没有副本意味着丢失单个节点可能会导致数据丢失,因此重要的是数据位于其他地方,以便在出现问题时可以重试初始加载。初始加载完成后,您可以将 index.number_of_replicas 其设置回其原始值。

修改索引的副本数:

PUT index_name/_settings

{

    "number_of_replicas": 1

}

增大 refresh_interval 刷新间隔

在 Elasticsearch 中,写入和打开一个新段的轻量的过程叫做 refresh 。 默认情况下每个分片会每秒自动刷新一次。这就是为什么我们说 Elasticsearch 是 实时搜索: 文档的变化并不是立即对搜索可见,但会在一秒之内变为可见。

频繁刷新索引 也是有性能开销的。

es的写入原理:

大概分为三个步骤:write -> refresh -> flush

1、write:文档数据到内存缓存,并存到 translog

2、refresh:内存缓存中的文档数据,到文件缓存中的 segment 。此时可以被搜到。

3、flush 是缓存中的 segment 文档数据写入到磁盘

refresh_interval 配置索引的刷新间隔。

修改 refresh_interval 参数:

  • 可以在创建索引的时候,配置 settings

    {
        "settings": {
    		"refresh_interval": "30s"
        }
    }
    
  • 直接修改索引的 refresh_interval

    PUT index_name/_settings
    { 
      "refresh_interval": "30s" 
    }
    

默认情况下ElasticSearch索引的refresh_interval为1秒,这意味着数据写1秒才就可以被搜索到。

每次索引refresh会产生一个新的 lucene 段,这会导致频繁的 segment merge 行为,对系统 CPU 和 IO 占用都比较高。

如果产品对于实时性要求不高,则可以降低刷新周期,如:index.refresh_interval: 120s。

查询优化

深度分页问题

使用 search_after ,需要有排序的唯一字段,组合字段也可以,要保证唯一性

对于业务的需要,如果我们需要根据一些字段排序,需要将那些字段加到 search_after

查询示例:

// 查询第一页
GET search_test_02/_search
{
  "size": 2,
  "sort": [
    {
      "id": {
        "order": "desc"
      }
    }
  ]
}

// 查询第二页
GET search_test_02/_search
{
  "size": 2,
    // 这里的 6,是第一页的最后一条数据的 id
  "search_after": [6],
  "sort": [
    {
      "id": {
        "order": "desc"
      }
    }
  ]
}

使用 search_after 缺点:

  • search_after 的字段要和排序字段保持一致、顺序也要一致,如果后期要根据其他字段进行排序,需要那个字段加到 search_after 中;限制了业务的本身
  • 当 search_after 的字段增多,排序字段也随之增加,排序会占用大量的内存

过滤无用标签 - 自定义分词

  • Character Filter

    在 Tokenizer 之前对文本进行处理,例如增加删除及替换字符。可以配置多个 Character Filter。会影响 Tokenizer 的 position 和 offset 信息

    一些自带的 Character Filters:

    • HTML strip - 去除 html 标签
    • Mapping - 字符串替换
    • Pattern replace - 正则匹配替换
  • Tokenizer

    将原始文本按照一定的规则,切分为词(term or token)

    Elasticsearch 内置Tokenizers:

    • whitespace
    • standard
    • uax_url_email
    • pattern
    • keyword
    • path hierarchy 文件路径

    可以用 Java 开发插件,实现自己的 Tokenizer

  • Token Filter

    处理 Tokenizer 输出的单词 (term 分词后的每一个词),进行增加、修改、删除

    自带的Token Filter:

    • Lowercase 将字母转成小写
    • stop 去除停用词
    • synonym 添加近义词

示例:

POST _analyze
{
    "tokenizer": "keyword",
    "char_filter": ["html_strip"],
    "text": "<h1>我是一级标题</h1> <br/> <span>我是一个span</span>"
}

{

“tokens”: [

{

“token”: “\n我是一级标题\n \n 我是一个span”,

“start_offset”: 0,

“end_offset”: 43,

“type”: “word”,

“position”: 0

}

]

}

使用 char_filter 进行替换

POST _analyze
{
    "tokenizer": "standard",
    "char_filter": [
        {
            "type": "mapping",
            "mappings": [
                ":) => happy",
                ":( => sad"
            ]
        }
    ],
    "text": "Tom feels :). Mary feels :("
}

{

“tokens”: [

{

“token”: “Tom”,

“start_offset”: 0,

“end_offset”: 3,

“type”: ““,

“position”: 0

},

{

“token”: “feels”,

“start_offset”: 4,

“end_offset”: 9,

“type”: ““,

“position”: 1

},

{

“token”: “happy”,

“start_offset”: 10,

“end_offset”: 12,

“type”: ““,

“position”: 2

},

{

“token”: “Mary”,

“start_offset”: 14,

“end_offset”: 18,

“type”: ““,

“position”: 3

},

{

“token”: “feels”,

“start_offset”: 19,

“end_offset”: 24,

“type”: ““,

“position”: 4

},

{

“token”: “sad”,

“start_offset”: 25,

“end_offset”: 27,

“type”: ““,

“position”: 5

}

]

}

正则的方式:

{
    "tokenizer": "standard",
    "char_filter": [
        {
            "type": "pattern_replace",
            "pattern": "http://(.*)",
            "replacement": "$1"
        }
    ],
    "text": "http://baidu.com"
}

{

“tokens”: [

{

“token”: “baidu.com”,

“start_offset”: 0,

“end_offset”: 16,

“type”: ““,

“position”: 0

}

]

}

空格 和 停用词

{
    "tokenizer": "whitespace",
    "filter": ["lowercase", "stop"],
    "text": "The Tom is a monkey on the tree!"
}

{

“tokens”: [

{

“token”: “tom”,

“start_offset”: 4,

“end_offset”: 7,

“type”: “word”,

“position”: 1

},

{

“token”: “monkey”,

“start_offset”: 13,

“end_offset”: 19,

“type”: “word”,

“position”: 4

},

{

“token”: “tree!”,

“start_offset”: 27,

“end_offset”: 32,

“type”: “word”,

“position”: 7

}

]

}

使用 自定义的分词 创建索引

PUT user_test_analyzer
{
    "settings": {
        "analysis": {
            "analyzer": {
                "my_custom_analyzer": {
                    "type": "custom",
                    "char_filter": [
                        "emoticons"
                    ],
                    "tokenizer": "punctuation",
                    "filter": [
                        "lowercase",
                        "english_stop"
                    ]
                }
            },
            "tokenizer": {
                "punctuation": {
                    "type": "pattern",
                    "pattern": "[.,!?]"
                }
            },
            "char_filter": {
                "emoticons": {
                    "type": "mapping",
                    "mappings": [
                        ":) => happy",
                        ":( => sad"
                    ]
                }
            },
            "filter": {
                "english_stop": {
                    "type": "stop",
                    "stopwords": "_english_"
                }
            }
        }
    },
    "mappings": {
        "_doc": {
            "properties": {
                "name": {
                    "type": "keyword",
                    "copy_to": "full_index"
                },
                "content": {
                    "type": "text",
                    "analyzer": "my_custom_analyzer",
                    "copy_to": "full_index"
                },
                "age": {
                    "type": "integer",
                    "copy_to": "full_index"
                }
            }
        }
    }
}

测试

POST /user_test_analyzer/_analyze{    "analyzer": "my_custom_analyzer",    "text": "I'm a :) person, that looks :( , and you ?"}

{

“tokens”: [

{

“token”: “i’m a happy person”,

“start_offset”: 0,

“end_offset”: 15,

“type”: “word”,

“position”: 0

},

{

“token”: “ that looks sad “,

“start_offset”: 16,

“end_offset”: 31,

“type”: “word”,

“position”: 1

},

{

“token”: “ and you “,

“start_offset”: 32,

“end_offset”: 41,

“type”: “word”,

“position”: 2

}

]

}

优化全文索引字段

创建索引的时候,不用创建全文检索字段;在需要索引的字段使用 copy_to

1、创建测试索引

PUT copy_search
{
  "mappings": {
    "_doc": {
      "properties": {
        "name": {
          "type": "keyword",
          "copy_to": "full_index"
        },
        "content": {
          "type": "text",
          "copy_to": "full_index"
        },
        "age": {
          "type": "integer"
        }
      }
    }
  }
}

2、插入数据

POST copy_search/_doc
{
  "name": "zhangsan",
  "content": "I'm :)",
  "age": 18
}

3、全文检索

GET copy_search/_search
{
  "query": {
    "bool": {
      "filter": {
        "match": {
          "full_index": "zhangsan"
        }
      }
    }
  }
}

// result

{
  "took": 2,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": 2,
    "max_score": 0,
    "hits": [
      {
        "_index": "copy_search",
        "_type": "_doc",
        "_id": "kcKv534BLSHkQNHCbGTu",
        "_score": 0,
        "_source": {
          "name": "zhangsan",
          "content": "I'm :)",
          "age": 18
        }
      },
      {
        "_index": "copy_search",
        "_type": "_doc",
        "_id": "ksKx534BLSHkQNHCxWQS",
        "_score": 0,
        "_source": {
          "name": "zhangsan wanger 喂你好",
          "content": "I'm :)",
          "age": 19
        }
      }
    ]
  }
}

非检索字段,不分词、不索引

如果你不会执行基于一个确定的子段 聚合、排序或执行脚本(Script ),你可以选择关闭Doc Values;节省磁盘空间,提高索引数据的速度。

doc_values 默认启用,可以通过 Mapping 设置关闭;关闭之后,可以增加索引速度、减少磁盘

如果重新打开,需要重建索引

当明确不需要做排序及聚合分析时,可以关闭

{
    "mappings": {
        "_doc": {
            "properties": {
                "name": {
                    "type": "keyword",
                    "copy_to": "full_index"
                },
                "content": {
                    "type": "text",
                    "index": "not_analyzed",
                    "doc_values": false,
                    "copy_to": "full_index"
                },
                "age": {
                    "type": "integer",
                    "copy_to": "full_index"
                }
            }
        }
    }
}

字段用下面的设置,该字段 支持聚合,但不支持查询,因为不会对这个字段生成倒排索引

{
    "type":       "string",
    "index":      "not_analyzed",
    "doc_values": true,
    "index": "no"
}

**数据查询 **

因为我们创建索引的时候,使用了别名,又使用了滚动索引,所以我们查询的时候,只需要查询 索引的别名即可。

由于我们设置设置别名的时候,设置 is_write_index: true ,所以查询的时候,会自动查询所有的 索引(原理不详);写入的时候会写入最新的索引上。

目前只支持 elasticsearch 7.x

常规优化

对于 T+1 的数据新增,每天凌晨数据跑完,xshell 调用ES强制合并优化。

强制合并API:https://doc.codingdict.com/elasticsearch/205/

强制合并API允许通过API强制合并一个或多个索引。合并依赖于Lucene索引在每个分片中保存的分段数。强制合并操作允许通过合并分段来减少他们的数量。

此调用会阻塞,直到合并完成。如果 http 连接丢失,请求将在后台继续,并且任务新的请求都会阻塞,直到前面的强制合并完成

段合并的原理和作用:https://www.jianshu.com/p/661e6fe98602

索引管理

Rollover API 滚动创建索引

滚动索引一般可以与索引模板结合使用,实现按一定条件

示例 - 1

  • 创建索引

    PUT nginx-logs-00001
    {
        "aliases": {
            "nginx-logs-write": {}
        }
    }
    
    { - 
    "nginx-logs-00001": { - 
     "aliases": { - 
       "nginx-logs-write": { - 
    
       }
     },
     "mappings": { - 
       "properties": { - 
         "log": { - 
           "type": "text",
           "fields": { - 
             "keyword": { - 
               "type": "keyword",
               "ignore_above": 256
             }
           }
         }
       }
     },
     "settings": { - 
       "index": { - 
         "routing": { - 
           "allocation": { - 
             "include": { - 
               "_tier_preference": "data_content"
             }
           }
         },
         "number_of_shards": "1",
         "provided_name": "nginx-logs-00001",
         "creation_date": "1642647405156",
         "number_of_replicas": "1",
         "uuid": "JL96u8LpQ_SHPVBt2oJYhQ",
         "version": { - 
           "created": "7160399"
         }
       }
     }
    }
    }
    
  • 插入数据,插入 6 条数据

    POST nginx-logs-write/_doc
    {
        "log": "something"
    }
    

    { -
    "count": 6,
    "_shards": { -
    "total": 1,
    "successful": 1,
    "skipped": 0,
    "failed": 0
    }
    }

  • 执行 rollover API

    POST	nginx-logs-write/_rollover
    {
        "conditions": {
            "max_age": "1d",
            "max_docs": 5,
            "max_size": "5gb"
        }
    }
    
    { - 
    "acknowledged": true,
    "shards_acknowledged": true,
    "old_index": "nginx-logs-00001",
    "new_index": "nginx-logs-000002",
    "rolled_over": true,
    "dry_run": false,
    "conditions": { - 
     "[max_age: 1d]": false,
     "[max_docs: 5]": true,
     "[max_size: 5gb]": false
    }
    }
    

    此时 nginx-logs-write 已指向 索引 nginx-logs-000002

    我们再往 nginx-logs-write 插入 1 条数据的时候,实际数据已经落入到索引 nginx-logs-write-000002

    此时 nginx-logs-write-00001 有 6 条数据,nginx-logs-write-000002 有 1 条

    再查询 nginx-logs-write 数据条数为 7 条

示例 - 2

  • 创建索引

    PUT apache-logs-000001
    
    {
        "aliases": {
            "apache-logs-write": {
                "is_write_index": true
            }
        }
    }
    
  • 插入两条数据

    POST apache-logs-write/_doc
    
    {
        "logs": "something"
    }
    
  • 执行 rollover API

    POST apache-logs-write/_rollover
    
    {
        "conditions": {
            "max_age": "1d",
            "max_docs": 1,
            "max_size": "5gb"
        }
    }
    
  • 继续插入数据

  • 查看 apache-logs-write 数量 = 之前所有索引中数据的总和

经测试发现,elasticsearch 6.3.2 版本不能用上述的方式,可以使用如下方案

1、首先我们创建一个索引,nginx-logs-000001

{
  "aliases": {
    "nginx-logs": {
    }
  }
}

2、插入数据之后执行 rollover API
3、需要改变的是 我们查询的时候不再使用 别名,使用匹配符的形式;例如:

GET /nginx-logs-*/_search
{
	"query": {
		"match_phrase": {
			"logs": "115"
		}
	}
}

当索引越来越多,压力也会很大;

创建索引别名

1、已有索引创建别名

PUT  index_name/_alias/别名

2、批量创建别名

PUT _aliases?pretty
{
  "actions": [
    {
      "add": {
        "index": "yx-search-chat-other-000001",
        "alias": "chat-other-rollover"
      }
    },
    {
      "add": {
        "index": "yx-search-chat-other-000001",
        "alias": "chat-other-rollover-02"
      }
    }   
    ]
}

索引的生命周期

索引的生命周期是在 Elasticsearch 6.6 之后引入的,所以 6.3.2 版本是不支持的。

配合 IndexTemplate、Rollover 当索引满足一定条件,滚动创建索引。

案例:
创建一个索引,当索引中的文档数满足 3 个时,自动创建新的索引。

1、创建索引的声明周期

PUT /_ilm/policy/ph-policy?pretty
{
  "policy": {
    "phases": {
      "hot": {
        "min_age": "0ms",
        "actions": {
          "set_priority": {
            "priority": 100
          },
          "rollover": {
            "max_age": "30d",
            "max_docs": 3,
            "max_size": "5gb"
          }
        }
      }
    }
  }
}

2、创建索引模板

作用于以 ph- 开头的索引,并默认创建一个别名 ph-read-alias

PUT /_template/ph_template
{
  "index_patterns": ["ph-*"],
  "aliases": {
    "ph-read-alias": {}
  },
  "settings": {
    "index": {
      "lifecycle": {
        "name": "ph-policy",
        "rollover_alias": "ph-write-alias"
      },
      "refresh_interval": "30s",
      "number_of_shards": "2",
      "number_of_replicas": 1
    }
  },
  "mappings": {
    "properties": {
      "name": {
        "type": "keyword"
      }
    }
  }
}

3、创建符合条件的索引
创建一个可写入的别名

PUT ph-user-000001
{
  "aliases": {
    "ph-write-alias": {
      "is_write_index": true
    }
  }
}

4、查看索引的信息

GET ph-user-000001

查看索引是否符合我们的预期

5、连续插入多条数据

POST ph-write-alias/_doc
{
  "name": "hh"
}

由于我们设置 rollover 条件是 文档数 >= 3,当超过 3 条数据后,可以发现索引还是原来的,并没有创建新的索引。

6、修改 ilm 刷新的周期时间

elasticsearch 加载 ilm 的周期,默认是 10 分钟,可以改成 3 秒,否则 10 分钟才会检查一次,不方便我们的测试

PUT _cluster/settings
{
  "transient": {
    "indices.lifecycle.poll_interval": "3s"
  }
}

获取别名 对应的信息

GET ph-write-alias

此时该别名应该对应很多索引,我们可以一直插入数据,观察索引是否自动创建了;我们查询的时候可以根据这个别名进行查询,查询的是所有的索引,当索引数太多的时候,查询效率会有所降低。

分片设计与管理

每个索引

参考来源

1、知乎:https://zhuanlan.zhihu.com/p/43437056

2、官网优化建议:https://www.elastic.co/guide/en/elasticsearch/reference/current/tune-for-indexing-speed.html

3、官网近实时搜索:https://www.elastic.co/guide/cn/elasticsearch/guide/current/near-real-time.html

4、interval_refresh :

5、Elasticsearch 生命周期管理那些事:

6、Rollover API


标题:Elasticsearch相关优化
作者:gitsilence
地址:http://blog.lacknb.cn/articles/2022/07/19/1658216025085.html