目录
ElasticSearch
概念
什么是搜索
输入信息(关键字),期望找到这个关键字相关的有些信息
为什么数据库做搜索不好
某些信息会很长,比如商品的描述,可能长达数千,且数据库的关键字是不能拆分的,比如输入“生化机”,就搜索不出来“生化危机”,再有就是搜寻代价大
什么是全文检索和Lucene
全文检索:对数据进行拆分,放入一张映射表中 拆分的词对应出现的行号
Lucene,就是一个jar包,里面包含了封装好的各种建立倒排索引,以及进行搜索的代码,包括各种算法
什么是ElasticSearch
基于Lucene,隐藏复杂性,提供简单易用的restful api接口
- 分布式的文档存储引擎
- 分布式的搜索引擎和分析引擎
- 分布式,支持PB级数据
- 海量数据进行近实时的处理
自动维护数据的分布到多个结点的索引的建立
自动维护数据的冗余副本
封装了更多的高级功能
ElasticSearch概念
NRT
(Near Realtime) :近实时Cluster
: 集群 包含多个节点,每个节点属于哪个集群是通过一个配置(集群名称,默认是elasticsearch)来决定的Node
: 节点,节点的名称默认是随机分配,默认节点会去加入一个名称为“elasticsearch”的集群
如果直接启动一堆节点,那么它们会自动组成一个elasticsearch集群,当然一个节点也可以组成一个elasticsearch集群
Document&field
:文档,es中的最小数据单元,一个document可以是一条客户数据 用JSON
进行表示,每个index下的type中,都可以去存储多个document。一个document里面有多个fieldIndex
: 索引,一个index包含很多document,一个index就代表了一类类似的或者相同的document。一个商品索引就存放了所有商品数据Type
: Type是index中的一个逻辑数据分类,一个type下的document,都有相同的fieldShard
: 也叫Primary Shard
一个Index
中的数据切分为多个shard,每个shard都是一个lucene indexReplica
: 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串之间,必须有一个换行
,可以进行下面的操作
- delete:删除一个文档,只要1个json串就可以了
- create:PUT /index/type/id/_create,强制创建
- index:普通的put操作,可以是创建文档,也可以是全量替换文档
- 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对象
,形成一份大数据的拷贝,浪费内存空间,尽可能地保证性能
搜索方式
- query string search (不常用)
GET /ecommerce/product/_search
// hits.max_score: 匹配分数 越相关 越匹配
// hits.hits:包含了匹配搜索的document的详细数据
- 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"]
}
- query filter
// 搜索商品名称包含yagao,而且售价大于25元的商品
GET /ecommerce/product/_search
{
"query" : {
"bool" : {
"must" : {
"match" : {
"name" : "yagao"
}
},
"filter" : {
"range" : {
"price" : { "gt" : 25 }
}
}
}
}
}
- 全文检索
// 进行全文检索 只要能匹配上任意一个拆解后的单词,就可以作为结果返回
GET /ecommerce/product/_search
{
"query" : {
"match" : {
"producer" : "yagao producer"
}
}
}
- 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架构
- 对复杂的分布式机制的透明隐藏特性 隐藏了 分片,cluster discovery,shard负载均衡,shard副本
- 垂直扩容: 总数不变,采用容量更大的服务器替换原来的去满足要求
- 水平扩容:总数改变,采用相同容量的服务器去满足要求
- master节点:
- 创建/删除索引
- 增加/删除节点
- 节点平等的分布式架构
- 节点对等,每个节点接收所有的请求
- 自动请求路由
- 响应收集
- 增减节点时的数据rebalance 保持负载均衡
shard&replica机制梳理
- Index包含多个shard
- 每个shard都是一个最小工作单元,承载部分数据,lucene实例,完整的建立索引和处理请求的能力
- 增减节点时,shard会自动在nodes中负载均衡
- primary shard和replica shard,每个document肯定只存在于某一个primary shard以及其对应的replica shard中,不可能存在于多个primary shard
- replica shard是primary shard的副本,负责容错,以及承担读请求负载
- primary shard的数量在创建索引的时候就固定了,replica shard的数量可以随时修改
- primary shard的默认数量是5,replica默认是1,默认有10个shard,5个primary shard,5个replica shard
- 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删除
- 客户端选择一个Node发送请求,将其作为协调节点
- 之后这个协调节点就对doc进行路由,将请求转给对应的node
- node的p0对请求进行处理,然后将数据同步到r0
- 发现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 shard
和replica 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,网络带宽
搜索的相关参数
- preference:两个doc的field值相同,但不同的shard上,可能排序不同,轮询到不同replica shard看到的搜索结果的排序都不一样,要查询的结果都是一样,可以设置preference的值
- timeout:限定在一定时间内,将部分获取到的数据直接返回
- 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
- 多个node,还是选一个node作为coordinate node,构建一个priority queue,长度以paging操作from和size,默认为10
- 然后coordinate node 将所有的请求
转发到所有shard
每个Shard本地搜索,并构建一个本地的priority queue - 各个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,重新建立一个索引将旧索引的数据查询出来,再导入新索引
一开始的时候就给Java应用起一个别名,让Java使用别名
PUT /my_index/_alias/goods_index
创建一个新的index及原来名字的type
PUT /my_index_new { "mappings": { "my_type": { "properties": { "title": { "type": "text" } } } } }
使用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" }
- 切换之前的别名并检查是否切换成功
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 是否出现过
但实际上有许多结构
- 包含这个关键词的
document list
- 包含这个关键词的所有document的
数量
- 这个关键词在每个document中出现的
次数
- 这个关键词在这个document中的
次序
- 每个document的
长度
:length norm - 包含这个关键词的所有document的
平均长度
优点
- 不需要锁,提升并发能力,避免锁的问题
- 数据不变,一直保存在os cache中,只要cache内存足够
- filter cache一直驻留在内存,因为数据不变
- 可以压缩,节省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
- 查询的时候,直接可以拿到完整的document,不需要先拿document id,再发送一次请求拿document
- partial update基于_source实现
- reindex时,直接基于_source实现,不需要从数据库(或者其他外部存储)查询数据再修改
- 可以基于_source定制返回field
- 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"
}
}
}
}
}
定制策略
- date_detection
默认按照一定格式识别date,可以手动关闭某个type的date_detection,如果有需要,自己手动指定某个field为date类型
PUT /my_index/_mapping/my_type
{
"date_detection": false
}
- 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来搜索就搜索不到
- 定制自己的default mapping template(
index level
)
PUT /my_index
{
"mappings": {
"_default_": {
"_all": { "enabled": false }
},
"blog": {
"_all": { "enabled": true }
}
}
}