*******************************
系统中的数据, 随着业务的发展, 时间的推移, 将会非常多,而业务中往往采用模糊查询进行数据的 搜索,而模糊查询会导致查询引擎放弃索引, 导致系统查询数据时都是全表扫描,在百万级别的数据库中, 查询效率是非常低下的,而我们使用 ES 做一个全文索引, 将经常查询的系统功能的某些字段,比如说电 商系统的商品表中商品名,描述、价格还有 id 这些字段我们放入 ES 索引库里,可以提高查询速度。
- Elasticsearch 的选主是 ZenDiscovery 模块负责的, 主要包含 Ping(节点之间通过这个 RPC 来发现彼此)和 Unicast (单播模块包含一个主机列表以控制哪些节点需要 ping 通)这两部分
- 对所有可以成为 master 的节点(node.master: true)根据 nodeId 字典排序,每次选举每个节点都把自己所知道节点排一次序,然后选出第一个(第 0 位)节点, 暂且认为它是 master 节点
- 如果对某个节点的投票数达到一定的值(可以成为 master 节点数 n/2+1) 并且该节点自己也选举自己,那这个节点就是 master 。否则重新选举一直到满足上述条件
- master 节点的职责主要包括集群、节点和索引的管理, 不负责文档级别的管理; data 节点可以关闭 http功能
所谓脑裂问题(类似于精神分裂),就是同一个集群中的不同节点,对于集群的状态有了不一样的理解。
由于某些节点的失效,部分节点的网络连接会断开,并形成一个与原集群一样名字的集群,这种情况成为集群脑裂(split-brain)现象。这个问题非常危险,因为两个新形成的集群会同时索引和修改集群的数据。
“脑裂”问题可能的成因:
脑裂问题解决方案:
discovery.zen.minimum_master_nodes:1
,该参数是用于控制选举行为发生的最小集群主节点数量。当备选主节点的个数大于等于该参数的值, 且备选主节点中有该参数个节点认为主节点挂了, 进行选举。官方建议为(n/2) +1,n 为主节点个数 (即有资格成为主节点的节点个数)新建单个文档所需要的步骤顺序:
客户端向 Node 1 发送新建、索引或者删除请求。
节点使用文档的 _id 确定文档属于分片 0 。请求会被转发到 Node 3,因为分片 0 的主分片目前被分配在 Node 3 上。
Node 3 在主分片上面执行请求。如果成功了,它将请求并行转发到 Node 1 和 Node 2 的副本分片上。一旦所有的副本分片都报告成功, Node 3 将向协调节点报告成功,协调节点向客户端报告成功。
shard = hash(document_id) % (num_of_primary_shards)
- 删除和更新也都是写操作,但是 Elasticsearch 中的文档是不可变的,因此不能被删除或者改动以展示其变更;
- 磁盘上的每个段都有一个相应的.del 文件。当删除请求发送后,文档并没有真的被删除,而是在.del文件中被标记为删除。该文档依然能匹配查询,但是会在结果中被过滤掉。当段合并时,在.del 文件中被标记为删除的文档将不会被写入新段。
- 在新的文档被创建时, Elasticsearch 会为该文档指定一个版本号, 当执行更新时, 旧版本的文档在.del文件中被标记为删除, 新版本的文档被索引到一个新段。旧版本的文档依然能匹配查询, 但是会在结 果中被过滤掉。
- 搜索被执行成一个两阶段过程,我们称之为 Query Then Fetch;
- 在初始查询阶段时,查询会广播到索引中每一个分片拷贝(主分片或者副本分片) 。 每个分片在本地执行搜索并构建一个匹配文档的大小为 from + size 的优先队列。PS:在搜索的时候是会查询 Filesystem Cache 的,但是有部分数据还在 Memory Buffer,所以搜索是近实时的。
- 每个分片返回各自优先队列中 所有文档的 ID 和排序值 给协调节点, 它合并这些值到自己的优先队列中来产生一个全局排序后的结果列表。
- 接下来就是取回阶段, 协调节点辨别出哪些文档需要被取回并向相关的分片提交多个 GET 请求。每个分片加载并丰富文档,如果有需要的话,接着返回文档给协调节点。一旦所有的文档都被取回了, 协调节点返回结果给客户端。
- Query Then Fetch 的搜索类型在文档相关性打分的时候参考的是本分片的数据,这样在文档数量较少的时候可能不够准确, DFS Query Then Fetch 增加了一个预查询的处理,询问 Term 和 Document frequency,这个评分更准确, 但是性能会变差。
- 倒排词典的索引需要常驻内存,无法 GC,需要监控 data node 上 segment memory 增长趋势。
- 各类缓存, field cache, filter cache, indexing cache, bulk queue 等等, 要设置合理的大小, 并且要应该根据最坏的情况来看 heap 是否够用,也就是各类缓存全部占满的时候,还有 heap 空间可以分配给其他 任务吗?避免采用 clear cache 等“自欺欺人”的方式来释放内存。
- 避免返回大量结果集的搜索与聚合。确实需要大量拉取数据的场景, 可以采用 scan & scroll api 来实现。
- cluster stats 驻留内存并无法水平扩展,超大规模集群可以考虑分拆成多个集群通过 tribe node 连接。
- 想知道 heap 够不够,必须结合实际应用场景,并对集群的 heap 使用情况做持续的监控。
- 64 GB 内存的机器是非常理想的, 但是 32 GB 和 16 GB 机器也是很常见的。少于 8 GB 会适得其反。
- 如果你要在更快的 CPUs 和更多的核心之间选择,选择更多的核心更好。多个内核提供的额外并发远胜过稍微快一点点的时钟频率。
- 如果你负担得起 SSD,它将远远超出任何旋转介质。 基于 SSD 的节点, 查询和索引性能都有提升。如果你负担得起, SSD 是一个好的选择。
- 即使数据中心们近在咫尺,也要避免集群跨越多个数据中心。绝对要避免集群跨越大的地理距离。
- 请确保运行你应用程序的 JVM 和服务器的 JVM 是完全一样的。 在 Elasticsearch 的几个地方,使用 Java 的本地序列化。
- 通过设置 gateway.recover_after_nodes、gateway.expected_nodes、gateway.recover_after_time 可以在集群重启的时候避免过多的分片交换,这可能会让数据恢复从数个小时缩短为几秒钟。
- Elasticsearch 默认被配置为使用单播发现,以防止节点无意中加入集群。只有在同一台机器上运行的节点才会自动组成集群。最好使用单播代替组播。
- 不要随意修改垃圾回收器(CMS)和各个线程池的大小。
- 你的内存的(少于) 一半给 Lucene (但不要超过 32 GB!) ,通过 ES_HEAP_SIZE 环境变量设置。
- 内存交换到磁盘对服务器性能来说是致命的。如果内存交换到磁盘上,一个 100 微秒的操作可能变成 10 毫秒。 再想想那么多 10 微秒的操作时延累加起来。 不难看出 swapping 对于性能是多么可怕。
- Lucene 使用了大量的文件。同时, Elasticsearch 在节点和 HTTP 客户端之间进行通信也使用了大量的套接字。 所有这一切都需要足够的文件描述符。你应该增加你的文件描述符, 设置一个很大的值,如 64,000。
- 使用批量请求并调整其大小:每次批量数据 5 – 15 MB 大是个不错的起始点。
- 存储:使用 SSD
- 段和合并: Elasticsearch 默认值是 20 MB/s,对机械磁盘应该是个不错的设置。如果你用的是 SSD,可以考虑提高到 100 –200 MB/s。如果你在做批量导入, 完全不在意搜索, 你可以彻底关掉合并限流。 另外还可以增加 index.translog.flush_threshold_size 设置,从默认的 512 MB 到更大一些的值,比如 1 GB ,这可以在一次清空触发的时候在事务日志里积累出更大的段。
- 如果你的搜索结果不需要近实时的准确度,考虑把每个索引的 index.refresh_interval 改到 30s。
- 如果你在做大批量导入,考虑通过设置 index.number_of_replicas: 0 关闭副本。
面试官:想了解应聘者之前公司接触的 ES 使用场景、规模,有没有做过比较大规模的索引设计、规划、调优。
解答:如实结合自己的实践场景回答即可。
比如:ES 集群架构 13 个节点,索引根据通道不同共 20+索引,根据日期,每日递增 20+,索引:10分片,每日递增 1 亿+数据,每个通道每天索引大小控制:150GB 之内。
仅索引层面调优手段:
设计阶段调优
写入调优
查询调优
Elasticsearch 提供的首个近似聚合是 cardinality 度量。它提供一个字段的基数,即该字段的 distinct 或者 unique 值的数目。它是基于 HLL 算法的。 HLL 会先对我们的输入作哈希运算,然后根据哈希运算的 结果中的 bits 做概率估算从而得到基数。其特点是: 可配置的精度, 用来控制内存的使用(更精确 = 更 多内存);小的数据集精度是非常高的;我们可以通过配置参数,来设置去重需要的固定内存使用量。无 论数千还是数十亿的唯一值,内存使用量只与你配置的精确度相关
- 可以通过版本号使用乐观并发控制,以确保新版本不会被旧版本覆盖,由应用层来处理具体的冲突;
- 对于写操作,一致性级别支持 quorum/one/all,默认为 quorum,即只有当大多数分片可用时才允许写操作。但即使大多数可用, 也可能存在因为网络等原因导致写入副本失败, 这样该副本被认为故 障,分片将会在一个不同的节点上重建。
- 对于读操作, 可以设置 replication 为 sync(默认),这使得操作在主分片和副本分片都完成后才会返回;如果设置 replication 为 async 时,也可以通过设置搜索请求参数_preference 为 primary 来查询主分片, 确保文档是最新版本。
elasticsearch-head 插件通过 Kibana 监控 Elasticsearch。你可以实时查看你的集群健康状态和性能, 也可以分析过去的集群、 索引和节点指标
常用字典数据结构如下所示:
字典树又称单词查找树, Trie 树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计, 排序和保存大量的字符串 (但不仅限于字符串) ,所以经常被搜索引擎系统用于文本词频统计。
它的优点是:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希树高。
Trie 的核心思想是空间换时间,利用字符串的公共前缀来降低查询时间的开销以达到提高效率的目的。
它有 3 个基本性质:
- 根节点不包含字符,除根节点外每一个节点都只包含一个字符
- 从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串
- 每个节点的所有子节点包含的字符都不相同。
对于中文的字典树,每个节点的子节点用一个哈希表存储, 这样就不用浪费太大的空间, 而且查询速度上 可以保留哈希的复杂度 O(1)。
- 集群是一个或多个节点(服务器) 的集合, 它们共同保存您的整个数据, 并提供跨所有节点的联合索引和搜索功能。群集由唯一名称标识, 默认情况下为“elasticsearch”。此名称很重要, 因为如果节点设 置为按名称加入群集,则该节点只能是群集的一部分。
- 节点是属于集群一部分的单个服务器。它存储数据并参与群集索引和搜索功能。
- 索引就像关系数据库中的“数据库”。它有一个定义多种类型的映射。索引是逻辑名称空间, 映射到一个或多个主分片,并且可以有零个或多个副本分片。 MySQL =>数据库 Elasticsearch =>索引
- 文档类似于关系数据库中的一行。不同之处在于索引中的每个文档可以具有不同的结构(字段) ,但是对于通用字段应该具有相同的数据类型。 MySQL => Databases => Tables => Columns / Rows Elasticsearch => Indices => Types =>具有属性的文档.
- 类型是索引的逻辑类别/分区,其语义完全取决于用户。
倒排索引是搜索引擎的核心。搜索引擎的主要目标是在查找发生搜索条件的文档时提供快速搜索。 ES 中的倒排索引其实就是 lucene 的倒排索引,区别于传统的正向索引, 倒排索引会再存储数据时将关键词和数据进行关联,保存到倒排表中,然后查询时,将查询内容进行分词后在倒排表中进行查询,最后匹配数 据即可。
- 同步调用:直接在代码里写逻辑,数据在增删改查进数据库的同时,也往es里同步一份
- 使用官方的
logstash
,定时查询数据库,查询到数据有变化就发送到es中- 利用数据库的binlog同步变化数据,然后将数据发送给es,当然也可以通过java代码监听拿到数据,再发送到es或做其他处理。
- MQ中间件,有数据变化的时候,就通知mq,然后监听mq实现数据同步到mq
用更多的索引。通过增加新的补充索引来反映新近的修改, 而不是直接重写整 个倒排索引。每一个倒排索引都会被轮流查询到,从最早的开始查询完后再对结果进行合并。
- 主要功能:
1)海量数据的分布式存储以及集群管理,达到了服务与数据的高可用以及水平扩展;
2)近实时搜索,性能卓越。对结构化、全文、地理位置等类型数据的处理;
3)海量数据的近实时分析(聚合功能)
- 应用场景:
1)网站搜索、垂直搜索、代码搜索;
2)日志管理与分析、安全指标监控、应用性能监控、Web抓取舆情分析