Index(数据库)
Type(表)7.X版本中type概念已经被删除
Document(行,代表一条文档数据)
Fields(字段,对文档数据根据不同属性进行分类标识)
处理数据的方式和规则方面做一些限制,如:某个字段的数据类型、默认值、分析器、是否被索引等。
Elasticsearch 提供了将索引划分成多份的能力,每一份就称之为分片。
将分片分配给某个节点的过程,包括分配主分片或者副本。如果是副本,还包含从主分片复制数据的过程。这个过程是由 master 节点完成的。
ES文档分词并使用“倒排索引”存储。
当输入"小明",ES会自动分为"小"和"明"关键字进行倒排索引的匹配
正排索引如mysql中是通过id找到对应的值
id content
-----------------
101 my name is zhang san
102 my name is li si
而ES中是通过keyword找对应的id
keyword id
-----------------
name 101,102
zhang 101
PUT是幂等的:即请求会自动校验数据是否存在。不存在则create
POST请求则每次请求都是create数据,只有指定ID一样的情况下是update
PUT/GET/DELETE http://127.0.0.1:9200/shopping
GET http://127.0.0.1:9200/_cat/indices?v
POST http://127.0.0.1:9200/shopping/_doc
{
"title":"小米手机",
"category":"小米",
"images":"http://www.gulixueyuan.com/xm.jpg",
"price":3999.00
}
GET/POST/PUT/DELETE http://127.0.0.1:9200/shopping/_doc/1
GET http://127.0.0.1:9200/shopping/_search
GET http://127.0.0.1:9200/shopping/_search
查询category字段包含关键字小米
{
"query":{
"match":{
"category":"小米"
}
}
}
查询所有数据
{
"query":{
"match_all":{}
}
}
查询指定title字段
{
"query":{
"match_all":{}
},
"_source":["title"]
}
分页查询
{
"query":{
"match_all":{}
},
"from":0,
"size":2
}
查询排序,以price排序
{
"query":{
"match_all":{}
},
"sort":{
"price":{
"order":"desc"
}
}
}
多条件查询,(must相当于数据库的 && )bool表示条件。
匹配category是小米且price是1999
{
"query":{
"bool":{
"must":[{
"match":{
"category":"小米"
}
},{
"match":{
"price":1999.00
}
}]
}
}
}
范围查询
查询小米和华为的牌子,且价格小于2000(should相当于数据库的 || )
{
"query":{
"bool":{
"should":[{
"match":{
"category":"小米"
}
},{
"match":{
"category":"华为"
}
}],
"filter":{
"range":{
"price":{
"lt":2000
}
}
}
}
}
}
全文检索
{
"query":{
"match":{
"category" : "小华"
}
}
}
完全匹配
{
"query":{
"match_phrase":{
"category" : "为"
}
}
}
高亮查询
{
"query":{
"match_phrase":{
"category" : "为"
}
},
"highlight":{
"fields":{
"category":{}//<----高亮这字段
}
}
}
聚合允许使用者对 es 文档进行统计分析,类似与关系型数据库中的 group by,当然还有很多其他的聚合,例如取最大值max、平均值avg等等。
接下来按price字段进行分组:
{
"aggs":{//聚合操作
"price_group":{//名称,随意起名
"terms":{//分组
"field":"price"//分组字段
}
}
}
}
上面返回结果会附带原始数据的。若不想要不附带原始数据的结果添加字段"size":0
{
"aggs":{
"price_group":{
"terms":{
"field":"price"
}
}
},
"size":0
}
对所有手机价格求平均值。
{
"aggs":{
"price_avg":{//名称,随意起名
"avg":{//求平均
"field":"price"
}
}
},
"size":0
}
mapping:类似于数据库(database)中的表结构(table)。拥有哪些字段和约束信息
创建索引
PUT http://127.0.0.1:9200/user
创建mapping
PUT http://127.0.0.1:9200/user/_mapping
{
"properties": {
"name":{
"type": "text", //全文检索
"index": true
},
"sex":{
"type": "keyword", // 精确匹配
"index": true
},
"tel":{
"type": "keyword",
"index": false // 没有设置索引,无法直接查询
}
}
}
查询mapping
GET http://127.0.0.1:9200/user/_mapping
添加数据
PUT http://127.0.0.1:9200/user/_create/1001
{
"name":"小米",
"sex":"男的",
"tel":"1111"
}
查询name含有"小"的数据,可以全文检索到
GET http://127.0.0.1:9200/user/_search
{
"query":{
"match":{
"name":"小"
}
}
}
查询sex含有"男"的失败,keyword需要精确匹配才能查询到
GET http://127.0.0.1:9200/user/_search
{
"query":{
"match":{
"sex":"男"
}
}
}
安装google浏览器es head插件
PUT http://127.0.0.1:9200/users
{
"settings" : {
"number_of_shards" : 3,
"number_of_replicas" : 1
}
}
可以看到3个主分片已经成功了,但是副本状态为Unassigned,它们都没有被分配到任何节点。 在同 一个节点上既保存原始数据又保存副本是没有意义的。
当启动了第二个节点,集群将会拥有两个节点 : 所有主分片和副本分片都被分配 。
当启动了第三个节点,集群为了分散负载而对分片进行重新分配 。
PUT http://127.0.0.1:1001/users/_settings
{
"number_of_replicas" : 2
}
users 索引现在拥有 9 个分片: 3 个主分片和 6 个副本分片。 这意味着我们可以将集群扩容到 9 个节点,每个节点上一个分片。相比原来 3 个节点时,集群搜索性能理论上可以提升 3 倍。
我们关闭一个主节点。集群必须拥有一个主节点来保证正常工作,所以发生的第一件事情就是选举一个新的主节点: Node 3 。在我们关闭 Node 1 的同时也失去了主分片 1 和 2 ,并且在缺失主分片的时候索引也不能正常工作。 如果此时来检查集群的状况,我们看到的状态将会为 red :不是所有主分片都在正常工作。
因为在其它节点上存在着这两个主分片的完整副本, 所以新的主节点立即将这些分片在 Node 2 和 Node 3 上对应的副本分片提升为主分片, 此时集群的状态将会为yellow。这个提升主分片的过程是瞬间发生的。
当把node1节点恢复,集群可以将缺失的副本分片再次进行分配,那么集群的状态也将恢复成之前的状态。只是master节点换了
Elasticsearch 通过下列算法知道一个文档应该存放到哪个分片中。其中routing 是一个可变值,默认是文档的 _id ,也可以设置成一个自定义的值。
shard = hash(routing) % number_of_primary_shards
为什么我们要在创建索引的时候就确定好主分片的数量并且永远不会改变这个数量?
因为如果数量变化了,那么所有之前路由的值都会无效,文档也再也找不到了。
所有的文档API ( get . index . delete 、 bulk , update以及 mget )都接受一个叫做routing 的路由参数,通过这个参数我们可以自定义文档到分片的映射。一个自定义的路由参数可以用来确保所有相关的文档都被存储到同一个分片中。
使用routing寻址会提高读写速度?
新建、索引和删除请求都是写操作, 必须在主分片上面完成之后才能被复制到相关的副本分片。
mget和 bulk API的模式类似于单文档模式。区别在于协调节点知道每个文档存在于哪个分片中。它将整个多文档请求分解成每个分片的多文档请求,并且将这些请求并行转发到每个参与节点。协调节点一旦收到来自每个节点的应答,就将每个节点的响应收集整理成单个响应,返回给客户端。
倒排索引被写入磁盘后是不可改变的:它永远不会修改。
不需要锁。如果你从来不更新索引,你就不需要担心多进程同时修改数据的问题。
一旦索引被读入内核的文件系统缓存,便会留在哪里,由于其不变性。只要文件系统缓存中还有足够的空间,那么大部分读请求会直接请求内存,而不会命中磁盘。这提供了很大的性能提升。
其它缓存(像filter缓存),在索引的生命周期内始终有效。它们不需要在每次数据改变时被重建,因为数据不会变化。
写入单个大的倒排索引允许数据被压缩,减少磁盘IO和需要被缓存到内存的索引的使用量。
如何在保留不变性的前提下实现倒排索引的更新?
用更多的索引。通过增加新的补充索引来反映新近的修改,而不是直接重写整个倒排索引。每一个倒排索引都会被轮流查询到,从最早的开始查询完后再对结果进行合并。
Elasticsearch基于Lucene,这个java库引入了按段搜索的概念,每一段本身都是一个倒排索引,最早的会被先查询。因为倒排索引是不可改变的,所以既不能把文档从旧的段中移除,也不能修改旧的段来进行反映文档的更新。
当一个文档被"删除"时,它实际上只是在 .del 文件中被标记删除(逻辑删除) 一个被标记删除的文档仍然可以被查询匹配到,但它会在最终结果被返回前从结果集中移除。
当多个倒排索引合并为一个新的时,则可以把标记删除的倒排索引删除(物理删除)
文档更新也是类似的操作方式:当一个文档被更新时,旧版本文档被标记删除,文档的新版本被索引到一个新的段中。可能两个版本的文档都会被一个查询匹配到,但被删除的那个旧版本文档在结果集返回前就已经被移除。
与数据库不同,数据库是先写入log,再写入memory。ES刚好相反,因为memory操作很复杂,必须保证操作成功后再记录log。
并不是所有的情况都需要每秒刷新。可能你正在使用Elasticsearch索引大量的日志文件,你可能想优化索引速度而不是近实时搜索,可以通过设置refresh_interval (可动态更新),降低每个索引的刷新频率。在生产环境中,当你正在建立一个大的新索引时,可以先关闭自动刷新,待开始使用该索引时,再把它们调回来。
# 关闭自动刷新
PUT /users/_settings
{ "refresh_interval": -1 }
# 每30秒刷新
PUT /users/_settings
{ "refresh_interval": "30s" }
由于自动刷新流程每秒会创建一个新的段,这样会导致短时间内的段数量暴增。而段数目太多会带来较大的麻烦。每一个段都会消耗文件句柄、内存和 cpu运行周期。更重要的是,每个搜索请求都必须轮流检查每个段;所以段越多,搜索也就越慢。
合并大的段需要消耗大量的 I/O 和 CPU 资源,如果任其发展会影响搜索性能。 Elasticsearch在默认情况下会对合并流程进行资源限制,所以搜索仍然有足够的资源很好地执行。
#GET http://localhost:9200/_analyze
{
"analyzer": "standard",
"text": "Text to analyze"
}
{
"tokens": [
{
"token": "text",
"start_offset": 0,
"end_offset": 4,
"type": "",
"position": 1
},
{
"token": "to",
"start_offset": 5,
"end_offset": 7,
"type": "",
"position": 2
},
{
"token": "analyze",
"start_offset": 8,
"end_offset": 15,
"type": "",
"position": 3
}
]
}
实际场景下还需要用到中文分析器,而IK分析器是大家推荐的:下载IK分析器
将解压后的后的文件夹放入 ES 根目录下的 plugins 目录,重启
# GET http://localhost:9200/_analyze
{
"text":"测试单词",
"analyzer":"ik_max_word"
}
{
"tokens": [
{
"token": "测试",
"start_offset": 0,
"end_offset": 2,
"type": "CN_WORD",
"position": 0
},
{
"token": "单词",
"start_offset": 2,
"end_offset": 4,
"type": "CN_WORD",
"position": 1
}
]
}
有的词汇使用默认分词规则无法匹配到,但确实是词组,这种情况下可以使用扩展词汇。
<properties>
<comment>IK Analyzer 扩展配置comment>
<entry key="ext_dict">custom.dicentry>
<entry key="ext_stopwords">entry>
properties>
可以将一些字符转换,如将&符号转换为字母and这种。这种不太常用,但是ES都是支持的。
这种方法被关系型数据库广泛使用,它假定有变更冲突可能发生,因此阻塞访问资源以防止冲突。一个典型的例子是读取一行数据之前先将其锁住,确保只有放置锁的线程能够对这行数据进行修改。这种锁对性能有很大的影响
Elasticsearch 中使用乐观锁假定冲突是不可能发生的,并且不会阻塞正在尝试的操作。如果源数据在读写当中被修改,更新将会失败。应用程序接下来将决定该如何解决冲突。例如,可以重试更新、使用新的数据、或者将相关情况报告给用户。
每个文档都有一个_version(版本号),当文档被修改时版本号递增。Elasticsearch使用这个version号来确保变更以正确顺序得到执行。
使用SSD
使用多块磁盘
不实用NFS之类的远程存储
分片数设置完成后因为路由机制无法修改,需要了解:
一个分片的底层即为一个 Lucene 索引,会消耗一定文件句柄、内存、以及 CPU运转。
每一个搜索请求都需要命中索引中的每一个分片,如果每一个分片都处于不同的节点还好, 但如果多个分片都需要在同一个节点上竞争使用相同的资源就有些糟糕了。
用于计算相关度的词项统计信息是基于分片的。如果有许多分片,每一个都只有很少的数据会导致很低的相关度。
业务需要多大分片需要预估,通常遵循以下原则:
对于节点瞬时中断的问题,默认情况,集群会等待一分钟来查看节点是否会重新加入,如果这个节点在此期间重新加入,重新加入的节点会保持其现有的分片数据,不会触发新的分片分配。这样就可以减少 ES 在自动再平衡可用分片时所带来的极大开销。
通过修改参数 delayed_timeout ,可以延长再均衡的时间,可以全局设置也可以在索引级别进行修改:
#PUT /_all/_settings
{
"settings": {
"index.unassigned.node_left.delayed_timeout": "5m"
}
}
routing 默认值是文档的 id,也可以采用自定义值,比如 userid。
不带routing查询分两步:
分发:请求到达协调节点后,协调节点将查询请求分发到每个分片上。
聚合:协调节点搜集到每个分片上查询结果,在将查询的结果进行排序,之后给用户返回结果。
带routing查询
查询的时候,可以直接根据routing 信息定位到某个分片查询,不需要查询所有的分片,经过协调节点排序。向上面自定义的用户查询,如果routing 设置为userid 的话,就可以直接查询出数据来,效率提升很多。
针对于搜索性能要求不高,但是对写入要求较高的场景,可以考虑以下几个方面来提升写索引的性能:
Lucene 以段的形式存储数据。当有新的数据写入索引时, Lucene 就会自动创建一个新的段。
如果我们对搜索的实效性要求不高, Refresh 周期默认为1秒,可以延长至 30 秒。
这样可以有效地减少段刷新次数,但这同时消耗更多的 Heap 内存。
Flush 的主要目的是把文件缓存系统中的段持久化到硬盘,当 Translog 的数据量达到 512MB 或者 30 分钟时,会触发一次 Flush。index.translog.flush_threshold_size 参数的默认值是 512MB,适当调大可以在事务日志里积累出更大的段。
当写索引时,需要把写入的数据都同步到副本节点,副本节点越多,写索引的效率就越慢。
如果我们需要大批量进行写入操作,可以先禁止Replica复制,设置
index.number_of_replicas: 0 关闭副本。在写入完成后, Replica 修改回正常的状态。
ES 堆内存的分配需要满足以下两个原则:
不要超过物理内存的 50%: Lucene 的设计目的是把底层 OS 里的数据缓存到内存中。Lucene 的段是分别存储到单个文件中的,这些文件都是不会变化的,所以很利于缓存,同时操作系统也会把这些段文件缓存起来,以便更快的访问。如果我们设置的堆内存过大, Lucene 可用的内存将会减少,就会严重影响降低 Lucene 的查询性能。
堆内存的大小最好不要超过 32GB:在 Java 中,所有对象都分配在堆上,然后有一个 Klass Pointer 指针指向它的类元数据。这个指针在 64 位的操作系统上为 64 位, 64 位的操作系统可以使用更多的内存(2^64)。在 32 位
的系统上为 32 位, 32 位的操作系统的最大寻址空间为 4GB(2^32)。
但是 64 位的指针意味着更大的浪费,因为你的指针本身大了。浪费内存不算,更糟糕的是,更大的指针在主内存和缓存器(例如 LLC, L1 等)之间移动数据的时候,会占用更多的带宽。
最终我们都会采用 31 G 设置
-Xms 31g
-Xmx 31g
假设你有个机器有 128 GB 的内存,你可以创建两个节点,每个节点内存分配不超过 32 GB。也就是说不超过 64 GB 内存给 ES 的堆内存,剩下的超过 64 GB 的内存给 Lucene。
查询index配置
GET /_settings
查询集群配置
GET /_cluster/settings
使用curl修改示例
curl -X PUT “localhost:9200/_settings” -H ‘Content-Type: application/json’ -d’{“index” : {“translog”:{“durability”: “async”,“sync_interval”:“10s”}}}’
https://www.elastic.co/guide/en/elasticsearch/reference/7.x/how-to.html
https://www.elastic.co/guide/en/elasticsearch/reference/7.x/tune-for-indexing-speed.html
https://www.elastic.co/guide/en/elasticsearch/reference/7.x/tune-for-disk-usage.html
https://www.elastic.co/guide/en/elasticsearch/reference/7.x/tune-for-search-speed.html
https://blog.csdn.net/u011863024/article/details/115721328