PUT /索引名/_doc/文档id
文档内容 json 串
这种方式虽然方便, 但是在后面进行term索引的时候, 会有一定的问题.
因为这种默认的方式, 并不会进行ik分词
可以通过 GET /lisen 来看一下索引库结构:
#建表语句, img, price 都是默认的, title进行了ik分词处理, 否则在查询的时候, 对中文不是很支持
PUT /jd_goods
{
"mappings": {
"properties" : {
"img" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"price" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"title" : {
"type" : "text",
"analyzer": "ik_max_word"
}
}
}
}
#PUT 索引名/_doc/文档id 创建文档-指定id
#POST 索引名/_doc 创建文档-随机id
#POST 索引名/_update/文档id 修改文档
#DELETE 索引名/_doc/文档id 删除文档
#GET 索引名/_doc/文档id 通过id获取文档
#POST 索引名/_search 查询所有数据
#字符串类型 text keyword
#数值类型 long integer short byte double float, half float, scaled float
#日期类型 data
#布尔类型 boolean
#二进制类型 binary
PUT /lisen/_doc/1
{
"name":"elvin",
"age": 29,
"work": "java软件开发工程师",
"hobby":["篮球", "羽毛球", "乒乓球", "游泳"],
"desc": "一个喜欢运动, 爱老婆的boy"
}
PUT /lisen/_doc/2
{
"name":"john",
"age": 27,
"work": "前端开发工程师",
"hobby":["骑电瓶车", "羽毛球"],
"desc": "一个很刚的帅小伙"
}
PUT /lisen/_doc/3
{
"name":"sury",
"age": 35,
"work": "测试工程师",
"hobby":["带娃", "乒乓球"],
"desc": "一个很有想法的小伙"
}
PUT /lisen/_doc/4
{
"name":"主公",
"age": 35,
"work": "测试工程师",
"hobby":["带饭", "游泳"],
"desc": "一个摇摆不定的小伙"
}
GET /库名
可以获取建库信息
GET /库名/_doc/文档id
根据文档id获取信息, 相当于 select * from 库名 where id = #{id}
DELETE /库名
直接删索引库, 删除之后, 再 get 就回报错
get/
DELETE /库名/_doc/文档id
根据文档id进行删除
1) PUT方式修改 - 全量修改
2) POST方式修改 - 可以全量, 也可以非全量
PUT /库名/_doc/文档id
PUT 在 restFull 里面代表两个意思: add 和 update, 当没有id时, 新增, 当有id时修改
在这里, 也是一样的. 当有指定的id时, 修改, 当没有指定的id时, 新增这是一种全量修改方式
POST /库名/_doc/文档id
这也是一种全量方式, 结果和 PUT 是一样的. 同样的, post也可以新增数据.
POST /库名/_update/文档id
非全量方式, 他只有修改的功能, 没有新增文档的功能
如果只有全量方式, 那岂不是很不方便.
复杂查询可以实现 分页, 排序, 高亮, 模糊查询, 精确查询
查询有两种方式, 一种是拼参的方式, 另一种是 GET /库名/_search{} 的方式
GET /库名/_search
#_source显示那些列
可以直接写数组, 如: ["name", "age", "work", "hobby", "desc"]
还可以设置 includes 和 excludes, 如:
#sort 排序
对字段进行排序, 如:
"_source":
{
"includes": ["name", "age", "work", "hobby", "desc"],
"excludes": ["work", "hobby"]
}
"sort": [
{
"age": {
"order": "desc"
}
}
]
分页
#from 相当于pageNo
#size 相当于pageSize, 默认20
将上面组合起来, 查询看看:
sc"],
“excludes”: [“work”, “hobby”]
}
“sort”: [
{
“age”: {
“order”: “desc”
}
}
]
~~~java
分页
#from 相当于pageNo
#size 相当于pageSize, 默认20
将上面组合起来, 查询看看:
在生产环境,一个index是被分为多个各分片的,路由机制和分片机制密切相关。
举例有一个index,它被分3个分片,每个分片各有一个副本。
主分片P0,P1,P2
副分片R0,R1,R2
在index数据不指定routing时,ES默认用文档的ID来作为路由计算值,计算公式如下:
shard = hash(routing) % number_of_primary_shards
1-number_of_primary_shards是主分片数,这里就是3。对文档ID进行hash,再除以3,得到的余数肯定是0,1,2之间的数,对应主分片P0,P1,P2。这样起到了负载均衡的效果,使得每个分片里文档的数量理想情况下是几乎一样的。
举例ID=”abc“,shard = hash(“abc”) % 3 = 2,则把文档存到分片P2上。
3.1.1 根据文档ID查找
举例ID=”abc“,shard = hash(“abc”) % 3 = 2,则把这次查询发往分片P2上,因为插入时,ID=“abc”的数据就存在分片2上。这样就解释了index一旦创建好,主分片参数number_of_primary_shards是不能修改的,如果后期修改了它,查询时计算出的分片号和存入时分片号就不一致了。
3.1.2 自定义路由
如果不是根据文档ID查找,而是根据别的查询条件找,那默认是把请求发给所有的分片上,然后把每个分片的查询结果在协调节点上聚合,然后再给客户端最终结果。想想如果分片数很多,这样是很消耗系统系能的。
如果我们存数据时指定了路由routing参数,查询数据时也指定相同的routing参数,这样就会发给那一个分片,这样提高了查询效率,不用给每个分片发请求了。
PUT /pigg/_doc/100?routing=dept1
{
"name": "winter",
"dept": "dept1"
}
PUT /pigg/_doc/101?routing=dept1
{
"name": "dong",
"dept": "dept1"
}
查询时指定routing
GET /pigg/_search?routing=dept1
{
"query": {
"term": {
"dept.keyword": {
"value": "dept1"
}
}
}
}
注意使用routing时,这个和业务数据是相关联的,一定要考虑到业务数据的特性。
例如routing=dept1的有10万条,routing=dept2的数据只有1千条,那么10万条数据在一个分片上,1千条数据在另一个分片上,这样导致了数据倾斜。
3.2 timeout
当index操作时,可能主分片不可用,例如此时主分片正在恢复中,或在重定向。这个时候index操作就得等待主分片可用,默认这个等待时间是1分钟,当然也可以自定义。
等待主分片10秒
PUT /pigg/_doc/102?timeout=10s
{
"name": "dong2",
"dept": "dept1"
}
3.3 refresh
刚学ES时,都知道ES是一个近实时系统,写入的数据要约1秒后才能查询到。
refresh可以设置3种值,指定刷新行为。
ES的JavaAPI里定义了refresh的枚举如下:
public static enum RefreshPolicy implements Writeable {
NONE("false"),
IMMEDIATE("true"),
WAIT_UNTIL("wait_for");
//省略别的代码
}
它有false,true,wait_for三个值,ES默认是false。
(1)refresh = false
index后,不进行相关刷新操作,等待一定时间(约1秒),修改的数据可被查询到,ES默认这个。
(2)refresh = true
index后,立即刷新相关的主碎片和副本碎片(不是整个索引),以便可以立即搜索到修改后的文档。这样看着挺好,但是有不足的,否则ES也不会默认1S后可查询了。
如果频率的执行refresh=true的操作,会生成很多小的段文件,这也会给后期段文件的合并增加处理时间,因而会影响搜索效率,这样就得不偿失。
这个要谨慎考虑设置为true。
(3)refresh = wait_for
wait_for顾名思义,就是等待请求所做的更改被刷新可见。
对于在生产环境中,一般还是使用默认的配置refresh = false,很少有理由去设置refresh为别的,在技术选型时用ES了,也就得考虑到ES只是近实时,不是完全实时的。
3.3 version
每个文档都有一个版本号version,新增后version=1,以后每次修改,version自动加1,也可以指定version,比如让它从1一下子变成10这样。
#先新增一个ID=100的文档
PUT /pigg/_doc/100
{
"name": "三爷"
}
#返回结果如下,注意这个版本号_version=1
{
"_index" : "pigg",
"_type" : "_doc",
"_id" : "100",
"_version" : 1,//注意这个版本号
"result" : "created",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 1,
"_primary_term" : 1
}
但我们要修改文档前,我们已经知道当前version=1,如果文档被别人修改过,那version肯定大于1。
当我们要修改时,带上version参数(值是我们认定的更新前,当前文档的version值),如果我们指定的与ES里文档相等,则能成功,否则报异常。
#指定跟新前文档现有version=1
PUT /pigg/_doc/100?version=1
{
"name": "三爷2"
}
#因为中间没有别人操作过id=100的文档,所以修改成功,version变成2
{
"_index" : "pigg",
"_type" : "_doc",
"_id" : "100",
"_version" : 2,
"result" : "updated",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 2,
"_primary_term" : 1
}
3.5 if_seq_no & if_primary_term
ES是乐观并发控制的,在ES6.7这个版本之前是用version+version_type,现在新版本的ES用if_seq_no & if_primary_term在做并发控制。
seq_no不是属于当个文档,它是属于整个index,这个和version不同,version是每个文档修改后都+1,每个文档的version相互不影响。
_primary_term表示文档所在主分片的编号
但是每个文档依旧有自己的seq_no值,更新一个文档时,还是用该文档的seq_no控制并发。
seq_no属于整个index,当index中任何文档修改或新增,seq_no都会+1。
例如id=1的文档创建后,seq_no=1,
那么id=2的文档创建后,seq_no=2,
再然后id=1的文档修改后,seq_no=3
再对id=2的文档修改,要并发控制,if_seq_no得=2
(1)创建文档获取_seq_no和_primary_term
PUT pigg/_doc/300
{
"name": "winter"
}
返回结果如下,注意看最后2个_seq_no和_primary_term
{
"_index" : "pigg",
"_type" : "_doc",
"_id" : "300",
"_version" : 1,
"result" : "created",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 13,
"_primary_term" : 1
}
(2)修改时带上if_seq_no&if_primary_term
PUT pigg/_doc/300?if_seq_no=13&if_primary_term=1
{
"name": "winter1"
}
返回结果可见_seq_no加1了
{
"_index" : "pigg",
"_type" : "_doc",
"_id" : "300",
"_version" : 2,
"result" : "updated",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 14,//从13变成14了
"_primary_term" : 1
}
(3)模拟并发操作
再次执行相同的操作
更新时,指定序列号,如果序列号相同进行更新,不同则报错
PUT pigg/_doc/300?if_seq_no=13&if_primary_term=1
{
"name": "winter1"
}
返回报错,说seqNo已经是14了
{
"error" : {
"root_cause" : [
{
"type" : "version_conflict_engine_exception",
"reason" : "[300]: version conflict, required seqNo [13], primary term [1]. current document has seqNo [14] and primary term [1]",
"index_uuid" : "1EcXaVewTu2IFK_4OAGxIw",
"shard" : "0",
"index" : "pigg"
}
],
"type" : "version_conflict_engine_exception",
"reason" : "[300]: version conflict, required seqNo [13], primary term [1]. current document has seqNo [14] and primary term [1]",
"index_uuid" : "1EcXaVewTu2IFK_4OAGxIw",
"shard" : "0",
"index" : "pigg"
},
"status" : 409
}
#指定了version_type=external,必须version>当前的version
PUT /pigg/_doc/100?version=10&version_type=external
{
"name": "三爷3"
}
#返回结果如下,version变成指定的10
{
"_index" : "pigg",
"_type" : "_doc",
"_id" : "100",
"_version" : 10,
"result" : "updated",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 4,
"_primary_term" : 1
}