广安企业网站建设,网站建设的一般流程是怎样的,南京医院网站建设方案,网站开发公司 商业计划书前言
搜索引擎是对数据的检索#xff0c;而数据总体分为两种#xff1a;结构化数据和非结构化数据。而对于结构化数据#xff0c;因为他们具有特定的结构#xff0c;所以一般都是可以通过关系型数据库MySQL/oracle的二维表的方式存储和搜索#xff0c;也可以建立索引。…前言
搜索引擎是对数据的检索而数据总体分为两种结构化数据和非结构化数据。而对于结构化数据因为他们具有特定的结构所以一般都是可以通过关系型数据库MySQL/oracle的二维表的方式存储和搜索也可以建立索引。
对于非结构化数据对他们进行搜索主要有两种方法顺序扫描、全文检索。而全文检索就依靠Apache 的 Lucene全文检索引擎工具包。
核心概念
**ES 是使用 Java 编写的一种开源搜索引擎它在内部使用 Lucene 做索引与搜索通过对 Lucene 的封装隐藏了 Lucene 的复杂性取而代之的提供一套简单一致的 RESTful API。**然而Elasticsearch 不仅仅是 Lucene并且也不仅仅只是一个全文搜索引擎。 它可以被下面这样准确的形容 一个分布式的实时文档存储每个字段可以被索引与搜索。一个分布式实时分析搜索引擎。能胜任上百个服务节点的扩展并支持 PB 级别的结构化或者非结构化数据。 官网对 Elasticsearch 的介绍是 Elasticsearch 是一个分布式、可扩展、近实时的搜索与数据分析引擎。所以是怎么做到的呢 特性1可扩展-集群Cluster
ES 的集群搭建很简单不需要依赖第三方协调管理组件自身内部就实现了集群的管理功能。
ES 集群由一个或多个 Elasticsearch 节点组成每个节点配置相同的 cluster.name 即可加入集群默认值为 “elasticsearch”。确保不同的环境中使用不同的集群名称否则最终会导致节点加入错误的集群。
一个 Elasticsearch 服务启动实例就是一个节点Node。节点通过 node.name 来设置节点名称如果不设置则在启动时给节点分配一个随机通用唯一标识符作为名称。
1、发现机制
ES通过设置通用的集群名称就可以连接到同一个集群其内部是依靠Zen Discovery。Zen Discovery 是 Elasticsearch 的内置默认发现模块发现模块的职责是发现集群中的节点以及选举 Master 节点。它提供单播和基于文件的发现并且可以扩展为通过插件支持云环境和其他形式的发现。
Zen Discovery 与其他模块集成例如节点之间的所有通信都使用 Transport 模块完成。节点使用发现机制通过 Ping 的方式查找其他节点。Elasticsearch 默认被配置为使用单播发现以防止节点无意中加入集群。只有在同一台机器上运行的节点才会自动组成集群。 如果集群的节点运行在不同的机器上使用单播你可以为 Elasticsearch 提供一些它应该去尝试连接的节点列表。 **当一个节点联系到单播列表中的成员时它就会得到整个集群所有节点的状态然后它会联系 Master 节点并加入集群。**这意味着单播列表不需要包含集群中的所有节点 它只是需要足够的节点当一个新节点联系上其中一个并且说上话就可以了。 2、选举
如果你使用 Master 候选节点作为单播列表你只要列出三个就可以了。这个配置在 elasticsearch.yml 文件中
discovery.zen.ping.unicast.hosts: [host1, host2:port] 节点启动后先 Ping 如果 discovery.zen.ping.unicast.hosts 有设置则 Ping 设置中的 Host 否则尝试 ping localhost 的几个端口。
**Elasticsearch 支持同一个主机启动多个节点Ping 的 Response 会包含该节点的基本信息以及该节点认为的 Master 节点。选举开始先从各节点认为的 Master 中选规则很简单按照 ID 的字典序排序取第一个。**如果各节点都没有认为的 Master 则从所有节点中选择规则同上。
这里有个限制条件就是 discovery.zen.minimum_master_nodes 如果节点数达不到最小值的限制则循环上述过程直到节点数足够可以开始选举。最后选举结果是肯定能选举出一个 Master 如果只有一个 Local 节点那就选出的是自己。
如果当前节点是 Master 则开始等待节点数达到 discovery.zen.minimum_master_nodes然后提供服务。如果当前节点不是 Master 则尝试加入 Master 。Elasticsearch 将以上服务发现以及选主的流程叫做 Zen Discovery 。 由于它支持任意数目的集群 1- N 所以不能像 Zookeeper 那样限制节点必须是奇数也就无法用投票的机制来选主而是通过一个规则。只要所有的节点都遵循同样的规则得到的信息都是对等的选出来的主节点肯定是一致的。 但分布式系统的问题就出在信息不对等的情况这时候很容易出现脑裂Split-Brain的问题。大多数解决方案就是设置一个 Quorum 值要求可用节点必须大于 Quorum一般是超过半数节点才能对外提供服务。而 Elasticsearch 中这个 Quorum 的配置就是 discovery.zen.minimum_master_nodes 。 3、节点角色
每个节点既可以是候选主节点也可以是数据节点通过在配置文件 …/config/elasticsearch.yml 中设置即可默认都为 true。
node.master: true //是否候选主节点
node.data: true //是否数据节点 数据节点负责数据的存储和相关的操作例如对数据进行增、删、改、查和聚合等操作所以数据节点Data 节点对机器配置要求比较高对 CPU、内存和 I/O 的消耗很大。通常随着集群的扩大需要增加更多的数据节点来提高性能和可用性。
候选主节点可以被选举为主节点Master 节点集群中只有候选主节点才有选举权和被选举权其他节点不参与选举的工作。主节点负责创建索引、删除索引、跟踪哪些节点是群集的一部分并决定哪些分片分配给相关的节点、追踪集群中节点的状态等稳定的主节点对集群的健康是非常重要的。 但是通常来说如果一个节点即是主节点、又是数据节点那么可能会对主节点产生影响从而导致整个集群的状态产生影响。在实践过程中一般都是使用几个配置较低的机器群作为候选主节点群。 协调节点
**主节点和其他节点的心跳机制都是采用Ping的方式进行相互检查。虽然做了角色区分但是用户的请求可以发往任何一个节点并由该节点执行请求分发、收集结果等不需要主节点转发。而这些节点可称之协调节点**协调节点是不需要指定和配置的集群中的任何节点都可以充当协调节点的角色。
脑裂
如果由于网络或其他原因导致集群中选举出多个 Master 节点使得数据更新时出现不一致这种现象称之为脑裂即集群中不同的节点对于 Master 的选择出现了分歧出现了多个 Master 竞争。 “脑裂”问题可能有以下几个原因造成 网络问题 集群间的网络延迟导致一些节点访问不到 Master认为 Master 挂掉了从而选举出新的 Master并对 Master 上的分片和副本标红分配新的主分片。节点负载 主节点的角色既为 Master 又为 Data访问量较大时可能会导致 ES 停止响应假死状态造成大面积延迟此时其他节点得不到主节点的响应认为主节点挂掉了会重新选取主节点。内存回收 主节点的角色既为 Master 又为 Data当 Data 节点上的 ES 进程占用的内存较大引发 JVM 的大规模内存回收造成 ES 进程失去响应。 如何解决脑裂
适当调大响应时间减少误判。 通过参数 discovery.zen.ping_timeout 设置节点状态的响应时间默认为 3s可以适当调大。
如果 Master 在该响应时间的范围内没有做出响应应答判断该节点已经挂掉了。调大参数如 6sdiscovery.zen.ping_timeout:6可适当减少误判。
选举触发。 我们需要在候选集群中的节点的配置文件中设置参数 discovery.zen.munimum_master_nodes 的值。这个参数表示在选举主节点时需要参与选举的候选主节点的节点数默认值是 1官方建议取值(master_eligibel_nodes2)1其中 master_eligibel_nodes 为候选主节点的个数。
这样做既能防止脑裂现象的发生也能最大限度地提升集群的高可用性因为只要不少于 discovery.zen.munimum_master_nodes 个候选节点存活选举工作就能正常进行。当小于这个值的时候无法触发选举行为集群无法使用不会造成分片混乱的情况。
**角色分离。**这样可以减轻主节点的负担防止主节点的假死状态发生减少对主节点“已死”的误判。
特性2分布式-分片
ES 支持 PB 级全文搜索当索引上的数据量太大的时候ES 通过水平拆分的方式将一个索引上的数据拆分出来分配到不同的数据块上拆分出来的数据库块称之为一个分片。 这类似于 MySQL 的分库分表只不过 MySQL 分库分表需要借助第三方组件而 ES 内部自身实现了此功能。
**在一个多分片的索引中写入数据时通过路由来确定具体写入哪一个分片中所以在创建索引的时候需要指定分片的数量并且分片的数量一旦确定就不能修改。**分片的数量和副本数量都是可以通过创建索引时的 Settings 来配置**ES 默认为一个索引创建 5 个主分片, 并分别为每个分片创建一个副本。
ES 通过分片的功能使得索引在规模上和性能上都得到提升每个分片都是 Lucene 中的一个索引文件每个分片必须有一个主分片和零到多个副本。
副本
副本就是对分片的 Copy每个主分片都有一个或多个副本分片当主分片异常时副本可以提供数据的查询等操作。主分片和对应的副本分片是不会在同一个节点上的所以副本分片数的最大值是 N-1其中 N 为节点数。
对文档的新建、索引和删除请求都是写操作必须在主分片上面完成之后才能被复制到相关的副本分片。**ES 为了提高写入的能力这个过程是并发写的同时为了解决并发写的过程中数据冲突的问题ES 通过乐观锁的方式控制每个文档都有一个 _version 版本号当文档被修改时版本号递增。**一旦所有的副本分片都报告写成功才会向协调节点报告成功协调节点向客户端报告成功。
小结
将数据分片是为了提高可处理数据的容量和易于进行水平扩展为分片做副本是为了提高集群的稳定性和提高并发量。副本是乘法越多消耗越大但也越保险。分片是除法分片越多单分片数据就越少也越分散。
副本越多集群的可用性就越高但是由于每个分片都相当于一个 Lucene 的索引文件会占用一定的文件句柄、内存及 CPU。并且分片间的数据同步也会占用一定的网络带宽所以索引的分片数和副本数也不是越多越好。
特性3近实时的搜索与数据分析 数据结构
**映射Mapping是用于定义 ES 对索引中字段的存储类型、分词方式和是否存储等信息就像数据库中的 Schema 描述了文档可能具有的字段或属性、每个字段的数据类型。**只不过关系型数据库建表时必须指定字段类型而 ES 对于字段类型可以不指定然后动态对字段类型猜测也可以在创建索引时具体指定字段的类型。
对字段类型根据数据格式自动识别的映射称之为动态映射Dynamic Mapping创建索引时具体定义字段类型的映射称之为静态映射或显示映射Explicit Mapping。
数据类型
ESv6.8中字段数据类型主要有以下几类 Text
**Text 用于索引全文值的字段例如电子邮件正文或产品说明。这些字段是被分词的它们通过分词器传递 以在被索引之前将字符串转换为单个术语的列表。**分析过程允许 Elasticsearch 搜索单个单词中每个完整的文本字段。文本字段不用于排序很少用于聚合。
Keyword
Keyword 用于索引结构化内容的字段例如电子邮件地址主机名状态代码邮政编码或标签。它们通常用于过滤排序和聚合。Keyword 字段只能按其确切值进行搜索。
Date
时间字段也许我们需要指定它的时间格式还有一些字段我们需要指定特定的分词器等等。
例子
所以创建索引的时候一个完整的格式应该是指定分片和副本数以及 Mapping 的定义如下
PUT my_index
{ settings : { number_of_shards : 5, number_of_replicas : 1 } mappings: { _doc: { properties: { title: { type: text }, name: { type: text }, age: { type: integer }, created: { type: date, format: strict_date_optional_time||epoch_millis } } } }
} 旁外话1-版本的选型
版本问题
在决定使用 Elasticsearch 的时候首先要考虑的是版本问题Elasticsearch 排除 0.x 和 1.x目前有如下常用的稳定的主版本2.x5.x6.x7.xcurrent。你可能会发现没有 3.x 和 4.xES 从 2.4.6 直接跳到了 5.0.0。其实是为了 ELKElasticSearchLogstashKibana技术栈的版本统一免的给用户带来混乱。
在 Elasticsearch 是 2.x 2.x 的最后一版 2.4.6 的发布时间是 July 25, 2017 的情况下Kibana 已经是 4.xKibana 4.6.5 的发布时间是 July 25, 2017。
那么在 Kibana 的下一主版本肯定是 5.x 了所以 Elasticsearch 直接将自己的主版本发布为 5.0.0 了。
统一之后我们选版本就不会犹豫困惑了我们选定 Elasticsearch 的版本后再选择相同版本的 Kibana 就行了不用担忧版本不兼容的问题。
JDK版本
Elasticsearch 是使用 Java 构建所以除了注意 ELK 技术的版本统一我们在选择 Elasticsearch 的版本的时候还需要注意 JDK 的版本。因为每个大版本所依赖的 JDK 版本也不同目前 7.2 版本已经可以支持 JDK11。
旁外话2-集群的健康状态
要检查群集运行状况我们可以在 Kibana 控制台中运行以下命令 GET /_cluster/health得到如下信息
{ cluster_name : wujiajian, status : yellow, timed_out : false, number_of_nodes : 1, number_of_data_nodes : 1, active_primary_shards : 9, active_shards : 9, relocating_shards : 0, initializing_shards : 0, unassigned_shards : 5, delayed_unassigned_shards : 0, number_of_pending_tasks : 0, number_of_in_flight_fetch : 0, task_max_waiting_in_queue_millis : 0, active_shards_percent_as_number : 64.28571428571429
} 集群状态通过 绿黄红 来标识
绿色集群健康完好一切功能齐全正常所有分片和副本都可以正常工作。黄色**预警状态所有主分片功能正常但至少有一个副本是不能正常工作的。**此时集群是可以正常工作的但是高可用性在某种程度上会受影响。红色**集群不可正常使用。某个或某些分片及其副本异常不可用这时集群的查询操作还能执行但是返回的结果会不准确。**对于分配到这个分片的写入请求将会报错最终会导致数据的丢失。
当集群状态为红色时它将会继续从可用的分片提供搜索请求服务但是你需要尽快修复那些未分配的分片。
ES 机制原理
写索引原理
一般来说3 个节点的集群共拥有 12 个分片其中有 4 个主分片S0、S1、S2、S3和 8 个副本分片R0、R1、R2、R3每个主分片对应两个副本分片节点 1 是主节点Master 节点负责整个集群的状态。
**写索引是只能写在主分片上然后同步到副本分片。**这里有四个主分片一条数据 ES 是根据什么规则写到特定分片上的呢实际上这个过程是根据下面这个公式决定的
shard hash(routing) % number_of_primary_shards Routing 是一个可变值默认是文档的 _id 也可以设置成一个自定义的值。Routing 通过 Hash 函数生成一个数字然后这个数字再除以 number_of_primary_shards 主分片的数量后得到余数。
**这个在 0 到 number_of_primary_shards-1 之间的余数就是我们所寻求的文档所在分片的位置。**这也就是要在创建索引的时候就确定好主分片的数量并且永远不会改变这个数量因为如果数量变化了那么所有之前路由的值都会无效文档也再也找不到了。
由于在 ES 集群中每个节点通过上面的计算公式都知道集群中的文档的存放位置所以每个节点都有处理读写请求的能力。在一个写请求被发送到某个节点后该节点即为前面说过的协调节点协调节点会根据路由公式计算出需要写到哪个分片上再将请求转发到该分片的主分片节点上。 假如此时数据通过路由计算公式取余后得到的值是 shardhash(routing)%40。 则具体流程如下 客户端向 ES1 节点协调节点发送写请求通过路由计算公式得到值为 0则当前数据应被写到主分片 S0 上。ES1 节点将请求转发到 S0 主分片所在的节点 ES3ES3 接受请求并写入到磁盘。并发将数据复制到两个副本分片 R0 上其中通过乐观并发控制数据的冲突。一旦所有的副本分片都报告成功则节点 ES3 将向协调节点报告成功协调节点向客户端报告成功。 存储原理
上面说明在 ES 内部索引的写处理流程这个流程是在 ES 的内存中执行的数据被分配到特定的分片和副本上之后最终是存储到磁盘上的这样在断电的时候就不会丢失数据。
具体的存储路径可在配置文件 ../config/elasticsearch.yml 中进行设置默认存储在安装目录的 Data 文件夹下。建议不要使用默认值因为若 ES 进行了升级则有可能导致数据全部丢失
path.data: /path/to/data //索引数据
path.logs: /path/to/logs //日志记录 分段存储
索引文档以段的形式存储在磁盘上。索引文件被拆分为多个子文件则每个子文件叫作段每一个段本身都是一个倒排索引并且段具有不变性一旦索引的数据被写入硬盘就不可再修改。
在底层采用了分段的存储模式使它在读写时几乎完全避免了锁的出现大大提升了读写性能。段被写入到磁盘后会生成一个提交点提交点是一个用来记录所有提交后段信息的文件。一个段一旦拥有了提交点就说明这个段只有读的权限失去了写的权限。相反当段在内存中时就只有写的权限而不具备读数据的权限意味着不能被检索。 段的概念提出主要是因为**在早期全文检索中为整个文档集合建立了一个很大的倒排索引并将其写入磁盘中。如果索引有更新就需要重新全量创建一个索引来替换原来的索引。**这种方式在数据量很大时效率很低并且由于创建一次索引的成本很高所以对数据的更新不能过于频繁也就不能保证时效性。 数据更新如何处理
新增新增很好处理由于数据是新的所以只需要对当前文档新增一个段就可以了。**删除由于不可修改所以对于删除操作不会把文档从旧的段中移除而是通过新增一个 .del 文件文件中会列出这些被删除文档的段信息。**这个被标记删除的文档仍然可以被查询匹配到 但它会在最终结果被返回前从结果集中移除。**更新不能修改旧的段来进行反映文档的更新其实更新相当于是删除和新增这两个动作组成。会将旧的文档在 .del 文件中标记删除然后文档的新版本被索引到一个新的段中。**可能两个版本的文档都会被一个查询匹配到但被删除的那个旧版本文档在结果集返回前就会被移除。
优缺点 优势主要表现在 不需要锁。如果你从来不更新索引你就不需要担心多进程同时修改数据的问题。一旦索引被读入内核的文件系统缓存便会留在哪里由于其不变性。只要文件系统缓存中还有足够的空间那么大部分读请求会直接请求内存而不会命中磁盘。这提供了很大的性能提升。其它缓存(像 Filter 缓存)在索引的生命周期内始终有效。它们不需要在每次数据改变时被重建因为数据不会变化。写入单个大的倒排索引允许数据被压缩减少磁盘 I/O 和需要被缓存到内存的索引的使用量。 缺点如下 当对旧数据进行删除时旧数据不会马上被删除而是在 .del 文件中被标记为删除。而旧数据只能等到段更新时才能被移除这样会造成大量的空间浪费。若有一条数据频繁的更新每次更新都是新增新的标记旧的则会有大量的空间浪费。每次新增数据时都需要新增一个段来存储数据。当段的数量太多时对服务器的资源例如文件句柄的消耗会非常大。在查询的结果中包含所有的结果集需要排除被标记删除的旧数据这增加了查询的负担。 延迟写策略
看过mysql底层原理或者RocketMQ的都知道数据的持久性都是由磁盘来保证的而为了性能最大化一般都采用先写入缓存再刷盘到磁盘中。
为了提升写的性能ES 并没有每新增一条数据就增加一个段到磁盘上而是采用延迟写的策略。每当有新增的数据时就将其先写入到内存中在内存和磁盘之间是文件系统缓存。
当达到默认的时间1 秒钟或者内存的数据达到一定量时会触发一次刷新Refresh将内存中的数据生成到一个新的段上并缓存到文件缓存系统 上稍后再被刷新到磁盘中并生成提交点。这里的内存使用的是 ES 的 JVM 内存而文件缓存系统使用的是操作系统的内存。
新的数据会继续的被写入内存但内存中的数据并不是以段的形式存储的因此不能提供检索功能。由内存刷新到文件缓存系统的时候会生成新的段并将段打开以供搜索使用而不需要等到被刷新到磁盘。
**在 Elasticsearch 中写入和打开一个新段的轻量的过程叫做 Refresh 即内存刷新到文件缓存系统。默认情况下每个分片会每秒自动刷新一次。**这就是为什么我们说 Elasticsearch 是近实时搜索因为文档的变化并不是立即对搜索可见但会在一秒之内变为可见。
当然也可以手动触发 RefreshPOST /_refresh 刷新所有索引POST /nba/_refresh 刷新指定的索引。 Tips尽管刷新是比提交轻量很多的操作它还是会有性能开销。当写测试的时候 手动刷新很有用但是不要在生产环境下每次索引一个文档都去手动刷新。而且并不是所有的情况都需要每秒刷新。 可能你正在使用 Elasticsearch 索引大量的日志文件 你可能想优化索引速度而不是近实时搜索。 这时可以在创建索引时在 Settings 中通过调大 refresh_interval 30s 的值 降低每个索引的刷新频率设值时需要注意后面带上时间单位否则默认是毫秒。当 refresh_interval-1 时表示关闭索引的自动刷新。 虽然通过延时写的策略可以减少数据往磁盘上写的次数提升了整体的写入能力但是我们知道文件缓存系统也是内存空间属于操作系统的内存只要是内存都存在断电或异常情况下丢失数据的危险。 事务日志
为了避免丢失数据Elasticsearch 添加了事务日志Translog事务日志记录了所有还没有持久化到磁盘的数据。 添加了事务日志后整个写索引的流程 一个新文档被索引之后先被写入到内存中但是为了防止数据的丢失**会追加一份数据到事务日志中。**不断有新的文档被写入到内存同时也都会记录到事务日志中。这时新数据还不能被检索和查询。当达到默认的刷新时间或内存中的数据达到一定量后会触发一次 Refresh将内存中的数据以一个新段形式刷新到文件缓存系统中并清空内存。这时虽然新段未被提交到磁盘但是可以提供文档的检索功能且不能被修改。**随着新文档索引不断被写入当日志数据大小超过 512M 或者时间超过 30 分钟时会触发一次 Flush。**内存中的数据被写入到一个新段同时被写入到文件缓存系统当文件系统缓存中数据通过 Fsync 刷新到磁盘中生成提交点日志文件被删除创建一个空的新日志。 通过这种方式当断电或需要重启时ES 不仅要根据提交点去加载已经持久化过的段还需要工具 Translog 里的记录把未持久化的数据重新持久化到磁盘上避免了数据丢失的可能。
段合并
由于自动刷新流程每秒会创建一个新的段 这样会导致短时间内的段数量暴增。而段数目太多会带来较大的麻烦。每一个段都会消耗文件句柄、内存和 CPU 运行周期。更重要的是每个搜索请求都必须轮流检查每个段然后合并查询结果所以段越多搜索也就越慢。
Elasticsearch 通过在后台定期进行段合并来解决这个问题。小的段被合并到大的段然后这些大的段再被合并到更大的段。段合并的时候会将那些旧的已删除文档从文件系统中清除。被删除的文档不会被拷贝到新的大段中。合并的过程中不会中断索引和搜索。
段合并在进行索引和搜索时会自动进行合并进程选择一小部分大小相似的段并且在后台将它们合并到更大的段中这些段既可以是未提交的也可以是已提交的。合并结束后老的段会被删除新的段被 Flush 到磁盘同时写入一个包含新段且排除旧的和较小的段的新提交点新的段被打开可以用来搜索。
段合并的计算量庞大 而且还要吃掉大量磁盘 I/O段合并会拖累写入速率如果任其发展会影响搜索性能。Elasticsearch 在默认情况下会对合并流程进行资源限制所以搜索仍然有足够的资源很好地执行。
六、ES 的性能优化
存储设备
磁盘在现代服务器上通常都是瓶颈。Elasticsearch 重度使用磁盘你的磁盘能处理的吞吐量越大你的节点就越稳定。 这里有一些优化磁盘 I/O 的技巧 **使用 SSD。**就像其他地方提过的 他们比机械磁盘优秀多了。使用 RAID 0。条带化 RAID 会提高磁盘 I/O代价显然就是当一块硬盘故障时整个就故障了。不要使用镜像或者奇偶校验 RAID 因为副本已经提供了这个功能。另外使用多块硬盘并允许 Elasticsearch 通过多个 path.data 目录配置把数据条带化分配到它们上面。不要使用远程挂载的存储比如 NFS 或者 SMB/CIFS。这个引入的延迟对性能来说完全是背道而驰的。如果你用的是 EC2当心 EBS。即便是基于 SSD 的 EBS通常也比本地实例的存储要慢。 内部索引优化
**Elasticsearch 为了能快速找到某个 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 IndexFST 以字节的方式存储所有的 Term这种压缩方式可以有效的缩减存储空间使得 Term Index 足以放进内存但这种方式也会导致查找时需要更多的 CPU 资源。
对于存储在磁盘上的倒排表同样也采用了压缩技术减少存储所占用的空间。
调整配置参数
调整配置参数建议如下 给每个文档指定有序的具有压缩良好的序列模式 ID避免随机的 UUID-4 这样的 ID这样的 ID 压缩比很低会明显拖慢 Lucene。 **对于那些不需要聚合和排序的索引字段禁用 Doc values。**Doc Values 是有序的基于 documentfield value 的映射列表。 不需要做模糊检索的字段使用 Keyword 类型代替 Text 类型这样可以避免在建立索引前对这些文本进行分词。 如果你的搜索结果不需要近实时的准确度考虑把每个索引的 index.refresh_interval 改到 30s 。 如果你是在做大批量导入导入期间你可以通过设置这个值为 -1 关掉刷新还可以通过设置 index.number_of_replicas: 0 关闭副本。别忘记在完工的时候重新开启它。 避免深度分页查询建议使用 Scroll 进行分页查询。普通分页查询时会创建一个 fromsize 的空优先队列每个分片会返回 fromsize 条数据默认只包含文档 ID 和得分 Score 给协调节点。 如果有 N 个分片则协调节点再对fromsize×n 条数据进行二次排序然后选择需要被取回的文档。当 from 很大时排序过程会变得很沉重占用 CPU 资源严重。 **减少映射字段只提供需要检索聚合或排序的字段。**其他字段可存在其他存储设备上例如 Hbase在 ES 中得到结果后再去 Hbase 查询这些字段。 **创建索引和查询时指定路由 Routing 值这样可以精确到具体的分片查询提升查询效率。**路由的选择需要注意数据的分布均衡。
JVM 调优
JVM 调优建议如下
确保堆内存最小值 Xms 与最大值 Xmx 的大小是相同的防止程序在运行时改变堆内存大小。Elasticsearch 默认安装后设置的堆内存是 1GB。可通过 ../config/jvm.option 文件进行配置但是最好不要超过物理内存的50%和超过 32GB。GC 默认采用 CMS 的方式并发但是有 STW 的问题可以考虑使用 G1 收集器。ES 非常依赖文件系统缓存Filesystem Cache快速搜索。一般来说应该至少确保物理上有一半的可用内存分配到文件系统缓存。
总结常见问题
1、它们内部是如何运行的
数据采用乐观锁机制通过算法来判断存储在哪个分片上先存入缓存后刷盘到磁盘。缓存中只能写不能读磁盘中只能读不能写。所以需要删除的数据有专门的文件来进行维护数据实际上可以查询到但是不返回类似Spring后置处理器专门处理一下。
2、主分片和副本分片是如何同步的
集群的协调节点接收到请求通过路由算法确定该文档所属的主分片将请求转发给该主分片所在节点当主节点写入成功之后会将请求同时转发给多个对应副分片所在节点。
3、创建索引的流程是什么样的
1客户端发送索引请求客户端向ES节点发送索引请求。
以RestClient客户端发起请求为例ES提供了Java High Level REST Client可以通过RestClient发送请求
RestClient restClient RestClient.builder(new HttpHost(127.0.0.1, 9200, http),new HttpHost(127.0.0.2, 9200, http) ).build(); 其中127.0.0.1127.0.0.2是ES节点地址充当coordinate node节点的角色接收客户端请求如果设置有专用coorinate node则应该将接受客户端请求的节点设置为该专用节点负责请求的接受和转发。在RestClient中使用round-robin轮询算法进行发送节点的选取。
2参数检查。
对请求中的参数进行检查检查参数是否合法不合法的参数直接返回失败给客户端。
3.数据预处理
如果请求指定了pipeline参数则对数据进行预处理数据预处理的节点为Ingest Node如果接受请求的节点不具备数据处理能力则转发给其他能处理的节点。
在Ingest Node上有定义好的处理数据的PipelinePipeline中有一组定义好的Processor每个Processor分别具有不同的处理功能ES提供了一些内置的Processor如split、join、set 、script等同时也支持通过插件的方式实现自定义的Processor。数据经过Pipeline处理完毕后继续进行下一步操作。
4判断索引是否存在判断索引是否存在。
如果索引不存在则判断是否能够自动创建可以通过action.auto_create_index设置能否自动创建索引如果节点支持Dynamic Mapping写入文档时如果字段尚未在mapping中定义则会根据索引文档信息推算字段的类型但并不能完全推算正确。 Dynamictrue时文档有新增字段的时候索引的mapping也会同步更新。 Dynamicfalse时索引的mapping不会被更新新增字段无法被索引到。 Dynamicstrict时索引有新增字段时将会报错。 5创建索引
创建索引请求被发送到Master节点由Master节点负责进行索引的创建索引创建成功后Master节点会更新集群状态clusterstate。更新完毕后将索引创建的情况返回给Coordinate节点收到Master节点返回后进入下一流程。
6请求预处理
6-1获取集群状态信息判断集群是否正常
6-2从集群状态中获取对应索引的元信息从元信息中获取索引的mapping、version等信息从请求中解析routing、id信息如果请求没有指定文档的id则会生成一个UUID作为文档的id。
7路由计算
根据请求的routing、id信息计算文档应该被索引到哪个分片。
计算公式为shard_num hash(routing) % num_primary_shards
其中routing默认值为文档idnum_primary_shards是主分片个数所以从算法中即可以看出索引的主分片个数一旦指定便无法修改因为文档利用主分片的个数来进行定位。
当使用自定义routing或者id时按照上面的公式计算数据可能会大量聚集于某些分片造成数据分布不均衡所以ES提供了routing_partition_size参数routing_partition_size越大数据的分布越均匀。
此时分片的计算公式变为shard_num (hash(_routing) hash(_id) % routing_partition_size) % num_primary_shards
**定位到分片序号后还需要定位分片所属的数据节点从集群状态的内容路由表获取主分片所在的节点并将请求转发至节点。**需要注意的是分片到数据节点的映射关系不是固定的当检测到数据分布不均匀、新节点加入或者节点宕掉等会进行分片的重新分配。
8主分片索引文档
当主分片所在节点接受到请求后节点开始进行本节点的文档写入文档写入过程
1**文档写入时不会直接写入到磁盘中而是先将文档写入到Index Buffer内存空间中到一定的时间Index Buffer会Refresh把内存中的文档写入Segment中。**当文档在Index Buffer中时是无法被查询到的这就是ES不是实时搜索而是近实时搜索的原因。
2因为文档写入时先写入到内存中当文档落盘之前节点出现故障重启、宕机等会造成内存中的数据丢失所以索引写入的同时会同步向Transaction Log写入操作内容。
3每隔固定的时间间隔ES会将Index Buffer中的文档写入到Segment中这个写入的过程叫做RefreshRefresh的时间可以通过index.refresh_interval设置默认情况下为1秒。
4写入到Segment中并不代表文档已经落盘因为Segment写入磁盘的过程相对耗时Refresh时会先将Segment写入缓存开放查询也就是说当文档写入Segment后就可以被查询到。
每次refresh的时候都会生成一个新的segment太多的Segment会占用过多的资源而且每个搜索请求都会遍历所有的SegmentSegment过多会导致搜索变慢所以ES会定期合并Segment减少Segment的个数并将Segment和并为一个大的Segment。
在操作Segment时会维护一个Commit Point文件其中记录了所有Segment的信息同时维护.del文件用于记录所有删除的Segment信息。 **单个倒排索引文件被称为Segment。多个Segment汇总在一起就是Lucene的索引对应的就是ES中的shard。**Lucene倒排索引由单词词典及倒排列表组成 单词词典记录所有文档的单词记录单词到倒排列表的关系数据量比较大一般采用B树哈希拉链法实现。 倒排列表记录单词对应的文档集合由倒排索引项组成。倒排索引项结构如表所示 文档ID记录单词所在文档的ID 词频记录单词在文档中出现的次数 位置记录单词在文档中的位置 偏移记录单词的开始位置结束位置。 4、ES 如何将索引数据分配到不同的分片上的以及这些索引数据是如何存储的
由shard hash(routing) % number_of_primary_shards 这个公式来判断分配到哪个分片上。索引数据按段存储先在缓存中再经过Refresh生成段存储到磁盘中。
5、为什么说 ES 是近实时搜索引擎而文档的 CRUD (创建-读取-更新-删除) 操作是实时的
默认情况下Refresh每个分片会每秒自动刷新一次。
CRUD也算是近实时是按照Refresh到磁盘中为依据
6、以及 Elasticsearch 是怎样保证更新被持久化在断电时也不丢失数据
磁盘文件、事务日志。
根据提交点去加载已经持久化过的段还需要工具 Translog 里的记录把未持久化的数据重新持久化到磁盘上
7、还有为什么删除文档不会立刻释放空间
因为会用于段合并 其他后续再进行填充优化