目录

ElasticSearch

概念

什么是搜索

输入信息(关键字),期望找到这个关键字相关的有些信息

为什么数据库做搜索不好

某些信息会很长,比如商品的描述,可能长达数千,且数据库的关键字是不能拆分的,比如输入“生化机”,就搜索不出来“生化危机”,再有就是搜寻代价大

什么是全文检索和Lucene

  • 全文检索:对数据进行拆分,放入一张映射表中 拆分的词对应出现的行号

  • Lucene,就是一个jar包,里面包含了封装好的各种建立倒排索引,以及进行搜索的代码,包括各种算法

什么是ElasticSearch

基于Lucene,隐藏复杂性,提供简单易用的restful api接口

  1. 分布式的文档存储引擎
  2. 分布式的搜索引擎和分析引擎
  3. 分布式,支持PB级数据
  4. 海量数据进行近实时的处理

自动维护数据的分布到多个结点的索引的建立

自动维护数据的冗余副本

封装了更多的高级功能

ElasticSearch概念

  1. NRT(Near Realtime) :近实时
  2. Cluster: 集群 包含多个节点,每个节点属于哪个集群是通过一个配置(集群名称,默认是elasticsearch)来决定的
  3. Node: 节点,节点的名称默认是随机分配,默认节点会去加入一个名称为“elasticsearch”的集群

如果直接启动一堆节点,那么它们会自动组成一个elasticsearch集群,当然一个节点也可以组成一个elasticsearch集群

  1. Document&field:文档,es中的最小数据单元,一个document可以是一条客户数据 用JSON进行表示,每个index下的type中,都可以去存储多个document。一个document里面有多个field
  2. Index: 索引,一个index包含很多document,一个index就代表了一类类似的或者相同的document。一个商品索引就存放了所有商品数据
  3. Type: Type是index中的一个逻辑数据分类,一个type下的document,都有相同的field
  4. Shard: 也叫Primary Shard一个Index中的数据切分为多个shard,每个shard都是一个lucene index
  5. Replica: shard的副本,可以有一个或多个,primary shard(建立索引时一次设置,不能修改,默认5个),replica shard(随时修改数量,默认1个),默认每个索引10个shard,5个primary shard,5个replica shard

最小的高可用配置,是2台服务器

Document 行 Type 表 Index 库

Windows启动

基本操作

检查集群健康状况

GET /_cat/health?v

-- green:每个索引的primary shard和replica shard都是active状态的
-- yellow:每个索引的primary shard都是active状态的,但是部分replica shard不是active状态,处于不可用的状态
-- red:不是所有索引的primary shard都是active状态的,部分索引有数据丢失了

CRUD

// 增加
PUT /ecommerce/product/1
{
    "name" : "gaolujie yagao",
    "desc" :  "gaoxiao meibai",
    "price" :  30,
    "producer" :      "gaolujie producer",
    "tags": [ "meibai", "fangzhu" ]
}
// 删除
DELETE /ecommerce/product/1
// 更改
  
// 更改对应的列 partial update 其内置了以乐观锁为基础的并发策略
POST /ecommerce/product/1/_update
{
  "doc": {
    "name": "jiaqiangban gaolujie yagao"
  }
}
/**
 * 先发起get请求,得到doc展示到前台界面
  用户在前台发送修改数据,发送到后台
  后台代码,将用户修改的数据放在内存进行执行,然后封装好修改的全量数据
  发送PUT请求,进行全量替换
  将老的doc进行标记为deleted,重新创建新的doc
 */
 
post /index/type/id/_update?retry_on_conflict=5&version=6
  
/**retry策略
1、再次获取 document数据和最新版本
2、基于最新版本号再次去更新,如果成功那么就ok了
3、如果失败,重复1和2两个步,最多重复几次呢?可以通过ret那个参数的值指定,比如5次 
*/


// 所有的查询、修改和写回操作,都发生在es中的一个 shard内部
// 替换 也可以理解为覆盖
PUT /ecommerce/product/1
{
    "name" : "jiaqiangban gaolujie yagao",
    "desc" :  "gaoxiao meibai",
    "price" :  30,
    "producer" :      "gaolujie producer",
    "tags": [ "meibai", "fangzhu" ]
}

