1.分布式架构:
1.es是点对点的方式(可以直接与其他节点通讯)
2.hadoop生态是master-slave方式
2.本地一次启动多个节点
elasticsearch -E node.name=node-2 -E cluster.name=my-es -d
elasticsearch -E node.name=node-3 -E cluster.name=my-es -d
elasticsearch -E node.name=node-4 -E cluster.name=my-es -d
3. query string 和dsl区别
URI查询就是用直接在uri上添加查询条件
比如:
GET /movies/_search?q=title:2012&sort=year:desc&from=0&size=10&timeout=1s
但是我们常常使用的都是request body search方式(也就是DSL方式)
那么uri查询的语法也是可以移植到DSL方式查询:这就是叫做(query string search),也就是query string是uri查询在DSL的表现形式。
例如:
POST users/_search
{
"query": {
"query_string": {
"default_field": "name",
"query": "Ruan AND Yiming"
}
}
}
4.底层数据是怎么记录的
我们经常遇到:明明设置不让es存储字段,为什么还能查询到该字段的值呢?分为几种情况
1.数据可以存入es中,也可以被索引(就是通常我们设置字段的格式)
2.数据可以存入es中,但是不能被检索(也就是不可以用这个字段去搜索文档,也就是在倒排索引中没有该字段,mapping也没有该字段的定义)
3._source可能看到不是真实的(眼见不为实):例如source中是name的值是 Quick star,当我们用标准分词器standard分词后,其实在倒排索引中是 quick 和 star词项,也就是你直接使用Quick查询,就查不到该文档了。使用quick可以查到。所以_source只是记录你写入es的数据的原始数据。至于es认识不认识你的查询,是看倒排索引中是否有你查询的词项的。
5.分词器的组成结构
分词器包含三部分,比如常用的standard、keyword、stop、whitespace都是内置的分词器,一些中文分词器:hanLp、IK、pinyin。
1.分词前的特征过滤(比如过滤标点符号,html标签等)(character filter)
2.分词规则(tokenizer)【内置的分词规则:whitespace、standard、pattern、keyword等】
3.分词的再加工(比如把复数变成原始的单词等)(token filter)
例如:
POST _analyze
{
"tokenizer":"keyword", ---分词器的分词规则(注意这里的keyword不是分词器keyword,这里是表示这个分词的规则是不分词)
"char_filter":["html_strip"], --分词前的特征过滤
"text": "<b>hello world</b>" --原始文本
}
6.索引模板和动态模板
一般来说:索引模板是来动态生成索引的,动态模板是为某一个特定的索引定义mapping中各个字段的。
7.多功能字段属性
多字段功能:一个字段可以给该字段再赋予其他字段的属性。就是同一个文本可以有多个属性。
PUT my_index
{
"mappings": {
"type": {
"properties": {
"addr": {
"type": "text",
"fields": {
"aaa": {
"type": "keyword",
"ignore_above": 256
}
}
}
}
}
}
}
8.影响评分的因素
评分的标准:5之前是tf-idf算法。5和之后是BM25算法
1.设置各个属性的boost 权重值
2.使用dis_max查询(should是把各个查询项加起来算得分,dis_max使用各个项的最大值作为得分)
3.function score query:自定义算分。
9.master节点作用
master作用:创建删除索引,决定分片如何分配给哪些节点,维护集群状态。
集群状态:所有节点的信息、所有索引和相关的mapping和settings信息、分片的路由信息。
所有的节点都会保存集群状态,但是只有master节点可以更改状态信息,其他节点把状态同步到自己中。
10.master的选举
10.1选举过程:
选举master节点过程(只有是候选的master节点才能竞选):通过候选master节点相互ping对方,node的id小的成为master节点【这是主要的规则】(当然还有必须得到多少个候选者的支持等条件),其他的节点将加入该集群中,并把状态同步到自己身上。
10.2脑裂问题:
脑裂的问题:比如原来三个master node,其中node1是master节点,但是当node1的网络连接不到node2,node3节点,这时就会产生,node1自己选自己作为master节点,node2和node3选择node2作为master节点,此时就会有两个master节点,都更新集群的状态,但是当网络恢复正常后,集群状态就出现不一致了。
10.3 es怎么避免脑裂的
es解决脑裂的问题:通过加入选举条件(必须得到多少个候选者的支持),discovery.zen.minimum_master_nodes: 2,通过这个参数来设置,比如集群中有3个候选者,则必须有两个及以上的候选者支持,才能成为master节点。像上述的问题就不会出现了。
计算公式是:total number of master-eligible nodes / 2 + 1
注意:7.0后,就不需要改配置了,也可以规避脑裂的问题。
11 es集群节点丢失故障转移过程
1.此时集群为red
状态
2.把该节点中的主分片,用其他正常节点对应的复制分片设置为主分片,此时集群为yellow
状态
3.把丢失节点和在步骤2中替换的复制分片,重新复制分片,保证集群有正常的主分片和复制分片的关系,此时集群为green
状态。
12 为什么es中索引的主分片数不可更改?
因为创建一个文档,文档属于哪个主分区的算法是:hash(_routing)%number_of_primary_shards,所以当主分区数量变化了,当文档进行更新等操作,就找不到原来属于哪个主分区了,造成数据丢失。_routing默认是文档的id,当然我们可以指定routing值,比如把相同城市的数据放到同一个分区的时候,就可以指定城市代码为routing。
如果需要更改主分区,可以reindex索引。也就是新建索引了。
13 为什么es说是一个去中心化的分布式集群?
因为我们发往集群的请求,任何节点都可以接收,任何节点都可以作为协作节点,然后把你真正请求分发给对应的节点,等他们响应后,节点再整合其他节点的响应,返回给客户端。
14 为什么es是近实时的?(需要大于1s才能搜索到)?什么是refresh?
因为refresh是默认1s执行一次。
数据先写入到index buffer
,然后执行refresh
操作,把数据写入到segment
中,此时才能被搜索到,当index buffer
被写满时,也会自动触发refresh
操作,最后清空index buffer
。其实这是数据并没有落盘,此时segment
中的数据是写入内存中,并允许查询。
为了防止断电等内存数据丢失,此时数据也是写入transaction log
,如果数据丢失,数据可以从transaction log
中恢复。
15 什么是transaction log?
其实就是一个日志。由于写入segment
后,后面会写入磁盘,这是比较耗时的,所以refresh
在写入segmen
t中放入内存中并可以查询,也会把index buffer中
的数据写入transaction log
文件中。(大部分实时框架都有日志功能)每个shard
会有一个transaction log
,该文件也是会落盘的。
16 为什么es中的数据是不可以变的。
因为一个文档就是一个segment
,具有不可变性(底层就是一个Lucene
),多个segment
组合在一起就是一个分片。
所以当对es数据进行更新的时候,都是先删除原来的数据,再重建,是把原来索引的segment
都是删除,在重新建该数据的segment
。
17 什么是Flush?
上面我们知道已经解决数据的缓存,写入segment后写磁盘慢的问题,但是没有解决transaction log
何时清空的问题。
Flush
就是解决这个问题的,那什么时候transaction log
可以清空呢?transaction log
其实就是解决segment
写磁盘慢的问题(避免断电等数据丢失),所以当transaction log
对应的segment
都写入磁盘的时候,也就是transaction log
可以清空的时候。
所以flush
过程是:
1.执行refresh
:把所有的数据都写入transaction log
和segment
,清空index buffer
2.将segment
中的数据都写入到磁盘。
3.清空transaction log
默认flush
30分钟一次,或者transaction log
(512M)满的时候。
可以看出flush
其实是上述过程的整合。
18 什么是Merge?
当我们写入很多文档后,也就是会建立很多segment,这时候数据过多,对查询会有较大的性能影响,此时会把segment合并,同时把标记删除的文档也清理掉。
这个操作是比较重的,所以可以优化下:1、避免已经很大的segment再次合并,可以设置当segment超过范围,不在参与合并操作(默认是5G) 2.设置系统中合并后,最后最多有几个segment(默认10)
19 query过程
es query过程:(包含query和fetch两个阶段, query是执行节点查询,fetch是协调节点再次处理)
客户端发送情况,任何一个节点都会充当协作节点把请求发送给对应节点的每个分区的主分片或者复制分片(其中一个),对应节点执行搜索过程,并把结果反馈给协作节点,协作节点再次经过处理(比如排序,top操作,from、size等操作后)把结果反馈给客户端。
可能存在问题:1.性能消耗过大,每个分片需要处理的数据是 from+size个,整个集群需要处理主分片数乘以(from+size),所以分页或者top查询都比较耗性能。
解决方案:1.尽量设置比较小的主分片数 2.保证数据可以均匀的分部到不同的分片上。
20 term聚合为什么会出现结果偏差?
查询结果的偏差:比如top查询,就是每个分片返回top个文档,然后协调节点在对多个节点的数据最后排序,最后取出top文档,返回给客户端,此时会出现结果偏差问题,比如当某个分片的topN+1个文档加上其他分片上的该文档的个数都大于其他节点的数据,但是此文档不会被返回给协调文档,最终可能不会返回给客户端。
解决方案:
1.当数据量不大,主分区设置为1
2.使用shard_size参数,从每个shard获取更多的数据,最终提高拿到的结果精度
21 排序
排序是使用文档的原始文本(_source)作为依据的,所以倒排索引无效,只能使用原始文本进行比较【这就是有些字段可以被索引,不可以排序的原因】。如果要使用某个字段作为排序的规则,那么该字段的必须设置为fielddata或者doc_values属性。
注意doc_values默认是打开的,但是text字段是不支持的(所以默认不能用text字段作为排序依据),所以fielddata主要针对text字段可以作为排序的依据的属性。
"addr": {
"type": "text",
"fielddata":true,
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}
"session_id": {
"type": "keyword",
"doc_values": false
}
22 分页和遍历:
上面的from+size的方式是比较耗内存的,需要处理主分片数乘以(from+size)个数据。
解决方法是使用 search_after :常用于深度分页。
1.search_after:不可以指定from,只能按照size个数往下找。但是需要指定当前排序的唯一值。
比如每页的大小size是10,要取990后面的10个,要知道990个唯一id(一般使用是文档id或者和其他属性组合)。这样做的好处,每个shard只需要拿出唯一id后面的10个,返回给协作文档就可以,所以总共处理的数据是:主分片数乘以size。
2.scroll方式:在查询时候创建一个快照,当新数据写入以后,无法被查找。常用于把全部数据导出。
23 es怎么控制并发?
通过version号控制,当发现提交的版本信息小于等于当前版本就报版本冲突异常。
现在7.0方式,通过if_seq_no 和if_primary_term组合控制版本信息。
也可以使用外部数据,作为版本信息来控制版本号(比如版本号存在有事务机制的mysql中)
24 嵌套对象有什么优势?
可以保持嵌套对象中各个属性相关关联的关系,避免联合查一个对象中的一个属性值和另一个对象的属性值,两个对象都可以查到,其实这时候想要都查不到。而嵌套对象就是做上面的场景的。
下面情况就应该出现。
设置为嵌套对象。
25 ingest node和ingest pipeline
ingest node:默认所有的节点都是ingest node,可以对数据进行预处理。
而数据处理是在pipeline中完成的,pipeline有可以定义很多的处理器processor,这些processor就会正常的工作了。
比如原来索引中tags字段是keyword类型,但是数据存入的是"openstack,k8s",这是我们不能对其中的元素进行聚合。可以使用处理器把tags字段的数据变成数组形式。
首先定义pipeline和对应的processor
PUT _ingest/pipeline/blog_pipeline
{
"description": "a blog pipeline",
"processors": [
{
"split": {
"field": "tags",
"separator": ","
}
},
{
"set":{
"field": "views",
"value": 0
}
}
]
}
#查看Pipleline
GET _ingest/pipeline/blog_pipeline
#使用pipeline写入数据
PUT tech_blogs/_doc/2?pipeline=blog_pipeline
{
"title": "Introducing cloud computering",
"tags": "openstack,k8s",
"content": "You konw, for cloud"
}
#把原来的数据,按照pipeline更新数据。
POST tech_blogs/_update_by_query?pipeline=blog_pipeline
{
"query": {
"bool": {
"must_not": {
"exists": {
"field": "views"
}
}
}
}
}
当然我们在使用pipeline也是可以测试pipeline的。
#测试一个已经定义的pipeline,_simulate是关键字
POST _ingest/pipeline/blog_pipeline/_simulate
{
"docs": [
{
"_source": {
"title": "Introducing cloud computering",
"tags": "openstack,k8s",
"content": "You konw, for cloud"
}
}
]
}
#测试一个还没有定义在es中的pipeline。
POST _ingest/pipeline/_simulate
{
"pipeline": {
"description": "to split blog tags",
"processors": [
{
"split": {
"field": "tags",
"separator": ","
}
},
{
"set":{
"field": "views",
"value": 0
}
}
]
},
"docs": [
{
"_index":"index",
"_id":"id",
"_source":{
"title":"Introducing big data......",
"tags":"hadoop,elasticsearch,spark",
"content":"You konw, for big data"
}
},
{
"_index":"index",
"_id":"idxx",
"_source":{
"title":"Introducing cloud computering",
"tags":"openstack,k8s",
"content":"You konw, for cloud"
}
}
]
}
26 painless内置的脚本语言
painless是es内部的脚本语言,6之后的版本只支持该脚本了,其他不支持。
不同的使用场景:该语法表现形式是不一样的。
在ingestion中的语法格式是:ctx.field_name
在更新的语法格式是:ctx._source.field_name
在查询和聚合格式是:doc[“field_name”]
27 集中常见属性的区别
- index:不可以搜索,但可以排序、聚合
- enabled:不可以搜索,排序、聚合
- doc_values:可以搜索,但不可以排序、聚合
- store:可以搜索、排序、聚合
28 数据建模的规范:
建议一、
1、优先考虑反范式化 2、当数据包含多数值对象,而且需要查询,使用nested字段 3、有关联对象关系,而且有一方更新比较频繁,使用父子关系。
建议二、
避免过多的字段,字段定义都是存在集群状态state中,默认最大字段为1000个。
建议三、
进行不使用正则查询。
建议四、
避免写入null,会导致聚合结果不准确
29 常见的部署集群的方式:
29.1 部署不同的角色:master/data/ingest/coordinating
这样就可以针对不同的情况,进行节点的水平扩展、或者多数据中心部署保证健壮性,另外也可以做到读写分离(读数据发送到coordinating节点、写数据发送到ingest节点)。
29.2 根据机器的性能配置不同的功能
为了降低集群的预算,可以根据机器的性能配置不同的功能,比如高配置的机器设置为hot属性,低配置的机器设为warm属性,可以把频繁读写的索引放到hot机器上等操作。
在hot机器上创建索引。
把很少访问的数据放到warn上。
29.2 机架隔离
也可以跟进机架设置。避免一个机架断电,造成数据丢失。
其实这些都是通过给节点配置不能的属性,然后根据这些属性,做不同的操作和分配。
同时也可以根据自己业务的特点按照地区、时间等分区建索引。也可以根据地区、时间路由到特定的分片上,提高系统的吞吐量。
30分片默认的设置
构建自己的集群运维工具:
31 常见集群变红和解决方案:
32 提高写入性能的方法
- 0、首先要保证数据建模是非常好的,不然很难优化、或很难提升性能。
- 1.客户端:多线程,批量写(每次数据不要超过15MB)
- 2.服务器端:2.1:降低IO操作:使用es自动生成的文档ID(内部要get一次数据)。
2.2:减少CPU和存储的开销:减少不必要分词、避免不需要的doc_values、文档的字段尽量保证相同的顺序,可以提高文档的压缩率。
2.3:尽可能写入和分片均衡负载,实现水平扩展。
2.4调整bluk线程池和队列 - 3.从写入过程着手(index_buffer->refresh->transtion_log->flush):3.1增加refresh时间间隔时长(默认1s)增大index_buffer缓存大小 3.2增加transtion_log间隔时长和增加size大小。
33 提高读性能的方法
- 1.首先要保证数据建模要非常好,扁平化数据,反范式数据(会有大量冗余数据,但是不用做join查询)。
- 2.使用nested类型,查询慢几倍;使用parent、child关系,会慢几百倍。
- 3.使用filter查询,可以使用缓存,而且不需要算分。
- 4.严禁使用*开头的通配符查询。
- 5.分片数据不要过多,单个分片不要过大
- 6.force-merge减少segment数量,或者设置为只读的索引等。
34 缓存和内存
缓存:es中缓存只要有三种:node query cache、shard request cache、fielddata
- 1.node query cache:缓存的是segment结果,每一个节点都有一个node query cache,所有shard共享,默认是机器的10%内存。当segment合并后该缓存清空。
- 2.shard request cache:缓存每个分片上查询结果,将整个查询的json串作为key(所以查询的时候,最好保存字段顺序不变,更好的命中缓存),默认是机器的1%。当分片refresh后该缓存清空。
- 3.fielddata:该属性只会应用到text类型字段,用于聚合和排序时候打开,但是该缓存是没有限制大小的,所以要是使用该属性,最好监控该缓存的大小,避免gc过多。segment合并后该缓存清空。
我们知道机器的一半都是用于上面的缓存的,如果缓存管理不当,会导致full GC过多,严重影响性能。
35 几种常见导致full GC的情况
- 1:segment数据过多,过大;解决方案:force_merge,把不用的索引设置为只读。
- 2:fielddata数据过大;这个数据的大小,es不会主动释放;解决方案:
indices.fielddata.cache.size
设置小点,重启节点。 - 3:复杂的查询(嵌套查询):解决方案:优化查询dsl。
另外可以使用熔断器(circuit breaker),设置各个缓存的熔断器,超过发生告警。
查看缓存使用情况:
GET _nodes/stats/indices?pretty
GET _cat/nodes?v&h=name,queryCacheMemory,queryCacheEvictions,requestCacheMemory,requestCacheHitCount,request_cache.miss_count
GET _cat/nodes?h=name,port,segments.memory,segments.index_writer_memory,fielddata.memory_size,query_cache.memory_size,request_cache.memory_size&v
36 索引的生命周期和生命周期管理
可以自定义每个索引的生命周期,每个生命周期都可以设置触发的条件,完成索引从创建到删除的自动化管理。
命令是_ilm
:可以定义各个生命周期的action。这里不写了。
PUT _cluster/settings
{
"persistent": {
"indices.lifecycle.poll_interval":"1s"
}
}
# 设置 Policy
PUT /_ilm/policy/log_ilm_policy
{
"policy": {
"phases": {
"warm": {
"min_age": "10s",
"actions": {
"allocate": {
"number_of_replicas": 0,
"include": {
"box_type": "warm"
},
"exclude": {},
"require": {}
},
"set_priority": {
"priority": 10
}
}
},
"cold": {
"min_age": "15s",
"actions": {
"allocate": {
"include": {
"box_type": "cold"
},
"exclude": {},
"require": {}
}
}
},
"hot": {
"min_age": "0ms",
"actions": {
"rollover": {
"max_size": "1gb",
"max_age": "1d",
"max_docs": 5
}
}
},
"delete": {
"min_age": "20s",
"actions": {
"delete": {}
}
}
}
}
}
最后把上面的ilm应用到某个索引上,或者索引模板上,则该索引就会按照上面的条件,完成生命周期了。
当然,要实现上面的功能,要首先集群有响应的配置,比如哪些节点是hot、warm、cold等等。