elasticSearch:
概述:
Elasticsearch,简称为es, es是一个开源的高扩展的分布式全文检索引擎,它可以近乎实时的存储、检索数据;本身扩展性很好,可以扩展到上百台服务器,处理PB级别的数据。
es也使用Java开发并使用Lucene作为其核心来实现所有索引和搜索的功能,但是它的目的是通过简单的RESTful API来隐藏Lucene的复杂性,从而让全文搜索变得简单。
对比Solr:
Solr 利用 Zookeeper 进行分布式管理,而 Elasticsearch 自身带有分布式协调管理功能;
Solr 支持更多格式的数据,而 Elasticsearch 仅支持json文件格式;
Solr 官方提供的功能更多,而 Elasticsearch 本身更注重于核心功能,高级功能多有第三方插件提供;
Solr 在传统的搜索应用中表现好于 Elasticsearch,但在处理实时搜索应用时效率明显低于 Elasticsearch
安装部署:
单机环境:
tar -zxvf elasticsearch-7.3.0-linux-x86_64.tar.gz -C ../servers
配置文件config/elasticsearch.yml
node.name: node-1
network.host: linux121
http.port: 9200
cluster.initial_master_nodes: ["node-1"]
配置config/jvm.options
-Xms2g
-Xmx2g
添加es用户
useradd es
adduser es root
chown -R es /opt/lagou/servers/elasticsearch/
修改/etc/sysctl.conf
vm.max_map_count=655360
sysctl -p
启动:
su estest
bin/elasticsearch
集群启动:
对应配置elasticsearch.yml
path.data: /opt/lagou/servers/data/es
path.logs: /opt/lagou/servers/logs/es
cluster.initial_master_nodes: ["linux121","linux122","linux123"]
discovery.seed_hosts: ["linux121", "linux122","linux123"]
http.cors.enabled: true
http.cors.allow-origin: "*"
启动es服务
nohup /opt/lagou/servers/es/elasticsearch/bin/elasticsearch >/dev/null 2>&1 &
访问:
http:
安装elasticsearch-head插件:
1. 显示集群的拓扑 能够快速访问并显示集群的状态,并且能够执行索引和节点级别操作
2. 搜索接口能够查允询集群中原始json或表格格式的检索数据
3. 有一个输入窗口,许任意调用RESTful API。
架构:
节点角色:
Master node.master: true 节点可以作为主节点
DataNode node.data: true 默认是数据节点
Coordinate node 协调节点,一个节点只作为接收请求、转发请求到其他节点、汇总各个节点返回数据等功能的节点,就叫协调节点,如果仅担任协调节点,将上两个配置设为false。
说明:一个节点可以充当一个或多个角色,默认三个角色都有
面向文档:
Elasticsearch 是 面向文档 的,意味着它存储整个对象或 文档。Elasticsearch 不仅存储文档,还会索引每个文档的内容使之可以被检索。
在 Elasticsearch 中,你 对文档进行索引、检索、排序和过滤,而不是对行列数据。
这是一种完全不同的思考数据的方式,也是 Elasticsearch 能支持复杂全文检索的原因。
索引(index)
类似的数据放在一个索引,非类似的数据放不同索引, 一个索引也可以理解成一个关系型数据库。
类型(type)
代表document属于index中的哪个类别(type)。也有一种说法一种type就像是数据库的表,
比如dept表,user表。
注意ES每个大版本之间区别很大:
ES 5.x中一个index可以有多种type。 ES 6.x中一个index只能有一种type。 ES 7.x以后 要逐渐移除type这个概念。
映射(mapping)
mapping定义了每个字段的类型等信息。相当于关系型数据库中的表结构。
核心概念:
索引index:
一个索引就是一个拥有几分相似特征的文档的集合。比如说,你可以有一个客户数据的索引,另一个产品目录的索引,还有一个订单数据的索引。
一个索引由一个名字来标识(必须全部是小写字母的),并且当我们要对对应于这个索引中的文档进行索引、搜索、更新和删除的时候,都要使用到这个名字。在一个集群中,可以定义任意多的索引。
类型type:
在一个索引中,你可以定义一种或多种类型。一个类型是你的索引的一个逻辑上的分类/分区,其语义完全由你来定。
通常,会为具有一组共同字段的文档定义一个类型。类型 在 Elasticsearch 中表示一类相似的文档。 类型由名称比如 user 或 blogpost —和 映射 组成。
高版本ES中逐渐抛弃了type的概念,会有一个默认的type:_doc
类型陷阱:
不能定义两个不同的类型,每个字段都有同名的字段,但映射不同(比如一个是字符串一个是数字)
原因:
映射在本质上被 扁平化 成一个单一的、全局的模式。
所以,两个类型不能定义冲突的字段,否则映射被扁平化时,Lucene 不知道如何去处理。
字段Field:
相当于是数据表的字段,对文档数据根据不同属性进行的分类标识
映射mapping:
mapping是处理数据的方式和规则方面做一些限制,如某个字段的数据类型、默认值、分析器、是否被索引等等,这些都是映射里面可以设置的,其它就是处理es里面数据的一些使用规则设置也叫做映射,按着最优规则处理数据对性能提高很大,因此才需要建立映射,并且需要思考如何建立映射才能对性能更好。
常用数据类型:text、keyword、number、array、range、boolean、date、geo_point、ip、nested、object
文档document:
一个文档是一个可被索引的基础信息单元。比如,你可以拥有某一个客户的文档,某一个产品的一个文档,当然,也可以拥有某个订单的一个文档。
文档以JSON(Javascript Object Notation)格式来表示,而JSON是一个到处存在的互联网数据交互格式。
在一个index/type里面,你可以存储任意多的文档。注意,尽管一个文档,物理上存在于一个索引之中,文档必须被索引/赋予一个索引的type。
接近实时NRT:
Elasticsearch是一个接近实时的搜索平台。这意味着,从索引一个文档直到这个文档能够被搜索到有一个轻微的延迟(通常是1秒以内)
cluster集群:
集群(Cluster)
一个Elasticsearch集群由多个节点(Node)组成,每个集群都有一个共同的集群名称作为标识
节点(Node)
一个Elasticsearch实例即一个Node,一台机器可以有多个实例,正常使用下每个实例都应该会部署在不同的机器上。
Elasticsearch的配置文件中可以通过node.master、node.data来设置节点类型。
node.master:表示节点是否具有成为主节点的资格
true代表的是有资格竞选主节点
false代表的是没有资格竞选主节点
node.data:表示节点是否存储数据
Node节点组合
主节点 + 数据节点(master+data) 默认
节点既有成为主节点的资格,又存储数据
node.master: true
node.data: true
数据节点(data)
节点没有成为主节点的资格,不参与选举,只会存储数据
node.master: false
node.data: true
客户端节点(client)
不会成为主节点,也不会存储数据,主要是针对海量请求的时候可以进行负载均衡
node.master: false
node.data: false
分片
每个索引有1个或多个分片,每个分片存储不同的数据。分片可分为主分片(primary shard)和复制分片(replica shard),复制分片是主分片的拷贝。
默认每个主分片有一个复制分片,每个索引的复制分片的数量可以动态地调整,复制分片从不与它的主分片在同一个节点上
副本
这里指主分片的副本分片(主分片的拷贝)
作用:
提高恢复能力:当主分片挂掉时,某个复制分片可以变成主分片;
提高性能:get 和 search 请求既可以由主分片又可以由复制分片处理;
注意:
每个索引可以被分成多个分片。一个索引也可以被复制0次(意思是没有复制)或多次。一旦复制了,每个索引就有了主分片(作为复制源的原来的分片)和复制分片(主分片的拷贝)之别。
配置:
分片和复制的数量可以在索引创建的时候指定。在索引创建之后,你可以在任何时候动态地改变复制的数量,但是你事后不能改变分片的数量。
默认情况下,Elasticsearch中的每个索引被分片5个主分片和1个复制,这意味着,如果你的集群中至少有两个节点,
你的索引将会有5个主分片和另外5个复制分片(1个完全拷贝),这样的话每个索引总共就有10个分片。
索引管理:
创建索引:
在请求体里面传入设置或类型映射
PUT /my_index
{
"settings": { "属性名": "属性值" },
"mappings": {
"properties":{
"字段名":{
"映射属性名":"映射属性值"
}
}
}
}
查询索引:
GET /索引名称
查询所有索引:
GET _all
GET /_cat/indices?v
打开索引:
POST /索引名称/_open
关闭索引:
POST /索引名称/_close 索引只能显示元数据信息,不能够进行读写操作。
删除索引:
DELETE /my_index curl -X DELETE "localhost:9200/my_index"
删除多个索引:
DELETE /index_one,index_two
DELETE /index_*
删除全部索引:
DELETE /_all
DELETE
索引配置:
number_of_shards
每个索引的主分片数,默认值是 5 。这个配置在索引创建后不能修改。
number_of_replicas
每个主分片的副本数,默认值是 1 。对于活动的索引库,这个配置可以随时修改。
创建
PUT /my_temp_index
{
"settings": {
"number_of_shards" : 1,
"number_of_replicas" : 0
}
}
修改配置
PUT /my_temp_index/_settings
{
"number_of_replicas": 1
}
重新索引:
用新的设置创建新的索引并把文档从旧的索引复制到新的索引。
并行重建时,可以按照日期或者时间 这样的字段作为过滤条件把大的重建索引分成小的任务。
索引别名:
curl -X PUT "localhost:9200/my_index_v1"
curl -X PUT "localhost:9200/my_index_v1/_alias/my_index"
查看
curl -X GET "localhost:9200/*/_alias/my_index"
场景:
重新索引完后,重索引
POST /_aliases
{
"actions": [
{ "remove": { "index": "my_index_v1", "alias": "my_index" }},
{ "add": { "index": "my_index_v2", "alias": "my_index" }}
]
}
分词器:
概述:
一个分析器就是在一个包里面组合了三种函数的一个包装器,将一块文本分成适合于倒排索引的独立的 词条 ,之后,将这些词条统一化为标准格式以提高它们的“可搜索性”,或者 recall
字符过滤器
html清除 字符过滤器
分词器
标准分词器
标准分析器是Elasticsearch默认使用的分析器。它是分析各种语言文本最常用的选择。它根据 Unicode 联盟 定义的 单词边界 划分文本。
删除绝大部分标点。最后,将词条小写。
简单分词器
简单分析器在任何不是字母的地方分隔文本,将词条小写。
空格分词器
空格分析器在空格的地方划分文本。
语言分词器
特定语言分析器可用于 很多语言。它们可以考虑指定语言的特点。
例如, 英语 分析器附带了一组英语无用词,如the,and,等对相关性影响不大的
词单元过滤器
词条按顺序通过每个 token 过滤器 。这个过程可能会改变词条(例如,小写化 Quick ),删除词条(例如, 像 a`, `and`, `the 等无用词),或者增加词条。
常见过滤器:
lowercase
扩展词:就是不想让哪些词被分开,让他们分成一个词。
stop词过滤器
词干过滤器:把单词 遏制 为 词干
ascii_folding 过滤器:移除变音符,把一个像 "très" 这样的词转换为 "tres"
ngram 和 edge_ngram 词单元过滤器:可以产生 适合用于部分匹配或者自动补全的词单元。
配置分词器:
PUT /spanish_docs
{
"settings": {
"analysis": {
"analyzer": {
"es_std": {
"type": "standard",
"stopwords": "_spanish_"
}
}
}
}
}
IK中文分词器:
安装:
在elasticsearch安装目录的plugins目录下新建 analysis-ik 目录,解压文件
重启Elasticsearch
模式:
ik_max_word和ik_smart模式
使用:
POST _analyze
{
"analyzer": "ik_max_word",
"text": "南京市长江大桥"
}
配置自定义扩展,停用词典:
tomcat服务转发文件
自定义分析器:
创建:
PUT /my_index
{
"settings": {
"analysis": {
"char_filter": {
"&_to_and": {
"type": "mapping",
"mappings": [ "&=> and "]
}
},
"tokenizer": { ... custom tokenizers ... },
"filter": {
"my_stopwords": {
"type": "stop",
"stopwords": [ "the", "a" ]
}
},
"analyzer": {
"my_analyzer": {
"type": "custom",
"char_filter": [ "html_strip", "&_to_and" ],
"tokenizer": "standard",
"filter": [ "lowercase", "my_stopwords" ]
} }
}
}
}
使用自定义分析器:
PUT /my_index/_mapping/my_type
{
"properties": {
"title": {
"type": "string", 把这个分析器应用在一个 string 字段上
"analyzer": "my_analyzer"
}
}
}
常用数据类型:
字符串:
text:可分词,不可参与聚合
keyword:不可分词,数据会作为完整字段进行匹配,可以参与聚合
整数:
byte, short, integer, long
浮点数:
double、float、half_float、scaled_double
布尔型:
boolean
字节类型:
binary
日期:
date
elasticsearch可以对日期格式化为字符串存储,但是建议我们存储为毫秒值,存储为long,节省空间。
数组:
object:
json对象
nested:
json列表
映射:
概述:
索引创建之后,等于有了关系型数据库中的database。
Elasticsearch7.x取消了索引type类型的设置,不允许指定类型,默认为_doc,但字段仍然是有的,我们需要设置字段的约束信息,叫做字段映射(mapping)
字段的约束包括但不限于:
字段的数据类型
是否要存储
是否要索引
分词器
基本操作:
查看映射:
GET /索引名称/_mapping
更新映射:
PUT /索引库名/_mapping 注意:修改映射只能是增加字段操作,做其它更改只能删除索引 重新建立映射 。
{
"properties": {
"字段名": {
"type": "类型",
"index": true,
"store": true,
"analyzer": "分词器"
}
}
}
新增映射:
PUT /索引库名/_mapping
{
"properties": {
"字段名": {
"type": "数据类型",
"index": true,
"store": false,
"analyzer": "分词器"
}
}
}
动态推测数据类型:
方法一:
提前创建索引指定mapping映射规则,开启数值类型检测
不适合动态日志创建索引的logstash
PUT lagou-order
{
"mappings": {
"numeric_detection": true
}
}
方法二:
动态映射规则模板,自动匹配新建的索引
PUT /_template/my_logs
{
"template" : "logstash-*",
"settings" : {
"index.number_of_shards": 5,
"number_of_replicas": 0
},
"mappings" : {
"dynamic_templates" : [{
"string_fields" : {
"match" : "*Money",
"match_mapping_type" : "string",
"mapping" : {
"type" : "double", "doc_values" : true
}
}
}]
}}
文档:
概述:
文档,即索引库中的数据,会根据规则创建索引,将来用于搜索。可以类比做数据库中的一行数据。
常见操作:
写入:
POST /索引名称/_doc/{id}
{
"first_name" : "John",
"last_name" : "Smith",
"age" : 25,
"about" : "I love to go rock climbing",
"interests": [ "sports", "music" ]
}
读取:
GET /索引名称/_doc/{id} 即可拿到文档的一些元数据,以及 _source 属性,内容是 John Smith 雇员的原始 JSON 文档:
_index
文档在哪存放
_type
文档表示的对象类别
_id
文档唯一标识
_version
document的版本号,Elasticsearch利用_version (版本号)的方式来确保应用中相互冲突的变更不会导致数据丢失。
需要修改数据时,需要指定想要修改文档的version号,如果该版本不是当前版本号,请求将会失败
_seq_no
严格递增的顺序号,每个文档一个,Shard级别严格递增,保证后写入的Doc seq_no大于先写入的Doc的seq_no
_primary_term
primary_term也和seq_no一样是一个整数,每当Primary Shard发生重新分配时,比如重启,Primary选举等,_primary_term会递增1
found
true/false,是否查找到文档
_source: {} 文档内容
GET部分字段:
GET /website/blog/123?_source=title,text
GET不包含元信息:
GET /website/blog/123/_source
全部更新:
只需再次PUT请求即可,修改必须指定id。id对应文档不存在,则新增。
Elasticsearch执行更新操作的时候,Elasticsearch首先将旧的文档标记为删除状态,然后添加新的文档,旧的文档不会立即消失,但是你也无法访问,Elasticsearch会在你继续添加更多数据的时候在后台清理已经标记为删除状态的文档。
部分更新:
POST /索引名/_update/{id}
删除文档:
根据id:
DELETE /索引名/_doc/{id}
根据条件:
POST /索引库名/_delete_by_query
{
"query": {
"match": {
"字段名": "搜索关键字"
}
}
}
批量操作:
批量查询:
GET /_mget
{
"docs" : [
{
"_index" : "website",
"_type" : "blog",
"_id" : 2
},
{
"_index" : "website",
"_type" : "pageviews",
"_id" : 1,
"_source": "views"
}
]
}
GET /索引字段/_mget
{
"docs" : [
{
"_id" : 2
},
{
"_id" : 1
}
]
}
批量增删改:
概述:
Bulk 操作解释将文档的增删改查一些列操作,通过一次请求全都做完。减少网络传输次数。
数量取决于硬件,文档大小以及复杂性,索引以及搜索的负载。 一般建议是1000-5000个文档,大小建议是5-15MB,默认不能超过100M,可以在es的配置文件(ES的config下的elasticsearch.yml)中配置。
语法:
POST /_bulk
{ "delete": { "_index": "website", "_type": "blog", "_id": "123" }} # delete 动作不能有请求体,它后面跟着的是另外一个操作。
{ "create": { "_index": "website", "_type": "blog", "_id": "123" }}
{ "title": "My first blog post" }
{ "index": { "_index": "website", "_type": "blog" }}
{ "title": "My second blog post" }
{ "update": { "_index": "website", "_type": "blog", "_id": "123", "_retry_on_conflict" : 3} }
{ "doc" : {"title" : "My updated blog post"} }
格式:每个json不能换行。相邻json必须换行。
隔离:每个操作互不影响。操作失败的行会返回其失败信息。
原子性:bulk 请求不是原子的: 不能用它来实现事务控制。
轻量查询(query string)
概述:
没有请求body,而是将参数放在请求path上。
精确匹配:
+ 前缀表示必须与查询条件匹配。
- 前缀表示一定不与查询条件匹配。
没有表明时,表示可选,匹配越多,相关性越大
注:+ - :的URL编码复杂
缺点:
查询字符串在做了适当的编码后,可读性很差:
查询字符串搜索允许任何用户在索引的任意字段上执行可能较慢且重量级的查询,这可能会暴露隐私信息,甚至将集群拖垮。
查询表达式:
概述:
Elasticsearch提供了基于JSON的完整查询DSL(Domain Specific Language 特定域的语言)来定义查询。将查询DSL视为查询的AST(抽象语法树),它由两种子句组成:
叶子查询子句 叶子查询子句 在特定域中寻找特定的值,如 match,term或 range查询。
复合查询子句 复合查询子句包装其他叶子查询或复合查询,并用于以逻辑方式组合多个查询(例如 bool或dis_max查询),或更改其行为(例如 constant_score查询)。
语法:
POST /索引库名/_search
{
"query":{
"查询类型":{
"查询条件":"查询条件值"
}
}
}
查询类型:
例如: match_all匹配所有文档, match , term , range 等等
查询条件:
查询条件会根据类型的不同,写法也有差异。
结果示例:
{
"took": 6, 执行整个搜索请求耗费了多少毫秒。
"timed_out": false, 查询是否超时
"_shards": { ... }, 告诉我们在查询中参与分片的总数、成功和失败个数
"hits": { 搜索结果总览对象
"total": 3, 搜索到的总条数
"max_score": 1, 所有结果中文档得分的最高分
"hits": [ 搜索结果的文档对象数组,每个元素是一条搜索到的文档信息
_index:索引库
_type:文档类型
_id:文档id
_score:文档得分
_source:文档的源数据
]
}
}
查询所有:
{
"query":{
"match_all": {}
}
}
全文搜索:
匹配搜索
or关系
{
"query":{
"match":{
"字段":"xxx"
}
}
}
全文查询的标准查询,需要指定字段名,输入文本会进行分词,然后进行查询,多个词条之间是or的关系
比如"hello elasticsearch"会进行拆分为hello和elasticsearch,然后匹配,如果字段中包含hello或者elasticsearch,或者都包含的结果都会被查询出来,也就是说match是一个部分匹配的模糊查询。查询条件相对来说比较宽松
and关系
{
"query":{
"match":{
"字段": {"query": "xxx", "operator": "and" }
}
}
}
精确搜索
match_phrase的分词结果必须在text字段分词中都包含,而且顺序必须相同,而且必须都是连续的
{
"query":{
"match_phrase":{
"字段":"xxx"
}
}
}
query_string查询
该查询与match类似,但是match需要指定字段名,query_string是在所有字段中搜索,范围更广泛。
{
"query": {
"query_string" : {
"query" : "手机 OR/AND 小米", "大米~1"模糊查询
"default_field" : "title",
"fields": ["title","price"] 可以指定在哪些字段上进行匹配
}
}
}
多字段匹配搜索
需要在多个字段上进行文本搜索,可用multi_match 。multi_match在 match的基础上支持对多个字段进行文本查询。
{
"multi_match": {
"query": "full text search",
"fields": [ "title", "body" ]
}
}
词条级搜索
概述:
可以使用term-level queries根据结构化数据中的精确值查找文档。结构化数据的值包括日期范围、IP地址、价格或产品ID。
与全文查询不同,term-level queries不分析搜索词。相反,词条与存储在字段级别中的术语完全匹配。
词条搜索:
{
"query": {
"term" : { "字段" : "搜索词"/[搜索词列表] } 可以指定多值进行匹配。如果这个字段包含了指定值中的任何一个值,那么这个文档满足条件
}
}
范围查询:
{
"range": {
"age": {
"gte": 20,
"lt": 30
}
}
}
exists查询和missing搜索
exists 查询和 missing 查询被用于查找那些指定字段中有值 (exists) 或无值 (missing) 的文档。
这与SQL中的 IS_NULL (missing) 和 NOT IS_NULL (exists) 在本质上具有共性。
这些查询经常用于某个字段有值的情况和某个字段缺值的情况。
{
"query": {
"exists/missing" : { "field" : "字段" }
}
}
词项前缀搜索
{
"query": {
"prefix" : { "字段" : "前缀" }
}
}
正则搜索
regexp允许使用正则表达式进行term查询.注意regexp如果使用不正确,会给服务器带来很严重的性能压力。
比如.*开头的查询,将会匹配所有的倒排索引中的关键字,这几乎相当于全表扫描,会很慢。因此如果可以的话,最好在使用正则前,加上匹配的前缀。
{
"query": {
"regexp":{
"字段": "前缀.*"
}
}
}
模糊查询
{
"query": {
"fuzzy" : {
"name" : {
"value": "so"
"fuzziness": 2
}
}
}
}
ids搜索
{
"query": {
"ids" : {
"values" : ["1", "3"]
}
}
}
复合搜索:
布尔搜索(bool query)
bool 查询用bool操作来组合多个查询子句为一个查询。 可用的关键字:
must:必须满足
filter:必须满足,对集合包含/排除的简单检查,计算速度非常快,不参与、不影响评分
should:或
must_not:必须不满足,在filter上下文中执行,不参与、不影响评分
{
"query": {
"bool": {
"filter": {"match": {}},
"must": {子查询}
}
}
}
过滤器查询:
概述:
Elasticsearch中的所有的查询都会触发相关度得分的计算。对于那些不需要相关度得分的场景下,Elasticsearch以过滤器的形式提供了另一种查询功能,过滤器在概念上类似于查询,但是它们有非常快的执行速度,执行速度快主要有以下两个原因:
过滤器不会计算相关度的得分,所以它们在计算上更快一些。
过滤器可以被缓存到内存中,这使得在重复的搜索查询上,其要比相应的查询快出许多。
如果相关度是不重要的,使用过滤器,否则使用查询。
示例:
{
"query": {
"bool": {
"filter": {"match": {}},
"must": {"must": {"must_all": {} }} 经常用于只需要执行一个 filter 而没有其它查询,让所有文档应用一个恒定分数(默认为 1 )
}
}
}
排序:
概述:
相关性得分 由一个浮点数进行表示,并在搜索结果中通过 _score 参数返回, 默认排序是 _score 降序
查询示例:
GET /_search
{
"query" : {
"bool" : {
"filter" : { "term" : { "user_id" : 1 }}
}
},
"sort": { "_score/其他字段": { "order": "desc" }}
}
多个条件时,先后次序
多值字段的排序
可以通过使用 min 、 max 、 avg 或是 sum 排序模式 。
"sort": {
"dates": {
"order": "asc",
"mode": "min"
}
}
字符串排序与多字段
用两种方式对同一个字符串进行索引,这将在文档中包括两个字段: analyzed 用于搜索, not_analyzed 用于排序
"tweet": {
"type": "string",
"analyzer": "english",
"fields": {
"raw": {
"type": "string",
"index": "not_analyzed" # 用于排序
}
}
}
分页:
语法:
{
"query": {
"match_all": {}
},
"sort": [
{"price": {"order": "desc"}}
],
"size": 2,
"from": 0
}
参数说明:
from 显示应该跳过的初始结果数量,默认是 0
size 显示应该返回的结果数量,默认是 10
GET /_search?size=5&from=10
注:和mysql一样,深度分页如果深度过深,每个分片前面的结果也要产生相同数量,再丢弃掉N*from个结果,速度会下降
高亮搜索:
增加一个新的 highlight 参数:
{
"query" : {
"match_phrase" : {
"about" : "rock climbing"
}
},
"highlight": {
"pre_tags": "<font color='pink'>",
"post_tags": "</font>",
"fields" : {
"about" : {}
}
}
}
返回结果多了一个叫做 highlight 的部分。这个部分包含了 about 属性匹配的文本片段,并以 HTML 标签 <em></em> 封装
聚合分析:
概述:
聚合分析是数据库中重要的功能特性,完成对一个查询的数据集中数据的聚合计算,如:找出某字段(或计算表达式的结果)的最大值、最小值,计算和、平均值等。
Elasticsearch作为搜索引擎兼数据库,同样提供了强大的聚合分析能力。
对一个数据集求最大、最小、和、平均值等指标的聚合,在ES中称为指标聚合 metric 而关系型数据库中除了有聚合函数外,还可以对查询出的数据进行分组group by,再在组上进行指标聚合。
在 ES 中group by 称为分桶,桶聚合bucketing
常用聚合函数:
max/min
value_count统计某个字段有值的数量
cardinality值去重计数 基数
stats 统计 count max min avg sum 5个值
extended_stats高级统计,比stats多4个统计结果: 平方和、方差、标准差、平均值加/减两个标准差的区间
Percentiles 占比百分位对应的值统计
Percentiles rank 统计值小于等于指定值的文档占比
示例:
POST /book/_search
{
"size": 0,
"aggs": {
"max_price": {
"max": {
"field": "price"
}
}
}
}
桶聚合:
执行的是对文档分组的操作(与sql中的group by类似),把满足相关特性的文档分到一个桶里,即桶分,输出结果往往是一个个包含多个文档的桶(一个桶就是一个group)
{
"size": 0,
"aggs": {
"group_by_price": {
"range": {
"field": "price",
"ranges": [
{
"from": 0,
"to": 200
},
{
"from": 200,
"to": 400
}
]
},
"aggs": {
"average_price": {
"avg": {
"field": "price"
}
},
"having": {
"bucket_selector": {
"buckets_path": {
"avg_price": "average_price"
},
"script": {
"source": "params.avg_price >= 200 "
}
}
}
}
}
}
}
影响搜索过程的几个查询参数:
1.preference 允许 用来控制由哪些分片或节点来处理搜索请求。
(1.接受像 _primary, _primary_first, _local, _only_node:xyz, _prefer_node:xyz, 和 _shards:2,3
(2.最有用的值是某些随机字符串,它可以避免 bouncing results 问题。
(3.两个文档有同样值的时间戳字段,搜索结果用 timestamp 字段来排序。在主分片和副分片处理的顺序结果不一样
即每次用户刷新页面,搜索结果表现是不同的顺序。
2.timeout 告诉 分片允许处理数据的最大时间。
(1.如果没有足够的时间处理所有数据,这个分片的结果可以是部分的,甚至是空数据。
(2.超时检查是基于每文档做的。 但是某些查询类型有大量的工作在文档评估之前需要完成。
这种 "setup" 阶段并不考虑超时设置,所以太长的建立时间会导致超过超时时间的整体延迟。
(3.因为时间检查是基于每个文档的,一次长时间查询在单个文档上执行并且在下个文档被评估之前不会超时。
这也意味着差的脚本(比如带无限循环的脚本)将会永远执行下去。
3.路由routing
自定义分片ID
通过指定几个 routing 值来限定只搜索几个相关的分片:GET /_search?routing=user_1,user2
4.搜索类型
缺省的搜索类型是 query_then_fetch 。
另外一种search_type 为 dfs_query_then_fetch 来改善相关性精确度,有预查询阶段
概述:
游标查询允许先做查询初始化,然后再批量地拉取结果。 这有点儿像传统数据库中的 cursor 。
游标查询会取某个时间点的快照数据。 查询初始化之后索引上的任何变化会被它忽略。
游标查询用字段 _doc 来排序。 这个指令让 Elasticsearch 仅仅从还有结果的分片返回下一批结果。
查询:
启用游标查询可以通过在查询的时候设置参数 scroll 的值为我们期望的游标查询的过期时间。
游标查询的过期时间会在每次做查询的时候刷新,所以这个时间只需要足够处理当前批的结果就可以了,而不是处理查询结果的所有文档的所需时间。
GET /old_index/_search?scroll=1m
{
"query": { "match_all": {}},
"sort" : ["_doc"],
"size": 1000
}
这个查询的返回结果包括一个字段 _scroll_id`, 传递字段 `_scroll_id 到 _search/scroll 查询接口获取下一批结果
GET /_search/scroll
{
"scroll": "1m",
"scroll_id" : "cXVlcnlUaGVuRmV0Y2g7NTsxMDk5NDpkUmpiR2FjOFNhNnlCM1ZDMWpWYnRROzEwOTk1OmRSamJHYWM4U2E2eUIzVkMxalZidFE7MTA5OTM6ZFJqYkdhYzhTYTZ5QjNWQzFqVmJ0UTsxMTE5MDpBVUtwN2lxc1FLZV8yRGVjWlI2QUVBOzEwOTk2OmRSamJHYWM4U2E2eUIzVkMxalZidFE7MDs="
}
这个游标查询返回的下一批结果。当没有更多的结果返回的时候,我们就处理完所有匹配的文档了。
倒排索引:
概述:
Elasticsearch 使用一种称为倒排索引的结构,它适用于快速的全文搜索。一个倒排索引由文档中所有不重复词的列表构成,对于其中每个词,有一个包含它的文档列表。
创建流程:
首先要将每个文档内容拆分成单独的词,创建一个包含所有不重复词条的排序列表
然后列出每个词条出现在哪个文档。
结构:
倒排索引由两部分组成,所有独立的词列表称为索引,词对应的一系列表统称为倒排表。
索引表,叫Terms Dictionary ,是由于一系列的Term组成的。
倒排表,称Postings List ,即是由所有的Term对应的Postings组成的。
Term doc1 doc2
xxx x
xxx x
存储结构:
排序列表Array/List 使用二分法查找,不平衡
HashMap/TreeMap 性能高,内存消耗大,几乎是原始数据的三倍
Skip List 跳跃表,可快速查找词语,在lucene、redis、Hbase等均有实现,模糊查询支持很差
Trie 适合英文词典,如果系统中存在大量字符串且这些字符串基本没有公共前缀,则相应的trie树将非常消耗内存
Double Array Trie 适合做中文词典,内存占用小,很多分词工具均采用此种算法
Finite State Transducers (FST) 一种有限状态转移机,Lucene 4有开源实现,并大量使用(Trie + 有限状态机)
索引文件分析:
Lucene将索引文件拆分为了多个文件,此处仅讨论倒排索引部分。
tip: Lucene把用于存储Term的索引文件叫Terms Index,它的后缀是.tip ;
doc: 把Postings信息分别存储在.doc ,分别记录Postings的DocId信息和Term的词频信息。
tim: Terms Dictionary的文件后缀称为.tim ,它是Term与Postings的关系纽带,存储了Term和其对应的Postings文件指针。
Term Dictionary:把Term按字典序排列,然后用二分法查找Term (存在磁盘)
在Lucene, Terms Dictionary 被存储在.tim文件上。当一个Segment的文档数量越来越多的同时Dictionary的词汇也会越来越多,那查询效率必然也会慢慢变低。
如果有一个很好的结构也为Dictionary建构一个索引,将Dictionary的索引进一步压缩,这就是后来的Terms Index(.tip)。
Term Index:是Term Dictionary的索引,存Term的前缀,和与该前缀对应的Term Dictionary中的第一个Term的block的位置,找到这个第一个Term后会再往后顺序查找,直到找到目标Term。(存在内存)
总结:
通过Terms Index(.tip)可以快速地在Terms Dictionary(.tim)中找到你的想要的Term,以及它对应的Postings文件指针(指向doc)。
Terms Index实际上一个或者多个FST 组成的,Segment上每个字段都有自己的一个FST(FSTIndex)记录在.tip 上。(FST类似一种TRIE树)
写入和近实时搜索原理:
存储结构:
image-20220330193122938
概述:
Elasticsearch 存储的基本单元是shard, ES中一个Index可能分为多个shard, 事实上每个shard都是一个Lucence的Index,并且每个 Lucence Index 由多个 Segment 组成, 每个 Segment 事实上是一些倒排索引的集合, 每次创建一个新的Document , 都会归属于一个新的 Segment, 而不会去修改原来的 Segment 。
且每次的文档删除操作,会仅仅标记 Segment 中该文档为删除状态, 而不会真正的立马物理删除。
Lucene负责编写和维护Lucene索引文件,而Elasticsearch在Lucene之上编写与功能相关的元数据,例如字段映射,索引设置和其他集群元数据,用户和支持功能由Elasticsearch提供。
结构:
indices
uuid格式子目录A 一个分片
数字,标识shard编号
index es的数据目录
segments_N存储lucene数据文件的元信息,记录所有segment的元数据信息。
.tip 倒排索引文件
.tim 倒排索引的元数据信息
.dvd/dvm lucene的docvalues文件,即列式存储,用作聚合和排序
.tvx/.tvd/.tvf 保存索引子段的矢量信息。对term进行高亮、计算文本相关性中使用。
.liv 记录了segment中删除的doc
_state 当前shard的信息,比如主副分片
translog
_state存储了索引的状态,包括setting、mapping等文件
uuid格式子目录B
node.lock 确保每次只有一个es应用读写该目录
_state全局状态文件,包含集群全局元数据
预写入日志:
文档会被首先写入内存 buffer 和 translog 文件。每个 shard 都对应一个 translog 文件
translog:
概述:
提供所有还没有被刷到磁盘的操作的一个持久化记录。当 Elasticsearch 启动的时候, 它会从磁盘中使用最后一个提交点去恢复已知的段,并且会重放 translog 中所有在最后一次提交后发生的变更操作。
translog也被用来提供实时CRUD。当你试着通过 ID 查询、更新、删除一个文档,它会在尝试从相应的段中检索之前, 首先检查 translog 任何最近的变更。所以可以实时地获取到文档的最新版本。
在刷新(flush)之后,段被全量提交,并且事务日志被清空。
数据一致性:
问题:
如果数据被写入内存buffer后,还没写入到translog前,如果是异步fsync,则机器重启会导致数据丢失。
解决:
设置每次写请求后都执行一个 fsync
"index.translog.durability": "request"
异步模式:
PUT /my_index/_settings {
"index.translog.durability": "async",
"index.translog.sync_interval": "5s"
}
refresh:
架构:
image-20220330193808136
概述:
在Elasticsearch中, _refresh 操作默认每秒执行一次,
意味着将内存 buffer 的数据(暂不能查询)写入到一个新的Segment(Linux 的高速缓存File system cache)中。
这个时候索引变成了可被检索的。
写入新Segment后 会清空内存buffer。
api:
刷新(Refresh)所有的索引
POST /_refresh
只刷新(Refresh)blogs索引
POST /my_blogs/_refresh
只刷新文档
PUT /my_blogs/_doc/1?refresh
设置刷新频率:
PUT /my_logs
{
"settings": { "refresh_interval": "30s" } -1表示关闭
}
flush:
架构:
image-20220330193729420
概述:
Flush 操作意味着:
将内存 buffer 的数据全都写入新的 Segments 中。
清空内存buffer。
一个提交点被写入硬盘。
并将文件系统缓存 通过fsync 将所有的 Segments 全部刷盘,
并且清空translog日志的过程。
默认30分钟。
api:
手动刷新索引
POST /blogs/_flush
刷新所有的索引并且等待所有刷新在返回前完成
POST /_flush?wait_for_ongoing
注意事项:
一般来说,自动刷新就足够了。
在重启节点或关闭索引之前执行 flush有益于你的索引。当 Elasticsearch 尝试恢复或重新打开一个索引,它需要重放 translog 中所有的操作,所以如果日志越短,恢复越快。
写入流程:
1.客户端向master发送写入请求,该节点作为协调节点。
2.确定文档分片位置,协调节点请求转到节点的主分片;
向ES中添加一个文档对象,由于ES是分布式集群并且底层设计为一个索引有众多shard(分片),所以添加文档时需要确定该文档属于哪个分片,确定的规则为:
shard = hash(routing) % number_of_primary_shards
routing是一个可变值,默认是文档的_id,也可以设置成一个自定义的值。
routing通过hash函数生成一个数字,然后这个数字再除以number_of_primary_shards(主分片的数量)后得到余数。
这个分布在0到number_of_primary_shards - 1之间的余数,就是我们所寻求的文档所在分片的位置。
3.在节点3上执行写入请求,成功之后,节点3根据副本数将请求并行转到副本分片对应节点,一旦副本分片执行完成,都向节点3报告成功,节点3将向协调节点报告成功,协调节点再向客户端报告成功。
注意事项:
1.数据的一致性
版本5.0前可以调整consistency参数
all
quorum
1
如果没有足够数量则会等待直到timeout。
number_of_replicas 指的是在索引设置中的设定副本分片数
缺点:
consistency检查是在Put之前做的。然而,虽然检查的时候,shard满足quorum,但是真正从primary shard写到replica之前,仍会出现shard挂掉,但Update Api会返回succeed。因此,这个检查并不能保证replica成功写入,甚至这个primary shard是否能成功写入也未必能保证。
5.0之后使用wait_for_active_shards参数
PUT /test_index/_doc/1?wait_for_active_shards=2&timeout=10s
{
"name":"xiao mi"
}
2.多请求时,mget或者bulk 会为每个分片构建多文档获取请求
读取流程:
1.客户端发送search请求到协调节点。
2.协调节点将查询请求转发到索引的每个主分片或副分片中。
协调结点在每次请求的时候都会通过轮询所有的副本分片来达到负载均衡。
3.每个分片在本地执行查询,并使用本地的Term/Docuemnt Frequency信息进行打分,添加结果到大小为from+size的本地优先队列中。
4.每个分片返回各自优先队列中所有文档的ID和排序值给协调节点,协调节点合并这些值到自己的优先队列中,产生一个全局排序后的列表。返回给客户端。
分片所在节点在返回文档数据时,处理有可能出现的 _source字段和高亮参数
段合并:
问题:
默认的自动刷新流程每秒会创建一个新的段 ,这样会导致短时间内的段数量暴增。而段数目太多会带来较大的麻烦。
每一个段都会消耗文件句柄、内存和cpu运行周期。更重要的是,每个搜索请求都必须轮流检查每个段;所以段越多,搜索也就越慢。
解决:
Elasticsearch通过在后台进行段合并来解决这个问题。小的段被合并到大的段,然后这些大的段再被合并到更大的段。
段合并的时候会将那些旧的已删除文档 从文件系统中清除。 被删除的文档(或被更新文档的旧版本)不会被拷贝到新的大段中。
流程:
1.当索引的时候,刷新(refresh)操作会创建新的段并将段打开以供搜索使用。
2.合并进程选择一小部分大小相似的段,并且在后台将它们合并到更大的段中。这并不会中断索引和搜索。
3.合并完成后:
- 新的段被刷新(flush)到了磁盘,写入一个包含新段且排除旧的和较小的段的新提交点。
- 新的段被打开用来搜索。
- 老的段被删除。
配置:
合并大的段需要消耗大量的I/O和CPU资源,如果任其发展会影响搜索性能。默认情况下会对合并流程进行资源限制,所以搜索仍然 有足够的资源很好地执行。
归并的字节上限:
默认情况下,归并线程的限速配置indices.store.throttle.max_bytes_per_sec 是 20MB。
PUT /_cluster/settings
}
归并线程的数目
推荐设置为cpu核心数的一半。 如果觉得自己磁盘性能跟不上,可以降低配置,免得IO情况瓶颈。
index.merge.scheduler.max_thread_count
归并策略 policy
归并线程是按照一定的运行策略来挑选 segment 进行归并的。主要有以下几条:
index.merge.policy.floor_segment 默认 2MB,小于这个大小的 segment,优先被归并。
index.merge.policy.max_merge_at_once 默认一次最多归并 10 个 segment
index.merge.policy.max_merge_at_once_explicit 默认 optimize 时一次最多归并 30 个 segment。
index.merge.policy.max_merged_segment 默认 5 GB,大于这个大小的 segment,不用参与归并。optimize 除外。
强制合并API:
语法:
POST /logstash-2014-10/_optimize?max_num_segments=1
概述:
将一个分片强制合并到 max_num_segments 参数指定大小的段数目。 这样做的意图是减少段的数量(通常减少到一个),来提升搜索性能。
场合:
不应该 被用在一个动态索引————一个正在被活跃更新的索引。后台合并流程已经可以很好地完成工作。 optimizing 会阻碍这个进程。
用在比如每天、每周、每月的日志被存储在一个索引中等只读老索引的场合中
注意:
使用 optimize API 触发段合并的操作一点也不会受到任何资源上的限制。
想要对索引执行 `optimize`,需要先使用分片分配(查看 迁移旧索引)把索引移到一个安全的节点,再执行。
事务:
概述:
es采用乐观锁,加version字段的方式来解决并发处理的问题
示例:
PUT /test_index/_doc/4?if_seq_no=0&if_primary_term=1
{
"test_field": "client2 changed"
}
DocValues机制:
背景:
ElasticSearch 之所以搜索这么快速,归功于它的 倒排索引的设计,然而它也不是万能的,倒排索引的检索性能是非常快的,但是在字段值排序时却不是理想的结构。
如果想要排序的话每一个 doc都去获取一次文档内容岂不非常耗时? DocValues 的出现使得这个问题迎刃而解。
概述:
字段的 doc_values 属性有两个值, true、false。默认为 true ,即开启。
当 doc_values 为 fasle 时,无法基于该字段排序、聚合、在脚本中访问字段值。
当 doc_values 为 true 时,ES 会增加一个相应的正排索引,这增加的磁盘占用,也会导致索引数据速度慢一些。
示例:
PUT /person
{
"mappings" : {
"properties" : {
"name" : {
"type" : "keyword",
"doc_values": true
},
"age" : {
"type" : "integer",
"doc_values": false
}
}
}
}
原理:
Docvalues 通过转置倒排索引和正排索引两者间的关系来解决这个问题。倒排索引将词项映射到包含它们的文档,Docvalues 将文档映射到它们包含的词项。
当数据被转置之后,想要收集到每个文档行,获取所有的词项就非常简单了。所以搜索使用倒排索引查找文档,聚合操作收集和聚合 DocValues 里的数据,这就是 ElasticSearch 。
Doc_1 | brown, dog, fox, jumped, lazy, over, quick, the
Doc_2 | brown, dogs, foxes, in, lazy, leap, over, quick, summer
存储:
DocValues 是在索引时与倒排索引同时生成。也就是说 DocValues 和 倒排索引一样,基于 Segement 生成并且是不可变的。
同时 DocValues 和 倒排索引一样序列化到磁盘,这样对性能和扩展性有很大帮助。
DocValues 通过序列化把数据结构持久化到磁盘,我们可以充分利用操作系统的内存,而不是 JVM 的 Heap 。
当workingset 远小于系统的可用内存,系统会自动将 DocValues 保存在内存中,使得其读写十分高速; 不过,当其远大于可用内存时,操作系统会自动把 DocValues写入磁盘。
压缩:
从广义来说, DocValues 本质上是一个序列化的 列式存储,这个结构非常适用于聚合、排序、脚本等操作。而且,这种存储方式也非常便于压缩,特别是数字类型。这样可以减少磁盘空间并且提高访问速度。
会按依次检测以下压缩模式:
如果所有的数值各不相同(或缺失),设置一个标记并记录这些值
如果这些值小于 256,将使用一个简单的编码表
如果这些值大于 256,检测是否存在一个最大公约数
如果没有存在最大公约数,从最小的数值开始,统一计算偏移量进行编码
当然如果存储 String 类型,其一样可以通过顺序表对 String 类型进行数字编码,然后再把数字类型构建DocValues 。
配置:
DocValues 默认对所有字段启用,除了 analyzed strings 。也就是说所有的数字、地理坐标、日期、IP 和不分析( not_analyzed )字符类型都会默认开启。
analyzed strings 暂时还不能使用 DocValues ,是因为经过分析以后的文本会生成大量的 Token ,这样非常影响性能。
虽然 DocValues 非常好用,但是如果你存储的数据确实不需要这个特性,就不如禁用他,这样不仅节省磁盘空间,也许会提升索引的速度。
要禁用 DocValues ,在字段的映射(mapping)设置 doc_values:false 即可。
DELETE /my_index
PUT my_index
{
"mappings": {
"properties": {
"session_id": {
"type": "keyword",
"doc_values": false
}
}
}
}
调优:
设计阶段:
1.根据业务增量需求,采取基于日期模板创建索引,通过roll over api滚动索引
2.使用别名进行索引管理
3.定时每天对索引做force_merge操作,释放空间
4.采取冷热分离机制,热数据存储到SSD,提高 检索效率,冷数据定期shrink以缩减空间
5.采取curator进行索引的生命周期管理
6.仅针对需要分词的字段,合理设置分词器
7.mapping阶段充分结合各个字段的属性,是否需要检索,是否需要存储等
写入优化:
1.写入前副本数设置为0
2.写入前关闭refresh_interval设置为-1,禁用刷新机制
3.写入采取bulk批量
4.写入后恢复副本数和刷新间隔
5.尽量使用自动生成的id
查询调优:
禁用wildcard
禁用批量terms
充分利用倒排索引机制,能keyword尽量keyword
数据量大可以先基于时间确定索引
设置设立的路由机制