@author Gandalf
介绍
ES是什么
Elasticsearch实时的分布式全文搜索分析引擎,内部使用Lucene做索引与搜索,开发语言为Java
全文:对全部的文本内容进行分析,建立索引,使之可以被搜索
实时:新增到 ES 中的数据在1秒后就可以被检索到,这种新增数据对搜索的可见性称为“准实时/近实时搜索”。
分布式:可以动态调整集群规模,弹性扩容
Github:https://github.com/elastic/elasticsearch
可以做什么
- 搜索:搭建搜索引擎(类似百度、谷歌、知乎等可搜索功能的实现)、日志收集分析系统
- 聚合分析:进行数据分析、统计,生成指标数据。
- 适用场景:适合用于中等规模数据的应用场景,据官方描述,集群规模支持“上百”个节点,ES适合中等数据量的业务,不适合海量数据存储
优缺点
迭代快速,目前每2周左右就会发布新版本
社区活跃且生态丰富
语法与关系型数据库迥异,语法学习成本高
基本概念
ES是面向文档的。各种文本内容以文档(document)的形式存储到ES中,文档可以是一封邮件、一条日志,一条关系型数据库的记录,或者一个网页的内容。使用 JSON 作为文档的序列化格式,文档可以有很多字段,创建索引的时候,同关系型数据库创建表约束一样,可指定各字段的数据类型(也可不指定,动态生成文档),可指定不同字段不同的分词器,指定字段是否被搜索到。
索引结构
存储结构上,由_index
、_type
和_id
标识一个文档
_index
:为指向一个或多个分片的物理存储命名,存储某一类型文档的集合_type
:用于区分一个_index
中的不同细分,多个_type
的数据结构应是相似的,如建立一个订单类的索引,有不同_type
类型的订单:物流订单、采购订单、出库订单...,索引中的多个type,实际上是存放在一起的,一个index下,不能有多个字段重名但type不一致_id
:文档唯一标识,可自行指定,ES自动生成的id,长度为20个字符,URL安全,base64编码,GUID,分布式环境也不会发生冲突
分片
分片(Shard): ES利用分片解决单机无法存储规模巨大的问题,将数据分为多个分片,分片就是一个lucene实例,会自动创建索引和处理请求,不同分片可存储在不同的机器上,通过路由策略找到分片所在的位置;分布式存储中还会把分片复制成多个副本,实现水平扩展能力以及高可用(HA),同时副本还可以执行读请求,分担集群压力,ES将数据副本分为主从两部分,即主分片primary hard
和副分片replica shard
,主数据作为权威数据,写过程中先写主分片,成功后再写副分片,当集群规模扩大或缩小时,ES 会自动在各节点中迁移分片,使数据仍然均匀分布在集群里
primary shard不能和自己replica shad在同一节点,单节点创建primary shard和replica shard在同一台机器上时,是不会分配给replica shard的。
Tip :分片一旦指定久可以修改,所以一开始就尽量规划好主分片数量,先依据硬件情况定好单个分片容量,然后依据业务场景预估数据量和增长量,再除以单个分片容量。
文档结构
_mapping
:自动或者手动为ES中index的typet建立的一种数据结构和相关配置
_all
:ES会自动将多个field的值用串联的方式链接起来,变成一个长的字符串,作为_all field的值,同时建立索引,在搜索的时候,没有指定field的值时候,就默认搜索_all
倒排索引
建立倒排索引的时候,会执行一个操作,对各个拆分的各个单词进行相应的处理(noralization)归一化,搜索的时候也要进行分词并归一化,提升搜索命中文档的概率 likes-->like Tom-->tom
如不提前指定索引及类型,es会自动建立index和type,不需要提前创建,会默认对每个field建立倒排索引,让其可以被搜索
各节点说明
-
master节点:管理ES的元数据,管理索引的创建和删除,结点增加和移除,默认会选出一台做master,不会处理所有的请求,通过配置
node.master: true
(默认),使节点具有被选举为 Master 的资格,主结点也可做数据节点,生产环境尽量分离主节点与数据节点 - 数据节点(Data node):保存数据、执行数据相关操作:CRUD、搜索、聚合等。数据节点对CPU、内存、I/O要求较高
- 预处理节点(Ingest node):在索引文档之前,即写入数据之前,通过定义好的一系列的processors(处理器)和pipeline(管道),对数据进行某种转换。processors和pipeline拦截bulk和index请求
- 协调节点(Coordinateing node):处理客户端请求的节点,并将请求转发给文档所在的位置,在收集各节点的处理请求后,合并数据并转换为客户端,每个节点都可以作为协调节点
ES读请求与写请求都是首先通过协调节点,由协调节点进行请求分发到对应的shard以及对应的node,各node处理完后回复协调节点再向客户端做响应
分词
IK 分词器简介:ik有两种分词模式,ik_max_word,和ik_smart模式;
ik_max_word: 会将文本做最细粒度的拆分,比如会将“中华人民共和国国歌”拆分为“中华人民共和国,中华人民,中华,华人,人民共和国,人民,人,民,共和国,共和,和,国国,国歌”,会穷尽各种可能的组合;
ik_smart: 会做最粗粒度的拆分,比如会将“中华人民共和国国歌”拆分为“中华人民共和国,国歌”。
进阶知识
集群健康状态
从数据完整性的角度划分,集群健康状态分为三种:
Green:所有的主分片和副分片都正常运行。
Yellow:所有的主分片都正常运行,但不是所有的副分片都正常运行。这意味着存在单点故障风险
Red:有主分片没能正常运行,不是所有的主分片都是active状态。
集群状态是全局信息,包括内容路由信息、配置信息,描述了“哪个分片位于哪个节点”这种信息,
集群状态发布流程:集群状态由主节点负责维护,主节点更新后广播到其它节点 ,该操作是一个不完全分布事务,分为两阶段,commit与apply阶段, master变更集群信息后,广播变更信息到各个子节点,多数节点确认后,集群状态发布成功
主节点主分片选举过程
当master节点挂掉后,会自动选举一个node成为新的maste,基于Bully算法,每个节点个ID,选出各节点认为ID值最小的作为主节点
新主节点将丢失掉的primary shard的某个replica shard提升为primary shard, 此时cluster status会变为yellow,因为primary全都变成active了,但少了一个replica shard
重启故障的node,主节点会将缺失的副本都cpy到该node上,而且该node会用之前已有的shard数据,只是同步一下挂掉后发生过的修改
之后cluster status状态就变为green,因为primary shard和replica shard都属于可用状态
ES节点内部通信与数据传输都是基于TCP,基于netty,包括REST接口也是基于netty进行封装路由
ES数据可被搜索流程
段合并
默认情况下索引的refresh_interval为1秒,
ES并发控制
通过乐观锁实现并发控制,通过版本号的方式控制,每次更新之前先拿到版本号与ES中的版本号对比,如果不一致,则过去当前版本号中的最新数据,再修改
悲观锁:每次获取数据都会加锁,悲观的认为别人会修改数据,其余线程想获取数据必须等到该锁被释放后才能获取到,悲观适用于多写,乐观适用于多读,乐观锁存在ABA问题
悲观锁可通过版本号与CAS实现,如Java中的原子类;
写数据流程
- 客户端向NODE1发送写请求。
- NODE1使用文档ID来确定文档属于分片0,通过集群状态中的内容路由表信息获知分片0的主分片位于NODE3,因此请求被转发到NODE3上。
- NODE3上的主分片执行写操作。如果写入成功,则它将请求并行转发到 NODE1和NODE2的副分片上,等待返回结果。当所有的副分片都报告成功,NODE3将向协调节点报告成功,协调节点再向客户端报告成功
详细流程
在客户端收到成功响应时,意味着写操作已经在主分片和所有副分片都执行完成
建立索引流程
读数据流程
查询分为两阶段:QUERY_THEN_FETCH查询与获取,Query阶段知道了要取哪些数据,但是并没有取具体的数据,Fetch阶段获取具体的数据
Query阶段
QUERY_THEN_FETCH搜索类型的查询阶段步骤如下:
(1)客户端发送search请求到NODE 3。
(2)Node 3将查询请求转发到索引的每个主分片或副分片中。
(3)每个分片在本地执行查询,并使用本地的Term/DocumentFrequency信息进行打分,添加结果到大小为from + size的本地有序优先队列中。
(4)每个分片返回各自优先队列中所有文档的ID和排序值给协调节点,协调节点合并这些值到自己的优先队列中,产生一个全局排序后的列表。
Fetch阶段
Fetch阶段由以下步骤构成:
(1)协调节点向相关NODE发送GET请求。
(2)分片所在节点向协调节点返回数据。
(3)协调节点等待所有文档被取得,然后返回给客户端。
分布式系统搜索执行流程
删数据流程
删除方式为标记删除,只会对文档进行标记为delete,不会真正物理删除,等es存储达到一定阈值时,才会自动删除标记为delete的文档,查询时会过滤掉被标记为删除的数据,或者lucene段合并时才会清除标记为删除的数据
修改数据流程
ES配置
节点配置
创建独立主节点的配置:
node.master: true
node.data: false
数据节点配置
node.master: false
node.data: true
node.ingest: false
预处理节点配置
node.master: false
node.data: false
node.ingest: true
协调节点配置
node.master: false
node.data: false
node.ingest: false
ES模板配置
{
"template": "*",
"order" : 0,
"settings":
{
"index.merge.policy.max_merged_segment" : "2gb",
"index.merge.policy.segments_per_tier" : "24",
"index.number_of_replicas" : "1",
"index.number_of_shards" : "24",
"index.optimize_auto_generated_id" : "true",
"index.refresh_interval" : "120s",
"index.translog.durability" : "async",
"index.translog.flush_threshold_size" : "1000mb",
"index.translog.sync_interval" : "120s",
"index.unassigned.node_left.delayed_timeout" : "5d"
}
}
配置修改
重要配置修改
https://www.elastic.co/guide/cn/elasticsearch/guide/current/important-configuration-changes.html
设置es堆内存
https://www.elastic.co/guide/cn/elasticsearch/guide/current/heap-sizing.html
时间类型存储:往 ES 写入时间类型的数据的时候,可以写入不同的时间格式的值,最终在 ES 中都会转化成 long 类型用于查询和排序。
ES使用
安装使用
下载 https://www.elastic.co/cn/downloads/elasticsearch
java环境:确保服务器的jvm和运行程序的jvm的版本是完全一致的
- 启动
/usr/java/jdk1.8.0_144/bin/java -Xms24g -Xmx24g -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly -XX:+AlwaysPreTouch -server -Xss1m -Djava.awt.headless=true -Dfile.encoding=UTF-8 -Djna.nosys=true -Djdk.io.permissionsUseCanonicalPath=true -Dio.netty.noUnsafe=true -Dio.netty.noKeySetOptimization=true -Dio.netty.recycler.maxCapacityPerThread=0 -Dlog4j.shutdownHookEnabled=false -Dlog4j2.disable.jmx=true -Dlog4j.skipJansi=true -XX:+Hepath.home=/usr/elasticsearch-5.5.2 -cp /usr/elasticsearch-5.5.2/lib/* org.elasticsearch.bootstrap.Elasticsearch -d
- 访问
启动成功后,ES提供REST接口,http端口为9200,Java默认访问端口为9300
集群运维管理
查询集群状态
-- 默认请求到主节点
curl -X GET "localhost:9200/_cluster/state
-- 接收请求的节点执行,可用于验证当前节点的集群状态是否为最新
curl -X GET "localhost:9200/_cluster/state?local=true
查询集群健康状态
curl -X GET localhost:9200/_cat/health?v
curl -X GET localhost:9200/_cluster/health?pretty
集群节点信息
curl http://127.0.0.1:9200/_cat/nodes?v
查询索引列表
curl -X GET "localhost:9200/_cat/indices?v"
节点监控
-- 可以全面的查看集群各个节点的各种性能指标情况
curl -X GET "localhost:9200/_nodes/stats?pretty"
查询节点信息
curl -X GET "localhost:9200/_nodes?pretty"
集群磁盘分布情况
curl http://127.0.0.1:9200/_cat/allocation?v
修改索引允许动态映射
-- 修改后插入文档时索引遇到默认字段会自动修改mapping(表接口)
curl -XPUT "http://localhost:9200/index_name/type_name/_mapping" -H 'Content-Type: application/json' -d '{
"dynamic": true
}'
修改索引为一个副本
curl -XPUT "http://localhost:9200/index_name/_settings" -H 'Content-Type: application/json' -d '{"number_of_replicas":1}'
ES正在执行的任务
curl -X GET 'localhost:9200/_tasks?pretty'
-- 取消正在执行的任务
curl -X POST 'localhost:9200/_tasks/node_id:task_id/_cancel'
-- 过滤出正在执行的查询任务
curl -X GET 'localhost:9200/_tasks?actions=cluster:*&pretty'
curl -X GET 'localhost:9200/_tasks?actions=*&detailed&pretty'
分片状态及管理
索引恢复进度
curl localhost:9200/_cat/recovery?pretty&v
-- 查询某个具体索引的恢复过程
curl localhost:9200/index_name/_recovery?pretty&v
节点自动分配
查看是否自动副本分配
-- 如修改副本数量后,索引为自动均衡,可通过用此配置查询
curl -XGET 'localhost:9200/_cluster/settings?pretty' -d '{
"persistent": {},
"transient": {
"cluster": {
"routing": {
"allocation": {
"enable": "all"
}
}
}
}
}'
设置自动分配分片
curl -XPUT 'localhost:9200/_cluster/settings' -d'{
"transient": {
"cluster.routing.allocation.enable": "all"
}
}'
分片分布情况
curl localhost:9200/_cat/shards
索引分片同步列表
curl 'localhost:9200/_cluster/state?filter_path=metadata.indices.index_name.in_sync_allocations.*,routing_table.indices.index_name.*&pretty'
问题定位
热点线程查询
curl -X GET "localhost:9200/_nodes/node_name1,node_name2/hot_threads"
查询未分配索引的详细解释
curl localhost:9200/_cluster/allocation/explain
查询节点线程池使用
curl -X GET "http://localhost:9200/_cat/thread_pool"
常用语法
ES查询语法:QueryDSL
DSL:Domain specified language:特定领域的语言,使用http构建复杂的请求体进行查询
查询返回语法解释
GET /index_name/type_name/_search
took 耗费了多少毫秒
time_out 是否超时
_shards:数据拆分成了5个分片
hists.total:查询结果的数量
hist.max_score:score的含义,就是document对于一个search的相关度的匹配分数,分数越高,相关度越高
hits.hits:包含了匹配搜索的数据
查询排序
-- 查询index_name索引,按时间排序,默认取前20条
curl -i -XGET 'http://localhost:9200/index_name/_search?sort=createTime:asc?pretty'
批量查询 mget
查询不同index下的不同document
GET /_mget
{
"docs" :[
{
"_index":"index_name",
"_type":"type_name",
"_id":1
},{
"_index":"index_name2",
"_type":"type_name",
"_id":2
}
]
}
--查询同一个index下的不同document
GET /index_name/_mget
{
"docs" :[
{
"_type":"type_name",
"_id":1
},{
"_type":"type_name",
"_id":2
}
]
}
bool查询
bool查询,组合查询
复合子句可以合并多种子句为一个单一的查询,无论是叶子子句还是其他的复合子句。
合并多子句
http://192.168.41.11:9200/index_name/type_name/_search
{
"query":{
"bool":{
"must":{"match":{"type":"1"} },
"must_not":{"match":{ "blockCode":"5201030003"}},
"should":{"match":{ "doorName":"Openai大模型"} }
}
}
}
分页查询
{
"query":{
"bool":{
"must":[
{
"match":{"blockCode":"5201030003"}
},
{
"match":{"type":"23"}
}
]
}
},
"size":"2",
"from":"0",
"sort":{
"createdDate":{
"order":"desc"
}
}
}
ES分页from默认从0开始,size默认为10
深分页问题
deep paging:当集群节点众多,且分页查询的页数过大时,会很消耗服务器资源,如分页from1000,size10 的分页请求过来后,会将请求打到各个shard或者replica节点上,去取每个节点去10010条数据到coordinate结点上,10个节点共100100条进行排序选出10条。
超时查询
返回在指定超时时间内查询到的数据,有可能数据没有查询完毕
curl localhost:9200/index_name/index_name/_search?timeout=1s
精确值查询
term过滤:term主要用于精确匹配哪些值,比如数字,日期,布尔值或 not_analyzed的字符串,允许使用多个匹配条件
----条件查询并排序
http://localhost:9200/index_name/type_name/_search
{
"query":{
:"match":{
"residentId":"1234123"
}
},
"sort":[{
"createdDate":"desc"
}]
}
-----全文检索
http://localhost:9200/index_name/type_name/_search
{
"query": {
"match":{"photoLargeUuid":"84cb5b32 a195229e0a7d"}
}
}
全文搜索会将查询条件分割进行组合查询按权重排序
---phrase 查询
短语全词匹配
复合查询
GET /item/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"brand": {
"query": "小米"
}
}
},
{
"term": {
"title": {
"value": "小米"
}
}
}
]
}
}
}
更新语法
post /index_name/type_name/1/update
{
"doc":{
"residentId":"341234123412"
}
}
删除语法
-- delete post 删除ID为796098575183802的记录
curl -x delete http://localhost:9200/index_name/type_name/796098575183802
--delete post 删除该索引index_name,谨慎执行
curl -X DELETE "localhost:9200/index_name?pretty"
统计语法
统计索引条数
curl 'localhost:9200/index_name/_count?pretty'
curl 'localhost:9200/face_picture_index/_count?pretty'
curl 'localhost:9200/index_name/_count?pretty'
聚合统计
按类型进行统计
http://localhost:9200/index_name/type_name/_search
{
"size":0,
"aggs": {
"group_by_type": {
"terms": {
"field": "type"
}
}
}
}
-----聚合统计,按月分段,以开门方式为维度进行统计
{
"size":0,
"aggs":{
"group_by_date":{
"range":{
"field":"createdDate",
"ranges":[
{"from":1555261319001,
"to":1557853319000
},{
"from":1552582919001,
"to":1555261319000
},{
"from":1550163719000,
"to":1552582919000}
]
},
"aggs":{
"group_by_type":{
"terms":{
"field":"type"
}
}
}
}
}
}
ES优化
1. 慢查询配置
curl -XPUT "http://localhost:9200/index_name/_settings" -H 'Content-Type: application/json' -d'{
"index.search.slowlog.threshold.fetch.debug": "5s",
"index.search.slowlog.threshold.query.warn": "5s",
"index.indexing.slowlog.threshold.index.info": "5s",
"index.indexing.slowlog.threshold.index.warn": "10s"
}'
配置慢查询日志,将索引中的慢查询,慢索引打印出来
2. 索引配置优化
数据量大的情况下:基于日期轮询的索引可以每天生成一个索引,然后用别名关联,或者使用索引通配符匹配
3. 系统层面优化
- 关闭swap,调整max open files
- http://dockone.io/article/505
- 调整shard数,replica数
- elasticsearch三个重要的优化 https://zhaoyanblog.com/archives/319.html
4. 结构优化
不需要排序聚合,不需要在脚本中访问的字段可以禁用doc_valus字段节省存储空间
建议周期性的建立新索引
5. 写入速度优化
- 加大 Translog Flush ,目的是降低 Iops(每秒读写数)、Writeblock。但是会降低实时性,需要接受一定概率的数据丢失(服务器断电)
- 增加 Index Refresh 间隔,目的是减少 Segment Merge 的次数。
- 增长索引刷新时间,默认为1s,index_refresh_interval:120s
- 调整 Bulk 线程池和队列。
- 优化节点间的任务分布。
6. 定位cup过高
查看节点hot_threads
curl -X GET "localhost:9200/_cat/thread_pool"
curl -X GET "localhost:9200/_nodes/node-241/hot_threads"
curl -X GET "localhost:9200/_nodes/node-241,node-131/hot_threads"
active:当前正在执行任务的线程数量,queue:队列中等待处理的请求数量,rejected:队列已满,请求被拒绝的次数。
7. ES写入数据过慢原因
随着写入索引数据增多,单个shard数据量太大(超过50GB),导致写入速度变慢
随着数据写入,segment合并变得频繁,es节点负载变高
8.存储优化
强制段合并
elasticsearch删除数据并未真正从磁盘进行删除已,而是将数据标记为删除,等到段合并时才会清楚标记为删除的数据,强制Elasticsearch进行段合并segment merging
,Segment merging要消耗CPU,以及大量的I/O资源,所以一定要在你的ElasticSearch集群处于维护窗口期间,并且有足够的I/O空间的
curl -XPOST 'http://localhost:9200/索引名称/_forcemerge?only_expunge_deletes=true'
9. 节点内存使用率过高原因
分段内存一个Lucene分段就是一个完整的倒排索引,倒排索引由单词词典和倒排列表组成。在 Lucene 中,单词词典中的 FST 结构会被加载到内存。因此每个分段都会占用一定的内存空间。可以通过下面的
API来查看所有节点上的所有分段占用的内存总量:
curl -X GET "localhost:9200/_cat/nodes?v&h=segments.memory"
也可以单独查看每个分段的内存占用量:
curl -X GET "localhost:9200/_cat/segments?v&h=index,shard,segment,size.memory"
查询分片缓存请求大小, 默认为堆1%大小
curl -X GET "localhost:9200/_stats/request_cache?pretty"
查询节点查询缓存占用大小,默认为堆10%大小
curl -X GET "localhost:9200/_cat/nodes?v&h=query_cache.memory_size"
故障处理
1. 如何安全重启elasticsearch?
https://www.elastic.co/guide/cn/elasticsearch/guide/current/_rolling_restarts.html
2. 重启节点后有部分索引分片未能恢复
生产环境中有两台节点,将主节点增加查询最大限制数后重启
indices.query.bool.max_clause_count: 10240
直接docker restart elasticsearch 后,重启elasticsearch后,有一个索引长期yellow,有两个分片长期处于未分配状态,es日志出现以下关键错误日志
primary shard with sync id but number of docs differ: 7728586 (node-205-1, primary) vs 7728583(node-149-1)
直接重启节点后出现两个节点的某个分片的数据不一致
解决:
将这个有问题的索引修改副本数为0,再修改为1,待分配完后即可恢复
索引恢复(indices.recovery)是ES数据恢复过程,是集群启动过程中最缓慢的过程。集群完全重启,或者Master节点挂掉后,新选出的Master也有可能执行索引恢复过程。
待恢复的是哪些数据?是客户端写入成功,但是未执行刷盘(flush)的Lucene分段。
索引恢复有哪些好处?
保持数据完整性:当节点异常重启时,写入磁盘的数据先到文件系统的缓冲,未必来得及刷盘。如果不通过某种方式将未进行刷盘的数据找回,则会丢失一些数据。
数据副本一致性:由于写入操作在多个分片副本上没有来得及全部执行,副分片需要同步成和主分片完全一致。
3. 生产环境某台ES节点负载高原因:
未找到
4. profile定位查询缓慢原因
curl -X GET "http://localhost:9200/index_name/type_name/_search?pretty" -d'{
"profile":true,
"query":{
"match":{
"blockCode":"5203210005"
}
}
}'
fielddata=true,以便通过取消倒排索引将fielddata加载到内存中
ES官方解释为在es5.x以后,聚合排序这种操作需使用指定数据结构fieldData进行单独操作,需要单独开启,ES排序field的类型只支持text类型
解决办法如下两步:
第二:mapping修改
PUT my_index/_mapping
{
"properties": {
"createTime": {
"type": "text",
"fielddata": true
}
}
}
5. 索引有多个副本但副本分片未自动分配
问题描述:新建索引时设置了0个副本,后来设置为1个,但是集群没有自动分配分片,索引长期为yellow
解决:查看集群设置是否自动分配,是否为未设置自动分配,设置为自动分配
参考及推荐
《Elasticsearch 源码解析与优化实战》