基本概念
- 索引(Index): 对应关系型数据库的库(*必须全小写
- 类型(Type): 对应关系型数据库的表, 一个索引, 可以含一个或多个类型(*7.x开始已废除
- 文档(Document): 一个文档对应数据表的一行数据, 以 JSON格式来表示
- 字段(Field): 对应数据表的字段
- 映射(Mapping): 对每个字段设置规则做格式方面的限制(*如数据类型, 默认值, 分析器, 是否索引等
- 分片(Shards): ES分片是, 在底层是 Lucene索引
- 每个索引可以被分成多个分片(水平分割), 索引是分片的集合. 索引可以被复制0或多次, 分片后索引就有了主和复制分片的区别
- 分片和复制的数量可以在索引创建的时候指定. 创建之后, 不能更变分片的数量, 只能修改副本的数量(一个索引创建时设置1个分片& 1个副本, 这意味着集群中至少有两个节点
(*分片后执行过程: 查询请求到任意分片时, ES会将从自己或其他分片中收集所有匹配的数据再合并到一个全局的结果集再返回
- 副本(Replicas): 复制分片也称为副本. (*作用是故障转移和并行操作上的性能提升
高性能& 吞吐量
, 因为副本支持搜索(对应 Mysql的主从的从服务节点只为读和备份 - 分配(Allocation): 分片分配到节点的过程, 包括主分片或副本的分配(*如果是副本, 还包含从主分片复制数据的过程. 这个过程是由 master节点完成的
系统架构
- 集群是由一个或多个拥有相同 cluster.name配置的节点组成, 它们共同承担数据和负载的压力. 当有个节点加入或移除集群时, 集群会重新平均分布所有的数据
- 一个节点被选举成为主节点时, 它将负责管理集群范围内的所有变更 如增/删索引或增/删节点等, 而文档级别的除外
- 客户端无论请求到哪个节点, ES都能找到含我们所需文档的节点收集数据, 并将结果返回給客户端
分布式集群
- 创建索引时, 指定分片和副本个数
PUT http://127.0.0.1:9200/user
参数: { # 可选
"settings" : {
"number_of_shards" : 3, # 分片个数(索引创建后无法更改
"number_of_replicas" : 2 # 副本个数(可动态更改
}
}
*3个分片 + 各2个副本: 3 + 6(2x3) = 9个数据块; 此时适合安装3个ES也就是3个节点到3台服务器
# 修改副本个数(动态水平扩容
PUT http://127.0.0.1:9200/user/_settings
{
"settings" : {
"number_of_replicas" : 1 # 副本个数(可动态更改
}
}
*创建索引后可通过工具查看每个索引的分片及副本个数& 集群状态
# 下载 chrome的 elasticsearch-head插件(ES的集群监控器)
https://github.com/mobz/elasticsearch-head
# 安装 elasticsearch-head
浏览器 -> 更多工具 -> 扩展程序(chrome://extensions)
# 安装 elasticsearch-head到 chrome, 参考以下博文
https://blog.csdn.net/xixiyuguang/article/details/105392140
# elasticsearch-head使用说明
- 带星号的节点表示是主节点
- 边框为粗的分片是主分片
- 节点处, 标题为 Unassigned: 表示没有被分配到任何节点, 在同一个节点上既保存原始数据又保存副本
- 集群健康值:
yellow表示当前集群的全部主分片都正常运行, 但是副本分片没有全部处在正常状态
green表示都在正常运行
*当主节点关闭了, 集群会选举一个新的主节点
*每次在集群中增加或移除节点时, 会自动将分片& 副本均衡的分散包括已分配好的
*在3个分片(P0,P1,P2)& 各分1副本(R0,R1,R2)的集群中, 为了集群数据安全, 不能将同一个编号的分片和副本放到同一个节点上
*在相同节点数目的集群上加更多的副本, 并不能提高性能, 因为每个分片从节点上获得的资源会变少. 此时需要增加更多的硬件资源来提升吞吐量
- 路由计算:
*存取时寻找目标分片的公式: hash(routing) % number_of_primary_shards
routing是一个可变值, 默认是文档的 _id, 也可以是一个自定义的值. routing通过 hash函数生成一个数字, 再除以 number_of_primary_shards(主分片的数量)最后得到余数(分布在 0到 number_of_primary_shards-1之间的数), 就是目标分片的位置
*所有的文档 API(get, index, delete, bulk, update及 mget), 都接受 routing的路由参数, 通过此参, 可自定义文档到分片的映射. 以此可以用来确保所有相关的文档. 如同一个用户的文档存到同一个分片中
- 分片控制(读写更新的运作流程):
*假设某个集群由3个节点组成, 2个主分片, 各分片2个副本
Node 1[R0 P1], Node 2[R0 R1], Node 3[P0 R1]. 其中主节点为`P0`和`P1`
*所有的请求发送到 Node 1, 其称为协调节点(coordinating node)
- 写流程
1. 客户端向 Node 1请求, 新建/删除索引
2. 使用文档的 _id, 确定目标P0分片. 请求会被转发到 Node 3, 因为主分片P0在 Node 3
3. 在 Node 3的P0主分片上面执行请求. 如果成功了, 则会将请求并行转发到 Node 1和 Node 2的副本上. 一旦所有的副本都成功了, Node 3便向协调节点报告成功, 最后协调节点向客户端报告成功
- 读流程
1. 客户端向 Node 1请求读取
2. 使用文档的 _id, 确定目标P0分片. P0分片的副本存在于所有的节点上. 此时它将请求转发到 Node 2(为了负载均衡轮询选择的副本)
3. Node 2将文档返回给 Node 1, 最后将文档返回给客户端
- 更新流程(含读和写流程
1. 客户端向 Node 1请求更新
2. 使用文档的 _id, 确定目标P0分片(Node 3). 请求会被转发到 Node 3
3. 在P0分片检索指定文档, 修改 _source中的 JSON数据, 并尝试重新索引P0分片的文档. 如果文档正在被其他进程修改, 它会重试 3回, 一旦超过 retry_on_conflict, 则会放弃
4. 成功地更新文档后, 将新版本的数据并行异步转发到 Node 1和 Node 2的副本上, 并重新建立索引. 一旦所有副本都反馈成功, 之后 Node 3向协调节点返回成功, 最后协调节点向客户端返回成功
# 多文档操作流程
*mget和 Bulk API方式请求时, 协调节点(Node 1)会将多文档请求按分片分解成多请求, 并将这些请求并行转发到被选节点, 之后协调节点收到来自所有备选节点的应答, 再将这些响应整理成单个响应, 最后返回给客户端
- 单个 mget请求流程
1. 客户端向 Node 1请求
2. Node 1 将多文档请求按分片分解成多请求, 然后并将这些请求并行转发到被选节点. 一旦收到所有响应, Node 1再将这些响应构建成一个结果, 最后将其返回给客户端
- Bulk API请求流程
1. 客户端向 Node 1请求
2. Node 1为选定节点创建批量请求, 并将这些请求并行转发到每个包含主分片的节点
3. 主分片按顺序执行完每个操作. 当所有操作成功后, 主分片并行转发新文档(或删除)到副本. 所有的副本反馈操作成功时, 该节点将向协调节点报告成功. 最后协调节点再将这些响应整理成单个结果, 并返回给客户端
*有参数可以优化以上流程来提升性能, 不过这会引起数据同步时, 数据安全方面的问题(比如分片数据插入成功, 但副本还未同步). 因此建议使用默认参数
分片原理 - 索引:
> 正向索引(forward index): 就是搜索引擎会将待搜索的文件都对应一个文件 ID, 搜索时将这个 ID和搜索关键字进行对应, 形成 K-V对, 然后对关键字进行统计计数. 即文件 ID对应关键词的映射
> 反向索引(inverted index): 关键词到文件 ID的映射, 每个关键词都对应着一系列的文件
*反向索引又称`倒排索引` ES使用了该索引
- 文档搜索:
倒排索引被写入磁盘后是不可改变的, 以下是不变性的重要特性:
1. 不需要锁
2. 大部分读请求可在缓存操作, 而无需命中磁盘
3. 索引的生命周期内, 其他缓存(如 filter缓存), 始终有效不会在数据更变时重建
4. 写单个大的倒排索引时, 数据可以被压缩
- 动态更新索引:
ES基于 Lucene, 引入了按`段搜索`的概念. 每一段都是一个倒排索引, 索引在 Lucene中, 除表示所有段的集合外, 还有提交点的概念.
一个列出了所有已知段的文件, 按段搜索会以如下流程执行:
1. 新文档被收集到内存索引缓存
2. 不时地, 缓存被提交, 刷新到磁盘
3. 新的段被开启, 并打开供搜索使用
4. 内存缓存被清空, 等待接收新的文档
*当一个查询被触发, 所有已知的段按顺序被查询. 词项统计会对所有段的结果进行聚合, 以保证每个词和每个文档的关联都被准确计算
*由于段是不可改变的, 所以既不能从把文档从旧的段中移除, 也不能修改旧的段来进行反映文档的更新. 取而代之的是, 每个提交点会包含一个 .del文件, 文件中会列出这些被删除文档的段信息. 当一个文档被删除时, 它实际上只是在 .del文件中被标记删除. 一个被标记删除的文档仍然可被查询匹配到, 但返回最终结果前会从结果集中移除, 更新文档也是类似的过程
- 近实时搜索:
ES是近实时搜索, 意思是新插入的文档不能立马可见, 而是一秒后才可见(默认一秒可以设置), 在一些创建后同时查看的场景, 可以调用接口 /user索引/_refresh接口, 使其立马 refresh创建新的段并打开供搜索使用
*设置自动刷新频率为30秒:
{"settings": {"refresh_interval": "30s"}}
*当你正在建立一个大的新索引时, 可先关闭自动刷新, 待开始使用该索引时, 再把它们调回来
- 关闭自动刷新
PUT /user/_settings
{ "refresh_interval": -1 }
- 每一秒刷新
PUT /user/_settings
{ "refresh_interval": "1s" }
- 持久化变更:
*为了确保数据从失败中恢复, ES加了 translog事物日志(与 Mysql的事物日志类似), 将所有操作记录到 translog日志中
运作流程:
1. 一个文档被索引后, 会被加到内存缓冲区, 并记录到 translog
2. 分片每秒被刷新(refresh)一次:
(2-1) 将内存缓冲区的文档写入到新的段中, 未进行 fsync操作
(2-2) 打开该段, 使其可被搜索
(2-3) 清空内存缓冲区
3. 进程继续维持工作中, 更多的文档添加到内存缓冲区和事务日志中
4. 每隔一段时间 如 translog变得很大后 flush, 创建新的 translog
(4-1) 所有在内存缓冲区的文档都写到新的段中
(4-2) 清空缓冲区。
(4-3) 一个提交点写入硬盘
(4-4) 文件系统缓存通过 fsync刷新(flush)
(4-5) 将老的 translog删除
*translog提供所有还未被刷到磁盘的操作的持久化纪录. 当 ES启动的时候, 它会从磁盘中使用最后一个提交点去恢复已知的段, 并且重放 translog中, 所有在最后一次提交后发生的变更操作(translog是用来保证操作不会丢失)
*translog也被用来提供实时 CRUD. 当你试着通过 ID 查询, 更新, 删除一个文档, 它会尝试从相应的段中检索, 之前, 首先检查 translog任何最近的变更. 这意味着它总是能够实时地获取到文档的最新版本
*执行一个提交并且截断 translog的行为在 ES称作一次 flush, 分片每 30分钟被自动刷新(flush), 或者在 translog太大的时候也会自动刷新
*在重启或关闭节点之前执行 flush有益于 ES通过 translog重放操作日志, 因为日志越短, 恢复越快
*默认 translog是每 5秒被 fsync刷新到硬盘, 或每次写请求完成之后执行(e.g. index, delete, update, bulk). 这个过程在主分片和副本都会发生
*对于一些大容量的偶尔丢失几秒数据, 问题并不严重的集群, 使用异步的 fsync还是比较有益的. 如果决定使用异步 translog的话, 要想好发生 crash时, 丢失 sync_interval时间段内的数据. 请在决定使用前知晓这个特性. (如果对一切数据丢失敏感, 最好使用默认参数 "index.translog.durability": "request"来避免数据丢失
- 段合并:
*自动刷新流程每秒都会创建一个新的段, 这会导致短时间内的段数量暴增, 因为每个搜索请求都必须轮流检查每个段(每一个段都会消耗文件句柄, 内存和 cpu运行周期), 所以段越多, 搜索也会变的越慢. 段合并时, 会将旧的已删除文档从文件系统中清除. 被删除的文档(或被更新文档的旧版本)不会被拷贝到新的大段中. 启动段合并不需要做任何操作, 进行索引和搜索时会 ES自动进行的
1. 当索引的时候, 刷新(refresh)操作, 会创建新的段并打开以供搜索使用
2. 合并进程选择一小部分大小相似的段, 并在后台将它们合并到更大的段中. 此时不会中断索引和搜索
3. 一旦合并结束, 老的段被删除
(3-1) 新的段被刷新(flush)到磁盘
(3-1) 新的段被打开用来搜索
(3-1) 老的段被删除
*合并大的段需要消耗大量的 I/O和 CPU资源, ES在默认情况下会对合并流程进行资源限制
文档分析:
分析过程:
1. 将一块文本分成适合于倒排索引的独立词条
2. 将这些词条统一化为标准格式以提高它们的“可搜索性”, 或 recall
> 分析器实际将三个功能封装到一个包里:
(1) 字符过滤器
首先, 字符串按顺序通过每个字符过滤器. 他们的任务是在分词前整理字符串. 一个字符过滤器可以用来去掉 HTML, 或将 &转 and
(2) 分词器
其次, 字符串被分词器分为单个的词条. 一个简单的分词器遇到空格和标点的时候, 可能会将文本拆分成词条
(3) Token过滤器
最后, 词条按顺序通过每个 token过滤器. 这个过程可能会改变词条(例: 小写化Quick), 删除词条(例: 像 a, and, the等无用词), 或增加词条(例: 像 jump和 leap这种同义词)
- 内置分析器:
ES附带了可以直接使用的预装分析器. 为了证明它们的差异, 通过一下字符串查看每个分析器分析后得到的词条:
"Set the shape to semi-transparent by calling set_trans(5)"
> 标准分析器: 标准分析器是 ES的默认分析器. 它根据 Unicode联盟定义的单词边界划分文本. 删除绝大部分标点. 最后, 将词条小写
set, the, shape, to, semi, transparent, by, calling, set_trans, 5
> 简单分析器: 简单分析器在任何不是字母的地方分隔文本, 将词条小写:
set, the, shape, to, semi, transparent, by, calling, set, trans
> 空格分析器: 空格分析器在空格的地方划分文本:
Set, the, shape, to, semi-transparent, by, calling, set_trans(5)
> 语言分析器: 特定语言分析器可用于很多语言. 它们可以考虑指定语言的特点. 如英语分析器附带了一组英语无用词(常用单词, 如 and或 the, 它们对相关性没有多少影响), 它们会被删除. 由于理解英语语法的规则, 这个分词器可以提取英语单词的词干:
set, shape, semi, transpar, call, set_tran, 5s
*注transparent, calling和 set_trans已经变为词根格式
- 分析器使用场景:
当索引一个文档, 该文档的全文域被分析成词条以用来创建倒排索引. 但是, 当在全文域搜索的时候, 我们需要将查询字符串通过相同的分析过程, 以保证我们搜索的词条格式与索引中的词条格式一致
每个域的定义:
> 当查询一个全文域时, 会对查询字符串应用相同的分析器, 以产生正确的搜索词条列表
> 当你查询一个精确值域时, 不会分析查询字符串, 而是搜索你指定的精确值
- 测试分析器:
使用 analyze API来看文本是如何被分析的
GET http://localhost:9200/_analyze
{
"analyzer": "standard",
"text": "Text to analyze"
}
结果中每个元素代表一个单独的词条:
{"tokens": [{
"token": "text",
"start_offset": 0,
"end_offset": 4,
"type": "<ALPHANUM>",
"position": 1
},
{
"token": "to",
"start_offset": 5,
"end_offset": 7,
"type": "<ALPHANUM>",
"position": 2
},
{
"token": "analyze",
"start_offset": 8,
"end_offset": 15,
"type": "<ALPHANUM>",
"position": 3
}]}
*token是实际存储到索引中的词条. position指明词条在原始文本中出现的位置, start_offset和 end_offset指明字符在原始字符串中的位置
- 指定分析器:
当 ES在文档中检测到一个新的字符串域, 它会自动设置其为一个全文字符串域, 使用标准分析器对它进行分析, 但在国内我们很可能希望使用中文分析器. 有时候我们希望一个字符串域就是一个字符串域, 而不使用分析, 直接索引你传入的精确值, 例如用户 ID或一个内部的状态域& 标签等, 此情况可以手动指定域的映射
- IK分词器:
ES的默认分词器无法识别中文, 所以分析中文字符串会按每个字分为一个词
GET http://localhost:9200/_analyze
参数: {"text":"测试单词" }
返回: {"tokens": [{
"token": "测",
"start_offset": 0,
"end_offset": 1,
"type": "<IDEOGRAPHIC>",
"position": 0
},
{
"token": "试",
"start_offset": 1,
"end_offset": 2,
"type": "<IDEOGRAPHIC>",
"position": 1
},
{
"token": "单",
"start_offset": 2,
"end_offset": 3,
"type": "<IDEOGRAPHIC>",
"position": 2
},
{
"token": "词",
"start_offset": 3,
"end_offset": 4,
"type": "<IDEOGRAPHIC>",
"position": 3
}]}
显然这种结果不符合我们的要求
*下载 ES对应版本的中文分词器解决以上问题
*IK中文分词器下载地址: 解压后文件夹复制到 ES根下的 plugins目录内, 重启 ES
https://github.com/medcl/elasticsearch-analysis-ik/releases/tag/v7.8.0
> 区分粒度参数:
ik_max_word: 会将文本做最细粒度的拆分
ik_smart: 会将文本做最粗粒度的拆分
测试中文分词器效果
GET http://localhost:9200/_analyze
参数: {
"text":"测试单词",
"analyzer":"ik_max_word" # 指定文本拆分粒度
}
返回: {"tokens": [{
"token": "测试",
"start_offset": 0,
"end_offset": 2,
"type": "CN_WORD",
"position": 0
},
{
"token": "单词",
"start_offset": 2,
"end_offset": 4,
"type": "CN_WORD",
"position": 1
}]}
*例: 以下词`弗雷尔`中文分词器不识别, 则又会按单个字分词
GET http://localhost:9200/_analyze
参数: {
"text":"弗雷尔",
"analyzer":"ik_max_word"
}
返回: {"tokens": [{
"token": "弗",
"start_offset": 0,
"end_offset": 1,
"type": "CN_CHAR",
"position": 0
},
{
"token": "雷",
"start_offset": 1,
"end_offset": 2,
"type": "CN_CHAR",
"position": 1
},
{
"token": "尔",
"start_offset": 2,
"end_offset": 3,
"type": "CN_CHAR",
"position": 2
}]}
*可以将`弗雷尔`加到中文分词器中做扩展词汇, 进入 ES根下的 plugins/elasticsearch-analysis-ik-7.8.0/config目录, 创建 custom.dic文件, 写入`弗雷尔`. 同时打开 IKAnalyzer.cfg.xml文件, 将新建的 custom.dic配置其中, 重启 ES, 使其生效
IKAnalyzer.cfg.xml:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
<comment>IK Analyzer 扩展配置</comment>
<!--用户可以在这里配置自己的扩展字典 -->
<entry key="ext_dict">custom.dic</entry>
<!--用户可以在这里配置自己的扩展停止词字典-->
<entry key="ext_stopwords"></entry>
<!--用户可以在这里配置远程扩展字典 -->
<!-- <entry key="remote_ext_dict">words_location</entry> -->
<!--用户可以在这里配置远程扩展停止词字典-->
<!-- <entry key="remote_ext_stopwords">words_location</entry> -->
</properties>
返回: {
"tokens": [
{
"token": "弗雷尔",
"start_offset": 0,
"end_offset": 3,
"type": "CN_WORD",
"position": 0
}
]
}
- 自定义分析器:
> ES的强大之处在于, 可以创建自定义的分析器. 一个分析器就是组合了三种函数的一个包装器, 三种函数按照顺序被执行
(-) 字符过滤器: 字符过滤器用来整理一个尚未被分词的字符串. 过滤如 HTML标签等, 一个分析器可能有0个或多个字符过滤器
(-) 分词器: 分词器把字符串分解成单个词条或者词汇单元, 一个分析器只会有一个分词器
(-) 词单元过滤器: 单元过滤器可以修改, 添加或移除词单元. 我们已经提到过 lowercase和 stop词过滤器, 但是在 ES里面还有很多可供选择的词单元过滤器. 词干过滤器把单词遏制为词干. ascii_folding过滤器移除变音符, 把一个像"très"这样的词转换为"tres"
ngram和 edge_ngram词单元过滤器可以产生适合用于部分匹配或者自动补全的词单元
创建自定义的分析器:
PUT http://localhost:9200/my_index
{
"settings": {
"analysis": {
"char_filter": {
"&_to_and": {
"type": "mapping",
"mappings": [ "&=> and "] # 将 & 自动更变成 and
}
},
"filter": {
"my_stopwords": {
"type": "stop",
"stopwords": [ "the", "a" ] # 将独立的 the和 a过滤掉
}
},
"analyzer": {
"my_analyzer": {
"type": "custom",
"char_filter": [ "html_strip", "&_to_and" ],
"tokenizer": "standard",
"filter": [ "lowercase", "my_stopwords" ]
}
}
}
}
}
索引被创建后, 使用 analyze API来测试该分析器
GET http://127.0.0.1:9200/my_index/_analyze
参数: {
"text":"The quick & brown fox",
"analyzer": "my_analyzer"
}
返回: {
"tokens": [
{
"token": "quick",
"start_offset": 4,
"end_offset": 9,
"type": "<ALPHANUM>",
"position": 1
},
{
"token": "and",
"start_offset": 10,
"end_offset": 11,
"type": "<ALPHANUM>",
"position": 2
},
{
"token": "brown",
"start_offset": 12,
"end_offset": 17,
"type": "<ALPHANUM>",
"position": 3
},
{
"token": "fox",
"start_offset": 18,
"end_offset": 21,
"type": "<ALPHANUM>",
"position": 4
}
]
}
文档处理:
- 文档冲突:
当并发请求修改相同文档时, 最近的请求覆盖其他所有请求的值, 这里如果是修改部分字段的数据可能会是有问题的, 还有一些业务上是丢失一个变更也是很严重的 如电商平台的商品库存数, 必须能保证每次请求都有效
> 悲观并发控制
读取一行数据之前先将其锁住, 确保只有放置锁的线程能够对这行数据进行修改(强制阻塞其它
> 乐观并发控制
当一个源数据正在读写时, 该文档被其它线程修改导致文档的版本发生变化, 此时会导致当前线程的更新将会失败, 之后只能重新按照最新版本操作完成此次请求
- 乐观并发控制:
POST http://127.0.0.1:9200/goods/_update/zBSxhX0BA9oh2y0gY8fv?version=1
参数: {
"doc": {
"price":3000.00
}
}
*老的版本 es使用 version做乐观锁操作, 但新版本已经不支持了, 会报已下错误, 提示我们用 if_seq_no和 if_primary_term
{
"error": {
"root_cause": [
{
"type": "action_request_validation_exception",
"reason": "Validation Failed: 1: internal versioning can not be used for optimistic concurrency control. Please use `if_seq_no` and `if_primary_term` instead;"
}
],
"type": "action_request_validation_exception",
"reason": "Validation Failed: 1: internal versioning can not be used for optimistic concurrency control. Please use `if_seq_no` and `if_primary_term` instead;"
},
"status": 400
}
*按新版本的方式请求使用参数 if_seq_no和 if_primary_term
http://127.0.0.1:9200/goods/_update/zBSxhX0BA9oh2y0gY8fv?if_seq_no=0&if_primary_term=1
参数: {
"doc": {
"price":3001.00
}
}
如果参数编号不匹配则返回: { # 如果参数与最新相等, 则成功
"error": {
"root_cause": [
{
"type": "version_conflict_engine_exception",
"reason": "[zBSxhX0BA9oh2y0gY8fv]: version conflict, required seqNo [0], primary term [1]. current document has seqNo [2] and primary term [1]",
"index_uuid": "uKWIuBVYQYqN2Vy2Avtuhg",
"shard": "0",
"index": "goods"
}
],
"type": "version_conflict_engine_exception",
"reason": "[zBSxhX0BA9oh2y0gY8fv]: version conflict, required seqNo [0], primary term [1]. current document has seqNo [2] and primary term [1]",
"index_uuid": "uKWIuBVYQYqN2Vy2Avtuhg",
"shard": "0",
"index": "goods"
},
"status": 409
}
- 外部系统版本控制:
*加参数 &version_type=external, 将参数 version用于外部版本号; 这个版本号必须与当前版本号相同或高, 便可更改成功, 否则失败
POST http://127.0.0.1:9200/goods/_doc/zBSxhX0BA9oh2y0gY8fv?version=1&version_type=external
参数: {
"doc": {
"price":3002.00
}
}
如果版本不是最新则返回: { # 如果版本号相同或高, 则成功
"error": {
"root_cause": [
{
"type": "version_conflict_engine_exception",
"reason": "[zBSxhX0BA9oh2y0gY8fv]: version conflict, current version [3] is higher or equal to the one provided [1]",
"index_uuid": "uKWIuBVYQYqN2Vy2Avtuhg",
"shard": "0",
"index": "goods"
}
],
"type": "version_conflict_engine_exception",
"reason": "[zBSxhX0BA9oh2y0gY8fv]: version conflict, current version [3] is higher or equal to the one provided [1]",
"index_uuid": "uKWIuBVYQYqN2Vy2Avtuhg",
"shard": "0",
"index": "goods"
},
"status": 409
}
*版本号必须是大于零的整数, 且小于 9.2E+18
*外部版本号不仅在索引和删除请求是可以指定, 而且在创建新文档时也可以指定
如果您觉得有帮助,欢迎点赞哦 ~ 谢谢!!