ElasticSearch系列一:安装,使用,搜索

此处需补充: 安装cerebro及es集群

1.windows版

1.1安装ElasticSearch(以6.5.4版本为例)

1、安装JDK,至少1.8.0_73以上版本
2、下载和解压缩Elasticsearch安装包,目录结构
3、启动Elasticsearch:bin\elasticsearch.bat,es本身特点之一就是开箱即用,如果是中小型应用,数据量少,操作不是很复杂,直接启动就可以用了
4、检查ES是否启动成功:http://localhost:9200
5.若想修改集群名称,配置文件等,则到bin/elasticsearch.yml中修改即可
图片.png

1.2安装Kibana(以6.5.4版本为例)

1.下载和解压缩Kibana安装包
2.启动Kibana:bin\kibana.bat
3.检查是否启动成功: http://localhost:5601
4.进入Dev Tools界面
5.查看集群健康状况:    GET _cluster/health
图片.png

2.linux版

2.1安装ElasticSearch(以6.5.4版本为例)

root用户下,下载elasticsearch

wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-6.5.4.tar.gz

将下载包移至指定目录下

mv /root/elasticsearch-6.5.4.tar.gz /home/zx/software/

切换到该目录

cd /home/zx/software/

改变所属用户及组

chown zx:zx elasticsearch-6.5.4.tar.gz

切换至普通用户zx解压该包

su - zx
tar -zxf elasticsearch-6.5.4.tar.gz

修改bin/elasticsearch.yml

cluster.name: zx-es
node.name: node-1
network.host: 192.168.0.199 #ES所在服务器的ip
http.port: 9200

启动es

bin/elasticsearch -d #后台静默启动es

报错1

max file descriptors [4096] for elasticsearch process is too low, 
increase to at least [65536] #每个进程最大同时打开文件数太小

解决方案1

修改/etc/security/limits.conf文件,增加配置如下:

*               soft    nofile          65536
*               hard    nofile          65536

用户退出后重新登录生效

查看当前数量
ulimit -Hn
ulimit -Sn

报错2

max number of threads [3818] for user [es] is too low, 
increase to at least [4096]#最大线程个数太低

解决方案2

修改/etc/security/limits.conf文件,增加配置如下:

*               soft    nproc           4096
*               hard    nproc           4096

用户退出后重新登录生效

查看当前数量
ulimit -Hu
ulimit -Su

报错3

max virtual memory areas vm.max_map_count [65530] is too low, 
increase to at least [262144]

解决方案3

修改/etc/sysctl.conf文件,增加配置vm.max_map_count=262144

vi /etc/sysctl.conf
执行  sysctl -p 命令后生效
执行  sysctl -a | grep "vm.max_map_count"命令查看修改是否生效

再次启动es

bin/elasticsearch -d #后台静默启动
图片.png

其他问题

#centos7
#查看防火墙状态
firewall-cmd --state

#停止firewall
systemctl stop firewalld.service

#禁止firewall开机启动
systemctl disable firewalld.service 
# es的config目录下的jvm.options修改:(这俩值保持一致即可, 根据实际及其内存调整)
-Xms512m
-Xmx512m

2.2安装Linux版kibana(以6.5.4版本为例)

下载kinban

wget https://artifacts.elastic.co/downloads/kibana/kibana-6.5.4-linux-x86_64.tar.gz

将文件移至指定目录下后,解压包

tar -zxvf kibana-6.5.4-linux-x86_64.tar.gz

修改配置文件 vim config/kibana.yml

server.port: 5601
server.host: "0.0.0.0"
elasticsearch.url: "http://192.168.0.199:9200"
kibana.index: ".kibana"

后台静默启动kibana

nohup bin/kibana & #采用相对路径启动,当然,绝对路径也可
图片.png

2.3安装cerebro(不要跟es安装在同一台机器上)

#下载
wget https://github.com/lmenezes/cerebro/releases/download/v0.8.3/cerebro-0.8.3.tgz

# 解压即可
tar -zxf cerebro-0.8.3.tgz

#修改配置文件
vim conf/ application.conf

hosts = [
                   {
                       host = "http://192.168.10.64:9200"
                       name = "cm-elk"
                   },
             ]

#启动
nohup ./bin/cerebro -Dhttp.port=1234 -Dhttp.address=192.168.0.190 > /dev/null 2>&1 &

