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"
}
}
}
}
此时发现报错如下
需要设置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时长,可以缩短,也可以增长