index modules 主要是介绍index各个方面的特性,算是一些总结和补充
主要内容有以下几方面
index的常规设置一般分为两类,一类是静态的static,这一类一般都是在index create的时候指定,之后不能再进行修改(或者在index close之后才能修改),
还有一类设置被称为动态设置dynaic setting, 这一类的设置一般可以通过api进行直接的修改。
static的settin总共有以下相关的设置
index的primary shard 数量设置, 默认为1, 即使在closed index上也不能修改,每个index的shard被限制到1024个,
这样做是为了防止一些意外创建的index(shard过多)占用比较多的资源导致cluster不稳定。
这个最大值可以通过java options进行设置,比如
export ES_JAVA_OPTS="-Des.index.max_number_of_shards=128"
在es启动的时候对shard做哪些检查
false: (default) 不对shard的完整性做检查
checksum: 只校验物理存储上的完整性
true: 不仅会校验物理存储上的完整性,还会校验逻辑存储上的完整性,这对cpu和memory来讲可能是一个非常昂贵的开销,也有可能会消耗很多时间。
index数据存储的编码方式(压缩方式)
默认使用LZ4压缩方式,可以设置
"index.codec" : "best_compression"
来开启使用压缩率更好的算法DEFLATE,当担这个算法对cpu的消耗更大,所以也会再一定程度上影响写入的速度。
如果你更新了这个设置,新的存储凡是会在segments merge之后生效。
这个设置的作用不是很大,可以参考index的meta field _routing来理解
这个字段的值理论上小于index.number_of_shards(只有index.number_of_shards=1的时候可以相等),默认值为1
对于nested query 是否对filter操作做预加载,这个可能看了nested filter之后能够更近异步理解
index.load_fixed_bitset_filters_eagerly
Indicates whether cached filters are pre-loaded for nested queries. Possible values are true (default) and false.
动态的设置是指那些可以通过api动态修改的设置,相对static设置莱多,dynaimc 设置更多一些。
index.search.idle.after
时间内没有接收到search请求的话,那么这个shard不会再进行background的refresh 知道搜到search请求之后才会进行refresh,对应的search请求也会等到执行完refresh之后才会执行。这种设置主要是为了又会bulk index操作。如果不想执行这个默认的逻辑,可以通过显示的设置这个属性为1s。index相关的其他的module级别的特性有
这个不再赘述,前面有相关的文档介绍了anlysis
主要是定义 analyzers, tokenizers, token filters and character filters.
shard allocation主要控制了node上的shard分配相关的规则,他有一下能力
你可以使用shard allocation filters 来控制index的shard会被分配到哪些node上面。这个针对每个index的filters会与cluster范围内的allocation filter和 allocation awareness 配合使用。
shard allocation filters 可与基于node attribute, built-in _name, host_ip, publish_ip, _ip 和_host attributes进行过滤。 Index lifecycle management使用filters来决定如何如何对shard进行重分配。
shard allocation filter 的主要配置是 cluster.routing.allocation
这个设置是dynamic的,可以使live index的shard从当前的node上面迁移到别的上面。当然这些迁移不能打破其他的约束,比如不能吧primary和replica shard 放到同一个node上面。
比如说,你可以自定义一些node attribute 来指明不同node的性能特性,然后使用使用shard allocation filtering 来将不同的shard route到具有不同的硬件特性的node当中,这里适用的一个场景就是日志系统的冷热分离,如果是按天产生的索引,可以把索引进行冷热分离,集群中的机器分为两种,cold和hot, cold是大磁盘,可能直接使用机械盘,成本低,适合低写入打存储,提取速度要求不高的场景,hot是高性能磁盘,一般为ssd,但是磁盘的容量偏小,适合高写入的场景,可以将昨天的日志index迁移到cold的node,今天新产生的分配到hot node 来满足大量的写需求,同时又能满足大存储的需求,可以降低很多成本。
1.给对应的node设置attribute,假如我们为每个node标记一个容量size属性,有small,medium,big三个属性,
node.attr.size: medium
或者
./bin/elasticsearch -Enode.attr.size=medium
PUT test/_settings
{
"index.routing.allocation.include.size": "big,medium"
}
index.routing.allocation.include.{attribute}
只需要node的attribute中有一个在当前index的配置当中即可
index.routing.allocation.require.{attribute}
对应的node必须有全部的当前配置的attribute才会将分片分配上去
index.routing.allocation.exclude.{attribute}
对应的node没有任何当前配置的的attribute才会将分片分配上去
PUT test/_settings
{
"index.routing.allocation.include.size": "big",
"index.routing.allocation.include.rack": "rack1"
}
这个配置就会将test index 移动到rack位rack1, size为big的node上面
_name: Match nodes by node name
_host_ip: Match nodes by host IP address (IP associated with hostname)
_publish_ip: Match nodes by publish IP address
_ip: Match either _host_ip or _publish_ip
_host: Match nodes by hostname
PUT test/_settings
{
"index.routing.allocation.include._ip": "192.168.2.*"
}
感觉这个不常用,因为可能会变化,机器做了下线重新部署ip就变了,_name就有唯一性,不容易聚类
在因为一个node离开cluster的时候会造成unassigned shard,这个设置可以控制这些unassigned的shard 延迟分配。
在一个nodeA离开cluster后,正常情况下master会做下面这些操作
这些行为都是为了让集群能够避免数据丢失而且是能够更加快速的被备份。即使es对cluster层面和node层面能够并行恢复的shard数量,但是他还是会对cluster带来挺大的额外的load,如果一node突然挂了,然后又很快(几分钟)又恢复了并重新加入了集群,急着进行shard recovery似乎是不划算的。
想象一下下面的场景
在这个场景下,如果master在做完第二步之后啥都不做,等待个几分钟,在node5回来之后,丢失的shards会re-allocated到node5上面,但是这样的话需要通过网络拷贝的数据量大大减小
对于那些已经auto sync-flushed 的idle shard(没有进行index 操作),恢复会更快。
index的延迟分配可以通过 index.unassigned.node_left.delayed_timeout
参数进行设置
PUT _all/_settings
{
"settings": {
"index.unassigned.node_left.delayed_timeout": "5m"
}
}
如果进行了延迟分配设置,上面的情况就会变成这样
这个设置仅仅会对因为node丢失导致的shard missing起作用,对新索引创建等其他情况产生的没有影响。
在整个集群重启之后,如果重启前有node left导致的shard missing那么重启后会进行恢复
在master 失败的情况下,已经经过的dely time会丢失,然后重新计算
如果延迟的时间到了,就会进行shard的recovery.如果这个时间missing node又re-join 到cluster当中了,而且他的shard 仍然和primary shard有相同的sync-id, shard relocation会被cancelled,然后原来的那个shard被用来做recovery,所以,es将默认的delay time设置为 1 minute。
如果你要将一个node永久移除,直接将延迟设置为0即可
PUT _all/_settings
{
"settings": {
"index.unassigned.node_left.delayed_timeout": "0"
}
}
然后再missing shards 开始进行recover的时候需要将这个值重新设置回来。
有时候索引非常多,不容易发现到底是哪个index的哪个shard异常了,可以通过health API查看
GET _cluster/health
index.routing.allocation.total_shards_per_node
对于一个index来说,最多有多少个shards分配到单个node上面
cluster.routing.allocation.total_shards_per_node
对于集群范围来说,单个node的shards总数最大值
这两个设置都要谨慎使用,使用不当容易出错。
这个也不再赘述,前面已经阐述很多了
每一个shard都是一个lucene index, 每个lucene又由很多个segments构成,segement是数据存储的基本单元,后台会周期性的将small segment 合并为更大的segment,同时也会将delete segment删除的文档去掉。
这个过程会自动根据当前的硬件资源使用的情况进行限速throttling ,比如会考虑当前search的压力
使用的是ConcurrentMergeScheduler 来实现以上行为。merges是通过多个独立的线程来进行,ConcurrentMergeScheduler可以使用的最大线程数是可以设置的。
index.merge.scheduler.max_thread_count: 设置最大可以使用的线程数
如果没有设置的话,默认使用
Math.max(1, Math.min(4, Runtime.getRuntime().availableProcessors() / 2))
这个计算方式对于ssd来说工作的很不错,如果你的是机械转盘,可以把这个降低到1更好。
similarity 也被称为scoring/ranking dodel, 主要是定义了doc如何被打分。每个field都可以定义自己的similarity。
自定义similarity可以认为是一个专家级操作,正常情况下es的built-in similarities应该就足够了。
built-in similarities有
在配置的时候需要注意的是,如果filed没有特殊指出使用哪个similarity es会使用名字为 default
的similarity
可以在创建index的时候定一个similarity
PUT /index
{
"settings" : {
"index" : {
"similarity" : {
"my_similarity" : {
"type" : "DFR",
"basic_model" : "g",
"after_effect" : "l",
"normalization" : "h2",
"normalization.h2.c" : "3.0"
}
}
}
}
}
PUT /index/_mapping
{
"properties" : {
"title" : { "type" : "text", "similarity" : "my_similarity" }
}
}
可以有的配置
k1: 默认值1.2, 非线性的term frequency 归一化参数
b: 默认值0.75,doc length 归一化参数
discount_overlaps:
这个就像mysql的slow log一样,可以记录慢查询/写入
可以设置query和fetch两个阶段的慢日志
PUT /twitter/_settings
{
"index.search.slowlog.threshold.query.warn": "10s",
"index.search.slowlog.threshold.query.info": "5s",
"index.search.slowlog.threshold.query.debug": "2s",
"index.search.slowlog.threshold.query.trace": "500ms",
"index.search.slowlog.threshold.fetch.warn": "1s",
"index.search.slowlog.threshold.fetch.info": "800ms",
"index.search.slowlog.threshold.fetch.debug": "500ms",
"index.search.slowlog.threshold.fetch.trace": "200ms",
"index.search.slowlog.level": "info"
}
使用不同的日志等级是为了方便更快的进行grep操作。
对应的log4j2.properties配置为
appender.index_search_slowlog_rolling.type = RollingFile
appender.index_search_slowlog_rolling.name = index_search_slowlog_rolling
appender.index_search_slowlog_rolling.fileName = ${sys:es.logs.base_path}${sys:file.separator}${sys:es.logs.cluster_name}_index_search_slowlog.log
appender.index_search_slowlog_rolling.layout.type = PatternLayout
appender.index_search_slowlog_rolling.layout.pattern = [%d{ISO8601}][%-5p][%-25c] [%node_name]%marker %.-10000m%n
appender.index_search_slowlog_rolling.filePattern = ${sys:es.logs.base_path}${sys:file.separator}${sys:es.logs.cluster_name}_index_search_slowlog-%i.log.gz
appender.index_search_slowlog_rolling.policies.type = Policies
appender.index_search_slowlog_rolling.policies.size.type = SizeBasedTriggeringPolicy
appender.index_search_slowlog_rolling.policies.size.size = 1GB
appender.index_search_slowlog_rolling.strategy.type = DefaultRolloverStrategy
appender.index_search_slowlog_rolling.strategy.max = 4
logger.index_search_slowlog_rolling.name = index.search.slowlog
logger.index_search_slowlog_rolling.level = trace
logger.index_search_slowlog_rolling.appenderRef.index_search_slowlog_rolling.ref = index_search_slowlog_rolling
logger.index_search_slowlog_rolling.additivity = false
PUT /twitter/_settings
{
"index.indexing.slowlog.threshold.index.warn": "10s",
"index.indexing.slowlog.threshold.index.info": "5s",
"index.indexing.slowlog.threshold.index.debug": "2s",
"index.indexing.slowlog.threshold.index.trace": "500ms",
"index.indexing.slowlog.level": "info",
"index.indexing.slowlog.source": "1000"
}
log4j2.properties配置
appender.index_indexing_slowlog_rolling.type = RollingFile
appender.index_indexing_slowlog_rolling.name = index_indexing_slowlog_rolling
appender.index_indexing_slowlog_rolling.fileName = ${sys:es.logs.base_path}${sys:file.separator}${sys:es.logs.cluster_name}_index_indexing_slowlog.log
appender.index_indexing_slowlog_rolling.layout.type = PatternLayout
appender.index_indexing_slowlog_rolling.layout.pattern = [%d{ISO8601}][%-5p][%-25c] [%node_name]%marker %.-10000m%n
appender.index_indexing_slowlog_rolling.filePattern = ${sys:es.logs.base_path}${sys:file.separator}${sys:es.logs.cluster_name}_index_indexing_slowlog-%i.log.gz
appender.index_indexing_slowlog_rolling.policies.type = Policies
appender.index_indexing_slowlog_rolling.policies.size.type = SizeBasedTriggeringPolicy
appender.index_indexing_slowlog_rolling.policies.size.size = 1GB
appender.index_indexing_slowlog_rolling.strategy.type = DefaultRolloverStrategy
appender.index_indexing_slowlog_rolling.strategy.max = 4
logger.index_indexing_slowlog.name = index.indexing.slowlog.index
logger.index_indexing_slowlog.level = trace
logger.index_indexing_slowlog.appenderRef.index_indexing_slowlog_rolling.ref = index_indexing_slowlog_rolling
logger.index_indexing_slowlog.additivity = false
这个设置主要是针对文件系统的一些设置,物理机的文件系统是各种各样的,默认情况下es会根据操作系统的实际情况选择最佳读写方式,你也可以对这个进行设置。
这个设置是static的,而且这个也是一个专家级设置,有可能后面会移除这个设置
这个设置可以直接在elasticsearch.yml中设置全局的
index.store.type: niofs
也可以针对单个的index设置
PUT /my_index
{
"settings": {
"index.store.type": "niofs"
}
}
可以有以下设置:
fs: 默认文件系统实现。这将根据操作环境选择最佳的实现方式,当前的操作环境在所有受支持的系统上都是hybridfs,但可能会发生变化。
simplefs: Simple FS类型是使用随机访问文件直接实现文件系统存储(映射到Lucene SimpleFsDirectory)。此实现的并行性能较差(多个线程将成为瓶颈)。当您需要索引持久性时,通常最好使用niofs。
niofs: NIO FS类型使用NIO在文件系统上存储分片索引(映射到Lucene NIOFSDirectory)。它允许多个线程同时读取同一文件。由于SUN Java实现中存在bug,因此不建议在Windows上使用。
mmapfs: MMap FS类型通过将文件映射到内存(mmap)将碎片索引存储在文件系统上(映射到Lucene MMapDirectory)。内存映射会占用进程中虚拟内存地址空间的一部分,该空间等于要映射的文件的大小。在使用此类之前,请确保您已允许足够的虚拟地址空间。
hybridfs: hybridfs类型是niofs和mmapfs的混合类型,它根据读取访问模式为每种文件类型选择最佳的文件系统类型。当前,只有Lucene term 词典,norms 和doc values 文件才进行内存映射(mmap)。使用Lucene NIOFSDirectory打开所有其他文件。与mmapfs相似,请确保已允许大量虚拟地址空间。
如果你没有权限使用大量的memory maps 你可以通过node.store.allow_mmap 来设置,这个是一个boolean值。默认是true,你可以设置为false。
这个也是专家级设置,将来有可能修改。
默认情况下,Elasticsearch完全依靠操作系统文件系统缓存来缓存I / O操作。可以通过设置index.store.preload,以告知操作系统在opening索引的时候将索引文件的内容加载到内存中。
此设置接受以逗号分隔的文件扩展名列表:扩展名在列表中的所有文件将在打开时预加载。这对提高索引的搜索性能很有用,尤其是在重新启动主机操作系统时,因为这会导致文件系统缓存被破坏。
但是请注意,这可能会减慢索引的打开速度,因为只有将数据加载到物理内存中后,索引才能变得可用。
这个设置可以直接在elasticsearch.yml中设置全局的
index.store.preload: ["nvd", "dvd"]
也可以针对单个的index设置
PUT /my_index
{
"settings": {
"index.store.preload": ["nvd", "dvd"]
}
}
里面的设置也支持wildcard的设置。
这一部分就结合es的refresh,flush操作一起来理解好了。
一个lucene 的shard(后面称es_shard)在lucene中对应了一个索引index(后面称lucene_index)
lucene_index 的构成是由多个segment构成的。
lucene commit :lucene commit针对的是lucene_index不是某一个segment,会应用新的curd , merge 一些segment产生新的luncene_index 的segment,并持久化到磁盘。
lucene reopen : 想要新的增改删可以应用到查询中,比如进行reopen才行。
也就是所es想要新的内容可见的话理论上必须有一个commit+reopen的操作。实际上上这样做是比较耗时的,在这里可以简略的理解为es的一个可能实现。
es为了追求更好的近实时性,引入了tranlog。每一个增删改请求进来后会生成两份,一份是记录到translog当中,一份是记录到in-memory buffer当中。
当执行_refresh操作的时候(es默认每秒执行一次),in-memory-buffer 会被copy生成一个新的memory-segment,这个时候应该做了一些优化(实现了更快的类似commit+reopen)
这个memory-segment随后就是searchable的了。但是这个时候memory-segment并没有被持久化。这个时候如果服务崩了就可以通过translog来进行数据回放重建。translog可以设置为对Index, Bulk, Delete, or Update 在响应前都进行持久化,也可以设置为异步持久化(有丢数据风险)。
flush 对应的本质实际上是一个lucene的commit操作,他将memory-segment merge产生新的segment写到磁盘,同时创建新的translog文件(不会直接删除老的translog),这是一个相对昂贵的操作。
es没有删除translog主要是为了在replica从primary复制的时候为了加快复制速度有时候直接通过传输translog文件来加快recovery的过程。
index.translog.durability: 这个设置的是translog的durability(持久化)方式,默认的配置是request
,也就是意味着es对于index, delete, update, or bulk 请求,只有translog在primary和所有的replica上完成了持久化才会给client返回成功。他还可以被设置为 async
,这样的话就会对translog进行一步的fsyncs,时间是 index.translog.sync_interval
(默认to 5 seconds).
index.translog.sync_interval: 默认是5s,不能小于100ms
index.translog.flush_threshold_size: 进行flush操作的translog阈值,为了防止在shard recovery的时候通过大量的translog重建(相对较慢),会在translog达到一定的大小后进行lucene commit 操作,把translog中的内容应用到磁盘当中。
index.translog.retention.size: 这个设置的translog所有文件总的最大大小,保持多一些tranlog文件能够在replica恢复的时候直接通过网络拷贝primary的translog加快数据同步的过程。如果translog的比较低效,还是会走通过segment的文件进行同步。默认的大小是512mb,超过了之后会删除旧的文件。
index.translog.retention.age: tranlog文件最长保留时长,默认是12h.
这篇文章将es的refresh,flush,translog之间的关系讲的比较清楚
https://qbox.io/blog/refresh-flush-operations-elasticsearch-guide
https://www.jianshu.com/p/15837be98ffd
刘大佬的这篇文章可以作为一个注脚
https://www.cnblogs.com/forfuture1978/archive/2010/06/08/1753642.html
https://www.cnblogs.com/forfuture1978/archive/2010/06/27/1766162.html
luncene 的事务特性
https://www.cnblogs.com/forfuture1978/archive/2010/06/07/1752917.html
这个的作用是在lucene创建segment的时候指定文档的排列顺序,默认情况下lucene是按照index的先后直接排列的,没有固定的规则。
这个博客对index-sorting的特点介绍的很完整,
这篇lucene 也很好
index-sortring的功能主要就是在生成segment的时候使文档按照某个field排序后的值进行排列,这样的好处是doc_id的顺序和该field的顺序是一致的。在进行sort取topN的时候,只需要取每个segment的topN即可。
使用,下面是一个使用了多个字段排序的segment
PUT twitter
{
"settings" : {
"index" : {
"sort.field" : ["username", "date"],
"sort.order" : ["asc", "desc"]
}
},
"mappings": {
"properties": {
"username": {
"type": "keyword",
"doc_values": true
},
"date": {
"type": "date"
}
}
}
}
index.sort.field: 是一个list,标识按照哪些fields进行排序
index.sort.order: 对每个field的排序规则,asc, desc
index.sort.mode: es可以使用multi-valued fields, 也就是说这个field的值有可能是一个array, 这个时候可以选择使用选择array中的哪一个参与排序,可以有min,max
两个选项,分别标识使用最小值和最大值。
index.sort.missing: 对于没有排序字段的doc如何处理,有两个选项_last, _first
放在最后一位或者第一位
index-sorting只能在index create的时候指定,不能再已经创建过的index上进行设置或者update.
可以提前结束查询过程,返回查询结果。
PUT events
{
"settings" : {
"index" : {
"sort.field" : "timestamp",
"sort.order" : "desc"
}
},
"mappings": {
"properties": {
"timestamp": {
"type": "date"
}
}
}
}
默认情况下如果没有设置index-sorting ,es的一个request会遍历所有query命中的doc,根据doc id取出来sorted field对应的doc_value 然后排序,再取前N条。但是假如设置了index-soring,同时,查询使用的sort又是同样的field的话,这样的话可以只遍历前面N个doc即可。
请求样例
GET /events/_search
{
"size": 10,
"sort": [
{ "timestamp": "desc" }
]
}
这个查询因为没有query,所以lucene会直接去取每个segment的前N条即可,剩下的会被收集用来计算total_number,假如不需要total_number的话可以在查询当中设置 "track_total_hits": false
这样的话es在找到N个doc后就立即返回相对来说快了很多。
如果query里面有agg操作的话,会忽略"track_total_hits": false
的设置,还是会获取所有命中的doc。
GET /events/_search
{
"size": 10,
"sort": [
{ "timestamp": "desc" }
],
"track_total_hits": false
}
{
"_shards": ...
"hits" : {
"max_score" : null,
"hits" : []
},
"took": 20,
"timed_out": false
}
这里顺便提一下lucene的查询机制,lucene是以segment作为查询单位的,每个segment也被称为sub-index。
索引排序对于组织Lucene doc ID(不要和es中的_id弄混了)很有用,其方式是使AND 查询(a AND b AND…)更有效。为了高效,AND 查询依赖于以下事实:如果任何子查询不匹配,则整个查询都不匹配。通过使用索引排序,我们可以将不匹配的文档放到一起,这将有助于有效地跳过与连接符不匹配的大范围文档ID。
此技巧仅适用于低基数字段(也就是说这个字段的值只有有限个数,但是doc的数量可以很大)。一条经验法则是,您应首先对基数都很低且经常用于过滤的字段进行排序。排序顺序(升序或降序)无所谓,因为我们只关心将与相同子句匹配的值彼此靠近。
例如,如果您要索引要出售的汽车,则按燃料类型,车身类型,品牌,注册年份以及最终里程来分类可能会很有用。
For instance if you were indexing cars for sale, it might be interesting to sort by fuel type, body type, make, year of registration and finally mileage.