// 查看
GET /index/type/id

// 批量查询
// 1. 查询不同index的doc
GET /_mget
{
   "docs" : [
      {
         "_index" : "test_index",
         "_type" :  "test_type",
         "_id" :    1
      },
      {
         "_index" : "test_index",
         "_type" :  "test_type",
         "_id" :    2
      }
   ]
}
// 2. 查询同一个index不同type
GET /test_index/_mget
{
   "docs" : [
      {
         "_type" :  "test_type",
         "_id" :    1
      },
      {
         "_type" :  "test_type",
         "_id" :    2
      }
   ]
}

批量CRUD

bulk api对json的语法,有严格的要求,每个json串不能换行,只能放一行,同时一个json串和一个json串之间,必须有一个换行,可以进行下面的操作

  1. delete:删除一个文档,只要1个json串就可以了
  2. create:PUT /index/type/id/_create,强制创建
  3. index:普通的put操作,可以是创建文档,也可以是全量替换文档
  4. update:执行的partial update操作

bulk操作中,任意一个操作失败,是不会影响其他的操作的,但是在返回结果里,会告诉你异常日志

POST /test_index/test_type/_bulk
{ "delete": { "_id": "3" }} 
{ "create": { "_id": "12" }}
{ "test_field":    "test12" }
{ "index":  { }}
{ "test_field":    "auto-generate id test" }
{ "index":  { "_id": "2" }}
{ "test_field":    "replaced test2" }
{ "update": { "_id": "1", "_retry_on_conflict" : 3} }
{ "doc" : {"test_field2" : "bulk test1"} }

关于bulk的奇葩格式要求:它能够节省内存开销,直接按换行符切割Json,对每两个一组的json,读取meta,进行document路由,然后直接将对应的json发送到node上,这样和按照便于理解的格式对比,不需要将json数组解析为一个JSONArray对象,形成一份大数据的拷贝,浪费内存空间,尽可能地保证性能

搜索方式

  1. query string search (不常用)
GET /ecommerce/product/_search
// hits.max_score: 匹配分数 越相关 越匹配
// hits.hits:包含了匹配搜索的document的详细数据
  1. query DSL

JSON构造查询语法

// 查询所有
GET /ecommerce/product/_search
{
  "query": { "match_all": {} }
}
// 查询名字为yagao 按照价格进行降序排序
GET /ecommerce/product/_search
{
    "query" : {
        "match" : {
            "name" : "yagao"
        }
    },
    "sort": [
        { "price": "desc" }
    ]
}
// 从第二个开始 每页显示1条数据
GET /ecommerce/product/_search
{
  "query": { "match_all": {} },
  "from": 1,
  "size": 1
}
// 查询商品的名称和价格
GET /ecommerce/product/_search
{
  "query": { "match_all": {} },
  "_source": ["name", "price"]
}
  1. query filter
// 搜索商品名称包含yagao,而且售价大于25元的商品
GET /ecommerce/product/_search
{
    "query" : {
        "bool" : {
            "must" : {
                "match" : {
                    "name" : "yagao" 
                }
            },
            "filter" : {
                "range" : {
                    "price" : { "gt" : 25 } 
                }
            }
        }
    }
}
  1. 全文检索
// 进行全文检索 只要能匹配上任意一个拆解后的单词,就可以作为结果返回
GET /ecommerce/product/_search
{
    "query" : {
        "match" : {
            "producer" : "yagao producer"
        }
    }
}
  1. phrase Search
// 必须与指定的字段文本 完全包含一模一样 才会返回
GET /ecommerce/product/_search
{
    "query" : {
        "match_phrase" : {
            "producer" : "yagao producer"
        }
    }
}

嵌套聚合 下钻分析

// 计算每个tag下的商品数量
GET /ecommerce/product/_search
{
  "aggs": {
    "group_by_tags": {
      "terms": { "field": "tags" }
    }
  }
}

// 将文本field的fielddata属性设置为true

PUT /ecommerce/_mapping/product
{
  "properties": {
    "tags": {
      "type": "text",
      "fielddata": true
    }
  }
}

