文章目录
- 分片机制
- 存储原理
- 分段存储
- 段的优越点
- 延迟策略
- 段合并
- ES的性能优化
- ES存储设备
- ES内部索引优化
- 调整ES参数
- JVM性能调优
分片机制
分片是Elasticsearch在集群中分发数据的关键
把分片想象成数据的容器,文档存储在分片中,然后分片分配到集群中的节点,当集群扩容或缩小,ES将会自动在节点间迁移分片,以使集群保持平衡一个分片在es中是最小级别的工作单元.它只是保存了索引中所数据的一部分
分片可以是主分片或者复制分片,在集群中唯一一个空节点创建一个叫做blogs的索引,默认情况下,一个索引被分配5个主分片,每个主分片又分配一个副分片
主分片
在一个多分片的索引中写入数据时,通过路由来确定具体写入哪一个分片中,大致路由过程
shard = hash(routing) % number_of_primary_shards
路由规则: routing是一个可变值,默认存入文档的_id(可以设置自定义值),然后通过hash得到数字,这个数字在除以number_of_primary_shards(主分片数量),然后得到余数, 然后0 到number_of_primary_shards(主分片数量) 之间的余数,就是文档所在那个分片的位置
这也就是为什么,创建索引的时候确定主分片的数量,并且永远不能再改变 这个数量了,因为数量改变了之前的路由规则也就变了,文档在根据之前的路由规则找,也找不到了
索引中每个文旦都属于一个单独的主分片,所有主分片数量决定了索引最多能存储多少数据,实际数量取决于数据,硬件和应用场景
副分片
复制分片只是主分片的一个副本,它可以防止硬件故障导致的数据丢失,同时可以提供读请求,比如搜索或者从别的 shard 取回文档。
每个主分片都有一个或多个副本分片,当主分片异常时,副本可以提供数据的查询等操作。主分片和对应的副本分片是不会在同一个节点上的,所以副本分片数的最大值是 n -1(其中 n 为节点数)。
存储原理
ES写入流程,这个流程最终是在ES的内存中执行的,数据被分配到特点分片和副本之后,最周存储在磁盘上,这样如果服务起突发断电也就不会丢失数据,具体的存储路径可在配置文件 …/config/elasticsearch.yml中进行设置,默认在安装目录的data文件下
分段存储
索引文档以段的形式存储在磁盘上
什么是段:
索引文件被拆分成多个子文件,则每个文件叫做段,每一个段都是一个倒排索引,并且段具有不变性,一旦索引的数据被写入磁盘,就不可再修改,Es底层采用分段的存储模式,使它在读写几乎避免了锁的出现,大大提升读写性能
段被写入磁盘后会生成一个提交点.提交点是一个用来记录所有提交后段信息的文件,一个段如果拥有多个提交点,就说明这个段只有读的权限,相反,当段在内存中时,就是有写的权限,而不具备读数据库的权限,意味着不能被检索出来
段的概念提出是因为:早起全文检索中的整个文档集合建立了一个很大的倒排索引,并将其写入磁盘中,如果索引有更新,就需要更新创建一个索引来替换之前的索引,这种方式效率很低,且每创建一次索引的成本很高,索引对数据的更新不能过于频繁,照成性能缓慢
索引分段存储不可修改,那么 新增 更新和删除是怎么操作的呢
1.新增: 由于数据是新的,只需对当前文档新增一个段就可以了
2.删除:由于不可修改,所以对删除操作,不会把文档给移除掉,而是新增一个.del文件,文件中会列出这些被删除文档的段信息,这个被标记删除的文档仍然可以被查询匹配到,但它最终会在结果即返回前从结果集中移除
3.更新:更新不会修改旧的段来进行文档更新, 其实更新相当于删除和新增两个动作组成 会将旧的文档在啊.del文件中标记删除, 新文档被索引到一个新的段中’
段的优越点
优点:
1.不需要锁,如果你从来不更新索引,你就不需要担心多进程同时修改数据的问题
2.一旦索引被读入内核的文件系统缓存,便会留在那里,由于其不变性,只要文件系统缓存中还有足够的空间,那么大部分读请求会直接请求到内存,而不会命中磁盘,从而提高了性能
3.其他缓存.在索引的生命周期始终有效,他们不需要在每次数据改变时被重建,因为数据不会变化
4.写入单个大的倒排序允许数据被压缩,减少磁盘I/O和需要被缓存到内存的索引使用量
段的不变型缺点
1.当对旧数据进行删除时,旧数据不会马上删除,而是在.del文件中被标记删除,而旧数据只能等到段更新时才能被移除,这样会照成大量的空间浪费
2.若有一条数据频繁的更新,每次更新都是新增的标识旧的,则会有大量的空间浪费
3.每次新增数据时都需要新增一个段来存储数据,当段的数量太多时,对服务器的资源例如文件局柄的消耗会非常大
4.在查询的结果中包含所有的结果集,需要排除被标记删除的旧数据,这增加了查询的负担
延迟策略
Es为了提升写的性能,ES并没有每新增一条数据就增加一个段,而是采用了延迟写的策略
写入内存的数据无法被检索出来,只有在新增数据时ES将其数据先写入内存中,在内存和磁盘时文件系统缓存,当达到默认的时间(1秒)或内存的数据达到一定量时,会触发一下刷新(Refresh),将内存中的数据生成到一个新的段上并缓存到文件缓存系统上,稍后再次被刷新到磁盘中并生成提交点
虽然通过延时写的策略可以减少数据往磁盘上写的次数提升了整体的写入能力,但是我们知道文件缓存系统也是内存空间,属于操作系统的内存,只要是内存都存在断电或异常情况下丢失数据的危险。
为了避免丢失数据,Elasticsearch添加了事务日志(Translog),事务日志记录了所有还没有持久化到磁盘的数据。添加了事务日志后整个写索引的流程如下图所示。
一个新文档被索引之后,先被写入到内存中,但是为了防止数据的丢失,会追加一份数据到事务日志中。不断有新的文档被写入到内存,同时也都会记录到事务日志中。这时新数据还不能被检索和查询。
当达到默认的刷新时间或内存中的数据达到一定量后,会触发一次 refresh,将内存中的数据以一个新段形式刷新到文件缓存系统中并清空内存。这时虽然新段未被提交到磁盘,但是可以提供文档的检索功能且不能被修改。
随着新文档索引不断被写入,当日志数据大小超过512M或者时间超过30分钟时,会触发一次 flush。内存中的数据被写入到一个新段同时被写入到文件缓存系统,文件系统缓存中数据通过 fsync 刷新到磁盘中,生成提交点,日志文件被删除,创建一个空的新日志。
通过这种方式当断电或需要重启时,ES不仅要根据提交点去加载已经持久化过的段,还需要工具Translog里的记录,把未持久化的数据重新持久化到磁盘上,避免了数据丢失的可能。
段合并
由于自动刷新流程每秒会创建一个新的段 ,这样会导致短时间内的段数量暴增。而段数目太多会带来较大的麻烦。每一个段都会消耗文件句柄、内存和cpu运行周期。更重要的是,每个搜索请求都必须轮流检查每个段然后合并查询结果,所以段越多,搜索也就越慢。
Elasticsearch通过在后台定期进行段合并来解决这个问题。小的段被合并到大的段,然后这些大的段再被合并到更大的段。段合并的时候会将那些旧的已删除文档从文件系统中清除。被删除的文档不会被拷贝到新的大段中。合并的过程中不会中断索引和搜索。
ES的性能优化
ES存储设备
磁盘在现代服务器上通常都是瓶颈。Elasticsearch 重度使用磁盘,你的磁盘能处理的吞吐量越大,你的节点就越稳定。这里有一些优化磁盘 I/O 的技巧:
使用 SSD。就像其他地方提过的, 他们比机械磁盘优秀多了。
使用 RAID 0。条带化 RAID 会提高磁盘 I/O,代价显然就是当一块硬盘故障时整个就故障了。不要使用镜像或者奇偶校验 RAID 因为副本已经提供了这个功能。
另外,使用多块硬盘,并允许 Elasticsearch 通过多个 path.data 目录配置把数据条带化分配到它们上面。
不要使用远程挂载的存储,比如 NFS 或者 SMB/CIFS。这个引入的延迟对性能来说完全是背道而驰的。
如果你用的是 EC2,当心 EBS。即便是基于 SSD 的 EBS,通常也比本地实例的存储要慢
ES内部索引优化
快速找到某个term,先将所有的term排个序,然后根据二分法查找term,时间复杂度为logN,就像通过字典查找一样,这就是Term Dictionary。现在再看起来,似乎和传统数据库通过B-Tree的方式类似。
但是如果term太多,term dictionary也会很大,放内存不现实,于是有了Term Index,就像字典里的索引页一样,A开头的有哪些term,分别在哪页,可以理解term index是一颗树。这棵树不会包含所有的term,它包含的是term的一些前缀。通过term index可以快速地定位到term dictionary的某个offset,然后从这个位置再往后顺序查找。
在内存中用FST方式压缩term index,FST以字节的方式存储所有的term,这种压缩方式可以有效的缩减存储空间,使得term index足以放进内存,但这种方式也会导致查找时需要更多的CPU资源。演示地址:Build your own FST
对于存储在磁盘上的倒排表同样也采用了压缩技术减少存储所占用的空间,更多可以阅读 Frame of Reference and Roaring Bitmaps。
调整ES参数
给每个文档指定有序的具有压缩良好的序列模式ID,避免随机的UUID-4 这样的 ID,这样的ID压缩比很低,会明显拖慢 Lucene。
对于那些不需要聚合和排序的索引字段禁用Doc values。Doc Values是有序的基于document => field value的映射列表;
不需要做模糊检索的字段使用 keyword类型代替 text 类型,这样可以避免在建立索引前对这些文本进行分词。
如果你的搜索结果不需要近实时的准确度,考虑把每个索引的 index.refreshinterval 改到 30s 。如果你是在做大批量导入,导入期间你可以通过设置这个值为 -1 关掉刷新,还可以通过设置 index.numberof_replicas: 0关闭副本。别忘记在完工的时候重新开启它。
避免深度分页查询建议使用Scroll进行分页查询。普通分页查询时,会创建一个from + size的空优先队列,每个分片会返回from + size 条数据,默认只包含文档id和得分score给协调节点,如果有n个分片,则协调节点再对(from + size)× n 条数据进行二次排序,然后选择需要被取回的文档。当from很大时,排序过程会变得很沉重占用CPU资源严重。
减少映射字段,只提供需要检索,聚合或排序的字段。其他字段可存在其他存储设备上,例如Hbase,在ES中得到结果后再去Hbase查询这些字段。
创建索引和查询时指定路由routing值,这样可以精确到具体的分片查询,提升查询效率。路由的选择需要注意数据的分布均衡。
JVM性能调优
确保堆内存最小值( Xms )与最大值( Xmx )的大小是相同的,防止程序在运行时改变堆内存大小。Elasticsearch 默认安装后设置的堆内存是 1 GB。可通过 …/config/jvm.option文件进行配置,但是最好不要超过物理内存的50%和超过32GB。
GC 默认采用CMS的方式,并发但是有STW的问题,可以考虑使用G1收集器。
ES非常依赖文件系统缓存(Filesystem Cache),快速搜索。一般来说,应该至少确保物理上有一半的可用内存分配到文件系统缓存。