3.es常用命令

3.1快速检查集群的健康状况

es提供了一套api,叫做cat api,可以查看es中各种各样的数据

3.1.1查看健康状况

GET /_cat/health?v
如何快速了解集群的健康状况?green、yellow、red?

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

我们现在就一个笔记本电脑,就启动了一个es进程,相当于就只有一个node。
现在es中有一个index,就是kibana自己内置建立的index。
由于默认的配置是给每个index分配5个primary shard和5个replica shard,
而且primary shard和replica shard不能在同一台机器上(为了容错)。
现在kibana自己建立的index是1个primary shard和1个replica shard。
当前就一个node,所以只有1个primary shard被分配了和启动了,但是一个replica shard没有第二台机器去启动。

3.1.2快速查看集群中有哪些索引

GET /_cat/indices?v

3.1.3创建索引

自动

PUT /test_index?pretty

手动创建

PUT /hand_index
{
  "settings": {
    "number_of_shards": 1,  # 不可修改
    "number_of_replicas": 0 # 可修改
  },
  "mappings": {
    "hand_type_one": {
      "properties": {
        "field_one": {
          "type": "text"
        }
      }
    }
  }
}

3.1.4删除索引

切记:将elasticsearch.yml中:
action.destructive_requires_name: true
使之只能按照名称删除,不可全删
DELETE /test_index?pretty

3.1.5修改索引

PUT /hand_index
{
  "settings": {
    "number_of_replicas": 1 # 修改replica, primary不可被修改,与routing有关
  }
}

3.2商品的CRUD操作

document数据格式

面向文档的搜索分析引擎

(1)应用系统的数据结构都是面向对象的,复杂的
(2)对象数据存储到数据库中,只能拆解开来,变为扁平的多张表,每次查询的时候还得还原回对象格式,相当麻烦
(3)ES是面向文档的,文档中存储的数据结构,与面向对象的数据结构是一样的,基于这种文档数据结构,es可以提供复杂的索引,全文检索,分析聚合等功能
(4)es的document用json数据格式来表达

新增商品:新增文档,建立索引

新增语法:
PUT /index/type/id
{
  "json数据"
}

如:

PUT /ecommerce/product/1
{
    "name" : "gaolujie yagao",
    "desc" :  "gaoxiao meibai",
    "price" :  10,
    "producer" :      "gaolujie producer",
    "tags": [ "meibai", "fangzhu" ]
}

检索商品

es会自动建立index和type,不需要提前创建,而且es默认会对document每个field都建立倒排索引,让其可以被搜索
检索语法:
GET /index/type/id

如:

GET /ecommerce/product/1

修改商品:替换文档

POST /ecommerce/product/1/_update
{
  "doc": {
    "name": "jiaqiangban gaolujie yagao"
  }
}

删除商品:删除文档

DELETE /ecommerce/product/1

3.3搜索语法

3.3.1检索全部商品

GET /ecommerce/product/_search

3.3.2搜索商品名称中包含yagao的商品,而且按照售价降序排序

GET /ecommerce/product/_search?q=name:yagao&sort=price:desc

上述是query string search,下面是常用的 query Domain Specified Language

DSL:Domain Specified Language,特定领域的语言
http request body:请求体,可以用json的格式来构建查询语法,比较方便,可以构建各种复杂的语法,比query string search肯定强大多了

3.3.3检索所有商品

GET /ecommerce/product/_search
{
  "query": {"match_all": {}}
}

3.3.4查询名称包含yagao的商品,同时按照价格升序排序(定制排序规则)

默认情况下,是按照_score降序排序的
然而,某些情况下,可能没有有用的_score,比如说filter
当然,也可以是constant_score
GET /ecommerce/product/_search
{
  "query": {"match": {
    "name":"yagao"
  }},
  "sort": [
    {
      "price": "asc"
    }
  ]
}

3.3.5分页查询商品,总共2条商品,假设每页就显示1条商品,现在显示第2页,所以就查出来第2个商品

GET /ecommerce/product/_search
{
  "query": {"match_all": {}},
  "from": 1,
  "size": 1
}

3.3.6指定要查询出来商品的名称和价格就可以

GET /ecommerce/product/_search
{
  "query": {"match_all": {}},
  "_source": ["name", "price"]
}

