跳到主要内容

Elasticsearch

Tips

  • 文档不会被删除,只会被标记删除
  • 索引在内存中构建, 然后刷到磁盘
  • 一个索引由多个段(segment)组成,搜索会在所有段执行,最终合并结果
  • 段会定期合并
  • 每个段都会缓存 Field 和 Filter
  • Elasticsearch 不支持事务
  • 每个分片为一个 Lucene 索引
  • 近实时程度与性能有关,默认为 1 秒
  • Lucene 自身是增量索引
  • 每个段自身为一个小索引
  • 1.4 之前默认使用 UUID, 之后使用 Flask ID
  • 无法通过查询来删除
  • 无法通过查询来更新

Best practice

  • 只索引不更新
  • 日志索引分日期
  • 定义好 Mapping
  • 挂载多个磁盘使用多个 path.data
    • 提升索引速度
    • 减少数据丢失
  • 如果允许, 增大刷新间隔
  • 最大使用 32G 内存, 利用 JVM 压缩指针
    • 测试最大内存量
java -Xmx32600m -XX:+PrintFlagsFinal 2> /dev/null | grep UseCompressedOops
bool UseCompressedOops := true
  • 1.7 32600m, 1.8 32766m
  • 关闭内存 swap
swapoff -a
# 或者
sysctl vm.swappiness=1
# 在 Elasticsearch 配置中添加 bootstrap.mlockall: true

Reference

常用操作

批量导入

# 导入前关闭刷新
curl -XPUT localhost:9200/test/_settings -d '{"index" : {"refresh_interval" : "-1"} }'
# 导入前取消副本
curl -XPUT 'localhost:9200/test/_settings' -d '{"index" : {"number_of_replicas" : 0}}'
# 导入完成强制合并
curl -XPOST 'localhost:9200/test/_forcemerge?max_num_segments=5'

更改解析器

# 更改前需要先关闭所有
curl -XPOST 'localhost:9200/test/_close'

curl -XPUT 'localhost:9200/test/_settings' -d '{
"analysis" : {
"analyzer":{
"content":{
"type":"custom",
"tokenizer":"whitespace"
}
}
}
}'
# 完成后打开索引
curl -XPOST 'localhost:9200/test/_open'

诊断

# 查看 jvm 内存状态
curl localhost:9200/_nodes/stats | jq ".nodes[].jvm.mem"
# 单个节点内存状态
curl localhost:9210/_nodes/stats | jq "[.nodes[]]|.[1].jvm.mem"

# 快速信息查看端口, ?help 显示列的含义
curl localhost:9200/_cat
# 查看恢复进度
curl localhost:9200/_cat/recovery

模块

Elasticsearch 的所有功能都是由各个模块组成的.

模块配置分为静态配置和动态配置,静态配置需要在相关节点的 elasticsearch.yml 中指定,动态节点可通过相关接口 进行更改.

模块责任
Cluster控制如何为节点申请分片.
Discovery节点在集群中如何相互发现
Gateway集群中需要多少节点才能开始恢复
HTTP控制 HTTP REST 接口
Indices全局索引相关配置
Network控制默认网络设置
Node client加入到集群中的 Java 客户端,不会持有数据和承担主节点的责任.
Plugins使用插件来扩展 Elasticsearch
Scripting定制化的 Lucene Expressions, Groovy, Python, 和 Javascript 脚本.
Snapshot/Restore备份和恢复数据
Thread poolsElasticsearch 中的线程池信息
Transport传输层控制,节点内部通信
Tribe nodesA tribe node joins one or more clusters and acts as a federated client across them.

Query DSL

ES 的所有接口都是通过 RESTful 接口暴露的,因此所有对 ES 的数据操作均可以通过 REST Api 完成.所有的接口也都遵循 HTTP 请求方法语义.

ES 提供了一套通过 JSON 表示的查询接口.查询语句主要包含

叶查询语句/Leaf query clauses
用于数据的指定字段,例如字段匹配,字段范围等查询.
组合查询语句/Compound query clauses
使用逻辑运算符来组合一个或多个叶查询或组合查询语句.