GET /ecommerce/product/_search
{
  "size": 0,
  "aggs": {
    "all_tags": {
      "terms": { "field": "tags" }
    }
  }
}


// 对名称中包含yagao的商品,计算每个tag下的商品数量

GET /ecommerce/product/_search
{
  "size": 0,
  "query": {
    "match": {
      "name": "yagao"
    }
  },
  "aggs": {
    "all_tags": {
      "terms": {
        "field": "tags"
      }
    }
  }
}

// 先分组,再算每组的平均值,计算每个tag下的商品的平均价格

GET /ecommerce/product/_search
{
    "size": 0,
    "aggs" : {
        "group_by_tags" : {
            "terms" : { "field" : "tags" },
            "aggs" : {
                "avg_price" : {
                    "avg" : { "field" : "price" }
                }
            }
        }
    }
}
// 计算每个tag下的商品的平均价格,并且按照平均价格降序排序

GET /ecommerce/product/_search
{
    "size": 0,
    "aggs" : {
        "all_tags" : {
            "terms" : { "field" : "tags", "order": { "avg_price": "desc" } },
            "aggs" : {
                "avg_price" : {
                    "avg" : { "field" : "price" }
                }
            }
        }
    }
}
// 按照指定的价格范围区间进行分组,然后在每组内再按照tag进行分组,最后再计算每组的平均价格

GET /ecommerce/product/_search
{
  "size": 0,
  "aggs": {
    "group_by_price": {
      "range": {
        "field": "price",
        "ranges": [
          {
            "from": 0,
            "to": 20
          },
          {
            "from": 20,
            "to": 40
          },
          {
            "from": 40,
            "to": 50
          }
        ]
      },
      "aggs": {
        "group_by_tags": {
          "terms": {
            "field": "tags"
          },
          "aggs": {
            "average_price": {
              "avg": {
                "field": "price"
              }
            }
          }
        }
      }
    }
  }
}

ES架构

  1. 对复杂的分布式机制的透明隐藏特性 隐藏了 分片,cluster discovery,shard负载均衡,shard副本
  2. 垂直扩容: 总数不变,采用容量更大的服务器替换原来的去满足要求
  3. 水平扩容:总数改变,采用相同容量的服务器去满足要求
  4. master节点:
    1. 创建/删除索引
    2. 增加/删除节点
  5. 节点平等的分布式架构
    1. 节点对等,每个节点接收所有的请求
    2. 自动请求路由
    3. 响应收集
  6. 增减节点时的数据rebalance 保持负载均衡

shard&replica机制梳理

  1. Index包含多个shard
  2. 每个shard都是一个最小工作单元,承载部分数据,lucene实例,完整的建立索引和处理请求的能力
  3. 增减节点时,shard会自动在nodes中负载均衡
  4. primary shard和replica shard,每个document肯定只存在于某一个primary shard以及其对应的replica shard中,不可能存在于多个primary shard
  5. replica shard是primary shard的副本,负责容错,以及承担读请求负载
  6. primary shard的数量在创建索引的时候就固定了,replica shard的数量可以随时修改
  7. primary shard的默认数量是5,replica默认是1,默认有10个shard,5个primary shard,5个replica shard
  8. primary shard不能和自己的replica shard放在同一个节点上(否则节点宕机,primary shard和副本都丢失,起不到容错的作用),但是可以和其他primary shard的replica shard放在同一个节点上

2个Node下的replica shard分配

p(0,1,2) 放在第一个Node 其副本R(0,1,2)放在第二个Node,同步更新

横向扩容

primary&replica自动负载均衡,A (6个shard,3 primary,3 replica),Node上的Shard越少,得到的CPU,I/O资源越多,A条件下,假如给3台机器,则能容忍1台机器宕机,A条件下,6台机器时的性能最好,但超出扩容极限,就要动态修改replica数量

es容错机制

一个node故障,就会进行master选举,自动选举另一个node成为新的master,新的master会将原来的primary shard的副本提升为primary,之后重启故障的node,将缺失的副本copy一份到node上

document

_index

类似的数据放在一个索引中,非类似的放不同索引

_type

代表document属于index中的哪个类别。例:商品,可能划分为电子商品,生鲜商品,日化商品

_id