3.3.7搜索商品名称包含yagao,而且售价大于25元的商品

GET /ecommerce/product/_search
{
  "query": {
    "bool": {
      "must": [
      {"match": {
        "name": "yagao"
      }
      }
    ],"filter": {
      "range": {
        "price": {
          "lte": 10
        }
      }
    }
    }
  }
}

3.3.8full-text search(全文检索)

GET /ecommerce/product/_search
{
  "query": {
    "match": {
      "producer": "yagao hei"
    }
  }
}

3.3.9highlight search(高亮搜索结果)

GET /ecommerce/product/_search
{
  "query": {
    "match": {
      "producer": "producer"
    }
  },
  "highlight": {
    "fields": {
      "producer": {}
    }
  }
}

3.3.10phrase search(短语搜索)

跟全文检索相反,全文检索会将输入的搜索串拆解开来,去倒排索引里面去一一匹配,只要能匹配上任意一个拆解后的单词,就可以作为结果返回
phrase search,要求输入的搜索串,必须在指定的字段文本中,完全包含一模一样的,才可以算匹配,才能作为结果返回
GET /ecommerce/product/_search
{
  "query": {
    "match_phrase": {
      "producer": "hei producer"
    }
  }
}

3.3.11分页及deep paging

分页搜索

GET /_search?size=10
GET /_search?size=10&from=0
GET /_search?size=10&from=20
deep paging性能问题
假设搜索第1000页,则请求首先到coordinate node-->
各primary shard将数据放到coordinate,进行排序-->
根据_score进行排序,选择最相关的几条数据-->
返回数据
耗费资源,cpu,耗费内存,不建议使用

3.3.12query string基础语法(较少用)

要包含test_field是test

GET /test_index/test_type/_search?q=test_field:test

要包含test_field是test

GET /test_index/test_type/_search?q=+test_field:test

不含test_field是test

GET /test_index/test_type/_search?q=-test_field:test

_all metadata的原理和作用

GET /test_index/test_type/_search?q=test
直接可以搜索所有的field,任意一个field包含指定的关键字就可以搜索出来。
我们在进行中搜索的时候,难道是对document中的每一个field都进行一次搜索吗?不是的
es中的_all元数据,在建立索引的时候,我们插入一条document,它里面包含了多个field,
此时,es会自动将多个field的值,全部用字符串的方式串联起来,变成一个长的字符串,作为_all field的值,同时建立索引
后面如果在搜索的时候,没有对某个field指定搜索,就默认搜索_all field,其中是包含了所有field的值的

3.3.13bool组合多个搜索条件

GET /website/article/_search
{
  "query": {
    "bool": {
      "must": [ // 必须包含
        {
          "match": {
            "title": "elasticsearch"
          }
        }
      ],
      "should": [ // 可以包含
        {
          "match": {
            "content": "elasticsearch"
          }
        }
      ],
      "must_not": [ //不能包含
        {
          "match": {
            "author_id": 111
          }
        }
      ]
    }
  }
}

每个子查询都会计算一个document针对它的相关度分数,然后bool综合所有分数,合并为一个分数,当然filter是不会计算分数的

GET /website/article/_search
{
    "bool": {
        "must":     { "match": { "title": "how to make millions" }},
        "must_not": { "match": { "tag":   "spam" }},
        "should": [
            { "match": { "tag": "starred" }}
        ],
        "filter": {
          "bool": { 
              "must": [
                  { "range": { "date": { "gte": "2014-01-01" }}},
                  { "range": { "price": { "lte": 29.99 }}}
              ],
              "must_not": [
                  { "term": { "category": "ebooks" }}
              ]
          }
        }
    }
}

3.3.14filter与query的对比分析

PUT /company/employee/1
{
  "name": "tom",
  "age": 30,
  "join_date": "2018-12-31"
}

PUT /company/employee/2
{
  "name": "jerry",
  "age": 23,
  "join_date": "2018-10-31"
}
#搜索请求:年龄必须大于等于10,同时join_date必须是2018-12-31
GET /company/employee/_search
{
  "query": {
    "bool": {
      "must": [
        {"match": {
          "join_date": "2018-12-31"
        }}
      ],
      "filter": {
        "range": {
          "age": {
            "gte": 10
          }
        }
      }
    }
  }
}

filter与query比较