查询语句的行为可能会随使用的上下文而发生改变.

查询和过滤上下文

查询上下文
查询语句在查询上下文中用于回答 "该文档与查询语句有多么匹配" 的问题,在判断一个文档是否匹配的同时,也会计算出每个文档的匹配程度(分数 _score),用于表示该文档与查询的的匹配和相关程度.
过滤上下文
在过滤上下文中查询语句用于回答 "该文档是否匹配该查询语句",其结果只有匹配或不匹配.
GET _search
{
"query": {
"bool": {
"must": [
{ "match": { "title": "Search" }},
{ "match": { "content": "Elasticsearch" }}
],
"filter": [
{ "term": { "status": "published" }},
{ "range": { "publish_date": { "gte": "2015-01-01" }}}
]
}
}
}

查询地址

所有 Elasticsearch 的 API 均以 RESTful 的形式暴露, 基本查询的格式如下

[/索引名[,索引名...]][/类型名[,类型名...]]/_search

查询中的索引名和类型名是可以缺省的,并且也支持通配符,例如如下请求:

curl -XPOST localhost:9200/mq-*/meta,body/_search -d '{"query":{"match_all":{}}}'

Elasticsearch 的查询除了支持 GET 也支持使用 POST, 因为在现有的部分系统里, GET 不允许携带请求体.例如以下请求是相同的:

curl -XGET localhost:9200/_search -d '{"query":{"match_all":{}}}'
curl -XPOST localhost:9200/_search -d '{"query":{"match_all":{}}}'

除此之外, Elasticsearch 也支持针对单个文档的标示符进行查询,例如:

curl -XGET localhost:9200/mq-meta-local/meta/THIS_IS_MY_DOCUMENT_ID

查询类型

Elasticsearch 的底层搜索是 Lucene, 其提供的查询也主要是对 Lucene 查询的封装.

查询类型说明
match_all匹配所有文档,_score1.0
可通过 boost 更改分数值
match模糊近似匹配
multi_match与 match 相同,可指定多个字段
common_terms一个更特殊的查询,会更趋向于选择不常见的词
query_string支持 Lucene 查询语法,可使用 AND,OR,NOT 条件,可在单个查询中标示多个字段的搜索
需谨慎使用
simple_query_string一个更高效的 query_string 版本,适合普通用户使用
term匹配字段包含的词
terms匹配字段包含的词
range使用范围匹配字段的值(日期,数字,字符串等)
exists匹配非空字段
missing匹配不存在字段或为空的字段
prefix匹配字段值前缀
wildcard使用通配符来匹配字段的值
? 单个字符,* 多个字符
regexp使用正则表达式来匹配字段的值
fuzzy模糊匹配字段的值,使用莱文斯坦距离进行度量.
type匹配文档类型
ids查找文档 ID
constant_score该查询会包装另外一个查询,并在过滤上下文中执行.所有匹配的文档都会给予一个固定的分数(_score)
bool用于组合多个 must, should, must_not, 和 filter 查询.must 和 should 语句结果的分数会合并, must_not 和 filter 会在过滤上下文中执行.
dis_max生成多个子查询的并集,并为单个查询结果中分数最高的文档进行增强.
function_score使用指定的函数对查询结果的分数进行从新计算.
boosting可使用指定的查询语句来增强和削弱结果分数.
indices在指定索引中进行查询
and, or, not等同于 bool
filtered于 2.0.0-beta1 遗弃,使用 bool 查询替代.
limit限制每个分片查询的文档数
nested用于搜索 nested 类型的字段.
has_child,has_parent用于搜索父子关系的文档.
more_like_this查询类似于给定的字符串,文档或文档集合.
template模板查询
script脚本查询
geo_shape查找形状交叉的文档
geo_bounding_box查找点在该矩形内文档
geo_distance查找点距之内的文档
geo_distance_range查找点距在指定范围内的文档
geo_polygon查找点在该多边形内的文档
geohash_cell查找点与指定点 geohash 交叉的文档
span_term与 term 查询相同,用于与其他跨度查询结合使用
span_multi用于包装 term, range, prefix, wildcard, regexp, 或 fuzzy 查询
span_first匹配字段满足查询,并且在前 N 位置
span_near匹配满足多个查询,并且查询跨度在一定距离内,也可指定是否保持同样的顺序
span_or多个跨度查询的逻辑或
span_not排除一个跨度查询
span_containing指定一列跨度查询,但只返回跨度满足第二个跨度查询的结果
span_within指定一个跨度查询,返回跨度满足另外一列跨度查询的结果
全文查询
match,multi_match,common_terms,query_string,simple_query_string
查询字段必须是 analyzed
在查询前会对查询字符串应用字段的解析器(analyzer,search_analyzer)
词级查询
term,terms,range,exists,missing,prefix,wildcard,regexp,fuzzy,type,ids
不同于全文查询,在之前需要对查询字符串进行解析.词级查询用于查询字段确切的值.
组合查询
constant_score,bool,filter,dis_max,function_score,boosting,indices,and,or,not,filtered,limit
关联查询
nested,has_child,has_parent
GEO 查询
geo_shape,geo_bounding_box,geo_distance,geo_distance_range,geo_polygon,geohash_cell
跨度查询
span_term,span_multi,span_first,span_near,span_or,span_not,span_containing,span_within
除了 span_multi 以外,跨度查询不能与非跨度查询混合使用
https://lucidworks.com/blog/2009/07/18/the-spanquery/

