# /

# 索引

索引的负载均衡
索引的原子性
索引的一致性
索引的实时性
索引的持久性

# 路由(Routing)

# 老版本的路由算法
shard_num = hash(_routing) % num_primary_shards
1
# 新版本的路由算法
routing_factor = num_routing_shards / num_primary_shards
shard_num = (hash(_routing) % num_routing_shards) / routing_factor
1
2

注意:新版本路由算法类似一致性哈希算法,哈希取模数真实分片数量调整为虚拟分片数量,再通过映射关系routing_factor分配到真实分片。

# 乐观并发控制(Optimistic concurrency control)

Elasticsearch是分布式的。创建、更新或删除文档时,必须将文档的新版本复制到群集中的其他节点。Elasticsearch也是异步和并发的,这意味着这些复制请求是并行发送的,可能会按顺序到达目的地。Elasticsearch需要一种方法来确保文档的旧版本永远不会覆盖新版本。
为了确保文档的旧版本不会覆盖新版本,对文档执行的每个操作都由协调更改的主碎片分配一个序列号。序列号随着每次操作而增加,因此保证较新的操作具有比较旧的操作更高的序列号。然后,Elasticsearch可以使用操作的序列号来确保较新的文档版本永远不会被分配了较小序列号的更改所覆盖。
_seq_no:全局序列号
_primary_term:主任期。每当Primary Shard发生重新分配时,比如重启、Primary选举等,_primary_term会递增1。

# PUT一个文档

例如,以下索引命令将创建一个文档,并为其分配一个初始序列号和主要术语:

PUT products/_doc/1567
{
  "product" : "r2d2",
  "details" : "A resourceful astromech droid"
}