filter:
(1)仅仅只是按照搜索条件过滤出需要的数据而已,不计算任何相关度分数,对相关度没有任何影响
(2)不需要计算相关度分数,不需要按照相关度分数进行排序,同时还有内置的自动cache最常使用filter的数据,性能较好

query:
(1)会去计算每个document相对于搜索条件的相关度,并按照相关度进行排序
(2)要计算相关度分数,按照分数进行排序,而且无法cache结果,性能较差

总结:
一般来说,
如果你是在进行搜索,需要将最匹配搜索条件的数据先返回,那么用query;
如果你只是要根据一些条件筛选出一部分数据,不关注其排序,那么用filter;
除非是你的这些搜索条件,你希望越符合这些搜索条件的document越排在前面返回,那么这些搜索条件要放在query中;
如果你不希望一些搜索条件来影响你的document排序,那么就放在filter中即可

如果单纯想用filter,需加一个constant_score

GET /company/employee/_search
{
  "query": {
    "constant_score": {
      "filter": {
        "range": {
          "age": {
            "gte": 10,
            "lte": 25
          }
        }
      }
    }
  }
}

3.3.15multi match

GET /test_index/test_type/_search
{
  "query": {
    "multi_match": {
      "query": "test", # 关键词是test
      "fields": ["test_field", "test_field1"]  #test_field或test_field1包含test都行
    }
  }
}

3.3.16range,可以放到query中,也可以放到filter中

GET /company/_search
{
  "query": {
    "range": {
      "age": {  // 年龄大于等于10,小于30
        "gte": 10,
        "lt": 30
      }
    }
  }
}

3.3.17term查询

与match查询不同,term中查询的字符串不会被分词,而是exact value
GET /company/_search
{
  "query": {
    "term": {
      "name": "jerry 1" # 此处会作为一个整串去查询
    }
  }
}

3.3.18terms查询

与term查询类似,只是,可以对某个field指定多个搜索词
GET /company/_search
{
  "query": {
    "terms": {
      "name": ["jerry", "tom"] #这里可以指定多个搜索词
    }
  }
}

3.3.19将一个field索引两次来解决字符串排序问题

创建mapping: 将title索引两次,一次用来分词,用来搜索;一次加上raw,不分词,用来排序

PUT /website
{
  "mappings": {
    "article": {
      "properties": {
        "title": {
          "type": "text",
          "fields": {
            "raw": {
              "type": "keyword"
            }
          },
          "fielddata": true
        },
        "content": {
          "type": "text"
        },
        "post_date": {
          "type": "date"
        },
        "author_id": {
          "type": "long"
        }
      }
    }
  }
}

插入数据

PUT /website/article/1
{
  "title": "first article",
  "content": "this is my first article",
  "post_date": "2019-01-01",
  "author_id": 100
}

PUT /website/article/2
{
  "title": "second article",
  "content": "this is my second article",
  "post_date": "2019-01-01",
  "author_id": 101
}

进行排序搜索

GET /website/article/_search
{
  "query": {"match_all": {}},
  "sort": [
    {
      "title.raw": {
        "order": "desc"
      }
    }
  ]
}
图片.png

3.4聚合分析

3.4.1计算每个tag下的商品数量

GET /ecommerce/product/_search
{
  "aggs": {
    "groups_by_tags": {
      "terms": {
        "field": "tags"
      }
    }
  }
}

此时发现报错如下

图片.png

需要设置tags的fielddata属性为true

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

然后再执行上述查询语句即可

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

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

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

GET /ecommerce/product/_search
{
  "size": 0,
  "aggs": {
    "group_by_tags": {
      "terms": {
        "field": "tags"
      },
      "aggs": {
        "avg_price": {
          "avg": {
            "field": "price"
          }
        }
      }
    }
  }
}

3.4.4计算每个tag下的商品的平均价格,并且按照平均价格升序排序

GET /ecommerce/product/_search
{
  "size": 0,
  "aggs": {
    "all_tags": {
      "terms": {
        "field": "tags",
        "order": {
          "avg_price": "asc"
        }
      },
      "aggs": {
        "avg_price": {
          "avg": {
            "field": "price"
          }
        }
      }
    }
  }
}

3.4.5按照指定的价格范围区间进行分组,然后在每组内再按照tag进行分组,最后再计算每组的平均价格

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

3.5mget批量查询

