shawsun 2020-04-07
Elastic Search 是分布式的,但是对于我们开发者来说并未过多的参与其中,我们只需启动对应数量的节点,并给它们分配相同的 cluster.name,让它们归属于同一个集群,创建索引的时候只需指定索引主分片数和副分片数即可,其他的都交给了 ES 内部自己去实现。
这和数据库的分布式和同源的 solr 实现分布式都是有区别的,数据库要做集群分布式,比如分库分表需要我们指定路由规则和数据同步策略等,包括读写分离,主从同步等,solr 的分布式也需依赖 zookeeper,但是 Elastic Search 完全屏蔽了这些。
虽然 Elastic Search 天生就是分布式的,并且在设计时屏蔽了分布式的复杂性,但是我们还得知道它内部的原理。


当新增一个文档的时候,文档会被存储到一个主分片中。 Elastic Search 如何知道一个文档应该存放到哪个分片中呢?当我们创建文档时,它如何决定这个文档应当被存储在分片 1 还是分片 2 中呢?
首先这肯定不会是随机的,否则将来要获取文档的时候我们就不知道从何处寻找了。实际上,这个过程是根据下面这个公式决定的:
shard = hash(routing) % number_of_primary_shards
routing 是一个可变值,默认是文档的 _id,也可以设置成一个自定义的值。routing通过 hash 函数生成一个数字,然后这个数字再除以 number_of_primary_shards (主分片的数量)后得到余数。这个分布在 0 到 number_of_primary_shards-1 之间的余数,就是我们所寻求的文档所在分片的位置。
这就解释了为什么我们要在创建索引的时候就确定好主分片的数量并且永远不会改变这个数量:因为如果数量变化了,那么所有之前路由的值都会无效,文档也再也找不到了。
新增一个文档(指定id)
PUT /nba/_doc/1
{
"name": "哈登",
"team_name": "?火箭",
"position": "得分后卫",
"play_year": "10",
"jerse_no": "13"
}查看文档在哪个分片上
GET /nba/_search_shards?routing=1
返回值如下:
{
"nodes": {
"V1JO7QXLSX-yeVI82WkgtA": {
"name": "node-1",
"ephemeral_id": "_d96PgOSTnKo6nrJVqIYpw",
"transport_address": "192.168.1.101:9300",
"attributes": {
"ml.machine_memory": "8589934592",
"xpack.installed": "true",
"ml.max_open_jobs": "20"
}
},
"z65Hwe_RR_efA4yj3n8sHQ": {
"name": "node-3",
"ephemeral_id": "MOE_Ne7ZRyaKRHFSWJZWpA",
"transport_address": "192.168.1.101:9500",
"attributes": {
"ml.machine_memory": "8589934592",
"ml.max_open_jobs": "20",
"xpack.installed": "true"
}
}
},
"indices": {
"nba": {}
},
"shards": [
[
{
"state": "STARTED",
"primary": true,
"node": "V1JO7QXLSX-yeVI82WkgtA",
"relocating_node": null,
"shard": 2,
"index": "nba",
"allocation_id": {
"id": "leX_k6McShyMoM1eNQJXOA"
}
},
{
"state": "STARTED",
"primary": false,
"node": "z65Hwe_RR_efA4yj3n8sHQ",
"relocating_node": null,
"shard": 2,
"index": "nba",
"allocation_id": {
"id": "6sUSANMuSGKLgcIpBa4yYg"
}
}
]
]
}Elastic Search 中对文档的 index, GET 和 delete 请求时,都会返回一个 _version,当文档被修改时版本号递增。
所有文档的更新或删除 API,都可以接受 version 参数,这允许你在代码中使用乐观的并发控制,这里要注意的是版本号要大于旧的版本号,并且加上 version_type=external。
获取文档
GET /nba/_doc/1
返回值如下:
{
"_index": "nba",
"_type": "_doc",
"_id": "1",
"_version": 1,
"_seq_no": 4,
"_primary_term": 7,
"found": true,
"_source": {
"name": "哈登",
"team_name": "?火箭",
"position": "得分后卫",
"play_year": "10",
"jerse_no": "13"
}
}通过版本号新增文档(version 要大于旧的 version)
POST /nba/_doc/1?version=2&version_type=external
参数:
{
"name": "哈登",
"team_name": "?火箭",
"position": "得分后卫",
"play_year": "10",
"jerse_no": "13"
}我们打开 NBA 中国官网,搜索 james 得到以下结果