Query String

Elasticsearch 可以利用 Lucene 中强大的 query_string 查询语法,其功能强大并且书写简单,也可以直接在 url 中进行查询. 例如:

# Query string syntax
curl -XGET 'localhost:9200/index/type/_search?q=name:wener AND age:>10'

如果将该查询转换为基于 DSL 的查询则是

# DSL syntax
curl -XPOST 'localhost:9200/index/type/_search' -d'
{
"query":{
"bool":{
"must":[
"term":{"name":"wener"},
"range":{"age":{"gt":10}}
]
}
}
}'

可见善用 query_string 查询是非常有用的.在 Kibana 的视图查询界面均是 query_string 查询.

总结

Elasticsearch 对于基础的查询提供了非常多的查询方式,但对于关系型的支持较少,官方建议是在应用端做数据关联,对关系型的处理有一定的复杂度.内置了两种对关系型数据的支持(Nested Object, Parent-Child),但支持都相对较为薄弱,难以处理复杂的场景,还不如使用通常的文档,这样对文档的控制更全面.

参考

高可用机制

Elasticsearch 的高可用是通过分片的多副本保障的.对于每个索引指定一定的分片数量和副本数量.每个文档都有一个唯一标示,通过该标示符来对文档进行分片路由.如果接受到请求的节点没有该分配,则会由该分片转发到该分片主分片的主节点.

ELasticsearch 的高可用形式和 Kafak, Hazelcast 的高可用几乎相同,均是固定分片和副本.

Elasticsearch 集群中节点角色分为主节点,数据节点,客户端节点, Tribe 节点.

所有的索引操作都会对磁盘进行追加操作,而不是直接的对文档进行修改,后台有固定的线程的追加的数据进行合并.

  • 同步机制
  • 数据刷盘方式

主副本分片交互过程

  • 主副本分片结构
  • 创建,索引或删除文档
  • 获取单个文档
  • 部分更新
    • 即便是部分更新,每次发送给副本的都是一个全量的文档
    • 此时的文档分发是异步的,不保证其顺序
  • 同时操作多个文档
    • mget
    • bulk

Discovery