语法一: 当_index与_type都不一样时
GET /_mget
{
  "docs": [
    {
    "_index": "test_index",
    "_type": "test_type",
    "_id": "4"
    },
    {
    "_index": "test_index",
    "_type": "test_type",
    "_id": "3"
    }
  ]
}
语法二:当_index与_type都一样时:
GET /test_index/test_type/_mget
{
  "ids":[1,2,3,4]
}
语法三:当_index一样,_type不一样时:
GET /test_index/_mget
{
  "docs":[
    {
      "_type": "test_type",
      "_id": 1
    },
        {
      "_type": "test_type",
      "_id": 4
    }
  ]
}

3.6multi-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的数据

3.7查询逻辑

1、客户端发送请求到任意一个node,成为coordinate node
2、coordinate node对document进行路由,将请求转发到对应的node,此时会使用round-robin随机轮询算法,在primary shard以及其所有replica中随机选择一个,让读请求负载均衡
3、接收请求的node返回document给coordinate node
4、coordinate node返回document给客户端
5、特殊情况:document如果还在建立索引过程中,可能只有primary shard有,任何一个replica shard都没有,此时可能会导致无法读取到document,但是document完成索引建立之后,primary shard和replica shard就都有了

3.8查询结果含义剖析

GET /_search

结果:
{
  "took": 6,
  "timed_out": false,
  "_shards": {
    "total": 6,
    "successful": 6,
    "failed": 0
  },
  "hits": {
    "total": 10,
    "max_score": 1,
    "hits": [
      {
        "_index": ".kibana",
        "_type": "config",
        "_id": "5.2.0",
        "_score": 1,
        "_source": {
          "buildNum": 14695
        }
      }
    ]
  }
}

3.8http协议中get是否可以带上request body

HTTP协议,一般不允许get请求带上request body,
但是因为get更加适合描述查询数据的操作,因此还是这么用了
很多浏览器,或者是服务器,也都支持GET+request body模式
如果遇到不支持的场景,也可以用POST /_search
POST /_search
{
  "from":0,
  "size":10
}

4.document的核心元数据以及图解剖析index创建

{
  "_index": "test_index",
  "_type": "test_type",
  "_id": "1",
  "_version": 1,
  "found": true,
  "_source": {
    "test_content": "test test"
  }
}
took:整个搜索请求花费了多少毫秒

hits.total:本次搜索,返回了几条结果

hits.max_score:本次搜索的所有结果中,最大的相关度分数是多少,每一条document对于search的相关度,越相关,_score分数越大,排位越靠前

hits.hits:默认查询前10条数据,完整数据,_score降序排序

shards:shards fail的条件(primary和replica全部挂掉),不影响其他shard。
        默认情况下来说,一个搜索请求,会打到一个index的所有primary shard上去,
        当然了,每个primary shard都可能会有一个或多个replic shard,
        所以请求也可以到primary shard的其中一个replica shard上去。

timeout:默认无timeout,latency平衡completeness,手动指定timeout,timeout查询执行机制
          timeout=10ms,timeout=1s,timeout=1m

GET /_search?timeout=10m

4.1_index元数据

(1)代表一个document存放在哪个index中
(2)类似的数据放在一个索引,非类似的数据放不同索引:product index(包含了所有的商品),sales index(包含了所有的商品销售数据),inventory index(包含了所有库存相关的数据)。如果你把比如product,sales,human resource(employee),全都放在一个大的index里面,比如说company index,不合适的。
(3)index中包含了很多类似的document:类似是什么意思,其实指的就是说,这些document的fields很大一部分是相同的,你说你放了3个document,每个document的fields都完全不一样,这就不是类似了,就不太适合放到一个index里面去了。
(4)索引名称必须是小写的,不能用下划线开头,不能包含逗号:product,website,blog

4.2_type元数据

(1)代表document属于index中的哪个类别(type)
(2)一个索引通常会划分为多个type,逻辑上对index中有些许不同的几类数据进行分类:因为一批相同的数据,可能有很多相同的fields,但是还是可能会有一些轻微的不同,可能会有少数fields是不一样的,举个例子,就比如说,商品,可能划分为电子商品,生鲜商品,日化商品,等等。
(3)type名称可以是大写或者小写,但是同时不能用下划线开头,不能包含逗号

4.3_id元数据

