Index API

搜索API可以添加或修改一个 JSON 格式的文档到指定索引中,让它能被搜索到。一下例子插入了一个 ID 为1的 JSON 文档到 “twitter” 索引下的 tweet 类型中。

  1. PUT /twitter/tweet/1
  2. {
  3. "user" : "kimchy",
  4. "post_date" : "2009-11-15T14:12:12",
  5. "message" : "trying out Elasticsearch"
  6. }

索引操作结果如下:

  1. {
  2. "_shards" : {
  3. "total" : 10,
  4. "failed" : 0,
  5. "successful" : 10
  6. },
  7. "_index" : "twitter",
  8. "_type" : "tweet",
  9. "_id" : "1",
  10. "_version" : 1,
  11. "created" : true
  12. }

_shard头提供了索引操作过程的复制信息

  • total - 表明索引操作在几个分片(主分片和副本分片)上执行了
  • successful - 表明索引操作在几个分片上操作成功
  • failures - 索引操作失败的分片数量

successful 至少为1索引才算插入成功。

译者批注:所有的增删改操作都必须要在主分片上执行,每个主分片维护了一组元数据,能够让我们找到某个分片在哪个具体的节点,当一个文档在一个主分片上操作完成后,请求会被同步到这个分片在其他节点上的副本

注意:副本可能还没有执行完就会返回 successful (默认半数以上执行完认为成功)。这种情况下,total 将会返回所有的主分片和副本,successful 将会返回所有执行成功的分片(主分片加副本)。如果没有错误发生,failed 等于0。

索引自动创建

如果插入操作的索引不存在则会自动创建(手动创建索引查看创建索引 API),并会自动创建 type 的映射到指定 type 下(手动创建映射,查看 创建映射API )。

映射本身十分灵活而且是无模式的。新的字段和对象将会加入指定 type 下的映射的定义中。映射定义详见 mapping章节。

动态索引创建可以通过在配置文件中配置 action.auto_create_indexfalse 来禁用。动态映射创建可以通过配置 index.mapper.dynamicfalse 来禁用(或者设置指定的索引的配置)。

动态创建可以基于一个黑白名单指定模式,例如,配置 action.auto_create_index 的值为 +aaa*,-bbb*,+ccc*,-*等(+表示允许,-表示不允许)。

版本

每个文档都有一个版本号。版本号作为 index API 响应的一部分返回。当指定 version 参数的时候,index API 使用乐观锁并发控制,这能控制操作文档的版本。一个不错的使用版本的方法是使用读后再更新的事务策略。指定最初读取文档的版本,能够确保在此期间不会发生改变。(当更新前读取的时候,建议指定 preference 值为 _primary)。例如:

  1. PUT /twitter/tweet/1?version=2
  2. {
  3. "message" : "elasticsearch now has versioning support, double cool!"
  4. }

注意:版本是完全实时的,并不受搜索操作的准实时方面的影响。如果没有提供版本,则不进行版本检查。

默认的,内部版本号从1开始,每次修改删除操作加1。版本可以由外部提供(例如在数据库保持一份)。为了支持这个功能,version_type 参数要指定为 external,版本值必须是大于等于0的数字或long类型。而且要小于 9.2*10^18。当使用外部版本类型时,不是匹配版本号是否一致,而是系统检查索引请求的版本号是否大于当前存储文档的版本,如果大于当前版本,则文档入索引并使用新版本;如果小于当前版本,则会触发版本冲突异常,并且当前请求失败。

译者批注:外部版本可以使用数据库记录的数据中的某些值,比如自增的id,或者数据库再记录一个插入时间戳。数据库的每个操作类似一个 elasticsearch 的操作日志,当出现同一个记录并发操作可能导致冲突的时候,更新带上版本号,这样就算新的操作再旧的操作后面,也不会被就的操作覆盖

警告:外部版本号支持以0开始。这样就支持使用从0开始的外部版本系统同步到索引中。当然副作用就是只要是版本号为0的文档,既不可以通过 Update-By-Query API更新,也不可以通过 Delete By Query API删除。

好的作用是只要外部版本数据源使用数据库,则异步的索引操作对于数据库数据源的变化,不用保持严格的排序。如果使用外部版本,即使是来自数据库的更新,索引更新也很简单,因为如果索引操作因为任何原因出问题,它将使用最新版本。

译者批注:此处翻译比较拗口,大概意思就是通过外部版本控制,不会导致数据库同步过程中出现并发冲突,及时出现版本不对的情况下,你的数据还是es里面最新的那个版本。

版本控制类型

紧接着上面介绍的内部版本和外部版本控制,Elasticsearch 还根据指定的业务场景支持其他的版本控制类型。以下是这些不同类型的版本控制以及他们的含义。

  • internal - 只有给定版本等于当前版本的时候才索引文档。
  • external & external_gt只有当给定版本严格大于存储的当前版本,或者文档不存在的时候,文档才会用指定版本作为当前版本并存储新的文档,给定版本不能是负数。
  • external_gte - 和 external的不同在于给定版本等于当前版本也能生效。

注意:external_gte 类型的版本控制用于特殊业务场景,要小心使用。使用不当会丢数据。还有一个选项:force,不过已经废弃了,因为会导致住副本分片数据不一致