document的唯一标识,与index和type一起,可以唯一标识和定位一个document

  • 手动指定

可以使用系统中已有数据的唯一标识,作为es中document的id,比如将Mysql中的数据导入es,就使用Mysql中的主键

  • 自动创建

自动生成的id,长度为20个字符,URL安全,base64编码,GUID,分布式系统并行生成时不可能会发生冲突

_source

返回所有
GET index/type/id
定制返回
GET index/type/id?_source=source_name1

替换与删除

doc_id存在 就是替换,不存在就是创建,doc为不可变,要修改内容,就要进行全量替换,直接重新建立索引,替换里面所有的内容

es会将老的doc标记为delete,之后对比较老的doc且已经标记了的去删除

document原理

数据路由管理

一个index的数据是放在shard上的,用于添加doc的时候,就要决定是放在Index的哪个Shard上

路由算法

shard = hash(routing) % number_of_primary_shards

routing: 默认为doc的_id,也可以在发送请求的时候,手动指定一个routing value,例

// 手动routing可以保证一定会被路由到某个shard
put /index/type/id?routing=user_id

primary shard的 index建立,是不允许修改的。但是 replica Shard可以随时修改

doc查询

client任意选择一个node发送read请求,之后这个node就用来对请求进行路由(协调节点),采用round-robin随机轮询算法,让P0和R0都有Read请求,实现负载均衡,可能doc还在建立索引过程,只在p0上存在,而R0没有,此时若请求路由给了R0,会返回找不到该文档

doc删除
  1. 客户端选择一个Node发送请求,将其作为协调节点
  2. 之后这个协调节点就对doc进行路由,将请求转给对应的node
  3. node的p0对请求进行处理,然后将数据同步到r0
  4. 发现p0和r0都搞定后,返回结果

一致性原理

发送任何一个增删改的时候,可以带上consistency参数,指明想要的一致性是什么

  • one:要求我们这个写操作,只要有一个primary shard是active活跃可用的,就可以执行

  • all:要求我们这个写操作,必须所有的primary shard和replica shard都是活跃的,才可以执行这个写操作

  • quorum:默认的值,要求所有的shard中,必须是大部分的shard都是活跃的,可用的,才可以执行这个写操作

默认的要求写之前,大部分的shard都是活跃的,才可执行写操作

doc写入原理

lucene底层的 index是分为多个segment的,每个segmen都会存放部分数据 和redo log好像

写buffer,刷到index segment,然后index segment又被fsync到磁盘(且被打开,供search使用,buffer被清空)

fsync是比较费时间的,但要想被搜索到就要等到fsync,可以进行改进

每秒写到index segment后,再刷到os cache,此时(直接打开供search使用) fresh: 数据写入os cache,并被打开可供搜索

-- 可以自己定义,但最好不要自己动,让es自动处理
PUT /my_index
{
  "settings": {
    "refresh_interval": "30s" 
  }
}

再次优化 实现数据恢复

写入segment的时候,又写入translog中,translog达到一定程度,进行commit,然后buffer写入segment,commit写入磁盘,表明了之前所有写的segment,用fsync把cache写入磁盘上,写完后,清空translog,创建新的translog

fsync+清空translog,就是flush,默认每隔30分钟flush一次,或者当translog过大的时候,也会flush

translog,每隔5秒被fsync一次到磁盘上。在一次增删改操作之后,当fsync在primary shardreplica shard都成功之后,那次增删改操作才会成功,比较耗时,可以设置为异步

PUT /my_index/_settings
{
    "index.translog.durability": "async",
    "index.translog.sync_interval": "5s"
}

最终优化

每1s刷到segment,文件多的时候,查询每次都要遍历,十分耗时,此时可以优化为merge

(1)选择一些有相似大小的segment,merge成一个大的segment (2)将新的segment flush到磁盘上去 (3)写一个新的commit point,包括了新的segment,并且排除旧的那些segment (4)将新的segment打开供搜索 (5)将旧的segment删除

POST /my_index/_optimize?max_num_segments=1 还是尽量不要自己设置

删除

每次commit point时,会有一个.del文件,标记了哪些segment中的哪些document被标记为deleted了

me弄到一定程度的index segment,就会进行一次commit point,会有个.del文件 判断哪些segment被标记为deleted