#您可以在响应的字段中看到分配的序列号_seq_no和主要术语_primary_term:
{
  "_shards": {
    "total": 2,
    "failed": 0,
    "successful": 1
  },
  "_index": "products",
  "_type": "_doc",
  "_id": "1567",
  "_version": 1,
  "_seq_no": 362,
  "_primary_term": 2,
  "result": "created"
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# GET一个文档

Elasticsearch跟踪上次操作的序列号和主项,以更改它存储的每个文档。在GET API响应的字段中返回序列号_seq_no和主要术语_primary_term:

GET products/_doc/1567

#返回
{
  "_index": "products",
  "_type": "_doc",
  "_id": "1567",
  "_version": 1,
  "_seq_no": 362,
  "_primary_term": 2,
  "found": true,
  "_source": {
    "product": "r2d2",
    "details": "A resourceful astromech droid"
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 搜索一个索引

在Search API响应的字段中返回序列号_seq_no和主要术语_primary_term:

GET products/_search
{
  "query": {
    "match": {
      "details": "A"
    }
  }
}

GET products/_search
{
  "seq_no_primary_term": true,//在Search API响应的字段中返回序列号_seq_no和主要术语_primary_term
  "query": {
    "match": {
      "details": "A"
    }
  }
}

#返回
{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 0.2876821,
    "hits" : [
      {
        "_index" : "products",
        "_type" : "_doc",
        "_id" : "1567",
        "_seq_no" : 0,
        "_primary_term" : 1,
        "_score" : 0.2876821,
        "_source" : {
          "product" : "r2d2",
          "details" : "A resourceful astromech droid"
        }
      }
    ]
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# 乐观锁PUT

通过记下返回的序列号和主要术语,您可以确保只有在检索后没有对文档进行其他更改的情况下才更改文档。

PUT products/_doc/1567?if_seq_no=362&if_primary_term=2
{
  "product": "r2d2",
  "details": "A resourceful astromech droid",
  "tags": [ "droid" ]
}
1
2
3
4
5
6

# 写入一致性

# 活动分片(Active shards)

为了提高写入系统的弹性,可以将索引操作配置为在继续操作之前等待一定数量的活动碎片副本。如果所需数量的活动碎片副本不可用,则写入操作必须等待并重试,直到所需的碎片副本启动或发生超时。默认情况下,写入操作只等待wait for主碎片处于活动状态后再继续(即wait_for_active_shards=1)。可以通过设置index.write.wait_for_active_shards在索引设置中动态覆盖此默认值。若要更改每次操作的此行为,可以使用wait_for_active_shards请求参数。

注意:默认只要主分片写入完成,不需要全部分片都写入完成,则表示索引写入完成。

# 刷新(Refresh)

# 近实时搜索

Lucene引入了逐段搜索的概念。段类似于反向索引,但Lucene中的index一词的意思是“段的集合加上一个提交点”。提交后,将一个新的段添加到提交点,并清除缓冲区。
位于Elasticsearch和磁盘之间的是文件系统缓存。内存中索引缓冲区 in-memory indexing buffer (图4)中的文档被写入一个新的段 segment (图5)。新段首先写入文件系统缓存written to the filesystem cache(这很便宜),然后才刷新到磁盘flushed to disk(这很昂贵)。然而,文件在缓存中之后,可以像其他任何文件一样打开和读取该文件。
图4。内存缓冲区中有新文档的Lucene索引
图5。缓冲区内容被写入一个段,该段是可搜索的,但尚未提交
image.png
Lucene允许编写和打开新的分段,使它们所包含的文档对搜索可见而不执行完全提交。这是一个比提交到磁盘轻得多的过程,而且可以频繁执行而不会降低性能。
在Elasticsearch中,这个编写和打开新片段的过程被称为刷新refresh。刷新使自上次刷新以来对索引执行的所有操作都可用于搜索。
默认情况下,Elasticsearch每秒定期刷新索引,但仅对在过去30秒内in the last 30 seconds收到一个或多个搜索请求的索引进行刷新。这就是为什么我们说Elasticsearch具有近乎实时的搜索功能:文档更改不会立即显示在搜索中。

注意: 1、刷新refresh是指把内存缓存区memory-buffer中的索引写入到文件系统缓存filesystem-cache的过程。(同时打开新的分页) 2、默认刷新间隔是1秒,且只刷新最后30秒的请求。

# ?refresh

Empty string or true:操作发生后立即刷新相关的主碎片和副本碎片(而不是整个索引),以便更新后的文档立即出现在搜索结果中。
wait_for:这并不强制立即刷新,而是等待刷新发生。Elasticsearch会自动刷新每个index.refresh_interval(默认为一秒)。
false:默认值。

# 事务日志(Translog)

对Lucene的更改仅在Lucene提交期间持久化到磁盘,这是一个相对昂贵的操作,因此不能在每次索引或删除操作之后执行。在进程退出或硬件故障的情况下,Lucene将从索引中删除在一次提交之后和另一次提交之前发生的更改。

注意:在一次Lucene提交之后和另一次Lucene提交之前。

Lucene提交的成本太高,无法对每个单独的更改执行,因此每个碎片副本也会将操作写入其事务日志(translog)中。所有索引和删除操作都在内部Lucene索引处理后但在确认acknowledged之前写入translog。在崩溃的情况下,当碎片恢复时,会从translog中恢复最近已确认acknowledged但尚未包含在上次Lucene提交中的操作。

注意:在确认acknowledged之前写入translog。

Elasticsearch刷新flush是执行Lucene提交并开始新的translog生成的过程。刷新Flushes是在后台自动执行的,以确保translog不会增长得太大,这将使重放其操作在恢复过程中花费相当长的时间。手动执行冲洗flush的能力也通过API公开,尽管这很少需要。

注意:刷新Flushes是在后台(操作系统)自动执行的。

# 持久性(durability)

只有当translog被fsynced并提交时,translog中的数据才会持久化到磁盘。如果发生硬件故障、操作系统崩溃、JVM崩溃或碎片故障,自上次translog提交以来写入的任何数据都将丢失。
默认情况下,index.translog.durability设置为request,这意味着Elasticsearch只有在主副本primary和每个分配的副本allocated上成功同步fsync并提交translog后,才会向客户端报告索引、删除、更新或批量请求的成功。
如果将index.translog.durability设置为async,则Elasticsearch fsyncs仅在每个index.translog.sync_interval提交translog,这意味着在节点恢复时,崩溃前执行的任何操作都可能丢失。

注意:默认只有在主副本primary和每个分配的副本allocated上成功同步fsync并提交translog后,才会向客户端报告写请求成功。

# 时间间隔(sync_interval)

不管写操作如何,将事务日志translog同步fsync到磁盘并提交的频率。默认为5s。不允许小于100ms。

# 刷新阈值(flush_threshold_size)

translog存储所有尚未在Lucene中安全持久化的操作。此设置控制这些操作的最大总大小,以防止恢复花费太长时间。一旦达到最大大小,就会进行刷新flush,生成一个新的Lucene提交点。默认值为512mb。