操作类型

索引操作也可以接受一个 op_type 参数,可用来强制执行一个 create 操作,支持 put-if-absent 的行为(CAS)。当使用 create 操作时,如果文档存在,则索引操作将失败。 下面是使用 op_type 参数的例子:

  1. PUT twitter/_doc/1?op_type=create
  2. {
  3. "user" : "kimchy",
  4. "post_date" : "2009-11-15T14:12:12",
  5. "message" : "trying out Elasticsearch"
  6. }

另一个使用 create 操作的方法如下:

  1. PUT twitter/_doc/1/_create
  2. {
  3. "user" : "kimchy",
  4. "post_date" : "2009-11-15T14:12:12",
  5. "message" : "trying out Elasticsearch"
  6. }

ID自动生成

搜索操作可以不指定id,下面的这个例子,id将会自动创建。另外,op_type 将默认设置为 create。下面是例子(注意使用 POST请求):

  1. POST twitter/_doc/
  2. {
  3. "user" : "kimchy",
  4. "post_date" : "2009-11-15T14:12:12",
  5. "message" : "trying out Elasticsearch"
  6. }

一下是索引操作的返回结果:

  1. {
  2. "_shards" : {
  3. "total" : 2,
  4. "failed" : 0,
  5. "successful" : 2
  6. },
  7. "_index" : "twitter",
  8. "_type" : "_doc",
  9. "_id" : "6a8ca01c-7896-48e9-81cc-9f70661fcb32",
  10. "_version" : 1,
  11. "_seq_no" : 0,
  12. "_primary_term" : 1,
  13. "result": "created"
  14. }

路由

默认情况下,分片的位置 —— 或者说路由 —— 使用文档id的哈希值控制。路由值还可以通过 routing 参数来指明。例如:

  1. POST twitter/tweet?routing=kimchy
  2. {
  3. "user" : "kimchy",
  4. "post_date" : "2009-11-15T14:12:12",
  5. "message" : "trying out Elasticsearch"
  6. }

上面的例子中,文档根据 routing 参数值 “kimchy” 路由到某个分片。

当设置过映射后,_routing 字段用来指定索引操作到那个分片,并且路由值可以从文档中提取,这个方式会在解析文档时带来一点开销(非常少),如果 _routing 在映射的时候指定,并且设置了 required,当索引操作的文档对应的字段不存在时,操作会失败。

译者批注:2.0.0 以后就废弃的属性不做翻译了

父子关系

一个子文档在插入的时候可以指定它的父文档。例如:

  1. PUT /blogs/blog_tag/1122?parent=1111
  2. {
  3. "tag" : "something"
  4. }

当索引子文档的时候,路由值会自动和父文档保持一致,除非子文档在参数中指定了 routing 参数。

分布式

索引操作会根据路由信息被分发到主分片并在主分片所在节点执行。当主节点执行完毕,请求会被分发到可用到副本分片上。

写一致性

为确保写操作不在网络分区时发生错误,默认情况下爱,索引操作只有当半数以上的分片(> replicas / 2 + 1)操作成功后才会返回成功。这个配置可以通过 action.write_consistency 选项覆盖。如果想一步操作修改这个参数,可以使用 consistency 请求。

合法的参数值有:one , quorum , all

注意副本为1的情况(总共只有两个分片),此时默认认为主分片写成功才算成功。

索引操作仅在所有可用分片,包括副本写入文档后才返回(使用同步副本方式)。

译者批注:使用异步方式,不等副本写完就可以返回,不会不建议这么做,特别是数据一致性高的场景,而且可能会使 ES 过载

刷新

为了能够让文档能在搜索结果中立刻被查看,要在索引操作后立马刷新分片(不是整个索引),可以设置 refreshtrue 实现这点。设置这个需要你仔细思考并确认会不会导致索引和搜索的性能问题。注意,使用 get API是完全实时的,不需要刷新。

空的更新

当你创建文档的时候,总是会给文档增加一个新版本,哪怕是这个请求没有对文档做出改变。如果使用 _update 你不想让它这样的话,可以设置 detect_nooptrue 。这个操作在 index API 是无效的,因为 index 请求并不会先获得旧的文档的source,也就没法比较文档是否相同。

没有一个硬性规则要求使用 noop 这个特性。这个选择包含了很多因素,例如你更新文档出现 noop 的频率,以及你在更新频率高的分片上查询的QPS等等。

超时

执行索引操作的那个主分片有可能执行失败,可能的原因比如主分片正在恢复或者后台在进行分片平移。默认情况下索引操作将等待1分钟让分片变为可用,超过则失败并返回一个异常。timeout 参数用来指定这个等待时间。下面的例子我们设置它为5分钟:

  1. PUT /twitter/tweet/1?timeout=5m
  2. {
  3. "user" : "kimchy",
  4. "post_date" : "2009-11-15T14:12:12",
  5. "message" : "trying out Elasticsearch"
  6. }

译者批注:其实每种请求 API 理论上都是有请求超时限制的,建议给你的所有请求评估一个超时时间,一面部分请求超时阻塞请求队列导致其他请求也一起被阻塞,可以把这个时间做成动态可配置的,达到熔断效果