Master
唯一可以发布新的集群状态的节点
用于响应集群操作,修改节点,增删改索引信息,为节点分配分片
索引和搜索等不需要牵涉到主节点
discovery.zen:
minimum_master_nodes: 选举主节点的最小 master eligible 节点数,类似于 Zookeeper 里的大多数
ping.unicast: 单播时进行发现的主机信息
master_election:
filter_client: 默认 true
filter_data: 默认 false
fd: Fault Detection
ping_interval : 1
ping_timeout : 30s
ping_retries : 3
publish_timeout : 30s 发布集群状态的超时时间
no_master_block: 当没有 Master 时需要拒绝的操作,不会阻止和节点相关的 API
all
write 默认.可能会读取到过期数据.

Local Gateway

  • 用于本地存储集群节点和分片信息.即便在集群完全重启后也会保持.
  • 存储的节点和集群信息会用于确定何时进行数据恢复调度.
  • 配置项的更改只有在集群完全重启后才会生效.

参考

恢复备份

Elasticsearch 支持快照和恢复

# 需要配置 path.repo: /home/data/es-backup
# Snapshot repository
curl -XPUT 'localhost:9200/_snapshot/backup' -d '{
"type": "fs",
"settings": {
"compress": "true",
"location": "/home/data/es-backup/backup"
}
}'

curl -XGET localhost:9200/_snapshot/backup?pretty

curl -XGET localhost:9200/_snapshot/_all?pretty

# Backup
curl -XPUT 'localhost:9200/_snapshot/backup/bckp1'

curl -XPUT 'localhost:9200/_snapshot/backup/bckp2?wait_for_completion=true&pretty'

curl -XPUT 'localhost:9200/_snapshot/backup/bckp?wait_for_completion=true&pretty' -d '{
"indices": "mq-*",
"ignore_unavailable": "true",
"include_global_state": false,
"partial":false
}'

# Restore
curl -XPOST 'localhost:9200/_snapshot/backup/bckp1/_restore'
curl -XPOST 'localhost:9200/_snapshot/backup/bck1/_restore?pretty' -d '{"indices": "c*"}'

# Cancel/Delete
curl -XDELETE 'localhost:9200/_snapshot/backup/bckp1/_restore'

参考

数据建模

Parent-Child

  • Parent Child Relationship
  • 适合于子文档特别多,父文档较少的情况
  • 父子关系的映射会存储在内存中
  • 更新父文档不影响子文档
  • 增删改子文档不影响父文档
  • 减少父文档 ID 长度
  • 父子都存储在同一个分片
  • 可以实现祖孙关系,增加时需要使用 routing 来指定分片
  • has_child filter 不会缓存
  • 搜索子文档时,避免使用 score_mode
  • GET /_nodes/stats/indices/id_cache?human 可查看用于缓存父子占用的内存
  • 每次索引更改需要重建 Global Ordinals,当父文档较多时,需要占用大量的时间,可通过修改 fielddata 的 loading 来使其发生在 refresh 时而不是 query 时.

Nested Object

聚合

内存控制

  • Controlling Memory
  • $ES_HEAP_SIZE 可用于控制堆大小,小于可用的一半,小于 32G

Fielddata

  • 会将所有文档的 fielddata 加载到内存,主要是 uinvert index
  • 缓存以段位单位
  • 主要用于排序,聚合和部分过滤及脚本

插件

常用插件

插件开发

更新

更新主要指部分更新,部分更新在复杂场景下是非常常见的常见.部分更新分为脚本和文档合并两种方式.

curl -XPUT "localhost:9200/test/test/1" -d '{"name":"wener","age":15}'
# 需要
# script.inline: true
curl -XPOST "localhost:9200/test/test/1/_update" -d '{"script" : "ctx._source.age=16"}'
curl -XPOST "localhost:9200/test/test/1/_update" -d '{"script" : "ctx._source.sex='男'"}'
# params for script
curl -XPOST "localhost:9200/test/test/1/_update" -d '{
"script" : "ctx._source.age=age",
"params" : {
"age" : 20
}
}'
# upsert for missing
curl -XPOST "localhost:9200/test/test/1/_update" -d '{
"script" : "ctx._source.score+=1",
"upsert": {
"score": 0
}
}'
# doc for partial update
curl -XPOST "localhost:9200/test/test/1/_update" -d '{
"doc":{
"friends":[
"XXX"
]
}
}'
# doc_as_upsert will insert this doc if missing
curl -XPOST "localhost:9200/test/test/2/_update" -d '{
"doc":{
"friends":[
"CCC"
]
},
"doc_as_upsert": true
}'
# remove field
curl -XPOST "localhost:9200/test/test/1/_update" -d '{
"script" : "ctx._source.remove('friends')"
}'