(1)代表document的唯一标识,与index和type一起,可以唯一标识和定位一个document
(2)我们可以手动指定document的id(put /index/type/id),也可以不指定,由es自动为我们创建一个id

4.3.1.手动指定document id

put /index/type/id

PUT /test_index/test_type/2
{
  "test_content": "my test"
}

4.3.2自动生成document id

post /index/type

POST /test_index/test_type
{
  "test_content": "my test"
}
自动生成的id,长度为20个字符,URL安全,base64编码,GUID,分布式系统并行生成时不可能会发生冲突

4.4_source元数据

是指,我们在创建一个document的时候,
使用的那个放在request body中的json串,
默认情况下,在get的时候,会原封不动的给我们返回回来。

定制返回的结果,指定_source中,返回哪些field

GET /test_index/test_type/1?_source=field1,field2

4.5_version元数据

===Elasticsearch内部基于_version进行乐观锁并发控制===

第一次创建一个document的时候,它的_version内部版本号就是1;
以后,每次对这个document执行修改或者删除操作,都会对这个_version版本号自动加1;
哪怕是删除,也会对这条数据的版本号加1

4.5.1基于_version进行乐观锁并发控制

基于最新的数据和版本号,去进行修改,修改后,
带上最新的版本号,可能这个步骤会需要反复执行好几次,才能成功,
特别是在多线程并发更新同一条数据很频繁的情况下
加上?version=2,即对应的版本号即可
PUT /test_index/test_type/7?version=2 
{
  "test_field": "test client 2"
}

4.5.2基于external version进行乐观锁并发控制

es提供了一个feature,就是说,你可以不用它提供的内部_version版本号来进行并发控制,
可以基于你自己维护的一个版本号来进行并发控制。
举个列子,假如你的数据在mysql里也有一份,然后你的应用系统本身就维护了一个版本号,
无论是什么自己生成的,程序控制的。
这个时候,你进行乐观锁并发控制的时候,可能并不是想要用es内部的_version来进行控制,
而是用你自己维护的那个version来进行控制。
?version=1   #基于_version进行乐观锁并发控制
若document的_version=1,则更新时?version=1,才能更新成功

?version=1&version_type=external   #基于external version进行乐观锁并发控制
若document的_version=1,则更新时?version>1&version_type=external,才能成功,比如说?version=2&version_type=external

5.es文档的增,改,删

5.1 document的全量替换

(1)语法与创建文档是一样的,如果document id不存在,那么就是创建;如果document id已经存在,那么就是全量替换操作,替换document的json串内容
(2)document是不可变的,如果要修改document的内容,第一种方式就是全量替换,直接对document重新建立索引,替换里面所有的内容
(3)es会将老的document标记为deleted,然后新增我们给定的一个document,当我们创建越来越多的document的时候,es会在适当的时机在后台自动删除标记为deleted的document

5.2 document的强制创建

(1)创建文档与全量替换的语法是一样的,有时我们只是想新建文档,不想替换文档,如果强制进行创建呢?
(2)PUT /index/type/id?op_type=create,PUT /index/type/id/_create

5.3 document的删除

(1)DELETE /index/type/id
(2)不会理解物理删除,只会将其标记为deleted,当数据越来越多的时候,在后台自动删除

5.4 document的partial update

PUT /index/type/id,创建文档&替换文档,就是一样的语法

一般对应到应用程序中,每次的执行流程基本是这样的:

(1)应用程序先发起一个get请求,获取到document,展示到前台界面,供用户查看和修改
(2)用户在前台界面修改数据,发送到后台
(3)后台代码,会将用户修改的数据在内存中进行执行,然后封装好修改后的全量数据
(4)然后发送PUT请求,到es中,进行全量替换
(5)es将老的document标记为deleted,然后重新创建一个新的document

partial update语法

post /index/type/id/_update 
{
   "doc": {
      "要修改的少数几个field即可,不需要全量的数据"
   }
}
PUT /test_index/test_type/10
{
  "test_field1": "test1",
  "test_field2": "test2"
}

POST /test_index/test_type/10/_update
{
  "doc": {
    "test_field2": "updated test2"
  }
}

partial update内置乐观锁并发控制

post /index/type/id/_update?retry_on_conflict=5&version=6  #5表示重试次数,6表示版本号

5.5bulk:批量增删改