更新

对于更新操作,是将现有的doc标记为deleted,写入新的index segment,下次search的时候,会排除deleted标记的版本,即找到最新的版本

并发

多线程情况下,读取相同的数据,可能最终结果与理论上的结果的不一致

解决方案:

悲观锁

每次拿到数据都进行加锁

乐观锁

利用version版本号字段进行修改,要求当前版本号与存的时候的版本号要相同,否则要先进行读取,拿到最新的值

PUT /test_index/test_type/7?version=1 
{
  "test_field": "test client 1"
}

es的P0同步到R0是多线程异步的,不保证顺序,为了保证数据一致性,是基于版本号字段进行修改的

external version

当然也可以不用es内置的_version,而使用自己维护的版本号

?version=1&version_type=external

和内置的区别在于,version,只有当你提供的version和es中的,_version一样时,才能进行修改,而version_type = external的时候,只有比es_version大才能完成修改

PUT /test_index/test_type/8?version=2&version_type=external
{
  "test_field": "test client 1"
}

脚本

内置的脚本支持的,可以基于groovy脚本实现各种各样的复杂操作

内置脚本

POST /test_index/test_type/11/_update
{
   "script" : "ctx._source.num+=1"
   // D:\...\elasticsearch-5.2.0\config\scripts下进行编写
}

外部脚本

ctx._source.tags+=new_tag

POST /test_index/test_type/11/_update
{
  "script": {
    "lang": "groovy", 
    "file": "test-add-tags",
    "params": {
      "new_tag": "tag1"
    }
  }
}

用脚本删除文档

ctx.op = ctx._source.num == count ? 'delete' : 'none'

POST /test_index/test_type/11/_update
{
  "script": {
    "lang": "groovy",
    "file": "test-delete-document",
    "params": {
      "count": 1
    }
  }
}

搜索引擎

timeout

默认无timeout,latency平衡completeness,手动指定timeout,达到timeout时间,在 timeout时间范围内,将搜索到的部分数据(也可能全都搜索到了),直接理解返回给 client程序

GET /_search?timeout=10m

multi-index和multi-type搜索模式

