
3.3 容量控制与缓存机制
索引容量由分片数量和分片容量决定,分片数量可以通过_split接口扩容,也可以通过_shrink接口缩容。需要注意的是,这种扩容、缩容的方式依然是将原索引扩容、缩容到新索引上,并不是在原索引上做扩展;而且使用_split接口扩容时,分片扩容依然存在上限,如果增加分片的数量超过了容量上限,就只能通过_reindex接口对索引做重新索引。
除了这三个可以直接或间接控制索引容量的接口以外,Elasticsearch为了提升性能还默认开启了缓存机制。由于容量与缓存实际上都与索引性能相关,本节最后还会介绍一组可以查看索引及分片运行状态的接口,它们经常会在性能调优时使用。
3.3.1 _split接口
_split接口可以在新索引中将每个主分片分裂为两个或更多分片,所以使用_split扩容时分片总量都是成倍增加而不能逐个增加。使用_split接口分裂分片虽然会创建新的索引,但新索引中的数据只是通过文件系统硬连接到新索引中,所以并不存在数据复制过程。而扩容的分片又是在本地分裂,所以不存在不同节点间网络传输数据的开销,所以_split扩容效率相对其他方案来说还是比较高的。
_split接口做动态扩容需要预先设置索引的number_of_routing_shards参数,Elasticsearch向分片散列文档采用一致性哈希算法,这个参数实际上设置了索引分片散列空间。所以分裂后分片数量必须是number_of_routing_shards的因数,同时是number_of_shards的倍数。例如,设置number_of_routing_shards为12,number_of_shards为2,则分片再分裂存在2→4→12、2→6→12和2→12三种可能的扩容路径。分裂后分片数量可通过_split接口的index.number_of_shards参数设置,数量必须满足前述整数倍的要求。需要注意的是,这个参数的“index.”前缀是不能省略的,因为这是在_split接口中而不是在创建索引接口中。上面讲解的这些规则比较抽象,下面通过创建一个具体示例来看一下如何通过_split接口扩容索引。
首先创建一个索引employee,将它的主分片数量number_of_shards设置为2,散列空间number_of_routing_shards设置为12。然后,通过将索引的blocks.write参数设置为true,将索引设置为只读,这是因为使用_split接口要求索引必须为只读。最后调用_split接口将employee索引的分片分裂到新索引splited_employee中,index.number_of_shards参数设置为4,即分裂为4个分片。如示例3-20所示:

示例3-20 _split分裂分片
在执行成功后,可调用“GET _cat/shards”查看分片。在返回结果中可以看到employee索引共有4个分片,即2个主分片和2个副本分片;新索引splited_employee会有8个分片,即4个主分片和4个副本分片。_split接口在创建新索引的同时,会将原索引的配置也一同设置到新索引中。所以index.blocks.write参数也会一同被复制过来,但这可能并不是我们想要的。所幸在分裂分片的同时支持通过aliases和settings设置新索引的别名和配置,所以可以在分裂分片的同时将index.blocks.write参数覆盖。在示例3-20中就将这个参数覆盖了,同时还添加了新的别名stu。另外,还可以在地址中添加copy_settings=false参数禁止从源索引中复制配置,但这个参数在版本8中有可能被废止,所以添加这个参数会收到警告。
需要注意的是,使用_split接口成功分裂分片后,原索引并不会被自动删除。通过原索引和新索引都可以查看到相同的文档数据,原索引是否删除应根据业务需要具体判断。
3.3.2 _shrink接口
与_split接口相反,_shrink接口用于缩减索引分片。尽管它们在逻辑上正好相反,但它们在应用时的规则基本上是一致的。比如,_shrink接口在缩减索引分片数量时也要求原始分片数量必须是缩减后分片数量的整数倍。例如原始分片数量为12,则可以按12→6→3→1的路径缩减,也可以按12→4→2→1的路径缩减。在调用_shrink接口前要满足两个条件,第一个条件与_split接口类似,就是要求索引在缩容期间必须只读;第二个条件有些特殊,就是要求索引所有分片(包括副本分片)都要复制一份存储在同一节点,并且要求健康状态为green,这可以通过routing.allocation.require._name指定节点名称实现。如果想要查看节点名称,可调用“GET _nodes”接口相看所有集群节点。
与_split接口类似,索引在缩减后的具体分片数量可通过_shrink接口的index.number_of_shards参数设置。但它的值必须与原始分片数量保持整数比例关系,如果不设置该参数将直接缩减为1个分片。如示例3-21所示,缩减后索引分片数量为2,同时还清除了两项配置:

示例3-21 _shrink接口
同样地,使用_shrink接口缩容后会创建新索引shrinked_employee,原索引和新索引都可以查询到相同的文档数据。
3.3.3 _reindex接口
尽管_split接口和_shrink接口可以对索引分片数量做扩容和缩容,但在分片数量上有倍数要求,并且分片总量受散列空间(即number_of_routing_shards参数)的限制。如果索引容量超出了散列空间或者有其他特殊要求,则可以按新需求创建新的索引。Elasticsearch提供的_reindex接口支持将文档从一个索引重新索引到到另一个索引中。但显然重新索引在性能上的开销要比_split和_shrink大,所以尽量不要使用这种办法。_reindex接口需要两个参数source和dest,前者指明文档来源索引而后者则指明了文档添加的新索引。例如在示例3-22中是将users索引中的文档添加到users_copy索引中:

示例3-22 _reindex接口
需要注意的是,在重新索引时,不会将原索引的配置信息复制到新索引中。如果事先没有指定索引配置,重新索引时将根据默认配置创建索引及映射。另外,使用_reindex接口必须将索引的_source字段开启。
在实际应用中,_reindex接口并不是应用于扩容和缩容,而是主要应用于索引数据的合并。所以在_reindex接口中还提供了一些其他参数,用于处理在合并过程中出现的问题。例如:


示例3-23 合并索引
在示例3-23中,source参数除了指定源索引以外,还添加了term查询用于过滤文档,而dest参数则使用op_type参数设置了合并时只添加不存在的文档。
3.3.4 缓存机制
为了提升数据检索时的性能,Elasticsearch为索引提供了三种缓存。第一种缓存称为节点查询缓存(Node Query Cache),负责存储节点查询结果。节点查询缓存是节点级别的,一个节点只有一个缓存,同一节点上的分片共享同一缓存。在默认情况下,节点查询缓存是开启的,可通过索引index.queries.cache.enabled参数关闭。节点查询缓存默认使用节点内存的10%作为缓存容量上限,可通过indices.queries.cache_size更改,这个参数是节点的配置而非索引配置。
第二种缓存被称为分片请求缓存(Shard Request Cache),负责存储分片接收到的查询结果。分片请求缓存不会缓存查询结果的hits字段,也就是具体的文档内容,它一般只缓存聚集查询的相关结果。在默认情况下,分片请求缓存也是开启的,通过索引index.requests.cache.enable参数关闭。另一种关闭该缓存的办法,是在调用_search接口时添加request_cache=false参数。分片请求缓存使用的键是作为查询条件JSON字符串,所以如果查询条件JSON串完全相同,文档的查询几乎可以达到实时。但由于JSON属性之间并没有次序要求,这意味着即使JSON描述的是同一个对象,只要它们属性的次序不同就不能在缓存中命中数据。这一点在使用时需要格外注意。
最后一种缓存就是text类型字段在开启fielddata机制后使用的缓存,它会将text类型字段提取的所有词项全部加载到内存中,以提高使用该字段做排序和聚集运算的效率。由于fielddata是text类型对文档值机制的代替,所以天然就是开启的且不能关闭。但可通过indices.fielddata.cache.size设置这个缓存的容量,默认情况下该缓存没有容量上限。
缓存的引入使得文档检索性能得到了提升,但缓存一般会带来两个主要问题:一是如何保证缓存数据与实际数据的一致;另一个问题是当缓存容量超出时如何清理缓存。数据一致性问题,Elasticsearch是通过让缓存与索引刷新频率保持一致实现的。还记得索引是准实时的吗?索引默认情况下会以每秒一次的频率将文档编入索引,Elasticsearch会在索引更新的同时让缓存也失效,这就保证了索引数据与缓存数据的一致性。缓存数据容量问题则是通过LRU的方式,将最近最少使用的缓存条目清除。同时,Elasticsearch还提供了一个_cache接口用于主动清理缓存。之所以要提供这个接口,是因为Elasticsearch为索引提供了一个主动刷新的接口_refresh,所以最好在主动刷新索引后再主动清理缓存。
1._refresh接口
_refresh接口用于主动刷新一个或多个索引,将已经添加的文档编入索引以使它们在检索时可见。在调用该接口时,可以直接调用或与一个或多个索引一起使用,还可以使用_all刷新所有索引,所以以下调用都是正确的:

示例3-24 _refresh接口
事实上,除了使用_refresh接口主动刷新索引外,也可以在操作文档时通过refresh参数刷新索引,具体请参考3.4节的讲解。
2._cache接口
_cache接口用于主动清理缓存,在调用该接口时需要在_cache后附加关键字clear。_cache接口可以清理所有缓存,也可以清理某一索引甚至某一字段的缓存,还可以只清理某一种类型的缓存。例如:

示例3-25 _cache接口
在示例3-25中,query、request、fielddata参数分别对应于不同的缓存类型,而fields参数则用于定义清理哪一个字段的缓存。
3.3.5 查看运行状态
除此上述接口以外,Elasticsearch还提供了一组用于查看索引及分片运行情况的接口,包括_stat、_shard_stores和_segments等。由于它们往往在性能分析时使用,所以本小节将它们放在一起讲解。
1._stat接口
_stats接口用于查看索引上不同操作的统计数据,可以直接请求也可以与索引名称一起使用。_stats接口返回的统计数据非常多,如果只对其中某一组统计数据感兴趣,可以在_stats接口后附加统计名称。例如以下对_stats接口的调用都是正确的:

示例3-26 _stats接口
在_stats接口中可以使用的统计名称及它们的含义见表3-4,它们在返回结果中的含义与此相同。
表3-4 _stats接口统计名称

2._shard_stores和_segments接口
_shard_stores接口用于查询索引分片存储情况,而_segments接口则用于查看底层Lucene的分段情况。这两个接口都只能通过GET方法请求,同时都可以针对一个或多个索引,例如:

示例3-27 _shard_stores和_segments接口