翻页

默认翻页内容最多 10000 条(配置项 index.max_result_window),

vs Others

将 Elasticsearch 和其他产品做比较时,需要先考虑它的设计初衷

  • 一次写多次读
    • 符合日志的特性
    • 能够做大量缓存
  • 搜索
  • 统计分析

Elasticsearch 自身本可以作为主数据库使用, 但目前常用的做法都是作为一个附属库,当数据被写入到一个支持事务的高一致性的存储(HBase,PostgreSQL)后,再异步推送到 Elasticsearch 进行索引,以实现对存储的数据进行高性能的检索和分析.

MongoDB

MongoDB 是用于传统用途(CRUD) 的基于文档的数据库,其主要特色不是搜索或者统计分析,而是能够以数据本身的文档结构进行存储.

MongoDB 与 Elasticsearch 两者并不违背,可以同时使用.

虽然 Elasticsearch 不需要 Schema ,但是因为底层为 Lucene, 所有的文档都会扁平化处理,因此无法真正的把 Elasticsearch 作为 Schemaless 来使用,反而需要慎重的对待.而 MongoDB 却能很好地做到 Schemaless, 可以从 JavaScript 的处理角度出发看待起处理文档的方式.

Elasticsearch 大多情况适用于处理小文档,当处理不需要索引的大文档时,其功能有限.而 MongoDB 支持 GridFS, 可以很好地使用同一套接口来处理大文档.

Solar

FAQ

指定配置文件

启动时可通过 path.conf 来指定配置文件目录, 读取的配置文件来该目录下的 elasticsearch.yml, 不能直接指定该配置文件,也不能修改该配置文件名.

# 确保目录下配置文件正确
ls my-config
elasticsearch.yml logging.yml
# 启动时指定配置目录
./bin/elasticsearch -Dpath.conf=my-config

使用 ROOT 启动

Elasticsearch 默认是不允许使用 ROOT 用户启动的,可通过启动时添加 es.insecure.allow.root 参数允许使用 ROOT 启动.

# 允许使用 ROOT 启动
./bin/elasticsearch -Des.insecure.allow.root=true

Java 版本

Elasticsearch 对 Java 版本有要求,至少需要 1.7, 并且在 1.7 的某些版本中的 Bug 可能会影响 Lucene 的一致性,因此 Elasticsearch 在启动时会给予警告.

Exception in thread "main" java.lang.RuntimeException: Java version: Oracle Corporation 1.7.0_45 [Java HotSpot(TM) 64-Bit Server VM 24.45-b08] suffers from critical bug https://bugs.openjdk.java.net/browse/JDK-8024830 which can cause data corruption.
Please upgrade the JVM, see http://www.elastic.co/guide/en/elasticsearch/reference/current/_installation.html for current recommendations.
If you absolutely cannot upgrade, please add -XX:-UseSuperWord to the JAVA_OPTS environment variable.
Upgrading is preferred, this workaround will result in degraded performance.
at org.elasticsearch.bootstrap.JVMCheck.check(JVMCheck.java:123)
at org.elasticsearch.bootstrap.Bootstrap.init(Bootstrap.java:283)
at org.elasticsearch.bootstrap.Elasticsearch.main(Elasticsearch.java:35)
Refer to the log for complete error details.
Use -XX:-UseSuperWord if you are running on 7u40 <= JVM < 7u55

使用 term 查找不到指定内容

  • 当使用 term 进行全文匹配时,要求查找的字段为非解析字段,否则无法进行全文匹配.

  • 大写会存储为小写,查询时需要手动转为小写.

  • Exact values versus full text