/_search:所有索引,所有type下的所有数据都搜索出来
/index1/_search:指定一个index,搜索其下所有type的数据
/index1,index2/_search:同时搜索两个index下的数据
/*1,*2/_search:按照通配符去匹配多个索引
/index1/type1/_search:搜索一个index下指定的type的数据
/index1/type1,type2/_search:可以搜索一个index下多个type的数据
/index1,index2/type1,type2/_search:搜索多个index下的多个type的数据
/_all/type1,type2/_search:_all,可以代表搜索所有index下的指定type的数据

搜索原理

client发送一个搜索请求,会把请求打到所有的primary shard上去执行,因为每个shard都包含部分数据,所以每个shard上都可能会包含搜索请求的结果

分页搜索

size,from

GET /_search?size=10
GET /_search?size=10&from=0
GET /_search?size=10&from=20

GET /test_index/test_type/_search

"hits": {
    "total": 9,
    "max_score": 1,
}

deep paging

简单来说,就是搜索的特别深,200000条数据,每页10条,你搜1000页,实际就拿到10000~10010的数据,就要从其他的node将数据发给协调节点,然后进行统筹并排序,再根据分数_source进行返回,极其耗费内存,I/O,网络带宽

搜索的相关参数

  1. preference:两个doc的field值相同,但不同的shard上,可能排序不同,轮询到不同replica shard看到的搜索结果的排序都不一样,要查询的结果都是一样,可以设置preference的值
  2. timeout:限定在一定时间内,将部分获取到的数据直接返回
  3. routing:document文档路由,_id路由,routing=user_id,这样的话可以让同一个user对应的数据到一个shard上去

scoll

批批检索数据,不是分页

先搜索一批数据,然后下次再搜索一批数据,以此类推,直到搜索出全部的数据来,第一次搜索的时候,保存一个当时的视图快照,之后基于这个快照进行搜索

GET /test_index/test_type/_search?scroll=1m
{
  "query": {
    "match_all": {}
  },
  "sort": [ "_doc" ],
  "size": 3
}

GET /_search/scroll
{
    "scroll": "1m", 
    "scroll_id" : "DnF1ZXJ5VGhlbkZldGNoBQAAAAAAACxeFjRvbnNUWVZaVGpHdklqOV9zcFd6MncAAAAAAAAsYBY0b25zVFlWWlRqR3ZJajlfc3BXejJ3AAAAAAAALF8WNG9uc1RZVlpUakd2SWo5X3NwV3oydwAAAAAAACxhFjRvbnNUWVZaVGpHdklqOV9zcFd6MncAAAAAAAAsYhY0b25zVFlWWlRqR3ZJajlfc3BXejJ3"
}

query string用法

GET /test_index/test_type/_search?q=test_field:test
GET /test_index/test_type/_search?q=+test_field:test
GET /test_index/test_type/_search?q=-test_field:test
-- + : 包含tes 词  - : 不保存 test 词

Mapping

就是index的type的元数据,每个type都有一个自己的mapping,决定了数据类型,建立倒排索引的行为,还有进行搜索的行为

GET /website/article/_search?q=2017			3条结果             

GET /website/article/_search?q=2017-01-01        	3条结果

GET /website/article/_search?q=post_date:2017-01-01   	1条结果

GET /website/article/_search?q=post_date:2017         	1条结果

搜索结果为什么不一致,因为es自动建立mapping的时候,设置了不同的field不同的data type。不同的data type的分词、搜索等行为是不一样的。所以出现了_all field和post_date field的搜索表现完全不一样

es里面直接插入数据,es会自动建立索引,同时建立type以及对应的mapping

mapping中自动定义了每个field的数据类型

不同的数据类型(比如说text和date),可能有的是exact value,有的是full text 所以搜索方式就不同

可以用es的dynamic mapping,让其自动建立mapping,包括自动设置数据类型

数据类型

string

byte,short,integer,long

float,double

boolean

date

dynamic mapping就是对给定的文本进行数据类型的判断

empty: null,[],[null]

Query DSL

-- 基本语法
{
    QUERY_NAME: {
        ARGUMENT: VALUE,
        ARGUMENT: VALUE,...
    }
}
-- 组合多个搜索条件 title必须包含elasticsearch,content可以包含elasticsearch也可以不包含,author_id必须不为111
query
  bool
    [must,should,must_not,filter]
GET /website/article/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "title": "elasticsearch"
          }
        }
      ],
      "should": [
        {
          "match": {
            "content": "elasticsearch"
          }
        }
      ],
      "must_not": [
        {
          "match": {
            "author_id": 111
          }
        }
      ]
    }
  }
}
-- 单纯的过滤
query
  constant_score
    filter
      range

关于分数的计算,子查询去计算一个doc针对它的相关度分数,然后bool去综合所有的分数

精确匹配与全文检索

精确匹配

必须完全一样

全文(正常化)匹配

可以缩写格式转换大小写同义词

内置分词器

正常化可以提升召回率,进行一些character filter,预处理,去除html标签啊,特殊符号

召回率: 搜索的时候,增加搜索到结果的数量

  • standard analyzer:set, the, shape, to, semi, transparent, by, calling, set_trans, 5(默认的是standard)
  • simple analyzer:set, the, shape, to, semi, transparent, by, calling, set, trans
  • whitespace analyzer:Set, the, shape, to, semi-transparent, by, calling, set_trans(5)
  • language analyzer(特定的语言的分词器,比如说,english,英语分词器):set, shape, semi, transpar, call, set_tran, 5

默认的分词器

standard tokenizer:以单词边界进行切分 standard token filter:什么都不做 lowercase token filter:将所有字母转换为小写 stop token filer(默认被禁用):移除停用词,比如a the it等等

-- 启动english停用token filter
PUT /my_index
{
  "settings": {
    "analysis": {
      "analyzer": {
        "es_std": {
          "type": "standard",
          "stopwords": "_english_"
        }
      }
    }
  }
}
-- 对text使用standard分词器
GET /my_index/_analyze
{
 "analyzer": "standard", 
  "text": "a dog is in the house"
}
-- 对某个字段使用分词器
PUT /my_index/_mapping/my_type
{
"properties": {
    "content": {
      "type": "text",
      "analyzer": "my_analyzer"
    }
  }
}

定制分词器

PUT /my_index
{
  "settings": {
    "analysis": {
      "char_filter": {
        "&_to_and": {
          "type": "mapping",
          "mappings": ["&=> and"]
        }
      },
      "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"]
        }
      }
    }
  } 
}

query string分词

分为

  • post_date,date:exact value
  • _all:full text document所有的field都会拼接成一个大串,进行分词

不同类型的field,可能有的就是full text,有的就是exact value

测试分词器

GET /_analyze
{
  "analyzer": "standard",
  "text": "Text to analyze"
}

Filter 与 Query

  • filter:仅仅只是按照搜索条件过滤出需要的数据而已,不计算任何相关度分数,对相关度没有任何影响,内置的自动cache最常使用filter的数据
  • query:会去计算每个document相对于搜索条件的相关度,并按照相关度进行排序,无法cache结果

Validate

验证复杂搜索的时候是否合法

GET

string fileld排序

创建文档的时候 在field的时候,指定新的

"article": {
      "properties": {
        "title": {
          "type": "text",
          "fields": {
            "raw": {
              "type": "string",
              "index": "not_analyzed"
            }
          },
          "fielddata": true
        },
         "content": {
          "type": "text"
        }
     }
   }

相关度评分算法

  • relevance score算法:计算出,一个索引中的文本,与搜索文本,他们之间的关联匹配程度

ES使用的是term frequency/inverse document frequency算法,简称为TF/IDF算法

Term frequency: 搜索文本中的各个词条在field文本中出现了多少次,出现次数越多,就越相关

Inverse document frequency:文本中的各个词条在整个索引的所有文档中出现了多少次,出现的次数越多,就越不相关

Field-length norm:field长度,field越长,相关度越弱

doc value

搜索的时候,是使用倒排索引,排序的时候,使用的是正排索引(doc value) ,其是被保存于磁盘,若内存足够,啧会缓存于内存

query phase

  1. 多个node,还是选一个node作为coordinate node,构建一个priority queue,长度以paging操作from和size,默认为10
  2. 然后coordinate node 将所有的请求转发到所有shard 每个Shard本地搜索,并构建一个本地的priority queue
  3. 各个shard将自己的priority queue返回给coordinate node,并构建一个全局的priority queue

索引

创建

PUT /my_index
{
    "settings": { ... any settings ... },
    "mappings": {
        "type_one": { ... any mappings ... },
        "type_two": { ... any mappings ... },
        ...
    }
}

修改

PUT /my_index/_settings
{
    "number_of_replicas": 1
}

删除

DELETE /my_index
DELETE /index_one,index_two
DELETE /_all

为了避免手滑删除数据,可以去elasticsearch.yml中设置 action.destructive_requires_name: true,必须指定名称

重建索引

一个field的设置是不能被修改的,如果要修改一个Field,那么应该重新按照新的mapping建立一个index,然后将数据批量查询出来,重新用bulk api写入index中

比如想把一个type的title field从date类型改为text,则需要进行reindex,重新建立一个索引将旧索引的数据查询出来,再导入新索引

  1. 一开始的时候就给Java应用起一个别名,让Java使用别名

    PUT /my_index/_alias/goods_index
    
    
  2. 创建一个新的index及原来名字的type

    PUT /my_index_new
    {
    "mappings": {
    "my_type": {
      "properties": {
        "title": {
          "type": "text"
        }
      }
    }
    }
    }
    
    
  3. 使用scroll api将数据批量查出来并批量写入新索引,循环执行,直到全部写入

-- 查询
GET /my_index/_search?scroll=1m
{
    "query": {
        "match_all": {}
    },
    "sort": ["_doc"],
    "size":  1
}
-- 写入
POST /_bulk
{ "index":  { "_index": "my_index_new", "_type": "my_type", "_id": "2" }}
{ "title":    "2017-01-02" }
  1. 切换之前的别名并检查是否切换成功
POST /_aliases
{
    "actions": [
        { "remove": { "index": "my_index", "alias": "goods_index" }},
        { "add":    { "index": "my_index_new", "alias": "goods_index" }}
    ]
}

GET /goods_index/my_type/_search

倒排索引

最简单的来说就是一个关键词对应着doc 1 doc 2 是否出现过

但实际上有许多结构

  1. 包含这个关键词的document list
  2. 包含这个关键词的所有document的数量
  3. 这个关键词在每个document中出现的次数
  4. 这个关键词在这个document中的次序
  5. 每个document的长度:length norm
  6. 包含这个关键词的所有document的平均长度
优点
  1. 不需要锁,提升并发能力,避免锁的问题
  2. 数据不变,一直保存在os cache中,只要cache内存足够
  3. filter cache一直驻留在内存,因为数据不变
  4. 可以压缩,节省cpu和io开销
缺点

有更改的时候,需要进行重新构建

Type的数据结构

index中用来区分类似的数据,而在lucene是没有type的概念,在document中,实际上将type作为一个document的field来存储 例如

{
   "ecommerce": {
      "mappings": {
         "elactronic_goods": {
            "properties": {
               "name": {
                  "type": "string",
               },
               "price": {
                  "type": "double"
               },
	       "service_period": {
		  "type": "string"
	       }			
            }
         },
         "fresh_goods": {
            "properties": {
               "name": {
                  "type": "string",
               },
               "price": {
                  "type": "double"
               },
	       "eat_period": {
		  "type": "string"
	       }
            }
         }
      }
   }
}


{
  "name": "geli kongtiao",
  "price": 1999.0,
  "service_period": "one year"
}

实际的存储是这样的

{
  "_type": "elactronic_goods",
  "name": "geli kongtiao",
  "price": 1999.0,
  "service_period": "one year",
  "eat_period": ""
}

所以是将类似的type放在一个index下,type应该是有多个field是相同的,若将两个type的field完全不同,每条数据会像上面那样产生很多的空值

Root Object

某个type对应的mapping json,包括了properties,metadata(_id,_source,_type),settings(analyzer),其他settings(比如include_in_all)

properties

PUT /my_index/_mapping/my_type
{
  "properties": {
    "title": {
      "type": "text"
    }
  }
}

_source

  1. 查询的时候,直接可以拿到完整的document,不需要先拿document id,再发送一次请求拿document
  2. partial update基于_source实现
  3. reindex时,直接基于_source实现,不需要从数据库(或者其他外部存储)查询数据再修改
  4. 可以基于_source定制返回field
  5. debug query更容易,因为可以直接看到_source
-- 禁用_source
PUT /my_index/_mapping/my_type2
{
  "_source": {"enabled": false}
}

_all

所有field打包在一起,作为一个all field,建立索引,没指定任何field进行搜索时,就是使用_all field在搜索

Dynamic mapping

true:遇到陌生字段,就进行dynamic mapping false:遇到陌生字段,就忽略 strict:遇到陌生字段,就报错

PUT /my_index
{
  "mappings": {
    "my_type":{
      "dynamic":"strict",
      "properties": {
        "title":{
          "type": "text"
        },
        "address":{
          "type": "object",
          "dynamic":"true"
        }
      }
    }
  }
}

定制策略

  1. date_detection

默认按照一定格式识别date,可以手动关闭某个type的date_detection,如果有需要,自己手动指定某个field为date类型

PUT /my_index/_mapping/my_type
{
    "date_detection": false
}
  1. dynamic mapping template (type level)
PUT /my_index
{
  "mappings": {
    "my_type":{
      "dynamic_templates":[
        {
          "en":{
            "match":"*_en",
            "match_mapping_type":"string",
            "mapping":{
              "type":"string",
              "analyzer":"english"
            }
          }
        }
        ]
    }
  }
}
  • title没有匹配到任何的dynamic模板,默认就是standard分词器,不会过滤停用词,is会进入倒排索引,用is来搜索是可以搜索到的
  • title_en匹配到了dynamic模板,就是english分词器,会过滤停用词,is这种停用词就会被过滤掉,用is来搜索就搜索不到
  1. 定制自己的default mapping template(index level
PUT /my_index
{
    "mappings": {
        "_default_": {
            "_all": { "enabled":  false }
        },
        "blog": {
            "_all": { "enabled":  true  }
        }
    }
}