每一个操作要两个json串,语法如下:

{"action": {"metadata"}}
{"data"}

举例,比如你现在要创建一个文档,放bulk里面,看起来会是这样子的:

{"index": {"_index": "test_index", "_type", "test_type", "_id": "1"}}
{"test_field1": "test1", "test_field2": "test2"}
有哪些类型的操作可以执行呢?
(1)delete:删除一个文档,只要1个json串就可以了
(2)create:PUT /index/type/id/_create,强制创建
(3)index:普通的put操作,可以是创建文档,也可以是全量替换文档
(4)update:执行的partial update操作
bulk api对json的语法,有严格的要求,
每个json串不能换行,只能放一行,同时一个json串和一个json串之间,必须有一个换行

示例:

POST /_bulk
{"delete": {"_index": "test_index","_type":"test_type","_id": 3}}
{"create": {"_index": "test_index","_type":"test_type","_id": 5}}
{"name": "tom5","age": 35}
{"index": {"_index": "test_index","_type": "test_type","_id": 6}}
{"name": "tom6","age": 36}
{"index": {"_index": "test_index","_type": "test_type","_id": 1}}
{"name": "tom6 replace tom1","age": 36}
{"update": {"_index": "test_index","_type": "test_type","_id": 2}}
{"doc":{"name": "tom2 update tom2_"}}
bulk操作中,任意一个操作失败,是不会影响其他的操作的,
但是在返回结果里,会告诉你异常日志

bulk size最佳大小

bulk request会加载到内存里,如果太大的话,性能反而会下降,
因此需要反复尝试一个最佳的bulk size。
一般从1000~5000条数据开始,尝试逐渐增加。
另外,如果看大小的话,最好是在5~15MB之间。

5.6增删改原理

(1)客户端选择一个node发送请求过去,这个node就是coordinating node(协调节点)
(2)coordinating node,对document进行路由,将请求转发给对应的node(有primary shard)
(3)实际的node上的primary shard处理请求,然后将数据同步到replica node
(4)coordinating node,如果发现primary node和所有replica node都搞定之后,就返回响应结果给客户端

5.7写一致性操作: quorum机制

在发送任何一个增删改操作的时候,
比如说put /index/type/id,都可以带上一个consistency参数,
指明我们想要的写一致性是什么?
put /index/type/id?consistency=quorum

one:要求我们这个写操作,只要有一个primary shard是active活跃可用的,就可以执行
all:要求我们这个写操作,必须所有的primary shard和replica shard都是活跃的,才可以执行这个写操作
quorum:默认的值,要求所有的shard中,必须是大部分的shard都是活跃的,可用的,才可以执行这个写操作

consistency,one(primary shard),all(all shard),quorum(default)

quorum机制,写之前必须确保大多数shard都可用:

int( (primary + number_of_replicas) / 2 ) + 1,当number_of_replicas>1时才生效
quroum = int( (primary + number_of_replicas) / 2 ) + 1

举个例子,3个primary shard,number_of_replicas=1,总共有3 + 3 * 1 = 6个shard
quorum = int( (3 + 1) / 2 ) + 1 = 3

所以,要求6个shard中至少有3个shard是active状态的,才可以执行这个写操作
如果节点数少于quorum数量,可能导致quorum不齐全,进而导致无法执行任何写操作:

3个primary shard,replica=1,
要求至少3个shard是active,3个shard按照之前学习的shard&replica机制,必须在不同的节点上,
如果说只有2台机器的话,是不是有可能出现说,3个shard都没法分配齐全,此时就可能会出现写操作无法执行的情况

es提供了一种特殊的处理场景,就是说当number_of_replicas>1时才生效,因为假如说,你就一个primary shard,replica=1,此时就2个shard
(1 + 1 / 2) + 1 = 2,要求必须有2个shard是活跃的,但是可能就1个node,此时就1个shard是活跃的,如果你不特殊处理的话,导致我们的单节点集群就无法工作
(4)quorum不齐全时,wait,默认1分钟,timeout,100,30s
等待期间,期望活跃的shard数量可以增加,最后实在不行,就会timeout
我们其实可以在写操作的时候,加一个timeout参数,比如说put /index/type/id?timeout=30,这个就是说自己去设定quorum不齐全的时候,es的timeout时长,可以缩短,也可以增长

推荐阅读更多精彩内容