假设文档集合如下图所示

假设文档集合如下图所示

完善倒排索引

参数解释
前言一
我们创建一个文档
PUT test/_doc/1
参数:{
"msg": "乔丹是篮球之神"
}我们通过‘乔丹‘这个关键词来搜索这个文档
POST /test/_search
参数:
{
"query": {
"match": {
"msg": "乔丹"
}
}
}我们发现能匹配文档出来,那整一个过程的原理是怎样的呢?
前言二
我们来试下使用中文分词器
PUT test/_mapping
参数:
{
"properties": {
"msg_chinese": {
"type": "text",
"analyzer": "ik_max_word"
}
}
}
POST test/_doc/1
参数:
{
"msg": "乔丹是篮球之神",
"msg_chinese": "乔丹是篮球之神"
}
POST /test/_search
参数:
{
"query": {
"match": {
"msg_chinese": "乔"
}
}
}
POST /test/_search
参数:
{
"query": {
"match": {
"msg": "乔"
}
}
}为什么同样是输入‘乔‘,为什么 msg 能匹配出文档,而 msg_chinese 不能呢?
写时分词
我们来分析 msg 这个字段是怎样分词的
POST test/_analyze
参数:
{
"field": "msg",
"text": "乔丹是篮球之神"
}
返回值:
乔,丹,是,篮,球,之,神再来分析 msg_chinese 这个字段是怎样分词的
POST test/_analyze
参数:
{
"field": "msg_chinese",
"text": "乔丹是篮球之神"
}
返回值:
乔丹, 是, 篮球, 之神文档写入的时候会根据字段设置的分词器类型进行分词,如果不指定就是默认的 standard 分词器。
写时分词器需要在 mapping 中指定,而且一旦指定就不能再修改,若要修改必须重建索引。
读时分词
由于读时分词器默认与写时分词器默认保持一致,拿上面的例子,你搜索 msg 字段,那么读时分词器为 standard ,搜索 msg_chinese 时分词器则为 ik_max_word。这种默认设定也是非常容易理解的,读写采用一致的分词器,才能尽最大可能保证分词的结果是可以匹配的。
允许读时分词器单独设置
POST test/_search
参数:
{
"query": {
"match": {
"msg_chinese": {
"query": "乔丹",
"analyzer": "standard"
}
}
}
}一般来讲不需要特别指定读时分词器,如果读的时候不单独设置分词器,那么读时分词器的验证方法与写时一致。
深入分析

分析器(analyzer)有三部分组成
char filter(字符过滤器)
字符过滤器以字符流的形式接收原始文本,并可以通过添加、删除或更改字符来转换该流。一个分析器可能有0个或多个字符过滤器。
tokenizer (分词器)
一个分词器接收一个字符流,并将其拆分成单个 token (通常是单个单词),并输出一个 token 流。比如使用 whitespace 分词器当遇到空格的时候会将文本拆分成 token。
"eating an apple" >> [eating, an, apple]。一个分析器必须只能有一个分词器
POST _analyze
{
"text": "eating an apple",
"analyzer": "whitespace"
}token filter (token过滤器)
token 过滤器接收 token 流,并且可能会添加、删除或更改 tokens。比如一个 lowercase token filter 可以将所有的 token 转成小写。一个分析器可能有0个或多个 token 过滤器,它们按顺序应用。
standard分析器